fix(pi): close facade browser resources

SH
2mo ago
+280-12

why

This is PR 3 of the facade-close stack. It depends on #2793, which in turn depends on #2788.

Pi is a separate native facade host with its own launch and shutdown lifecycle. Once the shared facade propagates browser.close(), Pi must provide a real host callback; otherwise the compatibility boundary correctly reports that cleanup is unsupported. Keeping this adapter in a separate PR makes Pi's shutdown and retry policy reviewable without mixing it into the Eve implementation.

what changed

  • Pi supplies the shared facade lifecycle callback and detaches the expected tools before cleanup.
  • Cleanup is serialized so shutdown, explicit close, and replacement cannot race ownership.
  • Model-requested close surfaces Stagehand/browser failures; session_shutdown remains best-effort.
  • Browserbase release is captured immediately after launch, before Stagehand.create(), so an initialization failure cannot lose the remote session.
  • A rejected memoized browser close retains a fresh SDK release operation for retry before the next launch and during final shutdown.
  • Explicit cleanup failures are surfaced as a dedicated StagehandFacadeCleanupError with a fixed sanitized message and no raw SDK/CDP causes or aggregate members.
  • Focused tests cover explicit-close failure/retry, best-effort shutdown, and failed initialization followed by retained-release retry.

behavior before and after

Pi lifecycleBeforeAfter
await browser.close()No host lifecycle callback.Awaits Pi-owned cleanup.
Explicit cleanup failureCan be swallowed while the tool succeeds.Surfaced to the caller.
Shutdown cleanupBest-effort but can lose failed ownership.Best-effort with retained release targets.
Init + browser-close failureRemote release path can be lost.Release is attempted immediately and retained on failure.
Next launchCan race or bypass failed cleanup.Waits for serialized cleanup/retry first.

test plan

  • Pi tests: 6/6
  • Pi typecheck
  • Focused oxfmt/oxlint
  • Core tests on PR #2788: 37/37
  • Eve tests on PR #2793: 9/9

This PR does not claim a separate live Pi-agent E2E; the shared remote lifecycle behavior is covered by the Eve live A/B in #2793, while this PR focuses on Pi-specific ownership, error, retry, and shutdown paths.

No changeset is included because the Pi integration is a private example.


Summary by cubic

Pi now closes facade-launched browser resources and serializes cleanup. Previously model-requested browser.close() had no host callback and failures could be swallowed; now explicit close surfaces a sanitized error while shutdown remains best-effort.

  • Routes browser.close() through a lifecycle callback in StagehandFacadeTools; session_shutdown uses the same path.
  • Serializes teardown via a shared cleanup promise and retries queued Browserbase releases before any re-launch; avoids closing replaced resources.
  • For Browserbase, captures a release operation at launch; on Stagehand init failure, attempts browser close and session release, queues failed releases for retry, and throws a combined init/cleanup error.
  • Explicit-close failures throw StagehandFacadeCleanupError (sanitized); shutdown suppresses errors but retains release targets for retry.
  • Adds focused tests in packages/integrations/pi/tests/lifecycle.test.ts.