From 3570bbd74c809f1977a2168869f2618bc1b84724 Mon Sep 17 00:00:00 2001 From: Bob Lee Date: Mon, 11 May 2026 10:01:39 +0800 Subject: [PATCH 1/2] fix: show infinity icon instead of original timeout when disabled When the user clicks the disable-timeout button, the denominator now shows an infinity icon instead of the original timeout value, making it clear that the timeout has been removed. Generated with BitFun Co-Authored-By: BitFun --- .../src/flow_chat/tool-cards/ToolTimeoutIndicator.scss | 5 +++++ src/web-ui/src/flow_chat/tool-cards/ToolTimeoutIndicator.tsx | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/web-ui/src/flow_chat/tool-cards/ToolTimeoutIndicator.scss b/src/web-ui/src/flow_chat/tool-cards/ToolTimeoutIndicator.scss index b549ea1f5..30a6b13fb 100644 --- a/src/web-ui/src/flow_chat/tool-cards/ToolTimeoutIndicator.scss +++ b/src/web-ui/src/flow_chat/tool-cards/ToolTimeoutIndicator.scss @@ -53,6 +53,11 @@ opacity: 0.5; } +.duration-timeout--infinity { + vertical-align: middle; + opacity: 0.8; +} + .duration-timeout--warning { color: var(--color-warning, #f59e0b); opacity: 1; diff --git a/src/web-ui/src/flow_chat/tool-cards/ToolTimeoutIndicator.tsx b/src/web-ui/src/flow_chat/tool-cards/ToolTimeoutIndicator.tsx index 308bf6aef..ab617145b 100644 --- a/src/web-ui/src/flow_chat/tool-cards/ToolTimeoutIndicator.tsx +++ b/src/web-ui/src/flow_chat/tool-cards/ToolTimeoutIndicator.tsx @@ -122,7 +122,7 @@ export const ToolTimeoutIndicator: React.FC = ({ className={`duration-timeout ${isTimeoutDisabled ? 'duration-timeout--disabled' : ''} ${isWarning ? 'duration-timeout--warning' : ''}`} > {isTimeoutDisabled - ? formatDurationLive(timeoutMs!) + ? : displayRemaining != null ? formatDurationLive(displayRemaining) : formatDurationLive(timeoutMs!)} From 8dd5d75fafbe25653fd2c5d203c182fd3b2725e3 Mon Sep 17 00:00:00 2001 From: Bob Lee Date: Mon, 11 May 2026 10:13:39 +0800 Subject: [PATCH 2/2] fix(browser-control): accurate tab count, real browser detection, hide selector when CDP connected - Only count CDP targets of type 'page' as tabs (ignore service workers, browser targets etc.) - Detect actual connected browser from CDP /json/version instead of user config - Add browser_kind_from_cdp_version() to parse browser identity from CDP version string - Hide browser selection dropdown when CDP is already connected; only show when not connected --- .../desktop/src/api/browser_control_api.rs | 27 +++++++++++-------- .../tools/browser_control/browser_launcher.rs | 21 +++++++++++++++ .../config/components/SessionConfig.tsx | 3 +++ 3 files changed, 40 insertions(+), 11 deletions(-) diff --git a/src/apps/desktop/src/api/browser_control_api.rs b/src/apps/desktop/src/api/browser_control_api.rs index d630887ec..1cf94635a 100644 --- a/src/apps/desktop/src/api/browser_control_api.rs +++ b/src/apps/desktop/src/api/browser_control_api.rs @@ -104,26 +104,31 @@ pub async fn browser_control_get_status( ) -> Result { let port = request.port; let available = BrowserLauncher::is_cdp_available(port).await; - let kind = selected_browser_kind().await?; - - let (version, page_count) = if available { - let ver = CdpClient::get_version(port) - .await - .ok() - .and_then(|v| v.browser); + let configured_kind = selected_browser_kind().await?; + + let (version, page_count, actual_kind) = if available { + let ver_info = CdpClient::get_version(port).await.ok(); + let ver = ver_info.as_ref().and_then(|v| v.browser.clone()); + // Identify the actual browser from CDP version response. + let kind = ver + .as_deref() + .and_then(|v| BrowserLauncher::browser_kind_from_cdp_version(v)) + .unwrap_or_else(|| configured_kind.clone()); + // Only count targets of type "page" (real browser tabs), + // not service workers, browser targets, etc. let pages = CdpClient::list_pages(port) .await .ok() - .map(|p| p.len()) + .map(|p| p.iter().filter(|t| t.page_type.as_deref() == Some("page")).count()) .unwrap_or(0); - (ver, pages) + (ver, pages, kind) } else { - (None, 0) + (None, 0, configured_kind) }; Ok(BrowserControlStatusResponse { cdp_available: available, - browser_kind: kind.to_string(), + browser_kind: actual_kind.to_string(), browser_version: version, port, page_count, diff --git a/src/crates/core/src/agentic/tools/browser_control/browser_launcher.rs b/src/crates/core/src/agentic/tools/browser_control/browser_launcher.rs index 2d1a7de73..ff3ab154b 100644 --- a/src/crates/core/src/agentic/tools/browser_control/browser_launcher.rs +++ b/src/crates/core/src/agentic/tools/browser_control/browser_launcher.rs @@ -254,6 +254,27 @@ impl BrowserLauncher { *cache = None; } + /// Parse a `BrowserKind` from the CDP `/json/version` "Browser" field. + /// The field typically looks like `"HeadlessChrome/130.0..."` or + /// `"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36"` + /// or `"Microsoft Edge/130.0..."`. + pub fn browser_kind_from_cdp_version(version_str: &str) -> Option { + let lower = version_str.to_ascii_lowercase(); + if lower.contains("edg") || lower.contains("edge") { + Some(BrowserKind::Edge) + } else if lower.contains("brave") { + Some(BrowserKind::Brave) + } else if lower.contains("chromium") { + Some(BrowserKind::Chromium) + } else if lower.contains("chrome") { + Some(BrowserKind::Chrome) + } else if lower.contains("arc") { + Some(BrowserKind::Arc) + } else { + None + } + } + pub fn browser_kind_from_config(value: &str) -> Option { match value.trim().to_ascii_lowercase().as_str() { "" | "default" => None, diff --git a/src/web-ui/src/infrastructure/config/components/SessionConfig.tsx b/src/web-ui/src/infrastructure/config/components/SessionConfig.tsx index 390c5aa82..7a367c6e0 100644 --- a/src/web-ui/src/infrastructure/config/components/SessionConfig.tsx +++ b/src/web-ui/src/infrastructure/config/components/SessionConfig.tsx @@ -1091,6 +1091,8 @@ const SessionSettingsPanels: React.FC = ({ variant } > {IS_TAURI_DESKTOP ? ( <> + {/* Only show browser selector when CDP is not connected */} + {!browserCdpAvailable && ( = ({ variant } /> + )}