* fix: Windows auto-connect profiling (#835) Fix three interrelated bugs causing `--auto-connect` to fail on Windows, plus a UX issue where auto-connect hijacked existing tabs: 1. Stale DevToolsActivePort — add TCP port liveness check before returning M144+ WebSocket URL; remove stale files when port is dead. 2. Missing Windows error codes — add os error 10061 (WSAECONNREFUSED) and 10054 (WSAECONNRESET) to is_transient_error() so daemon startup races are retried on Windows. 3. --auto-connect not propagated to daemon — add auto_connect to DaemonOptions, set AGENT_BROWSER_AUTO_CONNECT env var via apply_daemon_env(), and guard the headed launch block so it doesn't send a second launch that overrides the auto-connect. 4. Auto-connect opens a fresh tab — after connecting to an existing Chrome, create a new about:blank tab and bring it to front so navigations don't hijack the user's existing tabs. Made-with: Cursor * fix: address review feedback — cargo fmt, shared helper, Windows tests - Run cargo fmt on is_port_reachable() formatting - Extract duplicated auto-connect-with-fresh-tab logic into connect_auto_with_fresh_tab() helper used by both handle_launch() and auto_launch() - Add unit tests for Windows WSAECONNREFUSED (os error 10061) and WSAECONNRESET (os error 10054) in is_transient_error() --------- Co-authored-by: ctate <366502+ctate@users.noreply.github.com>
This commit is contained in:
@@ -792,6 +792,19 @@ pub async fn execute_command(cmd: &Value, state: &mut DaemonState) -> Value {
|
||||
// Auto-launch
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Connect to a running Chrome via auto-discovery and open a fresh tab so
|
||||
/// subsequent navigations don't hijack the user's existing tabs.
|
||||
async fn connect_auto_with_fresh_tab() -> Result<BrowserManager, String> {
|
||||
let mut mgr = BrowserManager::connect_auto().await?;
|
||||
mgr.tab_new(None).await?;
|
||||
let session_id = mgr.active_session_id()?.to_string();
|
||||
let _ = mgr
|
||||
.client
|
||||
.send_command("Page.bringToFront", None, Some(&session_id))
|
||||
.await;
|
||||
Ok(mgr)
|
||||
}
|
||||
|
||||
async fn auto_launch(state: &mut DaemonState) -> Result<(), String> {
|
||||
let options = launch_options_from_env();
|
||||
let engine = env::var("AGENT_BROWSER_ENGINE").ok();
|
||||
@@ -806,8 +819,7 @@ async fn auto_launch(state: &mut DaemonState) -> Result<(), String> {
|
||||
}
|
||||
|
||||
if env::var("AGENT_BROWSER_AUTO_CONNECT").is_ok() {
|
||||
let mgr = BrowserManager::connect_auto().await?;
|
||||
state.browser = Some(mgr);
|
||||
state.browser = Some(connect_auto_with_fresh_tab().await?);
|
||||
state.subscribe_to_browser_events();
|
||||
state.update_stream_client().await;
|
||||
try_auto_restore_state(state).await;
|
||||
@@ -971,7 +983,7 @@ async fn handle_launch(cmd: &Value, state: &mut DaemonState) -> Result<Value, St
|
||||
}
|
||||
|
||||
if auto_connect {
|
||||
state.browser = Some(BrowserManager::connect_auto().await?);
|
||||
state.browser = Some(connect_auto_with_fresh_tab().await?);
|
||||
state.subscribe_to_browser_events();
|
||||
state.update_stream_client().await;
|
||||
return Ok(json!({ "launched": true }));
|
||||
|
||||
@@ -453,9 +453,17 @@ pub async fn auto_connect_cdp() -> Result<String, String> {
|
||||
if let Ok(ws_url) = discover_cdp_url("127.0.0.1", port).await {
|
||||
return Ok(ws_url);
|
||||
}
|
||||
// M144+: direct WebSocket
|
||||
let ws_url = format!("ws://127.0.0.1:{}{}", port, ws_path);
|
||||
return Ok(ws_url);
|
||||
// M144+: direct WebSocket — verify the port is actually listening
|
||||
// before returning, otherwise a stale DevToolsActivePort file
|
||||
// (left behind after Chrome exits/crashes) produces a confusing
|
||||
// "connection refused" error instead of falling through.
|
||||
if is_port_reachable(port) {
|
||||
let ws_url = format!("ws://127.0.0.1:{}{}", port, ws_path);
|
||||
return Ok(ws_url);
|
||||
}
|
||||
// Port is dead — remove the stale file so future runs skip it.
|
||||
let stale = dir.join("DevToolsActivePort");
|
||||
let _ = std::fs::remove_file(&stale);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -469,6 +477,12 @@ pub async fn auto_connect_cdp() -> Result<String, String> {
|
||||
Err("No running Chrome instance found. Launch Chrome with --remote-debugging-port or use --cdp.".to_string())
|
||||
}
|
||||
|
||||
fn is_port_reachable(port: u16) -> bool {
|
||||
use std::net::TcpStream;
|
||||
let addr = format!("127.0.0.1:{}", port);
|
||||
TcpStream::connect_timeout(&addr.parse().unwrap(), Duration::from_millis(500)).is_ok()
|
||||
}
|
||||
|
||||
fn get_chrome_user_data_dirs() -> Vec<PathBuf> {
|
||||
let mut dirs = Vec::new();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user