fix(cdp): 自动拉起 9333 专用浏览器

This commit is contained in:
leeguooooo
2026-03-09 17:45:13 +09:00
parent b3ed4b63be
commit 8eee9310a4
11 changed files with 249 additions and 100 deletions
+4 -25
View File
@@ -623,8 +623,9 @@ fn main() {
}
// Project policy: when no explicit connection mode is provided,
// commands should attach to an existing browser.
// Try CDP :9333 first, then fall back to auto-connect discovery.
// commands should use the dedicated automation browser on localhost:9333.
// If 9333 is unavailable, the native daemon auto-starts a managed Chrome
// instance with a non-default profile and retries the CDP connection.
if can_try_default_cdp {
let mut launch_cmd = json!({
"id": gen_id(),
@@ -645,31 +646,9 @@ fn main() {
if let Ok(resp) = send_command(launch_cmd, &flags.session) {
attached_to_existing_browser = resp.success;
}
if !attached_to_existing_browser {
let mut auto_connect_cmd = json!({
"id": gen_id(),
"action": "launch",
"autoConnect": true
});
if let Some(ref cs) = flags.color_scheme {
auto_connect_cmd["colorScheme"] = json!(cs);
}
if let Some(ref tg) = flags.tab_group {
auto_connect_cmd["tabGroup"] = json!(tg);
}
if let Some(ref plugin_id) = flags.tab_group_plugin_id {
auto_connect_cmd["tabGroupPluginId"] = json!(plugin_id);
}
if let Ok(resp) = send_command(auto_connect_cmd, &flags.session) {
attached_to_existing_browser = resp.success;
}
}
}
if can_try_default_cdp && !attached_to_existing_browser {
let msg = "Project policy requires using your existing browser. Could not connect to CDP at localhost:9333 and auto-discovery also failed. Start Chrome with remote debugging (for example, --remote-debugging-port=9333), or pass --cdp <port|url>.";
let msg = "Project policy requires using the dedicated automation browser on localhost:9333. Could not connect to or auto-start the managed Chrome profile. Start Chrome with --remote-debugging-port=9333 and a non-default --user-data-dir, or pass --cdp / --auto-connect explicitly.";
if flags.json {
println!(r#"{{"success":false,"error":"{}"}}"#, msg);
} else {
+19 -2
View File
@@ -4,7 +4,7 @@ use tokio::sync::broadcast;
use super::auth;
use super::browser::{BrowserManager, WaitUntil};
use super::cdp::chrome::LaunchOptions;
use super::cdp::chrome::{LaunchOptions, MANAGED_CDP_PORT};
use super::cdp::types::{
AttachToTargetParams, AttachToTargetResult, CdpEvent, ConsoleApiCalledEvent,
CreateTargetResult, ExceptionThrownEvent, TargetCreatedEvent, TargetDestroyedEvent,
@@ -790,6 +790,7 @@ fn launch_options_from_env() -> LaunchOptions {
.unwrap_or(false),
color_scheme: env::var("AGENT_BROWSER_COLOR_SCHEME").ok(),
download_path: env::var("AGENT_BROWSER_DOWNLOAD_PATH").ok(),
remote_debugging_port: None,
}
}
@@ -898,7 +899,22 @@ async fn handle_launch(cmd: &Value, state: &mut DaemonState) -> Result<Value, St
}
if let Some(port) = cdp_port {
state.browser = Some(BrowserManager::connect_cdp(&port.to_string()).await?);
let headed = !headless;
let port_u16 = u16::try_from(port).map_err(|_| format!("Invalid CDP port: {}", port))?;
let browser = match BrowserManager::connect_cdp(&port.to_string()).await {
Ok(browser) => browser,
Err(err) if port_u16 == MANAGED_CDP_PORT => {
if std::env::var("AGENT_BROWSER_DEBUG").as_deref() == Ok("1") {
eprintln!(
"[DEBUG] Preferred CDP port {} unavailable ({}), launching managed Chrome profile",
MANAGED_CDP_PORT, err
);
}
BrowserManager::launch_managed_cdp(executable_path.clone(), headed).await?
}
Err(err) => return Err(err),
};
state.browser = Some(browser);
state.subscribe_to_browser_events();
return Ok(json!({ "launched": true }));
}
@@ -990,6 +1006,7 @@ async fn handle_launch(cmd: &Value, state: &mut DaemonState) -> Result<Value, St
.get("downloadPath")
.and_then(|v| v.as_str())
.map(String::from),
remote_debugging_port: None,
};
if let Some(ref domains) = cmd
+30 -2
View File
@@ -5,7 +5,8 @@ use tokio::sync::Mutex;
use tokio::time::{timeout, Duration};
use super::cdp::chrome::{
auto_connect_cdp, discover_cdp_url, launch_chrome, ChromeProcess, LaunchOptions,
auto_connect_cdp, discover_cdp_url, launch_chrome, launch_managed_chrome, ChromeProcess,
LaunchOptions,
};
use super::cdp::client::CdpClient;
use super::cdp::lightpanda::{launch_lightpanda, LightpandaLaunchOptions, LightpandaProcess};
@@ -144,6 +145,7 @@ pub enum BrowserProcess {
pub struct BrowserManager {
pub client: CdpClient,
browser_process: Option<BrowserProcess>,
cdp_connection: bool,
pages: Vec<PageInfo>,
active_page_index: usize,
default_timeout_ms: u64,
@@ -202,6 +204,7 @@ impl BrowserManager {
let mut manager = Self {
client,
browser_process: Some(process),
cdp_connection: false,
pages: Vec::new(),
active_page_index: 0,
default_timeout_ms: 25_000,
@@ -264,6 +267,31 @@ impl BrowserManager {
let mut manager = Self {
client,
browser_process: None,
cdp_connection: true,
pages: Vec::new(),
active_page_index: 0,
default_timeout_ms: 10_000,
};
manager.discover_and_attach_targets().await?;
Ok(manager)
}
pub async fn launch_managed_cdp(
executable_path: Option<String>,
headed: bool,
) -> Result<Self, String> {
let process =
tokio::task::spawn_blocking(move || launch_managed_chrome(executable_path, headed))
.await
.map_err(|e| format!("Managed Chrome launch task failed: {}", e))??;
let ws_url = process.ws_url.clone();
let client = CdpClient::connect(&ws_url).await?;
let mut manager = Self {
client,
browser_process: Some(BrowserProcess::Chrome(process)),
cdp_connection: true,
pages: Vec::new(),
active_page_index: 0,
default_timeout_ms: 10_000,
@@ -643,7 +671,7 @@ impl BrowserManager {
/// Returns true if this manager was connected via CDP (as opposed to local launch).
pub fn is_cdp_connection(&self) -> bool {
self.browser_process.is_none()
self.cdp_connection
}
/// Ensures the browser has at least one page. If `pages` is empty, creates a new
+46 -1
View File
@@ -73,6 +73,7 @@ pub struct LaunchOptions {
pub ignore_https_errors: bool,
pub color_scheme: Option<String>,
pub download_path: Option<String>,
pub remote_debugging_port: Option<u16>,
}
impl Default for LaunchOptions {
@@ -91,6 +92,7 @@ impl Default for LaunchOptions {
ignore_https_errors: false,
color_scheme: None,
download_path: None,
remote_debugging_port: None,
}
}
}
@@ -101,8 +103,10 @@ struct ChromeArgs {
}
fn build_chrome_args(options: &LaunchOptions) -> Result<ChromeArgs, String> {
let remote_debugging_port = options.remote_debugging_port.unwrap_or(0);
let mut args = vec![
"--remote-debugging-port=0".to_string(),
format!("--remote-debugging-port={}", remote_debugging_port),
"--remote-debugging-address=127.0.0.1".to_string(),
"--no-first-run".to_string(),
"--no-default-browser-check".to_string(),
"--disable-background-networking".to_string(),
@@ -186,6 +190,47 @@ fn build_chrome_args(options: &LaunchOptions) -> Result<ChromeArgs, String> {
})
}
pub const MANAGED_CDP_PORT: u16 = 9333;
pub fn managed_cdp_profile_dir() -> PathBuf {
dirs::home_dir()
.unwrap_or_else(|| std::env::temp_dir())
.join(".agent-browser")
.join("chrome-bot-profile")
}
fn cleanup_managed_profile_locks(profile_dir: &Path) {
let _ = std::fs::remove_file(profile_dir.join("DevToolsActivePort"));
if let Ok(entries) = std::fs::read_dir(profile_dir) {
for entry in entries.flatten() {
let name = entry.file_name();
if name.to_string_lossy().starts_with("Singleton") {
let _ = std::fs::remove_file(entry.path());
}
}
}
}
pub fn launch_managed_chrome(
executable_path: Option<String>,
headed: bool,
) -> Result<ChromeProcess, String> {
let profile_dir = managed_cdp_profile_dir();
std::fs::create_dir_all(&profile_dir)
.map_err(|e| format!("Failed to create managed Chrome profile dir: {}", e))?;
cleanup_managed_profile_locks(&profile_dir);
let options = LaunchOptions {
headless: !headed,
executable_path,
profile: Some(profile_dir.to_string_lossy().to_string()),
remote_debugging_port: Some(MANAGED_CDP_PORT),
..Default::default()
};
launch_chrome(&options)
}
pub fn launch_chrome(options: &LaunchOptions) -> Result<ChromeProcess, String> {
let chrome_path = match &options.executable_path {
Some(p) => PathBuf::from(p),
+3 -2
View File
@@ -2461,7 +2461,7 @@ Options:
--headed Show browser window (not headless) (or AGENT_BROWSER_HEADED=1/true)
--cdp <port> Connect via CDP (Chrome DevTools Protocol)
--auto-connect Auto-discover and connect to running Chrome
Project default: try localhost:9333 first, then auto-discovery (no managed local-launch fallback)
Explicit existing-browser mode; may trigger Chrome permission prompts
--color-scheme <scheme> Color scheme: dark, light, no-preference (or AGENT_BROWSER_COLOR_SCHEME)
--download-path <path> Default download directory (or AGENT_BROWSER_DOWNLOAD_PATH)
--tab-group <name> Base title for agent tab groups (CDP plugin mode; silent no-op if plugin unavailable)
@@ -2490,7 +2490,8 @@ Policy:
--profile / AGENT_BROWSER_PROFILE are forbidden
--channel / AGENT_BROWSER_CHANNEL are forbidden
Daemon auto-shuts down after 10 minutes of inactivity unless --resident is set
Auto-attach existing browser (prefer CDP localhost:9333, then auto-discovery), or pass --cdp explicitly
Default mode uses localhost:9333. If 9333 is unavailable, agent-browser auto-starts a dedicated Chrome profile at ~/.agent-browser/chrome-bot-profile
Use --auto-connect only when you explicitly want to attach to an existing manual browser session
Configuration:
agent-browser looks for agent-browser.json in these locations (lowest to highest priority):