diff --git a/apps/server/src/mcp/toolkits/preview/tools.ts b/apps/server/src/mcp/toolkits/preview/tools.ts index 33c3443a1c26e..b1f9e0d1077e8 100644 --- a/apps/server/src/mcp/toolkits/preview/tools.ts +++ b/apps/server/src/mcp/toolkits/preview/tools.ts @@ -52,7 +52,7 @@ const readonlyBrowserTool = (tool: T): T => const PreviewStatusTool = Tool.make("preview_status", { description: - "Report preview tab state: owner, pending dialog, URL, title, loading, viewport, and size. `tabs` lists this thread's tabs. `profiles` lists logins for `preview_open`. Read any listed tab. Act only on your tab, or an unclaimed tab with no human in control.", + "Report tab state: owner, dialog, URL, title, loading and viewport. No tab (`tabId:null`): try `preview_open`. `tabs`: thread tabs; `profiles`: logins. Read any tab; act on yours or unclaimed tabs without human control.", parameters: PreviewAutomationTabTargetInput, success: PreviewAutomationStatus, failure: PreviewToolFailure, diff --git a/apps/server/src/preview/ServerBrowser.test.ts b/apps/server/src/preview/ServerBrowser.test.ts index c558631fd2a51..88581267ffd12 100644 --- a/apps/server/src/preview/ServerBrowser.test.ts +++ b/apps/server/src/preview/ServerBrowser.test.ts @@ -2035,6 +2035,39 @@ it.live("agents read a human's tab with no arguments and act on it only while no ).pipe(Effect.provide(layer)), ); +it.live("browser automation stays available without an open tab", () => + Effect.scoped( + Effect.gen(function* () { + const { broker, tabId } = yield* ready; + const emptyScope = { + ...scope, + thread: { ...scope.thread, threadId: ThreadId.make("empty-browser-thread") }, + }; + const empty = yield* broker.invoke({ + scope: emptyScope, + operation: "status", + input: {}, + }); + expect(empty).toMatchObject({ available: true, visible: false, tabId: null }); + yield* broker.invoke({ scope, tabId, operation: "close", input: {} }); + const closed = yield* broker.invoke({ + scope, + operation: "status", + input: {}, + }); + expect(closed).toMatchObject({ available: true, visible: false, tabId: null }); + expect(contexts).toHaveLength(1); + const reopened = yield* broker.invoke({ + scope, + operation: "open", + input: { show: false }, + }); + expect(reopened.available).toBe(true); + expect(reopened.tabId).not.toBeNull(); + }), + ).pipe(Effect.provide(layer)), +); + it.live("a session's own tab stays its default while other sessions' tabs are listed", () => Effect.scoped( Effect.gen(function* () { diff --git a/apps/server/src/preview/ServerBrowser.ts b/apps/server/src/preview/ServerBrowser.ts index e03d7e3b35db1..cc7bdad8a5f94 100644 --- a/apps/server/src/preview/ServerBrowser.ts +++ b/apps/server/src/preview/ServerBrowser.ts @@ -1577,7 +1577,7 @@ const make = Effect.gen(function* () { ): Promise => { if (!tab) { return { - available: false, + available: true, visible: false, tabId: null, url: null, diff --git a/apps/server/src/provider/agentContextBudget.json b/apps/server/src/provider/agentContextBudget.json index bb586d5b9f4b4..6edabc25be3cf 100644 --- a/apps/server/src/provider/agentContextBudget.json +++ b/apps/server/src/provider/agentContextBudget.json @@ -1,5 +1,5 @@ { "claudeSystemAppendBytes": 5133, "codexAdditionalContextBytes": 6863, - "mcpToolListJsonBytes": 47498 + "mcpToolListJsonBytes": 47461 }