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>
This commit is contained in:
Chris Tate
2026-03-29 12:00:27 -06:00
committed by GitHub
co-authored by ctate
parent da7fef3fef
commit 6dd53449e8
11 changed files with 224 additions and 7 deletions
+36
View File
@@ -87,6 +87,7 @@ pub struct Config {
pub screenshot_quality: Option<u32>,
pub screenshot_format: Option<String>,
pub idle_timeout: Option<String>,
pub no_auto_dialog: Option<bool>,
}
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<u32>,
pub screenshot_format: Option<String>,
pub idle_timeout: Option<String>, // 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<String> {
"--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<String> = 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"]);
}
}