diff --git a/README.md b/README.md index 19b10f3..9005e39 100644 --- a/README.md +++ b/README.md @@ -307,6 +307,8 @@ agent-browser dialog dismiss # Dismiss agent-browser dialog status # Check if a dialog is currently open ``` +By default, `alert` and `beforeunload` dialogs are automatically accepted so they never block the agent. `confirm` and `prompt` dialogs still require explicit handling. Use `--no-auto-dialog` (or `AGENT_BROWSER_NO_AUTO_DIALOG=1`) to disable automatic handling. + When a JavaScript dialog is pending, all command responses include a `warning` field with the dialog type and message. ### Diff @@ -594,6 +596,7 @@ This is useful for multimodal AI models that can reason about visual layout, unl | `--confirm-actions ` | Action categories requiring confirmation (or `AGENT_BROWSER_CONFIRM_ACTIONS` env) | | `--confirm-interactive` | Interactive confirmation prompts; auto-denies if stdin is not a TTY (or `AGENT_BROWSER_CONFIRM_INTERACTIVE` env) | | `--engine ` | Browser engine: `chrome` (default), `lightpanda` (or `AGENT_BROWSER_ENGINE` env) | +| `--no-auto-dialog` | Disable automatic dismissal of `alert`/`beforeunload` dialogs (or `AGENT_BROWSER_NO_AUTO_DIALOG` env) | | `--config ` | Use a custom config file (or `AGENT_BROWSER_CONFIG` env) | | `--debug` | Debug output | diff --git a/cli/src/commands.rs b/cli/src/commands.rs index a802c06..aa24ceb 100644 --- a/cli/src/commands.rs +++ b/cli/src/commands.rs @@ -2322,6 +2322,7 @@ mod tests { screenshot_quality: None, screenshot_format: None, idle_timeout: None, + no_auto_dialog: false, } } diff --git a/cli/src/connection.rs b/cli/src/connection.rs index f423164..d532943 100644 --- a/cli/src/connection.rs +++ b/cli/src/connection.rs @@ -219,6 +219,7 @@ pub struct DaemonOptions<'a> { pub auto_connect: bool, pub idle_timeout: Option<&'a str>, pub cdp: Option<&'a str>, + pub no_auto_dialog: bool, } fn apply_daemon_env(cmd: &mut Command, session: &str, opts: &DaemonOptions) { @@ -300,6 +301,9 @@ fn apply_daemon_env(cmd: &mut Command, session: &str, opts: &DaemonOptions) { if let Some(cdp) = opts.cdp { cmd.env("AGENT_BROWSER_CDP", cdp); } + if opts.no_auto_dialog { + cmd.env("AGENT_BROWSER_NO_AUTO_DIALOG", "1"); + } } pub fn ensure_daemon(session: &str, opts: &DaemonOptions) -> Result { diff --git a/cli/src/flags.rs b/cli/src/flags.rs index 501127a..806d4e8 100644 --- a/cli/src/flags.rs +++ b/cli/src/flags.rs @@ -87,6 +87,7 @@ pub struct Config { pub screenshot_quality: Option, pub screenshot_format: Option, pub idle_timeout: Option, + pub no_auto_dialog: Option, } impl Config { @@ -132,6 +133,7 @@ impl Config { screenshot_quality: other.screenshot_quality.or(self.screenshot_quality), screenshot_format: other.screenshot_format.or(self.screenshot_format), idle_timeout: other.idle_timeout.or(self.idle_timeout), + no_auto_dialog: other.no_auto_dialog.or(self.no_auto_dialog), } } } @@ -298,6 +300,7 @@ pub struct Flags { pub screenshot_quality: Option, pub screenshot_format: Option, pub idle_timeout: Option, // Canonical milliseconds string for AGENT_BROWSER_IDLE_TIMEOUT_MS + pub no_auto_dialog: bool, // Track which launch-time options were explicitly passed via CLI // (as opposed to being set only via environment variables) @@ -429,6 +432,8 @@ pub fn parse_flags(args: &[String]) -> Flags { "AGENT_BROWSER_IDLE_TIMEOUT_MS", ) .or(config.idle_timeout), + no_auto_dialog: env_var_is_truthy("AGENT_BROWSER_NO_AUTO_DIALOG") + || config.no_auto_dialog.unwrap_or(false), cli_executable_path: false, cli_extensions: false, cli_profile: false, @@ -703,6 +708,13 @@ pub fn parse_flags(args: &[String]) -> Flags { i += 1; } } + "--no-auto-dialog" => { + let (val, consumed) = parse_bool_arg(args, i); + flags.no_auto_dialog = val; + if consumed { + i += 1; + } + } "--config" => { // Already handled by load_config(); skip the value i += 1; @@ -729,6 +741,7 @@ pub fn clean_args(args: &[String]) -> Vec { "--annotate", "--content-boundaries", "--confirm-interactive", + "--no-auto-dialog", ]; // Global flags that always take a value (need to skip the next arg too) const GLOBAL_FLAGS_WITH_VALUE: &[&str] = &[ @@ -1375,4 +1388,27 @@ mod tests { let merged = user.merge(project); assert_eq!(merged.extensions, Some(vec!["/ext2".to_string()])); } + + #[test] + fn test_no_auto_dialog_flag() { + let flags = parse_flags(&args("open example.com --no-auto-dialog")); + assert!(flags.no_auto_dialog); + } + + #[test] + fn test_no_auto_dialog_default_false() { + let flags = parse_flags(&args("open example.com")); + assert!(!flags.no_auto_dialog); + } + + #[test] + fn test_clean_args_removes_no_auto_dialog() { + let input: Vec = vec![ + "open".to_string(), + "example.com".to_string(), + "--no-auto-dialog".to_string(), + ]; + let clean = clean_args(&input); + assert_eq!(clean, vec!["open", "example.com"]); + } } diff --git a/cli/src/main.rs b/cli/src/main.rs index e046b83..979f592 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -721,6 +721,7 @@ fn main() { auto_connect: flags.auto_connect, idle_timeout: flags.idle_timeout.as_deref(), cdp: flags.cdp.as_deref(), + no_auto_dialog: flags.no_auto_dialog, }; let daemon_result = match ensure_daemon(&flags.session, &daemon_opts) { diff --git a/cli/src/native/actions.rs b/cli/src/native/actions.rs index 781a6dc..7048fc3 100644 --- a/cli/src/native/actions.rs +++ b/cli/src/native/actions.rs @@ -205,9 +205,15 @@ pub struct DaemonState { /// handling domain filtering, route interception, and origin-scoped headers /// without deadlocking navigation/evaluate. fetch_handler_task: Option>, + /// Background task that auto-accepts `alert` and `beforeunload` dialogs + /// so they never block the agent. + dialog_handler_task: Option>, pub mouse_state: MouseState, /// Tracks the currently open JavaScript dialog (alert/confirm/prompt), if any. pub pending_dialog: Option, + /// When true, automatically dismiss `beforeunload` dialogs and accept `alert` + /// dialogs so they never block the agent. Enabled by default. + pub auto_dialog: bool, /// Shared slot for stream server to receive CDP client when browser launches. pub stream_client: Option>>>>, /// Stream server instance kept alive so the broadcast channel remains open. @@ -252,8 +258,13 @@ impl DaemonState { origin_headers: Arc::new(RwLock::new(HashMap::new())), proxy_credentials: Arc::new(RwLock::new(None)), fetch_handler_task: None, + dialog_handler_task: None, mouse_state: MouseState::default(), pending_dialog: None, + auto_dialog: !matches!( + env::var("AGENT_BROWSER_NO_AUTO_DIALOG").as_deref(), + Ok("1" | "true" | "yes") + ), stream_client: None, stream_server: None, engine: env::var("AGENT_BROWSER_ENGINE").unwrap_or_else(|_| "chrome".to_string()), @@ -398,6 +409,65 @@ impl DaemonState { })); } + /// Start the background task that auto-accepts `alert` and `beforeunload` + /// dialogs so they never block the agent. `confirm` and `prompt` dialogs + /// are left for the agent to handle explicitly. + fn start_dialog_handler(&mut self) { + if let Some(task) = self.dialog_handler_task.take() { + task.abort(); + } + + if !self.auto_dialog { + return; + } + + let Some(ref browser) = self.browser else { + return; + }; + + let client = browser.client.clone(); + let mut rx = browser.client.subscribe(); + + self.dialog_handler_task = Some(tokio::spawn(async move { + loop { + match rx.recv().await { + Ok(event) if event.method == "Page.javascriptDialogOpening" => { + let dialog_type = event + .params + .get("type") + .and_then(|v| v.as_str()) + .unwrap_or(""); + if matches!(dialog_type, "beforeunload" | "alert") { + let message = event + .params + .get("message") + .and_then(|v| v.as_str()) + .unwrap_or(""); + eprintln!("[auto-dismiss] {} dialog: {}", dialog_type, message); + let sid = event.session_id.clone().unwrap_or_default(); + if let Err(e) = client + .send_command( + "Page.handleJavaScriptDialog", + Some(json!({ "accept": true })), + Some(&sid), + ) + .await + { + eprintln!( + "[auto-dismiss] failed to dismiss {} dialog: {}", + dialog_type, e + ); + } + } + } + Ok(_) => continue, + Err(broadcast::error::RecvError::Lagged(_)) => continue, + Err(_) => break, + } + } + })); + } + /// Update the stream server's CDP client slot when browser is set or cleared. pub async fn update_stream_client(&self) { if let Some(ref slot) = self.stream_client { @@ -918,12 +988,22 @@ impl DaemonState { event.params.clone(), ) { - self.pending_dialog = Some(PendingDialog { - dialog_type: dialog_event.dialog_type, - message: dialog_event.message, - url: dialog_event.url, - default_prompt: dialog_event.default_prompt, - }); + // When auto_dialog is enabled, alert and beforeunload + // dialogs are handled by the background dialog_handler_task. + // Skip tracking them to avoid a stale warning. + let auto_handled = self.auto_dialog + && matches!( + dialog_event.dialog_type.as_str(), + "beforeunload" | "alert" + ); + if !auto_handled { + self.pending_dialog = Some(PendingDialog { + dialog_type: dialog_event.dialog_type, + message: dialog_event.message, + url: dialog_event.url, + default_prompt: dialog_event.default_prompt, + }); + } } } "Page.javascriptDialogClosed" => { @@ -961,6 +1041,9 @@ impl Drop for DaemonState { if let Some(task) = self.fetch_handler_task.take() { task.abort(); } + if let Some(task) = self.dialog_handler_task.take() { + task.abort(); + } } } @@ -1342,6 +1425,7 @@ async fn auto_launch(state: &mut DaemonState) -> Result<(), String> { state.browser = Some(mgr); state.subscribe_to_browser_events(); state.start_fetch_handler(); + state.start_dialog_handler(); state.update_stream_client().await; try_auto_restore_state(state).await; return Ok(()); @@ -1352,6 +1436,7 @@ async fn auto_launch(state: &mut DaemonState) -> Result<(), String> { state.browser = Some(connect_auto_with_fresh_tab().await?); state.subscribe_to_browser_events(); state.start_fetch_handler(); + state.start_dialog_handler(); state.update_stream_client().await; try_auto_restore_state(state).await; return Ok(()); @@ -1362,6 +1447,7 @@ async fn auto_launch(state: &mut DaemonState) -> Result<(), String> { state.browser = Some(mgr); state.subscribe_to_browser_events(); state.start_fetch_handler(); + state.start_dialog_handler(); state.update_stream_client().await; // Enable Fetch with handleAuthRequests for proxy authentication @@ -1505,6 +1591,7 @@ async fn handle_launch(cmd: &Value, state: &mut DaemonState) -> Result Result Result Result Result Categories requiring confirmation (or AGENT_BROWSER_CONFIRM_ACTIONS) --confirm-interactive Interactive confirmation prompts; auto-denies if stdin is not a TTY (or AGENT_BROWSER_CONFIRM_INTERACTIVE) --engine Browser engine: chrome (default), lightpanda (or AGENT_BROWSER_ENGINE) + --no-auto-dialog Disable automatic dismissal of alert/beforeunload dialogs (or AGENT_BROWSER_NO_AUTO_DIALOG) --config Use a custom config file (or AGENT_BROWSER_CONFIG env) --debug Debug output --version, -V Show version @@ -2881,6 +2882,7 @@ Environment: AGENT_BROWSER_ACTION_POLICY Path to action policy JSON file AGENT_BROWSER_CONFIRM_ACTIONS Action categories requiring confirmation AGENT_BROWSER_CONFIRM_INTERACTIVE Enable interactive confirmation prompts + AGENT_BROWSER_NO_AUTO_DIALOG Disable automatic dismissal of alert/beforeunload dialogs AGENT_BROWSER_ENGINE Browser engine: chrome (default), lightpanda HTTP_PROXY / HTTPS_PROXY Standard proxy env vars (fallback if AGENT_BROWSER_PROXY not set) ALL_PROXY SOCKS proxy (fallback for proxy) diff --git a/docs/src/app/commands/page.mdx b/docs/src/app/commands/page.mdx index a3e1d05..b6ea89e 100644 --- a/docs/src/app/commands/page.mdx +++ b/docs/src/app/commands/page.mdx @@ -230,6 +230,8 @@ agent-browser dialog dismiss # Dismiss dialog agent-browser dialog status # Check if a dialog is currently open ``` +By default, `alert` and `beforeunload` dialogs are automatically accepted so they never block the agent. `confirm` and `prompt` dialogs still require explicit handling. Use `--no-auto-dialog` (or `AGENT_BROWSER_NO_AUTO_DIALOG=1`) to disable automatic handling. + When a JavaScript dialog (`alert`, `confirm`, `prompt`) is pending, all command responses include a `warning` field with the dialog type and message. ## Streaming diff --git a/docs/src/app/configuration/page.mdx b/docs/src/app/configuration/page.mdx index 36ca990..334f402 100644 --- a/docs/src/app/configuration/page.mdx +++ b/docs/src/app/configuration/page.mdx @@ -77,6 +77,7 @@ Every CLI flag can be set in the config file using its camelCase equivalent: confirmActions--confirm-actionsstring confirmInteractive--confirm-interactiveboolean engine--enginestring (chrome, lightpanda) + noAutoDialog--no-auto-dialogboolean headers--headersstring (JSON) @@ -185,6 +186,7 @@ These environment variables configure additional daemon and runtime behavior: AGENT_BROWSER_CONFIRM_ACTIONSComma-separated action categories requiring confirmation.(none) AGENT_BROWSER_CONFIRM_INTERACTIVEEnable interactive confirmation prompts (auto-denies if stdin is not a TTY).(disabled) AGENT_BROWSER_ENGINEBrowser engine to use: chrome (default), lightpanda.chrome + AGENT_BROWSER_NO_AUTO_DIALOGDisable automatic dismissal of alert/beforeunload dialogs.(disabled) diff --git a/skills/agent-browser/SKILL.md b/skills/agent-browser/SKILL.md index a0979e6..00f97aa 100644 --- a/skills/agent-browser/SKILL.md +++ b/skills/agent-browser/SKILL.md @@ -184,7 +184,10 @@ agent-browser clipboard write "Hello, World!" # Write text to clipboard agent-browser clipboard copy # Copy current selection agent-browser clipboard paste # Paste from clipboard -# Dialogs (alert, confirm, prompt) +# Dialogs (alert, confirm, prompt, beforeunload) +# By default, alert and beforeunload dialogs are auto-accepted so they never block the agent. +# confirm and prompt dialogs still require explicit handling. +# Use --no-auto-dialog to disable automatic handling. agent-browser dialog accept # Accept dialog agent-browser dialog accept "my input" # Accept prompt dialog with text agent-browser dialog dismiss # Dismiss/cancel dialog diff --git a/skills/agent-browser/references/commands.md b/skills/agent-browser/references/commands.md index bae62c6..8fbfe36 100644 --- a/skills/agent-browser/references/commands.md +++ b/skills/agent-browser/references/commands.md @@ -209,9 +209,12 @@ The `frame` command accepts: ## Dialogs +By default, `alert` and `beforeunload` dialogs are automatically accepted so they never block the agent. `confirm` and `prompt` dialogs still require explicit handling. Use `--no-auto-dialog` to disable this behavior. + ```bash agent-browser dialog accept [text] # Accept dialog agent-browser dialog dismiss # Dismiss dialog +agent-browser dialog status # Check if a dialog is currently open ``` ## JavaScript