Commit Graph
774 Commits
Author SHA1 Message Date
leeguoooooandClaude Opus 4.6 6addc80aa1 feat(rebase): fork base on upstream v0.24.0 native architecture
- Rebased onto upstream/main (v0.24.0, full Rust native)
- Renamed package to agent-browser-stealth, version 0.24.0-fork.1
- Preserved fork-specific: abs alias, extensions/tab-group-cdp, .husky hooks
- Removed upstream-only: docs/, packages/dashboard, examples/, benchmarks/
- Simplified pnpm workspace to root-only
- Added [[bin]] section to keep binary name as "agent-browser"

Track 1 of native-stealth migration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-08 23:46:53 +09:00
Chris Tate 82eadcee41 Fix trusted publishing: add Release environment and per-job permissions (#1333) 2026-05-07 10:45:00 -05:00
Chris Tate c830d1b67d Prepare v0.27.0 release (#1332) 2026-05-07 10:15:30 -05:00
Thomas Kosiewski d33bdb36f3 Make dashboard work from proxied origins via same-origin proxy (#1111)
* Restore dashboard session proxy routes

Change-Id: I36ffc3727ce44100121bc94a81510a5f009ee0bc
Signed-off-by: Thomas Kosiewski <tk@coder.com>

* Port dashboard frontend and docs

Change-Id: I80356f64d618dab9d07b610ba67def14539f98ac
Signed-off-by: Thomas Kosiewski <tk@coder.com>

* docs: restore dashboard note in skill

Change-Id: Id0913c64e7a6f2cbbfc429ef03b34dae185d8487
Signed-off-by: Thomas Kosiewski <tk@coder.com>

* fix: tighten dashboard proxy same-origin checks

Change-Id: I792bc859a24cd47314bd46c94344ef3dfb7d6db5
Signed-off-by: Thomas Kosiewski <tk@coder.com>

---------

Signed-off-by: Thomas Kosiewski <tk@coder.com>
2026-05-07 09:08:12 -05:00
Chris Tate 3bb1d43f8b fix(doctor): make generated ids unique per call (#1330) 2026-05-06 10:48:19 -05:00
Andrew Qu 918d407411 Update README.md (#1328) 2026-05-05 16:55:19 -05:00
Walter KormanandClaude Opus 4.6 7ada3384e2 feat(docs): add AI Gateway app attribution headers (#1305)
Pass http-referer and x-title headers to streamText so Vercel can
identify agent-browser on AI Gateway pages.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-29 08:39:12 -07:00
Chris Tate 57405f9361 feat(react): React introspection, Web Vitals, and SPA primitives (#1257)
* 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
2026-04-20 16:12:47 -05:00
Chris Tate cff12598bf adds trusted publishing (#1273)
* adds trusted publishing

* rename
2026-04-20 00:24:06 -05:00
Chris Tate 717d1b09e1 v0.26.0 (#1255) 2026-04-16 18:33:23 -05:00
Chris Tate 14ece9b3ad feat: add doctor command for diagnosing installs and cleaning stale daemon state (#1254)
* feat: add `doctor` command for install diagnostics and cleanup

Adds `agent-browser doctor`, a one-shot diagnostic that checks
environment, Chrome install, daemon state, config, encryption key,
providers, network reachability, and a live headless launch test.
Auto-cleans stale `.sock` / `.pid` / `.version` / `.stream` sidecar
files on every run. Destructive repairs (reinstall Chrome, purge old
state, close version-mismatched daemons, generate missing encryption
key) are gated behind `--fix`. Supports `--offline`, `--quick`, and
`--json`.

* fixes
2026-04-16 18:20:41 -05:00
Chris Tate 4cc6ca40b7 feat(skills): rename "agent-browser" skill to "core"; make CLI-served main skill actually useful (#1253)
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.
2026-04-16 14:36:59 -05:00
Chris Tate 1afcaa0e84 docs(help): promote skills to the top of --help so agents discover them first (#1251)
The `Skills:` section was buried between `Setup:` and `Snapshot Options:` in
the top-level `--help`, where an agent skimming the output would pass over it
on the way to flag docs. Move it to a prominent "Start here (for AI agents)"
block directly below `Usage:` so it's the first thing an agent sees, and
reframe the copy so it conveys what skills *are* (workflow patterns, ref
usage, copy-paste examples) rather than just listing subcommand flags.

Skills are the intended entry point for agents. They ship with the CLI,
always version-match the installed binary, and cover both `agent-browser`
core usage and specialized workflows (Electron, Slack, exploratory testing,
cloud browser providers). Surfacing them up front prevents agents from
guessing commands out of flag docs when a hand-written workflow guide is
one command away.

No functional change. Only the ordering and wording of `--help` output.
2026-04-16 14:33:55 -05:00
Chris Tate 585d93a02b feat(tabs): t<N> prefix for tab ids; --label for named tabs; drop --tab peek flag (#1250)
* 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.
2026-04-16 14:33:43 -05:00
Chris Tate c201623710 fix(tabs): correct --tab scoped commands and un-break provider direct-page path (#1249)
* 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`.
2026-04-16 12:34:14 -05:00
Daniel Hails 67dc631977 Consistent Tab IDs & Global Tag Targeting (#892)
Introduces stable per-tab IDs and a global `--tab <id>` flag for scoping individual commands to a specific tab.

Breaking change: response payloads for `tab_list`, `tab_new`, `tab_switch`, `tab_close`, and `window_new` now use `tabId` instead of `index`. `tab_close` returns `{tabId, closed: true}` instead of `{closed, activeIndex}`. `agent-browser tab <unknown>` now errors instead of silently listing tabs.

Follow-up PR to land immediately after this fixes a compile error on the provider direct-page path, clears per-tab daemon state around scoped switches, and implements active-tab restoration so `--tab N` is non-intrusive as intended.
2026-04-16 12:02:55 -05:00
Chris Tate c691b269cb fix: improve config schema and serve from docs site (#1248)
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.
2026-04-16 10:42:44 -05:00
Michaelandvercel[bot] <35613825+vercel[bot]@users.noreply.github.com> 4f9edf9337 feat: add JSON Schema for agent-browser config files (#1242)
* 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>
2026-04-16 08:44:01 -05:00
Tom Dale 19808d08f8 fix: load storage state at launch when --state / AGENT_BROWSER_STATE is set (#1241)
* fix: load storage state at launch when --state / AGENT_BROWSER_STATE is set

The `--state` flag and `AGENT_BROWSER_STATE` env var were documented as
restoring saved browser state (cookies + localStorage) at launch, but
`load_state()` was never called after the browser started. The feature
has been broken since it was introduced.

Adds `try_load_storage_state()` and calls it from every early-return
path in `auto_launch()` (lazy launch triggered by commands like
`navigate`) and from `handle_launch()` (explicit `launch` command).

Also adds 4 e2e tests covering all state-persistence paths:
- Explicit launch with `storageState` field
- Auto-launch via `AGENT_BROWSER_STATE` env var
- Session-name auto-restore via `try_auto_restore_state`
- Explicit `state_load` command (baseline sanity check)

Fixes #1164.

* style: apply cargo fmt to e2e_tests.rs

Reformats a single long format\! call to satisfy CI's rustfmt check.
No behavior change.

* fix: call try_load_storage_state in all handle_launch branches

The CDP URL, CDP port, auto-connect, and provider early-return branches
were skipping storage state loading because try_load_storage_state was
only called in the normal BrowserManager::launch() path at the bottom
of handle_launch().

Also compute storage_state_owned once and reuse it across all branches
rather than borrowing storage_state (a &str tied to cmd) in a helper
that needs an owned Option<String>.

* Fix storage state reload on reused launches

* Fix storage-state launch errors

* Fix storage state replay ordering

* Align storage-state errors across launch paths

* Fix storageState launch cleanup
2026-04-16 08:38:54 -05:00
Chris Tate a884960806 Prepare v0.25.5 (#1246)
* fix(test): tolerate stale screencast frames in viewport e2e test

Chrome's `Page.startScreencast` `maxWidth`/`maxHeight` are upper bounds,
and early frames can arrive before the viewport resize fully takes effect.
Instead of asserting exact JPEG dimensions on the first frame, skip frames
with stale dimensions and wait for one that matches.

* Prepare v0.25.5
2026-04-16 01:19:52 -05:00
Chris Tate dba382350b fix(test): tolerate stale screencast frames in viewport e2e test (#1245)
Chrome's `Page.startScreencast` `maxWidth`/`maxHeight` are upper bounds,
and early frames can arrive before the viewport resize fully takes effect.
Instead of asserting exact JPEG dimensions on the first frame, skip frames
with stale dimensions and wait for one that matches.
2026-04-16 00:54:29 -05:00
Chris Tate 2e99293e80 fix(ci): install ffmpeg for e2e recording test (#1244)
The `e2e_recording_inherits_viewport` test added in #1208 requires
ffmpeg on the CI runner. Without it, `recording_start` fails with
"ffmpeg not found".
2026-04-16 00:20:04 -05:00
jin.2andhyunjinee b02e485a37 fix: prefer DevToolsActivePort websocket path over HTTP discovery in --auto-connect (#1218)
* fix: prefer DevToolsActivePort websocket path over HTTP discovery in --auto-connect

Reverses the discovery order in `auto_connect_cdp()` so the exact
WebSocket path from DevToolsActivePort is tried first, falling back
to legacy HTTP endpoints (`/json/version`, `/json/list`) only when
the direct path fails. This eliminates the duplicate remote-debugging
permission prompts caused by unnecessary HTTP probes on Chrome M144+.

Also adds `verify_ws_endpoint()` to validate the WebSocket URL is a
live CDP server before returning it, preventing stale URLs from being
handed to callers.

Fixes #1210
Fixes #1206

* chore: remove unrelated issue references from test comment

* style: apply rustfmt

---------

Co-authored-by: hyunjinee <leehj0110@kakao.com>
2026-04-15 17:50:11 -05:00
jin.2andhyunjinee db29d5fead fix: inherit viewport dimensions in recording context (#1208)
* fix: inherit viewport dimensions in recording context

When `record start` creates a new browser context, it now re-applies the
current viewport settings (from `set viewport` or `set device`) so the
recording resolution matches what the user configured instead of falling
back to the default 1280×720.

Closes #1207

* style: apply cargo fmt to e2e test

* chore: remove obvious comments

* chore: remove obvious comments from e2e test

---------

Co-authored-by: hyunjinee <leehj0110@kakao.com>
2026-04-13 23:40:26 -05:00
Chris Tate ddf6d6a2af fix: print data for get box and get styles in text mode (#1231) (#1233)
The text-mode output formatter had branches for most `get` subcommand
response shapes but was missing handlers for `boundingbox` and `styles`.
Both commands fell through to the default "Done" message instead of
printing the returned data.

Closes #1231
2026-04-13 23:39:11 -05:00
Asish Kumar 50323499c8 fix: preserve the active page when removing earlier tabs (#1220)
Adjust tab-removal bookkeeping so closing or losing a page before the active tab keeps the session pointed at the same logical page instead of silently shifting to the next one.

Add regression coverage for earlier-tab removal, later-tab removal, last-tab clamping, and the empty-page case.

Signed-off-by: Asish Kumar <officialasishkumar@gmail.com>
2026-04-13 16:50:46 -05:00
Chris Tate 2114bdf847 Prepare v0.25.4 release (#1228) 2026-04-12 13:44:15 -05:00
Chris Tate 7c2ff0a2a6 Move specialized skills to skill-data/ so npx skills add only finds one (#1227)
The skills CLI metadata.internal flag was never implemented (PRs #587
and #652 were both closed). All 6 skills were showing in the installer.

Move the 5 specialized skills (dogfood, electron, slack, vercel-sandbox,
agentcore) from skills/ to skill-data/, which the skills CLI does not
search. The bootstrap skill stays in skills/ for discovery. The Rust CLI
searches both directories so agent-browser skills list/get still serves
all 6.
2026-04-12 13:13:04 -05:00
Chris Tate 71343069d2 Add agent-browser skills command with evals (#1225)
* 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
2026-04-12 12:55:46 -05:00
Chris Tate fa043a496f fetch GitHub star count dynamically in docs header (#1202)
* fetch GitHub star count dynamically in docs header

Replace the hardcoded "27k" star count with a live fetch from the
GitHub API, revalidated every 24 hours via Next.js fetch caching.
Gracefully hides the count if the API is unreachable.

* remove GITHUB_TOKEN usage from star count fetch
2026-04-09 02:07:32 -05:00
Marshall Sun e4e2fe8633 fix(skill): correct duplicate Option numbering in auth section (#1161) 2026-04-07 01:29:01 -05:00
2164e71c30 fix: use custom viewport dimensions in streaming frame metadata and image resolution (#1033)
* fix: use custom viewport dimensions in streaming frame metadata

  CDP's Page.screencastFrame metadata returns physical device dimensions
  instead of the emulated viewport, causing frame messages to report
  incorrect deviceWidth/deviceHeight when a custom viewport is set.

  Use the viewport dimensions captured at screencast start instead of
  the CDP metadata values, since the screencast image is already captured
  at the configured viewport size.

  Closes #1031

* fix: resize browser content area on viewport change for correct
  screencast dimensions

  Emulation.setDeviceMetricsOverride only changes the CSS viewport, but
  screencast captures the actual browser content area. This caused frame
  images to have incorrect dimensions (e.g., 1000x451 instead of
  1000x1000)
  when a custom viewport was set.

  - Call Browser.setContentsSize after setDeviceMetricsOverride so the
    content area matches the emulated viewport
  - Restart active screencast when viewport dimensions change so
    maxWidth/maxHeight parameters are updated
  - Skip redundant screencast restarts when dimensions are unchanged
  - Extend E2E test to verify actual JPEG image dimensions, not just
    metadata

* fix: pass viewport dimensions to --window-size at launch and log setContentsSize failures

- Add viewport_size to LaunchOptions so --window-size matches the
  configured viewport from the start, reducing reliance on the
  experimental Browser.setContentsSize CDP call at runtime
- Log Browser.setContentsSize failures instead of silently ignoring
  them with let _ =

* fix: remove duplicate viewport change detection block (dead code from merge)

* fix: use log::debug! instead of eprintln! for setContentsSize failure

* revert: use eprintln! instead of log crate for setContentsSize failure

The daemon's stderr pipe is closed after startup, so log crate
subscribers cannot output during normal operation. eprintln! is
visible during startup and in tests, matching the existing convention.

---------

Co-authored-by: hyunjinee <leehj0110@kakao.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 01:28:36 -05:00
juniper929andwangjingjing 6520e4123c fix: re-apply ignore_https_errors to recording context (#1178)
Security.setIgnoreCertificateErrors is session-scoped, so creating a new
BrowserContext for recording (Target.createBrowserContext) starts with the
default certificate validation enabled, ignoring the launch-time flag.

Store ignore_https_errors in BrowserManager alongside download_path, and
re-apply Security.setIgnoreCertificateErrors to the new session after
recording context creation — matching the existing pattern for download
behavior re-application.

Fixes #1172

Co-authored-by: wangjingjing <wangjingjing.99@bytedance.com>
2026-04-07 01:26:07 -05:00
Chris Tate 6d05a9485d v0.25.3 (#1176) 2026-04-06 21:04:38 -05:00
jin.2andhyunjinee 1a6ea17ed0 fix: promote hidden radio/checkbox inputs in snapshot refs (#1085)
* fix: promote hidden radio/checkbox inputs in snapshot refs (#1024)

When a <label> wraps a display:none <input type="radio">, Chrome
excludes the input from the accessibility tree entirely. The label
appears as role="LabelText" with an empty name, making it impossible
for AI agents to identify radio buttons via data.refs.

Detect hidden radio/checkbox inputs during cursor-interactive scanning
and promote their parent LabelText/generic nodes to the correct role
with proper name and checked state.

- Add HiddenInputKind enum to validate input types at parse boundary
- Extend cursor-interactive JS to detect hidden inputs inside elements
- Extract promote_hidden_inputs() for testable role promotion logic
- Add unit tests for promotion, name preservation, and skip conditions

* style: apply cargo fmt

---------

Co-authored-by: hyunjinee <leehj0110@kakao.com>
2026-04-06 20:53:13 -05:00
Chris Tate c4e0f9d367 anchors (#1175) 2026-04-06 19:59:03 -05:00
Chris Tate b75fba130b v0.25.2 (#1174) 2026-04-06 18:56:05 -05:00
Chris Tate eb15cc0894 fix: remove PR_SET_PDEATHSIG that kills Chrome after ~10s idle (#1157) (#1173)
v0.24.1 introduced `prctl(PR_SET_PDEATHSIG, SIGKILL)` in #1137 to kill Chrome
when the daemon dies. However, `PR_SET_PDEATHSIG` tracks the **thread** that
called `fork()`, not the process (`prctl(2)` documents this). Chrome is spawned
via `tokio::task::spawn_blocking`, whose threads are reaped after ~10 seconds of
idle time. When the blocking thread exits, the kernel sends SIGKILL to Chrome
even though the daemon is still alive.

Symptoms reported in #1157:
- `tab list` shows `about:blank` after a few seconds
- `snapshot` returns an empty page
- All Chrome processes exit ~9 seconds after launch
- Any workflow involving navigation or waiting breaks

The fix removes `PR_SET_PDEATHSIG` from the Chrome `pre_exec` hook. Orphan
cleanup is already handled by the process-group kill (`kill(-pgid, SIGKILL)`) in
`ChromeProcess::kill()`, which runs via daemon signal handlers, `close_notify`,
idle timeout, and `Drop`.

Fixes #1157
2026-04-06 18:44:56 -05:00
Chris Tate 7b3f826cbb v0.25.1 (#1170) 2026-04-06 10:53:37 -05:00
Chris Tate 1f8757b215 embed dashboard (#1169)
* embed dashboard

* docs

* fmt
2026-04-06 10:45:08 -05:00
Chris Tate 3896ed0d9d fix: recover GitHub release when npm published but release creation failed (#1168)
check-release now detects when the npm version matches but the GitHub
release is missing. build-binaries and github-release run in that case
so binaries, dashboard, and release notes are created without requiring
a version bump.
2026-04-06 10:22:11 -05:00
Chris Tate 92d730e5fd fix dashboard build (#1167) 2026-04-06 10:05:35 -05:00
Chris Tate 77805ff4bc v0.25.0 (#1166) 2026-04-06 09:50:13 -05:00
Chris Tate c3bbb15c5f fix: CI test failures on Windows and E2E (#1165)
- Windows: match "actively refused it" error message in
  download_bytes_connection_refused test (os error 10061)
- E2E relaunch: use userAgent instead of extensions to trigger
  relaunch, since extensions force headed mode which requires a
  display server unavailable in CI
- E2E auth_login SPA: use addEventListener instead of inline
  onsubmit for more reliable form submission prevention
2026-04-06 09:42:30 -05:00
Chris Tate 131f229971 chat (#1163)
* chat

* docs

* fmt
2026-04-06 09:21:11 -05:00
Chris Tate 317e6869b6 Add AI chat to dashboard, refactor stream module, snapshot --urls, batch argument mode (#1160)
* chat

* refactor

* fixes

* fixes

* fixes

* fixes

* improvements

* download chat

* batch

* fixes

* fixes

* fixes

* fmt

* fixes

* fixes

* fixes

* fmt
2026-04-06 08:10:43 -05:00
jin.2andhyunjinee fcb6615f5a fix: support accessibility tree refs in upload command (#1156)
* fix: support accessibility tree refs in upload command (#1107)

The upload command only accepted CSS selectors while click/fill supported
accessibility tree refs (e.g. e1, @e1, ref=e1). This resolves the API
inconsistency by reusing resolve_element_object_id for all selector types.

* style: apply cargo fmt

---------

Co-authored-by: hyunjinee <leehj0110@kakao.com>
2026-04-05 15:38:49 -05:00
Chris Tateandctate c47756be9b fix(cli): honor AGENT_BROWSER_DEFAULT_TIMEOUT env var for wait commands (#1153)
* fix(cli): honor AGENT_BROWSER_DEFAULT_TIMEOUT env var for wait commands

The `AGENT_BROWSER_DEFAULT_TIMEOUT` environment variable was being ignored by CLI wait commands, causing them to use hardcoded 30-second timeouts instead of the configured default.

## Changes Made

- **Centralized timeout injection**: Modified `parse_command()` to automatically inject `flags.default_timeout` into any wait-family command that doesn't already have an explicit `--timeout` flag
- **Environment variable parsing**: Added `default_timeout` field to `Flags` struct that reads from `AGENT_BROWSER_DEFAULT_TIMEOUT` env var
- **Daemon propagation**: Updated daemon spawning to pass through the default timeout via environment variables
- **Unified timeout handling**: Added `timeout_ms()` helper method in `DaemonState` that all wait handlers now use instead of scattered `unwrap_or()` calls
- **Comprehensive test coverage**: Added 10 regression tests covering all wait command variants and edge cases

## Implementation Details

The fix uses a two-stage approach:
1. CLI parses the env var and injects timeout values into command JSON for any `wait*` action
2. Daemon reads the env var and provides a centralized fallback via `timeout_ms()` helper

This ensures new wait variants automatically inherit the default timeout without requiring per-variant wiring.

Fixes #1147

* fix: preserve 30s default timeout for backward compatibility

The default_timeout_ms fallback was set to 25_000ms, which silently
changes the existing 30_000ms behavior for users who haven't set
AGENT_BROWSER_DEFAULT_TIMEOUT. Restore the original 30s default.

---------

Co-authored-by: ctate <366502+ctate@users.noreply.github.com>
2026-04-05 14:15:00 -05:00
Chris Tate 44f37c92d3 fix(cli): improve dashboard download error handling and retry logic (#1154)
This PR fixes dashboard installation failures by improving HTTP error handling and adding retry logic for network issues.

## Problem
Users were experiencing dashboard installation failures with cryptic error messages like "error sending request for url" when network issues occurred or when GitHub releases were temporarily unavailable.

## Changes
- **Enhanced HTTP client**: Added proper User-Agent, timeouts (120s total, 30s connect), and better error formatting
- **Retry logic**: Added exponential backoff retry (up to 3 attempts) for connection errors and server errors (5xx)
- **Better error messages**: Improved error formatting with full error chain context
- **Comprehensive tests**: Added unit tests for various failure scenarios (404, connection errors, partial downloads)

## Implementation Details
- Replaced direct `reqwest::get()` calls with a configured HTTP client
- Added `format_reqwest_error()` to provide detailed error context
- Implemented retry logic in `download_bytes()` with exponential backoff
- Added extensive test coverage including mock HTTP server scenarios

Fixes #1146
2026-04-05 10:07:08 -05:00
jin.2andhyunjinee 9f51879012 fix: rewrite getByRole to use CDP accessibility tree with ref-based element resolution (#1145)
* fix: rewrite getByRole to use CDP accessibility tree instead of CSS selectors

The old `handle_getbyrole` generated `querySelectorAll('[role="link"], link')`
which matched `<link>` stylesheet elements instead of `<a>` anchor tags.
This happened because ARIA role names were used directly as CSS tag selectors,
and several roles differ from their HTML element names (e.g. link → a,
heading → h1-h6, textbox → input/textarea).

The fix replaces the JS-based DOM query with the CDP `Accessibility.getFullAXTree`
API, where the browser engine correctly computes implicit ARIA roles per the
WAI-ARIA / HTML-AAM spec. This is the same approach already used by `snapshot.rs`
and `element.rs` in this codebase.

Changes:
- Rewrite `handle_getbyrole` to query the browser's accessibility tree via CDP
- Add `find_ax_node_by_role` helper for AX tree traversal with role/name/exact matching
- Use `DOM.resolveNode` + `Runtime.callFunctionOn` to bridge AX node → DOM marker
- Add iframe support via `resolve_ax_session` (missing in old implementation)
- Fix cleanup to use correct CDP session (old code used default session, breaking iframe cleanup)
- Export `extract_ax_string` as `pub(super)` for reuse
- Add 4 regression tests for `find_ax_node_by_role`

Fixes #1123

* style: apply cargo fmt

* chore: remove redundant comments

* refactor: replace marker attribute with temporary ref for element resolution

Eliminates 3 CDP round-trips (DOM.resolveNode, Runtime.callFunctionOn,
Runtime.evaluate cleanup) by registering a temporary ref in the ref_map.
execute_subaction resolves the element via backendNodeId directly.
No more DOM pollution with marker attributes.

* fix: ref counter collision, ref_map leak, and stale fallback name

- Increment next_ref_num after inserting temp ref to prevent id collision
- Remove temp ref after execute_subaction to prevent unbounded ref_map growth
- Return actual AX name from find_ax_node_by_role for accurate fallback resolution
- Add RefMap::remove method

---------

Co-authored-by: hyunjinee <leehj0110@kakao.com>
2026-04-05 09:10:24 -05:00