feat(cdp): 默认优先连接 9333 常驻 Chrome
- 无显式连接参数时先尝试 CDP 9333,失败后回退本地浏览器启动 - 修复 CDP 页选择稳定性:过滤 omnibox 系统页、无可用页时自动创建 fallback 页 - 调整页面关闭后的 active 索引维护,降低 No page found 问题 - 新增 agent-browser-stealth 二进制入口并保持与 agent-browser 行为一致 - 同步更新 CLI 帮助、README、技能文档与 docs 说明
This commit is contained in:
@@ -813,6 +813,8 @@ These behaviors are always active and require no additional flags.
|
|||||||
|
|
||||||
Connect to an existing browser via Chrome DevTools Protocol:
|
Connect to an existing browser via Chrome DevTools Protocol:
|
||||||
|
|
||||||
|
By default in this fork, when you run commands without `--cdp`, agent-browser first tries `localhost:9333` (resident Chrome) and falls back to launching a local Playwright browser if CDP is unavailable.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Start Chrome with: google-chrome --remote-debugging-port=9222
|
# Start Chrome with: google-chrome --remote-debugging-port=9222
|
||||||
|
|
||||||
@@ -854,7 +856,7 @@ AGENT_BROWSER_AUTO_CONNECT=1 agent-browser snapshot
|
|||||||
|
|
||||||
Auto-connect discovers Chrome by:
|
Auto-connect discovers Chrome by:
|
||||||
1. Reading Chrome's `DevToolsActivePort` file from the default user data directory
|
1. Reading Chrome's `DevToolsActivePort` file from the default user data directory
|
||||||
2. Falling back to probing common debugging ports (9222, 9229)
|
2. Falling back to probing common debugging ports (9222, 9229, 9333)
|
||||||
|
|
||||||
This is useful when:
|
This is useful when:
|
||||||
- Chrome 144+ has remote debugging enabled via `chrome://inspect/#remote-debugging` (which uses a dynamic port)
|
- Chrome 144+ has remote debugging enabled via `chrome://inspect/#remote-debugging` (which uses a dynamic port)
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ license = "Apache-2.0"
|
|||||||
name = "agent-browser"
|
name = "agent-browser"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "agent-browser-stealth"
|
||||||
|
path = "src/main_stealth.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
|||||||
@@ -513,6 +513,39 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Default fork behavior: when no explicit connection mode is provided,
|
||||||
|
// try attaching to resident Chrome on CDP :9333 first. If unavailable,
|
||||||
|
// silently fall back to local launch behavior below.
|
||||||
|
let can_try_default_cdp = flags.cdp.is_none()
|
||||||
|
&& !flags.auto_connect
|
||||||
|
&& flags.provider.is_none()
|
||||||
|
&& flags.executable_path.is_none()
|
||||||
|
&& flags.profile.is_none()
|
||||||
|
&& flags.state.is_none()
|
||||||
|
&& flags.proxy.is_none()
|
||||||
|
&& flags.args.is_none()
|
||||||
|
&& flags.user_agent.is_none()
|
||||||
|
&& !flags.ignore_https_errors
|
||||||
|
&& !flags.allow_file_access
|
||||||
|
&& flags.extensions.is_empty();
|
||||||
|
|
||||||
|
let mut launched_via_default_cdp = false;
|
||||||
|
if can_try_default_cdp {
|
||||||
|
let mut launch_cmd = json!({
|
||||||
|
"id": gen_id(),
|
||||||
|
"action": "launch",
|
||||||
|
"cdpPort": 9333
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(ref cs) = flags.color_scheme {
|
||||||
|
launch_cmd["colorScheme"] = json!(cs);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(resp) = send_command(launch_cmd, &flags.session) {
|
||||||
|
launched_via_default_cdp = resp.success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Launch headed browser or configure browser options (without CDP or provider)
|
// Launch headed browser or configure browser options (without CDP or provider)
|
||||||
if (flags.headed
|
if (flags.headed
|
||||||
|| flags.executable_path.is_some()
|
|| flags.executable_path.is_some()
|
||||||
@@ -527,6 +560,7 @@ fn main() {
|
|||||||
|| flags.color_scheme.is_some())
|
|| flags.color_scheme.is_some())
|
||||||
&& flags.cdp.is_none()
|
&& flags.cdp.is_none()
|
||||||
&& flags.provider.is_none()
|
&& flags.provider.is_none()
|
||||||
|
&& !launched_via_default_cdp
|
||||||
{
|
{
|
||||||
let mut launch_cmd = json!({
|
let mut launch_cmd = json!({
|
||||||
"id": gen_id(),
|
"id": gen_id(),
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
include!("main.rs");
|
||||||
@@ -2122,6 +2122,7 @@ Options:
|
|||||||
--headed Show browser window (not headless)
|
--headed Show browser window (not headless)
|
||||||
--cdp <port> Connect via CDP (Chrome DevTools Protocol)
|
--cdp <port> Connect via CDP (Chrome DevTools Protocol)
|
||||||
--auto-connect Auto-discover and connect to running Chrome
|
--auto-connect Auto-discover and connect to running Chrome
|
||||||
|
Default launch tries CDP at localhost:9333 first, then falls back to local browser launch
|
||||||
--color-scheme <scheme> Color scheme: dark, light, no-preference (or AGENT_BROWSER_COLOR_SCHEME)
|
--color-scheme <scheme> Color scheme: dark, light, no-preference (or AGENT_BROWSER_COLOR_SCHEME)
|
||||||
--session-name <name> Auto-save/restore session state (cookies, localStorage)
|
--session-name <name> Auto-save/restore session state (cookies, localStorage)
|
||||||
--config <path> Use a custom config file (or AGENT_BROWSER_CONFIG env)
|
--config <path> Use a custom config file (or AGENT_BROWSER_CONFIG env)
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ export const metadata = pageMetadata("cdp-mode")
|
|||||||
|
|
||||||
Connect to an existing browser via Chrome DevTools Protocol:
|
Connect to an existing browser via Chrome DevTools Protocol:
|
||||||
|
|
||||||
|
Default behavior in this fork: when `--cdp` is omitted, agent-browser first tries `localhost:9333` and falls back to local browser launch if CDP is unavailable.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Start Chrome with: google-chrome --remote-debugging-port=9222
|
# Start Chrome with: google-chrome --remote-debugging-port=9222
|
||||||
|
|
||||||
@@ -52,7 +54,7 @@ AGENT_BROWSER_AUTO_CONNECT=1 agent-browser snapshot
|
|||||||
Auto-connect discovers Chrome by:
|
Auto-connect discovers Chrome by:
|
||||||
|
|
||||||
1. Reading Chrome's `DevToolsActivePort` file from the default user data directory
|
1. Reading Chrome's `DevToolsActivePort` file from the default user data directory
|
||||||
2. Falling back to probing common debugging ports (9222, 9229)
|
2. Falling back to probing common debugging ports (9222, 9229, 9333)
|
||||||
|
|
||||||
This is useful when:
|
This is useful when:
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ export const metadata = pageMetadata("configuration")
|
|||||||
|
|
||||||
Create an `agent-browser.json` file to set persistent defaults instead of repeating flags on every command.
|
Create an `agent-browser.json` file to set persistent defaults instead of repeating flags on every command.
|
||||||
|
|
||||||
|
In this fork, default launch behavior already prefers a resident Chrome at `localhost:9333` (CDP) and falls back to local Playwright launch when unavailable.
|
||||||
|
|
||||||
## Config File Locations
|
## Config File Locations
|
||||||
|
|
||||||
agent-browser checks two locations, merged in priority order:
|
agent-browser checks two locations, merged in priority order:
|
||||||
|
|||||||
@@ -179,6 +179,8 @@ agent-browser session list
|
|||||||
|
|
||||||
### Connect to Existing Chrome
|
### Connect to Existing Chrome
|
||||||
|
|
||||||
|
By default in this fork, commands without `--cdp` try `localhost:9333` first and automatically fall back to a local browser launch if CDP is unavailable.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Auto-discover running Chrome with remote debugging enabled
|
# Auto-discover running Chrome with remote debugging enabled
|
||||||
agent-browser --auto-connect open https://example.com
|
agent-browser --auto-connect open https://example.com
|
||||||
|
|||||||
+66
-6
@@ -69,7 +69,7 @@ describe('BrowserManager', () => {
|
|||||||
|
|
||||||
it('should apply init-script stealth policy for CDP connections', async () => {
|
it('should apply init-script stealth policy for CDP connections', async () => {
|
||||||
const addInitScript = vi.fn().mockResolvedValue(undefined);
|
const addInitScript = vi.fn().mockResolvedValue(undefined);
|
||||||
const mockPage = { url: () => 'http://example.com', on: vi.fn() };
|
const mockPage = { url: () => 'http://example.com', on: vi.fn(), isClosed: () => false };
|
||||||
const mockContext = {
|
const mockContext = {
|
||||||
pages: () => [mockPage],
|
pages: () => [mockPage],
|
||||||
on: vi.fn(),
|
on: vi.fn(),
|
||||||
@@ -99,7 +99,7 @@ describe('BrowserManager', () => {
|
|||||||
|
|
||||||
it('should disable stealth capabilities when launch stealth is false in CDP mode', async () => {
|
it('should disable stealth capabilities when launch stealth is false in CDP mode', async () => {
|
||||||
const addInitScript = vi.fn().mockResolvedValue(undefined);
|
const addInitScript = vi.fn().mockResolvedValue(undefined);
|
||||||
const mockPage = { url: () => 'http://example.com', on: vi.fn() };
|
const mockPage = { url: () => 'http://example.com', on: vi.fn(), isClosed: () => false };
|
||||||
const mockContext = {
|
const mockContext = {
|
||||||
pages: () => [mockPage],
|
pages: () => [mockPage],
|
||||||
on: vi.fn(),
|
on: vi.fn(),
|
||||||
@@ -926,15 +926,16 @@ describe('BrowserManager', () => {
|
|||||||
contexts: () => [
|
contexts: () => [
|
||||||
{
|
{
|
||||||
pages: () => [
|
pages: () => [
|
||||||
{ url: () => 'http://example.com', on: vi.fn() },
|
{ url: () => 'http://example.com', on: vi.fn(), isClosed: () => false },
|
||||||
{ url: () => '', on: vi.fn() }, // This page should be filtered out
|
{ url: () => '', on: vi.fn(), isClosed: () => false }, // This page should be filtered out
|
||||||
{ url: () => 'http://anothersite.com', on: vi.fn() },
|
{ url: () => 'http://anothersite.com', on: vi.fn(), isClosed: () => false },
|
||||||
],
|
],
|
||||||
on: vi.fn(),
|
on: vi.fn(),
|
||||||
setDefaultTimeout: vi.fn(),
|
setDefaultTimeout: vi.fn(),
|
||||||
|
addInitScript: vi.fn().mockResolvedValue(undefined),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
close: vi.fn(),
|
close: vi.fn().mockResolvedValue(undefined),
|
||||||
};
|
};
|
||||||
const spy = vi.spyOn(chromium, 'connectOverCDP').mockResolvedValue(mockBrowser as any);
|
const spy = vi.spyOn(chromium, 'connectOverCDP').mockResolvedValue(mockBrowser as any);
|
||||||
|
|
||||||
@@ -950,6 +951,65 @@ describe('BrowserManager', () => {
|
|||||||
expect(urls).toContain('http://example.com');
|
expect(urls).toContain('http://example.com');
|
||||||
spy.mockRestore();
|
spy.mockRestore();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should ignore omnibox popup pages during CDP connection', async () => {
|
||||||
|
const mockBrowser = {
|
||||||
|
contexts: () => [
|
||||||
|
{
|
||||||
|
pages: () => [
|
||||||
|
{
|
||||||
|
url: () => 'chrome://omnibox-popup.top-chrome/',
|
||||||
|
on: vi.fn(),
|
||||||
|
isClosed: () => false,
|
||||||
|
},
|
||||||
|
{ url: () => 'http://example.com', on: vi.fn(), isClosed: () => false },
|
||||||
|
],
|
||||||
|
on: vi.fn(),
|
||||||
|
setDefaultTimeout: vi.fn(),
|
||||||
|
addInitScript: vi.fn().mockResolvedValue(undefined),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
close: vi.fn().mockResolvedValue(undefined),
|
||||||
|
};
|
||||||
|
const spy = vi.spyOn(chromium, 'connectOverCDP').mockResolvedValue(mockBrowser as any);
|
||||||
|
|
||||||
|
const cdpBrowser = new BrowserManager();
|
||||||
|
await cdpBrowser.launch({ cdpPort: 9222 });
|
||||||
|
|
||||||
|
expect(cdpBrowser.getPages().length).toBe(1);
|
||||||
|
expect(cdpBrowser.getPages()[0]?.url()).toBe('http://example.com');
|
||||||
|
spy.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create a fallback page when CDP has only internal pages', async () => {
|
||||||
|
const newPage = { url: () => 'about:blank', on: vi.fn(), isClosed: () => false };
|
||||||
|
const context = {
|
||||||
|
pages: () => [
|
||||||
|
{
|
||||||
|
url: () => 'chrome://omnibox-popup.top-chrome/omnibox_popup_aim.html',
|
||||||
|
on: vi.fn(),
|
||||||
|
isClosed: () => false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
newPage: vi.fn().mockResolvedValue(newPage),
|
||||||
|
on: vi.fn(),
|
||||||
|
setDefaultTimeout: vi.fn(),
|
||||||
|
addInitScript: vi.fn().mockResolvedValue(undefined),
|
||||||
|
};
|
||||||
|
const mockBrowser = {
|
||||||
|
contexts: () => [context],
|
||||||
|
close: vi.fn().mockResolvedValue(undefined),
|
||||||
|
};
|
||||||
|
const spy = vi.spyOn(chromium, 'connectOverCDP').mockResolvedValue(mockBrowser as any);
|
||||||
|
|
||||||
|
const cdpBrowser = new BrowserManager();
|
||||||
|
await cdpBrowser.launch({ cdpPort: 9222 });
|
||||||
|
|
||||||
|
expect(context.newPage).toHaveBeenCalledTimes(1);
|
||||||
|
expect(cdpBrowser.getPages().length).toBe(1);
|
||||||
|
expect(cdpBrowser.getPages()[0]?.url()).toBe('about:blank');
|
||||||
|
spy.mockRestore();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('screencast', () => {
|
describe('screencast', () => {
|
||||||
|
|||||||
+82
-6
@@ -125,6 +125,8 @@ interface StealthContextDefaults {
|
|||||||
extraHTTPHeaders?: Record<string, string>;
|
extraHTTPHeaders?: Record<string, string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const IGNORED_CDP_PAGE_URL_PREFIXES = ['chrome://omnibox-popup.top-chrome/'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages the Playwright browser lifecycle with multiple tabs/windows
|
* Manages the Playwright browser lifecycle with multiple tabs/windows
|
||||||
*/
|
*/
|
||||||
@@ -483,6 +485,33 @@ export class BrowserManager {
|
|||||||
return this.pages.length > 0;
|
return this.pages.length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getSafePageUrl(page: Page): string {
|
||||||
|
try {
|
||||||
|
return page.url();
|
||||||
|
} catch {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private isIgnoredCDPPageUrl(url: string): boolean {
|
||||||
|
if (!url) return false;
|
||||||
|
const normalizedUrl = url.toLowerCase();
|
||||||
|
return IGNORED_CDP_PAGE_URL_PREFIXES.some((prefix) => normalizedUrl.startsWith(prefix));
|
||||||
|
}
|
||||||
|
|
||||||
|
private isUsableCDPPage(page: Page): boolean {
|
||||||
|
if (page.isClosed()) return false;
|
||||||
|
const url = this.getSafePageUrl(page);
|
||||||
|
if (!url) return false;
|
||||||
|
return !this.isIgnoredCDPPageUrl(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
private collectUsableCDPPages(contexts: BrowserContext[]): Page[] {
|
||||||
|
return contexts
|
||||||
|
.flatMap((context) => context.pages())
|
||||||
|
.filter((page) => this.isUsableCDPPage(page));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure at least one page exists. If the browser is launched but all pages
|
* Ensure at least one page exists. If the browser is launched but all pages
|
||||||
* were closed (stale session), creates a new page on the existing context.
|
* were closed (stale session), creates a new page on the existing context.
|
||||||
@@ -527,6 +556,24 @@ export class BrowserManager {
|
|||||||
if (this.pages.length === 0) {
|
if (this.pages.length === 0) {
|
||||||
throw new Error('Browser not launched. Call launch first.');
|
throw new Error('Browser not launched. Call launch first.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const current = this.pages[this.activePageIndex];
|
||||||
|
if (current && this.isUsableCDPPage(current)) {
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
const usableIndex = this.pages.findIndex((page) => this.isUsableCDPPage(page));
|
||||||
|
if (usableIndex !== -1) {
|
||||||
|
this.activePageIndex = usableIndex;
|
||||||
|
return this.pages[this.activePageIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
const openIndex = this.pages.findIndex((page) => !page.isClosed());
|
||||||
|
if (openIndex !== -1) {
|
||||||
|
this.activePageIndex = openIndex;
|
||||||
|
return this.pages[this.activePageIndex];
|
||||||
|
}
|
||||||
|
|
||||||
return this.pages[this.activePageIndex];
|
return this.pages[this.activePageIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1008,7 +1055,7 @@ export class BrowserManager {
|
|||||||
try {
|
try {
|
||||||
const contexts = this.browser.contexts();
|
const contexts = this.browser.contexts();
|
||||||
if (contexts.length === 0) return false;
|
if (contexts.length === 0) return false;
|
||||||
return contexts.some((context) => context.pages().length > 0);
|
return contexts.some((context) => context.pages().some((page) => this.isUsableCDPPage(page)));
|
||||||
} catch {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1722,11 +1769,32 @@ export class BrowserManager {
|
|||||||
throw new Error('No browser context found. Make sure the app has an open window.');
|
throw new Error('No browser context found. Make sure the app has an open window.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter out pages with empty URLs, which can cause Playwright to hang
|
let allPages = this.collectUsableCDPPages(contexts);
|
||||||
const allPages = contexts.flatMap((context) => context.pages()).filter((page) => page.url());
|
|
||||||
|
|
||||||
if (allPages.length === 0) {
|
if (allPages.length === 0) {
|
||||||
throw new Error('No page found. Make sure the app has loaded content.');
|
// Some Chrome instances (especially with custom UI pages) expose only internal/transient
|
||||||
|
// pages over CDP. Create a fresh page so commands always have a stable target.
|
||||||
|
let fallbackPage: Page | null = null;
|
||||||
|
for (const context of contexts) {
|
||||||
|
try {
|
||||||
|
const page = await context.newPage();
|
||||||
|
if (!fallbackPage) {
|
||||||
|
fallbackPage = page;
|
||||||
|
}
|
||||||
|
if (this.isUsableCDPPage(page)) {
|
||||||
|
fallbackPage = page;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Try next context
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fallbackPage) {
|
||||||
|
throw new Error('No page found. Make sure the app has loaded content.');
|
||||||
|
}
|
||||||
|
|
||||||
|
allPages = [fallbackPage];
|
||||||
}
|
}
|
||||||
|
|
||||||
// All validation passed - commit state
|
// All validation passed - commit state
|
||||||
@@ -1831,7 +1899,7 @@ export class BrowserManager {
|
|||||||
* Discovery strategy:
|
* Discovery strategy:
|
||||||
* 1. Read DevToolsActivePort from Chrome's default user data directories
|
* 1. Read DevToolsActivePort from Chrome's default user data directories
|
||||||
* 2. If found, connect using the port and WebSocket path from that file
|
* 2. If found, connect using the port and WebSocket path from that file
|
||||||
* 3. If not found, probe common debugging ports (9222, 9229)
|
* 3. If not found, probe common debugging ports (9222, 9229, 9333)
|
||||||
* 4. If a port responds, connect via CDP
|
* 4. If a port responds, connect via CDP
|
||||||
*/
|
*/
|
||||||
private async autoConnectViaCDP(): Promise<void> {
|
private async autoConnectViaCDP(): Promise<void> {
|
||||||
@@ -1866,7 +1934,7 @@ export class BrowserManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Strategy 2: Probe common debugging ports
|
// Strategy 2: Probe common debugging ports
|
||||||
const commonPorts = [9222, 9229];
|
const commonPorts = [9222, 9229, 9333];
|
||||||
for (const port of commonPorts) {
|
for (const port of commonPorts) {
|
||||||
const wsUrl = await this.probeDebugPort(port);
|
const wsUrl = await this.probeDebugPort(port);
|
||||||
if (wsUrl) {
|
if (wsUrl) {
|
||||||
@@ -1922,6 +1990,9 @@ export class BrowserManager {
|
|||||||
const index = this.pages.indexOf(page);
|
const index = this.pages.indexOf(page);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
this.pages.splice(index, 1);
|
this.pages.splice(index, 1);
|
||||||
|
if (index < this.activePageIndex) {
|
||||||
|
this.activePageIndex--;
|
||||||
|
}
|
||||||
if (this.activePageIndex >= this.pages.length) {
|
if (this.activePageIndex >= this.pages.length) {
|
||||||
this.activePageIndex = Math.max(0, this.pages.length - 1);
|
this.activePageIndex = Math.max(0, this.pages.length - 1);
|
||||||
}
|
}
|
||||||
@@ -1935,6 +2006,11 @@ export class BrowserManager {
|
|||||||
*/
|
*/
|
||||||
private setupContextTracking(context: BrowserContext): void {
|
private setupContextTracking(context: BrowserContext): void {
|
||||||
context.on('page', (page) => {
|
context.on('page', (page) => {
|
||||||
|
const pageUrl = this.getSafePageUrl(page);
|
||||||
|
if (this.isIgnoredCDPPageUrl(pageUrl)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Only add if not already tracked (avoids duplicates when newTab() creates pages)
|
// Only add if not already tracked (avoids duplicates when newTab() creates pages)
|
||||||
if (!this.pages.includes(page)) {
|
if (!this.pages.includes(page)) {
|
||||||
this.pages.push(page);
|
this.pages.push(page);
|
||||||
|
|||||||
+38
-4
@@ -405,7 +405,9 @@ export async function startDaemon(options?: {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-launch if not already launched and this isn't a launch/close/state_load command
|
// Auto-launch if not already launched and this isn't a launch/close/state_load command.
|
||||||
|
// Default behavior for this fork: first try attaching to a resident Chrome on CDP :9333,
|
||||||
|
// then fall back to launching a local Playwright browser if CDP is unavailable.
|
||||||
if (
|
if (
|
||||||
!manager.isLaunched() &&
|
!manager.isLaunched() &&
|
||||||
parseResult.command.action !== 'launch' &&
|
parseResult.command.action !== 'launch' &&
|
||||||
@@ -452,13 +454,13 @@ export async function startDaemon(options?: {
|
|||||||
const allowFileAccess = process.env.AGENT_BROWSER_ALLOW_FILE_ACCESS === '1';
|
const allowFileAccess = process.env.AGENT_BROWSER_ALLOW_FILE_ACCESS === '1';
|
||||||
// Stealth is always enabled in agent-browser-stealth
|
// Stealth is always enabled in agent-browser-stealth
|
||||||
const colorSchemeEnv = process.env.AGENT_BROWSER_COLOR_SCHEME;
|
const colorSchemeEnv = process.env.AGENT_BROWSER_COLOR_SCHEME;
|
||||||
const colorScheme =
|
const colorScheme: 'dark' | 'light' | 'no-preference' | undefined =
|
||||||
colorSchemeEnv === 'dark' ||
|
colorSchemeEnv === 'dark' ||
|
||||||
colorSchemeEnv === 'light' ||
|
colorSchemeEnv === 'light' ||
|
||||||
colorSchemeEnv === 'no-preference'
|
colorSchemeEnv === 'no-preference'
|
||||||
? colorSchemeEnv
|
? colorSchemeEnv
|
||||||
: undefined;
|
: undefined;
|
||||||
await manager.launch({
|
const launchOptions = {
|
||||||
id: 'auto',
|
id: 'auto',
|
||||||
action: 'launch' as const,
|
action: 'launch' as const,
|
||||||
headless: process.env.AGENT_BROWSER_HEADED !== '1',
|
headless: process.env.AGENT_BROWSER_HEADED !== '1',
|
||||||
@@ -474,7 +476,39 @@ export async function startDaemon(options?: {
|
|||||||
|
|
||||||
colorScheme,
|
colorScheme,
|
||||||
autoStateFilePath: getSessionAutoStatePath(),
|
autoStateFilePath: getSessionAutoStatePath(),
|
||||||
});
|
};
|
||||||
|
|
||||||
|
let launchedViaDefaultCdp = false;
|
||||||
|
try {
|
||||||
|
// Keep default CDP attempt minimal. Launch-only options like profile/extensions
|
||||||
|
// are incompatible with CDP and can cause a false-negative fallback.
|
||||||
|
const cdpLaunchOptions = {
|
||||||
|
id: launchOptions.id,
|
||||||
|
action: launchOptions.action,
|
||||||
|
cdpPort: 9333,
|
||||||
|
ignoreHTTPSErrors: launchOptions.ignoreHTTPSErrors,
|
||||||
|
colorScheme: launchOptions.colorScheme,
|
||||||
|
userAgent: launchOptions.userAgent,
|
||||||
|
};
|
||||||
|
await manager.launch({
|
||||||
|
...cdpLaunchOptions,
|
||||||
|
});
|
||||||
|
launchedViaDefaultCdp = true;
|
||||||
|
if (process.env.AGENT_BROWSER_DEBUG === '1') {
|
||||||
|
console.error('[DEBUG] Auto-launch connected via default 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, falling back to local launch: ${message}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!launchedViaDefaultCdp) {
|
||||||
|
await manager.launch(launchOptions);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user