fix(cli): 增加 abs start 并修复 managed 9333 启动链路

This commit is contained in:
leeguooooo
2026-03-10 12:45:50 +09:00
parent 8eee9310a4
commit fcd891e8a9
13 changed files with 255 additions and 68 deletions
+44 -1
View File
@@ -729,8 +729,12 @@ pub async fn execute_command(cmd: &Value, state: &mut DaemonState) -> Value {
async fn auto_launch(state: &mut DaemonState) -> Result<(), String> {
let options = launch_options_from_env();
let engine = env::var("AGENT_BROWSER_ENGINE").ok();
let debug_enabled = env::var("AGENT_BROWSER_DEBUG").as_deref() == Ok("1");
if let Ok(cdp) = env::var("AGENT_BROWSER_CDP") {
if debug_enabled {
eprintln!("[DEBUG] auto_launch: connecting via AGENT_BROWSER_CDP={}", cdp);
}
let mgr = BrowserManager::connect_cdp(&cdp).await?;
state.browser = Some(mgr);
state.subscribe_to_browser_events();
@@ -739,6 +743,9 @@ async fn auto_launch(state: &mut DaemonState) -> Result<(), String> {
}
if env::var("AGENT_BROWSER_AUTO_CONNECT").is_ok() {
if debug_enabled {
eprintln!("[DEBUG] auto_launch: connecting via AGENT_BROWSER_AUTO_CONNECT");
}
let mgr = BrowserManager::connect_auto().await?;
state.browser = Some(mgr);
state.subscribe_to_browser_events();
@@ -746,13 +753,36 @@ async fn auto_launch(state: &mut DaemonState) -> Result<(), String> {
return Ok(());
}
let mgr = BrowserManager::launch(options, engine.as_deref()).await?;
let mgr = if should_auto_launch_managed_cdp(&options) {
if debug_enabled {
eprintln!("[DEBUG] auto_launch: launching managed Chrome on localhost:9333");
}
BrowserManager::launch_managed_cdp(options.executable_path.clone(), !options.headless)
.await?
} else {
if debug_enabled {
eprintln!("[DEBUG] auto_launch: launching local browser from env options");
}
BrowserManager::launch(options, engine.as_deref()).await?
};
state.browser = Some(mgr);
state.subscribe_to_browser_events();
try_auto_restore_state(state).await;
Ok(())
}
fn should_auto_launch_managed_cdp(options: &LaunchOptions) -> bool {
options.proxy.is_none()
&& options.proxy_bypass.is_none()
&& options.profile.is_none()
&& !options.allow_file_access
&& options.args.is_empty()
&& options.extensions.as_ref().is_none_or(|ext| ext.is_empty())
&& options.storage_state.is_none()
&& options.user_agent.is_none()
&& !options.ignore_https_errors
}
fn launch_options_from_env() -> LaunchOptions {
let headed = env::var("AGENT_BROWSER_HEADED")
.map(|v| v == "1" || v == "true")
@@ -5225,6 +5255,19 @@ mod tests {
);
}
#[test]
fn test_should_auto_launch_managed_cdp_for_bare_defaults() {
let opts = launch_options_from_env();
assert!(should_auto_launch_managed_cdp(&opts));
}
#[test]
fn test_should_not_auto_launch_managed_cdp_when_proxy_is_set() {
let mut opts = launch_options_from_env();
opts.proxy = Some("http://127.0.0.1:8080".to_string());
assert!(!should_auto_launch_managed_cdp(&opts));
}
#[tokio::test]
async fn test_execute_unknown_command() {
let mut state = DaemonState::new();
+42 -7
View File
@@ -111,6 +111,11 @@ pub fn to_ai_friendly_error(error: &str) -> String {
error.to_string()
}
fn is_startup_internal_page(url: &str) -> bool {
let lower = url.to_lowercase();
lower.starts_with("chrome://profile-picker")
}
#[derive(Debug, Clone)]
pub struct PageInfo {
pub target_id: String,
@@ -416,8 +421,22 @@ impl BrowserManager {
if attached_pages.is_empty() {
self.create_and_attach_blank_page().await?;
} else {
let preferred_index = attached_pages
.iter()
.position(|page| !is_startup_internal_page(&page.url));
self.pages = attached_pages;
self.active_page_index = 0;
if let Some(index) = preferred_index {
self.active_page_index = index;
} else {
if std::env::var("AGENT_BROWSER_DEBUG").as_deref() == Ok("1") {
eprintln!(
"[DEBUG] All discovered pages were Chrome startup pages; creating a fresh about:blank target"
);
}
self.create_and_attach_blank_page().await?;
}
}
}
@@ -450,6 +469,7 @@ impl BrowserManager {
pub async fn navigate(&mut self, url: &str, wait_until: WaitUntil) -> Result<Value, String> {
let session_id = self.active_session_id()?.to_string();
let rx = self.client.subscribe();
let nav_result: PageNavigateResult = self
.client
@@ -467,7 +487,7 @@ impl BrowserManager {
return Err(format!("Navigation failed: {}", error_text));
}
self.wait_for_lifecycle(wait_until, &session_id).await?;
self.wait_for_lifecycle(wait_until, &session_id, rx).await?;
let page_url = self.get_url().await.unwrap_or_else(|_| url.to_string());
let title = self.get_title().await.unwrap_or_default();
@@ -484,14 +504,14 @@ impl BrowserManager {
&self,
wait_until: WaitUntil,
session_id: &str,
mut rx: tokio::sync::broadcast::Receiver<CdpEvent>,
) -> Result<(), String> {
let event_name = match wait_until {
WaitUntil::Load => "Page.loadEventFired",
WaitUntil::DomContentLoaded => "Page.domContentEventFired",
WaitUntil::NetworkIdle => return self.wait_for_network_idle(session_id).await,
WaitUntil::NetworkIdle => return self.wait_for_network_idle(session_id, rx).await,
};
let mut rx = self.client.subscribe();
let timeout = tokio::time::Duration::from_millis(self.default_timeout_ms);
tokio::time::timeout(timeout, async {
@@ -506,8 +526,11 @@ impl BrowserManager {
.map_err(|_| format!("Timeout waiting for {}", event_name))?
}
async fn wait_for_network_idle(&self, session_id: &str) -> Result<(), String> {
let mut rx = self.client.subscribe();
async fn wait_for_network_idle(
&self,
session_id: &str,
mut rx: tokio::sync::broadcast::Receiver<CdpEvent>,
) -> Result<(), String> {
let pending = Arc::new(Mutex::new(HashSet::<String>::new()));
let timeout = tokio::time::Duration::from_millis(self.default_timeout_ms);
@@ -626,7 +649,8 @@ impl BrowserManager {
wait_until: WaitUntil,
session_id: &str,
) -> Result<(), String> {
self.wait_for_lifecycle(wait_until, session_id).await
self.wait_for_lifecycle(wait_until, session_id, self.client.subscribe())
.await
}
pub async fn close(&mut self) -> Result<(), String> {
@@ -1270,4 +1294,15 @@ mod tests {
let msg = "Some custom error message";
assert_eq!(to_ai_friendly_error(msg), msg);
}
#[test]
fn test_is_startup_internal_page_detects_profile_picker() {
assert!(is_startup_internal_page("chrome://profile-picker/"));
}
#[test]
fn test_is_startup_internal_page_ignores_normal_pages() {
assert!(!is_startup_internal_page("https://example.com/"));
assert!(!is_startup_internal_page("about:blank"));
}
}