feat: connect-chrome pre-flight cleanup + improved onboarding docs

Adds Step 0 pre-flight cleanup that kills stale browse servers and cleans
Chromium profile locks before connecting. Improves the onboarding flow with
clearer instructions for finding the extension, opening the Side Panel, and
troubleshooting connection issues. Fixes Mode check from cdp to headed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-03-26 18:46:09 -06:00
parent e348957adf
commit 84b6f354be
4 changed files with 371 additions and 141 deletions

View File

@@ -511,8 +511,27 @@ Refs: After 'snapshot', use @e1, @e2... as selectors:
}
}
// Clean up Chromium profile locks (can persist after crashes)
// Kill orphaned Chromium processes that may still hold the profile lock.
// The server PID is the Bun process; Chromium is a child that can outlive it
// if the server is killed abruptly (SIGKILL, crash, manual rm of state file).
const profileDir = path.join(process.env.HOME || '/tmp', '.gstack', 'chromium-profile');
try {
const singletonLock = path.join(profileDir, 'SingletonLock');
const lockTarget = fs.readlinkSync(singletonLock); // e.g. "hostname-12345"
const orphanPid = parseInt(lockTarget.split('-').pop() || '', 10);
if (orphanPid && isProcessAlive(orphanPid)) {
try { process.kill(orphanPid, 'SIGTERM'); } catch {}
await new Promise(resolve => setTimeout(resolve, 1000));
if (isProcessAlive(orphanPid)) {
try { process.kill(orphanPid, 'SIGKILL'); } catch {}
await new Promise(resolve => setTimeout(resolve, 500));
}
}
} catch {
// No lock symlink or not readable — nothing to kill
}
// Clean up Chromium profile locks (can persist after crashes)
for (const lockFile of ['SingletonLock', 'SingletonSocket', 'SingletonCookie']) {
try { fs.unlinkSync(path.join(profileDir, lockFile)); } catch {}
}
@@ -545,17 +564,30 @@ Refs: After 'snapshot', use @e1, @e2... as selectors:
console.log(`Connected to real Chrome\n${status}`);
// Auto-start sidebar agent
const agentScript = path.resolve(__dirname, 'sidebar-agent.ts');
// __dirname is inside $bunfs in compiled binaries — resolve from execPath instead
let agentScript = path.resolve(__dirname, 'sidebar-agent.ts');
if (!fs.existsSync(agentScript)) {
agentScript = path.resolve(path.dirname(process.execPath), '..', 'src', 'sidebar-agent.ts');
}
try {
if (!fs.existsSync(agentScript)) {
throw new Error(`sidebar-agent.ts not found at ${agentScript}`);
}
// Clear old agent queue
const agentQueue = path.join(process.env.HOME || '/tmp', '.gstack', 'sidebar-agent-queue.jsonl');
try { fs.writeFileSync(agentQueue, ''); } catch {}
// Resolve browse binary path the same way — execPath-relative
let browseBin = path.resolve(__dirname, '..', 'dist', 'browse');
if (!fs.existsSync(browseBin)) {
browseBin = process.execPath; // the compiled binary itself
}
const agentProc = Bun.spawn(['bun', 'run', agentScript], {
cwd: config.projectDir,
env: {
...process.env,
BROWSE_BIN: path.resolve(__dirname, '..', 'dist', 'browse'),
BROWSE_BIN: browseBin,
BROWSE_STATE_FILE: config.stateFile,
BROWSE_SERVER_PORT: String(newState.port),
},