gstack/browse
Garry Tan 449144cda5
feat(terminal-agent): sessionId-aware grant + scoped restart + eager spawn
Wires the pty-session-lease primitive (3aada48b) into terminal-agent so
the Commit 2 work in server.ts (next commit) can route /pty-restart and
re-attach by session identity rather than by single-use token.

Changes:

  * validTokens: Set<string> → Map<string, string|null>. Each grant carries
    its bound sessionId (or null for legacy single-grant callers). On WS
    upgrade, the agent surfaces the bound sessionId via ws.data so open()
    can register the session in the new reverse index.
  * sessionsById: Map<sessionId, PtySession> — populated in open(),
    cleared in close(). Required so /internal/restart can find and dispose
    one specific session by id rather than enumerating all live sessions.
  * /internal/restart: scoped to one sessionId. Codex T2 of the eng review
    caught the gap — pre-spec the route would have disposed every PTY on
    the agent, breaking pair-agent and any future multi-sidebar setup.
    The body now requires `{sessionId}`; missing or unknown id returns
    `{killed: 0}` and leaves siblings alone.
  * maybeSpawnPty(ws, session): hoisted from the inline binary-frame spawn
    block so both the legacy "spawn on first keystroke" trigger AND the
    new `{type:"start"}` text-frame trigger land in the same code path.
    Idempotent on session.spawned.
  * `{type:"start"}` text frame: explicit spawn trigger. forceRestart
    (extension side, lands in Commit 2C) sends this immediately on every
    fresh WS so claude boots without requiring a keystroke. Pre-v1.44 the
    lazy-binary-spawn pattern made the restart feel stuck.
  * close(ws): drops the sessionsById entry alongside the existing
    sessions WeakMap + validTokens cleanup. Commit 3 will revisit this to
    keep the session alive for a 60s detach window before disposing.

Test (browse/test/terminal-agent-session-routing.test.ts):
  * 8 static-grep tripwires pinning the load-bearing properties: validTokens
    is a Map (not Set), sessionsById exists, /internal/restart is scoped
    (negative-assert against enumerate-all patterns), WS upgrade plumbs
    sessionId, maybeSpawnPty is the single spawn entry, close() drops the
    index. Live spawn cycles belong in the e2e tier.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 23:15:49 -07:00
..
bin feat: multi-agent support — gstack works on Codex, Gemini CLI, and Cursor (v0.9.0) (#226) 2026-03-19 18:20:50 -07:00
scripts fix: ngrok Windows build + close CI error-swallowing gap (v0.18.0.1) (#1024) 2026-04-16 13:49:04 -07:00
src feat(terminal-agent): sessionId-aware grant + scoped restart + eager spawn 2026-05-23 23:15:49 -07:00
test feat(terminal-agent): sessionId-aware grant + scoped restart + eager spawn 2026-05-23 23:15:49 -07:00
PLAN-snapshot-dropdown-interactive.md fix: snapshot -i auto-detects dropdown/popover interactive elements (#845) 2026-04-05 22:57:45 -07:00
SKILL.md v1.39.1.0 feat: EXIT PLAN MODE GATE for plan-mode review skills (#1512) 2026-05-15 08:13:20 -07:00
SKILL.md.tmpl v1.28.0.0 feat: browse --headed/--proxy/--navigate + gstack/llms.txt + webdriver-only stealth (#1363) 2026-05-07 20:14:59 -07:00