feat: ungate sidebar agent + raise timeout to 5 minutes (v0.12.0)

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) <noreply@anthropic.com>
This commit is contained in:
Garry Tan 2026-03-26 10:46:04 -06:00
parent b2e1ee9e7b
commit 376b572c5a
No known key found for this signature in database
GPG Key ID: C1F69E85C74EFE1D
3 changed files with 30 additions and 42 deletions

View File

@ -520,18 +520,15 @@ Refs: After 'snapshot', use @e1, @e2... as selectors:
// Delete stale state file // Delete stale state file
try { fs.unlinkSync(config.stateFile); } catch {} try { fs.unlinkSync(config.stateFile); } catch {}
const chatMode = commandArgs.includes('--chat'); console.log('Launching headed Chromium with extension + sidebar agent...');
console.log(chatMode
? 'Launching headed Chromium with extension + standalone chat (experimental)...'
: 'Launching headed Chromium with extension...');
try { try {
// Start server in headed mode with extension auto-loaded // Start server in headed mode with extension auto-loaded
// Use a well-known port so the Chrome extension auto-connects // Use a well-known port so the Chrome extension auto-connects
const serverEnv: Record<string, string> = { const serverEnv: Record<string, string> = {
BROWSE_HEADED: '1', BROWSE_HEADED: '1',
BROWSE_PORT: '34567', BROWSE_PORT: '34567',
BROWSE_SIDEBAR_CHAT: '1',
}; };
if (chatMode) serverEnv.BROWSE_SIDEBAR_CHAT = '1';
const newState = await startServer(serverEnv); const newState = await startServer(serverEnv);
// Print connected status // Print connected status
@ -547,31 +544,28 @@ Refs: After 'snapshot', use @e1, @e2... as selectors:
const status = await resp.text(); const status = await resp.text();
console.log(`Connected to real Chrome\n${status}`); console.log(`Connected to real Chrome\n${status}`);
// Auto-start sidebar agent only when --chat is enabled // Auto-start sidebar agent
if (chatMode) { const agentScript = path.resolve(__dirname, 'sidebar-agent.ts');
const agentScript = path.resolve(__dirname, 'sidebar-agent.ts'); try {
const agentLogFile = path.join(process.env.HOME || '/tmp', '.gstack', 'sidebar-agent.log'); // Clear old agent queue
try { const agentQueue = path.join(process.env.HOME || '/tmp', '.gstack', 'sidebar-agent-queue.jsonl');
// Clear old agent queue try { fs.writeFileSync(agentQueue, ''); } catch {}
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], { const agentProc = Bun.spawn(['bun', 'run', agentScript], {
cwd: config.projectDir, cwd: config.projectDir,
env: { env: {
...process.env, ...process.env,
BROWSE_BIN: path.resolve(__dirname, '..', 'dist', 'browse'), BROWSE_BIN: path.resolve(__dirname, '..', 'dist', 'browse'),
BROWSE_STATE_FILE: config.stateFile, BROWSE_STATE_FILE: config.stateFile,
BROWSE_SERVER_PORT: String(newState.port), BROWSE_SERVER_PORT: String(newState.port),
}, },
stdio: ['ignore', 'ignore', 'ignore'], stdio: ['ignore', 'ignore', 'ignore'],
}); });
agentProc.unref(); agentProc.unref();
console.log(`[browse] Sidebar agent started (PID: ${agentProc.pid})`); console.log(`[browse] Sidebar agent started (PID: ${agentProc.pid})`);
} catch (err: any) { } catch (err: any) {
console.error(`[browse] Sidebar agent failed to start: ${err.message}`); console.error(`[browse] Sidebar agent failed to start: ${err.message}`);
console.error(`[browse] Run manually: bun run ${agentScript}`); console.error(`[browse] Run manually: bun run ${agentScript}`);
}
} }
} catch (err: any) { } catch (err: any) {
console.error(`[browse] Connect failed: ${err.message}`); console.error(`[browse] Connect failed: ${err.message}`);

View File

@ -36,7 +36,7 @@ ensureStateDir(config);
const AUTH_TOKEN = crypto.randomUUID(); const AUTH_TOKEN = crypto.randomUUID();
const BROWSE_PORT = parseInt(process.env.BROWSE_PORT || '0', 10); 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 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 { function validateAuth(req: Request): boolean {
const header = req.headers.get('authorization'); 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 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; const MAX_QUEUE = 5;
let sidebarSession: SidebarSession | null = null; let sidebarSession: SidebarSession | null = null;
@ -811,7 +811,7 @@ async function start() {
tabs: browserManager.getTabCount(), tabs: browserManager.getTabCount(),
currentUrl: browserManager.getCurrentUrl(), currentUrl: browserManager.getCurrentUrl(),
token: AUTH_TOKEN, // Extension uses this for Bearer auth token: AUTH_TOKEN, // Extension uses this for Bearer auth
chatEnabled, chatEnabled: true,
agent: { agent: {
status: agentStatus, status: agentStatus,
runningFor: agentStartTime ? Date.now() - agentStartTime : null, runningFor: agentStartTime ? Date.now() - agentStartTime : null,
@ -910,13 +910,7 @@ async function start() {
// ─── Sidebar endpoints (auth required — token from /health) ──── // ─── Sidebar endpoints (auth required — token from /health) ────
// Gate all sidebar/chat routes behind --chat flag // Sidebar routes are always available in headed mode (ungated in v0.12.0)
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 chat history — read from in-memory buffer // Sidebar chat history — read from in-memory buffer
if (url.pathname === '/sidebar-chat') { if (url.pathname === '/sidebar-chat') {

View File

@ -205,14 +205,14 @@ async function askClaude(queueEntry: any): Promise<void> {
}); });
}); });
// Timeout after 120 seconds // Timeout after 300 seconds (5 min — multi-page tasks need time)
setTimeout(() => { setTimeout(() => {
try { proc.kill(); } catch {} 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; isProcessing = false;
resolve(); resolve();
}); });
}, 120000); }, 300000);
}); });
} }