From 05b5a9906cd1b3983d3654f0208985aa839fea1d Mon Sep 17 00:00:00 2001 From: Nikhil Sonti Date: Thu, 12 Mar 2026 16:47:07 -0700 Subject: [PATCH 1/2] fix: scheduled task agent not using hidden window for new pages The agent prompt only told the agent to pass windowId with `new_page` but not `new_hidden_page`, which the agent prefers for background work. The agent also had no instruction against closing or replacing its dedicated hidden window, causing pages to scatter across uncontrolled windows. Expanded the scheduled task prompt rules to: - Cover both `new_page` and `new_hidden_page` windowId requirement - Forbid closing the dedicated hidden window - Forbid creating new windows - Added `new_hidden_page` to tool reference for MCP consumers Co-Authored-By: Claude Opus 4.6 --- apps/server/src/agent/prompt.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/apps/server/src/agent/prompt.ts b/apps/server/src/agent/prompt.ts index f1efc7ca..08b7b661 100644 --- a/apps/server/src/agent/prompt.ts +++ b/apps/server/src/agent/prompt.ts @@ -352,11 +352,15 @@ function getPageContext( '\n\n**CRITICAL RULES:**\n1. **Do NOT call `get_active_page` or `list_pages` to find your starting page.** Use the **page ID from the Browser Context** directly.' if (options?.isScheduledTask) { - const windowLine = options.scheduledTaskWindowId - ? `When creating new pages with \`new_page\`, always pass \`windowId: ${options.scheduledTaskWindowId}\`.` - : 'When creating new pages with `new_page`, pass the `windowId` from the Browser Context.' - prompt += `\n2. ${windowLine}` - prompt += '\n3. Complete the task end-to-end and report results.' + const windowRef = options.scheduledTaskWindowId + ? `\`windowId: ${options.scheduledTaskWindowId}\`` + : 'the `windowId` from the Browser Context' + prompt += `\n2. **Always pass ${windowRef}** when calling \`new_page\` or \`new_hidden_page\`. Never omit the \`windowId\` parameter.` + prompt += + '\n3. **Do NOT close your dedicated hidden window** (via `close_window`). It is managed by the system and will be cleaned up automatically.' + prompt += + '\n4. **Do NOT create new windows** (via `create_window` or `create_hidden_window`). Use your existing hidden window for all pages.' + prompt += '\n5. Complete the task end-to-end and report results.' } prompt += '\n' From 45efa8de6b4feb95b2cddfb86c06cfef20dc79ca Mon Sep 17 00:00:00 2001 From: Nikhil Sonti Date: Thu, 12 Mar 2026 16:58:41 -0700 Subject: [PATCH 2/2] fix: remove duplicate hidden window creation from scheduled task frontend The server's ChatService already creates a hidden window for scheduled tasks (chat-service.ts:99-126), but the frontend (scheduledJobRuns.ts) was also creating a minimized Chrome window that the server immediately overwrote. This caused two windows to be created per scheduled task run, with only one being used. Removed from scheduledJobRuns.ts: - chrome.windows.create() call - 1-second race condition delay hack (FIXME) - chrome.windows.remove() cleanup - windowId/activeTab params to getChatServerResponse() Co-Authored-By: Claude Opus 4.6 --- .../background/scheduledJobRuns.ts | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/apps/agent/entrypoints/background/scheduledJobRuns.ts b/apps/agent/entrypoints/background/scheduledJobRuns.ts index 98102fc5..0257048b 100644 --- a/apps/agent/entrypoints/background/scheduledJobRuns.ts +++ b/apps/agent/entrypoints/background/scheduledJobRuns.ts @@ -109,25 +109,6 @@ export const scheduledJobRuns = async () => { throw new Error(`Job not found: ${jobId}`) } - const backgroundWindow = await chrome.windows.create({ - url: 'chrome://newtab', - focused: false, - state: 'minimized', - type: 'normal', - }) - - // FIXME: Race condition - the controller-ext extension sends a window_created - // WebSocket message to register window ownership, but our HTTP request may arrive - // at the server before that registration completes. This delay is a temporary fix. - // Proper solution: ControllerBridge should wait/poll for window ownership registration. - await new Promise((resolve) => setTimeout(resolve, 1000)) - - const backgroundTab = backgroundWindow?.tabs?.[0] - - if (!backgroundWindow || !backgroundTab) { - throw new Error('Failed to create background window') - } - const jobRun = await createJobRun(jobId, 'running') const abortController = new AbortController() runAbortControllers.set(jobRun.id, abortController) @@ -135,8 +116,6 @@ export const scheduledJobRuns = async () => { try { const response = await getChatServerResponse({ message: job.query, - activeTab: backgroundTab, - windowId: backgroundWindow.id, signal: abortController.signal, }) @@ -163,13 +142,6 @@ export const scheduledJobRuns = async () => { }) } finally { runAbortControllers.delete(jobRun.id) - if (backgroundWindow.id) { - try { - await chrome.windows.remove(backgroundWindow.id) - } catch { - // Window may already be closed - } - } await updateJobLastRunAt(jobId) } }