From 376b572c5a901a0f72bb2b2e1b20ac41d3cb66b5 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Thu, 26 Mar 2026 10:46:04 -0600 Subject: [PATCH] feat: ungate sidebar agent + raise timeout to 5 minutes (v0.12.0) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sidebar chat is now always available in headed mode — no --chat flag needed. Agent tasks get 5 minutes instead of 2, enabling multi-page workflows like navigating directories and filling forms across pages. Changes: - cli.ts: remove --chat flag, always set BROWSE_SIDEBAR_CHAT=1, always spawn agent - server.ts: remove chatEnabled gate (403 response), raise AGENT_TIMEOUT_MS to 300s - sidebar-agent.ts: raise child process timeout from 120s to 300s Co-Authored-By: Claude Opus 4.6 (1M context) --- browse/src/cli.ts | 52 ++++++++++++++++--------------------- browse/src/server.ts | 14 +++------- browse/src/sidebar-agent.ts | 6 ++--- 3 files changed, 30 insertions(+), 42 deletions(-) diff --git a/browse/src/cli.ts b/browse/src/cli.ts index ba7a35e1..28e4a79e 100644 --- a/browse/src/cli.ts +++ b/browse/src/cli.ts @@ -520,18 +520,15 @@ Refs: After 'snapshot', use @e1, @e2... as selectors: // Delete stale state file try { fs.unlinkSync(config.stateFile); } catch {} - const chatMode = commandArgs.includes('--chat'); - console.log(chatMode - ? 'Launching headed Chromium with extension + standalone chat (experimental)...' - : 'Launching headed Chromium with extension...'); + console.log('Launching headed Chromium with extension + sidebar agent...'); try { // Start server in headed mode with extension auto-loaded // Use a well-known port so the Chrome extension auto-connects const serverEnv: Record = { BROWSE_HEADED: '1', BROWSE_PORT: '34567', + BROWSE_SIDEBAR_CHAT: '1', }; - if (chatMode) serverEnv.BROWSE_SIDEBAR_CHAT = '1'; const newState = await startServer(serverEnv); // Print connected status @@ -547,31 +544,28 @@ Refs: After 'snapshot', use @e1, @e2... as selectors: const status = await resp.text(); console.log(`Connected to real Chrome\n${status}`); - // Auto-start sidebar agent only when --chat is enabled - if (chatMode) { - const agentScript = path.resolve(__dirname, 'sidebar-agent.ts'); - const agentLogFile = path.join(process.env.HOME || '/tmp', '.gstack', 'sidebar-agent.log'); - try { - // Clear old agent queue - const agentQueue = path.join(process.env.HOME || '/tmp', '.gstack', 'sidebar-agent-queue.jsonl'); - try { fs.writeFileSync(agentQueue, ''); } catch {} + // Auto-start sidebar agent + const agentScript = path.resolve(__dirname, 'sidebar-agent.ts'); + try { + // Clear old agent queue + const agentQueue = path.join(process.env.HOME || '/tmp', '.gstack', 'sidebar-agent-queue.jsonl'); + try { fs.writeFileSync(agentQueue, ''); } catch {} - const agentProc = Bun.spawn(['bun', 'run', agentScript], { - cwd: config.projectDir, - env: { - ...process.env, - BROWSE_BIN: path.resolve(__dirname, '..', 'dist', 'browse'), - BROWSE_STATE_FILE: config.stateFile, - BROWSE_SERVER_PORT: String(newState.port), - }, - stdio: ['ignore', 'ignore', 'ignore'], - }); - agentProc.unref(); - console.log(`[browse] Sidebar agent started (PID: ${agentProc.pid})`); - } catch (err: any) { - console.error(`[browse] Sidebar agent failed to start: ${err.message}`); - console.error(`[browse] Run manually: bun run ${agentScript}`); - } + const agentProc = Bun.spawn(['bun', 'run', agentScript], { + cwd: config.projectDir, + env: { + ...process.env, + BROWSE_BIN: path.resolve(__dirname, '..', 'dist', 'browse'), + BROWSE_STATE_FILE: config.stateFile, + BROWSE_SERVER_PORT: String(newState.port), + }, + stdio: ['ignore', 'ignore', 'ignore'], + }); + agentProc.unref(); + console.log(`[browse] Sidebar agent started (PID: ${agentProc.pid})`); + } catch (err: any) { + console.error(`[browse] Sidebar agent failed to start: ${err.message}`); + console.error(`[browse] Run manually: bun run ${agentScript}`); } } catch (err: any) { console.error(`[browse] Connect failed: ${err.message}`); diff --git a/browse/src/server.ts b/browse/src/server.ts index a4668008..fe288e9e 100644 --- a/browse/src/server.ts +++ b/browse/src/server.ts @@ -36,7 +36,7 @@ ensureStateDir(config); const AUTH_TOKEN = crypto.randomUUID(); const BROWSE_PORT = parseInt(process.env.BROWSE_PORT || '0', 10); const IDLE_TIMEOUT_MS = parseInt(process.env.BROWSE_IDLE_TIMEOUT || '1800000', 10); // 30 min -const chatEnabled = process.env.BROWSE_SIDEBAR_CHAT === '1'; +// Sidebar chat is always enabled in headed mode (ungated in v0.12.0) function validateAuth(req: Request): boolean { const header = req.headers.get('authorization'); @@ -116,7 +116,7 @@ interface SidebarSession { } const SESSIONS_DIR = path.join(process.env.HOME || '/tmp', '.gstack', 'sidebar-sessions'); -const AGENT_TIMEOUT_MS = 120_000; +const AGENT_TIMEOUT_MS = 300_000; // 5 minutes — multi-page tasks need time const MAX_QUEUE = 5; let sidebarSession: SidebarSession | null = null; @@ -811,7 +811,7 @@ async function start() { tabs: browserManager.getTabCount(), currentUrl: browserManager.getCurrentUrl(), token: AUTH_TOKEN, // Extension uses this for Bearer auth - chatEnabled, + chatEnabled: true, agent: { status: agentStatus, runningFor: agentStartTime ? Date.now() - agentStartTime : null, @@ -910,13 +910,7 @@ async function start() { // ─── Sidebar endpoints (auth required — token from /health) ──── - // Gate all sidebar/chat routes behind --chat flag - if (!chatEnabled && url.pathname.startsWith('/sidebar')) { - return new Response(JSON.stringify({ error: 'Chat not enabled. Use: $B connect --chat' }), { - status: 403, - headers: { 'Content-Type': 'application/json' }, - }); - } + // Sidebar routes are always available in headed mode (ungated in v0.12.0) // Sidebar chat history — read from in-memory buffer if (url.pathname === '/sidebar-chat') { diff --git a/browse/src/sidebar-agent.ts b/browse/src/sidebar-agent.ts index 76c75d19..6f28f5f4 100644 --- a/browse/src/sidebar-agent.ts +++ b/browse/src/sidebar-agent.ts @@ -205,14 +205,14 @@ async function askClaude(queueEntry: any): Promise { }); }); - // Timeout after 120 seconds + // Timeout after 300 seconds (5 min — multi-page tasks need time) setTimeout(() => { try { proc.kill(); } catch {} - sendEvent({ type: 'agent_error', error: 'Timed out after 120s' }).then(() => { + sendEvent({ type: 'agent_error', error: 'Timed out after 300s' }).then(() => { isProcessing = false; resolve(); }); - }, 120000); + }, 300000); }); }