mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-19 02:42:29 +08:00
feat: live tab awareness for the Terminal pane
claude in the PTY now has continuous tab-aware context. Three pieces:
1. Live state files. background.js listens to chrome.tabs.onActivated /
onCreated / onRemoved / onUpdated (throttled to URL/title/status==
complete so loading spinners don't spam) and pushes a snapshot. The
sidepanel relays it as a custom event; sidepanel-terminal.js sends
{type:"tabState"} text frames over the live PTY WebSocket.
terminal-agent.ts writes:
<stateDir>/tabs.json all open tabs (id, url, title, active,
pinned, audible, windowId)
<stateDir>/active-tab.json current active tab (skips chrome:// and
chrome-extension:// internal pages)
Atomic write via tmp + rename so claude never reads a half-written
document. A fresh snapshot is pushed on WS open so the files exist by
the time claude finishes booting.
2. New $B tab-each <command> [args...] meta-command. Fans out a single
command across every open tab, returns
{command, args, total, results: [{tabId, url, title, status, output}]}.
Skips chrome:// pages; restores the originally active tab in a finally
block (so a mid-batch error doesn't leave the user looking at a
different tab); uses bringToFront: false so the OS window doesn't
jump on every fanout. Scope-checks the inner command BEFORE the loop.
3. --append-system-prompt hint at spawn time. Claude is told about both
the state files and the $B tab-each command up front, so it doesn't
have to discover the surface by trial. Passed via the --append-system-
prompt CLI flag, NOT as a leading PTY write — the hint stays out of
the visible transcript.
Tests:
- browse/test/tab-each.test.ts (new) — registration + source-level
invariants (scope check before loop, finally-restore, bringToFront:false,
chrome:// skip) + behavior tests with a mock BrowserManager that verify
iteration order, JSON shape, error handling, and active-tab restore.
- browse/test/terminal-agent.test.ts — three new assertions for
tabState handler shape, atomic-write pattern, and the
--append-system-prompt wiring at spawn.
Verified live: opened 5 tabs, ran $B tab-each url against the live
server, got per-tab JSON results back, original active tab restored
without OS focus stealing.
This commit is contained in:
@@ -169,6 +169,38 @@ describe('Source-level guard: terminal-agent', () => {
|
||||
expect(dispose).toContain("'SIGKILL'");
|
||||
expect(dispose).toContain('3000');
|
||||
});
|
||||
|
||||
test('tabState frames write tabs.json + active-tab.json', () => {
|
||||
expect(AGENT_SRC).toContain("msg?.type === 'tabState'");
|
||||
expect(AGENT_SRC).toContain('function handleTabState');
|
||||
const fn = AGENT_SRC.slice(AGENT_SRC.indexOf('function handleTabState'));
|
||||
// Atomic write via tmp + rename for both files (so claude never reads
|
||||
// a half-written JSON document).
|
||||
expect(fn).toContain("'tabs.json'");
|
||||
expect(fn).toContain("'active-tab.json'");
|
||||
expect(fn).toContain('renameSync');
|
||||
// Skip chrome:// and chrome-extension:// pages — they're not useful
|
||||
// targets for browse commands.
|
||||
expect(fn).toContain("startsWith('chrome://')");
|
||||
expect(fn).toContain("startsWith('chrome-extension://')");
|
||||
});
|
||||
|
||||
test('claude is spawned with --append-system-prompt tab-awareness hint', () => {
|
||||
expect(AGENT_SRC).toContain('function buildTabAwarenessHint');
|
||||
const hint = AGENT_SRC.slice(AGENT_SRC.indexOf('function buildTabAwarenessHint'));
|
||||
// The hint must mention the live state files and the fanout command —
|
||||
// those are the two affordances that distinguish a gstack-PTY claude
|
||||
// from a plain `claude` session.
|
||||
expect(hint).toContain('tabs.json');
|
||||
expect(hint).toContain('active-tab.json');
|
||||
expect(hint).toContain('tab-each');
|
||||
// And it must be passed via --append-system-prompt at spawn time
|
||||
// (NOT written into the PTY as user input — that would pollute the
|
||||
// visible transcript).
|
||||
const spawn = AGENT_SRC.slice(AGENT_SRC.indexOf('function spawnClaude'));
|
||||
expect(spawn).toContain("'--append-system-prompt'");
|
||||
expect(spawn).toContain('tabHint');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Source-level guard: server.ts /pty-session route', () => {
|
||||
|
||||
Reference in New Issue
Block a user