10205 Commits

Author SHA1 Message Date
Magnus Müller 5c892e013a chore: pin release dependencies for 0.13.10 (#5670)
## Summary

- release Browser Use 0.13.10 with exact pins for browser-harness
0.1.13, Pydantic 2.13.5, pydantic-settings 2.15.0, MCP 2.1.1, and pypdf
6.16.2
- migrate both MCP servers and the MCP client/controller to the MCP 2
low-level server API
- pin Hatchling 1.32.0
- make pydantic-settings an explicit runtime dependency instead of
relying on MCP 1 to provide it transitively

The publish-workflow uv pin is split into #5669 because repository
policy requires workflow-guardians approval for `.github/workflows/**`.
This PR replaces #5667 with identical source tree changes but clean
history so the workflow-only rule is scoped correctly.

## Security

- pypdf 6.16.2 resolves all three open Dependabot alerts on main
(GHSA-23w6-3w8w-8484, GHSA-763m-79hh-57f2, GHSA-jp53-mhqp-8xcg)
- all external direct, optional, dev, and build dependencies in
pyproject.toml are exact-pinned
- all newly selected PyPI artifacts checked have digital provenance
attestations
- isolated installed-runtime `pip-audit`: no known vulnerabilities

## Verification

- all GitHub test shards passed on the identical code tree in #5667
- code style, type checker, CodeQL, GitGuardian, and Cubic review passed
- clean wheel installs and CLI smoke passed on macOS, Linux, Windows,
and uvx
- real stdio MCP initialization/list-tools passed for both `browser-use
--mcp` and `browser-use --cli-mcp`
- 204 focused PDF/save/filesystem tests passed
- wheel and sdist build passed

A local live OpenAI agent probe could navigate to example.com, but the
configured local OpenAI credential returns 401. GitHub model adapter
tests passed.

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Pins release dependencies for 0.13.10 and migrates the MCP servers and
client to MCP 2's low-level request-handler API.

- Exact pins: `browser-harness==0.1.13`, `pydantic==2.13.5`,
`pydantic-settings==2.15.0`, `mcp==2.1.1`, `pypdf==6.16.2`,
`hatchling==1.32.0`.
- `pydantic-settings` is now an explicit runtime dependency instead of
coming transitively via MCP 1.
- The MCP server now reports the real package version instead of the
hardcoded `0.1.0`.

**Migration**
- Request handlers registered via `add_request_handler` replace the
`list_tools`/`call_tool` decorators.
- Tool schemas and results use MCP 2 field names: `input_schema`,
`is_error`, `read_only_hint`.
- Tool failures and unknown tool calls now return `is_error=True`
results instead of plain error text.

**Security**
- `pypdf==6.16.2` fixes three Dependabot alerts (GHSA-23w6-3w8w-8484,
GHSA-763m-79hh-57f2, GHSA-jp53-mhqp-8xcg).

<sup>Written for commit 743f630923.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/browser-use/browser-use/pull/5670?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->
0.13.10
2026-09-03 20:28:40 -07:00
MagMueller 743f630923 test: satisfy MCP handler context typing 2026-09-03 20:24:29 -07:00
MagMueller afbc714a60 fix: report unknown MCP tools as errors 2026-09-03 20:23:43 -07:00
MagMueller b1ae95ec81 chore: pin release dependencies for 0.13.10 2026-09-03 20:17:35 -07:00
Magnus Müller 64c1f46e33 chore: release 0.13.9 with Browser Harness 0.1.12 (#5666)
## Summary

- bump `browser-use` to 0.13.9
- pin `browser-harness==0.1.12`
- sync the bundled Browser Use skill from the immutable Browser Harness
v0.1.12 tag
- allow the documented iTerm app name in codespell

This ships the persistent local approval flow from browser-harness
v0.1.12, including the `mac-approve` guidance.

## Validation

- `uv run pre-commit run --all-files --show-diff-on-failure`
- isolated full suite: 1,136 passed, 34 skipped
- `uv build --wheel`
- clean wheel install verified browser-use 0.13.9, browser-harness
0.1.12, and packaged `mac-approve` skill guidance
- live Anthropic provider test passed (`claude-sonnet-4-6`)
- end-to-end agent smoke passed: opened Hacker News and extracted the
top three stories with points and comment counts in two steps, zero
errors

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Ships `browser-use` 0.13.9 with `browser-harness` 0.1.12. This replaces
the standalone `mac-approve` run with a persistent approval flow: keep
the original browser command running and call `mac-approve` with the
matching `BU_NAME`.

- Syncs the bundled Browser Use skill from the Browser Harness v0.1.12
tag.
- Documents `BH_TAB_MARKER=0` to leave page titles unchanged.
- Adds guidance to avoid slow per-character typing for long text.
- Allows the documented `iterm` app name in codespell.

<sup>Written for commit 0e0983e30e.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/browser-use/browser-use/pull/5666?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->
0.13.9
2026-09-03 18:29:16 -07:00
MagMueller 0e0983e30e Release 0.13.9 with Browser Harness 0.1.12 2026-09-03 18:26:02 -07:00
Magnus Müller 5b50d1f511 fix(browser): restore session handlers after event bus reset (#5230)
## Summary

- extract BrowserSession handler registration into a reusable helper
- restore session handlers after `stop()` replaces the event bus
- restore session handlers after `kill()` replaces the event bus
- add regression coverage for both reset paths

## Problem

`BrowserSession.stop()` and `BrowserSession.kill()` clear the current
event
bus and replace it with a new `ResilientEventBus`.

The BrowserSession handlers are registered only from
`model_post_init()`,
which runs when the session object is constructed. The replacement event
bus
therefore has no handlers, including no `BrowserStartEvent` handler.

As a result, calling `start()` on the same BrowserSession after `stop()`
or
`kill()` dispatches the event without reconnecting or launching a
browser.

## Solution

Move BrowserSession handler registration into a dedicated helper and
invoke
it both during model initialization and after creating a replacement
event
bus.

The existing duplicate-handler checks remain in place.

## Testing

- Added a parameterized regression test covering both `stop()` and
`kill()`.
- Verified that the replacement bus is a new instance.
- Verified that its complete BrowserSession handler mapping matches the
  original bus.

Commands run:

```bash
pre-commit run --files \
  browser_use/browser/session.py \
  tests/ci/browser/test_session_start.py

pytest \
  tests/ci/browser/test_session_start.py::TestBrowserSessionEventSystem::test_event_bus_initialization \
  tests/ci/browser/test_session_start.py::TestBrowserSessionEventSystem::test_session_handlers_registered_after_event_bus_reset \
  tests/ci/browser/test_session_start.py::TestBrowserSessionEventSystem::test_event_handlers_registration \
  tests/ci/test_event_bus_resilience.py -q

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Fixes `BrowserSession` losing its event handlers when `stop()` or `kill()` replaces the event bus, so calling `start()` again now reconnects and launches as expected.

- **Bug Fixes**
  - Re-registers session handlers after `stop()` or `kill()` creates a new `ResilientEventBus`.
  - Keeps duplicate-handler safeguards.
  - Adds regression tests covering both reset paths.

<sup>Written for commit 780274dc69. Summary will update on new commits.</sup>

<a href="https://cubic.dev/pr/browser-use/browser-use/pull/5230?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->
2026-09-03 11:10:13 -07:00
Magnus Müller 780274dc69 Merge branch 'main' into fix/session-handler-registration 2026-09-03 11:08:54 -07:00
Magnus Müller 89637449ab fix: detect browser process exit early in _wait_for_cdp_url (#4599)
Fixes #4471 (partial — addresses Part 1: Chromium launch watchdog
timeout)

## Problem

When Chrome/Chromium fails to start on headless Linux (e.g. due to
missing
sandbox capabilities, a missing virtual display, or absent system
dependencies), `_wait_for_cdp_url` previously polled the CDP endpoint
for the
entire 30-second timeout before raising a generic `TimeoutError`:

```
BrowserStartEvent (30s TIMEOUT)
  BrowserLaunchEvent (30s)
    DownloadsWatchdog (0s) OK
    LocalBrowserWatchdog (30s) INTERRUPTED
```

This gave users no indication of *why* the browser failed to start or
how to
fix it — they just saw a timeout.

Additionally, `_wait_for_cdp_url` used the deprecated
`asyncio.get_event_loop()` inside an `async def`, which should use
`asyncio.get_running_loop()` per Python 3.10+ best practice.

## Solution

- Added an optional `process: psutil.Process | None = None` parameter to
  `_wait_for_cdp_url`. When provided, the polling loop checks on every
iteration whether the browser process is still alive. If it has exited,
a
`RuntimeError` is raised **immediately** (< 0.1 s instead of 30 s) with
a
  message that points users toward the likely fixes:
`--no-sandbox` for Docker/headless environments, or `Xvfb` for headless
  Linux without a display.
- `psutil.AccessDenied` is silently ignored so the CDP poll continues
  normally on systems where process inspection is restricted.
- `_launch_browser` now passes `process=process` to `_wait_for_cdp_url`.
- Replaced `asyncio.get_event_loop().time()` with
  `asyncio.get_running_loop().time()` throughout the method.

## Testing

The change is backward-compatible: `process` defaults to `None`, so all
existing callers that don't pass a process are unaffected. The only new
behaviour is for the error path (process exits before CDP is ready),
which
previously resulted in a 30-second hang followed by an uninformative
`TimeoutError`.

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Fixes #4471 (part 1) by detecting early browser process exit in
`_wait_for_cdp_url`, replacing the 30s CDP timeout with an immediate,
actionable error when Chromium fails to start on headless Linux.

- **Bug Fixes**
- Added optional `process: psutil.Process | None` to
`_wait_for_cdp_url`; raises a descriptive `RuntimeError` with hints
(`--no-sandbox`, `Xvfb`) when the browser exits, ignoring
`psutil.AccessDenied`.
- `_launch_browser` now passes the process; default remains `None` for
backward compatibility.

- **Refactors**
- Replaced `asyncio.get_event_loop().time()` with
`asyncio.get_running_loop().time()`.

<sup>Written for commit f4e2bb1f74.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/browser-use/browser-use/pull/4599?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->
2026-09-03 11:07:43 -07:00
Magnus Müller f4e2bb1f74 Merge branch 'main' into fix/issue-4471-fail-fast-browser-startup 2026-09-03 11:06:16 -07:00
Magnus Müller 1346f8caf4 Merge branch 'main' into fix/session-handler-registration 2026-09-03 11:06:15 -07:00
Magnus Müller 102cdb4f99 fix(dom): expose image context for directly clickable images (#5586)
## Summary

- include the existing bounded image context when the interactive node
is itself an `img`, not only when an image is below an interactive
parent
- keep the existing three-context and 100-descendant limits, with the
direct image counting toward the context limit without consuming
descendant budget
- normalize URL-parser-ignored controls before rejecting `data:`
sources, and omit oversized raw image-context attributes before
processing

## Why

Follow-up to #5541 (and its fix for #4312). That change starts traversal
at `node.children`, but production clickability detection can mark an
`img` itself interactive through click listeners, interactive
attributes/roles, icon heuristics, or pointer cursor. Because `src` is
not in `DEFAULT_INCLUDE_ATTRIBUTES`, an unlabeled directly clickable
image still serializes without the filename context that #5541
introduced for child images.

The same sanitizer now handles the direct node. During adversarial
review, URL preprocessing was also aligned with browser behavior so
obfuscated `data:` schemes using C0 controls cannot expose inline
payloads through `image_src`.

## Validation

- exact-current-main red/green harness: direct image context is empty
before and contains the sanitized filename after
- isolated serializer regressions for query/fragment stripping,
browser-normalized `data:` rejection, oversized raw sources, and
direct-plus-descendant context limits
- `python3 -m py_compile` for both changed files
- repository-configured Ruff rules for changed code (apart from the
unchanged current-main import-order baseline in the newly merged test
file)
- repository-configured `ruff format --check` for both changed files

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Exposes sanitized image context for directly clickable `img` elements,
not just images nested under interactive parents.

- Direct images now contribute their own `alt`, `title`, `aria-label`,
and `src` filename to the LLM DOM while preserving the existing
three-context and 100-descendant limits.
- Normalizes `data:` URLs by stripping C0 controls and spaces before
rejecting them, matching browser behavior so obfuscated schemes don't
leak inline payloads.
- Skips oversized image context attributes (>4096 chars) before scanning
or serializing them.

<sup>Written for commit 4e09955e57.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/browser-use/browser-use/pull/5586?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->
2026-09-03 11:04:41 -07:00
Magnus Müller 57f60e4cf6 Merge branch 'main' into fix/issue-4471-fail-fast-browser-startup 2026-09-03 11:03:22 -07:00
Magnus Müller 3042d8c1df Merge branch 'main' into fix/session-handler-registration 2026-09-03 11:03:22 -07:00
Magnus Müller 4e09955e57 Merge branch 'main' into r266/direct-interactive-image-context 2026-09-03 11:03:22 -07:00
Magnus Müller ada68b4833 fix(browser): support literal plus in send_keys (#5582)
Fix send_keys handling for literal plus characters.

Previously, send_keys(keys="+") was parsed as an empty key combination,
causing empty key events instead of inserting +. Shortcuts such as
Control++ were also parsed incorrectly.

This change:

Sends literal + through the character-input path.
Supports shortcuts like Control++.
Preserves existing shortcuts such as Control+a and special keys like
Enter.
Prevents empty key events from being dispatched.
Testing
Added regression tests for +, C++, and Control++.
Verified existing Control+a and Enter behavior.
Focused test suite: 4 passed.
Fixes #5569

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Fixes `send_keys` handling so a literal `+` is sent as a character
instead of an empty key event, and shortcuts like `Control++` work
correctly. Previously, `send_keys(keys="+")` dispatched empty key events
and `Control++` was parsed incorrectly.

- `+` and text containing `+` (like `C++`) now go through the
character-input path.
- `Control++` is recognized as a shortcut with `+` as the main key.
- Existing shortcuts like `Control+a` and special keys like `Enter`
behave as before.
- Adds regression tests for `+`, `C++`, and `Control++`.

Fixes #5569.

<sup>Written for commit 8043fec5e6.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/browser-use/browser-use/pull/5582?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->
2026-09-03 09:01:10 -07:00
Magnus Müller 501e49ccc7 Merge branch 'main' into r266/direct-interactive-image-context 2026-09-03 09:00:45 -07:00
Magnus Müller b78b3fcebd Merge branch 'main' into fix/session-handler-registration 2026-09-03 09:00:42 -07:00
Magnus Müller 8043fec5e6 Merge branch 'main' into fix/5569-send-keys-plus 2026-09-02 22:44:36 -07:00
Magnus Müller 7976b64553 Merge pull request #5500 from Faseeh06/fix/switch-tab-error-reporting
Fixes #5486

`switch` returned a non-error `ActionResult` on both of its failure
paths (a stale/unknown `tab_id`, and a `SwitchTabEvent` that produced no
result), so callers had no way to tell a failed switch from a real one.
The false "Switched to tab #..." claim was written into
`long_term_memory`, so subsequent agent steps reasoned from a tab that
was never actually reached.

This raises `BrowserError` on both failure paths instead, following the
same convention `upload_file` already uses in this file. The error
message preserves the actual underlying cause (e.g. the stale tab_id)
rather than a generic string, so `ActionResult.error` carries actionable
information back to the agent.

## Changes
- `switch()` now raises `BrowserError` on both failure paths (stale
`tab_id`, and a missing `SwitchTabEvent` result), instead of returning a
success-shaped `ActionResult`.
- Added regression tests covering the failing `tab_id` case and
confirming the happy path is unaffected.

## Test plan
- [x] Verified against a real Chrome instance: failure path sets
`ActionResult.error` with the actual cause, happy path unaffected
- [x] New tests added to `tests/ci/browser/test_tabs.py`
- [x] `ruff check` / `ruff format --check` clean

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Reports failed tab switches as errors instead of silent successes.
Previously `switch()` returned a success-shaped `ActionResult` for a
stale/unknown `tab_id` or when `SwitchTabEvent` produced no result; now
both paths raise `BrowserError` with the real cause so callers see
`ActionResult.error` and no false success.

- Uses `event_result(raise_if_any=True)` so handler failures surface
their actual cause instead of a generic message.
- Success path is unchanged; it still returns "Switched to tab #…".
- Adds regression tests for a nonexistent `tab_id`, the "no result" path
via an `event_bus.dispatch` monkeypatch, and the happy path; uses a
static `_TabActionModel` to satisfy pyright.
- Migration: after calling `switch`, check `ActionResult.error` instead
of inferring success from memory strings.

<sup>Written for commit f3a6becbf0.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/browser-use/browser-use/pull/5500?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->
2026-09-02 17:37:54 -07:00
Magnus Müller f3a6becbf0 Merge branch 'main' into fix/switch-tab-error-reporting 2026-09-02 17:34:56 -07:00
Magnus Müller fffcc57119 Show the live value of pre-filled inputs to the agent (#5650)
## Problem

Fixes #5647. The DOM the agent sees only carried the static `value`
attribute. When JavaScript, autofill, or a framework binding fills a
field, the value lives in the element property, so pre-filled inputs
looked empty and the agent retyped over them or skipped them. Form
filling is one of the most common tasks, so this shows up a lot.

## Fix

`DOMSnapshot.captureSnapshot` already returns `inputValue`, `textValue`,
and `inputChecked`. This reads them once per document, keeps them on
`EnhancedSnapshotNode`, and surfaces the live value as the `value`
attribute for `input` and `textarea` nodes so the serializer and
`get_meaningful_text_for_llm` pick it up without further changes.
Password, file, and hidden inputs are skipped.

## Proof

`tests/ci/test_dom_live_input_value.py` loads a page whose fields are
filled by script and asserts the values reach `selector_map` and the LLM
representation, and that a password value does not.

- on `main`: `AssertionError: assert None == 'Ada Lovelace'`
- with this change: passes; `test_dom_visibility.py` and
`test_ax_name_matching.py` still pass.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01UV3AvcViJQzAV75u67XZCF

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Fixes #5647 by showing the live value of pre-filled inputs to the agent.
The DOM snapshot only carried the static `value` attribute, so fields
filled by JavaScript, autofill, or framework bindings looked empty; now
the live value is surfaced for `input` and `textarea` nodes, and
checkboxes/radios show their live checked state. Password, file, hidden,
payment (`cc-*`), and one-time-code fields never expose their live
value.

**Details**
- Reads `inputValue`, `textValue`, and `inputChecked` from
`DOMSnapshot.captureSnapshot` and stores them on `EnhancedSnapshotNode`.
- Adds a regression test covering script-filled values, sensitive
exclusions, and checked state.

<sup>Written for commit 3b12a946cc.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/browser-use/browser-use/pull/5650?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->
2026-09-02 16:11:55 -07:00
Magnus Müller 3b12a946cc Keep sensitive live values out of the snapshot and surface checked state
Review follow-up: password, file, hidden, payment (cc-*) and one-time-code
fields never get their live value stored on EnhancedSnapshotNode, so it
cannot leak through __json__. Checkbox and radio inputs now show their live
checked state as the checked attribute.
2026-09-01 19:25:39 -07:00
Magnus Müller 5ffd3090ad Show the live value of pre-filled inputs to the agent
DOM attributes only carry the static value=... written in the HTML. When
JavaScript, autofill, or a framework fills a field, the value lives in the
element property, which DOMSnapshot exposes as inputValue/textValue. The
agent saw such fields as empty and retyped or skipped them (#5647).

Read inputValue, textValue, and inputChecked from the snapshot, keep them on
EnhancedSnapshotNode, and surface the live value as the value attribute for
input and textarea nodes. Password, file, and hidden inputs are left alone.

Fixes #5647
2026-09-01 19:25:39 -07:00
Magnus Müller 564007d3d6 fix(openrouter): validate client and response inputs (#5599)
## Summary
- keep `top_p` and `seed` on completion requests instead of passing them
to `AsyncOpenAI`
- load only `OPENROUTER_API_KEY` for OpenRouter and fail clearly when it
is missing
- turn empty `choices` responses into provider errors and preserve their
status codes

## Tests
- `uv run pytest -q tests/ci/models/test_llm_openrouter.py
tests/ci/test_openrouter_token_cost.py`
- `uv run pre-commit run --files browser_use/llm/openrouter/chat.py
tests/ci/models/test_llm_openrouter.py`

Closes #5598

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Fixes OpenRouter and Vercel AI Gateway client setup and response
handling so unsupported params stay on completion requests and
credential or response gaps surface as provider errors instead of
crashes. Closes #5598.

- `top_p` and `seed` go on completion requests, not to `AsyncOpenAI`.
- Only `OPENROUTER_API_KEY` is loaded; missing keys raise a 401 instead
of falling back to `OPENAI_API_KEY`.
- Empty `choices` raise a 502, and structured parse failures keep their
original status codes for both providers.
- Vercel AI Gateway now requires `AI_GATEWAY_API_KEY` or
`VERCEL_OIDC_TOKEN` and raises a 401 when missing.

<sup>Written for commit ca69ebdf89.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/browser-use/browser-use/pull/5599?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->
2026-09-01 11:51:05 -07:00
MagMueller ca69ebdf89 Merge remote-tracking branch 'origin/main' into agency/fix-openrouter-5598 2026-08-31 17:36:52 -07:00
Saurav Panda f0fe1030ac Add OrcaRouter as a named LLM provider (#5451)
Adds `ChatOrcaRouter` as a first-class LLM provider, wired the same way
as the existing OpenRouter integration.

## What's included

- **`browser_use/llm/orcarouter/chat.py`** — `ChatOrcaRouter`
(`provider='orcarouter'`), an OpenAI-compatible `BaseChatModel`
mirroring `ChatOpenRouter` (text + structured-output paths), default
base URL `https://api.orcarouter.ai/v1`.
- **`browser_use/llm/orcarouter/serializer.py`** —
`OrcaRouterMessageSerializer`, a thin delegate to the OpenAI message
serializer (OrcaRouter speaks the OpenAI wire format).
- **Registration** in both `browser_use/llm/__init__.py` and
`browser_use/__init__.py` (TYPE_CHECKING stubs + `_LAZY_IMPORTS` +
`__all__`), so `from browser_use import ChatOrcaRouter` works out of the
box.
- **`browser_use/tokens/service.py`** — OrcaRouter is a gateway with its
own pricing, so upstream model pricing is never incorrectly attributed
to it.
- **`.env.example`** — `ORCAROUTER_API_KEY=` entry.
- **`tests/ci/test_orcarouter.py`** — serializer, provider-defaults, and
token-cost guard tests.
- **`examples/models/orcarouter.py`** — usage example.

[OrcaRouter](https://www.orcarouter.ai) is an OpenAI-compatible model
routing gateway that brings 150+ models from OpenAI, Anthropic, Google,
DeepSeek, Qwen, MiniMax and xAI behind a single endpoint and API key.
Beyond routing, it runs gateway-level, zero-trust security for AI agents
on the same endpoint — screening every prompt/response and governing
every tool call on a default-deny basis, with no application code
changes. This PR registers it as a named provider so users can opt in
directly.

I'm an engineer on the OrcaRouter team.

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Adds `ChatOrcaRouter` as a first-class LLM provider using OrcaRouter's
OpenAI-compatible endpoint. Supports text and JSON-schema structured
outputs and correctly attributes token costs to `orcarouter/<model>`.

- `ChatOrcaRouter` (`provider='orcarouter'`) defaults to
`https://api.orcarouter.ai/v1`; returns string or Pydantic-validated
outputs and omits request-level params like `top_p`/`seed` from client
construction.
- Resolves the API key from the constructor or `ORCAROUTER_API_KEY`,
raising a 401 error if neither is set so `OPENAI_API_KEY` is never sent
to the gateway.
- Serializer delegates to the OpenAI serializer since OrcaRouter speaks
the OpenAI wire format; registered in both `__init__.py` files, with
`ORCAROUTER_API_KEY` added to `.env.example` plus an example and tests.
- Token-cost service namespaces models as `orcarouter/<model>` so
upstream model pricing is never attributed to the gateway.
- Guards empty `response.choices` with a descriptive
`ModelProviderError` and re-raises provider errors to preserve status
codes.

<sup>Written for commit be76e6ccee.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/browser-use/browser-use/pull/5451?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->
2026-08-31 16:20:53 -07:00
Saurav Panda be76e6ccee fix(orcarouter): resolve the API key explicitly instead of inheriting OPENAI_API_KEY
AsyncOpenAI falls back to OPENAI_API_KEY when api_key is unset, so
ChatOrcaRouter(model=...) with no key authenticated its requests to
api.orcarouter.ai with the user's OpenAI credentials. The shipped example
hit this path whenever ORCAROUTER_API_KEY was unset, and .env.example
documented a variable that nothing read.

Resolve api_key from the constructor then ORCAROUTER_API_KEY, and raise
ModelProviderError(401) when neither is set, matching ChatMistral.
2026-08-31 16:02:22 -07:00
Saurav Panda cf4aeaae8d Merge branch 'main' into add-orcarouter-provider 2026-08-31 15:54:27 -07:00
Magnus Müller 8f88f23a77 fix(browser): preserve default Chrome arg order (#5621)
## Why

`set(CHROME_DEFAULT_ARGS) - set(ignore_default_args)` makes Chrome's
default launch flags process-dependent. With `PYTHONHASHSEED=0`, the
first flag changes from `--disable-field-trial-config` to
`--disable-background-networking`.

## Fix

Filter the canonical list in place. Ignored flags are still removed, but
every remaining flag keeps its existing order.

## Proof

- Red: both order regressions fail on current `main` with
`PYTHONHASHSEED=0`.
- Green: both pass with seeds `0`, `1`, `2`, and `12345`.
- Existing security side-effect tests pass.
- All changed-file pre-commit hooks pass, including Ruff, format,
Pyright, and private-key detection.
- `git diff --check` passes.

## Customer impact

No API or flag membership changes. The patch only removes
nondeterministic reordering. Existing `disable_security` feature merging
remains covered.

This is the maintainer-owned replacement for the stalled contributor
implementations in #5470 and #5505; their branches were not edited.

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Fixes nondeterministic ordering of Chrome default launch args.
Previously the set difference in `get_args` could reorder flags
depending on `PYTHONHASHSEED`; now ignored flags are filtered from the
canonical list in place, with no flag membership or API behavior
changes. Adds regression tests covering default-order preservation and
ignored-flag removal.

<sup>Written for commit df2701e524.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/browser-use/browser-use/pull/5621?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->
2026-08-31 13:28:23 -07:00
Growth Radar QA df2701e524 fix(browser): preserve default Chrome arg order 2026-08-31 13:15:27 -07:00
MagMueller e0737a614f fix(vercel): preserve provider error status 2026-08-30 23:23:18 -07:00
MagMueller fb916aec56 fix(vercel): require gateway credentials 2026-08-30 23:16:08 -07:00
MagMueller 7d123b7628 Merge branch 'main' into agency/fix-openrouter-5598 2026-08-30 23:16:08 -07:00
Magnus Müller d379a32887 fix(browser): read persisted storage state as UTF-8 (#5359)
Storage state JSON is written with `encoding='utf-8'` and
`ensure_ascii=False`, but both file read paths currently use the
platform locale. On Windows, non-ASCII cookie or Web Storage values can
therefore fail to decode, and the watchdog continues without restoring
the expected session state.

This passes `encoding='utf-8'` to both the existing-state merge read and
the asynchronous startup load read. It does not change caller-selected
paths, permissions, backups, or dict-based loading.

The focused regressions verify the exact encoding argument, preserve a
non-ASCII value through load, and retain both existing Unicode and newly
captured cookies through merge/write.

Validation: 2 focused red-green tests; Ruff check/format; `py_compile`.

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Reads persisted storage state as UTF-8 so non-ASCII cookies and Web
Storage values no longer fail to decode, especially on Windows, and
sessions restore correctly. Both the merge read and async startup load
now use `encoding='utf-8'`; caller-selected paths, permissions, backups,
and dict-based loading are unchanged.

- **Bug Fixes**
- Force UTF-8 when reading existing JSON during merge and during async
startup load.
- Add tests asserting UTF-8 reads and preserving Unicode values and
cookie merge behavior.

<sup>Written for commit fa780c8e50.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/browser-use/browser-use/pull/5359?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->
2026-08-30 23:11:58 -07:00
Magnus Müller fa780c8e50 Merge branch 'main' into r266/storage-state-utf8 2026-08-30 23:04:21 -07:00
Magnus Müller 4f89716294 fix(agent): preserve downloaded file path order (#5453)
## Summary

- preserve caller-provided `available_file_paths` ordering when
downloads are discovered
- append new downloads in discovery order while deduplicating repeated
paths
- add a regression test covering existing inputs, ordered downloads, and
duplicate download entries

## Problem

`Agent._update_available_file_paths()` converted both the existing file
list and the latest downloads to sets, then rebuilt
`available_file_paths` from their union. Set iteration order is not the
order supplied by the caller or the browser, so the
`<available_file_paths>` block exposed to the model could change order
across processes.

## Fix

Use a set only for membership checks. Keep the original list as the
output source of truth and append each unseen download in first-seen
order.

## Verification

- regression test: RED on `main`, GREEN with this change
- `uv run pytest -q tests/ci/test_agent_download_paths.py
tests/ci/test_beta_agent.py -x`: 202 passed, 1 skipped
- `uv run pyright browser_use/agent/service.py
tests/ci/test_agent_download_paths.py`: 0 errors
- Ruff lint and format checks passed
- full `tests/ci` reached 479 passed / 33 skipped before one
pre-existing module-identity assertion failed; that exact test passed
when rerun alone

AI assistance was used to help inspect the codebase, draft the
regression test, and validate the change.

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Fixes unstable ordering of `available_file_paths` by preserving the
caller's list order and appending new downloads in discovery order.
Prevents reordering across processes and removes duplicates.

- **Bug Fixes**
- Replaced set-union logic with list + membership checks to keep
original order.
  - Append only unseen downloads in first-seen order; ignore duplicates.
- Added regression test covering input order, download order, and
duplicate entries.

<sup>Written for commit e785b615d2.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/browser-use/browser-use/pull/5453?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->
2026-08-30 22:50:02 -07:00
Magnus Müller e785b615d2 Merge branch 'main' into codex/fix-agent-download-path-order 2026-08-30 22:47:23 -07:00
Magnus Müller 34933c4d2c fix(agent): match URL negations as whole words (#5493)
## Summary

- match URL negation terms as whole words instead of arbitrary
substrings
- share the negation matcher across the standard and beta agent URL
extractors
- add regression coverage for both a false positive (`notable`) and an
explicit negation (`do not`)

## Problem

The direct-navigation URL extractors used substring checks for `not`. As
a result, normal prose such as `Open this notable site:
https://example.com` was treated as a negated instruction, so the agent
skipped its initial navigation action.

## Testing

- `python -m pytest -o addopts=''
tests/ci/test_beta_agent.py::test_beta_agent_exposes_task_helper_methods`
- `ruff check browser_use/utils.py browser_use/agent/service.py
browser_use/beta/service.py tests/ci/test_beta_agent.py`
- `ruff format --check browser_use/utils.py browser_use/agent/service.py
browser_use/beta/service.py tests/ci/test_beta_agent.py`

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Matches URL negations as whole words during direct-navigation
extraction, so prose like "notable" no longer causes false skips.
Previously the extractors substring-matched "not"; now both agents share
a word-boundary regex for never/not/don't (straight or curly
apostrophes) in the 20-character pre-URL context.

- Adds `URL_NEGATION_PATTERN` and `has_url_negation` in
`browser_use/utils.py`; used by `browser_use/agent/service.py` and
`browser_use/beta/service.py`.
- Removes per-extractor `excluded_words`; keeps the 20-character window;
injects the scheme after the negation check; updates debug messaging.
- Adds regression tests allowing "notable" and blocking "Do not open …"
for both agents.

<sup>Written for commit c05a826af8.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/browser-use/browser-use/pull/5493?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->
2026-08-30 19:20:02 -07:00
Magnus Müller c05a826af8 Merge branch 'main' into codex/fix-url-negation-boundaries 2026-08-30 19:17:36 -07:00
Magnus Müller 663089797e fix(llm): preserve zero max_retries for Anthropic Bedrock (#5348) 2026-08-30 19:16:30 -07:00
Magnus Müller 90eb2b42de Merge branch 'main' into fix/bedrock-zero-retries 2026-08-30 19:14:28 -07:00
Magnus Müller ad03ce5e32 Merge branch 'main' into codex/fix-url-negation-boundaries 2026-08-30 17:17:30 -07:00
Magnus Müller 5def56e6c0 fix(llm): preserve schema-keyword field names (#5605)
## What

Treat keys inside a schema `properties` map as user field names before
applying schema-keyword rules. This keeps fields named `description` or
`properties` from retaining dangling `$ref` values.

Fixes #5603

## Test

- `uv run pytest -q tests/ci/models/test_llm_schema_optimizer.py`
- `uv run pre-commit run --files browser_use/llm/schema.py
tests/ci/models/test_llm_schema_optimizer.py`

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Fixes schema optimization so user fields named `description` or
`properties` no longer keep dangling `$ref` values.

- Treats keys inside a `properties` map as field names before applying
schema-keyword rules.
- Simplifies `title` skipping, which now only applies outside
`properties`.
- Adds coverage for nested and aliased fields named after schema
keywords.

<sup>Written for commit b2507091aa.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/browser-use/browser-use/pull/5605?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->
2026-08-30 15:03:22 -07:00
MagMueller b2507091aa test: cover aliased schema keyword fields 2026-08-30 14:56:47 -07:00
MagMueller c48b1c9928 fix(llm): preserve schema-keyword field names 2026-08-30 14:48:44 -07:00
Magnus Müller 5fc228f4fd docs(cloud): add API v4 skill reference (#5591)
## Why

The official `cloud` skill still routes new hosted-agent integrations
through
API v2 or v3. Browser Use's current Cloud quickstart uses API v4, so
agents that
load the skill miss the current run, session, and workspace flow.

## What changed

- add a focused API v4 reference for Python, TypeScript, and REST
- route new hosted-agent setup to v4 while keeping v2 and v3 available
for existing integrations
- document the current browser boundary: v4 REST, explicit v3 SDK
namespace, and explicit stop required
- mark the old quickstart as the legacy v2 path

## Verification

- Cloud skill validator passed
- all 12 routed references resolve
- three Python examples parse
- six documented REST routes match the current v4 OpenAPI spec
- repository pre-commit passed for all three changed files
- three skill-install CI tests passed
- `git diff --check` passed

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Adds an API v4 reference to the `cloud` skill and routes new
hosted-agent setups to v4, while keeping v2 and v3 available for
existing integrations.

- Documents runs, sessions, workspaces, and direct browser control with
Python, TypeScript, and REST examples.
- Notes that the browser-management SDK wrapper still uses the `v3`
namespace, and that browsers must be stopped explicitly because closing
CDP does not stop billing.
- Marks the old quickstart as the legacy v2 path.
- Adds a CI test that verifies the v4 reference's workspace file-listing
examples.

<sup>Written for commit 442455465b.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/browser-use/browser-use/pull/5591?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->
2026-08-30 14:31:38 -07:00
MagMueller 442455465b Merge remote-tracking branch 'origin/main' into agency/cloud-skill-v4
# Conflicts:
#	tests/ci/test_browser_use_skill_install_docs.py
2026-08-30 10:01:11 -07:00
MagMueller cf0b917fba docs: remove stale session discount 2026-08-30 09:10:39 -07:00
MagMueller 3fdb4ab69a docs: correct cloud browser session price 2026-08-30 09:10:39 -07:00