mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-23 05:10:50 +02:00
Merge remote-tracking branch 'origin/main' into garrytan/gbrain-code-smell-audit
# Conflicts: # CHANGELOG.md # browse/test/dual-listener.test.ts # browse/test/fixtures/security-bench-haiku-responses.json # browse/test/sidebar-tabs.test.ts # browse/test/sidebar-ux.test.ts # browse/test/terminal-agent.test.ts # claude/SKILL.md.tmpl # scripts/gen-skill-docs.ts # scripts/proactive-suggestions.json # spec/SKILL.md # test/gen-skill-docs.test.ts # test/host-config.test.ts
This commit is contained in:
@@ -129,21 +129,26 @@ describe('Source-level guard: terminal-agent', () => {
|
||||
expect(wsHandler).toContain('validTokens.has');
|
||||
});
|
||||
|
||||
test('Sec-WebSocket-Protocol auth: strips gstack-pty. prefix and echoes back', () => {
|
||||
test('Sec-WebSocket-Protocol auth: strips gstack-pty. prefix, no manual echo', () => {
|
||||
const wsHandler = AGENT_SRC.slice(AGENT_SRC.indexOf("if (url.pathname === '/ws')"));
|
||||
// Browsers send `Sec-WebSocket-Protocol: gstack-pty.<token>`. The agent
|
||||
// must strip the prefix before checking validTokens, AND echo the
|
||||
// protocol back in the upgrade response — without the echo, the
|
||||
// browser closes the connection immediately.
|
||||
// must strip the prefix before checking validTokens. The protocol echo
|
||||
// is Bun's job: Bun >= 1.3 auto-echoes the first offered protocol in the
|
||||
// 101 response. A manual echo on top produced a DUPLICATE
|
||||
// Sec-WebSocket-Protocol header, which strict clients (Chromium, python
|
||||
// websockets) reject per RFC 6455 — the sidebar terminal could never
|
||||
// connect. Pin the invariant: no manual echo in the upgrade call.
|
||||
expect(wsHandler).toContain("'gstack-pty.'");
|
||||
expect(wsHandler).toContain('Sec-WebSocket-Protocol');
|
||||
expect(wsHandler).toContain('acceptedProtocol');
|
||||
expect(wsHandler).toContain('sec-websocket-protocol');
|
||||
expect(wsHandler).not.toContain("headers: { 'Sec-WebSocket-Protocol'");
|
||||
});
|
||||
|
||||
test('lazy spawn: claude PTY is spawned in message handler, not on upgrade', () => {
|
||||
// The whole point of lazy-spawn (codex finding #8) is that the WS
|
||||
// upgrade itself does NOT call spawnClaude. Spawn happens on first
|
||||
// message frame.
|
||||
// upgrade itself does NOT spawn claude. Spawn happens on first
|
||||
// message frame (binary input or the v1.44 explicit `start` frame),
|
||||
// routed through the maybeSpawnPty helper, which is the only caller
|
||||
// of spawnClaude.
|
||||
const upgradeBlock = AGENT_SRC.slice(
|
||||
AGENT_SRC.indexOf("if (url.pathname === '/ws')"),
|
||||
AGENT_SRC.indexOf("websocket: {"),
|
||||
@@ -151,11 +156,27 @@ describe('Source-level guard: terminal-agent', () => {
|
||||
// v1.44 renamed spawnClaude -> maybeSpawnPty (explicit `start` frame +
|
||||
// lazy first-byte spawn share one helper). Pin was stale from then until
|
||||
// the free suite got a CI job.
|
||||
expect(upgradeBlock).not.toContain('spawnClaude(');
|
||||
expect(upgradeBlock).not.toContain('maybeSpawnPty(');
|
||||
// Spawn must be invoked from the message handler (lazy on first byte).
|
||||
// v1.44 routes both spawn triggers (explicit {type:"start"} text frame
|
||||
// and the lazy binary-frame path) through the maybeSpawnPty helper.
|
||||
const messageHandler = AGENT_SRC.slice(AGENT_SRC.indexOf('message(ws, raw)'));
|
||||
expect(messageHandler).toContain('maybeSpawnPty(');
|
||||
expect(messageHandler).toContain('!session.spawned');
|
||||
// The open() upgrade handler must not spawn — it only creates the
|
||||
// (spawned: false) session record or re-attaches a detached one.
|
||||
const openBlock = AGENT_SRC.slice(
|
||||
AGENT_SRC.indexOf('open(ws)'),
|
||||
AGENT_SRC.indexOf('message(ws, raw)'),
|
||||
);
|
||||
expect(openBlock).not.toContain('spawnClaude(');
|
||||
expect(openBlock).not.toContain('maybeSpawnPty(');
|
||||
// And the helper itself is where spawnClaude actually happens, gated
|
||||
// on session.spawned so it stays a single-shot lazy spawn.
|
||||
const helperBlock = AGENT_SRC.slice(AGENT_SRC.indexOf('function maybeSpawnPty'));
|
||||
expect(helperBlock).toContain('spawnClaude(');
|
||||
expect(helperBlock).toContain('if (session.spawned) return true;');
|
||||
});
|
||||
|
||||
test('process.on uncaughtException + unhandledRejection handlers exist', () => {
|
||||
|
||||
Reference in New Issue
Block a user