Release binaries / Build macOS ARM64 (push) Has been cancelled
Release binaries / Build macOS x64 (push) Has been cancelled
Release binaries / Build Linux ARM64 (push) Has been cancelled
Release binaries / Build Linux musl ARM64 (push) Has been cancelled
Release binaries / Build Linux musl x64 (push) Has been cancelled
Release binaries / Build Linux x64 (push) Has been cancelled
Release binaries / Build Windows x64 (push) Has been cancelled
Release binaries / Attach binaries to GitHub Release (push) Has been cancelled
New `chrome-use adopt <url-substring|targetId>`: drive a tab the user (or
another session) already has open, with ZERO new tabs. After group-scoped
isolation (#40) a session can't see foreign tabs, so adopt adds an explicit,
opt-in path:
- Relay (relay.rs): `ABRelay.getAllTargets` returns every attached target
UNSCOPED (ignores group scoping), so the agent can find a specific tab by URL
or targetId. +1 unit test.
- Daemon (browser.rs): `collect_all_targets` (unscoped, falls back to scoped on
older relays) + `adopt_existing_target` — matches by exact targetId or
case-insensitive URL substring, attaches it (the relay re-tags it into the
adopter's group, so isolation holds), pins it; never creates a tab. On no
match it errors AND lists the open tabs it can see, rather than launching.
discover_and_attach_targets honors AGENT_BROWSER_ADOPT at first connect, so no
about:blank is ever created.
- CLI (main.rs): `adopt` sets the env, forces a fresh daemon, and rewrites into
`connect <relay-url>` (like `extension connect`) so the daemon attaches to the
user's real Chrome before parse_command.
Extension (ab-connect 0.4.11): `reannounceAttachedTabs` now re-sends each tab's
url/title (it previously sent neither) so the relay's target list stays matchable
by URL after the MV3 service worker reconnects — otherwise reannounced tabs show
a blank url and `adopt <url>` can't find them. Repacked upload zip + crx.
Mechanism verified live (enumerated all 11 of the user's open tabs incl. the
target). 862 tests pass.
A mid-session 'chrome-use upgrade' (or a crashed worker) can leave per-session
daemons holding stale/cross-leaked tab handles, and the only fix was hunting
PIDs with pgrep/kill. Add a first-class command:
- 'daemon restart' kills every session daemon worker (SIGTERM→SIGKILL +
sidecar cleanup) but leaves the Chrome-launched __nm-host bridge alone, so
the extension relay stays up — the next command spins a fresh, clean daemon
against the same live Chrome. Closes no tabs.
- 'daemon status' lists running session daemons (pid + version) and relay state.
Wires connection::restart_all_daemons(), skips the command in the update-notify
nag, documents it in --help and the core skill. Unit tests cover the empty case
and a live-session kill (spawns a real child, asserts it's reaped + sidecars
cleaned). Issue #20.
After the agent-browser → chrome-use rename, the new binary used ~/.chrome-use
+ host com.leeguoo.chrome_use and couldn't find the relay that the still-old
native-messaging host wrote to ~/.agent-browser → it fell back to raw
--remote-debugging-port and re-popped 'Allow remote debugging?'.
- config_home()/config_dir_basename(): decide once per run — prefer the new
.chrome-use, but keep using an existing .agent-browser install if that's the
only one present; fresh installs get .chrome-use. get_socket_dir() routes
through it so sockets/state are consistent within a run.
- relay_url_path(): the relay-cdp-url is a cross-binary handoff (host writes,
CLI reads), so read from whichever brand dir actually has the file
(~/.chrome-use OR ~/.agent-browser).
Combined with keeping HOST_NAME=com.agent_browser.connect (b6febbe), the renamed
chrome-use binary now relays through the existing ab-connect 0.4.2 extension
with zero dialog. Verified live: chrome-use found ~/.agent-browser/relay-cdp-url
and listed the user's real tabs, no consent dialog.
Release binaries / Build macOS ARM64 (push) Has been cancelled
Release binaries / Build macOS x64 (push) Has been cancelled
Release binaries / Build Linux ARM64 (push) Has been cancelled
Release binaries / Build Linux musl ARM64 (push) Has been cancelled
Release binaries / Build Linux musl x64 (push) Has been cancelled
Release binaries / Build Linux x64 (push) Has been cancelled
Release binaries / Build Windows x64 (push) Has been cancelled
Release binaries / Attach binaries to GitHub Release (push) Has been cancelled
Standalone product rename across the whole repo (issue: project identity):
- Binary/package/repo/skill/docs: agent-browser[-stealth] → chrome-use
(single binary name `chrome-use`; old aliases agent-browser/abs dropped).
- Version: 0.27.0-fork.51 → 1.0.0 (drop the upstream-fork counter).
- Native-messaging host: com.agent_browser.connect → com.leeguoo.chrome_use
(CLI + ab-connect extension in lockstep — this is a breaking handshake change,
extension bumped 0.4.2 → 0.5.0, needs a Web Store republish).
- Config dir: ~/.agent-browser → ~/.chrome-use.
- README/zh: reframed from "stealth fork of agent-browser" to a standalone
product with a small `originally based on vercel-labs/agent-browser` credit.
- Kept AGENT_BROWSER_* env vars working (63 vars across the codebase; renaming
them would break every existing script/skill for no user-facing gain).
Build green, 802 unit tests pass, fmt + clippy clean. Upstream attribution to
vercel-labs/agent-browser preserved.
Field report #8: in extension-relay sessions, reads (eval/screenshot/network)
could silently run against whatever tab drifted into focus, with no signal,
and network capture was intermittently empty.
- #8.1: screenshot and `network requests` now print `screenshot @ <url>` /
`network @ <url>` to stderr (mirrors the existing `eval @ <url>`), and the
responses carry `origin`. A read against the wrong/drifted tab — and the
"0 captured" vs "wrong page" ambiguity — is now obvious.
- #8.3: `network requests --clear` now enables Network capture immediately
instead of lazily on the next read, so requests fired between `--clear` and
the following read are tracked (fixes the "No requests captured" on first
try, works on retry" race). Extracted enable_request_tracking helper.
- #8.2: the daemon version-mismatch restart notice now spells out that
in-memory context (active tab, refs, captured requests) is reset and tells
the user to re-open the target URL if the next read looks blank/wrong.
Verified on a launched browser: coordinate clicks land, screenshot/network
stamps appear, and a fetch after --clear is captured on the first read.
- eval now prints `eval @ <url>` to stderr (stdout stays the raw value) so an
agent can catch tab drift — e.g. a logged-in fetch that hit the wrong origin —
before trusting the result. Mitigates the issue #2/#3 P0 safety concern. (eval
already returned the origin; the default output just never surfaced it.)
- `type --focused <text>`: type into the currently-focused element with no
selector, for custom widgets that move focus to a hidden input (issue #2 P3).
- AGENT_BROWSER_HUMANIZE set to an unrecognized value now warns once (like the
--humanize flag) instead of being silently ignored (Hermes #3).
Root cause behind Hermes #1 (CLICK_MODE=dom "does nothing") and #2 (--humanize
"does nothing"): both are env vars the daemon reads, but the daemon's env is
frozen at spawn — set them on a command to an already-running daemon and they
were silently ignored. (Confirmed: setting CLICK_MODE=dom at daemon spawn made
dom_click fire; setting it later did not.)
Fix: the client forwards AGENT_BROWSER_CLICK_MODE / AGENT_BROWSER_HUMANIZE in the
command envelope (_clickMode/_humanize); execute_command applies them per command
— mirrors CLICK_MODE into the process env (interaction::click reads it fresh) and
sets the humanize session level. Each command is authoritative.
Verified on an already-running daemon: CLICK_MODE=dom now fires dom_click
(hits 0→1); --humanize human typing applies.
Cherry-picks upstream agent-browser #1396. Adds a configurable
--hide-scrollbars flag (AGENT_BROWSER_HIDE_SCROLLBARS env, hideScrollbars
config key, default true) that appends Chrome's --hide-scrollbars launch arg
for headless (non-extension) launches so native scrollbars aren't painted into
screenshots. Plumbed through flags.rs, connection.rs, main.rs, native/actions.rs
and native/cdp/chrome.rs; help text in output.rs + skill-data.
Fork adaptation:
- the arg lands in the headless && !has_extensions block, separate from the
stealth base args — no interaction with anti-detection.
- dropped upstream docs/, agent-browser.schema.json and README hunks (removed
or rewritten in this fork).
Verified: cargo check --tests passes.
Before: after `npm i -g` upgrade, the next agent-browser command would
detect daemon version mismatch, kill the old daemon, spawn a fresh one,
and connect to a brand-new about:blank tab. The user's previous
navigation state was silently lost — `get url` returned about:blank
even though the user's Chrome was still on the same page.
Now: before killing the old daemon, the CLI synchronously asks it for
its current URL via the existing socket. If non-empty and not
about:blank, it's persisted to a `.restore-url` sidecar in the socket
dir. After the new daemon spawns and auto-connects, it reads the
sidecar (read-and-delete), navigates the fresh tab to the saved URL,
and prints `⚠ Restored previous URL: <url>`.
Manual `agent-browser close` does NOT write the sidecar, so a clean
shutdown won't trigger surprise navigation. The sidecar is consumed on
read regardless of whether navigation succeeded, so a stale entry
can't haunt later auto-launches.
- Auto-connect is now ON by default (was opt-in via --auto-connect)
- Added --launch/--new flags to explicitly start a fresh browser
- CI environments (CI env var) automatically use --launch mode
- Friendly error message with platform-specific Chrome relaunch guide
- Mentions Chrome 144+ runtime CDP toggle (chrome://inspect)
- --cdp and --provider flags implicitly disable auto-connect
- AGENT_BROWSER_NO_AUTO_CONNECT=1 to disable, AGENT_BROWSER_FORCE_LAUNCH=1 to force
Track 3 of native-stealth migration.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* 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
* 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>
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>
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>
* 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>
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>
`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.
* 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>
* 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>
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>
* fix: Windows auto-connect profiling (#835)
Fix three interrelated bugs causing `--auto-connect` to fail on Windows,
plus a UX issue where auto-connect hijacked existing tabs:
1. Stale DevToolsActivePort — add TCP port liveness check before returning
M144+ WebSocket URL; remove stale files when port is dead.
2. Missing Windows error codes — add os error 10061 (WSAECONNREFUSED) and
10054 (WSAECONNRESET) to is_transient_error() so daemon startup races
are retried on Windows.
3. --auto-connect not propagated to daemon — add auto_connect to
DaemonOptions, set AGENT_BROWSER_AUTO_CONNECT env var via
apply_daemon_env(), and guard the headed launch block so it doesn't
send a second launch that overrides the auto-connect.
4. Auto-connect opens a fresh tab — after connecting to an existing
Chrome, create a new about:blank tab and bring it to front so
navigations don't hijack the user's existing tabs.
Made-with: Cursor
* fix: address review feedback — cargo fmt, shared helper, Windows tests
- Run cargo fmt on is_port_reachable() formatting
- Extract duplicated auto-connect-with-fresh-tab logic into
connect_auto_with_fresh_tab() helper used by both handle_launch()
and auto_launch()
- Add unit tests for Windows WSAECONNREFUSED (os error 10061) and
WSAECONNRESET (os error 10054) in is_transient_error()
---------
Co-authored-by: ctate <366502+ctate@users.noreply.github.com>
* fix: propagate --cdp flag to daemon via AGENT_BROWSER_CDP env var
The --cdp flag was not being passed to the daemon process as an environment
variable, causing auto-reconnection to fail. The daemon's auto_launch()
function checks for AGENT_BROWSER_CDP, but this was never set when spawning
the daemon.
This commit adds:
- cdp field to DaemonOptions struct
- AGENT_BROWSER_CDP env var setting in apply_daemon_env()
- flags.cdp propagation in main.rs
This ensures reliable CDP connection recovery when using --cdp with external
browsers like Lightpanda, Electron apps, or remote Chrome instances.
Fixes reconnection issues with --cdp flag after connection drops.
* chore: remove changeset
---------
Co-authored-by: Jake Shore <jakeshore@Jakes-Mac-mini.local>
Co-authored-by: ctate <366502+ctate@users.noreply.github.com>
* feat: add --idle-timeout CLI flag for daemon auto-shutdown
Add user-friendly --idle-timeout flag that converts time strings
to milliseconds. Supports formats like '10s', '3m', '1h', or raw ms.
This addresses a common need for ephemeral/CI environments where
daemon processes can be orphaned if not explicitly closed, leading
to resource consumption from zombie chrome-headless-shell processes.
Co-authored-by: Hermes (via claude-sonnet-4-20250520) <agent@hermes.ai>
* fix: address idle-timeout review feedback
* fix: normalize idle-timeout parsing
---------
Co-authored-by: Merlin <merlin@rbeckner.com>
Co-authored-by: Hermes (via claude-sonnet-4-20250520) <agent@hermes.ai>
The client (connection.rs) and native daemon (native/daemon.rs) used
different get_port_for_session() implementations on Windows:
- Client: i32, .chars(), djb2 — (hash << 5) - hash + c
- Daemon: i64, .bytes(), Java hashCode — hash * 31 + b
For session name "default", client computes port 50838 while the
daemon binds on 51174, causing a 5-second timeout and startup failure.
Fix: align native/daemon.rs to use the identical djb2 algorithm from
connection.rs (i32, chars, djb2), so both sides agree on the port.
Unix is unaffected (uses Unix domain sockets, no port hashing).
Tests: add port hash regression tests to all three implementations
(native/daemon.rs, connection.rs, daemon.ts) to prevent future drift.
Fixes#705
TypeScript CI has prettier --check but Rust CI only runs cargo test.
Add cargo fmt --check to catch formatting issues early, and fix the
8 pre-existing formatting violations on main.
When the daemon process crashes during startup (e.g., missing
Playwright), stderr was discarded via Stdio::null(), so users only
saw "Daemon failed to start (port: ...)" with no diagnostic info.
Now captures daemon stderr via Stdio::piped() and detects early process
exit with try_wait() during the startup polling loop. If the daemon
crashes, the actual error from stderr is shown to the user.
Also forwards --debug flag to the daemon process as AGENT_BROWSER_DEBUG
so debug logging works end-to-end.
Closes#56
* fix(windows): resolve daemon startup failures and Git Bash compatibility
Three root causes behind 27 open Windows issues:
1. Path::canonicalize() returns \\?\ prefixed paths on Windows that
Node.js cannot parse, preventing daemon startup. Strip the prefix
before passing to Node. (fixes#522, #390, #56, #25, #37, #89)
2. Git Bash/MSYS2 translates Unix-style paths and resolves node to
a shell wrapper script. Use node.exe explicitly and set
MSYS_NO_PATHCONV/MSYS2_ARG_CONV_EXCL to prevent argument mangling.
(fixes#148, #108, #171)
3. postinstall fixWindowsShims() hardcoded x64 arch and did not verify
the native binary exists before rewriting shims. Now detects arch
dynamically and validates the binary path. (fixes#262)
Also:
- Error messages now show TCP port on Windows instead of Unix socket path
- Windows CI expanded to test full daemon lifecycle (open, snapshot, close)
* fix(windows): strip \\?\ prefix in auth-cli path (fixes#579)
Same canonicalize() issue as the daemon spawn path, but in
run_auth_cli() which passes the script path to Node.js.
Per POSIX, kill(pid, 0) returns EPERM when the process exists but the
caller lacks permission to signal it, and ESRCH when it does not exist.
The daemon liveness checks in both the Rust CLI and TypeScript daemon
treated any kill failure as "not running", which is incorrect when
running inside a macOS sandbox that restricts signal delivery to
(target self). This caused the CLI to delete the real daemon's socket
and PID files, then spawn a duplicate daemon.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* add security hardening features
- Add authentication vault (`auth save/login/list/show/delete`) so credentials are stored locally and never exposed to the LLM (fixes Snyk W007)
- Add `--content-boundaries` flag to wrap page-sourced output in structural markers, helping LLMs distinguish tool output from untrusted page content (fixes Snyk W011)
- Add `--allowed-domains` flag to restrict browser navigation to trusted domains
- Add `--action-policy` for static allow/deny gating of action categories, with opt-in `--confirm-actions`/`--confirm-interactive` for orchestrator or human-in-the-loop confirmation
- Add `--max-output` flag to truncate large page outputs, preventing context flooding
- New docs page at /security, updated README, SKILL.md, CLI help text, and templates
* fixes
* fixes
* fixes
* fixes
* fixes
* fixes
* fixes
* docs
* feat: add --download-path option
Adds a `--download-path` flag (and `AGENT_BROWSER_DOWNLOAD_PATH` env / `downloadPath` config key) to set a default download directory for browser downloads.
Without this, Playwright stores downloads in a temp directory that is deleted when the browser closes. The new option passes through to Playwright's `downloadsPath` on `launch()` and `launchPersistentContext()`.
Fixes#507
* improvements
* fixes
* fixes
Rebased and fixed implementation of PR #184 features on current main:
Session persistence:
- --session-name flag and AGENT_BROWSER_SESSION_NAME env var auto-save/restore
cookies and localStorage across browser restarts
- State files stored in ~/.agent-browser/sessions/ with owner-only permissions
- AES-256-GCM encryption via AGENT_BROWSER_ENCRYPTION_KEY env var
- Auto-expiration of old state files (AGENT_BROWSER_STATE_EXPIRE_DAYS, default 30)
State management commands:
- state list: list saved state files with metadata
- state show <file>: display state summary (cookies, origins, domains)
- state rename <old> <new>: rename state files
- state clear [name] [--all]: clear saved states
- state clean --older-than <days>: delete expired states
New --new-tab flag for click command:
- Opens link href in a new tab instead of navigating the current tab
Security hardening:
- Session name validation prevents path traversal (CLI + daemon)
- safeHeaderMerge prevents prototype pollution in header merging
- WebSocket stream server binds to 127.0.0.1 only
- State files written with 0o600 permissions
Fixes applied over the original PR:
- Use color.rs module instead of hardcoded ANSI escape codes
- Align CLI output field names with daemon response format
- Add CLI-level --session-name validation (not just daemon-side)
- Avoid adding "DOM" to tsconfig.json lib (use proper typing in evaluate)
- Keep version at 0.9.3 (matches current main)
- Centralize session name validation in daemon.ts helper
- Update all documentation (README, SKILL.md, docs site, --help output)
Co-authored-by: Chris Tate <chris@ctate.dev>
* cargo fmt
* fix: remove redundant `use libc` import (clippy::single_component_path_imports)
* fix: use `.first()` instead of `.get(0)` (clippy::get_first)
* fix: use `.copied()` instead of `.map(|s| *s)` (clippy::map_clone)
* fix: allow too_many_arguments on ensure_daemon (clippy::too_many_arguments)
* fix: use `then_some` instead of `then` with closure (clippy::unnecessary_lazy_evaluations)
* fix: use pattern match instead of redundant guard (clippy::redundant_guards)
* fix: use pattern match instead of redundant guard in commands.rs (clippy::redundant_guards)
* fix: use `contains()` instead of `iter().any()` for simple equality (clippy::manual_contains)
* Add changeset
* feat: add --allow-file-access flag for file:// URL support
Adds the ability to open and interact with local files using file:// URLs.
This enables use cases like viewing local PDFs, testing local HTML files,
and allowing JavaScript to access other local files via XHR.
The flag adds Chromium's --allow-file-access-from-files and --allow-file-access
launch arguments. Only supported in Chromium browsers.
Fixes#345
* fix: add cli_allow_file_access tracking to prevent spurious warning
When --allow-file-access is set via AGENT_BROWSER_ALLOW_FILE_ACCESS env var
(not CLI), don't warn about the flag being ignored when daemon is already running.
* fix: add retry logic for transient socket errors
Fixes race condition when rapidly closing and opening browser sessions.
The daemon has a 100ms shutdown delay, which caused the CLI to detect
stale daemons as "running" and fail with EAGAIN errors.
Changes:
- Add retry logic (5 attempts, exponential backoff) for transient errors
including EAGAIN, EOF, connection reset, and connection refused
- Add 150ms verification delay in ensure_daemon to detect shutting-down daemons
- Add cleanup_stale_files to remove leftover socket/PID files before starting
a new daemon
Tested with 20 rapid close/open cycles and 100+ parallel commands.
* test: add unit tests for transient error detection
Extracts is_transient_error() function and adds 14 unit tests covering:
- EAGAIN errors (macOS os error 35, Linux os error 11)
- WouldBlock and Resource temporarily unavailable
- EOF and empty JSON response errors
- Connection reset (macOS os error 54, Linux os error 104)
- Broken pipe errors
- Socket not found (os error 2)
- Connection refused (macOS os error 61, Linux os error 111)
- Non-transient errors (verifies they are NOT retried)
* feat: add support for ignoring HTTPS certificate errors
* fix: update warning message for already running daemon to include ignore HTTPS errors option
* docs: add documentation for --ignore-https-errors option in README and SKILL.md
* feat: initialize ignore_https_errors flag in command context
* fix: change launch_cmd to mutable for cdp value handling
* fix: use ~/.agent-browser for socket files instead of TMPDIR
This fixes issue #163 where different TMPDIR values (common with
tmux/screen/VSCode/IntelliJ) caused the CLI and daemon to use
different socket paths.
Socket directory priority:
1. AGENT_BROWSER_SOCKET_DIR (explicit override)
2. $XDG_RUNTIME_DIR/agent-browser (Linux standard)
3. ~/.agent-browser (fallback, like Docker Desktop)
Both CLI (Rust) and daemon (Node.js) now use the same logic.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: session list now looks in correct socket directory
- Make get_socket_dir() public in connection.rs
- Update session list to use get_socket_dir() instead of temp_dir()
- Update pid file pattern from agent-browser-{session}.pid to {session}.pid
- Add tmpdir fallback to daemon.ts when homedir is unavailable
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* test: add unit tests for socket directory resolution
Add comprehensive tests for get_socket_dir/getSocketDir to verify:
- AGENT_BROWSER_SOCKET_DIR takes priority
- Empty strings are ignored (fixes Rust/TypeScript consistency)
- XDG_RUNTIME_DIR fallback works correctly
- Home directory fallback when env vars unset
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>