fix(browser): share headless fallback and recover native profiles
Problem and behavior
After native attachment times out, the agent can use a headless page while the desktop later displays a separate native page for the same tab. This PR preserves the working headless page and streams it in the Browser panel, so the user and agent act on the same page. The chosen backing survives late native events, failed-launch retries and popups; closing during creation releases the pending page. Existing tab capacity accounting remains in place.
The latest user report identifies a remaining problem: headless storage does not contain the desktop profile's sign-in. preview_status and preview_open now report the actual serverPageMode (native or headless), including each entry in tabs. The optional field preserves older-host compatibility; absence means unknown.
Recovery uses existing inputs rather than switching a live headless page:
preview_open({
profileId: "<id or name from preview_status.profiles>",
open: true,
reuseExistingTab: false,
url: "https://the-requested-site.example"
})
On a desktop-started server, an explicitly selected persistent profile with these foreground/new-tab inputs opens blank and connects to the actual native page before visiting the URL. If attachment or connection setup fails, the new blank tab closes and the tool returns an actionable error without visiting the URL. Previous headless tabs retain their state and control. Omitted-profile, reused-tab, background, Incognito and standalone-server opens retain headless support. Tool descriptions document the recovery path. Native connections acquired before a setup failure are also released.
Scope
Addresses the attachment-timeout path in #16901. Julius' direction feedback accepts preserving and streaming the headless page; his requested input/floating evidence is below. #16620 handles environment-wide hydration separately.
This is explicit new-tab recovery, not automatic migration of a live page or its DOM. The native-cookie test verifies reuse of a desktop profile's sign-in; it does not establish that third-party bot protection or CAPTCHA challenges are avoided. Capture/compositing, host routing and concurrent-open capacity limitations are unchanged.
Verification
The recovery follow-up passes 59 ServerBrowser tests and 28 contract tests, scoped format/lint, server/contracts typechecks and git diff --check. Regression checks fail before the follow-up: explicit profile recovery returned headless success, status encoding stripped the backing mode, and post-connect setup failures did not release the native connection. Focused tests cover foreground recovery and failure cleanup, preserved headless state, failed CDP/render setup, concurrent close, and unchanged background/Incognito/standalone behavior. Independent source review found no remaining introduced blocker.
An isolated packaged Electron app exercised the real MCP transport and native CDP connection against a local HTTP fixture:
- A server-issued
HttpOnlylogin cookie persisted into a fresh native tab in the selected profile. - A different native profile remained signed out.
- Holding native attachment produced a signed-out headless fallback; its form state was retained.
- Releasing attachment and explicitly opening a fresh profile tab recovered the native login. Status correctly reported both native and headless tabs.
- A subsequent failed explicit native open returned an error, removed the new blank tab and made no request to the requested URL. The old headless tab still executed its button action.
The fixture supplied only a synthetic active-run context and an attachment-delay gate; browser results, HTTP responses and login cookies were real. No provider model ran and no private user profiles were copied. The test app was foregrounded with its synthetic thread selected so its normal native Browser synchronization was active. The installed main app was not modified.
The original streaming change at 19306f0 separately passed 67 server/manager tests, 33 web tests, 27 contract tests, scoped checks and server/web/contracts typechecks. Its real web-client pass used a Chromium headless stream: keyboard/click input, panel reopening and floating preview retained the same form state. The follow-up does not change those web components; the previously uploaded images and recordings below are from that pass.
Visual evidence
Reported before: split-page screenshot, with a working foreground control in #16901.
After, with a real headless stream in the isolated web client:
After keyboard/click input and reopening the panel, the field and output retain "Hi":
The floating preview retains the same field and output:
Input recording (focus, keyboard entry and Apply):
https://github.com/user-attachments/assets/67cf5128-75b8-456f-8a34-de8d23b83590
Floating recording (panel → floating → panel, "Hi" retained):
https://github.com/user-attachments/assets/d280feff-5a73-4251-ba8a-700d67743401
Prepared with GPT-6.1-Sol through the Codex harness in T3 Code.