Files
gstack/test/open-gstack-browser-liveness.test.ts
T
garrytan 9cfc76d7b7 W4: /open-gstack-browser asks before replacing a live browse daemon
Step 0 probes with BROWSE_NO_AUTOSTART=1 $B status (no daemon is booted by
the probe). No live daemon: clear Chromium profile locks and run plain
$B connect, which reaps orphans and refuses to replace a busy daemon. Already
headed: connect reports it. Live headless daemon: AskUserQuestion, and only
an explicit yes runs $B connect --force-restart; spawned or headless sessions
leave it running and print the stop line with the resolved browse binary.
The hand-rolled kill / kill -9 of the recorded PID and the browse.json
deletion are gone. Supersedes s10 M13.

Tests: new open-gstack-browser-liveness.test.ts (no kill of the recorded PID,
status probe before connect, --force-restart only after the yes branch,
profile-lock cleanup kept, spawned/headless stop line). The state-extraction
tripwire now covers only the port snippet, since the pid snippet is gone.
2026-10-03 00:08:46 +00:00

69 lines
3.2 KiB
TypeScript

/**
* /open-gstack-browser must not kill a live browse daemon on its own.
*
* Step 0 probes with `$B status` (without auto-starting a daemon). No live
* daemon → plain `$B connect`, which reaps orphans and refuses to replace a
* busy daemon itself. A live daemon → AskUserQuestion, and only an explicit
* yes runs `$B connect --force-restart`. Spawned or headless sessions leave it
* running and print how to replace it. The skill never kills the PID recorded
* in .gstack/browse.json by hand.
*/
import { describe, expect, test } from 'bun:test';
import * as fs from 'fs';
import * as path from 'path';
const ROOT = path.resolve(import.meta.dir, '..');
const TMPL = fs.readFileSync(path.join(ROOT, 'open-gstack-browser', 'SKILL.md.tmpl'), 'utf-8');
// Printed with the resolved browse binary path in place of %s.
const STOP_LINE = 'Live browse daemon left running. Run %s stop, then re-run /open-gstack-browser to replace it.';
const section = (from: string, to: string) => {
const start = TMPL.indexOf(from);
const end = TMPL.indexOf(to, start + from.length);
expect(start).toBeGreaterThanOrEqual(0);
expect(end).toBeGreaterThan(start);
return TMPL.slice(start, end);
};
describe('/open-gstack-browser daemon liveness', () => {
test('no hand-rolled kill of the recorded daemon PID', () => {
expect(TMPL).not.toMatch(/\bkill\b[^\n]*(_OLD_PID|\$\w*PID)/);
expect(TMPL).not.toMatch(/kill -9/);
expect(TMPL).not.toMatch(/grep -o '"pid":/);
expect(TMPL).not.toMatch(/rm -f[^\n]*browse\.json/);
});
test('Step 0 probes with $B status without auto-starting a daemon, before any connect', () => {
const probeAt = TMPL.indexOf('BROWSE_NO_AUTOSTART=1 $B status');
expect(probeAt).toBeGreaterThanOrEqual(0);
expect(probeAt).toBeLessThan(TMPL.indexOf('$B connect'));
});
test('--force-restart appears only in the explicit-yes branch', () => {
const live = section('`DAEMON: live`', '## Step 2');
const ask = live.indexOf('AskUserQuestion');
expect(ask).toBeGreaterThanOrEqual(0);
const occurrences = [...TMPL.matchAll(/--force-restart/g)].map((m) => m.index!);
expect(occurrences.length).toBeGreaterThan(0);
const liveStart = TMPL.indexOf('`DAEMON: live`');
const askAbs = liveStart + ask;
for (const at of occurrences) expect(at).toBeGreaterThan(askAbs);
expect(live).toMatch(/only an explicit a runs[^\n]*--force-restart/i);
expect(TMPL).toMatch(/after an explicit a[^\n]*\n+```bash\n\$B connect --force-restart\n```/i);
});
test('the no-daemon branch runs plain connect and keeps the Chromium profile-lock cleanup', () => {
const none = section('`DAEMON: none`', '`DAEMON: headed`');
expect(none).toContain('plain `$B connect`');
expect(none).toContain('SingletonLock SingletonSocket SingletonCookie');
expect(none).not.toContain('--force-restart');
});
test('spawned or headless sessions leave the daemon running and print the resolved-binary stop line', () => {
const live = section('`DAEMON: live`', '## Step 1');
expect(live).toMatch(/spawned[\s\S]{0,40}headless[\s\S]{0,40}do not ask/i);
expect(live).toContain(`printf '${STOP_LINE}\\n' "$B"`);
expect(live.indexOf(STOP_LINE)).toBeLessThan(live.indexOf('AskUserQuestion'));
});
});