fix(cdp): 自动拉起 9333 专用浏览器
This commit is contained in:
@@ -175,7 +175,7 @@ flowchart TD
|
||||
- Project policy forbids:
|
||||
- `--profile` / `AGENT_BROWSER_PROFILE`
|
||||
- `--channel` / `AGENT_BROWSER_CHANNEL`
|
||||
- Default CLI policy auto-attaches an existing browser: try CDP `localhost:9333` first, then auto-discovery unless explicit connection options are provided.
|
||||
- Default CLI policy uses a dedicated automation browser on CDP `localhost:9333`. If `:9333` is unavailable, agent-browser auto-starts Chrome with the persistent profile `~/.agent-browser/chrome-bot-profile`.
|
||||
|
||||
## Principle 2: Multi-Layer Fingerprint Hardening
|
||||
|
||||
|
||||
+4
-25
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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):
|
||||
|
||||
@@ -6,7 +6,7 @@ export const metadata = pageMetadata('cdp-mode');
|
||||
|
||||
Connect to an existing browser via Chrome DevTools Protocol:
|
||||
|
||||
Default behavior in this fork: when `--cdp` is omitted, agent-browser auto-attaches to an existing browser by trying `localhost:9333` first, then auto-discovery. If both fail, the command exits (no managed local-launch fallback).
|
||||
Default behavior in this fork: when `--cdp` is omitted, agent-browser targets the managed automation browser on `localhost:9333`. If `:9333` is unavailable, it auto-starts Chrome with the persistent profile `~/.agent-browser/chrome-bot-profile` and retries the CDP connection.
|
||||
|
||||
Project policy:
|
||||
|
||||
@@ -67,6 +67,8 @@ This is useful when:
|
||||
- You want a zero-configuration connection to your existing browser
|
||||
- You don't want to track which port Chrome is using
|
||||
|
||||
Use this mode only when you intentionally want to attach to an existing manual browser session. Recent Chrome builds may display a permission prompt before allowing remote debugging access to that session.
|
||||
|
||||
## Color scheme
|
||||
|
||||
Playwright overrides the browser's color scheme to `light` by default when connecting via CDP. Use `--color-scheme` to set a persistent preference:
|
||||
|
||||
@@ -6,7 +6,7 @@ export const metadata = pageMetadata('configuration');
|
||||
|
||||
Create an `agent-browser.json` file to set persistent defaults instead of repeating flags on every command.
|
||||
|
||||
In this fork, default launch behavior auto-attaches to an existing browser by trying `localhost:9333` (CDP) first, then auto-discovery. If both fail, commands exit instead of launching a managed browser.
|
||||
In this fork, default launch behavior uses a dedicated automation browser on `localhost:9333` (CDP). If `:9333` is unavailable, agent-browser auto-starts Chrome with the persistent profile `~/.agent-browser/chrome-bot-profile` and retries the connection.
|
||||
|
||||
## Config File Locations
|
||||
|
||||
|
||||
@@ -220,14 +220,14 @@ Default-session commands intentionally reap all non-default daemon sessions (`pa
|
||||
|
||||
### Connect to Existing Chrome
|
||||
|
||||
By default in this fork, commands without `--cdp` auto-attach to your existing browser with this order:
|
||||
By default in this fork, commands without `--cdp` use a dedicated automation browser with this order:
|
||||
|
||||
1. Try CDP at `localhost:9333`
|
||||
2. If unavailable, fall back to `--auto-connect`-style discovery
|
||||
3. If both fail, exit with guidance (no automatic managed local browser launch on this path)
|
||||
2. If unavailable, auto-start Chrome with the persistent profile `~/.agent-browser/chrome-bot-profile`
|
||||
3. If managed `:9333` startup fails, exit with guidance
|
||||
|
||||
```bash
|
||||
# Auto-discover running Chrome with remote debugging enabled
|
||||
# Explicitly attach to an existing manual Chrome session
|
||||
agent-browser --auto-connect open https://example.com
|
||||
agent-browser --auto-connect snapshot
|
||||
|
||||
@@ -331,7 +331,7 @@ agent-browser screenshot output.png
|
||||
|
||||
- `--profile` / `AGENT_BROWSER_PROFILE` are forbidden
|
||||
- `--channel` / `AGENT_BROWSER_CHANNEL` are forbidden
|
||||
- Use existing browser sessions (default attach path: CDP `localhost:9333` then auto-discovery) or pass `--cdp` explicitly
|
||||
- Default mode uses the managed CDP browser on `localhost:9333`; use `--auto-connect` only for explicit existing-browser attachment
|
||||
|
||||
### Stealth Mode (Always On)
|
||||
|
||||
|
||||
+127
-2
@@ -14,9 +14,10 @@ import {
|
||||
type CDPSession,
|
||||
type Video,
|
||||
} from 'playwright-core';
|
||||
import { spawn, spawnSync } from 'node:child_process';
|
||||
import path from 'node:path';
|
||||
import os from 'node:os';
|
||||
import { existsSync, mkdirSync, rmSync, readFileSync, statSync } from 'node:fs';
|
||||
import { existsSync, mkdirSync, readdirSync, rmSync, readFileSync, statSync } from 'node:fs';
|
||||
import { writeFile, mkdir } from 'node:fs/promises';
|
||||
import type {
|
||||
DoctorCheck,
|
||||
@@ -134,6 +135,8 @@ interface StealthContextDefaults {
|
||||
}
|
||||
|
||||
const IGNORED_CDP_PAGE_URL_PREFIXES = ['chrome://omnibox-popup.top-chrome/'];
|
||||
const MANAGED_CDP_PORT = 9333;
|
||||
const MANAGED_CDP_START_TIMEOUT_MS = 20_000;
|
||||
const DEFAULT_TAB_GROUP_NAME = 'Agent Browser Stealth';
|
||||
const DEFAULT_TAB_GROUP_PLUGIN_ID = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
|
||||
const TAB_GROUP_REQUEST_MESSAGE_TYPE = 'AB_TAB_GROUP_REQUEST';
|
||||
@@ -149,6 +152,65 @@ interface TabGroupIntent {
|
||||
allowedDomains: string[];
|
||||
}
|
||||
|
||||
function getManagedCdpProfileDir(): string {
|
||||
return path.join(os.homedir(), '.agent-browser', 'chrome-bot-profile');
|
||||
}
|
||||
|
||||
function cleanupManagedCdpProfileLocks(profileDir: string): void {
|
||||
rmSync(path.join(profileDir, 'DevToolsActivePort'), { force: true });
|
||||
try {
|
||||
for (const entry of readdirSync(profileDir)) {
|
||||
if (entry.startsWith('Singleton')) {
|
||||
rmSync(path.join(profileDir, entry), { force: true, recursive: true });
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Best-effort cleanup for stale lock files.
|
||||
}
|
||||
}
|
||||
|
||||
function findManagedChromeExecutable(): string {
|
||||
const configured = process.env.AGENT_BROWSER_EXECUTABLE_PATH;
|
||||
if (configured && existsSync(configured)) {
|
||||
return configured;
|
||||
}
|
||||
|
||||
const platform = os.platform();
|
||||
const candidates =
|
||||
platform === 'darwin'
|
||||
? [
|
||||
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
||||
'/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
|
||||
'/Applications/Chromium.app/Contents/MacOS/Chromium',
|
||||
]
|
||||
: platform === 'win32'
|
||||
? [
|
||||
'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
|
||||
'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
|
||||
]
|
||||
: [];
|
||||
|
||||
for (const candidate of candidates) {
|
||||
if (existsSync(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
if (platform !== 'win32') {
|
||||
for (const name of ['google-chrome', 'google-chrome-stable', 'chromium-browser', 'chromium']) {
|
||||
const result = spawnSync('which', [name], { encoding: 'utf8' });
|
||||
if (result.status === 0) {
|
||||
const resolved = result.stdout.trim();
|
||||
if (resolved.length > 0) {
|
||||
return resolved;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Chrome not found. Install Chrome or set AGENT_BROWSER_EXECUTABLE_PATH.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages the Playwright browser lifecycle with multiple tabs/windows
|
||||
*/
|
||||
@@ -189,6 +251,61 @@ export class BrowserManager {
|
||||
private tabGroupCapabilityBySession: Map<string, TabGroupPluginAvailability> = new Map();
|
||||
private tabGroupInFlight: WeakSet<Page> = new WeakSet();
|
||||
|
||||
private isManagedCdpEndpoint(cdpEndpoint: string): boolean {
|
||||
return (
|
||||
cdpEndpoint === String(MANAGED_CDP_PORT) ||
|
||||
cdpEndpoint === `http://localhost:${MANAGED_CDP_PORT}` ||
|
||||
cdpEndpoint === `http://127.0.0.1:${MANAGED_CDP_PORT}` ||
|
||||
cdpEndpoint === `ws://127.0.0.1:${MANAGED_CDP_PORT}` ||
|
||||
cdpEndpoint.includes(`127.0.0.1:${MANAGED_CDP_PORT}/devtools/browser/`) ||
|
||||
cdpEndpoint.includes(`localhost:${MANAGED_CDP_PORT}/devtools/browser/`)
|
||||
);
|
||||
}
|
||||
|
||||
private async ensureManagedCdpBrowser(): Promise<void> {
|
||||
if (await this.probeDebugPort(MANAGED_CDP_PORT)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const profileDir = getManagedCdpProfileDir();
|
||||
mkdirSync(profileDir, { recursive: true });
|
||||
cleanupManagedCdpProfileLocks(profileDir);
|
||||
|
||||
const executablePath = findManagedChromeExecutable();
|
||||
const headed =
|
||||
process.env.AGENT_BROWSER_HEADED === '1' || process.env.AGENT_BROWSER_HEADED === 'true';
|
||||
const args = [
|
||||
'--remote-debugging-address=127.0.0.1',
|
||||
`--remote-debugging-port=${MANAGED_CDP_PORT}`,
|
||||
`--user-data-dir=${profileDir}`,
|
||||
'--no-first-run',
|
||||
'--no-default-browser-check',
|
||||
];
|
||||
if (!headed) {
|
||||
args.push('--headless=new', '--window-size=1280,720');
|
||||
}
|
||||
|
||||
const child = spawn(executablePath, args, {
|
||||
detached: true,
|
||||
stdio: 'ignore',
|
||||
});
|
||||
child.unref();
|
||||
|
||||
const deadline = Date.now() + MANAGED_CDP_START_TIMEOUT_MS;
|
||||
while (Date.now() < deadline) {
|
||||
const wsUrl = await this.probeDebugPort(MANAGED_CDP_PORT);
|
||||
if (wsUrl) {
|
||||
return;
|
||||
}
|
||||
if (child.exitCode !== null) {
|
||||
throw new Error(`Managed Chrome exited before opening CDP port ${MANAGED_CDP_PORT}`);
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 250));
|
||||
}
|
||||
|
||||
throw new Error(`Timed out waiting for managed Chrome on localhost:${MANAGED_CDP_PORT}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the persistent color scheme preference.
|
||||
* Applied automatically to all new pages and contexts.
|
||||
@@ -2009,7 +2126,15 @@ export class BrowserManager {
|
||||
}
|
||||
|
||||
if (cdpEndpoint) {
|
||||
await this.connectViaCDP(cdpEndpoint);
|
||||
try {
|
||||
await this.connectViaCDP(cdpEndpoint);
|
||||
} catch (error) {
|
||||
if (!this.isManagedCdpEndpoint(cdpEndpoint)) {
|
||||
throw error;
|
||||
}
|
||||
await this.ensureManagedCdpBrowser();
|
||||
await this.connectViaCDP(cdpEndpoint);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+10
-58
@@ -536,10 +536,10 @@ export async function startDaemon(options?: {
|
||||
// Auto-launch desktop browser
|
||||
const launchOptions = buildAutoLaunchOptionsFromEnv();
|
||||
|
||||
let attachedToExistingBrowser = false;
|
||||
let attachedToManagedBrowser = false;
|
||||
try {
|
||||
// Keep default CDP attempt minimal. Launch-only options like extensions
|
||||
// are incompatible with CDP and can cause false-negative attach failures.
|
||||
// Keep the preferred localhost:9333 path minimal so the daemon can
|
||||
// connect to or auto-start the dedicated automation Chrome profile.
|
||||
const cdpLaunchOptions = {
|
||||
id: launchOptions.id,
|
||||
action: launchOptions.action,
|
||||
@@ -553,52 +553,26 @@ export async function startDaemon(options?: {
|
||||
await manager.launch({
|
||||
...cdpLaunchOptions,
|
||||
});
|
||||
attachedToExistingBrowser = true;
|
||||
attachedToManagedBrowser = true;
|
||||
if (process.env.AGENT_BROWSER_DEBUG === '1') {
|
||||
console.error('[DEBUG] Auto-launch connected via default CDP port 9333');
|
||||
console.error('[DEBUG] Auto-launch connected via managed CDP port 9333');
|
||||
}
|
||||
} catch (error) {
|
||||
if (process.env.AGENT_BROWSER_DEBUG === '1') {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
console.error(
|
||||
`[DEBUG] Default CDP port 9333 unavailable, trying auto-connect discovery: ${message}`
|
||||
);
|
||||
console.error(`[DEBUG] Managed CDP port 9333 unavailable: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!attachedToExistingBrowser) {
|
||||
try {
|
||||
await manager.launch({
|
||||
id: launchOptions.id,
|
||||
action: launchOptions.action,
|
||||
autoConnect: true,
|
||||
ignoreHTTPSErrors: launchOptions.ignoreHTTPSErrors,
|
||||
colorScheme: launchOptions.colorScheme,
|
||||
userAgent: launchOptions.userAgent,
|
||||
tabGroup: launchOptions.tabGroup,
|
||||
tabGroupPluginId: launchOptions.tabGroupPluginId,
|
||||
});
|
||||
attachedToExistingBrowser = true;
|
||||
if (process.env.AGENT_BROWSER_DEBUG === '1') {
|
||||
console.error('[DEBUG] Auto-launch connected via auto-connect discovery');
|
||||
}
|
||||
} catch (error) {
|
||||
if (process.env.AGENT_BROWSER_DEBUG === '1') {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
console.error(`[DEBUG] Auto-connect discovery failed: ${message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!attachedToExistingBrowser) {
|
||||
if (!attachedToManagedBrowser) {
|
||||
throw new Error(
|
||||
'Project policy requires using your existing browser. Could not connect to CDP at localhost:9333 and auto-discovery also failed.'
|
||||
'Project policy requires using the dedicated automation browser on localhost:9333. Could not connect to or auto-start the managed Chrome profile.'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For doctor, attempt the same default attach flow but do not fail hard if attach is unavailable.
|
||||
// For doctor, attempt the same managed localhost:9333 flow but do not fail hard if attach is unavailable.
|
||||
// This keeps diagnostics actionable even when CDP is down.
|
||||
if (!manager.isLaunched() && isDoctor && manager instanceof BrowserManager) {
|
||||
try {
|
||||
@@ -622,29 +596,7 @@ export async function startDaemon(options?: {
|
||||
process.env.AGENT_BROWSER_TAB_GROUP_PLUGIN_ID?.trim() || undefined,
|
||||
});
|
||||
} catch {
|
||||
try {
|
||||
await manager.launch({
|
||||
id: 'doctor-auto-connect',
|
||||
action: 'launch',
|
||||
autoConnect: true,
|
||||
ignoreHTTPSErrors: process.env.AGENT_BROWSER_IGNORE_HTTPS_ERRORS === '1',
|
||||
userAgent: process.env.AGENT_BROWSER_USER_AGENT,
|
||||
colorScheme:
|
||||
process.env.AGENT_BROWSER_COLOR_SCHEME === 'dark' ||
|
||||
process.env.AGENT_BROWSER_COLOR_SCHEME === 'light' ||
|
||||
process.env.AGENT_BROWSER_COLOR_SCHEME === 'no-preference'
|
||||
? (process.env.AGENT_BROWSER_COLOR_SCHEME as
|
||||
| 'dark'
|
||||
| 'light'
|
||||
| 'no-preference')
|
||||
: undefined,
|
||||
tabGroup: process.env.AGENT_BROWSER_TAB_GROUP?.trim() || undefined,
|
||||
tabGroupPluginId:
|
||||
process.env.AGENT_BROWSER_TAB_GROUP_PLUGIN_ID?.trim() || undefined,
|
||||
});
|
||||
} catch {
|
||||
// Keep running: doctor should report failures instead of exiting early.
|
||||
}
|
||||
// Keep running: doctor should report failures instead of exiting early.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user