Skip to content

feat(desktop): add browser tabs and Chromium diagnostics - #44838

Merged
Hona merged 34 commits into
v2from
browser-experimental
Sep 7, 2026
Merged

Hona merged 34 commits into
v2from
browser-experimental

Conversation

@Hona

@Hona Hona commented Aug 25, 2026 •

Copy link
Copy Markdown
Member

Browser tabs and Chromium tools

  • Open, focus, and close multiple browser tabs from the existing Review pane and its + menu.
  • Share tab ownership between user controls and agent tools; every page operation targets an explicit tab ID.
  • Add input actions, cross-origin frame inspection, snapshots/search/waits, screenshots, dialogs, and file transfers.
  • Add console/network inspection, trace and CPU capture/analysis, heap inspection/comparison, and Lighthouse snapshot audits.
  • Execute browser work in Electron main through the plugin-owned RPC contract. No browser-specific Core service or server route.
  • Transfer file bytes between desktop and server rather than assuming shared paths. Keep RPC traffic outside Chromium's connection pool.
const tab = await tools.browser.tabs.open({ url: "https://example.com" })
const page = await tools.browser.snapshot({ tabID: tab.id })
return page
flowchart LR
  Agent --> Plugin[Server browser plugin]
  Plugin -->|Request ID| Desktop[Electron main]
  Desktop -->|Command and result RPCs| Plugin
  Review[Review pane tabs] -->|Local IPC| Desktop
  Desktop --> Chromium[Tab-scoped Chromium and CDP]
  Chromium --> Site[Desktop-accessible website]
Loading

Review pane controls

Actual application UI in an isolated test instance. These crops show the tab strip and address controls; native page content is outside the crop.

Single browser tab Multiple browser tabs
  • Screenshots require a focused, visible tab. Other background operations do not implicitly select it.
  • File transfers are capped at 5 MiB. Trace exports contain the target renderer process and report incomplete capture after process changes or buffer loss.
  • Browser content is untrusted. Permission enforcement follows in feat(plugin): expose permission assertions #46530.

Depends on the browser plugin in #46531.

Review follow-ups

Each item notes whether the change fixes the root cause or patches a symptom.

flowchart LR
  A[Shell session tab] -->|owns| B[BrowserAttachmentsProvider<br/>server + session → registration]
  B -->|state| C[Session route view model<br/>layout tab sync, focus, commands]
  B -->|state| D[SessionModel.tabs<br/>Ctrl+W target]
  C --> E[SortableTab<br/>files and browser tabs, one ordered list]
Loading

Root fixes

  • Attachment lifetime: a shell-level BrowserAttachmentsProvider owns registration, retry, and replacement per server + session. It closes when the shell session tab closes or the setting turns off, so Settings and session switches keep native pages, files, and agent work.
  • Ctrl/Cmd+W closing the wrong tab: SessionModel and the side panel now read the same attached flag; browser keys flow through panelTabs() in layout order.
  • Browser tabs not draggable: they were rendered outside the sortable <For>. Browser tabs now render through SortableTab in tabs().all() order, so reordering and persistence come for free.
  • Sidecar auth: the main process resolves the sidecar credential itself (SidecarCredentials.authorization), so the renderer never sees the password and Node requests no longer bypass the header injection.
  • press("Enter")/press("Space"): named keys resolve to their character data before dispatch.
  • Nested same-process frames: sessionFor(frameID) walks to the nearest ancestor with a CDP target instead of requiring one per frame.
  • Scaled iframes: coordinates map through the owner's getBoundingClientRect / offsetWidth scale, including width and height.
  • WebSocket and *ExtraInfo network events: one begin/requestHeaders/responseInfo/finish path handles renderer and network-stack events in either arrival order.
  • Heap edge counts: validated once at parse (edge_count sum equals the edge array, every to_node is a real node offset) instead of trusting the file during traversal.
  • Replaced-attachment race: outbound sends are non-fatal; only attach's outcome decides the close reason.
  • Unreachable command-failure branch: ensuring no longer aborts the signal, so a real retrieval failure stops the attachment as intended.
  • Policy test under Bun: pure URL policy moved to browser/policy.ts, Electron-free.

Contained patches

  • wait deadline: each check() races the remaining time; cancellation is still bounded by the deadline rather than interrupting a hung check.
  • Sub-frame blob:/data:: the http(s)-only guard applies to the main frame only.
  • dispose() reason argument, navigate in the retained set, 0o700/0o600 capture files, dead i18n keys, puppeteer-core as a devDependency.
  • Native suite skipped when CI is set; it needs a display.

Left as-is, on purpose

  • Eager registration per visited session: the agent can only open the browser if the desktop is already attached, so deferring to first user click would break agent-initiated browsing. Sharing one event stream per server across attachments is a later optimisation.
  • 401 as terminal: with sidecar credentials resolved in main there is no expected 401 path left; the existing backoff remains for transient drops.
Sortable browser tabs (dev app)

Review follow-ups, round two

Root fixes

  • Focus events: the registry now fans desktop focus requests out to listeners of the mounted route instead of storing them; nothing merges into a stale object and nothing replays when you switch sessions.
  • Attachment ownership: the owning shell tab is resolved whenever any tab references the session and remembered by tabKey, so returning from a child session to its parent, or hydration finishing after the route mounted, no longer closes the attachment.
  • Reconnects use the current endpoint: attach() refreshes the retained connection when the same server key arrives as a new connection, and register() reads it per attempt.
  • The session.created subscription runs under the provider owner, so leaving an optimistic session before creation finishes can no longer orphan its live entry.
  • Unacknowledged state: rpc.state retries with exponential backoff and clears lastState on failure, so a temporarily unreachable server still learns about every tab.
  • Redirect hops: ExtraInfo is applied to the earliest hop that has not consumed its own, and the redirect finalization merges rather than replaces, so each hop keeps its own set-cookie/location.

Contained patches

  • rpc.command retrieval failures no longer stop() the attachment; a cancelled-before-retrieval request or an unknown operation fails only that request.
  • WebSocket timing starts at the handshake; a socket refused before it reports durationMs: 0, and webSocketClosed fails a still-pending socket.
  • Header names are lower-cased before merging renderer and wire copies.
  • cookie, set-cookie, authorization, and proxy-authorization values are redacted in network.get; the names remain so the model knows they were sent.
  • SortableTab resolves custom children once with children().

New native assertions cover the 503-then-retry state path, the two-hop redirect, the refused WebSocket, and redaction.

Review follow-ups, round three

Root fixes

  • State publication is retried until the server acknowledges it or the attachment ends (capped exponential backoff, no retry on a declared unavailable). There is no exhaustion state to resynchronise from: results queued behind a state never name a tab the server lacks, and the renderer publishes locally only once the server has the inventory. The native suite now rejects five publications in a row, beyond the previous budget.
  • Attachment registrations take their endpoint from the server's shared transport (ctx.sdk.url) at each attempt, so a background session follows a restarted sidecar's port without any route being mounted. The route-driven existing.server refresh is gone.
  • Redirect hops without ExtraInfo are marked consumed from CDP's own redirectHasExtraInfo / hasExtraInfo flags, so a cached 301's successor keeps its own wire headers and status. Native fixture: a cacheable 301 ΓåÆ 201 chain fetched twice records 301, 201, 301, 201, and the cached hop carries none of the target's headers.

Contained patches

  • The review panel keeps the one-click "Open file" button when the browser pane is unavailable (web app, setting off); the "Add tab" menu appears only when there are two choices. The three regression specs pass locally against this branch.
  • A command this desktop cannot decode is answered with an unsupported failure result immediately instead of waiting out the server's timeout; a vanished (already cancelled) request still needs nothing.

Left as is

  • Child-session attachments live as long as their owning shell tab. That is the price of agent-initiated browsing from any session in that tab; a per-server event stream shared across attachments is the follow-up that would reduce the cost.

Review follow-ups, round four

Root fixes

  • A dialog that wins the race also aborts the action still running underneath: execute receives a run-scoped signal, fill_form checks it between fields, and fill checks it after focusing and before inserting, so a change validation alert on one field leaves the next one untouched after dismissal. navigate is exempt so a beforeunload dialog can still be answered and the load proceed.
  • Date, time, datetime-local, month, and week inputs take their value directly with input/change events, the way Playwright fills them; a value the control rejects restores the previous one and fails with the required format.
  • Upload and download temp files keep their basename (spaces and non-ASCII included); only characters no supported filesystem accepts are replaced, so the page's File.name matches the server file. Per-file directories already prevent collisions.

Contained patches

  • Log.enable per instrumented session; Chromium-generated entries (CSP refusals, failed resource loads, deprecations) join the bounded console buffer with their source URL.
  • Weak heap edges are excluded from retainers; they remain visible as references from their owner.

Native assertions cover the interrupted fill_form, date/time values and the rejected malformed value, the preserved Quarter 1 日本語.csv upload name, and the 404 log entry; unit tests cover the sanitizer and the weak edge.

@Hona
Hona force-pushed the browser-experimental branch 4 times, most recently from b128bf7 to 0926215 Compare September 1, 2026 02:40
@Hona

Hona commented Sep 1, 2026

Copy link
Copy Markdown
Member Author

The desktop dev-tooling fixes are now in #46523. They have been removed from this branch so this PR stays focused on the browser feature.

@Hona
Hona force-pushed the browser-experimental branch from 4f0fb9e to 47350e0 Compare September 1, 2026 03:36
@Hona
Hona changed the base branch from v2 to browser-plugin September 1, 2026 03:37
@Hona
Hona marked this pull request as ready for review September 1, 2026 03:37
@Hona
Hona requested a review from Brendonovich as a code owner September 1, 2026 03:37
Copilot AI lite review requested due to automatic review settings September 1, 2026 03:37
@Hona Hona changed the title feat(browser): add experimental desktop browser feat(desktop): connect browser pane through plugin RPC Sep 1, 2026

Copilot AI 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.

Pull request overview

This PR introduces an experimental desktop-hosted browser pane integrated into the desktop session UI and wired through desktop IPC so agents can drive navigation and interactions via the browser tool while keeping execution inside the desktop client.

Changes:

  • Adds a Desktop IPC RPC + event surface for a “browser pane” (register/layout/command/close) and a main-process implementation backed by a sandboxed Chromium WebContentsView.
  • Adds an App session-side browser model + UI pane (toolbar + address bar) and session layout updates to accommodate the browser alongside existing panels.
  • Adds an experimental setting toggle and English i18n strings for UI/permissions copy.

Reviewed changes

Copilot reviewed 25 out of 26 changed files in this pull request and generated no comments.

Show a summary per file
File Description
packages/desktop/src/shared/ipc-rpc/events.ts Adds BrowserPaneEvent and includes it in the desktop event stream + RPC group.
packages/desktop/src/shared/ipc-rpc/browser.ts Defines the BrowserPane IPC request/event schemas and BrowserPane RPC.
packages/desktop/src/renderer/platform/index.ts Exposes platform.browserPane registration API backed by desktop IPC calls.
packages/desktop/src/renderer/api.ts Adds renderer-side IPC bindings for BrowserPane request/send/event listening.
packages/desktop/src/renderer/api-types.ts Extends the Electron API types to include BrowserPane request/event types.
packages/desktop/src/main/ipc-handlers/events.ts Wires BrowserPane RPC into the main-process handler and owns lifecycle disposal.
packages/desktop/src/main/browser-pane.ts Implements BrowserPane ownership, OpenCode RPC bridging, and request execution lifecycle.
packages/desktop/src/main/browser-pane-policy.test.ts Adds a focused policy test for allowed/blocked navigation destinations.
packages/desktop/src/main/browser-chromium.ts Implements sandboxed Chromium page driver: navigation, snapshotting, input, screenshots.
packages/desktop/package.json Adds workspace deps needed for browser RPC + schema usage in Desktop.
packages/app/src/settings/model.tsx Adds general.experimentalBrowser setting with default + setters.
packages/app/src/settings/general/general.tsx Adds UI toggle row for the experimental browser pane (desktop-only).
packages/app/src/session/session-panel-layout.ts Extends panel layout logic to consider browser panel visibility/stacking.
packages/app/src/session/session-panel-layout.test.ts Updates tests to cover new browser panel layout behavior.
packages/app/src/session/screen.tsx Integrates session browser model and conditionally renders browser pane vs review pane.
packages/app/src/session/screen-layout.ts Updates screen layout sizing/resizability to account for the browser panel.
packages/app/src/session/header/session-header.tsx Adds browser toggle wiring into the session header actions model.
packages/app/src/session/header/session-header-actions.tsx Adds a browser toggle button (ARIA-wired) to header actions.
packages/app/src/session/browser/pane.tsx Adds the browser pane UI (controls + address input) and layout reporting to desktop host.
packages/app/src/session/browser/model.ts Adds session browser state/model: availability, registration, open/close, command dispatch.
packages/app/src/runtime/platform/platform.tsx Extends the platform type to optionally provide a browserPane capability.
packages/app/src/runtime/platform/browser-pane.ts Adds shared platform-side BrowserPane types used by App.
packages/app/src/runtime/i18n/en.ts Adds English strings for browser toggle, pane UI labels, settings row, permission descriptions.
packages/app/src/desktop.ts Re-exports BrowserPane platform types from the desktop entrypoint.
packages/app/package.json Adds @opencode-ai/browser dependency for shared Browser RPC types in App.
bun.lock Locks workspace dependency additions for the new browser package usage.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@Hona
Hona force-pushed the browser-experimental branch from 744f2a9 to b264265 Compare September 1, 2026 04:07
@Hona
Hona force-pushed the browser-experimental branch from b264265 to bb610d7 Compare September 1, 2026 23:12
@Hona
Hona force-pushed the browser-experimental branch from bb610d7 to b09774b Compare September 1, 2026 23:30
@Hona
Hona force-pushed the browser-experimental branch from b09774b to 3376a88 Compare September 2, 2026 04:56
@Hona
Hona force-pushed the browser-experimental branch from 808dc9b to b4ac905 Compare September 2, 2026 08:37
@Hona
Hona force-pushed the browser-experimental branch 2 times, most recently from e89a3f6 to 07370ba Compare September 2, 2026 09:34
@Hona
Hona force-pushed the browser-experimental branch from 0281da0 to c507eea Compare September 2, 2026 22:51
@Hona
Hona force-pushed the browser-experimental branch from 5d22078 to ec93d00 Compare September 7, 2026 07:16
@Hona
Hona merged commit 8a1a6b8 into v2 Sep 7, 2026
8 of 10 checks passed
@Hona
Hona deleted the browser-experimental branch September 7, 2026 10:58
jinhuang712 pushed a commit to jinhuang712/opencode that referenced this pull request Sep 26, 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.

2 participants