feat(connect): make auto-connect to user's Chrome the default behavior
- Auto-connect is now ON by default (was opt-in via --auto-connect) - Added --launch/--new flags to explicitly start a fresh browser - CI environments (CI env var) automatically use --launch mode - Friendly error message with platform-specific Chrome relaunch guide - Mentions Chrome 144+ runtime CDP toggle (chrome://inspect) - --cdp and --provider flags implicitly disable auto-connect - AGENT_BROWSER_NO_AUTO_CONNECT=1 to disable, AGENT_BROWSER_FORCE_LAUNCH=1 to force Track 3 of native-stealth migration. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
77616a209c
commit
7ee3d5fb94
@@ -410,6 +410,7 @@ pub struct DaemonOptions<'a> {
|
|||||||
pub confirm_actions: Option<&'a str>,
|
pub confirm_actions: Option<&'a str>,
|
||||||
pub engine: Option<&'a str>,
|
pub engine: Option<&'a str>,
|
||||||
pub auto_connect: bool,
|
pub auto_connect: bool,
|
||||||
|
pub force_launch: bool,
|
||||||
pub idle_timeout: Option<&'a str>,
|
pub idle_timeout: Option<&'a str>,
|
||||||
pub default_timeout: Option<u64>,
|
pub default_timeout: Option<u64>,
|
||||||
pub cdp: Option<&'a str>,
|
pub cdp: Option<&'a str>,
|
||||||
@@ -495,6 +496,9 @@ fn apply_daemon_env(cmd: &mut Command, session: &str, opts: &DaemonOptions) {
|
|||||||
if opts.auto_connect {
|
if opts.auto_connect {
|
||||||
cmd.env("AGENT_BROWSER_AUTO_CONNECT", "1");
|
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 {
|
if let Some(idle) = opts.idle_timeout {
|
||||||
cmd.env("AGENT_BROWSER_IDLE_TIMEOUT_MS", idle);
|
cmd.env("AGENT_BROWSER_IDLE_TIMEOUT_MS", idle);
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-2
@@ -308,6 +308,7 @@ pub struct Flags {
|
|||||||
pub allow_file_access: bool,
|
pub allow_file_access: bool,
|
||||||
pub device: Option<String>,
|
pub device: Option<String>,
|
||||||
pub auto_connect: bool,
|
pub auto_connect: bool,
|
||||||
|
pub force_launch: bool,
|
||||||
pub session_name: Option<String>,
|
pub session_name: Option<String>,
|
||||||
pub annotate: bool,
|
pub annotate: bool,
|
||||||
pub color_scheme: Option<String>,
|
pub color_scheme: Option<String>,
|
||||||
@@ -443,8 +444,11 @@ pub fn parse_flags(args: &[String]) -> Flags {
|
|||||||
allow_file_access: env_var_is_truthy("AGENT_BROWSER_ALLOW_FILE_ACCESS")
|
allow_file_access: env_var_is_truthy("AGENT_BROWSER_ALLOW_FILE_ACCESS")
|
||||||
|| config.allow_file_access.unwrap_or(false),
|
|| config.allow_file_access.unwrap_or(false),
|
||||||
device: env::var("AGENT_BROWSER_IOS_DEVICE").ok().or(config.device),
|
device: env::var("AGENT_BROWSER_IOS_DEVICE").ok().or(config.device),
|
||||||
auto_connect: env_var_is_truthy("AGENT_BROWSER_AUTO_CONNECT")
|
auto_connect: !env_var_is_truthy("AGENT_BROWSER_NO_AUTO_CONNECT")
|
||||||
|| config.auto_connect.unwrap_or(false),
|
&& (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")
|
session_name: env::var("AGENT_BROWSER_SESSION_NAME")
|
||||||
.ok()
|
.ok()
|
||||||
.or(config.session_name),
|
.or(config.session_name),
|
||||||
@@ -682,10 +686,17 @@ pub fn parse_flags(args: &[String]) -> Flags {
|
|||||||
"--auto-connect" => {
|
"--auto-connect" => {
|
||||||
let (val, consumed) = parse_bool_arg(args, i);
|
let (val, consumed) = parse_bool_arg(args, i);
|
||||||
flags.auto_connect = val;
|
flags.auto_connect = val;
|
||||||
|
if !val {
|
||||||
|
flags.force_launch = true;
|
||||||
|
}
|
||||||
if consumed {
|
if consumed {
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"--launch" | "--new" => {
|
||||||
|
flags.force_launch = true;
|
||||||
|
flags.auto_connect = false;
|
||||||
|
}
|
||||||
"--session-name" => {
|
"--session-name" => {
|
||||||
if let Some(s) = args.get(i + 1) {
|
if let Some(s) = args.get(i + 1) {
|
||||||
flags.session_name = Some(s.clone());
|
flags.session_name = Some(s.clone());
|
||||||
@@ -842,6 +853,8 @@ pub fn clean_args(args: &[String]) -> Vec<String> {
|
|||||||
"--ignore-https-errors",
|
"--ignore-https-errors",
|
||||||
"--allow-file-access",
|
"--allow-file-access",
|
||||||
"--auto-connect",
|
"--auto-connect",
|
||||||
|
"--launch",
|
||||||
|
"--new",
|
||||||
"--annotate",
|
"--annotate",
|
||||||
"--content-boundaries",
|
"--content-boundaries",
|
||||||
"--confirm-interactive",
|
"--confirm-interactive",
|
||||||
|
|||||||
+6
-20
@@ -511,7 +511,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let args: Vec<String> = env::args().skip(1).collect();
|
let args: Vec<String> = env::args().skip(1).collect();
|
||||||
let flags = parse_flags(&args);
|
let mut flags = parse_flags(&args);
|
||||||
let clean = clean_args(&args);
|
let clean = clean_args(&args);
|
||||||
|
|
||||||
let has_help = args.iter().any(|a| a == "--help" || a == "-h");
|
let has_help = args.iter().any(|a| a == "--help" || a == "-h");
|
||||||
@@ -752,6 +752,7 @@ fn main() {
|
|||||||
confirm_actions: flags.confirm_actions.as_deref(),
|
confirm_actions: flags.confirm_actions.as_deref(),
|
||||||
engine: flags.engine.as_deref(),
|
engine: flags.engine.as_deref(),
|
||||||
auto_connect: flags.auto_connect,
|
auto_connect: flags.auto_connect,
|
||||||
|
force_launch: flags.force_launch,
|
||||||
idle_timeout: flags.idle_timeout.as_deref(),
|
idle_timeout: flags.idle_timeout.as_deref(),
|
||||||
default_timeout: flags.default_timeout,
|
default_timeout: flags.default_timeout,
|
||||||
cdp: flags.cdp.as_deref(),
|
cdp: flags.cdp.as_deref(),
|
||||||
@@ -840,24 +841,9 @@ fn main() {
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if flags.auto_connect && flags.cdp.is_some() {
|
// Explicit --cdp or --provider disables auto-connect (they specify the connection)
|
||||||
let msg = "Cannot use --auto-connect and --cdp together";
|
if flags.cdp.is_some() || flags.provider.is_some() {
|
||||||
if flags.json {
|
flags.auto_connect = false;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if flags.provider.is_some() && !flags.extensions.is_empty() {
|
if flags.provider.is_some() && !flags.extensions.is_empty() {
|
||||||
@@ -1069,7 +1055,7 @@ fn main() {
|
|||||||
|| !flags.extensions.is_empty())
|
|| !flags.extensions.is_empty())
|
||||||
&& flags.cdp.is_none()
|
&& flags.cdp.is_none()
|
||||||
&& flags.provider.is_none()
|
&& flags.provider.is_none()
|
||||||
&& !flags.auto_connect
|
&& (flags.force_launch || !flags.auto_connect)
|
||||||
{
|
{
|
||||||
let mut launch_cmd = json!({
|
let mut launch_cmd = json!({
|
||||||
"id": gen_id(),
|
"id": gen_id(),
|
||||||
|
|||||||
+76
-21
@@ -1555,18 +1555,40 @@ async fn auto_launch(state: &mut DaemonState) -> Result<(), String> {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
if env::var("AGENT_BROWSER_AUTO_CONNECT").is_ok() {
|
let force_launch = env::var("AGENT_BROWSER_FORCE_LAUNCH").is_ok();
|
||||||
state.reset_input_state();
|
|
||||||
state.browser = Some(connect_auto_with_fresh_tab().await?);
|
// Default behavior: try to connect to the user's existing Chrome first.
|
||||||
state.subscribe_to_browser_events();
|
// This shares cookies/sessions so the agent can reuse logged-in state.
|
||||||
state.start_fetch_handler();
|
// Skip if --launch/--new was passed or running in CI.
|
||||||
state.start_dialog_handler();
|
if env::var("AGENT_BROWSER_AUTO_CONNECT").is_ok() && !force_launch {
|
||||||
state.update_stream_client().await;
|
match connect_auto_with_fresh_tab().await {
|
||||||
apply_launch_init_scripts(state).await;
|
Ok(mgr) => {
|
||||||
try_auto_restore_state(state).await;
|
state.reset_input_state();
|
||||||
try_load_storage_state(state, &storage_state_path).await;
|
state.browser = Some(mgr);
|
||||||
apply_stealth_to_browser(state).await;
|
state.subscribe_to_browser_events();
|
||||||
return Ok(());
|
state.start_fetch_handler();
|
||||||
|
state.start_dialog_handler();
|
||||||
|
state.update_stream_client().await;
|
||||||
|
apply_launch_init_scripts(state).await;
|
||||||
|
try_auto_restore_state(state).await;
|
||||||
|
try_load_storage_state(state, &storage_state_path).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 <url>\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(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cloud provider: when AGENT_BROWSER_PROVIDER is set, connect via the
|
// Cloud provider: when AGENT_BROWSER_PROVIDER is set, connect via the
|
||||||
@@ -1689,6 +1711,23 @@ async fn apply_launch_init_scripts(state: &DaemonState) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Inject stealth scripts into the active browser session.
|
/// 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.
|
/// Called after every successful launch / CDP connect / auto-connect.
|
||||||
async fn apply_stealth_to_browser(state: &DaemonState) {
|
async fn apply_stealth_to_browser(state: &DaemonState) {
|
||||||
if env::var("AGENT_BROWSER_STEALTH").map(|v| v == "0").unwrap_or(false) {
|
if env::var("AGENT_BROWSER_STEALTH").map(|v| v == "0").unwrap_or(false) {
|
||||||
@@ -1998,15 +2037,31 @@ async fn handle_launch(cmd: &Value, state: &mut DaemonState) -> Result<Value, St
|
|||||||
}
|
}
|
||||||
|
|
||||||
if auto_connect {
|
if auto_connect {
|
||||||
state.reset_input_state();
|
match connect_auto_with_fresh_tab().await {
|
||||||
state.browser = Some(connect_auto_with_fresh_tab().await?);
|
Ok(mgr) => {
|
||||||
state.subscribe_to_browser_events();
|
state.reset_input_state();
|
||||||
state.start_fetch_handler();
|
state.browser = Some(mgr);
|
||||||
state.start_dialog_handler();
|
state.subscribe_to_browser_events();
|
||||||
state.update_stream_client().await;
|
state.start_fetch_handler();
|
||||||
load_storage_state_or_rollback(state, &storage_state_owned).await?;
|
state.start_dialog_handler();
|
||||||
apply_launch_init_scripts(state).await;
|
state.update_stream_client().await;
|
||||||
return Ok(json!({ "launched": true }));
|
load_storage_state_or_rollback(state, &storage_state_owned).await?;
|
||||||
|
apply_launch_init_scripts(state).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 <url>\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()) {
|
if let Some(provider) = cmd.get("provider").and_then(|v| v.as_str()) {
|
||||||
|
|||||||
+3
-2
@@ -3076,8 +3076,9 @@ Authentication:
|
|||||||
(or AGENT_BROWSER_SESSION_NAME env)
|
(or AGENT_BROWSER_SESSION_NAME env)
|
||||||
--state <path> Load saved auth state (cookies + storage) from JSON file
|
--state <path> Load saved auth state (cookies + storage) from JSON file
|
||||||
(or AGENT_BROWSER_STATE env)
|
(or AGENT_BROWSER_STATE env)
|
||||||
--auto-connect Connect to a running Chrome to reuse its auth state
|
--auto-connect Connect to a running Chrome (DEFAULT - shares cookies/sessions)
|
||||||
Tip: agent-browser --auto-connect state save ./auth.json
|
Tip: enable CDP via chrome://inspect/#remote-debugging
|
||||||
|
--launch, --new Launch a fresh browser instead of connecting to existing
|
||||||
--headers <json> HTTP headers scoped to URL's origin (e.g., Authorization bearer token)
|
--headers <json> HTTP headers scoped to URL's origin (e.g., Authorization bearer token)
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|||||||
Reference in New Issue
Block a user