Skip to content

fix(cli): restore V3 network capture through a CDP sidecar - #2849

Open
shrey150 wants to merge 2 commits into
mainfrom
agent/browse-v4-network-sidecar
Open

shrey150 wants to merge 2 commits into
mainfrom
agent/browse-v4-network-sidecar

Conversation

@shrey150

@shrey150 shrey150 commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

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. chore(cli): import Browse V3 baseline #2833 — exact Browse V3 baseline import
  2. refactor(cli): migrate Browse runtime and commands to Stagehand V4 #2834 — Stagehand V4 runtime and standard command parity
  3. feat(cli): restore cursor overlay through page.evaluate #2869 — CLI-owned cursor overlay
  4. fix(cli): restore V3 network capture through a CDP sidecar #2849 — CLI-private CDP sidecar; V3 network parity
  5. refactor(cli): remove coordinate XPath return flag #2835 — remove --return-xpath; supported V3 parity/release checkpoint
  6. test(evals): exercise the workspace V4 CLI #2838 — eval and packaging integration
  7. fix(cli): persist context names in Browserbase #2839 — managed Context names (fast-follow)
  8. refactor(cli): consume shared Functions core #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:

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.

@changeset-bot

changeset-bot Bot commented Aug 28, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 99fe4ca

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 7 files

Architecture diagram
sequenceDiagram
    participant User as Browse CLI User
    participant CLI as Browse Command Handler
    participant Manager as Driver Session Manager
    participant Stagehand as Stagehand V4 Client
    participant Browser as Remote Browser
    participant Sidecar as CLI CDP Sidecar
    participant Capture as Network Capture Writer
    participant Files as Private Network Files

    Note over User,Files: Browse V3-compatible network capture on Stagehand V4

    User->>CLI: browse network on
    CLI->>Manager: Get active page
    Manager-->>CLI: V4 page with pageId
    CLI->>Manager: Get browser CDP debugger URL
    Manager->>Stagehand: Read browserWebSocketDebuggerUrl
    Stagehand-->>Manager: Signed browser WebSocket endpoint
    Manager-->>CLI: Endpoint kept internal
    CLI->>Capture: enable(pageId, endpoint)
    Capture->>Sidecar: attach(endpoint, pageId)
    Sidecar->>Browser: Open one browser-level CDP WebSocket
    Browser-->>Sidecar: WebSocket connected
    Sidecar->>Browser: Target.attachToTarget(flatten, pageId)
    Browser-->>Sidecar: CDP sessionId
    Sidecar-->>Capture: Network CDP session
    Capture->>Sidecar: Network.enable
    Capture->>Sidecar: Register request and response listeners
    CLI-->>User: enabled=true, network path

    Note over Browser,Files: CDP events are routed through the flattened page session

    Browser-->>Sidecar: Network.requestWillBeSent
    Sidecar-->>Capture: Request event
    Capture->>Files: Write request.json and allocate counter directory
    Browser-->>Sidecar: Network.responseReceived
    Sidecar-->>Capture: Response metadata
    Capture->>Sidecar: Network.getResponseBody
    Sidecar->>Browser: Fetch response body
    Browser-->>Sidecar: Body or CDP error
    Sidecar-->>Capture: Response body result
    Capture->>Files: Write response.json when available

    alt Concurrent network on requests
        User->>CLI: browse network on
        CLI->>Capture: enable(...)
        Capture-->>CLI: alreadyEnabled=true
    else CDP sidecar connection failure
        Sidecar-->>Capture: Connection or command error
        Capture-->>CLI: network sidecar error
        CLI-->>User: Capture enable failed
    end

    User->>CLI: browse network off
    CLI->>Capture: disable()
    Capture->>Sidecar: Remove registered listeners
    Capture->>Sidecar: Network.disable
    Sidecar->>Browser: Disable Network domain
    Capture->>Sidecar: Target.detachFromTarget
    Sidecar->>Browser: Detach page session
    Sidecar-->>Capture: Page session detached
    Note over Sidecar,Browser: Browser-level WebSocket remains open across off/on cycles
    Capture-->>CLI: enabled=false, retained network path
    CLI-->>User: Capture stopped

    opt Later network on in the same Browse session
        User->>CLI: browse network on
        CLI->>Capture: enable(next pageId, same endpoint)
        Capture->>Sidecar: Attach a new flattened page session
        Sidecar->>Browser: Target.attachToTarget(flatten, pageId)
        Capture->>Files: Continue from highest existing request counter
        CLI-->>User: Capture resumed without overwriting files
    end

    User->>CLI: browse stop
    CLI->>Manager: Close Browse session
    Manager->>Capture: close()
    Capture->>Sidecar: Disable and detach active page session
    Capture->>Sidecar: Close browser-level WebSocket
    Sidecar->>Browser: Close auxiliary CDP connection
    Manager->>Stagehand: Close Stagehand client
    CLI-->>User: Session stopped
Loading

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/cli/tests/network-capture.test.ts Outdated
@shrey150
shrey150 force-pushed the agent/browse-v4-network-sidecar branch from 9887732 to 6f3096e Compare September 15, 2026 23:10
shrey150 added a commit that referenced this pull request Sep 15, 2026
## 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.
@shrey150
shrey150 force-pushed the agent/browse-v4-network-sidecar branch from 6f3096e to 3f2c701 Compare September 15, 2026 23:31
shrey150 added a commit that referenced this pull request Sep 16, 2026
…2834)

## Summary

Migrate Browse's browser lifecycle and standard command surface together
from Stagehand V3 to V4.

- Replace the V3 constructor/init lifecycle with V4 browser factories
and `Stagehand.create()`.
- Support managed local, Browserbase remote, and attached CDP connection
targets.
- Preserve owned-versus-attached cleanup, daemon persistence,
Browserbase session identity, and timeout handling.
- Restore navigation, page information, deterministic locator actions,
keyboard/mouse input, viewport/screenshot, snapshot, eval, and tab
commands on V4 APIs.
- Keep click/fill/select deterministic; this does not add a model-free
structured `act()` path.
- Make the remaining cursor, network, and coordinate-XPath gaps fail
explicitly for the stack layers that restore or remove them.

## 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 shape

The lifecycle and command migration remain two ordered implementation
commits:

1. `389e2dae6` — V4 browser/session foundation and lifecycle ownership.
2. `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
`24178275f` adds 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 `returnXPath` request 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.

| Command / flow | Observed output | Confidence / sufficiency |
| --- | --- | --- |
| `pnpm install --frozen-lockfile` | Lockfile passed supply-chain
policy, was already up to date, and installation completed | Proves the
flattened stack resolves exactly from the committed lockfile |
| `pnpm exec turbo run build --filter=browse` | Protocol, extension,
Stagehand SDK, and Browse CLI built successfully (4/4 tasks) | Proves
the CLI was tested against this head's protocol/extension/SDK artifacts,
not stale workspace `dist` files |
| Compare the rebuilt extension with
`packages/sdk-go/internal/extensionassets/stagehand-extension.zip` |
Exact byte match; both SHA-256
`8efc7d171a625cca95c02d02d369b59435fae776cae6c7dd2f6fe72eb19785c0`;
archive manifest and package version both `1.0.2` | Confirms the
TypeScript/CLI build and Go-embedded extension are synchronized |
| Built CLI: `browse open <synthetic-data-url> --remote`; `browse
status` | Remote browser connected and initialized; deterministic
fixture loaded | Exercises production Browserbase provisioning plus the
V4 daemon/session lifecycle on the exact final head |
| `browse get text //h1`; `fill`; `select`; `click`; `is checked`; `wait
selector`; `highlight`; `viewport`; `screenshot`; `snapshot --full` |
XPath returned `Ready`; input became `Ada`; select became `b`; click
produced `Clicked`; checkbox was true; PNG was 17,761 bytes; snapshot
contained the fixture | Covers deterministic V4 reads, actions, waits,
state, and rendering without an LLM |
| Set a page marker; `tab new`; `tab list`; `tab close`; read the marker
from a new CLI process | Tab count changed to 2 and the original page
returned marker `yes` | Proves daemon persistence, active-tab handling,
and state reuse across invocations |
| Inspect the isolated runtime directory/PID; `browse stop`; poll the
Browserbase session | Modes were `0700` / `0600`; the owned remote
session reached `COMPLETED` | Proves owner-only daemon files and owned
Browserbase resource cleanup |
| `browse cursor`; `browse network on`; `browse mouse hover ...
--return-xpath` | Each exited 1 with the intended explicit
layer-boundary error | Confirms this intermediate layer fails honestly
until the cursor, network, and flag-removal layers land |
| `pnpm --filter browse test` | 25 files / 385 tests passed | Full
Browse unit/integration suite on the exact final head |

The 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.
@shrey150
shrey150 force-pushed the agent/browse-v4-network-sidecar branch from 3f2c701 to eb42f45 Compare September 16, 2026 00:23
@shrey150
shrey150 force-pushed the agent/browse-v4-network-sidecar branch from eb42f45 to d8dbfd0 Compare September 16, 2026 21:50
Base automatically changed from agent/browse-v4-cli-cursor-overlay to main September 17, 2026 03:20
shrey150 added a commit that referenced this pull request Sep 17, 2026
## 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.

![Browse V4 CLI cursor overlay surviving navigation and pointing inside
an
iframe](https://raw.githubusercontent.com/browserbase/stagehand/agent/browse-v4-e2e-assets/e2e-proof/browse-v4-cli-cursor-navigation-iframe-20260901.png)

No LLM path or customer data was involved.
@shrey150
shrey150 force-pushed the agent/browse-v4-network-sidecar branch from d8dbfd0 to 99fe4ca Compare September 17, 2026 03:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant