refactor(cli): migrate Browse runtime and commands to Stagehand V4 - #2834
Conversation
|
7e9c944 to
34c5c7d
Compare
df91115 to
389e2da
Compare
There was a problem hiding this comment.
All reported issues were addressed across 22 files
Architecture diagram
sequenceDiagram
participant CLI as Browse CLI Client
participant Daemon as Driver Daemon
participant Manager as Driver Session Manager
participant Factory as Stagehand V4 Browser Factories
participant BB as Browserbase
participant Browser as Browser or CDP Target
participant Stagehand as Stagehand V4 Session
participant Context as Browser Context and Pages
participant Page as Active Page and Locators
Note over CLI,Page: Browse runtime uses a daemon-owned session and Stagehand V4 browser lifecycle
CLI->>Daemon: Open or execute driver command
Daemon->>Daemon: Resolve session, target, and forwarded environment
Daemon->>Manager: Dispatch request
alt Managed local target
Manager->>Factory: localBrowser.launch(headless, Chrome args)
Factory->>Browser: Launch local browser
Browser-->>Factory: Browser handle
else Browserbase remote target
Manager->>Factory: browserbase.launch(apiKey, userMetadata, flags)
Factory->>BB: Create managed remote session
BB-->>Factory: Browser handle and session identity
Factory->>BB: Lookup debug URL
BB-->>Factory: Optional live-view URL
Factory-->>Manager: Browser and Browserbase identity
else Attached CDP target
Manager->>Factory: localBrowser.connect(cdpUrl)
Factory->>Browser: Attach to existing browser
Browser-->>Factory: Browser handle
end
Manager->>Stagehand: Stagehand.create(browser, logging off)
Stagehand->>Context: Resolve browser context
Context-->>Manager: Context and page collection
Manager-->>Daemon: Session initialized
Daemon-->>CLI: Open result, mode, pages, and session identity
CLI->>Daemon: Navigation, query, input, tab, or runtime command
Daemon->>Manager: Dispatch command
Manager->>Context: Resolve active or requested page
Context-->>Manager: Active page
Manager->>Page: Call V4 page API
alt Standard command succeeds
Page->>Page: Navigate, query locator, click, fill, select, type, screenshot, eval, or manage tabs
Page-->>Manager: Result
Manager-->>Daemon: Command response
Daemon-->>CLI: Result data
else Coordinate XPath requested
Page-->>Manager: Error: coordinate XPath unavailable in V4
Manager-->>Daemon: Explicit capability error
Daemon-->>CLI: Command failure
else Cursor or network capability requested
Manager-->>Daemon: Explicit unavailable capability error
Daemon-->>CLI: Command failure
end
Note over Daemon,Manager: Open requests allow browser initialization time plus the configured navigation timeout
CLI->>Daemon: Stop session
Daemon->>Manager: Close session
Manager->>Stagehand: Close Stagehand session
Manager->>Browser: Close owned browser
alt Attached CDP target
Manager->>Browser: Preserve externally owned browser
else Managed local or Browserbase target
Browser-->>Manager: Owned resources cleaned up
end
Manager-->>Daemon: Session stopped
Daemon-->>CLI: Stop result
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
c607e3f to
21e78e2
Compare
21e78e2 to
6f7e9c2
Compare
6f7e9c2 to
07ed1a0
Compare
## Summary Import `packages/cli/**` exactly from the published `browse@0.9.6` V3 release, without changing its source or runtime behavior. This is intentionally a provenance checkpoint, not a line-by-line feature review. The imported source is kept runnable by a root, version-scoped pnpm override that resolves its unchanged Stagehand dependency to `3.7.1`. #2834 removes that override and starts the V4 migration. ## Exact-source provenance - Annotated tag: `browse@0.9.6` (`548c56407431db27823a212f53475443c7e8358d`) - Release commit: `1d49a95c0c230c346f8d50647e10303d6310fcd2` - Authoritative CLI tree: `b4048badce921cf54f199f96033d9a014ef977ec` - This PR's `HEAD:packages/cli` tree: `b4048badce921cf54f199f96033d9a014ef977ec` The tag's ignored README whitespace is retained too; formatting the import would invalidate the tree proof. ## Verification - Current remote head: `a77e1507b85e3c02553f36ead6ebd0237b0cccc6`, based on current `main`. - `HEAD:packages/cli` exactly equals the published V3 tree hash above. - pnpm 11 frozen install and the repository supply-chain release-age policy pass. - Browse lint, typecheck, and build pass; the full baseline suite passes: 25 files / 366 tests. - A fresh extension build still exactly matches the Go-embedded archive: SHA-256 `8efc7d171a625cca95c02d02d369b59435fae776cae6c7dd2f6fe72eb19785c0` on both files. This specifically verifies that adding the V3 dependency graph does not perturb the current V4 extension artifact. - This layer intentionally exercises V3 through the scoped Stagehand 3.7.1 override. V4 behavior starts in #2834. ## Stack (#2872) 1. **#2833 — exact Browse V3 baseline import** 2. #2834 — Stagehand V4 runtime and standard command parity 3. #2869 — CLI-owned cursor overlay 4. #2849 — CLI-private CDP sidecar; V3 network parity 5. #2835 — remove `--return-xpath`; supported V3 parity/release checkpoint 6. #2838 — eval and packaging integration 7. #2839 — managed Context names (fast-follow) 8. #2701 — shared Functions core consumer (fast-follow) ## Review and landing boundary Review this PR by verifying the tree hashes, dependency pin, root package wiring, and changeset—not by treating the imported V3 source as newly authored code. This head deliberately imports V3 code into the V4 repository and is not independently publishable. It lands only as the base of the complete stack. The framework network-event schema proposal in #2832 is intentionally outside this landing stack.
07ed1a0 to
8ff04c9
Compare
AzamAbdul
left a comment
There was a problem hiding this comment.
LGTM at first pass of v4 vs v3 contract changes. One comment from Astra wrt potential left over connections
Addresses review feedback from AzamAbdul.
## Summary
Restore Browse's visible cursor as a CLI-owned DOM overlay, without
adding a cursor API to core Stagehand V4.
- Keep the overlay implementation in one dedicated `cursor-overlay.ts`
file.
- Install it idempotently for the current document through
`page.evaluate(CURSOR_OVERLAY_SCRIPT)` and for future navigations
through `page.addInitScript(...)`.
- Retry installation on `DOMContentLoaded` when the init script runs
before the document root exists.
- Keep injection in the top frame and update the marker from coordinate
input, including when input lands inside a child frame.
- Treat visual position updates as best-effort so they cannot block or
invalidate real mouse input.
- Preserve the V3 `browse cursor` JSON response: `{ "cursor": "enabled"
}`.
## Stack (#2872)
1. #2833 — exact Browse V3 baseline import
2. #2834 — Stagehand V4 runtime and standard command parity
3. **#2869 — CLI-owned cursor overlay**
4. #2849 — CLI-private CDP sidecar; V3 network parity
5. #2835 — remove `--return-xpath`; supported V3 parity/release
checkpoint
6. #2838 — eval and packaging integration
7. #2839 — managed Context names (fast-follow)
8. #2701 — shared Functions core consumer (fast-follow)
## Why this is separate
The cursor is a self-contained optional visual feature with different
review concerns from the combined V4 runtime/command migration: injected
DOM/CSS, idempotency, event handling, and screenshot behavior. Keeping
it additive on #2834 lets this feature be reviewed or reverted without
disturbing browser lifecycle or commands.
## E2E Test Matrix
Review-feedback verification compared the exact prior head `6a9d6aa09`
with fixed implementation head `1035fbbf5` through the built CLI and
real Browserbase browsers. Final head `68f6fcb2c` only expands automated
coverage and does not change runtime code. Targets were the public
`example.com` and `example.org` pages.
| Command / flow | Observed output | Confidence / sufficiency |
| --- | --- | --- |
| Prior head: enable cursor, alternate 20 cross-origin navigations,
inspect `#__browse_cursor_overlay__` before any mouse input | Overlay
count was `0` after 20/20 navigations | Reproduces the DOM-readiness bug
raised in review |
| Fixed head: repeat the same 20-navigation flow | Overlay count was `1`
after 20/20 navigations (0 misses) | Proves the `DOMContentLoaded` retry
restores the overlay after navigation in the real browser path |
| Prior head: replace the page's cursor-position callback with a
throwing function, then run `browse mouse click 200 200` against an
oversized synthetic button | Command exited `1`; the page's click state
remained `null` | Reproduces the visual-update failure blocking real
mouse input |
| Fixed head: repeat the same forced overlay failure and click | CLI
returned `{ "clicked": true }`; page click state became `"yes"` | Proves
overlay rendering is best-effort while real input still executes |
| Built CLI: `browse cursor` | `{ "cursor": "enabled" }` (prior head
returned `{ "enabled": true }`) | Confirms V3-compatible output for
existing scripts |
| `pnpm --filter browse lint` | Passed formatting, ESLint, and
TypeScript checks | Static validation on the final head |
| `pnpm --filter browse test:cli` | 26 files / 393 tests passed | Full
Browse suite, including DOM readiness, safe styling, idempotency,
top-frame isolation, cursor positioning/clamping, and all four
coordinate input commands |
| `browse stop` after each live run | Completed successfully | Covers
Browserbase session and daemon cleanup |
The already-uploaded screenshot below remains representative visual
proof of the same overlay behavior.

No LLM path or customer data was involved.
## Summary Restore Browse V3 network-capture behavior on Stagehand V4 through a CLI-private CDP sidecar, without committing core Stagehand, its protocol, or generated SDKs to a public network-event schema. ## Stack (#2872) 1. #2833 — exact Browse V3 baseline import 2. #2834 — Stagehand V4 runtime and standard command parity 3. #2869 — CLI-owned cursor overlay 4. **#2849 — CLI-private CDP sidecar; V3 network parity** 5. #2835 — remove `--return-xpath`; supported V3 parity/release checkpoint 6. #2838 — eval and packaging integration 7. #2839 — managed Context names (fast-follow) 8. #2701 — shared Functions core consumer (fast-follow) ## Architecture - Lazily open one CLI-private browser-level CDP WebSocket using the endpoint already held by the initialized Stagehand client. - Attach a flattened CDP session to the active V4 page target. - Present the unchanged V3 network writer with the same `on` / `off` / `send` session shape. - On `network off`, remove listeners, send `Network.disable`, and detach the page target. - Keep the browser-level sidecar WebSocket alive across off/on cycles; closing an auxiliary Browserbase socket during the live session can disconnect the browser. - Close the sidecar only during Browse session cleanup. - Never log or return the signed browser WebSocket endpoint. The request correlation and request/response JSON writer are inherited from the V3 CLI. No public protocol schema, extension, SDK, or generated-client changes are included. #2832 remains open outside the landing stack for that separate API-design discussion. ## User-visible behavior The V3 command and file surface is retained: ```bash browse network on browse network path browse network clear browse network off ``` Each request directory contains `request.json` and, when available, `response.json`, including the existing treatment of POST bodies, response bodies, failures, redirects, cache hits, and binary responses. ## E2E Test Matrix Full post-propagation implementation verification ran against exact clean #2849 head `adbe80d8bbfc337fea9dc38e41a88da18c287046`. Its seven-file network patch has the same stable patch ID (`f062ac789108ddf640bc912776dafa210411f50c`) as the previously stress-tested head, so the deterministic V3/V4 and MSN/CNN evidence below applies unchanged. Frozen install, fresh builds, focused/full tests, and the real Browserbase lifecycle were rerun on `adbe80d8b`. Current review-fix head `9887732b6f0c32cdef0966ebfd2ac2c454321d4b` changes only the test helper timeout diagnostic; on that exact head, the targeted network-capture tests passed 2/2 and Browse formatting, ESLint, and TypeScript checks passed. The comparison CLI was the exact built V3 implementation at `7365a20d52955c10d72606f2e6ddd74791609d13`. Every CLI flow used a unique daemon directory; no pre-existing daemon was reused or stopped. | Command / flow | Observed output | Confidence / sufficiency | | --- | --- | --- | | `pnpm install --frozen-lockfile`; build extension, local Stagehand SDK, then `browse` | Frozen install and all three builds passed; the CLI manifest was generated from the tested workspace. | Proves the clean stacked head installs from its lockfile and the subprocess tests exercised fresh V4 SDK/extension/CLI artifacts, not stale `dist` output. | | Built V3 vs built V4 deterministic fixture: `open → network on → eval(GET, cached GET twice, POST, redirect, binary, abort) → path → off → path → clear → stop` | V3: 8 requests / 7 responses. V4: 8 requests / 7 responses. Evaluation results matched; normalized request/response diff count was **0**; command-shape diff was `[]`; clear left 0 entries for both. | Exact parity for the stable V3 command and on-disk contract. Normalization was limited to request ID, timestamp/duration, HTTP `Date`, and fixture origin/port. | | Concurrent real CLI enables: two `browse network on` subprocesses launched together, followed by one marked navigation | One subprocess returned `{ enabled: true }`; the other returned `{ alreadyEnabled: true, enabled: true }`. The marked navigation produced exactly 1 request record with 1 unique request ID. | Proves overlapping `network on` calls serialize and attach listeners once rather than duplicating capture. | | Real CLI `on → navigate → off → on → navigate → off` without clearing | The two records were numbered `000` and `001`; both URLs remained present, and the SHA-256 of the first `request.json` was unchanged after the second cycle. | Proves off/on resumes at the next on-disk counter and does not overwrite retained captures. | | Real CLI permissions and lifecycle | Capture directories were `0700`; `request.json` and `response.json` were `0600`; `network clear` left 0 entries; `browse stop` reported stopped. | Proves the V3 private-file contract and cleanup behavior through the built CLI. | | Real Browserbase, two `network on → open Example Domain → off` cycles in one session, then navigation with capture off | Both cycles captured GET 200 and the complete Example Domain body; the Browserbase session stayed stable; post-off `open --wait networkidle` worked; final status was connected, initialized, and remote; stop completed. | Proves the auxiliary sidecar survives real remote off/on while the owning Stagehand browser remains usable. No session ID or signed endpoint is included here. | | Real Browserbase stress, V3 and V4, public MSN then CNN; each cycle ran `on → path → open → scroll → collect 12s → off → path → navigate while off → status → clear` | All 21 commands completed for each CLI. V3 captured MSN 415/401 and CNN 192/187 request/response records. V4 captured MSN 498/481 and CNN 194/187. Across all four cycles: 0 malformed request JSON, 0 malformed response JSON, 0 response-ID mismatches, 0 new records after off, and 0 entries after clear. Both sessions stayed remote/connected, stopped cleanly, and emitted no WebSocket endpoint. | Proves bounded high-concurrency, cross-origin Document/Script/Fetch/XHR/Image/Font/Media traffic. Live request counts are intentionally not expected to match because sites and ad auctions are nondeterministic; contract invariants and command shapes matched. | | `pnpm --filter browse lint` | Passed formatting, ESLint, and TypeScript checks at full-verification head `adbe80d8b`; passed again at current review-fix head `9887732b6`. | Static support for both the fully exercised implementation head and the exact current head. | | `pnpm --filter browse test` with isolated daemon directory | At full-verification head `adbe80d8b`: **27 files / 390 tests passed**; focused network subset: **3 files / 30 tests passed**. At current review-fix head `9887732b6`: targeted network-capture tests passed **2/2**. | Covers sidecar routing, response bodies, listener teardown, attach/detach, request-write races, counter reuse, and overlapping enable serialization in addition to the real flows above; the current-head rerun is scoped to the test-only diagnostic change. | The deterministic artifact comparison includes method, URL/path, request/response headers and bodies, status/status text, MIME type, resource type, error shape, counter naming, and file modes. The live stress test also observed GET/POST/OPTIONS, failures, base64 bodies, cross-origin documents, and request-only records for traffic still in flight at the bounded `off` point. This matrix does not claim WebSocket-frame, SSE-message, service-worker, or every out-of-process-iframe edge-case coverage; those are outside the V3 JSON request/response file contract proven here.
Summary
Migrate Browse's browser lifecycle and standard command surface together from Stagehand V3 to V4.
Stagehand.create().act()path.Stack (#2872)
--return-xpath; supported V3 parity/release checkpointReview shape
The lifecycle and command migration remain two ordered implementation commits:
389e2dae6— V4 browser/session foundation and lifecycle ownership.b45167462— standard command translation on that foundation.They are one PR because both commits rewrite the same nine command/test files. Reviewing their combined final diff avoids temporary deletion/stubbing followed by reimplementation, while the commits still provide useful lifecycle-versus-command checkpoints. Review follow-up
24178275fadds narrowly scoped ownership, error-sanitization, and timeout guards. The resulting PR diff is 23 files, +988/−436.Cursor DOM injection and private CDP network transport remain separate because they are independently reviewable mechanisms and cleanly additive diffs. The legacy coordinate
returnXPathrequest is still accepted here but fails explicitly until #2835 removes the option. This remains an intentionally non-publishable intermediate head.E2E Test Matrix
Fresh post-flatten verification used the actual built CLI at final head
6f7e9c209. Every daemon command used an isolated owner-only runtime directory.pnpm install --frozen-lockfilepnpm exec turbo run build --filter=browsedistfilespackages/sdk-go/internal/extensionassets/stagehand-extension.zip8efc7d171a625cca95c02d02d369b59435fae776cae6c7dd2f6fe72eb19785c0; archive manifest and package version both1.0.2browse open <synthetic-data-url> --remote;browse statusbrowse get text //h1;fill;select;click;is checked;wait selector;highlight;viewport;screenshot;snapshot --fullReady; input becameAda; select becameb; click producedClicked; checkbox was true; PNG was 17,761 bytes; snapshot contained the fixturetab new;tab list;tab close; read the marker from a new CLI processyesbrowse stop; poll the Browserbase session0700/0600; the owned remote session reachedCOMPLETEDbrowse cursor;browse network on;browse mouse hover ... --return-xpathpnpm --filter browse testThe runner has no Chrome/Chromium installation, so a fresh attached-CDP ownership smoke was not possible. Attached-browser non-ownership remains covered by the focused suite and is not claimed as a fresh live result here.