Skip to content

feat(agents): add BrowserSessionConnector for persistent named browsers - #2371

Merged
ben-reitz merged 4 commits into
feat/browser-session-active-tabfrom
feat/browser-session-connector
Oct 1, 2026
Merged

ben-reitz merged 4 commits into
feat/browser-session-active-tabfrom
feat/browser-session-connector

Conversation

@ben-reitz

@ben-reitz ben-reitz commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

TL;DR: this actually adds the new codemode connector that the model uses.


BrowserSessionConnector is a codemode connector (cdp) over one Browser that persists between executions. The model drives it with raw CDP and never manages sessions. It's internal; #2372 wires it into browserTool.

Model surface (compared with BrowserConnector)

 cdp.send({ method, params, sessionId })
 cdp.attachToTarget({ targetId })        // → { sessionId: "target:<id>" }
 cdp.spec() · cdp.getDebugLog() · cdp.clearDebugLog()
-cdp.startSession() · sessionInfo() · closeSession() · resetSession()
-cdp.getLiveViewUrl()                    // bearer URL — host-only via browser.liveView()
await cdp.send({ method: "Page.navigate", params: { url }, sessionId: "active" });

sessionId: "active"

first use in a pass:
  stored activeTargetId still open  → that tab
  exactly one tab                   → it
  no tabs                           → new about:blank tab
  otherwise                         → first tab

Target.createTarget / cdp.attachToTarget  → becomes active
closing the active tab                    → re-resolve on next use

One pass

first cdp call  browser.connect()            reattach, or replace → restarted
                snapshot open tabs
…model code…
pass end        tabs the page opened (not the agent) → newTabs
                persist the active tab (cleared if it closed;
                  saved anyway if the socket dropped)
                close the socket; the browser keeps running

The { restarted, newTabs } report is held per executionId until the tool reads it; at most 100 unread reports are kept. Within one connector, newTabs covers every pass of an execution, minus tabs that closed again. Carrying the report across an approval resume (which may use a fresh connector) is left for when the browser tool gets approvals; nothing pauses today.

Errors

A page command sent without sessionId, or an event sent as a command, gets an error that tells the model what to do instead (usually sessionId: "active"). These are detected by CDP error code (-32601), not message text. Browser.close and Browser.crash* are refused, since the host owns the browser's lifetime. Arguments are validated before any browser is touched.

The instructions warn the model that the browser can still be replaced between runs, or between the passes of a paused run, and to check where it is before an action that matters.

Reviewing

session-connector.ts (~600 lines) plus 23 tests in browser-session-connector.test.ts, run against a fake Browser Run binding and CDP socket. Eight more run against real Chrome in src/browser-tests (pnpm run test:browser, not in CI): persistence across executions, popups, pause/resume, detach and reattach, teaching errors, Browser.close, and restart.

Architecture diff

Green-filled boxes are new, with one box per part. Grey boxes are unchanged, and dashed boxes arrive later in the stack.

flowchart TB
  classDef ctx fill:none,stroke:#8c959f,color:#8c959f
  classDef changes fill:#dafbe1,stroke:#1a7f37,color:#1f2328
  classDef removed fill:#ffebe9,stroke:#cf222e,color:#1f2328
  classDef later fill:none,stroke:#8c959f,stroke-dasharray:4 3,color:#8c959f

  Sandbox["sandbox code: cdp.*<br/>(via the codemode runtime)"]:::ctx

  subgraph Conn["BrowserSessionConnector · session-connector.ts (new)"]
    direction TB
    K1["+ cdp.send, attachToTarget, spec, debug log"]:::changes
    K2["+ sessionId #quot;active#quot; → the stored active tab"]:::changes
    K3["+ reports restarted and newTabs per execution"]:::changes
    K4["+ one CDP socket per pass; the browser persists"]:::changes
    K1 ~~~ K2 ~~~ K3 ~~~ K4
  end

  Val["validateConnectorArgs · connector-validation.ts"]:::ctx
  BS["Browser · browser.ts"]:::ctx
  BR["Browser Run"]:::ctx
  Next["browserTool (next PR)<br/>wraps it in an AI SDK tool"]:::later

  Sandbox --> Conn
  Conn --> Val
  Conn -->|"connect(), setActiveTarget(), spec()"| BS
  BS --> BR
  Next -.-> Conn

  style Conn fill:#dafbe1,stroke:#1a7f37,stroke-width:2px,color:#1f2328
Loading

@changeset-bot

changeset-bot Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 084e77f

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
agents Patch
@cloudflare/agent-think Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@ben-reitz
ben-reitz added this pull request to stack #2373 September 25, 2026 08:21
@agent-think

agent-think Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

✅ agents import sizes: no significant changes (db8330bd → 084e77f9, workflow run)

@pkg-pr-new

pkg-pr-new Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

agents

npm i https://pkg.pr.new/agents@2371

@cloudflare/ai-chat

npm i https://pkg.pr.new/@cloudflare/ai-chat@2371

@cloudflare/codemode

npm i https://pkg.pr.new/@cloudflare/codemode@2371

hono-agents

npm i https://pkg.pr.new/hono-agents@2371

@cloudflare/shell

npm i https://pkg.pr.new/@cloudflare/shell@2371

@cloudflare/think

npm i https://pkg.pr.new/@cloudflare/think@2371

@cloudflare/voice

npm i https://pkg.pr.new/@cloudflare/voice@2371

@cloudflare/worker-bundler

npm i https://pkg.pr.new/@cloudflare/worker-bundler@2371

commit: 084e77f

@ben-reitz
ben-reitz force-pushed the feat/browser-session-connector branch from 8a9cdaf to cd7cf17 Compare September 25, 2026 09:13
@ben-reitz
ben-reitz force-pushed the feat/browser-session-connector branch 4 times, most recently from a21df5c to acc8261 Compare September 25, 2026 13:48
@ben-reitz
ben-reitz force-pushed the feat/browser-session-connector branch 3 times, most recently from bc7768d to f56913a Compare September 27, 2026 22:56
@ben-reitz
ben-reitz marked this pull request as ready for review September 27, 2026 22:56
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@ben-reitz
ben-reitz force-pushed the feat/browser-session-connector branch from 75337c3 to 49b5dcb Compare September 28, 2026 09:40
@ben-reitz
ben-reitz force-pushed the feat/browser-session-connector branch 2 times, most recently from 300f8b9 to a7e93cc Compare September 28, 2026 12:29
@ben-reitz
ben-reitz removed this pull request from stack #2373 September 29, 2026 13:34
@ben-reitz
ben-reitz force-pushed the feat/browser-session-connector branch from a7e93cc to 173cd66 Compare September 29, 2026 13:36
@ben-reitz
ben-reitz added this pull request to stack #2412 September 29, 2026 13:36
devin-ai-integration[bot]

This comment was marked as resolved.

@ben-reitz
ben-reitz force-pushed the feat/browser-session-connector branch from 173cd66 to 3f4e955 Compare September 30, 2026 09:25

@devin-ai-integration devin-ai-integration Bot left a comment

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.

Devin Review found 1 new potential issue.

Devin Review

Comment thread packages/agents/src/browser/session-connector.ts
A raw Target.attachToTarget sent through cdp.send now returns the stable target:<id> handle, so replays after an approval pause and later runs keep working. Target.detachFromTarget accepts a handle. An expired raw CDP session id gets an error that points at "active" or cdp.attachToTarget.

newTabs now counts only tabs with a Chrome openerId, so a tab another execution creates with Target.createTarget on the same browser is no longer reported as a popup.
cdp.send now returns Chrome's own sessionId for a raw attach again, instead of swapping in a stable handle. The stable handles stay on cdp.attachToTarget. The instructions say a raw id only lasts for the current run, and a stale one still gets a hint. The fake browser now drops CDP sessions when their socket closes, like Chrome.
@ben-reitz
ben-reitz force-pushed the feat/browser-session-connector branch from 779eb5f to 419e054 Compare October 1, 2026 10:08
@ben-reitz
ben-reitz merged commit 9cddcd3 into main Oct 1, 2026
18 checks passed
@ben-reitz
ben-reitz deleted the feat/browser-session-connector branch October 1, 2026 10:53
@github-actions github-actions Bot mentioned this pull request Oct 2, 2026
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