Skip to content

Expose one typed facade bridge for native computer-use adapters - #2904

Open
miguelg719 wants to merge 12 commits into
external-contributor-pr-2771from
evals/consolidation-15-shared-cua-bridge
Open

Expose one typed facade bridge for native computer-use adapters#2904
miguelg719 wants to merge 12 commits into
external-contributor-pr-2771from
evals/consolidation-15-shared-cua-bridge

Conversation

@miguelg719

@miguelg719 miguelg719 commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

Provides one typed bridge from native computer-use adapters to the canonical facade on the runner-owned browser. The bridge centralizes MCP results, screenshots, evidence capture, and failure decoding; provider protocols remain in their own adapters.

An internal callback envelope preserves strings such as "42" and "null" as strings while also preserving JSON values and undefined. Malformed action results and incomplete action batches fail explicitly. Visible facade pages also expose their read-only Stagehand pageId, so native tab tools can use the shared visible context without reaching into the hidden keeper tab. Tool and transport failures use fixed typed diagnostics. Runner RPC deadlines now raise a typed StagehandFacadeTimeoutError carrying timeoutMs; the CUA adapter preserves that duration while keeping arbitrary transport-error text generic. Confirmed session loss still takes precedence. Only the runner-owned browser-loss getter can produce terminal session loss, so page content and model-written error strings cannot stop a run by impersonating that signal.

Validation: 23 bridge and transport tests pass, including the compiled facade/MCP fixture, an actual expired RPC, preserved timeout duration, generic handling of spoofed timeout text, and confirmed session loss. Eval typecheck, build prerequisites, and formatting pass.

Stack position: 15/17. Base: external-contributor-pr-2771 (#2889, the reused Cursor SDK contribution). Review the diff against the immediate predecessor.

Reviewer entry points:

@changeset-bot

changeset-bot Bot commented Sep 8, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 6349520

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 2 files

Architecture diagram
sequenceDiagram
    participant Adapter as Native CUA Adapter
    participant Bridge as cuaToolAdapter Bridge
    participant Channel as Tool Caller (MCP Channel)
    participant Facade as Stagehand Facade (Canonical)
    participant Browser as Runner-Owned Browser

    Note over Adapter,Browser: Shared Typed Facade Bridge for Native Computer-Use Adapters

    Adapter->>Bridge: bridgeCuaFacadeTools(callTool, timeoutMs)
    Bridge->>Channel: run(name, args, { timeoutMs })

    alt run() method
        Bridge->>Channel: run("run", { code })
        Channel-->>Bridge: Tool result (text or error)
        Bridge->>Bridge: Decode result text
        Bridge-->>Adapter: Parsed result (JSON or string)
    else runActions() method
        Bridge->>Channel: run("run", { actions })
        Channel-->>Bridge: Tool result (text)
        Bridge->>Bridge: Validate completed/url shape
        alt Valid result
            Bridge-->>Adapter: { completed, url }
        else Invalid result
            Bridge-->>Adapter: Throw "invalid result"
        end
    else snapshot() method
        Bridge->>Channel: run("snapshot", { includeIframes })
        Channel-->>Bridge: Tool result
        Bridge-->>Adapter: Snapshot text
    else screenshot() method
        Bridge->>Channel: run("screenshot", { ...options })
        Channel-->>Bridge: Tool result (image blocks)
        alt Image block found (PNG/JPEG)
            Bridge-->>Adapter: { data, mimeType }
        else No image
            Bridge-->>Adapter: Throw "no image"
        end
    end

    Note over Adapter,Bridge: Error propagation
    alt result.isError
        Bridge->>Bridge: Extract error text
        Bridge-->>Adapter: Throw Error
    end

    Note over Adapter,Bridge: Evidence Capture
    Adapter->>Bridge: captureCuaEvidence(callTool)
    Bridge->>Bridge: Create bridge with 15s timeout
    Bridge->>Channel: screenshot({ type: "jpeg", quality: 60 })
    alt Screenshot success
        Bridge->>Bridge: Buffer.from(base64 data)
        Bridge-->>Adapter: evidence.screenshot
    else Browser session lost
        Bridge-->>Adapter: Re-throw terminal error
    end
    Bridge->>Channel: run(return page.url())
    alt URL valid scheme
        Bridge-->>Adapter: evidence.url
    else Browser session lost
        Bridge-->>Adapter: Re-throw terminal error
    end

    Note over Adapter,Bridge: Bounded Idempotent Cleanup
    Adapter->>Bridge: cuaCleanup(cleanupFn, timeoutMs)
    Bridge->>Bridge: Create pending promise (shared)
    Bridge->>Bridge: Execute cleanup once
    alt Cleanup settles within timeout
        Bridge-->>Adapter: Resolve
    else Cleanup never settles
        Note over Bridge: Timer forces resolve after timeoutMs
        Bridge-->>Adapter: Resolve (bounded)
    end

    Note over Adapter,Browser: Loopback MCP Fixture Path
    Bridge->>Channel: callTool through MCP transport
    Channel->>Facade: MCP tools/call (run/snapshot/screenshot)
    Facade->>Browser: Execute against active page/context
    Browser-->>Facade: Response data
    Facade-->>Channel: Encoded result
    Channel-->>Bridge: Decoded result
    Bridge-->>Adapter: Typed result
Loading

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

Re-trigger cubic

Comment thread packages/evals/framework/cuaToolAdapter.ts Outdated
Comment thread packages/evals/framework/cuaToolAdapter.ts Outdated
Comment thread packages/evals/framework/cuaToolAdapter.ts Outdated
Comment thread packages/evals/framework/cuaToolAdapter.ts Outdated
Comment thread packages/evals/framework/cuaToolAdapter.ts Outdated
Comment thread packages/evals/tests/framework/cuaToolAdapter.test.ts
@miguelg719
miguelg719 force-pushed the evals/consolidation-15-shared-cua-bridge branch from 70b194d to 55f180a Compare September 8, 2026 16:27
@miguelg719
miguelg719 force-pushed the external-contributor-pr-2771 branch from 2b3676a to 0497a2d Compare September 8, 2026 16:27

@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 4 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread packages/evals/framework/cuaToolAdapter.ts
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