mirror of
https://github.com/hanzoai/dev.git
synced 2026-10-25 03:23:33 +00:00
tui: Clear stale cached Chrome WS URL and retry via port discovery when WS connect fails (handles 404 after Chrome restart).\n\n- Prefer cached WS; on failure, clear WS cache (in-memory + disk), fallback to cached port or auto-scan, and retry once.\n- Emit clear success/failure background events; persist new connection on success.\n\nbrowser(page.rs): Inline rationale and comments for screenshot strategy to avoid flash and reduce timeouts.\n\n- Prefer from_surface(false) for visible pages; conservative retries.\n- Non-visible: fast 8×8 preflight probe (350ms) cached ~5s to choose path.\n- Final from_surface(true) fallback only as last resort.\n\nDocs: Remove external doc; comments now self-contained.
This commit is contained in:
+168
-29
@@ -18,7 +18,7 @@ use chromiumoxide::cdp::browser_protocol::page::CaptureScreenshotFormat;
|
||||
use chromiumoxide::cdp::browser_protocol::page::CaptureScreenshotParams;
|
||||
use chromiumoxide::page::Page as CdpPage;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use std::time::{Duration, Instant};
|
||||
use tokio::sync::RwLock;
|
||||
// Use Mutex for cursor state (New)
|
||||
use tokio::sync::Mutex;
|
||||
@@ -46,6 +46,15 @@ pub struct Page {
|
||||
current_url: Arc<RwLock<Option<String>>>,
|
||||
// Add cursor state tracking (New)
|
||||
cursor_state: Arc<Mutex<CursorState>>,
|
||||
// Screenshot path preflight cache:
|
||||
// - We strongly prefer compositor captures via from_surface(false) to avoid visible flashes in the
|
||||
// user's real Chrome window. However, that path can be flaky or unavailable when the window is not
|
||||
// visible/minimized. A tiny 8×8 probe (guarded by a ~350ms timeout) predicts viability and is cached
|
||||
// for ~5 seconds to avoid repeated probes while navigating.
|
||||
// - IMPORTANT: This cache and probe logic protect both UX (no flash on visible windows) and reliability
|
||||
// (preventing repeated long timeouts when minimized). If you change this, ensure visible windows never
|
||||
// start with from_surface(true), and keep a short/cheap probe for hidden/minimized states.
|
||||
preflight_cache: Arc<Mutex<Option<(Instant, bool)>>>,
|
||||
}
|
||||
|
||||
impl Page {
|
||||
@@ -63,6 +72,7 @@ impl Page {
|
||||
config,
|
||||
current_url: Arc::new(RwLock::new(None)),
|
||||
cursor_state: Arc::new(Mutex::new(initial_cursor)),
|
||||
preflight_cache: Arc::new(Mutex::new(None)),
|
||||
};
|
||||
|
||||
// Register a unified bootstrap (runs on every new document):
|
||||
@@ -345,13 +355,20 @@ impl Page {
|
||||
}
|
||||
|
||||
/// Helper function to capture screenshot with retry logic.
|
||||
/// First tries with from_surface(false) to avoid flashing.
|
||||
/// If that fails, retries with from_surface(true) for when window is not visible.
|
||||
/// Strategy summary (critical to UX and reliability):
|
||||
/// - Visible pages: Start with from_surface(false) (no-flash path). If it fails once, retry false quickly.
|
||||
/// Only as a last resort use from_surface(true), because it can flash a visible window.
|
||||
/// - Non-visible pages: Use a fast 8×8 preflight (false) to decide. If compositor is unavailable, start
|
||||
/// with from_surface(true) immediately (safe when not visible). Fallbacks stay conservative.
|
||||
/// - Final fallback: If two attempts with false fail even while visible, we try true once rather than
|
||||
/// failing entirely. This prevents chronic timeouts; the flash trade-off is acceptable as a last resort.
|
||||
/// Do not loosen these guarantees casually; they were tuned to balance reliability and no-flash UX.
|
||||
async fn capture_screenshot_with_retry(
|
||||
&self,
|
||||
params_builder: chromiumoxide::cdp::browser_protocol::page::CaptureScreenshotParamsBuilder,
|
||||
) -> Result<chromiumoxide::cdp::browser_protocol::page::CaptureScreenshotReturns> {
|
||||
// Determine page visibility once, to decide if from_surface(true) is safe/necessary
|
||||
// Determine page visibility once to decide if from_surface(true) is safe/necessary.
|
||||
// If this check fails, assume visible to avoid accidentally picking the flashing path.
|
||||
let is_visible = {
|
||||
let eval = self
|
||||
.cdp_page
|
||||
@@ -373,23 +390,76 @@ impl Page {
|
||||
}
|
||||
};
|
||||
|
||||
// First try with from_surface(false) to avoid flashing, with timeout
|
||||
let params = params_builder.clone().from_surface(false).build();
|
||||
let first_attempt = tokio::time::timeout(
|
||||
tokio::time::Duration::from_secs(8),
|
||||
self.cdp_page.execute(params),
|
||||
)
|
||||
.await;
|
||||
// Preflight probe (non-visible only):
|
||||
// - A very fast 8×8 clip via from_surface(false) predicts if the compositor path is currently viable.
|
||||
// - Only run when page is not visible (no flash risk) and cache the result for ~5s.
|
||||
// This avoids long timeouts on minimized windows without reintroducing flash for visible ones.
|
||||
let mut prefer_false = true;
|
||||
if !is_visible {
|
||||
let now = Instant::now();
|
||||
{
|
||||
let mut cache = self.preflight_cache.lock().await;
|
||||
if let Some((ts, ok)) = *cache {
|
||||
if now.duration_since(ts) < Duration::from_secs(5) {
|
||||
prefer_false = ok;
|
||||
} else {
|
||||
*cache = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if prefer_false {
|
||||
let cached = {
|
||||
let cache = self.preflight_cache.lock().await;
|
||||
cache.is_some()
|
||||
};
|
||||
if !cached {
|
||||
let probe_params = params_builder
|
||||
.clone()
|
||||
.from_surface(false)
|
||||
.capture_beyond_viewport(true)
|
||||
.clip(chromiumoxide::cdp::browser_protocol::page::Viewport {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
width: 8.0,
|
||||
height: 8.0,
|
||||
scale: 1.0,
|
||||
})
|
||||
.build();
|
||||
let probe = tokio::time::timeout(Duration::from_millis(350), self.cdp_page.execute(probe_params)).await;
|
||||
let ok = matches!(probe, Ok(Ok(_)));
|
||||
let mut cache = self.preflight_cache.lock().await;
|
||||
*cache = Some((Instant::now(), ok));
|
||||
prefer_false = ok;
|
||||
if !prefer_false {
|
||||
debug!("Preflight suggests compositor path unavailable; non-visible context will use from_surface(true)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// First attempt policy:
|
||||
// - Visible: Always start with from_surface(false) and a short timeout to minimize flash risk.
|
||||
// - Not visible: Use preflight outcome; allow from_surface(true) immediately when compositor is unavailable.
|
||||
let (first_params, first_timeout, first_is_false) = if is_visible {
|
||||
(params_builder.clone().from_surface(false).build(), Duration::from_secs(3), true)
|
||||
} else if prefer_false {
|
||||
(params_builder.clone().from_surface(false).build(), Duration::from_secs(6), true)
|
||||
} else {
|
||||
(params_builder.clone().from_surface(true).build(), Duration::from_secs(6), false)
|
||||
};
|
||||
let first_attempt = tokio::time::timeout(first_timeout, self.cdp_page.execute(first_params)).await;
|
||||
|
||||
match first_attempt {
|
||||
Ok(Ok(resp)) => Ok(resp.result),
|
||||
Ok(Err(e)) => {
|
||||
debug!(
|
||||
"Screenshot with from_surface(false) failed: {} (visible={})",
|
||||
e, is_visible
|
||||
"Screenshot first attempt failed (used_false={}): {} (visible={})",
|
||||
first_is_false, e, is_visible
|
||||
);
|
||||
if !is_visible {
|
||||
// Retry with from_surface(true) ONLY when not visible (prevents flash when focused)
|
||||
if !is_visible || !first_is_false {
|
||||
// Non-visible or already tried true path: retry with from_surface(true).
|
||||
// Safe for minimized/hidden windows and avoids repeated long timeouts.
|
||||
let retry_params = params_builder.from_surface(true).build();
|
||||
let retry_attempt = tokio::time::timeout(
|
||||
tokio::time::Duration::from_secs(8),
|
||||
@@ -405,7 +475,7 @@ impl Page {
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
// Visible: avoid from_surface(true). Brief wait and retry once with from_surface(false)
|
||||
// Visible: avoid from_surface(true) if at all possible. Brief wait and retry once with false.
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(120)).await;
|
||||
let retry_params = params_builder.clone().from_surface(false).build();
|
||||
let retry_attempt = tokio::time::timeout(
|
||||
@@ -415,20 +485,55 @@ impl Page {
|
||||
.await;
|
||||
match retry_attempt {
|
||||
Ok(Ok(resp)) => Ok(resp.result),
|
||||
Ok(Err(err2)) => Err(err2.into()),
|
||||
Err(_) => Err(BrowserError::ScreenshotError(
|
||||
"Screenshot timed out (from_surface=false, retried)".to_string(),
|
||||
)),
|
||||
Ok(Err(err2)) => {
|
||||
// Last resort for visible pages: try from_surface(true).
|
||||
// This can flash; we only do it after exhausting the safer path to prevent permanent failures.
|
||||
debug!(
|
||||
"Retry with from_surface(false) failed while visible; attempting from_surface(true) as fallback"
|
||||
);
|
||||
let final_params = params_builder.from_surface(true).build();
|
||||
let final_attempt = tokio::time::timeout(
|
||||
tokio::time::Duration::from_secs(4),
|
||||
self.cdp_page.execute(final_params),
|
||||
)
|
||||
.await;
|
||||
match final_attempt {
|
||||
Ok(Ok(resp)) => Ok(resp.result),
|
||||
Ok(Err(e3)) => Err(e3.into()),
|
||||
Err(_) => Err(BrowserError::ScreenshotError(
|
||||
"Screenshot timed out (final from_surface=true fallback)".to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
// Timeout on second false attempt; try true once as last resort (may flash)
|
||||
debug!(
|
||||
"Retry with from_surface(false) timed out while visible; attempting from_surface(true) as fallback"
|
||||
);
|
||||
let final_params = params_builder.from_surface(true).build();
|
||||
let final_attempt = tokio::time::timeout(
|
||||
tokio::time::Duration::from_secs(4),
|
||||
self.cdp_page.execute(final_params),
|
||||
)
|
||||
.await;
|
||||
match final_attempt {
|
||||
Ok(Ok(resp)) => Ok(resp.result),
|
||||
Ok(Err(e3)) => Err(e3.into()),
|
||||
Err(_) => Err(BrowserError::ScreenshotError(
|
||||
"Screenshot timed out after retries (from_surface=true fallback)".to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
debug!(
|
||||
"Screenshot with from_surface(false) timed out (visible={})",
|
||||
is_visible
|
||||
"Screenshot first attempt timed out (used_false={}, visible={})",
|
||||
first_is_false, is_visible
|
||||
);
|
||||
if !is_visible {
|
||||
// Not visible: try from_surface(true)
|
||||
if !is_visible || !first_is_false {
|
||||
// Not visible (safe) or already tried false: try from_surface(true)
|
||||
let retry_params = params_builder.from_surface(true).build();
|
||||
let retry_attempt = tokio::time::timeout(
|
||||
tokio::time::Duration::from_secs(8),
|
||||
@@ -443,7 +548,7 @@ impl Page {
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
// Visible: do not use from_surface(true); retry quickly with false
|
||||
// Visible: avoid from_surface(true) if possible; retry quickly with false
|
||||
let retry_params = params_builder.clone().from_surface(false).build();
|
||||
let retry_attempt = tokio::time::timeout(
|
||||
tokio::time::Duration::from_secs(4),
|
||||
@@ -452,10 +557,44 @@ impl Page {
|
||||
.await;
|
||||
match retry_attempt {
|
||||
Ok(Ok(resp)) => Ok(resp.result),
|
||||
Ok(Err(e)) => Err(e.into()),
|
||||
Err(_) => Err(BrowserError::ScreenshotError(
|
||||
"Screenshot timed out after retries (from_surface=false only)".to_string(),
|
||||
)),
|
||||
Ok(Err(e)) => {
|
||||
// Final fallback with from_surface(true) even though visible (see doc rationale)
|
||||
debug!(
|
||||
"Second attempt with from_surface(false) failed while visible; attempting final from_surface(true)"
|
||||
);
|
||||
let final_params = params_builder.from_surface(true).build();
|
||||
let final_attempt = tokio::time::timeout(
|
||||
tokio::time::Duration::from_secs(4),
|
||||
self.cdp_page.execute(final_params),
|
||||
)
|
||||
.await;
|
||||
match final_attempt {
|
||||
Ok(Ok(resp)) => Ok(resp.result),
|
||||
Ok(Err(e3)) => Err(e3.into()),
|
||||
Err(_) => Err(BrowserError::ScreenshotError(
|
||||
"Screenshot timed out after retries (final from_surface=true)".to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
// Timeout on second false attempt, try true once (final)
|
||||
debug!(
|
||||
"Second attempt with from_surface(false) timed out while visible; attempting final from_surface(true)"
|
||||
);
|
||||
let final_params = params_builder.from_surface(true).build();
|
||||
let final_attempt = tokio::time::timeout(
|
||||
tokio::time::Duration::from_secs(4),
|
||||
self.cdp_page.execute(final_params),
|
||||
)
|
||||
.await;
|
||||
match final_attempt {
|
||||
Ok(Ok(resp)) => Ok(resp.result),
|
||||
Ok(Err(e3)) => Err(e3.into()),
|
||||
Err(_) => Err(BrowserError::ScreenshotError(
|
||||
"Screenshot timed out after retries (from_surface=true fallback)".to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+195
-10
@@ -3204,6 +3204,9 @@ impl ChatWidget<'_> {
|
||||
browser_manager.set_enabled_sync(true);
|
||||
|
||||
// Configure for CDP connection (prefer cached ws/port on auto-detect)
|
||||
// Track whether we're attempting via cached WS and retain a cached port for fallback.
|
||||
let mut attempted_via_cached_ws = false;
|
||||
let mut cached_port_for_fallback: Option<u16> = None;
|
||||
{
|
||||
let mut config = browser_manager.config.write().await;
|
||||
config.headless = false;
|
||||
@@ -3219,11 +3222,13 @@ impl ChatWidget<'_> {
|
||||
Some(v) => v,
|
||||
None => codex_browser::global::get_last_connection().await,
|
||||
};
|
||||
cached_port_for_fallback = cached_port;
|
||||
if let Some(ws) = cached_ws {
|
||||
tracing::info!("[cdp] using cached Chrome WS endpoint");
|
||||
attempted_via_cached_ws = true;
|
||||
config.connect_ws = Some(ws);
|
||||
config.connect_port = None;
|
||||
} else if let Some(p) = cached_port {
|
||||
} else if let Some(p) = cached_port_for_fallback {
|
||||
tracing::info!("[cdp] using cached Chrome debug port: {}", p);
|
||||
config.connect_ws = None;
|
||||
config.connect_port = Some(p);
|
||||
@@ -3404,15 +3409,195 @@ impl ChatWidget<'_> {
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("[cdp] connect_to_chrome_only failed immediately: {}", e);
|
||||
use codex_core::protocol::{BackgroundEventEvent, Event, EventMsg};
|
||||
let _ = app_event_tx.send(AppEvent::CodexEvent(Event {
|
||||
id: uuid::Uuid::new_v4().to_string(),
|
||||
msg: EventMsg::BackgroundEvent(BackgroundEventEvent {
|
||||
message: format!("❌ Failed to connect to Chrome: {}", e),
|
||||
}),
|
||||
}));
|
||||
return;
|
||||
let err_msg = format!("{}", e);
|
||||
// If we attempted via a cached WS, clear it and fallback to port-based discovery once.
|
||||
if attempted_via_cached_ws {
|
||||
tracing::warn!("[cdp] cached WS connect failed: {} — clearing WS cache and retrying via port discovery", err_msg);
|
||||
let port_to_keep = cached_port_for_fallback;
|
||||
// Clear WS in-memory and on-disk
|
||||
codex_browser::global::set_last_connection(port_to_keep, None).await;
|
||||
let _ = write_cached_connection(port_to_keep, None).await;
|
||||
|
||||
// Reconfigure to use port (prefer cached port, else auto-detect)
|
||||
{
|
||||
let mut cfg = browser_manager.config.write().await;
|
||||
cfg.connect_ws = None;
|
||||
cfg.connect_port = Some(port_to_keep.unwrap_or(0));
|
||||
}
|
||||
|
||||
tracing::info!("[cdp] retrying connect via port discovery after WS failure…");
|
||||
let retry_deadline = tokio::time::Duration::from_secs(20);
|
||||
let retry = tokio::time::timeout(retry_deadline, browser_manager.connect_to_chrome_only()).await;
|
||||
match retry {
|
||||
Ok(Ok(_)) => {
|
||||
tracing::info!("[cdp] Fallback connect succeeded after clearing cached WS");
|
||||
// Emit success event and set up callbacks, mirroring the success path above
|
||||
let (detected_port, detected_ws) = codex_browser::global::get_last_connection().await;
|
||||
let mut port_num: Option<u16> = detected_port;
|
||||
if port_num.is_none() {
|
||||
if let Some(ws) = &detected_ws {
|
||||
if let Some(after_scheme) = ws.split("//").nth(1) {
|
||||
if let Some(hostport) = after_scheme.split('/').next() {
|
||||
if let Some(pstr) = hostport.split(':').nth(1) {
|
||||
if let Ok(p) = pstr.parse::<u16>() { port_num = Some(p); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let current_url = browser_manager.get_current_url().await;
|
||||
let success_msg = match (port_num, current_url) {
|
||||
(Some(p), Some(url)) if !url.is_empty() => {
|
||||
format!("✅ Connected to Chrome via CDP (port {}) to {}", p, url)
|
||||
}
|
||||
(Some(p), _) => format!("✅ Connected to Chrome via CDP (port {})", p),
|
||||
(None, Some(url)) if !url.is_empty() => {
|
||||
format!("✅ Connected to Chrome via CDP to {}", url)
|
||||
}
|
||||
_ => "✅ Connected to Chrome via CDP".to_string(),
|
||||
};
|
||||
use codex_core::protocol::{BackgroundEventEvent, Event, EventMsg};
|
||||
let _ = app_event_tx.send(AppEvent::CodexEvent(Event {
|
||||
id: uuid::Uuid::new_v4().to_string(),
|
||||
msg: EventMsg::BackgroundEvent(BackgroundEventEvent { message: success_msg }),
|
||||
}));
|
||||
|
||||
// Persist last connection cache
|
||||
tokio::spawn(async move {
|
||||
let (p, ws) = codex_browser::global::get_last_connection().await;
|
||||
let _ = write_cached_connection(p, ws).await;
|
||||
});
|
||||
|
||||
// Navigation callback
|
||||
let latest_screenshot_callback = latest_screenshot.clone();
|
||||
let app_event_tx_callback = app_event_tx.clone();
|
||||
browser_manager
|
||||
.set_navigation_callback(move |url| {
|
||||
tracing::info!("CDP Navigation callback triggered for URL: {}", url);
|
||||
let latest_screenshot_inner = latest_screenshot_callback.clone();
|
||||
let app_event_tx_inner = app_event_tx_callback.clone();
|
||||
let url_inner = url.clone();
|
||||
tokio::spawn(async move {
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(250)).await;
|
||||
let browser_manager_inner = ChatWidget::get_browser_manager().await;
|
||||
let mut attempt = 0;
|
||||
let max_attempts = 2;
|
||||
loop {
|
||||
attempt += 1;
|
||||
match browser_manager_inner.capture_screenshot_with_url().await {
|
||||
Ok((paths, _)) => {
|
||||
if let Some(first_path) = paths.first() {
|
||||
tracing::info!("[cdp] auto-captured screenshot: {}", first_path.display());
|
||||
if let Ok(mut latest) = latest_screenshot_inner.lock() {
|
||||
*latest = Some((first_path.clone(), url_inner.clone()));
|
||||
}
|
||||
use codex_core::protocol::{BrowserScreenshotUpdateEvent, Event, EventMsg};
|
||||
let _ = app_event_tx_inner.send(AppEvent::CodexEvent(Event {
|
||||
id: uuid::Uuid::new_v4().to_string(),
|
||||
msg: EventMsg::BrowserScreenshotUpdate(BrowserScreenshotUpdateEvent {
|
||||
screenshot_path: first_path.clone(),
|
||||
url: url_inner,
|
||||
}),
|
||||
}));
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!("[cdp] auto-capture failed (attempt {}): {}", attempt, e);
|
||||
if attempt >= max_attempts { break; }
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(250)).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
.await;
|
||||
// Set as global manager like success path
|
||||
codex_browser::global::set_global_browser_manager(browser_manager.clone()).await;
|
||||
|
||||
// Initial screenshot in background (best-effort)
|
||||
{
|
||||
let latest_screenshot_bg = latest_screenshot.clone();
|
||||
let app_event_tx_bg = app_event_tx.clone();
|
||||
tokio::spawn(async move {
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(250)).await;
|
||||
let browser_manager = ChatWidget::get_browser_manager().await;
|
||||
let mut attempt = 0;
|
||||
let max_attempts = 2;
|
||||
loop {
|
||||
attempt += 1;
|
||||
match browser_manager.capture_screenshot_with_url().await {
|
||||
Ok((paths, url)) => {
|
||||
if let Some(first_path) = paths.first() {
|
||||
tracing::info!("Initial CDP screenshot captured: {}", first_path.display());
|
||||
if let Ok(mut latest) = latest_screenshot_bg.lock() {
|
||||
*latest = Some((
|
||||
first_path.clone(),
|
||||
url.clone().unwrap_or_else(|| "Chrome".to_string()),
|
||||
));
|
||||
}
|
||||
use codex_core::protocol::{BrowserScreenshotUpdateEvent, Event, EventMsg};
|
||||
let _ = app_event_tx_bg.send(AppEvent::CodexEvent(Event {
|
||||
id: uuid::Uuid::new_v4().to_string(),
|
||||
msg: EventMsg::BrowserScreenshotUpdate(BrowserScreenshotUpdateEvent {
|
||||
screenshot_path: first_path.clone(),
|
||||
url: url.unwrap_or_else(|| "Chrome".to_string()),
|
||||
}),
|
||||
}));
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!("Failed to capture initial CDP screenshot (attempt {}): {}", attempt, e);
|
||||
if attempt >= max_attempts { break; }
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(250)).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
Ok(Err(e2)) => {
|
||||
tracing::error!("[cdp] Fallback connect failed: {}", e2);
|
||||
use codex_core::protocol::{BackgroundEventEvent, Event, EventMsg};
|
||||
let _ = app_event_tx.send(AppEvent::CodexEvent(Event {
|
||||
id: uuid::Uuid::new_v4().to_string(),
|
||||
msg: EventMsg::BackgroundEvent(BackgroundEventEvent {
|
||||
message: format!(
|
||||
"❌ Failed to connect to Chrome after WS fallback: {} (original: {})",
|
||||
e2, err_msg
|
||||
),
|
||||
}),
|
||||
}));
|
||||
return;
|
||||
}
|
||||
Err(_) => {
|
||||
tracing::error!("[cdp] Fallback connect timed out after {:?}", retry_deadline);
|
||||
use codex_core::protocol::{BackgroundEventEvent, Event, EventMsg};
|
||||
let _ = app_event_tx.send(AppEvent::CodexEvent(Event {
|
||||
id: uuid::Uuid::new_v4().to_string(),
|
||||
msg: EventMsg::BackgroundEvent(BackgroundEventEvent {
|
||||
message: format!(
|
||||
"❌ CDP connect timed out after {}s during fallback. Ensure Chrome is running with --remote-debugging-port and /json/version is reachable",
|
||||
retry_deadline.as_secs()
|
||||
),
|
||||
}),
|
||||
}));
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tracing::error!("[cdp] connect_to_chrome_only failed immediately: {}", err_msg);
|
||||
use codex_core::protocol::{BackgroundEventEvent, Event, EventMsg};
|
||||
let _ = app_event_tx.send(AppEvent::CodexEvent(Event {
|
||||
id: uuid::Uuid::new_v4().to_string(),
|
||||
msg: EventMsg::BackgroundEvent(BackgroundEventEvent {
|
||||
message: format!("❌ Failed to connect to Chrome: {}", err_msg),
|
||||
}),
|
||||
}));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user