* Make KERNEL_API_KEY optional for external credential injection
When running inside environments with external credential injection
(e.g. Vercel Sandbox credentials brokering), the KERNEL_API_KEY env
var can be omitted. The network layer injects the Authorization header
on outbound requests to api.onkernel.com, so the API key never needs
to exist inside the sandbox.
If KERNEL_API_KEY is set, it's used as before. If not, requests are
sent without an Authorization header, allowing external injection.
Without either, the Kernel API returns 401.
Made-with: Cursor
* Make KERNEL_API_KEY optional in native Rust daemon too
Applies the same change to the native Rust connect_kernel() function
so both the Node.js and native code paths support external credential
injection.
Made-with: Cursor
* Address review feedback: fix type errors, cargo fmt, always send cleanup DELETE
- Fix kernelApiKey assignment: use ?? null for undefined -> null
- Fix closeKernelSession signature: accept string | undefined
- Always send DELETE on cleanup even without local API key (external
injection covers it)
- Run cargo fmt on Rust code
Made-with: Cursor
* next.js guide
* better
* shadcn
* fixes
* fix: correct screenshot test assertion to check path instead of base64
The daemon returns { path: savePath } for screenshot commands, not base64.
* fix: cross-platform Chrome detection and gitignore hardening
- Replace hardcoded macOS Chrome path with findLocalChrome() that
searches common paths on macOS, Linux, and WSL, with a clear error
message when no Chrome is found.
- Add .env and .env*.local to .gitignore to prevent accidental
secret commits.
* fix: correct Vercel deploy button repo URL to vercel-labs/agent-browser
* clean up
* demo
* next page
---------
Co-authored-by: ctate <366502+ctate@users.noreply.github.com>
Add `cargo clippy -- -D warnings` step to the Rust CI job so that
clippy warnings fail the build. Also fix the one new lint
(`unnecessary_map_or`) introduced in the current stable clippy.
Fixes#672
Co-authored-by: hyunjinee <leehj0110@kakao.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Fix clippy warnings across CLI codebase
Fixes#653
* Fix remaining items_after_test_module clippy warnings
Move functions defined after `mod tests` blocks to before the test
modules in recording.rs and webdriver/client.rs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: ctate <366502+ctate@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Fix Chrome extensions not loading by forcing headed mode when extensions present
Fixes#640
* Restore wait_or_kill() and add tests for headless+extensions logic
Restore the ChromeProcess::wait_or_kill() method that was accidentally
removed. It is still referenced by BrowserProcess in browser.rs and is
needed for graceful shutdown / cookie persistence (PR #650).
Add unit tests verifying --headless=new is omitted when extensions are
present.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix window-size leak in headed+extensions mode and remove unused channel option
- Skip --window-size=1280,720 when extensions force headed mode (native)
- Remove unexplained channel: 'chromium' from extensions launch path (TS)
- Add window-size assertion to existing extension test
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: ctate <366502+ctate@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Fixes#566: Clarify snapshot vs snapshot -i usage
- Add guidance that snapshot -i is for clickable/fillable elements
- Add guidance that snapshot (no flag) is for reading page content
Fixes#565: Add reproducibility verification before collecting evidence
- Add guidance to verify issues are reproducible before recording video
- Prevent wasting turns on false positives
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.
reqwest_get_string() was hand-rolling HTTP/1.1 over raw TCP despite reqwest
being an existing dependency. The hand-rolled implementation had two bugs:
1. URL path parsing: url.find('/') matched the first '/' in 'http://',
producing path '//127.0.0.1:9222/json/version' instead of '/json/version'
2. read_to_end() hangs: Chrome's DevTools HTTP server ignores Connection: close
and keeps the socket open, so read_to_end() waits for EOF that never comes
This caused 'agent-browser --cdp <port>' to always timeout when AGENT_BROWSER_NATIVE=1.
Fix: replace 49 lines of broken TCP code with reqwest::get(), which was
already in Cargo.toml.
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: suppress spurious --native warning when set via env var
When AGENT_BROWSER_NATIVE=1 is set via environment variable, every
command after the first would warn:
⚠ --native ignored: daemon already running.
This is a false positive — the daemon was already spawned in native
mode and inherited the env var. The warning should only fire when
--native is explicitly passed on the CLI to an already-running daemon.
Add cli_native flag (consistent with existing cli_* pattern) to
distinguish CLI origin from env var origin.
* fix: add flag to test cfg
* fix: cli_native should track flag presence, not value
--native false on CLI should still warn when daemon is already
running, since the user is explicitly trying to change the mode.
Fixes#600
Three improvements to `--native` Chrome launching:
- `find_chrome()` now falls back to Playwright's browser cache (`~/.cache/ms-playwright/`) when no system Chrome is found
- Auto-detect containers/VMs (root, Docker, Podman, cgroups) and inject `--no-sandbox`
- Chrome stderr is now captured and included in launch error messages, with a hint when sandbox errors are detected
The native PR introduced tokio-tungstenite and reqwest with native-tls,
which depends on openssl-sys (C library). This breaks the release
workflow's cargo-zigbuild cross-compilation on Linux because zig's C
compiler can't find the system OpenSSL headers.
Switch to rustls (pure Rust TLS) which has zero C dependencies and
cross-compiles trivially. Also shrinks the dependency tree.
* 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.
Bug 1: `cookies clear` printed 'Request log cleared' instead of 'Cookies cleared'
because the output handler matched the generic `{ cleared: true }` response shape
without checking the action context. Now uses the `action` parameter to distinguish
`cookies_clear` from `requests --clear`.
Bug 2: `tab close` printed 'Browser closed' instead of 'Tab closed' because the
output handler matched the generic `{ closed: ... }` response shape without checking
the action context. Now uses the `action` parameter to distinguish `tab_close` from
`close` (full browser close).
Closes#556
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>
* fix: resolve unnamed element refs matching multiple elements (#500)
When a page has one unnamed button among several named buttons,
clicking its ref fails with "matched N elements" because the
locator `getByRole('button')` matches all buttons on the page.
Normalize unnamed interactive elements to `name: ""` so the
selector becomes `getByRole('button', { name: "", exact: true })`
which matches only buttons with empty accessible names.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: remove dead code branch in buildSelector
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: make RefMap.name required string, remove dead code branches
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: hyunjinee <leehj0110@kakao.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>