diff --git a/browse/src/browser-manager.ts b/browse/src/browser-manager.ts index a8659701..d7eb6426 100644 --- a/browse/src/browser-manager.ts +++ b/browse/src/browser-manager.ts @@ -242,12 +242,13 @@ export class BrowserManager { await this.newTab(); } - // Browser disconnect handler + // Browser disconnect handler — exit code 2 distinguishes from crashes (1) if (this.browser) { this.browser.on('disconnected', () => { if (this.intentionalDisconnect) return; - console.error('[browse] Real browser disconnected.'); - process.exit(1); + console.error('[browse] Real browser disconnected (user closed or crashed).'); + console.error('[browse] Run `$B connect` to reconnect.'); + process.exit(2); }); } diff --git a/browse/src/server.ts b/browse/src/server.ts index 8463d83b..f4d2b56b 100644 --- a/browse/src/server.ts +++ b/browse/src/server.ts @@ -601,6 +601,7 @@ async function shutdown() { console.log('[browse] Shutting down...'); killAgent(); messageQueue = []; + saveSession(); // Persist chat history before exit if (agentHealthInterval) clearInterval(agentHealthInterval); clearInterval(flushInterval); clearInterval(idleCheckInterval); @@ -624,10 +625,15 @@ async function shutdown() { process.on('SIGTERM', shutdown); process.on('SIGINT', shutdown); -// Emergency cleanup for crashes (OOM, uncaught exceptions) +// Emergency cleanup for crashes (OOM, uncaught exceptions, browser disconnect) function emergencyCleanup() { if (isShuttingDown) return; isShuttingDown = true; + // Kill agent subprocess if running + try { killAgent(); } catch {} + // Save session state so chat history persists across crashes + try { saveSession(); } catch {} + // Clean Chromium profile locks const profileDir = path.join(process.env.HOME || '/tmp', '.gstack', 'chromium-profile'); for (const lockFile of ['SingletonLock', 'SingletonSocket', 'SingletonCookie']) { try { fs.unlinkSync(path.join(profileDir, lockFile)); } catch {}