fix(integrations): propagate facade close lifecycle
why
Current main already recognizes browser.close() inside the Playwright-shaped worker runtime, thanks to #2769 / 544ec87f. The worker sets closeRequested, but the batch result drops that signal and the Node host never consumes it. A tool call can therefore report success without releasing the host-owned browser resources.
Cleanup must stay in the host: the Chrome extension worker should not receive Browserbase credentials or own session state. This PR establishes the shared facade contract and implements it for the core stdio host. Eve and Pi adoption are intentionally separated into stacked follow-ups so each host's ownership policy can be reviewed independently.
what changed
- The batch envelope carries
closeRequestedalongside the model value/error. JSON serialization is preflighted so a circular result cannot discard a prior close request. StagehandFacadeToolsawaits a host lifecycle callback before returning. Execution and close failures are both preserved, and hydrated snapshots are cleared.- Hosts that have not adopted lifecycle cleanup remain source-compatible, but
browser.close()throws a typedStagehandFacadeLifecycleErrorinstead of silently claiming cleanup. - The stdio host serializes cleanup, tracks in-flight/failed release targets, retries them before replacement and during shutdown, and preserves cleanup errors.
- Browserbase release uses the official
@browserbasehq/sdkwith a 10-second per-attempt timeout and two transient retries. An ambiguous failed update is verified withsessions.retrieve; onlyCOMPLETEDis accepted. - The canonical prompt and Eve's checked copy distinguish host-owned
browser.close()from page-onlypage.close(). - Current
mainintentionally permits unlistednoindex: truedocs pages; the navigation invariant now excludes those pages instead of failing every newly based PR.
This is PR 1 of a three-PR stack:
behavior before and after
| Stage | Before | After this PR |
|---|---|---|
| Worker callback | Sets a local close flag. | Returns the flag in a serializable envelope. |
| Shared facade | Drops the close request and returns the model result. | Awaits host cleanup before returning. |
| Adopted host | Has no lifecycle boundary. | Owns and reports cleanup through the callback. |
| Legacy host | Can appear to close while retaining resources. | Fails explicitly until its adapter adopts lifecycle cleanup. |
| Stdio cleanup | Can lose failed/racing cleanup ownership. | Serializes cleanup and retains retryable release targets. |
| Browserbase fallback | Hand-written, unbounded request path. | Official SDK with bounded retries/timeouts and completion verification. |
test plan
- Core integration tests: 37/37
- Docs SDK-reference/navigation tests: 27/27
- Existing Eve tests against the compatibility boundary: 5/5
- Existing Pi tests against the compatibility boundary: 3/3
- Core, Eve, and Pi typechecks
- Core build and Eve consumer build
- Focused oxfmt and oxlint checks
- Built-helper transport probe: two transient 500 responses produced SDK retry attempts 0/1/2 before success
The live Eve before/after and Online-Mind2Web-style evidence is reported in the stacked Eve PR, where the Eve-owned persistence and release behavior is introduced.
No changeset is included because the shared integration package and both examples are private.