v1.42.1.0 feat: gate terminal-agent teardown on ServerConfig.ownsTerminalAgent (unblocks gbrowser embedder) (#1615)

* feat: gate terminal-agent teardown on ServerConfig.ownsTerminalAgent

Adds ownsTerminalAgent?: boolean to ServerConfig (default true). Wraps the
three shutdown side effects (pkill -f terminal-agent\.ts + 2 safeUnlinkQuiet
calls for terminal-port and terminal-internal-token) inside a single
if (ownsTerminalAgent) block. Embedders (gbrowser phoenix overlay) pass
false to keep their own PTY lifecycle intact across gstack's teardown.

CLI start() call site passes ownsTerminalAgent: true explicitly; static-grep
test in the new test file catches a refactor that drops it.

Strict opt-out: only explicit false flips the gate (cfg.ownsTerminalAgent
=== false ? false : true). Defends against JS callers passing truthy non-bool
values.

Adds __resetShuttingDown test-only export mirroring __resetRegistry. The
module-scoped isShuttingDown latch otherwise silently no-ops a second
shutdown() in the same process.

Drops dead try/catch wrappers around safeUnlinkQuiet inside the new gate —
safeUnlinkQuiet already swallows all errors internally.

New test file (4 cases) stubs both process.exit AND child_process.spawnSync
so a real pkill -f terminal-agent\.ts never fires on the developer machine.
beforeAll/afterAll save and restore real-daemon file contents in the state
dir so the test cannot clobber a running gstack session.

* chore: file followup TODOs (identity-based pkill, cfg.config composition gap, ownership-object trigger)

Three P3 followups surfaced by /autoplan + /plan-eng-review while reviewing
the ownsTerminalAgent gate:

- Identity-based terminal-agent kill: pkill -f terminal-agent\.ts is a latent
  CLI footgun (regex match kills sibling gstack sessions, editor processes,
  etc.). Replace with PID-tracked process.kill at both cli.ts:1047 and
  server.ts:1281.

- shutdown() reads module-level config, not cfg.config (pre-existing
  composition gap). Same gap applies to cleanSingletonLocks(resolveChromiumProfile())
  at server.ts:1298 (should be cfg.chromiumProfile). Both are followup work
  for the embedder-composition story.

- 4th caller-owned teardown gate trigger: today ServerConfig has 3 (xvfb?,
  proxyBridge?, ownsTerminalAgent). If a 4th appears, collapse to
  cfg.callerOwns?: Set<...> ownership object.

* chore: bump version and changelog (v1.42.1.0)

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

* docs: note ServerConfig.ownsTerminalAgent in CLAUDE.md sidebar block

Adds a one-paragraph reference for the v1.42.1.0 embedder teardown gate
right after the Sidebar architecture block. Covers default semantics,
when embedders must pass `false`, polarity inversion vs xvfb?/proxyBridge?,
and the static-grep CI test that pins the CLI call site.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-05-20 08:41:29 -07:00
committed by GitHub
parent 7ca04d8ef0
commit b03cd1ae2d
7 changed files with 407 additions and 11 deletions

View File

@@ -236,6 +236,20 @@ Activity / Refs / Inspector as debug overlays behind the footer's
flow, dual-token model, and threat-model boundary — silent failures
here usually trace to not understanding the cross-component flow.
**Embedder terminal-agent ownership** (v1.42.1.0+). `buildFetchHandler`
in `browse/src/server.ts` accepts `ServerConfig.ownsTerminalAgent?:
boolean` (default `true`). When `true`, factory shutdown runs the full
teardown: `pkill -f terminal-agent\.ts` plus `safeUnlinkQuiet` on
`<stateDir>/terminal-port` and `<stateDir>/terminal-internal-token`.
Embedders (e.g. the gbrowser phoenix overlay) that pre-launch their
own PTY server must pass `false` so their discovery files survive
gstack teardown cycles. The flag is the third caller-owned teardown
gate in `ServerConfig` (alongside `xvfb?` and `proxyBridge?`); polarity
is inverted (explicit bool vs presence) and documented in the field's
JSDoc. CLI `start()` always passes `true` explicitly — the static-grep
test in `browse/test/server-embedder-terminal-port.test.ts` fails CI
if a refactor drops it.
**WebSocket auth uses Sec-WebSocket-Protocol, not cookies.** Browsers
can't set `Authorization` on a WebSocket upgrade, but they CAN set
`Sec-WebSocket-Protocol` via `new WebSocket(url, [token])`. The agent