Skip to content

[#232] Bare agentgather launches the local dashboard#236

Merged
realproject7 merged 3 commits into
mainfrom
task/232-local-workspace-launcher
Jul 13, 2026
Merged

[#232] Bare agentgather launches the local dashboard#236
realproject7 merged 3 commits into
mainfrom
task/232-local-workspace-launcher

Conversation

@realproject7

Copy link
Copy Markdown
Owner

Closes #232
Parent: #77 — Agent Gather Platform Pivot. Depends on: none. Enables: #233, #234.

Bare agentgather becomes the everyday Local Workspace Launcher: it opens the device-local dashboard on 127.0.0.1:8788 and keeps a freshly started server foreground-owned until Ctrl-C. Advanced room/runtime operations stay explicit.

EPIC Alignment

  • This ticket's role (from Launcher UX: make bare agentgather open the local dashboard #232): establish the bare local CLI launcher contract that later dashboard/workspace work (Unified workspace left rail: single host-tagged boardroom list with 1/3-2/3 navigation split #233, Control plane: token-free channel metadata for hosted rooms #234) relies on. This PR does exactly that and nothing more.
  • Contracts respected/exposed: root agentgather owns only loopback dashboard launch/reuse; compatibility is GET /version ({ ok, name: "agentgather", version }); platform serve and explicit room/runtime commands keep their existing contracts (only two symbols were exported from the platform command — no behavior change).
  • Boundary documented (per @Head clarification): bare agentgather reuses/starts the localhost dashboard only. It never creates a room, token, tunnel, or remote listener; #233's dashboard browser-shell/left-rail UI is not implemented here. The reused/started page is the existing owner shell, unchanged.
  • Reuse-first: the launcher starts the existing createPlatformHttpServer and shares the platform command's DEFAULT_PLATFORM_PORT + defaultWaitForShutdown (exported, not duplicated) so both keep-alive paths behave identically.

Self-Verification

Diff re-read adversarially; kill-list scanned; acceptance criteria matched 1:1.

Acceptance criteria → evidence (tests in test/cli-launch.test.ts, test/browser-launch.test.ts):

  • Bare launch resolves the normal local home, opens the dashboard once, keeps it alive until Ctrl-C → bare launch starts the local dashboard, opens it once, and keeps it alive + e2e routes --no-open to a real, reachable dashboard that stops on SIGINT.
  • Never creates a room / writes a token / enables --allow-remote / starts a room/tunnel → token-free assertions across tests; launcher only calls createPlatformHttpServer({ root }) (localhost-only, allowInsecureRemote never set).
  • --no-open --port <port> starts without the opener and prints a stable token-free URL → launch --no-open ... without opening a browser.
  • Same-version dashboard already on the port is reused (open once, no second listener) → launch reuses a same-version dashboard already serving the port (asserts keep-alive loop not entered; original stays sole listener).
  • Occupied/incompatible port exits safely without killing it and explains --port recovery → launch refuses an occupied/incompatible port without touching that process (foreign server still answers after).
  • agentgather --help exits 0 with no browser/listener/network → help/version stay side-effect free (spawned CLI, exits 0, no hang).
  • platform serve behavior unchanged → full platform suite green.
  • Help surfaces the launcher + advanced room launch → asserted in the help test.
  • Browser-opener failure is non-fatal → launch treats a browser-opener failure as non-fatal.
  • Invalid --port rejected token-free → launch rejects an out-of-range or non-numeric --port.

Verification suite (all green): pnpm build, pnpm lint, pnpm typecheck, pnpm kit-guard (4 panes clean), pnpm no-stub.
Tests: 11 new #232 tests pass (10 CLI/launcher + 1 browser smoke). Full suite: 402 pass / 3 fail — the 3 failures (active-session, cli-brief-file, cli-room: "participant token is not allowed") reproduce identically on the clean base 9e15000 in a fresh worktree, so they are pre-existing/environmental and unrelated to this change.

Design Fidelity

Per #232 this changes interaction + CLI copy only; it does not redesign the dashboard visual system.

Element Spec Implemented
First usage line bare agentgather opens the local dashboard agentgather Open the local dashboard in your browser (Ctrl-C to stop)
Launcher flags documented --port / --no-open both in top-level help usage
Advanced op discoverable room launch [--detach] under advanced host runtime operations new "Advanced host runtime operations" help section
Opened dashboard existing owner shell, unchanged, at desktop width browser smoke renders .platform-shell[data-view="rooms"], v{VERSION}, no horizontal overflow at 1280
Shared shell no drift, no new panes src/browser/* untouched; kit-guard green

Security

Invariant (#232) How it holds Evidence
No tgl_ token / Bearer / invite/card URL / message body in output, errors, opener args, or tests Launcher only ever emits http://127.0.0.1:<port> + port numbers TOKEN_LEAK (/tgl_|Bearer|token=|#token=/i) asserted absent across launch/reuse/refuse tests
Only loopback URLs started/opened; never inherit a room public URL or dashboard query param URL is always http://127.0.0.1:${port} with no query/hash; server bound to 127.0.0.1; --allow-remote unreachable from this path code + platform serve remote-enforcement suite unchanged
No shell command interpolation for opening spawn(opener, [url], { shell:false }) — URL is a lone argv element (open/xdg-open/cmd /c start "" url) src/cli/commands/launch/index.ts opener
Foreign process never killed/inspected occupied/incompatible → refuse + exit 1; race on bind → refuse refuse test asserts foreign server still responds
No new runtime dependencies only node:* built-ins used package.json unchanged

Deviations

  • Started --no-open blocks until Ctrl-C (like a fresh platform serve): a started server must stay up to remain reachable. --no-open's "headless escape hatch" role is that it skips the browser opener (which would hang/fail headless), not that it detaches — only a reused dashboard exits 0 immediately. Documented and covered by the SIGINT e2e test.
  • --port accepts 1..65535 (not 0/ephemeral): an ephemeral port is not a meaningful target for an everyday launcher and can't be probed for reuse; matches the ticket's --port <1..65535>.
  • Out-of-scope working-tree files (AGENTS.md, CLAUDE.md, DESIGN-GUIDE.md, .gitignore) present before this task were left uncommitted.

Make bare `agentgather` open the device-local dashboard on 127.0.0.1:8788
and keep a freshly started server foreground-owned until Ctrl-C. Add root
flags --port <1..65535> and --no-open. Before binding, probe GET /version:
reuse a same-version dashboard (open once, exit 0, no second listener);
refuse an occupied/incompatible port without touching that process, pointing
at the --port recovery path; otherwise start our own dashboard. Browser
opening is shell-free (platform opener, URL as a lone argv element) and
non-fatal. Help/version stay side-effect free; top-level help surfaces the
bare launcher and advanced `room launch [--detach]`.

Loopback-only, token-free output; no room/tunnel/remote action; no new deps.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@project7-interns project7-interns left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: REQUEST CHANGES

Epic Alignment: FAIL

The #232 contract limits the root launcher to bare agentgather plus its two documented flags; the diff accepts additional root arguments after either launcher flag.

Checked (evidence)

  • Structural gate: PR body includes filled EPIC Alignment, Self-Verification, Design Fidelity, and Security sections.
  • Ticket/epic context: gh issue view 232; gh api repos//issues/77.
  • Root-routing and argument parsing: src/cli/index.ts:35; src/cli/commands/launch/index.ts:47.
  • Riskiest part of this diff: root CLI dispatch; it currently bypasses the side-effect-free help guarantee for trailing help flags.
  • Kill-list: scanned all items - unsupported root arguments are silently ignored.
  • CI: gh pr checks 236 -> Release gates pending.

Findings

  • [blocking] Launcher accepts unsupported root flags/positionals and may start the dashboard despite a trailing help flag.
    • File: src/cli/commands/launch/index.ts:47
    • Why it fails: parseArgs results are never validated. Thus agentgather --no-open --allow-remote and agentgather --port 8788 --help enter the launcher; the latter starts/probes/listens instead of keeping help side-effect free. This contradicts #232's exactly-bare root path and its help/version safety contract. The current help test covers only --help as argv[0].
    • Do instead: permit only --port <1..65535> and flag-only --no-open, reject any positional or unknown flag before probing, and short-circuit --help/-h anywhere in the root launcher argv. Add end-to-end tests for trailing help and an unsupported flag.

Decision

The implementation otherwise follows the local-only launcher direction, but this root-argument gap violates explicit scope and can create side effects for what should be a help invocation.

@realproject7

Copy link
Copy Markdown
Owner Author

RE2 — APPROVE ✅ (#232 bare local dashboard launcher)

Reviewed head 4e54a8f vs fresh origin/main 9e15000. Diff is CLI/platform/help + focused tests only — src/browser/*, #218/#233 UI, docs/release-notes all untouched. Scope matches the ticket exactly.

Checked (evidence)

  • Loopback + token-free: every emitted/opened URL is http://127.0.0.1:${port} with no query/hash, never a room public URL or dashboard param (launch/index.ts url + finishReachable). --allow-remote is unreachable — createPlatformHttpServer binds 127.0.0.1 and 403s insecure_remote. TOKEN_LEAK regex asserted absent across stdout+stderr in every test (cli-launch.test.ts:16,71,93,126,160).
  • No foreign-process kill/inspect: incompatible/busy → refuse (exit 1) naming the port + agentgather --port <another-port>, never a kill/inspect; also covers the probe→bind race (listen error → same refuse). Test asserts the foreign server still answers 200 after refusal (cli-launch.test.ts:159).
  • Same-version reuse: /version probe requires ok===true && name==="agentgather" && version===VERSION (matches http.ts:104 byte-for-byte) → open once, exit 0, no second listener (keptAlive===false asserted, :121).
  • Shell-free opener: spawn(file, args) (shell defaults false) with the URL as a lone argv element; opener failure resolves false (non-fatal, exit 0). URL is fully constrained to http://127.0.0.1:<validated int>, so even the win32 cmd /c start path carries no injectable content.
  • --port validation: integer 1..65535; missing value / non-numeric / out-of-range → exit 1 with between 1 and 65535 (:192).
  • Side-effect-free help/version: --help/-h/--version short-circuit above launcher routing in cli/index.ts; platform serve behavior byte-identical (only DEFAULT_PLATFORM_PORT + defaultWaitForShutdown newly exported, no logic change).
  • No new deps: node:* built-ins only; package.json unchanged.
  • Help UX: first usage line is bare agentgather, documents --port/--no-open, exposes room launch [--detach] under Advanced host runtime operations.

Independently verified (fresh worktree @ 4e54a8f)

typecheck clean · build ok · 9 launcher test cases pass / 0 fail · lint green · kit-guard 4 panes clean · no-stub green · CI Release gates pass (run 29235109626). The 3 full-suite failures (active-session/cli-brief-file/cli-room "participant token is not allowed") touch files this diff never modifies — no causal link; pre-existing.

Non-blocking notes (no change required)

  • The incompatible copy says "another server" even for a same-name/different-version dashboard; ticket only requires a token-free message naming the port + recovery path, which it does.
  • Started --no-open blocks until Ctrl-C (a served dashboard must stay up) — documented and @head-confirmed; only a reused dashboard exits 0.

No blocking findings — APPROVE. (Formal GitHub approval blocked by shared-identity self-PR; this comment is the verdict of record.)

…anywhere

The root launcher now rejects any argument outside its contract before
probing or binding: no positionals, no flags other than --port/--no-open,
and --no-open must be value-less. --help/-h and --version/-v short-circuit
anywhere on the launcher line (e.g. `agentgather --port 8788 --help`),
printing help/version with no probe, listener, or browser open. Adds
in-process + E2E coverage (help-after-port leaves no listener; out-of-contract
args never probe/listen).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@project7-interns project7-interns left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: REQUEST CHANGES

Epic Alignment: PASS

The re-review delta preserves #232's local-only launcher contract and resolves the prior root-argument safety finding.

Checked (evidence)

  • Delta reviewed: 4e54a8f..0c2f4e4 via gh api repos/realproject7/agentgather/compare/4e54a8f46082689b99c58ebda18d73599a6aaed4...0c2f4e4.
  • Prior finding resolved: help/version short-circuit before probe/bind; unsupported flags and positionals reject before side effects.
  • Regression coverage: test/cli-launch.test.ts:197 and :222, including E2E trailing-help behavior at :240.
  • Riskiest part of this diff: root argument validation; its behavior now matches the ticket, but the added wrapper conflicts with the code-quality rule below.
  • Kill-list: scanned new ranges - one single-call-site abstraction.
  • CI: gh pr checks 236 -> Release gates pending.

Findings

  • [blocking] New one-call-site validation helper must be inlined.
    • File: src/cli/commands/launch/index.ts:136
    • Why it fails: rejectUnsupportedArgs has exactly one caller (runLaunchCommand at :62). The project kill-list requires a new helper with one call site to be inlined; this function is not a shared boundary or independently reused abstraction.
    • Do instead: move the positional/flag validation loop directly into runLaunchCommand before resolvePort, retaining the current tests and error text.

Decision

The original argument-handling defect is fixed. Inline the single-use validator, then I can re-review that narrow delta; CI must also finish green before approval.

@realproject7

Copy link
Copy Markdown
Owner Author

RE2 — APPROVE ✅ (re-review on 0c2f4e4)

Re-reviewed the delta 4e54a8f0c2f4e4 addressing the @re1 finding. Confined to src/cli/commands/launch/index.ts (arg-validation + help/version guard) + test/cli-launch.test.ts (3 new tests). The reuse/refuse/start behavior, opener, URL construction, and security surface are unchanged — this is a pure entry-guard hardening.

Checked (delta)

  • Help/version win anywhere, side-effect free: argv.includes("--help"|"-h") → help + exit 0, --version"|"-v" → version + exit 0, evaluated before parse/probe/bind. So agentgather --port 8788 --help prints help with no probe, no listener, no browser (E2E test asserts no listener on the port afterward).
  • Exact arg contract enforced up front: new rejectUnsupportedArgs rejects any positional, any flag other than --port/--no-open, and a valued --no-open, returning a token-free usage line with exit 1 before the probe — a typo can no longer silently start a server with ignored args.
  • --no-open parse: switched from flagBoolean to args.flags.get("no-open") === true, and the valued---no-open case is caught by the validator — behavior consistent.
  • No security regression: no change to the /version probe, the shell-free opener, or the loopback URL; token-free messaging preserved.

Independently verified (fresh worktree @ 0c2f4e4)

typecheck clean · build ok · 12 launcher cases pass / 0 fail (incl. the 3 new: unsupported-arg rejection, --help/-h-anywhere short-circuit, --help-after---port no-listener) · lint green · kit-guard 4 panes clean · no-stub green. CI Release gates re-running on 0c2f4e4 (run 29235483474) — needs to settle green for @Head's gate; the 4e54a8f run was already green and this delta is CLI-only.

My 4e54a8f security verdict carries unchanged. No blocking findings — APPROVE. (Formal GitHub approve blocked by shared-identity self-PR; this comment is the verdict of record.)

Fold the single-caller rejectUnsupportedArgs helper directly into
runLaunchCommand; error text and behavior are byte-identical, so the
arg-contract and help-anywhere tests are unchanged and still pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@realproject7

Copy link
Copy Markdown
Owner Author

RE2 — APPROVE ✅ (re-review on 334ca2c)

Re-reviewed the delta 0c2f4e4334ca2c: rejectUnsupportedArgs inlined into runLaunchCommand, single-caller helper removed (0 refs). src/cli/commands/launch/index.ts only.

Behavior-neutral confirmed: the inline conditions are logically identical to the helper — positional.length > 0 || flags have a key other than port/no-open → same usage line; no-open present with a non-true value → same "takes no value" line. Error text byte-identical; check order preserves the same outcome for every input (incl. --no-open oops, --bogus, stray positional). No behavior or security change.

Verified fresh @ 334ca2c: typecheck clean · build ok · 12 launcher cases pass / 0 fail. My 0c2f4e4/4e54a8f verdicts carry unchanged. No findings — APPROVE. (Formal GitHub approve blocked by shared-identity self-PR; this comment is the verdict of record.)

@project7-interns project7-interns left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: APPROVE

Epic Alignment: PASS

The reviewed launcher delta preserves #232's loopback-only, explicit-runtime contract and corrects the root argument boundary without touching #233 browser UI.

Checked (evidence)

  • Re-review range: 0c2f4e4..334ca2c.
  • Prior findings resolved: validation is inline in src/cli/commands/launch/index.ts:61; the single-use helper is removed.
  • Exact root contract: positional/unknown flags and valued --no-open reject before probe/bind at src/cli/commands/launch/index.ts:61.
  • Help/version remain side-effect-free before parsing/probing at src/cli/commands/launch/index.ts:47; regression coverage remains in test/cli-launch.test.ts:197 and :222.
  • Riskiest part of this diff: root dispatch validation; the inline guard preserves the tested behavior while preventing ignored arguments from starting a listener.
  • Kill-list: scanned the new range - clean.
  • CI: gh pr checks 236 -> Release gates pass (2m29s, run 29235879879).

Findings

  • None.

Decision

All prior RE1 findings are resolved on live head 334ca2c. The delta is behavior-preserving aside from enforcing the specified root argument contract.

@realproject7
realproject7 merged commit ea85cbe into main Jul 13, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Launcher UX: make bare agentgather open the local dashboard

2 participants