feat: add browser.disconnect() to the TypeScript and Python SDKs

Merged
MI
1h ago
+1389-44

why

there's no public way to detach from a browser without closing it. browser.close() quits a browser attached with localBrowser.connect(), & stagehand.close() keeps the connection open, so callers attached to a long-lived browser (e.g. one holding signed-in profiles) can't cleanly let go

what changed

  • added browser.disconnect() (ts) / await browser.disconnect() (python): closes an attached stagehand instance (incl. the extension's loopback cdp connection), then stagehand's connection. a later connect() + Stagehand.create() on the same browser works; before, it failed with "A Stagehand instance is already initialized". stagehand never sends Browser.close, never closes a launched process or profile, & never releases a browserbase session
  • on browserbase, keepAlive decides whether the session survives: with keepAlive: false (the default) browserbase ends the session when any cdp connection closes, including disconnect(). only keepAlive: true sessions keep running
  • the handle is finished after disconnect(): close() then throws ("connect() again to close it"); reconnect with localBrowser.connect({ cdpUrl }) / browserbase.connect({ sessionId }) & close that handle. repeated disconnect() / close() calls are idempotent
  • if the connection drops without disconnect(), close() still releases a browserbase session over its api & does nothing for local browsers
  • close() on a launched local browser sends Browser.close before the launcher terminates it (temp profile removed unless preserveUserDataDir)
  • handles expose cdpUrl (cdp_url in python) for local browsers, and connected: closed only means close()/disconnect() was called, so callers still close() a handle whose connection dropped
  • disconnect() waits at most 10s for the attached stagehand instance to close, then lets go anyway
  • docs: a disconnect section with a keepAlive warning & a per-case "to close it later" table, a disconnect column in the behavior-by-environment table split by keepAlive, & a corrected note about close() on connected browsers
  • go is intentionally not included

test plan

  • unit tests in both sdks across local & browserbase, launched & connected, keepAlive on & off: idempotency, close after disconnect (throws, releases nothing), close after a lost connection (browserbase released, local untouched), Browser.close before termination, cdpUrl, & re-attach
  • real chrome tests in both sdks: attach to a separately launched chrome, disconnect, & check it still serves its pages (they fail if close() is used instead); & connect → create → disconnect (no stagehand.close()) → connect → create → navigate (fails on the previous head)
  • pnpm check, pnpm test:unit, sdk-ts unit tests, python tests, ruff, ty, docs tests, sdk parity rules