Skip to content

[Feat] Add a private browser to Fast Sessions with the browse tool - #2420

Open
mrubens wants to merge 1 commit into
developfrom
feat/fast-browse-tool
Open

[Feat] Add a private browser to Fast Sessions with the browse tool#2420
mrubens wants to merge 1 commit into
developfrom
feat/fast-browse-tool

Conversation

@mrubens

@mrubens mrubens commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

What

Fast Sessions get a browse tool: a private browser per conversation, with no sandbox. The api process runs the agent-browser CLI against a Browser Use cloud browser, so a Session can open a page, read it, act on it, and capture screenshots or recordings for QA, error lookups, and other browser-only work that previously needed a coding task.

How it works

  • One tool, one command per call. The model passes an agent-browser command line (open, snapshot -i, click @e3, fill, press, screenshot, record start|stop, close, …). Commands are tokenized without a shell, the subcommand is allowlisted, and flags that could reach another session, attach to a CDP endpoint, launch a host binary, load a profile or extension, or switch providers are refused. The child process gets a minimal environment with only the provider key.
  • Per-conversation isolation. Each conversation gets a hashed agent-browser session name; the cloud browser closes after 10 minutes idle so it stops billing.
  • Captures become artifacts. screenshot and record stop upload as Session-owned visual-proof artifacts through a new binary sibling of the text artifact path (createSessionMediaArtifact, 50 MiB cap, signed raw URL). They render inline in the web transcript and in the Session Artifacts panel.
  • Chat delivery. The Slack image and video resolvers now accept Session-owned artifacts by mapping Fast conversation ids to their unified session rows, so captures can ride on send_chat_reply.
  • deliverToUser. GPT-family models treat a screenshot as delivered by their final message and do not call send_chat_reply afterwards (see below), so the delivery decision lives on the capture call itself. The harness attaches only the captures the model opted into, on its next reply.
  • Hidden until configured. Without R_FAST_BROWSER_PROVIDER the tool is filtered out of the model's catalog rather than failing per call.

Configuration

Variable Purpose
R_FAST_BROWSER_PROVIDER browseruse to enable; local launches Chrome on the api host (development only).
R_BROWSER_USE_API_KEY Browser Use API key. Stays on the control plane.
R_AGENT_BROWSER_PATH Optional CLI path when it is not on PATH.

All three are on the control-plane env denylist. The app image installs agent-browser globally; no Chrome ships in it.

Why deliverToUser instead of an imageArtifactIds instruction

Replaying the exact decision point via OpenRouter with the real tool descriptions and a real screenshot result:

Setup Attached the screenshot
gpt-5.6-sol, minimal or full Fast prompt, low / medium / high effort 0/3 each
gpt-5.6-sol, system reminder injected after the result 1/3
gpt-5.6-terra, gpt-5.6-luna 0/3
Claude Opus 5, same minimal prompt 3/3
gpt-5.6-sol and terra with deliverToUser on the screenshot call 3/3 each

In every GPT failure the model made no tool call and ended its turn with "Here's a screenshot of …", expecting the harness to render the image. For GPT-family models, anything that must happen after a tool belongs on the tool.

Tests

  • New unit tests for the command tokenizer, allowlist, CLI invocation, screenshot and recording capture, Session-owned video delivery, and the transcript preview for browse captures.
  • Existing Fast schema, bridge, policy, prompt, and service suites updated and passing. Lint, format, typecheck, and knip clean.

Known gaps

  • Exercised end to end from Slack against Browser Use on a local deployment. The local provider path is typechecked and unit-tested but has not been run.
  • Discord and Telegram live-reply paths already drop images before this change, so captures there are link-only.
  • eval, cookies, and storage are allowed; worth revisiting before users sign the browser into anything.
  • The api restart path leaves previous OpenCode servers orphaned (observed locally under the watcher); pre-existing and tracked separately.

Fast Sessions get a browse tool backed by agent-browser running against a
Browser Use cloud browser from the api process, so a Session can open pages,
read them, act on them, and capture screenshots or recordings without a
sandbox. Commands are tokenized without a shell, subcommands are allowlisted,
and flags that could reach another session, a host binary, a profile, or a
different provider are refused.

Captures upload as Session-owned visual-proof artifacts (a binary sibling of
the text artifact path) with a signed raw URL, render inline in the web
transcript, and can ride on Slack replies: the image and video resolvers now
accept Session-owned artifacts by mapping Fast conversation ids to their
unified session rows.

GPT-family models treat a screenshot as delivered by their final message and
do not call send_chat_reply afterwards, so the delivery decision lives on the
capture itself as deliverToUser; the next visible reply carries what the
model opted into.

The tool is hidden unless R_FAST_BROWSER_PROVIDER names a provider. The api
image installs the agent-browser CLI; no Chrome ships in it.
@roomote-community

roomote-community Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

2 issues outstanding. See task

  • packages/cloud-agents/src/server/fast-agent/fast-agent-browser.ts:65 allows cookies set --curl <path>, which makes the API-side CLI read a host file.
  • packages/cloud-agents/src/server/fast-agent/fast-agent-service.ts:3252 drops requested captures when replacing an inference-retry notice.

Reviewed 7b1a4a3

'eval',
'tab',
'set',
'cookies',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cookies is allowlisted here, and validateBrowseCommand does not reject cookies set --curl <path>. In agent-browser 0.37.1, that form imports cookie data by reading the supplied host file, so a Fast model can make the API-side CLI read arbitrary files despite the intended host-filesystem boundary. Remove this file-backed form (or reject --curl and any other path-bearing cookie variants) before exposing the tool.

!reply.imageArtifactIds?.length && defaultImageArtifactIds.length
? { ...reply, imageArtifactIds: defaultImageArtifactIds }
: reply;
const replyWithImages = attachRequestedCaptures(reply);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When an inference retry notice is being replaced, this sends the capture-bearing reply through replaceInferenceRetryReply. The surface replacers only consume message (for example, the Slack replacer never resolves imageArtifactIds or delivers videoArtifactIds), then return true, so the normal delivery path is skipped. Because attachRequestedCaptures has already spliced the pending IDs, a deliverToUser browse capture taken after a retry produces an edited text reply with no media. Bypass replacement for attachment-bearing replies or add attachment delivery to every replacer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant