Release binaries / Build macOS ARM64 (push) Has been cancelled
Release binaries / Build macOS x64 (push) Has been cancelled
Release binaries / Build Linux ARM64 (push) Has been cancelled
Release binaries / Build Linux musl ARM64 (push) Has been cancelled
Release binaries / Build Linux musl x64 (push) Has been cancelled
Release binaries / Build Linux x64 (push) Has been cancelled
Release binaries / Build Windows x64 (push) Has been cancelled
Release binaries / Attach binaries to GitHub Release (push) Has been cancelled
Standalone product rename across the whole repo (issue: project identity):
- Binary/package/repo/skill/docs: agent-browser[-stealth] → chrome-use
(single binary name `chrome-use`; old aliases agent-browser/abs dropped).
- Version: 0.27.0-fork.51 → 1.0.0 (drop the upstream-fork counter).
- Native-messaging host: com.agent_browser.connect → com.leeguoo.chrome_use
(CLI + ab-connect extension in lockstep — this is a breaking handshake change,
extension bumped 0.4.2 → 0.5.0, needs a Web Store republish).
- Config dir: ~/.agent-browser → ~/.chrome-use.
- README/zh: reframed from "stealth fork of agent-browser" to a standalone
product with a small `originally based on vercel-labs/agent-browser` credit.
- Kept AGENT_BROWSER_* env vars working (63 vars across the codebase; renaming
them would break every existing script/skill for no user-facing gain).
Build green, 802 unit tests pass, fmt + clippy clean. Upstream attribution to
vercel-labs/agent-browser preserved.
SKILL.md + click --help + README now cover what agents could otherwise
only discover by trial:
- coordinate click (click <x> <y> / <x>,<y> / --coords) as a first-class form
- tabs / get-text aliases
- the 'stale sessionId … re-open your target URL' relay error and how to recover
- eval/screenshot/network '@ <url>' stamps as a per-read wrong-tab sanity check
- network requests --clear as the 'start capturing fresh' step
--launch mode scored ~20% stealth on CreepJS because the srcdoc-iframe
contentWindow Proxy trips `hasIframeProxy` — the proxy that hides automation is
itself a fingerprintable tell (violates this fork's own "native > JS lies" rule).
Add a config-driven opt-out (no detectable global): AGENT_BROWSER_DISABLE_IFRAME_PROXY=1
drops the patch via __abStealth.disableIframeProxy → the iframe IIFE early-returns
→ clean 0% CreepJS, trading the niche srcdoc-iframe masking. Default keeps current
behavior. README now documents the --launch 20% honestly and scopes the headline
0% to the extension-connect path. Verified: launch + srcdoc page intact with the
toggle; stealth tests green (config strip-prefix kept in sync).
- README + README.zh: new Anti-detection subsections — "Human-like input
(behavioural stealth)" (curved trajectories / jitter / cadence / eased
scroll-drag, adaptive per-page escalation, off|fast|human) with the
trajectory contrast table, and "Silent operation" (background tabs, no
foreground stealing, focus-emulated). Added AGENT_BROWSER_HUMANIZE to the
tuning-knobs table.
- skill core: agents told operation is silent by default and how/when to use
--humanize (leave on auto; force human for known behavioural targets).
The comparison read like a checklist (Runtime.enable leak, permissions footprint
— proof, not pitch). Reframe the opening around the visceral wins:
- headline: "Give your AI agent the browser you already live in"
- pain→relief lead: no fresh Chrome, no re-login, no captcha walls; you watch it
work and take the wheel on 2FA; undetectable because it IS your browser (0% bot)
- three plain "why not just use X?" lines (Playwright/browser-use, Claude in
Chrome, raw debug port) instead of a wall of checkmarks
- the honest feature matrix moves into a collapsible "Full feature comparison".
Mirrored in README.zh.md.
Reader fact-checked the table — three rows overclaimed:
- CreepJS: all real-Chrome tools (Claude in Chrome, web-access, us) score ~0%; it
is NOT a win vs them. Reframe as "real-browser fingerprint" (✅ for all three,
❌ for Playwright/Puppeteer); note ours is the measured one.
- Runtime.enable leak: mark Claude in Chrome "—" (not independently tested) rather
than ✅; web-access/Playwright leak, ours is off by default (rebrowser-verified).
- Multi-agent: web-access CAN run parallel sub-agents (shared browser), so not ❌.
The real differentiator is per-session ISOLATED, command-scoped tab groups.
Added footnotes spelling out the caveats. Same fixes in README.zh.md.
- README opens with a head-to-head vs Claude in Chrome / web-access (raw CDP) /
Playwright·Puppeteer·browser-use: the only tool that drives your own logged-in
Chrome, from any agent, with no consent popup, undetectably (CreepJS 0%), and
multi-agent — addresses the recurring "why not just use <alternative>" question.
- add README.zh.md (简体中文) with a language switcher in both files.
The extension is live on the Web Store, so make the one-click, no-popup extension
path the recommended setup (native messaging — no debug port, no token, no "Allow
remote debugging?" dialog, restart-stable). Demote the raw --remote-debugging-port
method to a collapsed "Alternative" that notes it pops the consent dialog.
- assets/how-it-works.png: CLI → extension (native messaging) → your real Chrome
- assets/architecture.png: tab groups / service worker / native messaging / CLI
- comparison table vs raw-CDP-port tools (web-access) and chrome.debugger
(Claude in Chrome): the extension never triggers Chrome 136+'s "Allow remote
debugging?" consent dialog, keeps Runtime.enable off (rebrowser clean), scores
0% on CreepJS, and gives per-session tab groups for concurrent agents.
Release binaries / Build macOS ARM64 (push) Has been cancelled
Release binaries / Build macOS x64 (push) Has been cancelled
Release binaries / Build Linux ARM64 (push) Has been cancelled
Release binaries / Build Linux musl ARM64 (push) Has been cancelled
Release binaries / Build Linux musl x64 (push) Has been cancelled
Release binaries / Build Linux x64 (push) Has been cancelled
Release binaries / Build Windows x64 (push) Has been cancelled
Release binaries / Attach binaries to GitHub Release (push) Has been cancelled
- stealth(plugins): stop overwriting real native navigator.plugins in headed
mode (the JS fake had a non-native item(), broken uint32 wrap → incolumitas
overflowTest FAIL, and an anachronistic Native Client plugin). Leave native
plugins untouched when present; modernize the headless-escape fallback to the
real 5 PDF-viewer set with masked-native item()/namedItem().
- connect: auto_connect_cdp() now prefers the dialog-free ab-connect relay over
the raw :9222 CDP port, so Chrome 136+'s "Allow remote debugging?" consent
modal no longer fires when the extension relay is live. Gated by a bare-TCP
relay_is_live() probe (+3 unit tests).
- extension: rename ab-connect to "agent-browser-stealth" + new stealth icon set
(16/32/48/128).
- docs(README): hero/shield/fingerprint images, expanded detector results
(CreepJS 0% stealth, incolumitas all-OK, BrowserScan CDP-clean), and a
"Verify it yourself" section. .gitignore: allow assets/ + extension icons.
Real-world dogfooding surfaced clicks that resolve a valid @ref but still miss:
- Scroll the target into view before computing click coordinates
(scrollIntoViewIfNeeded). Without it, an element below the fold — or revealed
after a scroll/popup — yields off-viewport coordinates and the click lands on
whatever occupies that screen point.
- Fall back to a DOM-dispatched `.click()` when the coordinate path fails (a
persistent floating layer failing the occlusion guard, or coordinates that
won't resolve). The DOM dispatch targets the intended element directly instead
of a screen point, so an overlay or portal can't divert it.
- AGENT_BROWSER_CLICK_MODE: "" (default: scroll + coordinate + DOM fallback),
"coord" (strict coordinate, hard-fail on occlusion), "dom" (always
element.click() — best for autocomplete/menu <li> that close on input blur).
Fallback is limited to left single-clicks (DOM .click() can't express
right/middle/double). Non-left/multi and "coord" mode keep the original error.
Docs: README knob table + skill commands.md gain CLICK_MODE, a click-reliability
note, and a "debug forms/hidden inputs with eval" section (snapshot doesn't show
hidden inputs — the fast path to bugs like a hidden point_choice=none).
6 click/interaction e2e green; full suite 760 passed.
Borrow Scrapling's adaptive element finding, adapted to this project's
in-session AX-ref model. When a saved @ref's node is gone (or its identity
no longer matches) and the role/name/nth re-query also fails, score the
current page's candidate elements against an AX fingerprint captured at
snapshot time and relocate to the best match.
- New `adaptive` module: pure, browser-free scoring (role, accessible name
via Levenshtein, AX properties, ancestor-role LCS, parent/sibling) plus
pick_best with a high absolute threshold (0.70) AND a clear margin (0.15)
over the runner-up — so ambiguous twins are refused rather than mis-clicked,
matching the existing "fail loudly over wrong click" posture.
- Fingerprint captured during the existing AX-tree snapshot walk — no extra
CDP round-trips. TreeNode is AX-only (no DOM tag/attrs), so we use AX role
as the type and a few discriminating AX properties (value/url/level/checked);
DOM id/class would have cost an N×describeNode storm per snapshot.
- Wired into both resolve_element_center and resolve_element_object_id: on a
verify-identity mismatch or a stale-node fallback miss, relocation is tried
before erroring. A confident match overrides the identity guard; otherwise
the original error is surfaced. Opt out with AGENT_BROWSER_ADAPTIVE_REF=0.
README documents the new tuning knobs. Adds 9 unit tests; full suite 760 passed.
Addresses the footgun raised in issue #1 follow-up: plain `--launch` silently
uses a temporary EMPTY profile (no cookies/login), and the connect-failure
error even recommended it — trapping agents into thinking they reused the
logged-in browser when they didn't.
- `--profile auto`: resolves to the Chrome profile last used (from Local State
`profile.last_used`), falling back to "Default", then the first profile. So
`--launch --profile auto open <url>` reuses real login state without naming
the profile. (--profile <name>/Default already worked.)
- connect-failure error now recommends `--launch --profile auto` and states
plainly that bare `--launch` is a temporary EMPTY profile — no cookies/login.
- bare `--launch` (no --profile, not CI) now prints a warning to that effect.
- README: fix Setup (relaunch with --remote-debugging-port, not chrome://inspect)
and split Standalone mode into throwaway vs. keep-your-login (`--profile auto`).
Tests: resolve_chrome_profile("auto") prefers last_used, falls back to Default.
Addresses issue #1. The "Setup (one time)" section told users to toggle
chrome://inspect, which only enables target discovery and is NOT enough to
attach — the most-reported first-run failure. Replace with the correct model:
relaunch Chrome with --remote-debugging-port (a startup flag), expect the
Chrome 136+ "Allow remote debugging?" consent dialog, and use --launch as a
zero-setup fallback. Add a "Command names" note that agent-browser /
agent-browser-stealth / abs are the same binary (stealth is runtime behavior,
not a separate executable).
Distribute the prebuilt binary through GitHub Releases instead of the npm
registry — zero auth for the publisher (CI's GITHUB_TOKEN) and zero auth for
consumers (no npm token / 2FA / OTP, no GitHub Packages .npmrc).
- install.sh: detects OS/arch (incl. linux musl), downloads the matching
agent-browser-<platform>.tar.gz from the GitHub Release, verifies .sha256,
installs `agent-browser` + `abs` to /usr/local/bin or ~/.local/bin.
Override via AGENT_BROWSER_VERSION / AGENT_BROWSER_BIN_DIR.
- .github/workflows/release-binaries.yml: on tag push (v*), build all 7
platform variants (reusing the zigbuild cross-compile matrix), package each
as .tar.gz + .sha256, attach to the tag's GitHub Release. No npm, no token.
- remove .github/workflows/release.yml: it published to npm (--provenance) and
built the (removed) dashboard, so it broke on every main push.
- README install now leads with `curl … install.sh | sh`; npm demoted to a
legacy alternative.
- skill stub self-heals: if `agent-browser` is missing, run install.sh (don't
fall back to other browser tools).
- Add fork binary names (agent-browser-stealth, abs) to allowed-tools
in all 6 SKILL.md files so installs into Claude Code / Cursor don't
prompt for permission on every command
- Document `npx skills add leeguooooo/agent-browser-stealth` in README
- Bump README upstream-base mention from v0.24.0 to v0.27.0
* feat(react): first-class React introspection, Web Vitals, and nextjs skill
Add React-general and web-universal features as first-class agent-browser verbs
(react tree/inspect/renders/suspense, vitals, pushstate). Genuinely Next.js-specific
workflows (PPR cookie protocol, /_next/mcp bridge, dev-server endpoints) ship as
a new `nextjs` skill that composes the primitives. No new runtime dependencies -
the React DevTools installHook.js is vendored (MIT) and include_str!'d into the
binary.
New commands:
react tree Full React component tree (depth id parent name)
react inspect <fiberId> Props, hooks, state, source for one fiber
react renders start|stop Fiber profiler with Insts/Mounts/Re-renders/Self/DOM
+ prev->next change details
react suspense Suspense boundaries + classifier (client-hook,
request-api, server-fetch, cache, stream, framework)
+ root-cause grouping + recommendations
vitals [url] LCP/CLS/TTFB/FCP/INP + React hydration phases
pushstate <url> Generic SPA client-side navigation
removeinitscript <id> Remove a script registered via addinitscript
New launch flags:
--init-script <path> Register init scripts before first navigation
(repeatable; env AGENT_BROWSER_INIT_SCRIPTS)
--enable <feature> Built-in init scripts; currently react-devtools
(repeatable; env AGENT_BROWSER_ENABLE)
Other primitives:
network route ... --resource-type <csv> Filter by CDP resource type
cookies set --curl <file> Auto-detects JSON/cURL/Cookie-header
* fixes
* fixes
* fixes
Before this change, the main skill served by the CLI (`agent-browser
skills get agent-browser`) was a ~40-line discovery stub whose content
was essentially "run `agent-browser skills get <name>` before doing
anything." Agents already inside the CLI got no signal from it — the
content they needed to actually use the tool lived only in the `--full`
references.
Split the two jobs apart:
- **`skill-data/core/`** (new) — the runtime usage guide. 420-line
`SKILL.md` covering the snapshot-and-ref loop, common workflows
(login, extract, screenshot, multi-tab, sessions, iframes, dialogs),
waiting strategies, element selection strategies, troubleshooting,
and when to load a specialized skill. Supplementary `references/` and
`templates/` (moved from `skills/agent-browser/`) provide the full
command reference under `--full`.
- **`skills/agent-browser/SKILL.md`** — still the discovery stub that
`npx skills add` installs, now marked `hidden: true` so it stays out
of `skills list` inside the CLI. Body is a clean pointer to
`agent-browser skills get core` and the specialized skills.
The `hidden: true` frontmatter flag is a new, general mechanism: skills
marked hidden are omitted from `skills list` and `skills get --all` but
can still be fetched by explicit name. This keeps the stub reachable
for anyone who installed via `npx skills add` without polluting the
CLI-side skill listing.
## Behavior
```
$ agent-browser skills list
agentcore Run agent-browser on AWS Bedrock AgentCore cloud browsers...
core Core agent-browser usage guide. Read this before running...
dogfood Systematically explore and test a web application...
electron Automate Electron desktop apps (VS Code, Slack, Discord...)
slack Interact with Slack workspaces using browser automation...
vercel-sandbox Run agent-browser + Chrome inside Vercel Sandbox microVMs...
$ agent-browser skills get core # the actual usage guide
# ~420 lines of workflows, patterns, troubleshooting
$ agent-browser skills get agent-browser # still works if called explicitly
# the thin stub, now pointing at `core`
```
External `npx skills add vercel-labs/agent-browser` behavior is
unchanged: it finds and installs the thin `agent-browser` stub, which
tells the agent to run `agent-browser skills get core` for real
content. Version drift protection is preserved — the stub is the only
thing that gets copied; the real content is always runtime-fetched.
## Updated
- `cli/src/skills.rs` — `SkillInfo.hidden: bool`, parsed from
frontmatter; `run_list` and `run_get --all` filter it. 3 new unit
tests for the frontmatter parser.
- `cli/src/output.rs` — top-level `--help` and `skills` subcommand help
reference `skills get core` / `skills get core --full`.
- `AGENTS.md` — "update these files for user-facing features" now
points at `skill-data/core/` instead of the stub, with a note that
the stub is not the right place for feature content.
- `README.md`, `docs/src/app/skills/page.mdx` — describe the new
split and `skills get core --full` as the recommended entry point.
- `evals/cases/{command-usage,skill-selection}.ts` — expect
`skills get core` in agent output instead of `skills get
agent-browser`. Eval lib still reads `skills/agent-browser/SKILL.md`
(simulating what an agent sees after `npx skills add`).
All 11 skills unit tests pass. `cargo clippy -- -D warnings` and
`cargo fmt --check` clean. Verified end-to-end: `skills list` shows
`core` + specialized (no stub), `skills get core` returns the new
content, `skills get agent-browser` still returns the stub on explicit
request.
* fix(tabs): preserve refs across --tab peek and cover outer-tab-closed path
Follow-up to #1249 so `--tab <id>` is actually useful for agents:
- Save and restore the outer tab's `ref_map`, `iframe_sessions`, and
`active_frame_id` across a scoped command instead of clearing them.
`snapshot` → `--tab N <cmd>` → `click @e1` now keeps the outer tab's
refs intact. Scoped commands still see a clean slate so outer refs
can't resolve against the scoped tab's DOM.
- Close the coverage gap the Vercel review bot flagged on #1249: the
previous `e2e_tab_scoped_command_handles_outer_tab_closed` test used
`tab_close`, which is in the scoped-dispatch exclusion list, so it
never exercised the restore-skip branch it claimed to test. Renamed
to `e2e_tab_close_with_tab_id_closes_active_tab` with an honest
docstring, and added `e2e_tab_scoped_command_outer_tab_closed_mid_dispatch`
that actually hits the branch via `window.opener.close()` on a
script-opened intermediate tab.
- Add `e2e_tab_scoped_command_isolates_refs_from_outer_tab` pinning
that outer refs don't bleed into the scoped tab's DOM resolution.
- Rewrite `e2e_tab_scoped_command_clears_state_on_switch` as
`e2e_tab_scoped_command_preserves_outer_tab_state`, verifying the
restored @e1 still clicks end-to-end.
- Update the 52 `--help` entries for `--tab <id>` to describe peek /
restore semantics instead of a vague "Target specific tab ID".
- Update README, docs site, config schema, and the agent-facing
skills reference with working examples (refs survive the peek) and
a "when to use \`--tab <id>\` vs \`tab <id>\`" guide so agents pick
the right flag for their workflow.
* fix(tabs): use t<N> prefix for tab ids, add --label for named tabs
Follow-on to the tab work in #1249 and the prior commit, redesigning the
tab handle surface before release since nothing ships these features yet.
## Why
Incrementing integer tab ids (`1`, `2`, `3`) look indistinguishable from
positional indices in command output, LLM-generated scripts, and docs. In
the common single-agent case where position and id coincide, readers have
no visual cue for which mental model they're using. Positional indices
silently shift when unrelated tabs open/close, so misreading a handle as
an index is a correctness hazard.
## Changes
**Tab ids are now `t1`, `t2`, `t3` (strings).** Bare integer `tabId`
values are rejected with a teaching message rather than silently accepted.
The `t` prefix matches the `@e1` element-ref convention and makes ids
unmistakably non-positional at a glance.
**Labels.** Tabs can be created with a user-assigned label (e.g. `docs`,
`app`) via `tab new --label <name> [url]`. Labels are interchangeable
with `t<N>` ids everywhere a tab ref is accepted. They're never
auto-generated, never rewritten on navigation, and must be unique within
a session.
**Dashboard fix.** `packages/dashboard/src/types.ts` declared
`TabInfo.index: number` but the daemon has been sending `tabId` (not
`index`) since #892, making `tab.index` `undefined` and breaking the
dashboard's close/switch buttons silently. Updated the TS types and
usages to consume `tabId` (string) and optional `label`, restoring the
dashboard's tab interactions.
## Surface
- `cli/src/native/browser.rs`: `TabRef::parse` / `format_tab_id` /
`is_valid_label` / `PageInfo.label` / `BrowserManager::resolve_tab_ref`
/ `BrowserManager::has_label`. `tab_new` gains an optional label
argument with duplicate rejection. All JSON responses use the string
form and include the label.
- `cli/src/native/actions.rs`: scoped-command pre-dispatch and
`handle_tab_{switch,close,new}` parse string refs and resolve to
stable ids.
- `cli/src/{flags,commands,main,output}.rs`: `--tab` / config `tab`
are `String`; `tab` subcommand accepts `t<N>` or a label and supports
`tab new --label <name> [url]`. All 52 `--help` entries updated.
- `agent-browser.schema.json`: `tab` property type is now `string` with
a pattern matching `t<N>` or label form.
- `packages/dashboard`: `TabInfo.tabId: string` / `label?: string | null`;
`closeTabAtom`/`switchTabAtom` take `tabRef: string`; component props
updated.
- Docs: README, docs site (`commands/` and `configuration/`), and the
agent-facing skills reference rewritten with the new examples.
## Tests
- Added `TabRef::parse` / `format_tab_id` / `is_valid_label` unit tests
pinning the bare-integer rejection, the teaching error, label rules,
and round-tripping.
- Added `test_tab_switch_by_id` / `_by_label` / `test_tab_new_with_label`
/ `_with_label_and_url` / `_with_url_then_label` in `commands.rs`;
rewrote `test_tab_unknown_subcommand_errors` since labels make
`tab select` a legitimate ref.
- Added `e2e_tab_new_with_label_can_be_switched_and_peeked`,
`e2e_tab_new_with_duplicate_label_errors`,
`e2e_tab_scoped_command_rejects_bare_integer`.
- Migrated every existing tab e2e test (and one unit test) from
integer `tabId` to the string form.
`cargo fmt`, `cargo clippy -- -D warnings`, all 30 non-ignored tab unit
tests, all 13 tab e2e tests, and `tsc --noEmit` on the dashboard all
pass.
* refactor(tabs): drop --tab scoped peek flag; keep t<N> ids and labels
After fleshing out `--tab <id|label>` in the previous commits (scoped
pre/post-dispatch save/restore, ref preservation, outer-tab-closed edge
case, full e2e coverage), the machinery-to-value ratio makes the feature
hard to justify. Nixing it now while nothing has shipped.
## Why
- Every new daemon feature touching per-tab state has to reason about
scoped-dispatch interleaving. `ScopedRestore`, pre/post-dispatch hooks,
and the exclusion list add ongoing maintenance tax.
- Three separate PRs (#892, #1249, and this one pre-nix) were needed to
reach "works correctly." That's a smell.
- `tab <id|label>` switch + labels already cover the legible multi-tab
workflow case.
- `--tab` vs `tab <id>` have opposite lifecycle semantics but look
identical, teaching every agent two things where one would do.
- "Non-disruptive peek" isn't actually race-free: the daemon does swap
active tab during execution, so a concurrent client between pre- and
post-dispatch sees the scoped tab as active.
- Ref-based interaction with scoped tabs never worked ergonomically —
refs are per-tab, so `--tab N click @e1` requires `@e1` to already be
on tab N, which means a prior switch, which negates the peek.
- Adding a feature back is easy; removing shipped API is hard.
If per-tab caching (`HashMap<tab_id, RefMap>`) lands later, `--tab` can
be reintroduced essentially for free. That's the right time.
## Removed
- `--tab <id|label>` global flag (`cli/src/flags.rs`, `cli/src/main.rs`,
all 52 `--help` entries in `cli/src/output.rs`).
- `tab` property in `agent-browser.schema.json` and the config-options
row in `docs/src/app/configuration/page.mdx`.
- `ScopedRestore` struct, pre/post-dispatch save/restore in
`execute_command` (`cli/src/native/actions.rs`).
- `impl Default for RefMap` in `cli/src/native/element.rs` (only added
for `mem::take` in the scoped machinery).
- `e2e_tab_global_targeting`, `_snapshot`, `_snapshot_non_contiguous`,
`e2e_tab_scoped_command_preserves_outer_tab_state`,
`_isolates_refs_from_outer_tab`, `_restores_active_tab`,
`_outer_tab_closed_mid_dispatch`. 590 lines.
- The "When to use `--tab` vs `tab <id|label>`" sections in README,
docs site, and skills reference.
## Kept
- Stable tab ids (`t1`, `t2`, `t3`) with bare-integer rejection.
- User-assigned labels (`tab new --label docs [url]`), with duplicate
rejection and interchangeable use everywhere a tab ref is accepted.
- `BrowserManager::{active_tab_id, has_tab_id, resolve_tab_ref, has_label}`
accessors (still used by the remaining tab handlers).
- `TabRef::parse`, `format_tab_id`, `is_valid_label` and their unit
tests.
- Dashboard TS fix (`TabInfo.tabId` + `label`).
- `e2e_tab_close_with_tab_id_closes_active_tab` (renamed docstring to
drop the gone exclusion-list reference).
- `e2e_tab_new_with_label_can_be_switched_and_closed` (rewrite of the
previous `_and_peeked` test — now exercises only switch and close).
- `e2e_tab_switch_rejects_bare_integer` (rewrite targeting the
`tab_switch` daemon handler rather than the removed scoped path).
net: -900 lines across 12 files. `cargo fmt`, `cargo clippy -D warnings`,
all 25 non-ignored tab unit tests, all 6 tab e2e tests, and
`tsc --noEmit` on the dashboard all pass.
* fix(tabs): initialize tab_id on missing PageInfo sites
PR #892 added a required `tab_id: u32` field to `PageInfo` but missed two
initializer sites, which broke the build on the PR branch. CI never caught
this because the external-contributor workflow status was `action_required`
and never ran.
- `cli/src/native/browser.rs:395` — the `direct_page` branch of
`connect_cdp_inner` used by the cloud providers (Browserbase, Browserless,
Browser Use, Kernel, AgentCore). Use `assign_tab_id()` to get a fresh id.
- `cli/src/native/browser.rs:1580` — a unit test initializer. Use `tab_id: 1`
since the test doesn't exercise id assignment.
* feat(tabs): restore active tab and clear per-tab state for scoped --tab
Follow-up on PR #892's `--tab <id>` flag.
The original implementation called `tab_switch_by_id` directly from the
pre-dispatch block in `execute_command` but didn't touch the daemon's
per-tab state, and never restored the previously-active tab. Two concrete
issues this fixes:
1. `state.ref_map`, `state.iframe_sessions`, and `state.active_frame_id`
were left intact across the pre-dispatch switch, so `--tab N click @e1`
would try to resolve `@e1` against the scoped tab's DOM using a
backend-node id from the outer tab. In practice the click handler's
role+name fallback hid this as "element not found" errors, but on pages
where both tabs have similarly-labelled elements it could click the
wrong one.
2. The PR description promised scoped routing would "restore the previous
active tab", but the implementation permanently switched. `--tab 3
snapshot` would leave tab 3 as the active tab even after the command
returned, surprising subsequent non-scoped commands.
This change:
- Saves the current tab's stable `tab_id` (not its array index, which
would shift if the scoped command closed other tabs) before switching.
- Clears per-tab daemon state before the switch so refs/iframes/frame
context can't leak between tabs.
- After the action runs, restores the original active tab (also via
stable id) unless that tab was closed during the scoped command, in
which case we leave the scoped tab active.
- Adds `BrowserManager::active_tab_id()` and `has_tab_id()` accessors
to support the above without exposing the internal `pages` vector.
* test(tabs): regression tests for scoped --tab state clearing and restoration
Three new `#[ignore]` e2e tests pinning the fixed behavior:
- `e2e_tab_scoped_command_clears_state_on_switch` — populates `ref_map` on
tab 1, runs a `tabId: 2`-scoped command, asserts `ref_map`,
`iframe_sessions`, and `active_frame_id` are all cleared.
- `e2e_tab_scoped_command_restores_active_tab` — sets up two tabs, runs
a scoped command against the non-active one, asserts a subsequent
unscoped command reflects the originally-active tab.
- `e2e_tab_scoped_command_handles_outer_tab_closed` — runs a scoped
`tab_close` that kills the outer tab itself, asserts no error and the
scoped tab becomes active.
Also updates two misleading comments in the PR's existing
`e2e_tab_global_targeting*` tests to reflect restoration semantics; the
assertions themselves were already consistent with restoration.
* docs(tabs): document stable tab IDs and --tab scoped-command flag
Per AGENTS.md, changes that users or agents would need to know about must
land in every doc surface. Fills the gaps PR #892 left:
- `README.md` — new `--tab <id>` row in the Options table, rewrite the
tab command examples to use `<id>` instead of `<n>`, add a paragraph
explaining stable tab IDs and `--tab` peek semantics.
- `docs/src/app/commands/page.mdx` — same command-example rewrite plus a
new "Stable tab IDs and `--tab`" subsection.
- `docs/src/app/configuration/page.mdx` — add `tab` row to the config
options table so JSON config users can discover it.
- `agent-browser.schema.json` — add `tab` property with description,
matching the config schema.
- `skills/agent-browser/references/commands.md` — same command-example
rewrite plus a short paragraph for agents on when to use `--tab`.
Fix idleTimeout description to document human-friendly formats (30s,
5m, 1h) alongside raw milliseconds. Add trailing newline. Serve the
schema from the docs app at agent-browser.dev/schema.json via a
prebuild copy step, and update all $schema URLs to use the stable
docs-hosted URL instead of raw GitHub.
* feat: add JSON Schema for agent-browser config files
Adds agent-browser.schema.json describing all config options with
types and descriptions. Enables IDE autocomplete and validation when
referenced via $schema in agent-browser.json or
~/.agent-browser/config.json.
README and docs site updated to document the schema reference.
* fix(schema): use integer type for maxOutput to match usize deserialization
Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
---------
Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
* Add `agent-browser skills` command
Adds a `skills` CLI command that serves bundled skill content at runtime,
always matching the installed CLI version. This solves the problem of
agents relying on stale cached SKILL.md files after CLI upgrades.
The `npx skills add vercel-labs/agent-browser` flow now installs a single
thin discovery skill with trigger words for all use cases (browser
automation, dogfooding, Electron apps, Slack, etc.) that directs agents
to `agent-browser skills get <name>` for current instructions. The other
five skills (dogfood, electron, slack, vercel-sandbox, agentcore) are
marked `metadata.internal: true` so they are not installed by default but
remain accessible via the CLI command.
Subcommands:
skills [list] List available skills
skills get <name> [--full] Get skill content (with optional references)
skills get --all Get all skill content
skills path [name] Print skill directory path
* Fix skills command robustness: UTF-8 safety, flag handling, path output
- Make truncate_description UTF-8-safe using char_indices() instead of
byte-indexed slicing that panics on multi-byte codepoints
- Pass get_all as a bool parameter to run_get instead of embedding
--all as a sentinel string in the names list
- Canonicalize skills_dir path so `skills path` output is clean
- Warn on unrecognized flags in `skills get` instead of silently
ignoring them
* Add evals framework and strengthen SKILL.md for better agent compliance
Strengthen SKILL.md loading instructions to require `skills get` before
running commands, and trim skill descriptions to prevent agents from
guessing at command syntax. Add TypeScript/Bun eval framework that tests
skill-loading, skill-selection, and command-usage via Claude CLI with
Vercel AI Gateway. Evals pass 20/20 (100%), up from 85% baseline.
* Fix formatting in skills.rs
* Add Codex provider to evals framework
Add multi-provider support with a shared Provider interface. Codex
provider spawns `codex exec --json`, parses JSONL output, and writes
~/.codex/config.toml for AI Gateway routing. Use `--provider codex`
to run evals with Codex (default model: openai/o3). First run scores
19/20 (95%) with 100% on skill-loading and skill-selection.
* Use scoped temp dir for Codex config instead of overwriting ~/.codex
* feat(chrome): add Chrome profile name resolution and copy for --profile flag
When --profile receives a name without path separators (e.g., "Default"),
it now resolves the name against installed Chrome profiles, copies the
profile to a temp directory (excluding large cache dirs), and launches
Chrome with the copied profile to reuse login state.
Key changes:
- Add profile resolution: is_chrome_profile_name, find_chrome_user_data_dir,
list_chrome_profiles, resolve_chrome_profile (3-tier matching)
- Add copy_chrome_profile with best-effort copy and exclusion list
- Wire preprocessing into launch_chrome before retry loop
- Add use_real_keychain field to LaunchOptions for conditional keychain flags
- Make --password-store=basic and --use-mock-keychain conditional
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(cli): add `profiles` command to list available Chrome profiles
Adds `agent-browser profiles` command that reads Chrome's Local State
file to list available profiles with directory names and display names.
Supports --json output. Added help text in print_command_help and
print_help.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs: add Chrome profile reuse documentation across all locations
Update all 5 documentation locations per AGENTS.md:
- output.rs: updated --profile help text and examples
- README.md: added Chrome Profile Reuse section, updated options table
- SKILL.md: added profile reuse as Option 2
- docs/src/app/sessions/page.mdx: added Chrome profile reuse section
- chrome.rs: added doc comments to get_chrome_user_data_dirs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* style: fix formatting and clippy warning in chrome.rs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: simplify profile resolution and launch integration
- Only clone LaunchOptions when profile name requires resolution
(avoids unnecessary allocation on every Chrome launch)
- Remove redundant is_file() check before copy of Local State
(copy() handles missing files naturally)
- Extract format_profile_list() to deduplicate error formatting
- Remove unnecessary section comments in tests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor(tests): use RAII TempDir guard for test cleanup
Replace manual remove_dir_all calls with a TempDir struct that
auto-cleans on drop, preventing temp dir leaks on test panics.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: ctate <366502+ctate@users.noreply.github.com>
Add provider icons and session creation from the dashboard UI.
Sessions can now be created with cloud providers (Browserbase,
Browserless, Browser Use, Kernel) in addition to local engines.
CLI changes:
- Track provider via .provider files alongside .engine files
- Add WaitUntil::None variant to skip lifecycle event waits for providers
- Auto-set waitUntil=none when --provider is used with navigate
- Fix Browser Use: use direct WSS connection (wss://connect.browser-use.com)
- Add connect_cdp_direct for providers with page-level CDP proxies
- Fix resolve_cdp_url to convert https:// provider URLs to wss://
- Treat empty CDP session_id as None (omit from protocol messages)
- Fix Browserbase: send explicit JSON body + Content-Type header
- Increase CDP connect timeout to 25s for remote providers
- Clean up .provider files on session close
Dashboard changes:
- Show provider or engine icon per session in sidebar
- New session dialog with unified engine/provider selector grid
- Async session creation with loading state and error display
- Kill zombie daemons on provider connection failure
- Parse CLI JSON error output for user-friendly messages
- Default new session URL to https://agent-browser.dev
* Add auto-dismissal for alert and beforeunload dialogs
This PR adds automatic handling of JavaScript dialogs to prevent the agent from blocking indefinitely when `alert()` or `beforeunload` dialogs appear on web pages.
## Summary
Previously, when a website displayed native browser confirmation dialogs (like alerts or "Are you sure you want to leave?" prompts), agent-browser would hang waiting for manual intervention. This is a common issue since many websites use these dialogs for notifications or navigation warnings.
## Changes Made
- **Auto-dismiss functionality**: Added a background task that automatically accepts `alert` and `beforeunload` dialogs while leaving `confirm` and `prompt` dialogs for explicit handling
- **New flag**: Added `--no-auto-dialog` flag to disable automatic handling when needed
- **Environment variable**: Added `AGENT_BROWSER_NO_AUTO_DIALOG` for configuration
- **Documentation**: Updated README and docs with usage examples and configuration details
- **Tests**: Added comprehensive test coverage for flag parsing and dialog handling logic
## Implementation Details
- Only `alert` (notification-only) and `beforeunload` (navigation warning) dialogs are auto-handled for safety
- `confirm` and `prompt` dialogs still require explicit `dialog accept/dismiss` commands to ensure agents make deliberate choices for destructive actions
- The feature is enabled by default since these dialog types rarely require user decision-making
- Uses Chrome DevTools Protocol's `Page.handleJavaScriptDialog` for reliable dialog dismissal
Fixes#1070
* Log dialog type and message before auto-dismissal
Without this, auto-dismissed alert/beforeunload dialogs are silently
swallowed and the agent has no way to see what the dialog said. Adding
an eprintln before the CDP call makes the dismissal visible in stderr
for debugging.
* Log dialog dismissal errors instead of silently discarding them
- Remove premature "accepted" from log message since it fires before
the CDP command executes
- Replace `let _ =` with `if let Err(e)` to log failures when
Page.handleJavaScriptDialog fails
- Apply rustfmt to auto-dialog tests
---------
Co-authored-by: ctate <366502+ctate@users.noreply.github.com>
* dashboard
* fix: re-apply download behavior on recording context (#1019)
* fix: re-apply download behavior on recording context
record start creates a new browser context via Target.createBrowserContext.
Browser.setDownloadBehavior called at launch only applies to the default
context, so downloads in the recording context are silently dropped.
Fix:
1. Store download_path on BrowserManager (from LaunchOptions)
2. After creating the recording context, call Browser.setDownloadBehavior
with the new browserContextId
This ensures downloads work during recording.
Fixes#1018
* fix: add download_path to third BrowserManager constructor (auto_connect_cdp)
* fix: reap zombie Chrome process and fast-detect crash for auto-restart (#1023)
When Chrome crashes (e.g. SIGTRAP from CHECK() assertion), the daemon
now:
1. Reaps the zombie immediately via a SIGCHLD handler in the event loop
that calls waitpid(-1, WNOHANG)
2. Detects the crash instantly on the next command via a non-blocking
try_wait() check (has_process_exited), avoiding the 3-second CDP
timeout that is_connection_alive() would incur
3. Auto-relaunches Chrome transparently for the caller
Fixes#1017
Co-authored-by: ctate <366502+ctate@users.noreply.github.com>
* fix: route keyboard type through text input (#1014)
* fix: handle --clear flag in console command (#1015)
The console and errors commands parsed --clear from CLI args but the
action handlers silently ignored the flag. The handlers did not accept
the cmd parameter so they had no way to read the clear field.
Changes:
- Add clear_console() method to EventTracker in network.rs
- Update handle_console to accept cmd, read the clear field, and clear
the buffer when --clear is passed (returns {cleared: true})
- Update call site in execute_command to pass cmd
Co-authored-by: xuyongliang <yongliang.xyl@alibaba-inc.com>
* chore: patch release - ### Bug Fixes
- **Re-apply download behavior on r... (#1025)
* Add runtime stream enable/disable/status commands (#951)
* Add runtime stream management commands
* Run rustfmt and satisfy clippy
* Fix stream disable cleanup semantics
* Format stream disable regression tests
* fix: retain radio/checkbox elements in compact snapshot tree (#1008)
compact_tree() checked for "[ref=" to identify lines worth keeping, but
radio and checkbox elements render as e.g. [checked=false, ref=e1] where
the "[" opens before "checked=", not "ref=". Dropping the leading bracket
so the check is just "ref=" fixes the match for all elements with refs.
Fixes#1006
Co-authored-by: ctate <366502+ctate@users.noreply.github.com>
* chore: version packages (#1027)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* fixes
* dashboard
* fixes
* remove observe
* fmt
* fixes
* fixes
* jotai
* fmt
* upload dashboard
---------
Co-authored-by: Stefan Smiljkovic <stefan@vanila.io>
Co-authored-by: ctate <366502+ctate@users.noreply.github.com>
Co-authored-by: zhanba <c5e1856@gmail.com>
Co-authored-by: xuyongliang <478439790@qq.com>
Co-authored-by: xuyongliang <yongliang.xyl@alibaba-inc.com>
Co-authored-by: Thomas Kosiewski <thoma471@googlemail.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Fixes#992
When a JavaScript dialog (alert/confirm/prompt) blocks the page, agents
had no way to detect it — all commands just timed out with generic errors.
- Add `dialog status` command to check for pending dialogs
- Track dialog state via CDP Page.javascriptDialogOpening/Closed events
- Auto-inject `warning` field into all command responses when a dialog is
pending, so agents can distinguish dialog-blocked timeouts from other issues
- Document dialog commands in SKILL.md (was missing entirely), README.md,
docs site, and --help output
Co-authored-by: ctate <366502+ctate@users.noreply.github.com>
* feat: add network request detail and filtering for request tracking
- Add `network request <requestId>` command to view full request/response
details including response body via CDP Network.getResponseBody
- Add --type, --method, --status filter flags to `network requests`
- --type: comma-separated resource types (xhr,fetch,document)
- --method: filter by HTTP method
- --status: supports exact (200), class (2xx), range (400-499)
- Extend TrackedRequest with request_id, post_data, status,
response_headers, mime_type fields
- Update Network.responseReceived handler to also populate
tracked_requests (previously only updated HAR entries)
- Add tests for parse commands and matches_status_filter
- Update README, SKILL.md, docs, and help text
Closes#932
* fix: show request ID and status in network requests output
* fix: add ref for cursor-interactive content roles
* fix: format
* feat: always include cursor-interactive elements in snapshot, -C is deprecated
* feat: process StaticText aggregation and deduplication
* update test
* clean up
* fix: escape text of elements in snapshot
* fix: redundant slicing
* fix: cargo fmt
* feat: deduplicate redundant StaticText
---------
Co-authored-by: 羲洋 <lipengyang.lpy@alibaba-inc.com>
Navigate with load, then wait for username/password/submit selectors using the default action timeout. This avoids networkidle hangs on pages with continuous background requests.
Adds a new `agent-browser upgrade` command that automatically detects the installation method (npm, Homebrew, or Cargo) and runs the appropriate update command.
**Changes:**
- Added new `upgrade.rs` module with upgrade logic
- Updated `main.rs` to handle the `upgrade` command
- Added upgrade help text in `output.rs`
- Updated README.md and documentation with upgrade instructions
- Updated SKILL.md to mention the upgrade command
**Implementation details:**
- Fetches latest version from npm registry to show version diff
- Auto-detects installation method by checking Homebrew, Cargo paths, and npm global packages
- Provides fallback instructions if installation method cannot be determined
- Uses existing color module for consistent styled output
- Gracefully handles network failures and continues with upgrade
Fixes#895
Expose HAR recording as a CLI subcommand under the existing `network`
command so users can capture and export network traffic without a
separate tool or opening the browser twice.
- Parse `network har start` and `network har stop [path]` in commands.rs
- Enrich HarEntry with request/response headers, timestamps, status text,
resource type, HTTP version, and body sizes from CDP events
- Produce HAR 1.2 output with creator/browser metadata, query strings,
and proper header arrays compatible with Chrome DevTools HAR viewer
- Auto-generate output path under ~/.agent-browser/tmp/har/ when omitted
- Add har_stop to skip_launch list so export works without a live browser
- Update help text, README, docs site, SKILL.md, and security policy docs
- Add unit tests for parsing, HAR entry serialization, and stop behavior
Add `batch` command that reads a JSON array of commands from stdin
and executes them sequentially against the daemon. This avoids
per-command process startup overhead when AI agents run multi-step
browser workflows.
Supports --bail to stop on first error (default: continue all)
and --json for structured output as an array of results.
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: remove obsolete BrowserManager TypeScript API references from README
The TypeScript src/ was removed in commit 8e43469 (full native #754),
but README still referenced the non-existent BrowserManager API.
Replace Lambda example with CLI-based handler and remove Programmatic API section.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore
---------
Co-authored-by: hyunjinee <leehj0110@kakao.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add screenshot output config, clipboard CLI commands, and fix wait --text native path
## Summary
- Add `--screenshot-dir`, `--screenshot-quality`, and `--screenshot-format` CLI flags (with corresponding `AGENT_BROWSER_SCREENSHOT_DIR`, `AGENT_BROWSER_SCREENSHOT_QUALITY`, `AGENT_BROWSER_SCREENSHOT_FORMAT` env vars) so users can configure where and how screenshots are saved without specifying a full path every time
- Add `clipboard read`, `clipboard write <text>`, `clipboard copy`, and `clipboard paste` CLI commands, exposing the existing protocol-level clipboard handlers that were previously only accessible via JSON-RPC
- Fix `wait --text` in native mode: the CLI was emitting `selector: "text=..."` (a Playwright-style locator) which native's `querySelector` can't handle. Now emits a `text` field that correctly hits the native `wait_for_text` polling path
- Add native clipboard `copy` and `paste` support via CDP `Input.dispatchKeyEvent`, and a `write` operation to the Node.js handler
* fix: resolve CI failures in Rust formatting and TypeScript typecheck
Use string-based page.evaluate for clipboard writeText to avoid
referencing `navigator` in Node.js compilation context. Run cargo fmt
to fix formatting in commands.rs and screenshot.rs.
* fix: clipboard write captures full multi-word text
Use rest[1..].join(" ") instead of rest.get(1) so unquoted multi-word
input like `clipboard write hello world` sends the full string rather
than silently dropping everything after the first word.
* improvements
* fixes
* improvements
* improvements