Skip to content
Open
Show file tree
Hide file tree
Changes from 47 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
9467a34
feat(firefox): add WebDriver BiDi and DevTools protocol clients
lahfir Oct 21, 2026
3a4d201
fix(firefox): time out DevTools requests and keep replies aligned
lahfir Oct 21, 2026
80264c0
feat(firefox): find, validate and launch an owned Firefox
lahfir Oct 21, 2026
3b9c859
feat(firefox): launch Firefox sessions and route commands to them
lahfir Oct 21, 2026
d608135
fix(firefox): keep the DevTools socket path short
lahfir Oct 21, 2026
6d504f9
feat(firefox): page, tab, storage and capture commands
lahfir Oct 21, 2026
82fb8d4
test(firefox): add Firefox e2e harness and CI job
lahfir Oct 21, 2026
ed59e74
fix(firefox): engine switching, recovery, autosave and handshake bounds
lahfir Oct 21, 2026
e5c3051
fix(firefox): keep the BiDi connection on large full-page screenshots
lahfir Oct 22, 2026
4ea9acf
fix(firefox): auto-accept alert and beforeunload like Chrome
lahfir Oct 22, 2026
5030e4e
fix(firefox): default cookie paths like Chrome
lahfir Oct 23, 2026
413fc20
fix(state): write state files owner-only
lahfir Oct 23, 2026
dbf08d7
fix(firefox): restore saved storage in an isolated realm
lahfir Oct 23, 2026
6439d04
fix(firefox): skip storage origins that redirect and keep restoring
lahfir Oct 26, 2026
37e7d8d
fix(firefox): box the Firefox dispatch future so execute_command stay…
lahfir Oct 26, 2026
d2c9bef
fix(daemon): run confirm outside the dispatch match so it never nests
lahfir Oct 26, 2026
9f5cf9a
fix(firefox): validate restores and scope cookie reads like Chrome
lahfir Oct 27, 2026
ed6991f
test(daemon): clear AGENT_BROWSER_CDP in the unknown-command test
lahfir Oct 27, 2026
a3a943b
fix(firefox): launch, idle and autosave lifecycle fixes
lahfir Oct 27, 2026
3876137
chore: refresh MCP tool catalog snapshot
lahfir Oct 30, 2026
2d0c104
fix(firefox): preserve saved storage when reads are blocked
lahfir Oct 30, 2026
4233f25
fix(firefox): read the active page through BiDi
lahfir Nov 5, 2026
4d34776
Merge main into Firefox launch
lahfir Nov 6, 2026
479692a
test: preserve Windows runtime variables in MCP CLI check
lahfir Nov 6, 2026
c0bfa85
fix(firefox): preserve saved state and handle dialogs
lahfir Nov 6, 2026
075f3aa
fix: align browser startup and docs checks with current behavior
lahfir Nov 6, 2026
39fbe9d
fix: preserve paused target state during network setup
lahfir Nov 6, 2026
3995238
fix: align regression tests with integrated browser code
lahfir Nov 6, 2026
4f67a9e
Merge remote-tracking branch 'origin/main' into HEAD
lahfir Nov 6, 2026
d520d0b
fix: correct docs fixtures and bound popup regression waits
lahfir Nov 6, 2026
ee46cfa
fix(firefox): handle launch defaults and bound tab operations
lahfir Nov 6, 2026
ee2b154
fix(firefox): verify restore recovery and document capture limits
lahfir Nov 6, 2026
46bc159
merge: update Firefox launch against main
lahfir Nov 6, 2026
d3dc169
Merge remote-tracking branch 'origin/main' into HEAD
lahfir Nov 6, 2026
3f81c51
Merge remote-tracking branch 'origin/main' into HEAD
lahfir Nov 6, 2026
697b518
fix(firefox): run all Firefox e2es and reject unsupported headers
lahfir Nov 6, 2026
81a637d
fix(firefox): reset inherited engine when switching connections
lahfir Nov 6, 2026
7f9c7ef
fix(firefox): apply live dialog settings and count open tabs
lahfir Nov 7, 2026
3cfd68d
Run native browser end-to-end tests on pull requests
lahfir Nov 7, 2026
bb899ae
Run only Firefox browser tests on pull requests
lahfir Nov 7, 2026
ddb9ad8
Remove timed-out Firefox DevTools requests from the actor queue
lahfir Nov 7, 2026
a7a337d
Test RDP recovery after a request timeout
lahfir Nov 7, 2026
7116f6b
Remove unused DevTools setup from owned Firefox launch
lahfir Nov 7, 2026
3d9c589
Match RDP replies by command type after timeouts
lahfir Nov 7, 2026
f29c297
Fail closed after a Firefox DevTools timeout
lahfir Nov 7, 2026
711b057
Keep standard RDP replies after fail-closed timeouts
lahfir Nov 7, 2026
a31be91
Format fail-closed RDP timeout assertions
lahfir Nov 7, 2026
014a4f3
fix(firefox): drop tabs closed outside the daemon
lahfir Nov 8, 2026
f135636
fix(firefox): tab new races prompts and launch stops opening the debu…
lahfir Nov 8, 2026
0274da5
refactor(firefox): one owner each for navigation, tab lifecycle, prom…
lahfir Nov 8, 2026
9b5b378
fix(firefox): answer the prompt in the tab that opened it
lahfir Nov 8, 2026
71259cd
test(firefox): pin the prompt target in the dialog test
lahfir Nov 8, 2026
a13151c
fix(firefox): give window new its own cookie jar
lahfir Nov 8, 2026
63f2af8
fix(firefox): keep cookies and new tabs inside the window's user context
lahfir Nov 8, 2026
ab055e9
Merge remote-tracking branch 'origin/main' into HEAD
lahfir Nov 9, 2026
bee6a5c
fix(firefox): keep new tabs and restored cookies in the window's jar
lahfir Nov 9, 2026
7c363b0
Merge origin/main into laf/bidi-engine-launch
lahfir Nov 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,6 @@ jobs:

native-e2e:
name: Native E2E Tests
if: github.event_name != 'pull_request'
runs-on: ubuntu-latest
needs: rust
steps:
Expand All @@ -226,21 +225,34 @@ jobs:
workspaces: cli

- name: Install Chrome
if: github.event_name != 'pull_request'
run: |
cargo run --manifest-path cli/Cargo.toml -- install --with-deps

- name: Install ffmpeg
run: sudo apt-get update && sudo apt-get install -y ffmpeg

- name: Install Obscura
if: github.event_name != 'pull_request'
run: |
mkdir -p "$RUNNER_TEMP/obscura"
curl -fsSL https://github.com/h4ckf0r0day/obscura/releases/download/v0.2.4/obscura-x86_64-linux.tar.gz \
| tar xz -C "$RUNNER_TEMP/obscura"
echo "OBSCURA_BIN=$RUNNER_TEMP/obscura/obscura" >> "$GITHUB_ENV"

- name: Install Firefox
run: |
mkdir -p "$RUNNER_TEMP/firefox"
curl -fsSL "https://download.mozilla.org/?product=firefox-latest-ssl&os=linux64&lang=en-US" \
| tar xJ -C "$RUNNER_TEMP/firefox"
echo "FIREFOX_BIN=$RUNNER_TEMP/firefox/firefox/firefox" >> "$GITHUB_ENV"

- name: Run e2e tests
run: cargo test --profile ci --manifest-path cli/Cargo.toml e2e -- --ignored --test-threads=1
if: github.event_name != 'pull_request'
run: cargo test --profile ci --manifest-path cli/Cargo.toml e2e -- --ignored --test-threads=1 --skip e2e_firefox

- name: Run Firefox e2e tests
Comment thread
lahfir marked this conversation as resolved.
Resolved
run: cargo test --profile ci --manifest-path cli/Cargo.toml e2e_firefox -- --ignored --test-threads=1

windows-integration:
name: Windows Integration Test
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ The CLI sends each command to a background daemon that starts on the first comma

Chrome for Testing is the default browser. You can also attach to a running Chrome with `--cdp` or `--auto-connect` ([CDP mode](https://agent-browser.dev/cdp-mode)), select the [Lightpanda](https://agent-browser.dev/engines/lightpanda) or [Obscura](https://agent-browser.dev/engines/obscura) engine with `--engine`, drive Mobile Safari in the [iOS Simulator](https://agent-browser.dev/ios), or use a cloud browser provider such as [Browserbase](https://agent-browser.dev/providers/browserbase), [Browserless](https://agent-browser.dev/providers/browserless), [Browser Use](https://agent-browser.dev/providers/browser-use), [Kernel](https://agent-browser.dev/providers/kernel), or [AgentCore](https://agent-browser.dev/providers/agentcore).

Select Firefox with `--engine firefox`.

## For AI agents

The CLI ships skills that match its version:
Expand Down
18 changes: 18 additions & 0 deletions cli/src/doctor/chrome.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,24 @@ pub(super) fn check(checks: &mut Vec<Check>) {
.with_fix("install lightpanda or unset AGENT_BROWSER_ENGINE"),
);
}
} else if engine == "firefox" {
match crate::native::firefox::process::find_firefox(None) {
Ok(path) => {
let version = query_chrome_version(&path)
.map(|v| format!("{v} at "))
.unwrap_or_default();
checks.push(Check::new(
"chrome.engine_firefox",
category,
Status::Pass,
format!("{}{}", version, path.display()),
));
}
Err(e) => checks.push(
Check::new("chrome.engine_firefox", category, Status::Fail, e)
.with_fix("install Firefox 150 or newer or unset AGENT_BROWSER_ENGINE"),
),
}
} else if engine == "obscura" {
// Same lookup as launch, so doctor and launch agree on where the
// binary is.
Expand Down
8 changes: 4 additions & 4 deletions cli/src/mcp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1399,7 +1399,7 @@ fn parity_tools() -> Vec<Value> {
tool(
TOOL_COOKIES_GET,
"Cookies get",
"Get cookies.",
"Get cookies. When URLs are omitted, Firefox filters to the active page and its frames.",
json!({}),
&[],
),
Expand Down Expand Up @@ -1683,7 +1683,7 @@ fn parity_tools() -> Vec<Value> {
tool(
TOOL_STATE_SAVE,
"State save",
"Save cookies and storage state.",
"Save cookies and storage state, overwriting the target file.",
json!({ "path": { "type": "string" } }),
&["path"],
),
Expand Down Expand Up @@ -2163,7 +2163,7 @@ fn shared_property(key: &str) -> Value {
}),
"restore" => json!({
"oneOf": [{ "type": "boolean" }, { "type": "string" }],
"description": "Restore and auto-save browser state. true keys it by session; a string is an explicit key."
"description": "Restore and auto-save browser state. true uses the current session as the key; a string uses that explicit key. Firefox auto-save includes active iframe origins and preserves saved origins it cannot read."
}),
"allowedDomains" => json!({
"type": "array",
Expand Down Expand Up @@ -2194,7 +2194,7 @@ fn shared_property(key: &str) -> Value {
}
}

const SHARED_ARGUMENTS_INSTRUCTIONS: &str = "Every tool accepts these optional arguments even when its schema does not list them: session (isolated browser session name), restore (true or a key; restores and auto-saves browser state when the call launches the browser), allowedDomains (domain patterns that restrict browser and read traffic when the call launches the browser; also disables RTCPeerConnection in Chromium sessions, and is rejected when the server environment or config already sets allowed domains), capture (set to flow to record the session so flow steps and flow save can turn it into a reusable flow, when the call launches the browser), extraArgs (extra CLI arguments such as --engine; global flags that change network, file, policy, or host access, such as --allowed-domains, --executable-path, --args, and --config, are rejected, so set those in the MCP server environment or config), timeoutMs (positive integer, maximum milliseconds to wait for the tool call), namespace (isolates daemon sockets and restore-state directories), restoreSave (auto, always, or never), restoreCheckUrl, restoreCheckText, and restoreCheckFn (conditions restored state must satisfy), caCert (path to a CA certificate or PEM bundle for the CLI's HTTPS requests and locally launched Chromium on Linux), useSystemCa (boolean, use the OS trust store for the CLI's HTTPS requests), clearCaCert (boolean, clear CA trust retained by the session; cannot be combined with caCert), idleTimeout (such as 30s, 5m, 1h, or milliseconds; defaults to 1h, 0 disables idle shutdown), and relaunch (boolean, relaunch the session's browser even when no setting changed).";
const SHARED_ARGUMENTS_INSTRUCTIONS: &str = "Every tool accepts these optional arguments even when its schema does not list them: session (isolated browser session name), restore (true or a key; restores and auto-saves browser state when the call launches the browser; Firefox auto-save includes active iframe origins and preserves saved origins it cannot read), allowedDomains (domain patterns that restrict browser and read traffic when the call launches the browser; also disables RTCPeerConnection in Chromium sessions, and is rejected when the server environment or config already sets allowed domains), capture (set to flow to record the session so flow steps and flow save can turn it into a reusable flow, when the call launches the browser), extraArgs (extra CLI arguments such as --engine; global flags that change network, file, policy, or host access, such as --allowed-domains, --executable-path, --args, and --config, are rejected, so set those in the MCP server environment or config), timeoutMs (positive integer, maximum milliseconds to wait for the tool call), namespace (isolates daemon sockets and restore-state directories), restoreSave (auto, always, or never), restoreCheckUrl, restoreCheckText, and restoreCheckFn (conditions restored state must satisfy), caCert (path to a CA certificate or PEM bundle for the CLI's HTTPS requests and locally launched Chromium on Linux), useSystemCa (boolean, use the OS trust store for the CLI's HTTPS requests), clearCaCert (boolean, clear CA trust retained by the session; cannot be combined with caCert), idleTimeout (such as 30s, 5m, 1h, or milliseconds; defaults to 1h, 0 disables idle shutdown), and relaunch (boolean, relaunch the session's browser even when no setting changed).";

/// Levenshtein distance, for suggesting a close argument name.
fn edit_distance(a: &str, b: &str) -> usize {
Expand Down
Loading
Loading