Skip to content

feat: add Yutori Navigator n1.5 as a computer-use (CUA) agent provider#2194

Open
lawrencechen98 wants to merge 9 commits into
browserbase:mainfrom
yutori-ai:yutori-navigator-cua-upstream
Open

feat: add Yutori Navigator n1.5 as a computer-use (CUA) agent provider#2194
lawrencechen98 wants to merge 9 commits into
browserbase:mainfrom
yutori-ai:yutori-navigator-cua-upstream

Conversation

@lawrencechen98

@lawrencechen98 lawrencechen98 commented Jun 5, 2026

Copy link
Copy Markdown

Summary

Adds Yutori Navigator n1.5 as a computer-use (CUA) agent provider, alongside the existing OpenAI / Anthropic / Google CUA clients. Navigator takes a screenshot per turn and returns coordinate-based tool_calls in a normalized 1000×1000 space, served over an OpenAI-compatible Chat Completions API at https://api.yutori.com/v1 — so it reuses Stagehand's existing openai dependency and the provider-agnostic V3CuaAgentHandler. No new dependencies.

const agent = stagehand.agent({ mode: "cua", model: "yutori/n1.5-latest" });
const result = await agent.execute({ instruction: "…", maxSteps: 30 });

Auth via YUTORI_API_KEY (or clientOptions.apiKey / baseURL).

What's included

1. Navigator provider (YutoriCUAClient + yutoriActions)
Screenshot-per-turn loop; tool_callAgentAction with 1000×1000 → viewport denormalization; role:"tool" results carrying a Current URL: suffix; request-payload trimming that bounds recent screenshots; completion when there are no tool_calls; stop-and-summarize at maxSteps. Registered in AgentProvider, the model/agent public types, and providerEnvVarMap (yutoriYUTORI_API_KEY), plus the hosted API schema (openapi.v3.yaml).

2. Contained to the existing CUA provider boundary
All provider-specific logic lives in the client — no handler-shape changes for other providers:

  • Extra page capabilities are supplied as user tools via agent({ tools }) (advertised as function tools, executed in-client).
  • Structured output via execute({ output }) → Navigator json_schemaresult.output.
  • execute({ excludeTools })disable_tools (unioned with defaults); a CUA_SUPPORTED_EXECUTE_OPTIONS capability map lets validateExperimentalFeatures accept these for providers that implement them.
  • Graceful in-client degradation: refreshgoto(current URL), modifier-clicks return a tool error, hold_key degrades to a plain press.

3. Harness fixes (surfaced running Navigator through the eval harness)

  • Post-action Current-URL refresh (V3CuaAgentHandler) — restore the per-step updateClientUrl() call so a tool result reports the post-action URL rather than the pre-action one.
  • Native <select> dropdown shim (utils/replaceNativeSelect.ts) — a native <select>'s option list isn't clickable DOM for a CUA agent, so it re-clicks the closed control and the value never changes. We inject an idempotent in-page HTML overlay before each screenshot so options are visible and selectable (viewport-positioned; disabled options non-clickable; framework-controlled selects committed via the native value setter).
  • Native system prompt for Navigator (OnlineMind2Web eval) — let yutori/* models use their own tuned system prompt instead of the harness's generic one; all other models are unchanged.

Tests

Unit coverage for the client (action mapping, message/trajectory shape, structured output, error recovery, stop-and-summarize), the action helpers (coordinate denorm/validation, payload trimming), the dropdown shim, validateExperimentalFeatures, and API serialization — plus a runnable usage example. Core build (esm + cjs), the new unit suites, and the evals typecheck all pass locally; the provider has been exercised end-to-end against the live Navigator API.

Notes for reviewers

Rebased on latest main; mergeable, linear history. As a fork PR the build/test workflows are awaiting maintainer approval to run.

@changeset-bot

changeset-bot Bot commented Jun 5, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 06d9a74

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@browserbasehq/stagehand Patch
@browserbasehq/stagehand-evals Patch
@browserbasehq/stagehand-server-v3 Patch

Not sure what this means? Click here to learn what changesets are.

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

@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

This PR is from an external contributor and must be approved by a stagehand team member with write access before CI can run.
Approving the latest commit mirrors it into an internal PR owned by the approver.
If new commits are pushed later, the internal PR stays open but is marked stale until someone approves the latest external commit and refreshes it.

@github-actions github-actions Bot added external-contributor Tracks PRs mirrored from external contributor forks. external-contributor:awaiting-approval Waiting for a stagehand team member to approve the latest external commit. labels Jun 5, 2026
@lawrencechen98
lawrencechen98 force-pushed the yutori-navigator-cua-upstream branch from 931e0b7 to ce6c06e Compare June 5, 2026 22:15
@lawrencechen98
lawrencechen98 marked this pull request as ready for review June 5, 2026 22: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.

2 issues found across 23 files

Confidence score: 3/5

  • There is some real merge risk: stopAndSummarize() in packages/core/lib/v3/agent/YutoriCUAClient.ts bypasses expected flowLogger instrumentation, which can reduce traceability and make agent behavior/debugging less reliable.
  • packages/core/lib/v3/handlers/v3CuaAgentHandler.ts has a concrete replay correctness concern—modifiers used during CUA action execution are not persisted, so replay can diverge for modifier-dependent actions.
  • Given a high-confidence medium/high-severity instrumentation gap plus a replay-divergence bug, this looks mergeable only with caution rather than a low-risk merge.
  • Pay close attention to packages/core/lib/v3/agent/YutoriCUAClient.ts and packages/core/lib/v3/handlers/v3CuaAgentHandler.ts - missing flow logging and non-persisted modifiers can cause observability and replay consistency issues.
Architecture diagram
sequenceDiagram
    participant User as User Code
    participant Stagehand as Stagehand Instance
    participant Agent as Agent Provider
    participant Client as YutoriCUAClient
    participant Handler as V3CuaAgentHandler
    participant Page as Understudy Page
    participant CDP as Chrome DevTools Protocol
    participant Navigator as Yutori Navigator API

    Note over User,Navigator: NEW: Yutori Navigator n1.5 CUA Agent Provider

    User->>Stagehand: stagehand.agent({ mode: "cua", model: "yutori/n1.5-latest" })
    Stagehand->>Agent: create provider (modelToAgentProviderMap)
    Agent->>Client: new YutoriCUAClient(type, model, instructions, clientOptions)
    alt API key missing
        Client-->>Agent: throw Error
    end
    Client->>Client: configure toolSet, disableTools, jsonSchema, userTimezone, userLocation
    Stagehand-->>User: agent instance

    User->>Stagehand: agent.execute({ instruction, maxSteps })
    Stagehand->>Handler: new V3CuaAgentHandler(..., client=YutoriCUAClient)

    Handler->>Client: setScreenshotProvider()
    Handler->>Client: setActionHandler()
    Handler->>Handler: capture screenshot (page.screenshot)
    Handler->>Client: setCurrentUrl(page.url())
    Client->>Client: build message history (system prompt + user instruction with location/timezone context)

    loop Step Loop (maxSteps)
        Client->>Client: clone messages for request
        Client->>Client: trimImagesToFit (drop old screenshots under ~9.5 MB, keep latest)
        Client->>Navigator: POST /v1/chat/completions (OpenAI-compatible)
        Note over Client,Navigator: Extra params: tool_set, disable_tools, json_schema
        Navigator-->>Client: response with tool_calls (1000x1000 normalized coordinates)
        Client->>Client: parse tool_calls from assistant message

        alt No tool_calls
            Client-->>Handler: return final result (completed)
        else Has tool_calls
            par For each tool_call
                Client->>Client: denormalizeCoordinates (1000→viewport pixels)
                Client->>Client: mapNavigatorKeyToPlaywright (Navigator keys→Playwright keys)
                Client->>Handler: actionHandler(action)
                alt Action type: click (with possible modifier)
                    Handler->>Page: click(x, y, { modifiers })
                    Page->>CDP: dispatchMouseEvent(..., modifiers bitmask)
                else Action type: scroll (with possible modifier)
                    Handler->>Page: scroll(x, y, deltaX, deltaY, { modifiers })
                    Page->>CDP: dispatchMouseEvent(..., modifiers bitmask)
                else Action type: keypress (with optional holdMs delay)
                    Handler->>Page: keyPress(key, { delay })
                else Action type: refresh
                    Handler->>Page: reload({ waitUntil: "load" })
                    Page->>CDP: Page.reload
                else Action type: type, goto, back, forward, wait, drag
                    Handler->>Page: execute action
                end
                alt Action succeeded
                    Page-->>Handler: success
                else Action threw error
                    Page-->>Handler: error
                    Handler->>Page: still update client URL (page.url())
                    Handler-->>Client: action result with [ERROR]
                end
            end
            Client->>Client: append tool result (role:"tool" + "Current URL:" suffix)
            Client->>Client: captureScreenshot() for next turn
        end

        alt Payload size > max bytes
            Client->>Client: trimImagesToFit (strip old screenshots, keep recent)
        end
    end

    alt Max steps reached (no completion)
        Client->>Client: formatStopAndSummarize(task)
        Client->>Navigator: final request with summarize prompt (no json_schema)
        Navigator-->>Client: summary text
        Client-->>Handler: return result (completed=false, summary message)
    end

    Handler-->>Stagehand: AgentResult (output may include parsed_json)
    Stagehand-->>User: execution result
Loading

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

Fix all with cubic | Re-trigger cubic

Comment thread packages/core/lib/v3/agent/YutoriCUAClient.ts
Comment thread packages/core/lib/v3/handlers/v3CuaAgentHandler.ts Outdated
@lawrencechen98
lawrencechen98 force-pushed the yutori-navigator-cua-upstream branch from ef9c84b to eb906ec Compare July 1, 2026 21:06
lawrencechen98 pushed a commit to yutori-ai/stagehand that referenced this pull request Jul 1, 2026
…tion

Rework of browserbase#2194 so the Navigator n1.5 integration matches the existing CUA
provider boundary: all provider-specific logic lives in YutoriCUAClient,
and cross-cutting needs flow through abstractions Stagehand already has.

- Revert all understudy/handler/cache/replay/public-type changes to base;
  drop the expanded-tool stack (page bridge, ref registry, snapshot
  renderer) and the five Yutori fields on ClientOptions / cloud API schema
  (API surface change is now the yutori provider enum entries only).
- Custom page capabilities arrive as user tools via agent({ tools }):
  advertised as OpenAI function tools, executed in-client (Anthropic
  pattern), recorded as custom_tool trajectory actions (Google pattern).
- Structured output via execute({ output }) -> Navigator json_schema ->
  result.output; execute({ excludeTools }) -> disable_tools, unioned with
  defaults [mouse_down, mouse_up, hold_key].
- Graceful degradation in-client: refresh -> goto(current URL), modifier
  clicks return a tool error, hold_key degrades to a plain press.
- New CUA_SUPPORTED_EXECUTE_OPTIONS capability map on the provider
  registry lets validateExperimentalFeatures accept excludeTools/output
  for providers that implement them (still experimental-gated), instead
  of rejecting them wholesale in CUA mode.
- Example doubles as the migration recipe for the expanded tools
  (extractLinks / executeJs as tool() closures over the page).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
lawrencechen98 and others added 7 commits July 14, 2026 09:42
Integrates Yutori's Navigator n1.5 computer-use model as a Stagehand CUA
provider, mirroring the existing OpenAI/Anthropic/Google/Microsoft CUA clients.
Navigator is OpenAI-compatible Chat Completions at https://api.yutori.com/v1
(screenshot in, coordinate tool_calls out in a normalized 1000x1000 space), so
this reuses the existing `openai` dependency and the provider-agnostic CUA
handler — no new dependencies.

Usage: stagehand.agent({ mode: "cua", model: "yutori/n1.5-latest" }).
Auth via YUTORI_API_KEY or clientOptions (apiKey/baseURL). Core tool set.

- YutoriCUAClient: screenshot-per-turn loop; tool_call -> AgentAction with
  1000x1000 coordinate denormalization; role:"tool" results with a current-URL
  suffix; payload trimming; completion when no tool calls; stop-and-summarize on
  max steps. Faithful to the Yutori Python SDK reference loop.
- Provider registration (AgentProvider, AVAILABLE_CUA_MODELS, AgentType,
  providerEnvVarMap) and Navigator ClientOptions (toolSet/disableTools/
  jsonSchema/userTimezone/userLocation), incl. the cloud API + OpenAPI schema.
- Keyboard modifiers via a general page.click/scroll `modifiers` option (sets
  the CDP mouse-event modifiers bitmask); hold-key; refresh via page.reload with
  a faithful agent-replay step.
- Evals: skip building an AI-SDK text client for CUA-only models in the local
  bench harness path (also unblocks microsoft/fara-7b).
- Unit tests + usage example.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address review: stopAndSummarize() made a direct chat.completions.create
call without FlowLogger instrumentation. Wrap it with
FlowLogger.logLlmRequest/logLlmResponse, mirroring predict(), so every
direct Navigator LLM call is flow-logged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rolls

Address review (P2): modifiers applied during CUA action execution were not
captured in recorded agent-replay steps, so a cached replay re-ran a chorded
click/scroll as a plain one.

Thread modifiers through the selector-based path symmetrically with the
coordinate path:
- Add a shared `cdpModifierMask` helper (understudy/modifiers.ts); reuse it in
  Page (dedupes the previous private copy) and add modifier support to
  Locator.click via the CDP mouse-event modifiers bitmask.
- Action gains optional `modifiers`; performUnderstudyMethod forwards them to
  the locator click; takeDeterministicAction passes action.modifiers.
- The CUA handler records modifiers on the replay step for click (Action) and
  scroll (AgentReplayScrollStep); AgentCache re-applies them on replay.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…est)

Enable the expanded Navigator tool set (browser_tools_expanded-20260403) and
make it the default for yutori/n1.5-latest, on top of the core coordinate tools.

- DOM tools backed by Stagehand's a11y snapshot + deepLocator:
  - extract_elements / find render the hybrid accessibility tree in Navigator's
    format, minting stable ref_N tokens (NavigatorRefRegistry).
  - set_element_value resolves a ref to its xpath and fills via deepLocator.
  - execute_js evaluates JS in the page (expression-first, body fallback).
- ref-targeted coordinate tools: click/scroll/etc. may carry a `ref` instead of
  coordinates; it resolves to the element's on-screen center (deepLocator
  centroid, scroll-into-view), taking priority over model coordinates and
  falling back to them. A ref'd scroll scrolls the element into view.
- The CUA handler supplies a generic page bridge (a11y snapshot + evaluate +
  elementCenter); all Navigator-specific logic stays in the client.
- Unknown/stale refs return a recoverable error so the model re-extracts.

Unit tests cover rendering/find/ref resolution + the four-tool dispatch,
tool-set selection, scroll-into-view, and stale-ref handling; a
YUTORI_API_KEY-gated integration spec exercises the tools live.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tion

Rework of browserbase#2194 so the Navigator n1.5 integration matches the existing CUA
provider boundary: all provider-specific logic lives in YutoriCUAClient,
and cross-cutting needs flow through abstractions Stagehand already has.

- Revert all understudy/handler/cache/replay/public-type changes to base;
  drop the expanded-tool stack (page bridge, ref registry, snapshot
  renderer) and the five Yutori fields on ClientOptions / cloud API schema
  (API surface change is now the yutori provider enum entries only).
- Custom page capabilities arrive as user tools via agent({ tools }):
  advertised as OpenAI function tools, executed in-client (Anthropic
  pattern), recorded as custom_tool trajectory actions (Google pattern).
- Structured output via execute({ output }) -> Navigator json_schema ->
  result.output; execute({ excludeTools }) -> disable_tools, unioned with
  defaults [mouse_down, mouse_up, hold_key].
- Graceful degradation in-client: refresh -> goto(current URL), modifier
  clicks return a tool error, hold_key degrades to a plain press.
- New CUA_SUPPORTED_EXECUTE_OPTIONS capability map on the provider
  registry lets validateExperimentalFeatures accept excludeTools/output
  for providers that implement them (still experimental-gated), instead
  of rejecting them wholesale in CUA mode.
- Example doubles as the migration recipe for the expanded tools
  (extractLinks / executeJs as tool() closures over the page).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…n shim

Two CUA-loop fixes surfaced while running the Yutori Navigator (n1.5) provider
through the eval harness:

1. Restore the post-action Current-URL refresh. The provider-containment
   refactor kept updateClientUrl() but dropped its post-action call, so the
   client reported the pre-action URL in tool results for the rest of each
   step. Re-add `await this.updateClientUrl()` in the action handler's finally.

2. Add a native <select> dropdown shim (utils/replaceNativeSelect.ts). A native
   <select>'s option list is a browser/OS control, not regular DOM, so a CUA
   agent's clicks don't register as selections. Before each screenshot we inject
   an idempotent in-page HTML overlay: options become real, clickable DOM,
   committed via the native value setter (+ input/change) so framework-
   controlled selects accept them; the overlay is viewport-positioned so options
   stay on-screen and closes on an outside click. The overlay is resolved by id
   (re-created if the page tears it down) so a select bound on an earlier step
   never targets a stale node; state on window keeps per-step re-injection
   idempotent. Disabled options are non-clickable, hidden selects skipped.
   Covered by tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The OnlineMind2Web task injected a generic harness system prompt into every
agent. Navigator (n1.5) ships its own tuned system prompt and is degraded by
the harness's generic instructions, so skip systemPrompt for Navigator models
(yutori/*) and let it use its default. Every other model — including other CUA
providers — keeps the harness prompt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lawrencechen98
lawrencechen98 force-pushed the yutori-navigator-cua-upstream branch from eb906ec to d95e8e5 Compare July 14, 2026 16:50

@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.

8 issues found across 21 files

Confidence score: 3/5

  • The biggest risk is in packages/core/lib/v3/agent/YutoriCUAClient.ts: long Navigator sessions keep full base64 screenshots in persistent history even after request trimming, so memory usage can grow with each step and eventually destabilize long runs — trim/prune the stored history itself before send so dropped images can be garbage-collected.
  • Select interaction handling in packages/core/lib/v3/handlers/v3CuaAgentHandler.ts and packages/core/lib/v3/agent/utils/replaceNativeSelect.ts has a concrete regression path (shadow-DOM retargeting, missing nested open-shadow-root shims, and viewport/offset mismatch), which can make dropdowns close immediately or become unclickable — update event targeting to use e.composedPath(), recurse all discovered open shadow roots, and compute overlay positioning in viewport coordinates before merging.
  • packages/core/lib/v3/handlers/v3CuaAgentHandler.ts can unintentionally hijack a pre-existing page element with id="__sh-select-overlay", potentially clearing/restyling app UI when any select opens — only reuse helper-owned overlays and create a fresh element when ownership is unknown.
  • packages/core/lib/v3/v3.ts and packages/evals/tasks/bench/agent/onlineMind2Web.ts leave newly enabled Yutori excludeTools/output and prompt-selection behavior under-covered, so forwarding regressions could slip through silently; packages/core/lib/v3/agent/AgentProvider.ts docs are also stale — add V3/provider-resolution and prompt-selection tests and align the option docs before merge.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/evals/tasks/bench/agent/onlineMind2Web.ts">

<violation number="1" location="packages/evals/tasks/bench/agent/onlineMind2Web.ts:52">
P3: Navigator prompt selection has no regression coverage; add focused cases asserting `yutori/n1.5-latest` omits `systemPrompt` and a non-Yutori model retains it.

(Based on your team's feedback about unit-test coverage.) [FEEDBACK_USED].</violation>
</file>

<file name="packages/core/lib/v3/agent/AgentProvider.ts">

<violation number="1" location="packages/core/lib/v3/agent/AgentProvider.ts:52">
P3: Yutori users are documented away from both newly enabled execute options. Update `AgentExecuteOptionsBase.excludeTools` and `output` docs to describe the Navigator CUA exception and its server-side result behavior.</violation>
</file>

<file name="packages/core/lib/v3/v3.ts">

<violation number="1" location="packages/core/lib/v3/v3.ts:2099">
P3: Yutori `excludeTools`/`output` support now depends on V3 resolving and forwarding this map, but that wiring has no coverage. Add V3-level cases for model-ID and explicit-provider resolution so a regression does not silently restore the unsupported-feature error.

(Based on your team's feedback about adding unit tests for new behavior.) [FEEDBACK_USED].</violation>
</file>

<file name="packages/core/lib/v3/handlers/v3CuaAgentHandler.ts">

<violation number="1" location="packages/core/lib/v3/handlers/v3CuaAgentHandler.ts:98">
P3: A page already using `id="__sh-select-overlay"` can have its application element emptied and restyled when an agent opens any select. Mark helper-owned overlays and create a new element when the matching ID is not owned by the shim.</violation>

<violation number="2" location="packages/core/lib/v3/handlers/v3CuaAgentHandler.ts:98">
P2: Selecting an open-shadow-root `<select>` fails because its opening `mousedown` is retargeted to the shadow host at the document listener, which immediately closes the overlay. Check `e.composedPath()` for the active select (or skip shadow-root selects) before treating the event as outside.</violation>
</file>

<file name="packages/core/lib/v3/agent/YutoriCUAClient.ts">

<violation number="1" location="packages/core/lib/v3/agent/YutoriCUAClient.ts:412">
P2: Long Navigator runs retain every full base64 screenshot despite request trimming, causing heap growth proportional to steps. Trim the persistent history before sending so dropped images can be collected.</violation>
</file>

<file name="packages/core/lib/v3/agent/utils/replaceNativeSelect.ts">

<violation number="1" location="packages/core/lib/v3/agent/utils/replaceNativeSelect.ts:46">
P2: On pages with a positioned body or html element, the overlay offsets are evaluated from that ancestor while the select rectangle is viewport-relative, shifting the list away from screenshot click coordinates. Use viewport-fixed positioning with rectangle coordinates, or convert coordinates to the actual offset parent.</violation>

<violation number="2" location="packages/core/lib/v3/agent/utils/replaceNativeSelect.ts:112">
P2: Selects below a second open shadow root never receive the shim, leaving their native popup unselectable by coordinate actions. Recursively walk each discovered open shadow root rather than querying only the document's direct roots.</violation>
</file>
Architecture diagram
sequenceDiagram
    participant User as User/Client Code
    participant Stagehand as Stagehand V3
    participant Handler as V3CuaAgentHandler
    participant AgentProv as AgentProvider
    participant Navigator as YutoriCUAClient
    participant YutoriAPI as Yutori API (api.yutori.com)
    participant Browser as Browser (Playwright Page)
    participant SelectShim as Native Select Overlay

    Note over User,SelectShim: CUA Agent Execution with Yutori Navigator n1.5

    User->>Stagehand: stagehand.agent({ mode:"cua", model:"yutori/n1.5-latest", tools })
    Stagehand->>AgentProv: getAgentProvider("yutori/n1.5-latest")
    AgentProv-->>Stagehand: "yutori"
    Stagehand->>Navigator: new YutoriCUAClient(...)
    Note over Navigator: Strips "yutori/" prefix -> "n1.5-latest"
    Stagehand-->>User: agent instance

    User->>Stagehand: agent.execute({ instruction, maxSteps, output?, excludeTools? })
    Stagehand->>Stagehand: validateExperimentalFeatures(cuaSupportedExecuteOptions=yutori)
    Note over Stagehand: Accepts excludeTools & output for yutori
    Stagehand->>Handler: execute()
    Handler->>Navigator: setScreenshotProvider()
    Handler->>Handler: define screenshotProvider callback
    Handler->>Navigator: setActionHandler()

    loop Each Step (until done or maxSteps)
        Navigator->>Handler: captureScreenshot()
        Handler->>Browser: page.evaluate(REPLACE_NATIVE_SELECT_SCRIPT)
        Browser->>SelectShim: Inject or reuse overlay (idempotent)
        Handler->>Browser: page.screenshot({fullPage:false, type:"png"})
        Browser-->>Handler: screenshot buffer (base64)
        Handler-->>Navigator: base64 image

        Navigator->>Navigator: cloneMessagesForRequest()
        Navigator->>Navigator: trimImagesToFit() (drop old screenshots if oversize)
        Navigator->>Navigator: buildRequest({model, messages, temperature, tool_set:"browser_tools_core-20260403", disable_tools, json_schema?, tools?})

        alt Custom tools provided
            Navigator->>Navigator: customToolParams() -> function tools
        end

        alt output schema provided
            Navigator->>Navigator: include json_schema
        end

        Navigator->>YutoriAPI: POST /v1/chat/completions
        Note over Navigator,YutoriAPI: Non-streaming, extra params (tool_set, disable_tools, json_schema)
        YutoriAPI-->>Navigator: ChatCompletion response (assistant message with tool_calls or content)

        opt response has parsed_json (structured output)
            Navigator->>Navigator: store parsedJson
        end

        alt No tool_calls → Done
            Navigator->>Navigator: completion!
            Navigator-->>Handler: AgentResult { success: true, output: parsedJson? }
            Handler-->>Stagehand: result
            Stagehand-->>User: AgentResult

        else Max steps reached
            Navigator->>Navigator: formatStopAndSummarize(instruction)
            Navigator->>YutoriAPI: Send stop-and-summarize message (last screenshot)
            YutoriAPI-->>Navigator: summary text
            Navigator->>Navigator: stop and summarize
            Navigator-->>Handler: AgentResult { success: true, message }
            Handler-->>Stagehand: result

        else Tool calls received
            loop For each tool_call
                Navigator->>Navigator: denormalizeCoordinates(call.args)
                Note over Navigator: 1000x1000 → viewport pixels
                alt Unsupported/modifier keys on click/scroll
                    Navigator->>Navigator: [ERROR] modifier keys not supported
                else Valid action
                    Navigator->>Handler: actionHandler(action)
                    Handler->>Browser: perform action (click, type, scroll, etc.)
                    alt Action succeeds
                        Browser-->>Handler: ok
                    else Action throws
                        Handler->>Handler: catch error
                        Handler-->>Navigator: error as [ERROR] result
                    end
                end
                Navigator->>Navigator: record tool result (with "Current URL: {url}")
            end

            Handler->>Navigator: updateClientUrl() (finally block – post-action URL refresh)
            Note over Handler,Navigator: Restored per-step refresh so tool result reports current URL

            Navigator->>Navigator: append tool results to messages
            Navigator->>Navigator: continue loop
        end
    end
Loading

Tip: cubic used a learning from your PR history. Let your coding agent read cubic learnings directly with the cubic MCP.

Re-trigger cubic

Comment thread packages/core/lib/v3/agent/YutoriCUAClient.ts Outdated
// never changes). The overlay re-renders the options as real, clickable
// in-page DOM. Idempotent, so it is safe to re-run every step; failures
// are swallowed.
await page.evaluate(REPLACE_NATIVE_SELECT_SCRIPT).catch(() => {});

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.

P2: Selecting an open-shadow-root <select> fails because its opening mousedown is retargeted to the shadow host at the document listener, which immediately closes the overlay. Check e.composedPath() for the active select (or skip shadow-root selects) before treating the event as outside.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/core/lib/v3/handlers/v3CuaAgentHandler.ts, line 98:

<comment>Selecting an open-shadow-root `<select>` fails because its opening `mousedown` is retargeted to the shadow host at the document listener, which immediately closes the overlay. Check `e.composedPath()` for the active select (or skip shadow-root selects) before treating the event as outside.</comment>

<file context>
@@ -87,6 +88,15 @@ export class V3CuaAgentHandler {
+      // never changes). The overlay re-renders the options as real, clickable
+      // in-page DOM. Idempotent, so it is safe to re-run every step; failures
+      // are swallowed.
+      await page.evaluate(REPLACE_NATIVE_SELECT_SCRIPT).catch(() => {});
+
       const screenshotBuffer = await page.screenshot({
</file context>

(last as { content: unknown }).content = content;

// Trim old screenshots from a request copy to stay under the size cap.
const requestMessages = cloneMessagesForRequest(this.messages);

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.

P2: Long Navigator runs retain every full base64 screenshot despite request trimming, causing heap growth proportional to steps. Trim the persistent history before sending so dropped images can be collected.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/core/lib/v3/agent/YutoriCUAClient.ts, line 412:

<comment>Long Navigator runs retain every full base64 screenshot despite request trimming, causing heap growth proportional to steps. Trim the persistent history before sending so dropped images can be collected.</comment>

<file context>
@@ -0,0 +1,907 @@
+    (last as { content: unknown }).content = content;
+
+    // Trim old screenshots from a request copy to stay under the size cap.
+    const requestMessages = cloneMessagesForRequest(this.messages);
+    const { removed, sizeBytes } = trimImagesToFit(
+      requestMessages,
</file context>

Comment thread packages/core/lib/v3/agent/YutoriCUAClient.ts Outdated
Comment thread packages/core/lib/v3/agent/utils/replaceNativeSelect.ts Outdated
Comment thread packages/core/lib/v3/agent/utils/validateExperimentalFeatures.ts
export const CUA_SUPPORTED_EXECUTE_OPTIONS: Partial<
Record<AgentProviderType, { excludeTools?: boolean; output?: boolean }>
> = {
yutori: { excludeTools: true, output: true },

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.

P3: Yutori users are documented away from both newly enabled execute options. Update AgentExecuteOptionsBase.excludeTools and output docs to describe the Navigator CUA exception and its server-side result behavior.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/core/lib/v3/agent/AgentProvider.ts, line 52:

<comment>Yutori users are documented away from both newly enabled execute options. Update `AgentExecuteOptionsBase.excludeTools` and `output` docs to describe the Navigator CUA exception and its server-side result behavior.</comment>

<file context>
@@ -37,6 +38,18 @@ export const modelToAgentProviderMap: Record<string, AgentProviderType> = {
+export const CUA_SUPPORTED_EXECUTE_OPTIONS: Partial<
+  Record<AgentProviderType, { excludeTools?: boolean; output?: boolean }>
+> = {
+  yutori: { excludeTools: true, output: true },
 };
 
</file context>

typeof instructionOrOptions === "object"
? instructionOrOptions
: null,
cuaSupportedExecuteOptions,

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.

P3: Yutori excludeTools/output support now depends on V3 resolving and forwarding this map, but that wiring has no coverage. Add V3-level cases for model-ID and explicit-provider resolution so a regression does not silently restore the unsupported-feature error.

(Based on your team's feedback about adding unit tests for new behavior.) .

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/core/lib/v3/v3.ts, line 2099:

<comment>Yutori `excludeTools`/`output` support now depends on V3 resolving and forwarding this map, but that wiring has no coverage. Add V3-level cases for model-ID and explicit-provider resolution so a regression does not silently restore the unsupported-feature error.

(Based on your team's feedback about adding unit tests for new behavior.) .</comment>

<file context>
@@ -2078,6 +2096,7 @@ export class V3 {
                 typeof instructionOrOptions === "object"
                   ? instructionOrOptions
                   : null,
+              cuaSupportedExecuteOptions,
             });
 
</file context>

// never changes). The overlay re-renders the options as real, clickable
// in-page DOM. Idempotent, so it is safe to re-run every step; failures
// are swallowed.
await page.evaluate(REPLACE_NATIVE_SELECT_SCRIPT).catch(() => {});

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.

P3: A page already using id="__sh-select-overlay" can have its application element emptied and restyled when an agent opens any select. Mark helper-owned overlays and create a new element when the matching ID is not owned by the shim.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/core/lib/v3/handlers/v3CuaAgentHandler.ts, line 98:

<comment>A page already using `id="__sh-select-overlay"` can have its application element emptied and restyled when an agent opens any select. Mark helper-owned overlays and create a new element when the matching ID is not owned by the shim.</comment>

<file context>
@@ -87,6 +88,15 @@ export class V3CuaAgentHandler {
+      // never changes). The overlay re-renders the options as real, clickable
+      // in-page DOM. Idempotent, so it is safe to re-run every step; failures
+      // are swallowed.
+      await page.evaluate(REPLACE_NATIVE_SELECT_SCRIPT).catch(() => {});
+
       const screenshotBuffer = await page.screenshot({
</file context>

Comment thread packages/core/lib/v3/agent/YutoriCUAClient.ts
lawrencechen98 and others added 2 commits July 14, 2026 10:37
- YutoriCUAClient: implement AgentClient's ScreenshotProviderResult provider
  type and build the data URL from base64/mediaType (was Promise<string>,
  which broke against upstream's updated screenshot contract)
- YutoriCUAClient: fall back to 5s when a non-finite wait duration is given
- YutoriCUAClient: normalize scroll direction (lowercase + trim)
- replaceNativeSelect: render option.label (native display text) not .text
- tests: update screenshot-provider mocks to the {base64, mediaType} shape

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- YutoriCUAClient: custom tools can no longer shadow Navigator core action
  names — core actions take precedence at dispatch
- replaceNativeSelect: options inside a disabled <optgroup> now render as
  disabled (option.disabled does not reflect the ancestor group)
- docs: note excludeTools/output ARE supported for Navigator CUA
  (validateExperimentalFeatures + AgentExecuteOptionsBase), not universally
  unsupported

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@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.

2 issues found across 4 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/core/lib/v3/agent/utils/replaceNativeSelect.ts">

<violation number="1" location="packages/core/lib/v3/agent/utils/replaceNativeSelect.ts:68">
P3: Disabled `<optgroup>` behavior has no regression test, although it differs from the existing directly-disabled-option case. Add a test asserting its child row cannot change the select value.

(Based on your team's feedback about unit tests for new behavior.) [FEEDBACK_USED]</violation>
</file>

<file name="packages/core/lib/v3/agent/YutoriCUAClient.ts">

<violation number="1" location="packages/core/lib/v3/agent/YutoriCUAClient.ts:597">
P3: Core/custom precedence has no regression test, so a future edit can reintroduce browser-action hijacking without failing CI. Add a custom `left_click` tool test that asserts its `execute` is skipped and the action handler receives the core click.

(Based on your team's feedback about adding unit tests for new behavior.) [FEEDBACK_USED]</violation>
</file>

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

Re-trigger cubic

Array.from(select.options).forEach((opt) => {
// Native selects disable every option inside a disabled <optgroup>, but
// option.disabled does not reflect the ancestor group — derive it here.
const disabled =

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.

P3: Disabled <optgroup> behavior has no regression test, although it differs from the existing directly-disabled-option case. Add a test asserting its child row cannot change the select value.

(Based on your team's feedback about unit tests for new behavior.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/core/lib/v3/agent/utils/replaceNativeSelect.ts, line 68:

<comment>Disabled `<optgroup>` behavior has no regression test, although it differs from the existing directly-disabled-option case. Add a test asserting its child row cannot change the select value.

(Based on your team's feedback about unit tests for new behavior.) </comment>

<file context>
@@ -63,12 +63,19 @@ export const REPLACE_NATIVE_SELECT_SCRIPT = `(() => {
     Array.from(select.options).forEach((opt) => {
+      // Native selects disable every option inside a disabled <optgroup>, but
+      // option.disabled does not reflect the ancestor group — derive it here.
+      const disabled =
+        opt.disabled ||
+        (opt.parentNode &&
</file context>

// Custom user tool (stagehand.agent({ tools })): execute it directly —
// it is not a page action, so it never goes through the action handler —
// and record it in the trajectory like the other CUA providers do.
if (this.tools && name in this.tools && !CORE_ACTION_NAMES.has(name)) {

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.

P3: Core/custom precedence has no regression test, so a future edit can reintroduce browser-action hijacking without failing CI. Add a custom left_click tool test that asserts its execute is skipped and the action handler receives the core click.

(Based on your team's feedback about adding unit tests for new behavior.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/core/lib/v3/agent/YutoriCUAClient.ts, line 597:

<comment>Core/custom precedence has no regression test, so a future edit can reintroduce browser-action hijacking without failing CI. Add a custom `left_click` tool test that asserts its `execute` is skipped and the action handler receives the core click.

(Based on your team's feedback about adding unit tests for new behavior.) </comment>

<file context>
@@ -572,7 +594,7 @@ export class YutoriCUAClient extends AgentClient {
     // it is not a page action, so it never goes through the action handler —
     // and record it in the trajectory like the other CUA providers do.
-    if (this.tools && name in this.tools) {
+    if (this.tools && name in this.tools && !CORE_ACTION_NAMES.has(name)) {
       const action: AgentAction = {
         type: "custom_tool",
</file context>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

external-contributor:awaiting-approval Waiting for a stagehand team member to approve the latest external commit. external-contributor Tracks PRs mirrored from external contributor forks.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants