diff --git a/cli/src/main.rs b/cli/src/main.rs index 87df189..fb46e59 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -107,11 +107,31 @@ fn main() { let args: Vec = env::args().skip(1).collect(); let mut flags = parse_flags(&args); let clean = clean_args(&args); + let command_name = clean.first().map(|s| s.as_str()); if flags.engine.is_some() && !flags.native { flags.native = true; } + let can_try_default_cdp = flags.cdp.is_none() + && !flags.auto_connect + && flags.provider.is_none() + && flags.executable_path.is_none() + && flags.state.is_none() + && flags.proxy.is_none() + && flags.args.is_none() + && flags.user_agent.is_none() + && !flags.ignore_https_errors + && !flags.allow_file_access + && flags.extensions.is_empty(); + let can_force_native_for_cdp = !matches!(command_name, Some("close")); + if !flags.native + && can_force_native_for_cdp + && (flags.cdp.is_some() || flags.auto_connect || can_try_default_cdp) + { + flags.native = true; + } + let has_help = args.iter().any(|a| a == "--help" || a == "-h"); let has_version = args.iter().any(|a| a == "--version" || a == "-V"); @@ -605,18 +625,6 @@ fn main() { // Project policy: when no explicit connection mode is provided, // commands should attach to an existing browser. // Try CDP :9333 first, then fall back to auto-connect discovery. - let can_try_default_cdp = flags.cdp.is_none() - && !flags.auto_connect - && flags.provider.is_none() - && flags.executable_path.is_none() - && flags.state.is_none() - && flags.proxy.is_none() - && flags.args.is_none() - && flags.user_agent.is_none() - && !flags.ignore_https_errors - && !flags.allow_file_access - && flags.extensions.is_empty(); - if can_try_default_cdp { let mut launch_cmd = json!({ "id": gen_id(), diff --git a/cli/src/native/browser.rs b/cli/src/native/browser.rs index 7fb2fbf..df7c1fe 100644 --- a/cli/src/native/browser.rs +++ b/cli/src/native/browser.rs @@ -2,6 +2,7 @@ use serde_json::{json, Value}; use std::collections::HashSet; use std::sync::Arc; use tokio::sync::Mutex; +use tokio::time::{timeout, Duration}; use super::cdp::chrome::{ auto_connect_cdp, discover_cdp_url, launch_chrome, ChromeProcess, LaunchOptions, @@ -277,6 +278,50 @@ impl BrowserManager { Self::connect_cdp(&ws_url).await } + async fn create_and_attach_blank_page(&mut self) -> Result<(), String> { + let result: CreateTargetResult = self + .client + .send_command_typed( + "Target.createTarget", + &CreateTargetParams { + url: "about:blank".to_string(), + }, + None, + ) + .await?; + + let attach_result: AttachToTargetResult = self + .client + .send_command_typed( + "Target.attachToTarget", + &AttachToTargetParams { + target_id: result.target_id.clone(), + flatten: true, + }, + None, + ) + .await?; + + self.enable_domains_with_timeout(&attach_result.session_id) + .await?; + + self.pages = vec![PageInfo { + target_id: result.target_id, + session_id: attach_result.session_id, + url: "about:blank".to_string(), + title: String::new(), + target_type: "page".to_string(), + }]; + self.active_page_index = 0; + Ok(()) + } + + async fn enable_domains_with_timeout(&self, session_id: &str) -> Result<(), String> { + timeout(Duration::from_secs(3), self.enable_domains(session_id)) + .await + .map_err(|_| format!("Timed out enabling CDP domains for session {}", session_id))? + } + async fn discover_and_attach_targets(&mut self) -> Result<(), String> { self.client .send_command_typed::<_, Value>( @@ -300,40 +345,9 @@ impl BrowserManager { .collect(); if page_targets.is_empty() { - // Create a new tab - let result: CreateTargetResult = self - .client - .send_command_typed( - "Target.createTarget", - &CreateTargetParams { - url: "about:blank".to_string(), - }, - None, - ) - .await?; - - let attach_result: AttachToTargetResult = self - .client - .send_command_typed( - "Target.attachToTarget", - &AttachToTargetParams { - target_id: result.target_id.clone(), - flatten: true, - }, - None, - ) - .await?; - - self.pages.push(PageInfo { - target_id: result.target_id, - session_id: attach_result.session_id.clone(), - url: "about:blank".to_string(), - title: String::new(), - target_type: "page".to_string(), - }); - self.active_page_index = 0; - self.enable_domains(&attach_result.session_id).await?; + self.create_and_attach_blank_page().await?; } else { + let mut attached_pages = Vec::new(); for target in &page_targets { let attach_result: AttachToTargetResult = self .client @@ -347,18 +361,36 @@ impl BrowserManager { ) .await?; - self.pages.push(PageInfo { + let page_info = PageInfo { target_id: target.target_id.clone(), session_id: attach_result.session_id.clone(), url: target.url.clone(), title: target.title.clone(), target_type: target.target_type.clone(), - }); + }; + + match self + .enable_domains_with_timeout(&attach_result.session_id) + .await + { + Ok(()) => attached_pages.push(page_info), + Err(err) => { + if std::env::var("AGENT_BROWSER_DEBUG").as_deref() == Ok("1") { + eprintln!( + "[DEBUG] Skipping CDP target '{}' ({}): {}", + target.title, target.url, err + ); + } + } + } } - self.active_page_index = 0; - let session_id = self.pages[0].session_id.clone(); - self.enable_domains(&session_id).await?; + if attached_pages.is_empty() { + self.create_and_attach_blank_page().await?; + } else { + self.pages = attached_pages; + self.active_page_index = 0; + } } Ok(())