diff --git a/docs/config-reference.md b/docs/config-reference.md index 3d0738438..77758fd01 100644 --- a/docs/config-reference.md +++ b/docs/config-reference.md @@ -271,7 +271,7 @@ Notes: | Key | Default | Purpose | |---|---|---| -| `enabled` | `false` | Enable `browser_open` tool (opens URLs without scraping) | +| `enabled` | `false` | Enable `browser_open` tool (opens URLs in the system browser without scraping) | | `allowed_domains` | `[]` | Allowed domains for `browser_open` (exact/subdomain match, or `"*"` for all public domains) | | `session_name` | unset | Browser session name (for agent-browser automation) | | `backend` | `agent_browser` | Browser automation backend: `"agent_browser"`, `"rust_native"`, `"computer_use"`, or `"auto"` | diff --git a/docs/i18n/vi/config-reference.md b/docs/i18n/vi/config-reference.md index 8a054b0b8..3b1b6a14a 100644 --- a/docs/i18n/vi/config-reference.md +++ b/docs/i18n/vi/config-reference.md @@ -210,7 +210,7 @@ Lưu ý: | Khóa | Mặc định | Mục đích | |---|---|---| -| `enabled` | `false` | Bật tool `browser_open` (mở URL, không thu thập dữ liệu) | +| `enabled` | `false` | Bật tool `browser_open` (mở URL trong trình duyệt mặc định hệ thống, không thu thập dữ liệu) | | `allowed_domains` | `[]` | Tên miền cho phép cho `browser_open` (khớp chính xác hoặc subdomain) | | `session_name` | chưa đặt | Tên phiên trình duyệt (cho tự động hóa agent-browser) | | `backend` | `agent_browser` | Backend tự động hóa: `"agent_browser"`, `"rust_native"`, `"computer_use"` hoặc `"auto"` | diff --git a/docs/vi/config-reference.md b/docs/vi/config-reference.md index 8a054b0b8..3b1b6a14a 100644 --- a/docs/vi/config-reference.md +++ b/docs/vi/config-reference.md @@ -210,7 +210,7 @@ Lưu ý: | Khóa | Mặc định | Mục đích | |---|---|---| -| `enabled` | `false` | Bật tool `browser_open` (mở URL, không thu thập dữ liệu) | +| `enabled` | `false` | Bật tool `browser_open` (mở URL trong trình duyệt mặc định hệ thống, không thu thập dữ liệu) | | `allowed_domains` | `[]` | Tên miền cho phép cho `browser_open` (khớp chính xác hoặc subdomain) | | `session_name` | chưa đặt | Tên phiên trình duyệt (cho tự động hóa agent-browser) | | `backend` | `agent_browser` | Backend tự động hóa: `"agent_browser"`, `"rust_native"`, `"computer_use"` hoặc `"auto"` | diff --git a/src/agent/loop_.rs b/src/agent/loop_.rs index 5452ef13f..07e0bfe24 100644 --- a/src/agent/loop_.rs +++ b/src/agent/loop_.rs @@ -2886,7 +2886,7 @@ pub async fn run( if config.browser.enabled { tool_descs.push(( "browser_open", - "Open approved HTTPS URLs in Brave Browser (allowlist-only, no scraping)", + "Open approved HTTPS URLs in system browser (allowlist-only, no scraping)", )); } if config.composio.enabled { diff --git a/src/channels/mod.rs b/src/channels/mod.rs index e2970d3ae..c0537d607 100644 --- a/src/channels/mod.rs +++ b/src/channels/mod.rs @@ -3113,7 +3113,7 @@ pub async fn start_channels(config: Config) -> Result<()> { if config.browser.enabled { tool_descs.push(( "browser_open", - "Open approved HTTPS URLs in Brave Browser (allowlist-only, no scraping)", + "Open approved HTTPS URLs in system browser (allowlist-only, no scraping)", )); } if config.composio.enabled { diff --git a/src/config/schema.rs b/src/config/schema.rs index 9d37637f3..e4f71eb56 100644 --- a/src/config/schema.rs +++ b/src/config/schema.rs @@ -931,7 +931,7 @@ impl Default for BrowserComputerUseConfig { /// Controls the `browser_open` tool and browser automation backends. #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct BrowserConfig { - /// Enable `browser_open` tool (opens URLs in Brave without scraping) + /// Enable `browser_open` tool (opens URLs in the system browser without scraping) #[serde(default)] pub enabled: bool, /// Allowed domains for `browser_open` (exact or subdomain match) diff --git a/src/tools/browser_open.rs b/src/tools/browser_open.rs index 9b44a4ae3..2c31fa1c6 100644 --- a/src/tools/browser_open.rs +++ b/src/tools/browser_open.rs @@ -4,7 +4,7 @@ use async_trait::async_trait; use serde_json::json; use std::sync::Arc; -/// Open approved HTTPS URLs in Brave Browser (no scraping, no DOM automation). +/// Open approved HTTPS URLs in the system default browser (no scraping, no DOM automation). pub struct BrowserOpenTool { security: Arc, allowed_domains: Vec, @@ -60,7 +60,7 @@ impl Tool for BrowserOpenTool { } fn description(&self) -> &str { - "Open an approved HTTPS URL in Brave Browser. Security constraints: allowlist-only domains, no local/private hosts, no scraping." + "Open an approved HTTPS URL in the system browser. Security constraints: allowlist-only domains, no local/private hosts, no scraping." } fn parameters_schema(&self) -> serde_json::Value { @@ -69,7 +69,7 @@ impl Tool for BrowserOpenTool { "properties": { "url": { "type": "string", - "description": "HTTPS URL to open in Brave Browser" + "description": "HTTPS URL to open in the system browser" } }, "required": ["url"] @@ -109,72 +109,113 @@ impl Tool for BrowserOpenTool { } }; - match open_in_brave(&url).await { + match open_in_system_browser(&url).await { Ok(()) => Ok(ToolResult { success: true, - output: format!("Opened in Brave: {url}"), + output: format!("Opened in system browser: {url}"), error: None, }), Err(e) => Ok(ToolResult { success: false, output: String::new(), - error: Some(format!("Failed to open Brave Browser: {e}")), + error: Some(format!("Failed to open system browser: {e}")), }), } } } -async fn open_in_brave(url: &str) -> anyhow::Result<()> { +async fn open_in_system_browser(url: &str) -> anyhow::Result<()> { #[cfg(target_os = "macos")] { + let primary_error = match tokio::process::Command::new("open").arg(url).status().await { + Ok(status) if status.success() => return Ok(()), + Ok(status) => format!("open exited with status {status}"), + Err(error) => format!("open not runnable: {error}"), + }; + + // TODO(compat): remove Brave fallback after default-browser launch has been stable across macOS environments. + let mut brave_error = String::new(); for app in ["Brave Browser", "Brave"] { - let status = tokio::process::Command::new("open") + match tokio::process::Command::new("open") .arg("-a") .arg(app) .arg(url) .status() - .await; - - if let Ok(s) = status { - if s.success() { - return Ok(()); + .await + { + Ok(status) if status.success() => return Ok(()), + Ok(status) => { + brave_error = format!("open -a '{app}' exited with status {status}"); + } + Err(error) => { + brave_error = format!("open -a '{app}' not runnable: {error}"); } } } + anyhow::bail!( - "Brave Browser was not found (tried macOS app names 'Brave Browser' and 'Brave')" + "Failed to open URL with default browser launcher: {primary_error}. Brave compatibility fallback also failed: {brave_error}" ); } #[cfg(target_os = "linux")] { let mut last_error = String::new(); - for cmd in ["brave-browser", "brave"] { - match tokio::process::Command::new(cmd).arg(url).status().await { + for cmd in [ + "xdg-open", + "gio", + "sensible-browser", + "brave-browser", + "brave", + ] { + let mut command = tokio::process::Command::new(cmd); + if cmd == "gio" { + command.arg("open"); + } + command.arg(url); + match command.status().await { Ok(status) if status.success() => return Ok(()), Ok(status) => { last_error = format!("{cmd} exited with status {status}"); } - Err(e) => { - last_error = format!("{cmd} not runnable: {e}"); + Err(error) => { + last_error = format!("{cmd} not runnable: {error}"); } } } - anyhow::bail!("{last_error}"); + + // TODO(compat): remove Brave fallback commands (brave-browser/brave) once default launcher coverage is validated. + anyhow::bail!( + "Failed to open URL with default browser launchers; Brave compatibility fallback also failed. Last error: {last_error}" + ); } #[cfg(target_os = "windows")] { - let status = tokio::process::Command::new("cmd") + let primary_error = match tokio::process::Command::new("cmd") + .args(["/C", "start", "", url]) + .status() + .await + { + Ok(status) if status.success() => return Ok(()), + Ok(status) => format!("cmd start default-browser exited with status {status}"), + Err(error) => format!("cmd start default-browser not runnable: {error}"), + }; + + // TODO(compat): remove Brave fallback after default-browser launch has been stable across Windows environments. + let brave_error = match tokio::process::Command::new("cmd") .args(["/C", "start", "", "brave", url]) .status() - .await?; + .await + { + Ok(status) if status.success() => return Ok(()), + Ok(status) => format!("cmd start brave exited with status {status}"), + Err(error) => format!("cmd start brave not runnable: {error}"), + }; - if status.success() { - return Ok(()); - } - - anyhow::bail!("cmd start brave exited with status {status}"); + anyhow::bail!( + "Failed to open URL with default browser launcher: {primary_error}. Brave compatibility fallback also failed: {brave_error}" + ); } #[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]