diff --git a/cli/src/connection.rs b/cli/src/connection.rs index d532943..4cdf2f7 100644 --- a/cli/src/connection.rs +++ b/cli/src/connection.rs @@ -217,6 +217,7 @@ pub struct DaemonOptions<'a> { pub confirm_actions: Option<&'a str>, pub engine: Option<&'a str>, pub auto_connect: bool, + pub force_launch: bool, pub idle_timeout: Option<&'a str>, pub cdp: Option<&'a str>, pub no_auto_dialog: bool, @@ -295,6 +296,9 @@ fn apply_daemon_env(cmd: &mut Command, session: &str, opts: &DaemonOptions) { if opts.auto_connect { cmd.env("AGENT_BROWSER_AUTO_CONNECT", "1"); } + if opts.force_launch { + cmd.env("AGENT_BROWSER_FORCE_LAUNCH", "1"); + } if let Some(idle) = opts.idle_timeout { cmd.env("AGENT_BROWSER_IDLE_TIMEOUT_MS", idle); } diff --git a/cli/src/flags.rs b/cli/src/flags.rs index 806d4e8..bdbec24 100644 --- a/cli/src/flags.rs +++ b/cli/src/flags.rs @@ -285,6 +285,7 @@ pub struct Flags { pub allow_file_access: bool, pub device: Option, pub auto_connect: bool, + pub force_launch: bool, pub session_name: Option, pub annotate: bool, pub color_scheme: Option, @@ -380,8 +381,11 @@ pub fn parse_flags(args: &[String]) -> Flags { allow_file_access: env_var_is_truthy("AGENT_BROWSER_ALLOW_FILE_ACCESS") || config.allow_file_access.unwrap_or(false), device: env::var("AGENT_BROWSER_IOS_DEVICE").ok().or(config.device), - auto_connect: env_var_is_truthy("AGENT_BROWSER_AUTO_CONNECT") - || config.auto_connect.unwrap_or(false), + auto_connect: !env_var_is_truthy("AGENT_BROWSER_NO_AUTO_CONNECT") + && (env_var_is_truthy("AGENT_BROWSER_AUTO_CONNECT") + || config.auto_connect.unwrap_or(true)), + force_launch: env_var_is_truthy("AGENT_BROWSER_FORCE_LAUNCH") + || env::var("CI").is_ok(), session_name: env::var("AGENT_BROWSER_SESSION_NAME") .ok() .or(config.session_name), @@ -590,10 +594,17 @@ pub fn parse_flags(args: &[String]) -> Flags { "--auto-connect" => { let (val, consumed) = parse_bool_arg(args, i); flags.auto_connect = val; + if !val { + flags.force_launch = true; + } if consumed { i += 1; } } + "--launch" | "--new" => { + flags.force_launch = true; + flags.auto_connect = false; + } "--session-name" => { if let Some(s) = args.get(i + 1) { flags.session_name = Some(s.clone()); @@ -738,6 +749,8 @@ pub fn clean_args(args: &[String]) -> Vec { "--ignore-https-errors", "--allow-file-access", "--auto-connect", + "--launch", + "--new", "--annotate", "--content-boundaries", "--confirm-interactive", diff --git a/cli/src/main.rs b/cli/src/main.rs index 979f592..fa313f3 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -511,7 +511,7 @@ fn main() { } let args: Vec = env::args().skip(1).collect(); - let flags = parse_flags(&args); + let mut flags = parse_flags(&args); let clean = clean_args(&args); let has_help = args.iter().any(|a| a == "--help" || a == "-h"); @@ -719,6 +719,7 @@ fn main() { confirm_actions: flags.confirm_actions.as_deref(), engine: flags.engine.as_deref(), auto_connect: flags.auto_connect, + force_launch: flags.force_launch, idle_timeout: flags.idle_timeout.as_deref(), cdp: flags.cdp.as_deref(), no_auto_dialog: flags.no_auto_dialog, @@ -806,24 +807,9 @@ fn main() { exit(1); } - if flags.auto_connect && flags.cdp.is_some() { - let msg = "Cannot use --auto-connect and --cdp together"; - if flags.json { - print_json_error(msg); - } else { - eprintln!("{} {}", color::error_indicator(), msg); - } - exit(1); - } - - if flags.auto_connect && flags.provider.is_some() { - let msg = "Cannot use --auto-connect and -p/--provider together"; - if flags.json { - print_json_error(msg); - } else { - eprintln!("{} {}", color::error_indicator(), msg); - } - exit(1); + // Explicit --cdp or --provider disables auto-connect (they specify the connection) + if flags.cdp.is_some() || flags.provider.is_some() { + flags.auto_connect = false; } if flags.provider.is_some() && !flags.extensions.is_empty() { @@ -1035,7 +1021,7 @@ fn main() { || !flags.extensions.is_empty()) && flags.cdp.is_none() && flags.provider.is_none() - && !flags.auto_connect + && (flags.force_launch || !flags.auto_connect) { let mut launch_cmd = json!({ "id": gen_id(), diff --git a/cli/src/native/actions.rs b/cli/src/native/actions.rs index 3a7b51f..11ac74b 100644 --- a/cli/src/native/actions.rs +++ b/cli/src/native/actions.rs @@ -1426,16 +1426,38 @@ async fn auto_launch(state: &mut DaemonState) -> Result<(), String> { return Ok(()); } - if env::var("AGENT_BROWSER_AUTO_CONNECT").is_ok() { - state.reset_input_state(); - 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; - apply_stealth_to_browser(state).await; - return Ok(()); + let force_launch = env::var("AGENT_BROWSER_FORCE_LAUNCH").is_ok(); + + // Default behavior: try to connect to the user's existing Chrome first. + // This shares cookies/sessions so the agent can reuse logged-in state. + // Skip if --launch/--new was passed or running in CI. + if env::var("AGENT_BROWSER_AUTO_CONNECT").is_ok() && !force_launch { + match connect_auto_with_fresh_tab().await { + Ok(mgr) => { + state.reset_input_state(); + 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; + apply_stealth_to_browser(state).await; + return Ok(()); + } + Err(_e) => { + // Could not find a running Chrome with CDP enabled. + // Return a helpful error guiding the user to enable it. + return Err(format!( + "Could not connect to your Chrome browser.\n\n\ + To let agent-browser work with your existing Chrome (recommended):\n\ + {}\n\n\ + Or start a standalone browser with: agent-browser --launch open \n\n\ + Tip: On Chrome 144+, you can enable CDP without restarting:\n\ + Open chrome://inspect/#remote-debugging and toggle it on.", + chrome_relaunch_hint(), + )); + } + } } let mgr = BrowserManager::launch(options, engine.as_deref()).await?; @@ -1462,6 +1484,23 @@ async fn auto_launch(state: &mut DaemonState) -> Result<(), String> { } /// Inject stealth scripts into the active browser session. +/// Platform-specific hint for relaunching Chrome with CDP enabled. +fn chrome_relaunch_hint() -> &'static str { + if cfg!(target_os = "macos") { + " 1. Quit Chrome completely\n\ + 2. Run: open -a \"Google Chrome\" --args --remote-debugging-port=9222\n\ + 3. Then retry your agent-browser command" + } else if cfg!(target_os = "windows") { + " 1. Close Chrome completely\n\ + 2. Run: start chrome --remote-debugging-port=9222\n\ + 3. Then retry your agent-browser command" + } else { + " 1. Close Chrome completely\n\ + 2. Run: google-chrome --remote-debugging-port=9222\n\ + 3. Then retry your agent-browser command" + } +} + /// Called after every successful launch / CDP connect / auto-connect. async fn apply_stealth_to_browser(state: &DaemonState) { if env::var("AGENT_BROWSER_STEALTH").map(|v| v == "0").unwrap_or(false) { @@ -1635,13 +1674,29 @@ async fn handle_launch(cmd: &Value, state: &mut DaemonState) -> Result { + state.reset_input_state(); + state.browser = Some(mgr); + state.subscribe_to_browser_events(); + state.start_fetch_handler(); + state.start_dialog_handler(); + state.update_stream_client().await; + apply_stealth_to_browser(state).await; + return Ok(json!({ "launched": true })); + } + Err(_e) => { + return Err(format!( + "Could not connect to your Chrome browser.\n\n\ + To let agent-browser work with your existing Chrome (recommended):\n\ + {}\n\n\ + Or start a standalone browser with: agent-browser --launch open \n\n\ + Tip: On Chrome 144+, you can enable CDP without restarting:\n\ + Open chrome://inspect/#remote-debugging and toggle it on.", + chrome_relaunch_hint(), + )); + } + } } if let Some(provider) = cmd.get("provider").and_then(|v| v.as_str()) { diff --git a/cli/src/output.rs b/cli/src/output.rs index fa23267..56494ab 100644 --- a/cli/src/output.rs +++ b/cli/src/output.rs @@ -2791,8 +2791,9 @@ Authentication: (or AGENT_BROWSER_SESSION_NAME env) --state Load saved auth state (cookies + storage) from JSON file (or AGENT_BROWSER_STATE env) - --auto-connect Connect to a running Chrome to reuse its auth state - Tip: agent-browser --auto-connect state save ./auth.json + --auto-connect Connect to a running Chrome (DEFAULT - shares cookies/sessions) + Tip: enable CDP via chrome://inspect/#remote-debugging + --launch, --new Launch a fresh browser instead of connecting to existing --headers HTTP headers scoped to URL's origin (e.g., Authorization bearer token) Options: