100 Commits
Author SHA1 Message Date
Chris TateandMuhtasham e93acc68f8 Require same-origin stream commands (#1355)
* Require same-origin stream commands

Protect the per-session command relay from browser-originated cross-origin requests while preserving same-origin dashboard access.

Co-authored-by: Muhtasham <20128202+Muhtasham@users.noreply.github.com>

* Harden stream command origin checks

Require command relay requests to come from loopback same-origin metadata and prevent request bodies from spoofing security headers.

Co-authored-by: Muhtasham <20128202+Muhtasham@users.noreply.github.com>

---------

Co-authored-by: Muhtasham <20128202+Muhtasham@users.noreply.github.com>
2026-06-01 10:32:44 +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
Chris Tate 3bb1d43f8b fix(doctor): make generated ids unique per call (#1330) 2026-05-06 10:48:19 -05: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
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
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
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
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
Chris Tate 6d05a9485d v0.25.3 (#1176) 2026-04-06 21:04:38 -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
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
Chris Tate 1205e2ca9c v0.24.1 (#1142)
* v0.24.1

* fix: e2e test failures on CI

- e2e_relaunch_on_options_change: use headless for all launches;
  the third launch only changes extensions, which is sufficient to
  trigger the relaunch hash mismatch without needing an X display
- e2e_auth_login flake: reduce SPA render delay from 1200ms to 800ms
  to add headroom within the 5s preferred selector window on slower
  CI runners
2026-04-04 12:49:40 -05:00
Chris Tateandctate 354dd8b615 fix: pass --ignore-certificate-errors Chrome flag when --ignore-https-errors is set (#1132)
* fix: pass --ignore-certificate-errors Chrome flag when --ignore-https-errors is set

The existing CDP-level Security.setIgnoreCertificateErrors only takes
effect after Chrome opens a connection, but some TLS errors (e.g.
ERR_SSL_PROTOCOL_ERROR) are rejected at the network layer before CDP
can intervene. Adding the Chrome launch flag ensures certificate errors
are bypassed from process start.

Fixes #1124

* test: add unit tests for --ignore-certificate-errors Chrome flag

---------

Co-authored-by: ctate <366502+ctate@users.noreply.github.com>
2026-04-04 11:15:48 -05:00
Chris Tateandctate 9b0205ef50 fix: prevent orphaned Chrome processes on daemon exit (#1137)
Three changes to ensure headless Chrome process trees are fully cleaned
up when the daemon exits, whether gracefully or abnormally:

1. Spawn Chrome in its own process group (`setpgid(0,0)`) and kill the
   entire group (`kill(-pgid, SIGKILL)`) in `ChromeProcess::kill()`.
   This takes down all helper processes (GPU, renderer, utility,
   crashpad) instead of only the main Chrome PID.

2. On Linux, set `PR_SET_PDEATHSIG(SIGKILL)` on the Chrome process so
   the kernel automatically kills it when the daemon dies for any
   reason, including SIGKILL/OOM. No macOS equivalent exists.

3. Replace `process::exit(0)` in the daemon's close handler with a
   `Notify` signal back to the main loop, so Rust destructors
   (including `ChromeProcess::Drop`) actually run.

Co-authored-by: ctate <366502+ctate@users.noreply.github.com>
2026-04-04 11:15:26 -05:00
Chris Tateandctate c69f611d78 Fix CDP attach hang on real browser sessions (Chrome 144+) (#1133)
When connecting to a real, already-running browser (Chrome 144+) via CDP,
targets may be paused waiting for the debugger after attach. Without an
explicit Runtime.runIfWaitingForDebugger call, page-level commands hang
indefinitely even though the WebSocket connection is live.

Add Runtime.runIfWaitingForDebugger after Runtime.enable in all target
attachment paths: enable_domains (covers initial attach, tab_new,
tab_switch), enable_domains_direct (provider proxies), and the iframe
auto-attach handler. The call is placed before Network.enable to avoid
the documented deadlock when Network.enable precedes the resume. It is
a no-op for targets that are not paused.

Fixes #1130

Co-authored-by: ctate <366502+ctate@users.noreply.github.com>
2026-04-04 11:12:35 -05:00
Chris Tateandctate 2911d91ce3 Fix stale daemon after upgrade causing silent CDP failures (#1134)
After upgrading agent-browser, the old daemon process keeps running.
ensure_daemon() only checks socket connectivity, not version, so the
new CLI silently reuses the old daemon — causing broken CDP behavior
with no error or warning.

Add a version sidecar file (.version) written by the daemon on startup.
ensure_daemon() now compares it against the CLI's compiled version and
automatically kills/restarts on mismatch. Missing version files (from
pre-fix or Node.js-era daemons) are treated as mismatches so the first
upgrade to this version also benefits.

Fixes #1127

Co-authored-by: ctate <366502+ctate@users.noreply.github.com>
2026-04-04 11:07:30 -05:00
Chris Tateandctate 5e33672d08 fix: recover from stale daemon/socket state (#1136)
When a daemon is killed or crashes without cleaning up, stale .sock/.pid
files are left behind. Previously, `close --all` would fail to connect to
these zombie daemons and simply report an error, leaving the stale files
in place and poisoning all future sessions.

Three fixes:

1. `close --all` now force-kills unreachable daemon processes and removes
   all stale files (pid, sock, stream) instead of reporting failure. It
   also cleans up dead-but-lingering PID files during enumeration and
   scans for orphaned .sock files without corresponding .pid files.

2. `ensure_daemon` handles concurrent startup races: when a spawned
   daemon exits with "Address already in use" (another instance won the
   bind race), it checks whether the winner is accepting connections and
   piggybacks on it instead of failing.

3. `cleanup_stale_files` is now public so `close --all` can reuse it.

Fixes #1118

Co-authored-by: ctate <366502+ctate@users.noreply.github.com>
2026-04-04 10:54:46 -05:00
Chris Tateandctate c52d25d576 Fix HAR capture missing API requests under heavy traffic (#1135)
The CDP event broadcast buffer (256 events) was too small for pages with
many concurrent API requests, causing silent event drops. Modern SPAs
routinely fire 100+ API calls during page load, generating 300+ CDP
network events that would overflow the buffer between drain cycles.

Changes:
- Increase CDP broadcast buffer from 256 to 4096 (event channel) and
  512 to 4096 (raw channel)
- Reduce background drain interval from 500ms to 100ms
- Handle Network.loadingFailed events in HAR recording
- Enable Network.enable on cross-origin iframe sessions during HAR
  recording and request tracking
- Allow Network events from iframe sessions through the session filter
- Log a warning when buffer overflow occurs instead of silently dropping

Fixes #1128

Co-authored-by: ctate <366502+ctate@users.noreply.github.com>
2026-04-04 10:25:12 -05:00
Chris Tate 0a36666587 agentcore skill (#1122) 2026-04-02 21:00:15 -05:00
Chris Tate 2a44b515ee v0.24.0 (#1121) 2026-04-02 20:42:07 -05:00
Chris Tate 13ed01b3bd agentcore docs (#1120)
* agentcore docs

* fixes

* fixes
2026-04-02 20:31:14 -05:00
Chris Tate 89595836c6 v0.23.4 (#1100) 2026-03-31 02:09:57 -05:00
Chris Tate 7b4b124e7f Fix daemon hang on Linux caused by waitpid(-1) race condition (#1098)
* Fix daemon hang on Linux caused by waitpid(-1) race condition

Fixes #1035

The SIGCHLD handler added in v0.22.3 called `waitpid(-1, WNOHANG)` to reap zombie Chrome processes. This races with Rust's `Child::try_wait()` / `Child::wait()` because `waitpid(-1)` reaps *any* child in the process, stealing the exit status before the `Child` handle can collect it. The result is `ECHILD` errors in `BrowserManager::has_process_exited()` and `ChromeProcess::kill()`, leaving the daemon in a broken state that manifests as indefinite hangs on Linux servers.

The fix removes the global SIGCHLD handler and `reap_children()` function entirely. Instead, the existing 500ms drain interval now checks `mgr.has_process_exited()` (which delegates to `Child::try_wait()`) for targeted, race-free crash detection. When Chrome is detected as crashed, the `BrowserManager` is closed and daemon state is reset.

## Changes

- Removed `SIGCHLD` signal handler and `reap_children()` from the Unix daemon event loop
- Enhanced the drain interval to detect Chrome crashes via `has_process_exited()` and clean up state
- Added 3 regression tests:
  - Static source scan that fails if `waitpid(-1)` is re-introduced in production code
  - `try_wait()` correctness test for exit detection without a SIGCHLD handler
  - Kill detection test simulating a Chrome crash

* fmt
2026-03-31 01:59:07 -05:00
Chris Tate b2b6356d63 fix release notes (#1097)
* fix release notes

* contributors note
2026-03-30 20:54:59 -05:00
Chris Tate e6ba1eb8c9 v0.23.3 (#1096) 2026-03-30 20:29:20 -05:00
Chris Tate 6c93480d0d streamline release (#1095)
* update release

* more docs

* dates
2026-03-30 19:53:53 -05:00
Chris Tate 3c942e2874 prepare v0.23.2 (#1093) 2026-03-30 18:38:57 -05:00
Chris Tate 40fdb4284d feat: dashboard provider support and session creation improvements (#1092)
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
2026-03-30 18:35:12 -05:00
Chris Tate fbcab375b0 chore: prepare v0.23.1 release (#1089)
* chore: add patch changeset for v0.23.1 release

Add changeset covering 7 commits since v0.23.0: auto-dialog dismissal,
Puppeteer cache fallback, console output improvements, same-document
navigation fix, cross-domain save_state, external tab detection in CDP
mode, and dashboard hot-reload.

Fill documentation gaps: Puppeteer/Brave in browser discovery tables,
console --json args field, AGENT_BROWSER_NO_AUTO_DIALOG env var in
SKILL.md.

* chore: point package.json homepage to agent-browser.dev
2026-03-30 13:12:07 -05:00
Chris Tate 8d78fcbbb3 fix: Windows Chrome extraction and debugging environment (#1088)
* windows debugging

* fixes

* fixes

* fix: handle Windows path separators in Chrome zip extraction

The zip crate's enclosed_name() normalizes paths to use backslashes on
Windows, but extract_zip used split_once('/') which only matches forward
slashes. This caused Chrome to be extracted into a nested chrome-win64/
subdirectory instead of directly into the version directory.

Also adds debug diagnostics to find_installed_chrome() (gated behind
AGENT_BROWSER_DEBUG) and better error messages when Chrome cache exists
but no binary is found.

Fixes #1076

* feat: add Puppeteer browser cache as Chrome fallback

Search ~/.cache/puppeteer/chrome/ (or PUPPETEER_CACHE_DIR) for Chrome
binaries before falling back to Playwright's cache. Puppeteer v19+
stores Chrome for Testing in this location, so users with an existing
Puppeteer install can use agent-browser without a separate install step.

* fmt
2026-03-30 12:37:01 -05:00
Chris Tateandctate 6dd53449e8 Add auto-dismissal for alert and beforeunload dialogs (#1075)
* 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>
2026-03-29 12:00:27 -06:00
Chris Tateandctate da7fef3fef fix: dashboard server picks up installed files without restart (#1066)
The dashboard HTTP server checked for index.html once at startup and
cached the result. If the server started before `dashboard install`,
it permanently served the "not installed" fallback page.

Check for installed dashboard files on each request instead, so
`dashboard install` takes effect immediately on a running server.

Fixes #1065

Co-authored-by: ctate <366502+ctate@users.noreply.github.com>
2026-03-29 11:54:06 -06:00
Chris Tate bbad2de627 fix: include root package in pnpm workspace for changesets (#1053)
The dashboard PR introduced pnpm-workspace.yaml but only listed
packages/* and docs. Changesets could no longer find the root
agent-browser package, breaking the release CI. Adding '.' makes
the root a workspace package again.
2026-03-27 10:17:48 -07:00
Chris Tate 0f0f300d40 chore: add minor changeset for v0.23.0 release (#1052) 2026-03-27 09:46:17 -07:00
Chris Tate db215a1467 fix lightpanda (#1050)
* fix lightpanda

* fmt
2026-03-27 09:33:08 -07:00
Chris Tateandctate a95bc0f75a fix(windows): fall back to OS-assigned port when Hyper-V blocks daemon TCP bind (#1041)
On Windows the daemon derives a TCP port from the session name via a
djb2 hash (e.g. "default" → 50838). On many machines this port falls
inside Hyper-V's excluded port range (winnat), causing EACCES on bind
and preventing the daemon from starting.

Changes:
- daemon: try the hash-derived port first; on failure, bind to port 0
  (OS-assigned) and write the actual port to the .port file
- client (connection.rs, stream.rs): read the .port file to discover the
  daemon's actual port, falling back to the hash if the file is absent
- run_daemon: guard .sock file operations with #[cfg(unix)] and add
  .port file cleanup for #[cfg(windows)]

Fixes #390

Co-authored-by: ctate <366502+ctate@users.noreply.github.com>
2026-03-26 21:01:06 -07:00
Chris Tate 995a47fdb0 fix: use TCP instead of Unix socket on Windows in dashboard relay (#1038)
`relay_command_to_daemon` in stream.rs used `tokio::net::UnixStream`
unconditionally, which doesn't compile on Windows. Add platform-
conditional code matching the existing pattern in daemon.rs and
connection.rs: Unix sockets on unix, TCP on Windows.
2026-03-26 13:36:22 -07:00
Chris Tatectategithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Stefan SmiljkoviczhanbaxuyongliangxuyongliangThomas Kosiewski
f9174513c2 dashboard (#1034)
* 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>
2026-03-26 08:43:35 -07:00
Chris Tateandctate 89a8ceccf7 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>
2026-03-25 11:40:19 -07:00
Chris Tate eb64ca497a chore: patch release - ### Bug Fixes
- **Re-apply download behavior on r... (#1025)
2026-03-25 11:29:51 -07:00
Chris Tateandctate 5ac01fa743 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>
2026-03-25 08:04:43 -07:00
Chris Tateandgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 0865851293 chore: version packages (#1009)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-03-24 15:21:31 -07:00
Chris Tate a0981979ca chore: patch release - ### New Features
- **Dialog status command** - Ad... (#1005)
2026-03-24 15:03:10 -05:00
Chris Tateandctate cd1f255129 fix: handle proxy authentication via CDP Fetch.authRequired (#1000)
* fix: handle proxy authentication via CDP Fetch.authRequired

Chrome's --proxy-server flag does not support credentials embedded in
the URL. When a proxy requires authentication, Chrome receives a 407
from the proxy but has no way to respond with credentials, resulting
in net::ERR_INVALID_AUTH_CREDENTIALS.

Fix by:
1. Parsing credentials from the proxy URL (already done by parse_proxy)
2. Storing them in DaemonState.proxy_credentials
3. Enabling Fetch.enable with handleAuthRequests: true
4. Responding to Fetch.authRequired events with Fetch.continueWithAuth
5. Passing only the server URL (without credentials) to --proxy-server
6. Forwarding credentials to the daemon via dedicated env vars

Also adds fallback to standard proxy env vars (HTTP_PROXY, HTTPS_PROXY,
ALL_PROXY, NO_PROXY) when AGENT_BROWSER_PROXY is not set.

Fixes #990

* refactor: use typed struct for parse_proxy, fix double Fetch.enable and username-only auth

- Replace serde_json::Value return from parse_proxy with a typed ParsedProxy struct
- Fix double Fetch.enable call when both proxy auth and domain filter are active
  (the second call could overwrite handleAuthRequests from the first)
- Allow username-only proxy auth (some proxies don't require a password)
- Handle empty username/password in parse_proxy as None instead of Some("")
- Use install_domain_filter_fetch in auto_launch for consistency
- Update unit tests to use typed struct fields

---------

Co-authored-by: ctate <366502+ctate@users.noreply.github.com>
2026-03-24 12:52:39 -05:00
Chris Tateandctate 23a117c5c2 fix: add font packages to install --with-deps for CJK and emoji support (#1002)
The `--with-deps` flag installed font rendering libraries (libfreetype6,
libfontconfig1) but no actual font files, causing CJK characters and
emoji to render as invisible/tofu on headless Linux systems.

Add font file packages for all three supported package managers:
- apt: fonts-noto-color-emoji, fonts-noto-cjk, fonts-freefont-ttf
- dnf: google-noto-cjk-fonts, google-noto-emoji-color-fonts, liberation-fonts
- yum: google-noto-cjk-fonts, liberation-fonts

Closes #1001

Co-authored-by: ctate <366502+ctate@users.noreply.github.com>
2026-03-24 12:03:21 -05:00
Chris Tateandctate 32ffd8f3c4 feat: add dialog detection and document dialog commands (#999)
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>
2026-03-24 11:38:23 -05:00
Chris Tateandctate 780edb2c45 fix: download drops Browser-domain CDP events due to sessionId mismatch (#998)
Chrome's Browser.downloadWillBegin and Browser.downloadProgress events
may arrive without a sessionId or with a different sessionId than the
page session used to configure the download behavior. The previous code
required an exact session match, silently dropping these events and
causing the 30-second timeout -- which manifests as an endless download
loop when callers retry.

Changes:
- Accept Browser-domain download events regardless of sessionId while
  still matching Page-domain events by session to avoid cross-tab issues
- Add a brief retry loop (up to 1s) for the GUID file to appear on disk
  after Chrome signals completion, handling filesystem flush races
- Return a proper error instead of silently succeeding when the
  GUID-named file cannot be found
- Apply the same sessionId fix to handle_waitfordownload and add
  Browser.downloadProgress support (previously only checked
  Page.downloadProgress)

Fixes #989

Co-authored-by: ctate <366502+ctate@users.noreply.github.com>
2026-03-24 11:35:49 -05:00
Chris Tate 3a3317b048 chore: patch release - ### Bug Fixes
- Fixed **modifier key chords** (e.... (#985)
2026-03-23 20:30:53 -05:00
Chris Tateandctate f806b666ba fix: preserve query parameters in --cdp HTTP URLs (#982)
When --cdp is given an HTTP/HTTPS URL (e.g. http://host:5095?mode=Hello),
resolve_cdp_url extracts host and port for CDP discovery but discards the
query string. The discovered WebSocket URL therefore never includes the
user's original query parameters, breaking relay servers that depend on
them.

Thread the original query string through discover_cdp_url and append it
to the final WebSocket URL after host/port rewriting. WebSocket URLs
(ws://, wss://) are already passed through unchanged and are unaffected.

Fixes #977

Co-authored-by: ctate <366502+ctate@users.noreply.github.com>
2026-03-23 18:47:23 -05:00
Chris Tateandctate a7a59c94f3 Fix press Control+a and other modifier key chords (#980)
The `press` command was not parsing modifier+key chords (e.g.
`Control+a`, `Shift+Enter`). It sent the raw string as a single key
name, so CDP never applied the modifier — and the `text` field caused
the literal character to be inserted instead of triggering the shortcut.

Two changes:
1. `actions.rs` — add `parse_key_chord()` to split inputs like
   `Control+Shift+a` into the base key (`a`) and a CDP modifier
   bitmask (Alt=1, Ctrl=2, Meta=4, Shift=8), then call
   `press_key_with_modifiers` instead of `press_key`.
2. `interaction.rs` — suppress the `text` field in `keyDown`/`keyUp`
   events when Control or Meta modifiers are active, so the browser
   treats them as command chords rather than text input.

Includes unit tests for the chord parser covering plain keys, single
modifiers, multi-modifier combos, modifier aliases, and edge cases.

Co-authored-by: ctate <366502+ctate@users.noreply.github.com>
2026-03-23 16:43:43 -05:00
Chris Tate be30bc902d chore: add minor changeset for release (#973) 2026-03-23 12:07:07 -05:00
Chris Tateandctate 1391f00404 Fix download command to properly handle absolute paths and click elements (#970)
* Fix download command to properly handle absolute paths and click elements

The download command was not working correctly - it would return "done" but not actually download files to the specified path. The command was only setting download behavior without clicking the element or waiting for completion.

**Changes made:**
- Modified `handle_download` to take a `selector` parameter and click the download element
- Added proper absolute path resolution and directory creation
- Implemented CDP event listening to wait for download completion with 30s timeout
- Added file renaming logic to handle Chrome's GUID-based temporary filenames
- Changed response format to return the actual download path
- Fixed function signature to use `&mut DaemonState` for state modifications

**Implementation details:**
- Uses `Browser.downloadWillBegin` and `Browser.downloadProgress` CDP events to track downloads
- Falls back to finding the most recently modified file if GUID capture fails
- Creates parent directories automatically if they don't exist
- Handles both absolute and relative path inputs

Fixes #965

* Address review feedback: harden download path handling

- Canonicalize download directory to prevent path traversal attacks
- Remove dangerous fallback that renamed the most-recently-modified file
  in the directory (could silently rename unrelated files)
- Extract timeout to a named constant (DOWNLOAD_TIMEOUT)

* Fix download event loop: handle canceled state and Page.downloadWillBegin

- Detect "canceled" download state and return an error immediately instead
  of spinning until the 30s timeout.
- Also capture the download GUID from the deprecated Page.downloadWillBegin
  event for older Chrome compatibility, matching the existing
  Page.downloadProgress fallback.
- Consolidate duplicated session/event checks with a shared is_this_session
  variable and use match for cleaner state handling.

---------

Co-authored-by: ctate <366502+ctate@users.noreply.github.com>
2026-03-23 11:09:41 -05:00
Chris Tate c5020f2b89 Fix Enter key press not working by adding text field to keyDown events (#972)
This PR fixes an issue where pressing the Enter key via the `press Enter` command had no effect on web pages, even though the command executed successfully without errors.

## Problem
The Chrome DevTools Protocol `Input.dispatchKeyEvent` requires a `text` field in `keyDown` events for certain keys to trigger their default browser actions. Without this field, keys like Enter don't actually submit forms or perform their expected actions.

## Changes
- Added new `key_text()` function that returns the appropriate text value for special keys:
  - Enter returns `"\r"` (carriage return)
  - Tab returns `"\t"` (tab character)
  - Space returns `" "` (space character)
  - Single printable characters return themselves
  - Non-printable keys (Escape, Arrow keys, etc.) return `None`
- Modified `press_key_with_modifiers()` to populate both `text` and `unmodified_text` fields in the CDP keyDown event
- Added comprehensive unit tests for the new functionality

## Implementation Details
The fix ensures that when pressing Enter on a filled search box, the form actually submits as expected, matching the behavior of manually pressing the Enter key.

Fixes #966
2026-03-23 10:46:05 -05:00
Chris Tate 9b1961af93 fix: skip auto-connect when daemon already running to prevent multiple (#971)
Previously, the `--auto-connect` flag would send connection commands to the daemon on every CLI invocation, even when the daemon was already running and maintaining an active connection. This caused Chrome to repeatedly prompt for remote debugging permissions during long-running tasks.

This fix adds checks for `daemon_result.already_running` to skip sending launch commands when the daemon is already active and holding connections. The changes apply to:

- Auto-connect flow: Skip when daemon already running since it holds the connection from previous launch
- CDP connections: Skip sending commands but validate input eagerly for immediate error feedback
- Cloud provider connections: Skip when daemon already maintains the provider connection

This preserves the existing connection reuse logic in the daemon while preventing redundant connection attempts from the CLI side.

Fixes #962
2026-03-23 10:27:54 -05:00
Chris Tateandctate fb1e860b4e Improve upgrade command installation method detection robustness (#960)
* Improve upgrade command installation method detection robustness

The upgrade command was failing to detect installation method for users who installed via pnpm, yarn, or bun, showing "Could not detect installation method" errors.

## Changes Made

- **Added support for additional package managers**: Extended detection to include pnpm, yarn, and bun alongside existing npm, Homebrew, and Cargo support
- **Implemented install-time marker system**: Modified `postinstall.js` to write a `.install-method` marker file during installation, providing reliable detection that doesn't depend on fragile path heuristics
- **Enhanced path-based fallback detection**: Improved executable path analysis to better identify installation locations for all supported package managers
- **Added command probing**: Implemented fallback checks that query package managers directly to verify global installations

The detection now follows this robust hierarchy:
1. Read install-time marker file (most reliable)
2. Analyze executable path patterns
3. Probe package managers via subprocess calls

This ensures users can successfully upgrade regardless of their chosen package manager.

Fixes #954

* Add .install-method to .gitignore and note Yarn v2+ limitation

- Prevent bin/.install-method marker file from being accidentally
  committed during development
- Add comment clarifying that yarn global upgrade path only works
  with Yarn Classic (v1), not Yarn Berry (v2+)

---------

Co-authored-by: ctate <366502+ctate@users.noreply.github.com>
2026-03-22 09:51:58 -05:00
Chris Tate 7e5baa6d77 Fix viewport dimensions in streaming status messages and screencast (#952)
## Summary
Fixed an issue where custom viewport settings were ignored in WebSocket streaming, causing status messages to always report hardcoded dimensions (1280x720) instead of the actual viewport size.

## Changes Made
- **Added viewport tracking to StreamServer**: New `viewport_width` and `viewport_height` fields to store current dimensions
- **Updated viewport synchronization**: Modified `handle_viewport()` and `handle_device()` to update the stream server when viewport changes
- **Fixed status message broadcasting**: Replaced hardcoded dimensions with actual viewport values in `broadcast_status()` calls
- **Improved screencast defaults**: Changed screencast to use stored viewport dimensions as defaults instead of hardcoded 1280x720
- **Added viewport getter methods**: New `set_viewport()` and `viewport()` methods on StreamServer for dimension management

This ensures that WebSocket clients receive consistent viewport dimensions across all message types (status and frame messages), matching the actual browser viewport settings.

Fixes #950
2026-03-20 19:16:39 -05:00
Chris Tate 6daad22ada chore: patch release - ### Bug Fixes
- **WebSocket keepalive for remote ... (#946)
2026-03-20 08:36:04 -05:00
Chris Tateandctate 421f8fab82 fix: add WebSocket keepalive to prevent CDP connection drops on remote browsers (#936)
The v0.20.0 Rust rewrite dropped two keepalive mechanisms that the
Node.js/Playwright daemon provided, causing CDP connections to remote
Browserless instances to silently die between commands when traversing
multi-hop proxy topologies (Istio Envoy, OpenResty, etc.).

Restore parity with v0.19.0 and improve on it:

1. TCP SO_KEEPALIVE (v0.19.0 parity): Playwright's WebSocketTransport
   used HTTP agents with keepAlive: true, which set SO_KEEPALIVE on the
   underlying TCP socket. Restored via socket2::SockRef on the
   tokio_tungstenite stream before splitting.

2. WebSocket Ping frames (improvement): Send Ping frames every 30s on
   idle connections. This goes beyond v0.19.0 because L7 proxies (Envoy,
   nginx, OpenResty) can see WebSocket pings but not TCP keepalive
   probes, making this effective through application-layer proxy hops.

The keepalive task is coordinated with the reader loop via a watch
channel and stops automatically when the connection closes.

Fixes #934

Co-authored-by: ctate <366502+ctate@users.noreply.github.com>
2026-03-19 19:12:57 -05:00
Chris Tate 06b3b94493 colors + search for docs (#927)
* colors

* search
2026-03-19 01:50:19 -05:00
Chris Tate 757626f27c chore: add patch changeset for release (#919) 2026-03-18 17:02:34 -05:00
Chris Tateandctate 486e1b341f Fix Chrome headless launch failures with --enable-unsafe-swiftshader (#915)
* Fix Chrome headless launch failures by adding --disable-gpu flag

Fixes silent Chrome crashes in headless mode when GPU drivers are unavailable or restricted (common in VMs, containers, and cloud environments).

## Changes Made

- **Auto-add `--disable-gpu` flag**: Automatically includes `--disable-gpu` when launching Chrome in headless mode to prevent GPU initialization crashes
- **Improved error reporting**: Enhanced error messages to include Chrome's exit code when it crashes before writing DevToolsActivePort
- **Better user guidance**: Added helpful hints in error messages suggesting `--no-sandbox` and `--disable-gpu` flags for troubleshooting
- **Updated tests**: Added test coverage for the new `--disable-gpu` flag behavior

## Implementation Details

The `--disable-gpu` flag is only added in headless mode (when `options.headless && !has_extensions`), preserving GPU acceleration for non-headless usage. The error handling now captures Chrome's exit code and provides actionable debugging information when Chrome fails silently.

Fixes #914

* Use --enable-unsafe-swiftshader instead of --disable-gpu for Playwright parity

--disable-gpu disables all GPU acceleration and breaks WebGL on Chrome 130+.
Playwright uses --enable-unsafe-swiftshader to enable CPU-based software
rendering via SwiftShader, which prevents GPU-driver crashes while preserving
WebGL support. This matches the behavior from v0.19 (Playwright-based daemon).

---------

Co-authored-by: ctate <366502+ctate@users.noreply.github.com>
2026-03-18 16:30:09 -05:00
Chris Tate 1e7619d59d chore: add patch changeset for release (#910) 2026-03-18 10:39:34 -05:00
Chris Tate 8cfba1752d feat: add built-in upgrade command for self-update (#898)
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
2026-03-17 23:29:07 -05:00
Chris Tate c6de80b95e prepare v0.21 (#886) 2026-03-17 13:38:54 -05:00
Chris Tateandctate 1cd90078b4 fix: prevent system package removal during Ubuntu dependency install (#884)
* fix: prevent system package removal during Ubuntu dependency install

This PR fixes a critical issue where installing agent-browser dependencies on Ubuntu 24.04+ could trigger removal of hundreds of system packages due to library naming conflicts during the 64-bit time_t transition.

**Problem:**
On Ubuntu 24.04+, many core libraries were renamed with a "t64" suffix as part of the 64-bit time_t transition. The installer was trying to install old library names, causing apt to propose removing 400+ essential system packages to resolve conflicts.

**Changes:**
- Added comprehensive t64 variant detection for all apt dependencies
- Implemented pre-install simulation check to detect potential package removals
- Added safety abort mechanism when removals are detected (>0 packages)
- Enhanced error messaging to explain the issue and provide manual installation guidance
- Improved logging and user feedback during the installation process

**Implementation Details:**
- Uses `apt-get install --simulate` to safely check for conflicts before proceeding
- Checks for t64 variants first, falls back to original names if t64 versions don't exist
- Provides detailed output showing which packages would be removed
- Maintains compatibility with older Ubuntu versions that don't have t64 packages

Fixes #881

* refactor: extract duplicate install status handling into helper

---------

Co-authored-by: ctate <366502+ctate@users.noreply.github.com>
2026-03-17 11:32:07 -05:00
Chris Tateandctate 60f3afcf61 Add iframe support for CLI interactions and snapshots (#869)
* Add iframe support for CLI interactions and snapshots

This PR adds comprehensive iframe support to the agent browser CLI, allowing users to interact with elements inside iframes seamlessly.

## Problem
Users couldn't interact with elements inside iframes via the command line. The existing `frame` command was non-functional as it set `active_frame_id` but no other code read this value.

## Changes Made

### Enhanced Frame Context Tracking
- Added `frame_id` field to `RefEntry` to track which frame each element reference belongs to
- Updated `RefMap::add` and related methods to accept and store frame context
- Modified element resolution functions to use frame context from ref entries

### Improved Frame Command
- Fixed the existing `frame` command to actually work by threading `active_frame_id` through snapshot operations
- Added support for iframe element references (e.g., `frame @e2`) in addition to CSS selectors
- Enhanced frame detection to work with both named frames and iframe elements

### Updated Snapshot Behavior
- Modified `take_snapshot` to accept optional frame context parameter
- Updated all snapshot call sites to pass appropriate frame context
- Maintained backward compatibility while enabling frame-scoped operations

### Element Resolution Updates
- Updated `resolve_element_center` and `resolve_element_object_id` to use frame context from ref entries
- Modified `find_node_id_by_role_name` to support frame-specific element lookup
- Ensured all interaction functions work correctly within iframe contexts

## Implementation Details
- Frame context is now properly propagated through the entire element interaction pipeline
- The `frame` command can accept both CSS selectors and element references
- All existing functionality remains intact while adding iframe capabilities
- Added `Iframe` to interactive roles for better element discovery

Fixes #863

* docs: add iframe support documentation

Document the new iframe capabilities across all documentation surfaces:
- Auto-inlining of iframe content in snapshots
- Direct interaction with iframe element refs
- frame command support for element refs (@e3)
- Scoped snapshots via frame switching

* fix: pass active frame context to diff snapshots and fix nameless iframe lookup

- handle_diff_snapshot now respects active_frame_id instead of always
  passing None, so diff snapshots work correctly inside iframes
- Nameless/id-less iframes now fall back to src URL (or null) instead of
  the literal string 'frame' which never matched any frame in the tree

* fix: resolve iframe frame ID via DOM.describeNode and reduce code duplication

- handle_frame: Use DOM.describeNode + contentDocument.frameId to resolve
  iframe frame IDs directly, fixing failures for nameless iframes that
  lack name/id/src attributes
- element.rs: Deduplicate add() by delegating to add_with_frame()
- snapshot.rs: Guard against out-of-bounds insert_str when iframe marker
  is on the last line without a trailing newline

---------

Co-authored-by: ctate <366502+ctate@users.noreply.github.com>
2026-03-17 10:51:17 -05:00
Chris Tateandctate f51e955d99 refactor: make --full/-f a command-level flag instead of global (#877)
* refactor: make --full/-f a command-level flag instead of global

Move --full/-f from global flags (parsed in flags.rs) to command-level
parsing in commands.rs, scoped to the three commands that actually use
it: `screenshot`, `diff screenshot`, and `diff url`.

This frees up `-f` for other commands (e.g. `--follow` on
`console`/`errors`, see #867) and better reflects that full-page
capture is not a global concern.

Changes:
- Remove `full` from Flags struct, Config struct, and global flag parsing
- Remove `--full`/`-f` from clean_args global boolean flags list
- Parse `--full`/`-f` inline in `screenshot` command handler
- Accept `-f` shorthand in `diff screenshot` and `diff url` (previously
  only `--full` was accepted at command level)
- Remove fallback from global `flags.full` in diff subcommands
- Update tests to pass --full as a command argument rather than a global flag

Fixes #876

* fix: remove stale AGENT_BROWSER_FULL env var from help and add -f shorthand tests

- Remove AGENT_BROWSER_FULL from help text in output.rs since the env
  var is no longer read after moving --full to command-level parsing
- Add test_screenshot_full_page_shorthand to verify screenshot -f works
- Add test_diff_screenshot_command_full_flag_shorthand to verify
  diff screenshot -f works

---------

Co-authored-by: ctate <366502+ctate@users.noreply.github.com>
2026-03-17 10:40:53 -05:00
Chris Tate 664789f5c6 fix: use DOM textContent as fallback name for cursor-interactive snapshot nodes (#859)
Generic elements (e.g. <div>) with cursor:pointer/onclick have empty ARIA
names because their text lives in StaticText children, which get filtered
in interactive mode. Fall back to the JS-collected textContent so the text
appears on the rendered tree line.

Fixes e2e_snapshot_cursor_many_elements CI failure from #855.
2026-03-16 18:26:22 -05:00
Chris Tate c0d4cf6a93 chore: add patch changeset for release (#858) 2026-03-16 17:37:08 -05:00
Chris Tate eda956b754 chore: add patch changeset for release (#849) 2026-03-16 00:22:08 -05:00
Chris Tate d866ee2022 Fix network idle detection for cached pages by observing 500ms idle period (#847)
The `wait --load networkidle` command was incorrectly returning immediately when pages were served from cache, causing subsequent commands to fail. This happened because the network idle logic would return instantly when no network requests were pending, without observing any idle period.

## Changes Made

- Extract network idle polling logic into a separate `poll_network_idle` function for better testability
- Fix the timeout handling to start a 500ms idle timer when no requests are pending, instead of returning immediately
- Add comprehensive unit tests covering the regression case and normal network request flows
- Ensure the function always observes at least 500ms of network inactivity before resolving, even for cached pages

## Key Fix

The critical change is in the timeout branch: when no CDP events arrive within 600ms, we now start the idle timer if no requests are pending, rather than returning `Ok(())` immediately. This prevents false-positive idle detection for pages that load entirely from cache.

Fixes #846
2026-03-15 23:11:41 -05:00
Chris Tate 5fa239676b chore: add patch changeset for release (#844) 2026-03-15 20:48:08 -05:00
Chris Tateandctate 285eab46df fix: resolve snapshot -C and screenshot --annotate hang over WSS (#842)
* fix: resolve snapshot -C and screenshot --annotate hang over WSS (#841)

Root cause: sequential CDP round-trips per element in
find_cursor_interactive_elements() and collect_annotations() caused
timeouts over high-latency WSS connections (~200ms × 200+ elements
exceeds the 30s CDP timeout).

Fix:
- snapshot -C: Replace per-element CDP calls with a single JS eval
  that detects cursor:pointer/onclick/tabindex elements in-browser,
  then batch-resolve via DOM.querySelectorAll + concurrent
  DOM.describeNode calls using join_all
- screenshot --annotate: Replace sequential DOM.resolveNode +
  getRect calls with concurrent join_all, matching v0.19.0's
  Promise.all() pattern

Behavioral parity with v0.19.0 (Node.js/Playwright):
- cursor:pointer detection via getComputedStyle
- Inherited cursor:pointer dedup (skip children of pointer parents)
- interactiveTags and interactive ARIA roles exclusion
- Role differentiation: clickable vs focusable
- Text dedup against ARIA tree ref names and quoted strings
- Edge case: -i -C shows cursor elements even when ARIA tree is empty

Tests:
- 5 unit tests for build_dedup_set() helper
- 3 e2e regression tests: cursor-interactive detection, annotation
  scaling to 50 elements, cursor scaling to 100 elements

* fix: add hidden/aria-hidden filtering, contentEditable support, and cleanup robustness

- Restore hidden/aria-hidden element filtering in cursor-interactive JS
  (was present in old code, dropped during rewrite)
- Add contentEditable detection with 'editable' role and hint
- Replace fire-and-forget cleanup with warning on failure
- Simplify build_dedup_set to use ref_map only (eliminates fragile
  tree-text quote parsing; ref_map already has all ref-bearing names)

---------

Co-authored-by: ctate <366502+ctate@users.noreply.github.com>
2026-03-15 20:41:30 -05:00
Chris Tate 4b5fc78f71 chore: add patch changeset for release (#838) 2026-03-15 14:05:01 -05:00
Chris Tateandctate c092ffd82b fix: use correct VK codes for punctuation in type command (#836)
* fix: use correct Windows virtual-key codes for punctuation in type command

The `type` command was dropping punctuation characters like `.`, `'`, and
`#` because `char_to_key_info()` used raw ASCII codes as the
`windowsVirtualKeyCode` in CDP `Input.dispatchKeyEvent` calls. For
punctuation the ASCII value collides with unrelated VK codes — most
critically '.' (ASCII 46) equals VK_DELETE (0x2E), causing Chrome to
interpret periods as Delete key presses.

Changes:
- Add `punctuation_key_info()` with correct VK_OEM_* codes matching
  Playwright's USKeyboardLayout (e.g. Period=190, Slash=191, Semicolon=186)
- Fall back to `Input.insertText` for characters without a US keyboard
  mapping (emoji, CJK, etc.), matching Playwright's `keyboard.type()`
- Update e2e test to use `type` instead of `fill` workaround for email
- Add unit tests verifying VK code parity with Playwright's layout

Fixes #833

* style: fix rustfmt formatting for InsertTextParams

---------

Co-authored-by: ctate <366502+ctate@users.noreply.github.com>
2026-03-15 13:57:15 -05:00
Chris Tateandctate 8ac7fe916e fix: restore Playwright-parity check/uncheck for Material Design controls (#837)
The v0.20.0 migration from Playwright to the native Rust daemon introduced
two regressions in checkbox/radio handling:

1. `is_element_checked` only read `this.checked`, which is undefined on
   non-input elements. Material Design and ARIA controls use wrapper divs
   with `role="checkbox"` and `aria-checked`, or hide the native input
   off-screen inside a label. The function now mirrors Playwright's
   `getChecked()` with follow-label retargeting: native `.checked`,
   `aria-checked` for ARIA roles, `label.control` traversal, and nested
   input lookup.

2. `check`/`uncheck` accepted the coordinate-based CDP click result
   without verifying the state actually changed. When the AX tree's
   `backendDOMNodeId` points to a hidden off-screen input (common in
   Material Design), `Input.dispatchMouseEvent` hits nothing. The actions
   now re-check state after clicking and fall back to a JS `.click()` on
   the resolved input — matching Playwright's `_setChecked` verify step.

Adds e2e regression test covering Material Design (hidden input + ripple
overlay), ARIA-only, and native checkbox patterns.

Co-authored-by: ctate <366502+ctate@users.noreply.github.com>
2026-03-15 13:38:39 -05:00