fix(web): host local server browser tabs outside chat views
An agent opening a browser for a background local thread can fall back to headless while its Electron desktop is connected. ElectronBrowserHost stays mounted outside the router, but its session index depended on subscriptions in mounted chat/preview views. Without those views, the native tab did not attach during the server's ten-second wait.
The persistent host now loads its primary environment's existing tabs and subscribes to live preview events. preview.list accepts an omitted thread filter for that baseline; existing thread-scoped callers keep their filter. Reconciliation handles startup, reconnects, missing tabs, server epochs, and events that overtake the initial list. The host applies primary events once when a chat is also mounted. On an epoch change, it refreshes the separate thread queries; late results from a retired primary server cannot restore its native tabs. Remote chat queries can still adopt a new server epoch.
Addresses the local desktop fallback reproduced while investigating #16596. This qualifies as a focused fix for an existing hosting contract: ServerBrowser.desktopRenders already expects the attached desktop to render its server's tabs. The change repairs session synchronization within that contract. It adds no host selection, browser engine, identity override, or challenge handling. Remote environments retain their existing streaming path.
Verification:
- The original regression fails on the baseline with zero native tab creations when no chat is mounted. Both additional epoch regression cases fail on
92c1d426: a completed retired thread list restores old sessions, and the mounted chat query misses its refresh. They pass after this correction. Tests also cover startup hydration, close/release, reconnect, server restart, higher event revisions, one event application, and environment isolation. - On macOS 26.5.1 arm64, used an isolated copy of 0.0.46-nightly.20261006.2735 with Electron 44.4.2, separate T3 state, and fresh Electron profiles. The existing
PreviewAutomationBrokeropen/evaluate/snapshot flow ran for a background test thread with noChatViewmounted. The shipped client used HeadlessChrome 154 and returned HTTP 403 with the verification prompt below. For the final correction, the copied app used the current client build and the unchanged server bundle from92c1d426; every copied client file matched the local build by SHA-256. A tab created before the renderer was ready attached to native Electron/Chrome 152 and reachedhttps://platform.openai.com/login. DOM inspection and the screenshot show the actual login form. No credentials or challenge interaction were used. The installed app was untouched. - 96 focused tests pass across
HostedBrowserWebview.test.tsx,previewStateStore.test.ts,Manager.test.ts,ServerBrowser.test.ts,BrowserSession.test.ts, andCdpRelay.test.ts. Commands and results are in the linked JSON. - Web typecheck with
--checkers 2, web build, and formatting/lint of the three updated files pass. Server and contracts typechecks and the server bundle passed at92c1d426; their production code is unchanged in this update. The earlier changed-file lint run retained the existing extraresolvedThemedependency warning inElectronBrowserHost. All validation on the DevBox ran throughdevbox-heavy-check.
| Shipped client: verification prompt | Corrected client and server: actual login form |
|---|---|
![]() | ![]() |
Sanitized results and setup for 1722a7ae. These anonymous evidence assets are uploaded on the fork and kept out of the PR diff.
Limits: the authenticated organization portal was not exercised, and the original session's exact fallback cause and Cloudflare discriminator were not instrumented. Standalone remote headless browsers still face Cloudflare's documented support limit. An entirely offscreen native snapshot timed out during the initial verification. After the broker inspected the login page, bringing the isolated viewport on-screen allowed the snapshot to complete. This PR addresses session synchronization and hosting lifetime; that capture behavior remains separate.
Prepared with GPT-6.1-Sol through the Codex harness in T3 Code.

