feat(service,dev,luke): the browser tool
Merged
DA
5d ago+1672+1672+0123456789012345678901234567890123456789-55-55-01234567890123456789
PR D of the "Browser agent tab automation setup" rollout. It gives Luke (the brain), coding agents, and both their workers one agent tool, browser, which drives the Chromium in the agent's own sandbox (B1, B2) and keeps the agent's tabs in the service (C).
What it does
- The tool:
AGENT_TOOL.BROWSER, with its roles row for BRAIN, BRAIN_WORKER, CODER and CODER_WORKER. Its input is{ actions: browserAgentActionsSchema }, emitted through the shared JSON Schema emitter. - One routing point:
packages/service/src/agents/browser/browser-tool.ts. ItsbrowserForis the only place a call is routed to a browser, so ME adds Mac tabs there.- The browser session is
browserSessionOf(session), so a worker drives its root's browser. - The tabs belong to the admitted conversation: a coding agent's own, the task's for Luke, a worker's parent's.
- The browser session is
- Takeover: once the browser is reached, the call reads the takeovers (
browserTabDriver). The first action on a tab a member drives answers a failed step that says so ("…wait until they hand it back… or ask them"), and nothing after it runs. The steps before it stand. - Tab store:
- A tab that is opened or acted on is upserted with its current address and title.
closedrops the tab.listmakes the store match the browser's own pages.- The result also carries
tabs: the tabs the call acted on, as they now stand.
- Unreachable browser: answers
not-runwith Vercel's reason, never a throw. - Tab restore (
restore-tabs.ts, added at the orchestrator's request):- When
AgentBrowser.ensurereportsstarted: true(a resumed sandbox),restoreBrowserTabsreopens every kept tab at its last address. - It moves the row onto the new target with the new
retargetBrowserTab. The tab keeps its id, its opener, and any takeover. connectRestoringis exported for E's relay (#4314) to use as a follow-up.
- When
- Screenshots:
- They reach the model as image parts (
browserModelOutput, chosen in the brain'stasksModelOutputand in the dev tool'stoModelOutput). - The stored conversation keeps each step without its picture: the relay's
storedLookcallsstoredBrowserOutput, as it does forlook_at_picture. - No page content is logged or traced; a store failure is logged by its kind only.
- They reach the model as image parts (
- Wiring:
- The brain's
BRAIN_TOOLSandBrainCall.browser; the brain worker gets it through the registry. apps/dev/tools/browser.tsandapps/dev/subagents/worker/tools/browser.ts, throughbrowserFor(admit)inapps/dev/host.ts(the same pattern asrunOnMacFor).context-estimate.tscounts the tool.- Each eve root's runtime (
apps/luke/runtime.ts,apps/dev/runtime.ts) now providesAgentBrowser(agentBrowserServices=agentBrowserLayeroversandboxPtyLayer) on top ofwebServices. The prompt namedhost.ts, but the roots' layer is built inruntime.ts, so it's composed there.
- The brain's
- Instructions:
- Coding agents get a "Your browser" section: use it for what the developer should see or help with (a flow to show, a preview, a sign-in, MFA, a CAPTCHA); keep throwaway checks on sandbox Playwright; the developer may take a tab over, and the agent waits until it's handed back.
- The brain's tool list and both workers' prompts get a line each.
- Transcript row:
CALL_KIND.BROWSER. The line reads like "Clicked, typed in Sign in · Acme", using the tab's title from the answer, or the page it went to while the call runs. It is tagged "Browser" in the place "On your Mac" takes, with the Browser tab's globe icon. Words are masked as every other row's are.
Golden
packages/service/fixtures/coder-instructions/chat-off.json was rewritten with LUKE_UPDATE_FIXTURES=1. The only change is the new "## Your browser" section, in each brief's on-task and taskless prompt.
Tests
browser-tool.test.ts, overfakeCdpPeerand the real store on PGlite:- open and navigate keep the tab with its page; close drops it;
listreconciles the store with the browser;- an action on a driven tab is refused and nothing after it runs, then it runs once the takeover lapses;
- a fresh browser reopens kept tabs as the same tabs, takeover included;
- the screenshot is in the model output and not in the stored output;
- an unreachable browser answers why.
coder-host.test.ts: a relayed browser call's transcript keeps its steps and never the picture. This test fails without the relay wiring.brain-host-tools.test.ts: Luke's call throughrunToolkeeps the tab under the task, listed withagent: null.coder-tool-files.test.ts: the worker's tool files must equaltoolsOf(CODER_WORKER), like the agent's own. This stands in for a "tool set lists browser" test, which would be a change-detector test.agent-browser-tabs.test.ts:retargetBrowserTab.tool-call-model.test.ts: the browser row.
Not in this PR
- Claude Code and Codex agents reach Luke's tools through the Luke MCP server, which doesn't offer
browseryet. - The live view and takeover relay (E), the cloud Browser tab (F), and Mac tabs (MD, ME).
Made with Luke