fix(pair-agent): consent before killing a healthy headless daemon

The pair-agent headed switch spawned 'connect --force-restart'
unconditionally — auto-killing a live headless daemon (open tabs, cookies,
logins) in direct contradiction of the iron rule it sits beside ('only an
explicit --force-restart may kill a live daemon'). The CLI now captures
daemon liveness BEFORE ensureServer (which can itself boot a fresh daemon)
and relaunches only when the user passed --force-restart to pair-agent;
otherwise it prints the tab count and continues against the existing daemon.
The /pair-agent skill gains a matching one-way-door consent question
(template half rides the wave's template block).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-08-17 10:43:54 -07:00
co-authored by Claude Fable 5
parent 565ca9b13b
commit 6955dfa348
3 changed files with 139 additions and 21 deletions
+37
View File
@@ -122,3 +122,40 @@ describe('gate wiring — every tunnel activation point consults the guard', ()
expect(SERVER_SRC).toContain("process.env.BROWSE_TUNNEL === '1' && !isPairAgentEnabled()");
});
});
describe('pair-agent headed switch — #2219 iron-rule consent gate', () => {
// The behavioral leg (live daemon + no flag → notice, no kill) lives in
// busy-daemon-iron-rule.test.ts. These source pins cover the wiring the
// integration test can't exercise cheaply: the explicit-flag path and the
// capture-before-ensureServer ordering.
test('headed relaunch of a pre-existing live daemon is gated on explicit --force-restart', () => {
const gateAt = CLI_SRC.indexOf('if (pairAgentPreexistingDaemonAlive && !globalFlags.forceRestart) {');
expect(gateAt).toBeGreaterThan(-1);
// Refusal branch: notice printed, connect never spawned.
const elseAt = CLI_SRC.indexOf('} else {', gateAt);
expect(elseAt).toBeGreaterThan(gateAt);
const refusalBranch = CLI_SRC.slice(gateAt, elseAt);
expect(refusalBranch).toContain('continuing against it');
expect(refusalBranch).toContain('--force-restart to relaunch headed');
expect(refusalBranch).not.toContain('Bun.spawn');
// Consented branch (no pre-existing daemon OR explicit flag): the spawn
// of `connect --force-restart` lives here and ONLY here.
const branchEnd = CLI_SRC.indexOf('await handlePairAgent(state, commandArgs);', gateAt);
expect(branchEnd).toBeGreaterThan(elseAt);
const consentedBranch = CLI_SRC.slice(elseAt, branchEnd);
expect(consentedBranch).toContain("Bun.spawn([browseBin, 'connect', '--force-restart']");
// No second spawn site outside the gated block.
expect(CLI_SRC.indexOf("'connect', '--force-restart'")).toBe(CLI_SRC.lastIndexOf("'connect', '--force-restart'"));
});
test('pre-existing liveness is captured BEFORE ensureServer can boot a fresh daemon', () => {
// If the capture ran after ensureServer, a freshly-booted daemon would be
// indistinguishable from a session the user cares about — the gate would
// then refuse the headed switch even on a clean machine.
const captureAt = CLI_SRC.indexOf('pairAgentPreexistingDaemonAlive = Boolean(preState?.pid && isProcessAlive(preState.pid));');
const ensureAt = CLI_SRC.indexOf('let state = await ensureServer(globalFlags);');
expect(captureAt).toBeGreaterThan(-1);
expect(ensureAt).toBeGreaterThan(captureAt);
});
});