From 682888cc9584f965e760ccaad01c5dc064fe8c8c Mon Sep 17 00:00:00 2001 From: bviews Date: Mon, 16 Mar 2026 15:51:57 +0800 Subject: [PATCH 1/6] feat(baoyu-chrome-cdp): support connecting to existing Chrome session Add ability to discover and connect to an already-running Chrome browser (v144+) for cookie extraction, avoiding the need to launch a new window and re-login. Uses Chrome's DevToolsActivePort from default user data directories and process scanning as discovery mechanisms. Co-Authored-By: Claude Opus 4.6 --- packages/baoyu-chrome-cdp/src/index.ts | 79 ++++++++++++++++ .../utils/load-browser-cookies.ts | 91 +++++++++++++++++++ .../vendor/baoyu-chrome-cdp/src/index.ts | 79 ++++++++++++++++ 3 files changed, 249 insertions(+) diff --git a/packages/baoyu-chrome-cdp/src/index.ts b/packages/baoyu-chrome-cdp/src/index.ts index 1fcd241..ab6bf5a 100644 --- a/packages/baoyu-chrome-cdp/src/index.ts +++ b/packages/baoyu-chrome-cdp/src/index.ts @@ -43,6 +43,13 @@ type FindExistingChromeDebugPortOptions = { timeoutMs?: number; }; +export type ChromeChannel = "stable" | "beta" | "canary" | "dev"; + +type DiscoverRunningChromeOptions = { + channels?: ChromeChannel[]; + timeoutMs?: number; +}; + type LaunchChromeOptions = { chromePath: string; profileDir: string; @@ -204,6 +211,78 @@ export async function findExistingChromeDebugPort(options: FindExistingChromeDeb return null; } +export function getDefaultChromeUserDataDirs(channels: ChromeChannel[] = ["stable"]): string[] { + const home = os.homedir(); + const dirs: string[] = []; + + const channelDirs: Record = { + stable: { + darwin: path.join(home, "Library", "Application Support", "Google", "Chrome"), + linux: path.join(home, ".config", "google-chrome"), + win32: path.join(process.env.LOCALAPPDATA ?? path.join(home, "AppData", "Local"), "Google", "Chrome", "User Data"), + }, + beta: { + darwin: path.join(home, "Library", "Application Support", "Google", "Chrome Beta"), + linux: path.join(home, ".config", "google-chrome-beta"), + win32: path.join(process.env.LOCALAPPDATA ?? path.join(home, "AppData", "Local"), "Google", "Chrome Beta", "User Data"), + }, + canary: { + darwin: path.join(home, "Library", "Application Support", "Google", "Chrome Canary"), + linux: path.join(home, ".config", "google-chrome-canary"), + win32: path.join(process.env.LOCALAPPDATA ?? path.join(home, "AppData", "Local"), "Google", "Chrome SxS", "User Data"), + }, + dev: { + darwin: path.join(home, "Library", "Application Support", "Google", "Chrome Dev"), + linux: path.join(home, ".config", "google-chrome-dev"), + win32: path.join(process.env.LOCALAPPDATA ?? path.join(home, "AppData", "Local"), "Google", "Chrome Dev", "User Data"), + }, + }; + + const platform = process.platform === "darwin" ? "darwin" : process.platform === "win32" ? "win32" : "linux"; + + for (const ch of channels) { + const entry = channelDirs[ch]; + if (entry) dirs.push(entry[platform]); + } + + return dirs; +} + +export async function discoverRunningChromeDebugPort(options: DiscoverRunningChromeOptions = {}): Promise { + const channels = options.channels ?? ["stable", "beta", "canary", "dev"]; + const timeoutMs = options.timeoutMs ?? 3_000; + + const userDataDirs = getDefaultChromeUserDataDirs(channels); + for (const dir of userDataDirs) { + const portFile = path.join(dir, "DevToolsActivePort"); + try { + const content = fs.readFileSync(portFile, "utf-8"); + const [portLine] = content.split(/\r?\n/); + const port = Number.parseInt(portLine?.trim() ?? "", 10); + if (port > 0 && await isDebugPortReady(port, timeoutMs)) return port; + } catch {} + } + + if (process.platform !== "win32") { + try { + const result = spawnSync("ps", ["aux"], { encoding: "utf-8", timeout: 5_000 }); + if (result.status === 0 && result.stdout) { + const lines = result.stdout + .split("\n") + .filter((line) => line.includes("--remote-debugging-port=") && /chrome|chromium/i.test(line)); + + for (const line of lines) { + const portMatch = line.match(/--remote-debugging-port=(\d+)/); + const port = Number.parseInt(portMatch?.[1] ?? "", 10); + if (port > 0 && await isDebugPortReady(port, timeoutMs)) return port; + } + } + } catch {} + } + + return null; +} + export async function waitForChromeDebugPort( port: number, timeoutMs: number, diff --git a/skills/baoyu-danger-gemini-web/scripts/gemini-webapi/utils/load-browser-cookies.ts b/skills/baoyu-danger-gemini-web/scripts/gemini-webapi/utils/load-browser-cookies.ts index 97fa369..f1670bd 100644 --- a/skills/baoyu-danger-gemini-web/scripts/gemini-webapi/utils/load-browser-cookies.ts +++ b/skills/baoyu-danger-gemini-web/scripts/gemini-webapi/utils/load-browser-cookies.ts @@ -2,6 +2,7 @@ import process from 'node:process'; import { CdpConnection, + discoverRunningChromeDebugPort, findChromeExecutable as findChromeExecutableBase, findExistingChromeDebugPort, getFreePort, @@ -97,6 +98,84 @@ async function is_gemini_session_ready(cookies: CookieMap, verbose: boolean): Pr } } +async function fetch_cookies_from_existing_chrome( + timeoutMs: number, + verbose: boolean, +): Promise { + const port = await discoverRunningChromeDebugPort(); + if (port === null) return null; + + if (verbose) logger.info(`Found existing Chrome on port ${port}. Extracting cookies...`); + + let cdp: CdpConnection | null = null; + try { + const wsUrl = await waitForChromeDebugPort(port, 10_000, { includeLastError: true }); + cdp = await CdpConnection.connect(wsUrl, 15_000); + + const targets = await cdp.send<{ targetInfos: Array<{ targetId: string; url: string; type: string }> }>('Target.getTargets'); + const geminiTarget = targets.targetInfos.find( + (t) => t.type === 'page' && t.url.includes('gemini.google.com'), + ); + + let targetId: string; + let createdTab = false; + + if (geminiTarget) { + targetId = geminiTarget.targetId; + if (verbose) logger.debug('Found existing Gemini tab, attaching...'); + } else { + const created = await cdp.send<{ targetId: string }>('Target.createTarget', { url: GEMINI_APP_URL }); + targetId = created.targetId; + createdTab = true; + if (verbose) logger.debug('No Gemini tab found, created new tab...'); + } + + const { sessionId } = await cdp.send<{ sessionId: string }>( + 'Target.attachToTarget', + { targetId, flatten: true }, + ); + await cdp.send('Network.enable', {}, { sessionId }); + + const start = Date.now(); + let last: CookieMap = {}; + + while (Date.now() - start < timeoutMs) { + const { cookies } = await cdp.send<{ cookies: Array<{ name: string; value: string }> }>( + 'Network.getCookies', + { urls: ['https://gemini.google.com/', 'https://accounts.google.com/', 'https://www.google.com/'] }, + { sessionId, timeoutMs: 10_000 }, + ); + + const cookieMap: CookieMap = {}; + for (const cookie of cookies) { + if (cookie?.name && typeof cookie.value === 'string') cookieMap[cookie.name] = cookie.value; + } + + last = cookieMap; + if (await is_gemini_session_ready(cookieMap, verbose)) { + if (createdTab) { + try { await cdp.send('Target.closeTarget', { targetId }, { timeoutMs: 5_000 }); } catch {} + } + return cookieMap; + } + + await sleep(1000); + } + + if (createdTab) { + try { await cdp.send('Target.closeTarget', { targetId }, { timeoutMs: 5_000 }); } catch {} + } + + if (verbose) logger.debug(`Existing Chrome did not yield valid cookies. Last keys: ${Object.keys(last).join(', ')}`); + return null; + } catch (e) { + if (verbose) logger.debug(`Failed to connect to existing Chrome: ${e instanceof Error ? e.message : String(e)}`); + return null; + } finally { + if (cdp) cdp.close(); + } +} + async function fetch_google_cookies_via_cdp( profileDir: string, timeoutMs: number, @@ -178,6 +257,18 @@ export async function load_browser_cookies(domain_name: string = '', verbose: bo if (cached) return { chrome: cached }; } + const existingCookies = await fetch_cookies_from_existing_chrome(30_000, verbose); + if (existingCookies) { + const filtered: CookieMap = {}; + for (const [key, value] of Object.entries(existingCookies)) { + if (typeof value === 'string' && value.length > 0) filtered[key] = value; + } + + await write_cookie_file(filtered, resolveGeminiWebCookiePath(), 'cdp-existing'); + void domain_name; + return { chrome: filtered }; + } + const profileDir = process.env.GEMINI_WEB_CHROME_PROFILE_DIR?.trim() || resolveGeminiWebChromeProfileDir(); const cookies = await fetch_google_cookies_via_cdp(profileDir, 120_000, verbose); diff --git a/skills/baoyu-danger-gemini-web/scripts/vendor/baoyu-chrome-cdp/src/index.ts b/skills/baoyu-danger-gemini-web/scripts/vendor/baoyu-chrome-cdp/src/index.ts index 1fcd241..ab6bf5a 100644 --- a/skills/baoyu-danger-gemini-web/scripts/vendor/baoyu-chrome-cdp/src/index.ts +++ b/skills/baoyu-danger-gemini-web/scripts/vendor/baoyu-chrome-cdp/src/index.ts @@ -43,6 +43,13 @@ type FindExistingChromeDebugPortOptions = { timeoutMs?: number; }; +export type ChromeChannel = "stable" | "beta" | "canary" | "dev"; + +type DiscoverRunningChromeOptions = { + channels?: ChromeChannel[]; + timeoutMs?: number; +}; + type LaunchChromeOptions = { chromePath: string; profileDir: string; @@ -204,6 +211,78 @@ export async function findExistingChromeDebugPort(options: FindExistingChromeDeb return null; } +export function getDefaultChromeUserDataDirs(channels: ChromeChannel[] = ["stable"]): string[] { + const home = os.homedir(); + const dirs: string[] = []; + + const channelDirs: Record = { + stable: { + darwin: path.join(home, "Library", "Application Support", "Google", "Chrome"), + linux: path.join(home, ".config", "google-chrome"), + win32: path.join(process.env.LOCALAPPDATA ?? path.join(home, "AppData", "Local"), "Google", "Chrome", "User Data"), + }, + beta: { + darwin: path.join(home, "Library", "Application Support", "Google", "Chrome Beta"), + linux: path.join(home, ".config", "google-chrome-beta"), + win32: path.join(process.env.LOCALAPPDATA ?? path.join(home, "AppData", "Local"), "Google", "Chrome Beta", "User Data"), + }, + canary: { + darwin: path.join(home, "Library", "Application Support", "Google", "Chrome Canary"), + linux: path.join(home, ".config", "google-chrome-canary"), + win32: path.join(process.env.LOCALAPPDATA ?? path.join(home, "AppData", "Local"), "Google", "Chrome SxS", "User Data"), + }, + dev: { + darwin: path.join(home, "Library", "Application Support", "Google", "Chrome Dev"), + linux: path.join(home, ".config", "google-chrome-dev"), + win32: path.join(process.env.LOCALAPPDATA ?? path.join(home, "AppData", "Local"), "Google", "Chrome Dev", "User Data"), + }, + }; + + const platform = process.platform === "darwin" ? "darwin" : process.platform === "win32" ? "win32" : "linux"; + + for (const ch of channels) { + const entry = channelDirs[ch]; + if (entry) dirs.push(entry[platform]); + } + + return dirs; +} + +export async function discoverRunningChromeDebugPort(options: DiscoverRunningChromeOptions = {}): Promise { + const channels = options.channels ?? ["stable", "beta", "canary", "dev"]; + const timeoutMs = options.timeoutMs ?? 3_000; + + const userDataDirs = getDefaultChromeUserDataDirs(channels); + for (const dir of userDataDirs) { + const portFile = path.join(dir, "DevToolsActivePort"); + try { + const content = fs.readFileSync(portFile, "utf-8"); + const [portLine] = content.split(/\r?\n/); + const port = Number.parseInt(portLine?.trim() ?? "", 10); + if (port > 0 && await isDebugPortReady(port, timeoutMs)) return port; + } catch {} + } + + if (process.platform !== "win32") { + try { + const result = spawnSync("ps", ["aux"], { encoding: "utf-8", timeout: 5_000 }); + if (result.status === 0 && result.stdout) { + const lines = result.stdout + .split("\n") + .filter((line) => line.includes("--remote-debugging-port=") && /chrome|chromium/i.test(line)); + + for (const line of lines) { + const portMatch = line.match(/--remote-debugging-port=(\d+)/); + const port = Number.parseInt(portMatch?.[1] ?? "", 10); + if (port > 0 && await isDebugPortReady(port, timeoutMs)) return port; + } + } + } catch {} + } + + return null; +} + export async function waitForChromeDebugPort( port: number, timeoutMs: number, From 126b3040e69c66b82ed1556c0ce262851a72a1ff Mon Sep 17 00:00:00 2001 From: bviews Date: Mon, 16 Mar 2026 16:44:53 +0800 Subject: [PATCH 2/6] fix(gemini-web): reuse openPageSession and fix orphaned tab leak - Replace manual CDP target orchestration with openPageSession, keeping behavior consistent with fetch_google_cookies_via_cdp - Move created-tab cleanup into finally block so tabs are always closed even when Target.attachToTarget or Network.enable throws Co-Authored-By: Claude Opus 4.6 --- .../utils/load-browser-cookies.ts | 51 ++++++++----------- 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/skills/baoyu-danger-gemini-web/scripts/gemini-webapi/utils/load-browser-cookies.ts b/skills/baoyu-danger-gemini-web/scripts/gemini-webapi/utils/load-browser-cookies.ts index f1670bd..3b8b09d 100644 --- a/skills/baoyu-danger-gemini-web/scripts/gemini-webapi/utils/load-browser-cookies.ts +++ b/skills/baoyu-danger-gemini-web/scripts/gemini-webapi/utils/load-browser-cookies.ts @@ -108,33 +108,30 @@ async function fetch_cookies_from_existing_chrome( if (verbose) logger.info(`Found existing Chrome on port ${port}. Extracting cookies...`); let cdp: CdpConnection | null = null; + let createdTab = false; + let targetId: string | null = null; try { const wsUrl = await waitForChromeDebugPort(port, 10_000, { includeLastError: true }); cdp = await CdpConnection.connect(wsUrl, 15_000); const targets = await cdp.send<{ targetInfos: Array<{ targetId: string; url: string; type: string }> }>('Target.getTargets'); - const geminiTarget = targets.targetInfos.find( + const hasGeminiTab = targets.targetInfos.some( (t) => t.type === 'page' && t.url.includes('gemini.google.com'), ); + createdTab = !hasGeminiTab; - let targetId: string; - let createdTab = false; + if (verbose) logger.debug(hasGeminiTab ? 'Found existing Gemini tab, attaching...' : 'No Gemini tab found, creating new tab...'); - if (geminiTarget) { - targetId = geminiTarget.targetId; - if (verbose) logger.debug('Found existing Gemini tab, attaching...'); - } else { - const created = await cdp.send<{ targetId: string }>('Target.createTarget', { url: GEMINI_APP_URL }); - targetId = created.targetId; - createdTab = true; - if (verbose) logger.debug('No Gemini tab found, created new tab...'); - } - - const { sessionId } = await cdp.send<{ sessionId: string }>( - 'Target.attachToTarget', - { targetId, flatten: true }, - ); - await cdp.send('Network.enable', {}, { sessionId }); + const page = await openPageSession({ + cdp, + reusing: false, + url: GEMINI_APP_URL, + matchTarget: (target) => target.type === 'page' && target.url.includes('gemini.google.com'), + enableNetwork: true, + activateTarget: false, + }); + const { sessionId } = page; + targetId = page.targetId; const start = Date.now(); let last: CookieMap = {}; @@ -152,27 +149,23 @@ async function fetch_cookies_from_existing_chrome( } last = cookieMap; - if (await is_gemini_session_ready(cookieMap, verbose)) { - if (createdTab) { - try { await cdp.send('Target.closeTarget', { targetId }, { timeoutMs: 5_000 }); } catch {} - } - return cookieMap; - } + if (await is_gemini_session_ready(cookieMap, verbose)) return cookieMap; await sleep(1000); } - if (createdTab) { - try { await cdp.send('Target.closeTarget', { targetId }, { timeoutMs: 5_000 }); } catch {} - } - if (verbose) logger.debug(`Existing Chrome did not yield valid cookies. Last keys: ${Object.keys(last).join(', ')}`); return null; } catch (e) { if (verbose) logger.debug(`Failed to connect to existing Chrome: ${e instanceof Error ? e.message : String(e)}`); return null; } finally { - if (cdp) cdp.close(); + if (cdp) { + if (createdTab && targetId) { + try { await cdp.send('Target.closeTarget', { targetId }, { timeoutMs: 5_000 }); } catch {} + } + cdp.close(); + } } } From d9f9da639d65aca735cc8cf57445f11524df433c Mon Sep 17 00:00:00 2001 From: Alec Jiang Date: Mon, 16 Mar 2026 18:00:58 +0800 Subject: [PATCH 3/6] fix(baoyu-chrome-cdp): support Chrome 146 native remote debugging (approval mode) Chrome 146+ blocks all HTTP endpoints (/json/version) in approval mode. Read DevToolsActivePort ws path directly and use TCP port check instead of HTTP discovery. Add WebSocket connect retry loop for approval dialog. Unify findExistingChromeDebugPort to use the same mechanism. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/baoyu-chrome-cdp/src/index.ts | 57 +++++++++++++------ .../utils/load-browser-cookies.ts | 25 ++++++-- .../vendor/baoyu-chrome-cdp/src/index.ts | 57 +++++++++++++------ 3 files changed, 102 insertions(+), 37 deletions(-) diff --git a/packages/baoyu-chrome-cdp/src/index.ts b/packages/baoyu-chrome-cdp/src/index.ts index ab6bf5a..b455324 100644 --- a/packages/baoyu-chrome-cdp/src/index.ts +++ b/packages/baoyu-chrome-cdp/src/index.ts @@ -45,6 +45,11 @@ type FindExistingChromeDebugPortOptions = { export type ChromeChannel = "stable" | "beta" | "canary" | "dev"; +export type DiscoveredChrome = { + port: number; + wsUrl: string; +}; + type DiscoverRunningChromeOptions = { channels?: ChromeChannel[]; timeoutMs?: number; @@ -180,16 +185,33 @@ async function isDebugPortReady(port: number, timeoutMs = 3_000): Promise { + return new Promise((resolve) => { + const socket = new net.Socket(); + const timer = setTimeout(() => { socket.destroy(); resolve(false); }, timeoutMs); + socket.once("connect", () => { clearTimeout(timer); socket.destroy(); resolve(true); }); + socket.once("error", () => { clearTimeout(timer); resolve(false); }); + socket.connect(port, "127.0.0.1"); + }); +} + +function parseDevToolsActivePort(filePath: string): { port: number; wsPath: string } | null { + try { + const content = fs.readFileSync(filePath, "utf-8"); + const lines = content.split(/\r?\n/); + const port = Number.parseInt(lines[0]?.trim() ?? "", 10); + const wsPath = lines[1]?.trim(); + if (port > 0 && wsPath) return { port, wsPath }; + } catch {} + return null; +} + export async function findExistingChromeDebugPort(options: FindExistingChromeDebugPortOptions): Promise { const timeoutMs = options.timeoutMs ?? 3_000; - const portFile = path.join(options.profileDir, "DevToolsActivePort"); + const parsed = parseDevToolsActivePort(path.join(options.profileDir, "DevToolsActivePort")); - try { - const content = fs.readFileSync(portFile, "utf-8"); - const [portLine] = content.split(/\r?\n/); - const port = Number.parseInt(portLine?.trim() ?? "", 10); - if (port > 0 && await isDebugPortReady(port, timeoutMs)) return port; - } catch {} + if (parsed && await isPortListening(parsed.port, timeoutMs)) return parsed.port; + if (parsed && parsed.port > 0 && await isDebugPortReady(parsed.port, timeoutMs)) return parsed.port; if (process.platform === "win32") return null; @@ -248,19 +270,17 @@ export function getDefaultChromeUserDataDirs(channels: ChromeChannel[] = ["stabl return dirs; } -export async function discoverRunningChromeDebugPort(options: DiscoverRunningChromeOptions = {}): Promise { +export async function discoverRunningChromeDebugPort(options: DiscoverRunningChromeOptions = {}): Promise { const channels = options.channels ?? ["stable", "beta", "canary", "dev"]; const timeoutMs = options.timeoutMs ?? 3_000; const userDataDirs = getDefaultChromeUserDataDirs(channels); for (const dir of userDataDirs) { - const portFile = path.join(dir, "DevToolsActivePort"); - try { - const content = fs.readFileSync(portFile, "utf-8"); - const [portLine] = content.split(/\r?\n/); - const port = Number.parseInt(portLine?.trim() ?? "", 10); - if (port > 0 && await isDebugPortReady(port, timeoutMs)) return port; - } catch {} + const parsed = parseDevToolsActivePort(path.join(dir, "DevToolsActivePort")); + if (!parsed) continue; + if (await isPortListening(parsed.port, timeoutMs)) { + return { port: parsed.port, wsUrl: `ws://127.0.0.1:${parsed.port}${parsed.wsPath}` }; + } } if (process.platform !== "win32") { @@ -274,7 +294,12 @@ export async function discoverRunningChromeDebugPort(options: DiscoverRunningChr for (const line of lines) { const portMatch = line.match(/--remote-debugging-port=(\d+)/); const port = Number.parseInt(portMatch?.[1] ?? "", 10); - if (port > 0 && await isDebugPortReady(port, timeoutMs)) return port; + if (port > 0 && await isDebugPortReady(port, timeoutMs)) { + try { + const version = await fetchJson<{ webSocketDebuggerUrl?: string }>(`http://127.0.0.1:${port}/json/version`, { timeoutMs }); + if (version.webSocketDebuggerUrl) return { port, wsUrl: version.webSocketDebuggerUrl }; + } catch {} + } } } } catch {} diff --git a/skills/baoyu-danger-gemini-web/scripts/gemini-webapi/utils/load-browser-cookies.ts b/skills/baoyu-danger-gemini-web/scripts/gemini-webapi/utils/load-browser-cookies.ts index 3b8b09d..cd637c1 100644 --- a/skills/baoyu-danger-gemini-web/scripts/gemini-webapi/utils/load-browser-cookies.ts +++ b/skills/baoyu-danger-gemini-web/scripts/gemini-webapi/utils/load-browser-cookies.ts @@ -102,17 +102,32 @@ async function fetch_cookies_from_existing_chrome( timeoutMs: number, verbose: boolean, ): Promise { - const port = await discoverRunningChromeDebugPort(); - if (port === null) return null; + const discovered = await discoverRunningChromeDebugPort(); + if (discovered === null) return null; - if (verbose) logger.info(`Found existing Chrome on port ${port}. Extracting cookies...`); + if (verbose) logger.info(`Found existing Chrome on port ${discovered.port}. Connecting via WebSocket...`); let cdp: CdpConnection | null = null; let createdTab = false; let targetId: string | null = null; try { - const wsUrl = await waitForChromeDebugPort(port, 10_000, { includeLastError: true }); - cdp = await CdpConnection.connect(wsUrl, 15_000); + const connectStart = Date.now(); + const connectTimeout = 30_000; + let lastConnErr: unknown = null; + while (Date.now() - connectStart < connectTimeout) { + try { + cdp = await CdpConnection.connect(discovered.wsUrl, 5_000); + break; + } catch (e) { + lastConnErr = e; + if (verbose) logger.debug(`WebSocket connect attempt failed: ${e instanceof Error ? e.message : String(e)}, retrying...`); + await sleep(1000); + } + } + if (!cdp) { + if (verbose) logger.debug(`Could not connect to Chrome after ${connectTimeout / 1000}s: ${lastConnErr instanceof Error ? lastConnErr.message : String(lastConnErr)}`); + return null; + } const targets = await cdp.send<{ targetInfos: Array<{ targetId: string; url: string; type: string }> }>('Target.getTargets'); const hasGeminiTab = targets.targetInfos.some( diff --git a/skills/baoyu-danger-gemini-web/scripts/vendor/baoyu-chrome-cdp/src/index.ts b/skills/baoyu-danger-gemini-web/scripts/vendor/baoyu-chrome-cdp/src/index.ts index ab6bf5a..b455324 100644 --- a/skills/baoyu-danger-gemini-web/scripts/vendor/baoyu-chrome-cdp/src/index.ts +++ b/skills/baoyu-danger-gemini-web/scripts/vendor/baoyu-chrome-cdp/src/index.ts @@ -45,6 +45,11 @@ type FindExistingChromeDebugPortOptions = { export type ChromeChannel = "stable" | "beta" | "canary" | "dev"; +export type DiscoveredChrome = { + port: number; + wsUrl: string; +}; + type DiscoverRunningChromeOptions = { channels?: ChromeChannel[]; timeoutMs?: number; @@ -180,16 +185,33 @@ async function isDebugPortReady(port: number, timeoutMs = 3_000): Promise { + return new Promise((resolve) => { + const socket = new net.Socket(); + const timer = setTimeout(() => { socket.destroy(); resolve(false); }, timeoutMs); + socket.once("connect", () => { clearTimeout(timer); socket.destroy(); resolve(true); }); + socket.once("error", () => { clearTimeout(timer); resolve(false); }); + socket.connect(port, "127.0.0.1"); + }); +} + +function parseDevToolsActivePort(filePath: string): { port: number; wsPath: string } | null { + try { + const content = fs.readFileSync(filePath, "utf-8"); + const lines = content.split(/\r?\n/); + const port = Number.parseInt(lines[0]?.trim() ?? "", 10); + const wsPath = lines[1]?.trim(); + if (port > 0 && wsPath) return { port, wsPath }; + } catch {} + return null; +} + export async function findExistingChromeDebugPort(options: FindExistingChromeDebugPortOptions): Promise { const timeoutMs = options.timeoutMs ?? 3_000; - const portFile = path.join(options.profileDir, "DevToolsActivePort"); + const parsed = parseDevToolsActivePort(path.join(options.profileDir, "DevToolsActivePort")); - try { - const content = fs.readFileSync(portFile, "utf-8"); - const [portLine] = content.split(/\r?\n/); - const port = Number.parseInt(portLine?.trim() ?? "", 10); - if (port > 0 && await isDebugPortReady(port, timeoutMs)) return port; - } catch {} + if (parsed && await isPortListening(parsed.port, timeoutMs)) return parsed.port; + if (parsed && parsed.port > 0 && await isDebugPortReady(parsed.port, timeoutMs)) return parsed.port; if (process.platform === "win32") return null; @@ -248,19 +270,17 @@ export function getDefaultChromeUserDataDirs(channels: ChromeChannel[] = ["stabl return dirs; } -export async function discoverRunningChromeDebugPort(options: DiscoverRunningChromeOptions = {}): Promise { +export async function discoverRunningChromeDebugPort(options: DiscoverRunningChromeOptions = {}): Promise { const channels = options.channels ?? ["stable", "beta", "canary", "dev"]; const timeoutMs = options.timeoutMs ?? 3_000; const userDataDirs = getDefaultChromeUserDataDirs(channels); for (const dir of userDataDirs) { - const portFile = path.join(dir, "DevToolsActivePort"); - try { - const content = fs.readFileSync(portFile, "utf-8"); - const [portLine] = content.split(/\r?\n/); - const port = Number.parseInt(portLine?.trim() ?? "", 10); - if (port > 0 && await isDebugPortReady(port, timeoutMs)) return port; - } catch {} + const parsed = parseDevToolsActivePort(path.join(dir, "DevToolsActivePort")); + if (!parsed) continue; + if (await isPortListening(parsed.port, timeoutMs)) { + return { port: parsed.port, wsUrl: `ws://127.0.0.1:${parsed.port}${parsed.wsPath}` }; + } } if (process.platform !== "win32") { @@ -274,7 +294,12 @@ export async function discoverRunningChromeDebugPort(options: DiscoverRunningChr for (const line of lines) { const portMatch = line.match(/--remote-debugging-port=(\d+)/); const port = Number.parseInt(portMatch?.[1] ?? "", 10); - if (port > 0 && await isDebugPortReady(port, timeoutMs)) return port; + if (port > 0 && await isDebugPortReady(port, timeoutMs)) { + try { + const version = await fetchJson<{ webSocketDebuggerUrl?: string }>(`http://127.0.0.1:${port}/json/version`, { timeoutMs }); + if (version.webSocketDebuggerUrl) return { port, wsUrl: version.webSocketDebuggerUrl }; + } catch {} + } } } } catch {} From b7bcc8c09479357a29b2c8fb4318c40311ec3148 Mon Sep 17 00:00:00 2001 From: Alec Jiang Date: Mon, 16 Mar 2026 18:11:37 +0800 Subject: [PATCH 4/6] fix(baoyu-chrome-cdp): keep HTTP validation in findExistingChromeDebugPort findExistingChromeDebugPort callers (fetch_google_cookies_via_cdp) depend on /json/version being available. TCP-only check could misclassify non-CDP listeners as reusable, causing waitForChromeDebugPort to timeout instead of falling back to launching a new Chrome. Restore isDebugPortReady (HTTP) as the validator for this function; TCP-only check remains in discoverRunningChromeDebugPort for Chrome 146 approval mode. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/baoyu-chrome-cdp/src/index.ts | 1 - .../scripts/vendor/baoyu-chrome-cdp/src/index.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/baoyu-chrome-cdp/src/index.ts b/packages/baoyu-chrome-cdp/src/index.ts index b455324..ee4fd71 100644 --- a/packages/baoyu-chrome-cdp/src/index.ts +++ b/packages/baoyu-chrome-cdp/src/index.ts @@ -210,7 +210,6 @@ export async function findExistingChromeDebugPort(options: FindExistingChromeDeb const timeoutMs = options.timeoutMs ?? 3_000; const parsed = parseDevToolsActivePort(path.join(options.profileDir, "DevToolsActivePort")); - if (parsed && await isPortListening(parsed.port, timeoutMs)) return parsed.port; if (parsed && parsed.port > 0 && await isDebugPortReady(parsed.port, timeoutMs)) return parsed.port; if (process.platform === "win32") return null; diff --git a/skills/baoyu-danger-gemini-web/scripts/vendor/baoyu-chrome-cdp/src/index.ts b/skills/baoyu-danger-gemini-web/scripts/vendor/baoyu-chrome-cdp/src/index.ts index b455324..ee4fd71 100644 --- a/skills/baoyu-danger-gemini-web/scripts/vendor/baoyu-chrome-cdp/src/index.ts +++ b/skills/baoyu-danger-gemini-web/scripts/vendor/baoyu-chrome-cdp/src/index.ts @@ -210,7 +210,6 @@ export async function findExistingChromeDebugPort(options: FindExistingChromeDeb const timeoutMs = options.timeoutMs ?? 3_000; const parsed = parseDevToolsActivePort(path.join(options.profileDir, "DevToolsActivePort")); - if (parsed && await isPortListening(parsed.port, timeoutMs)) return parsed.port; if (parsed && parsed.port > 0 && await isDebugPortReady(parsed.port, timeoutMs)) return parsed.port; if (process.platform === "win32") return null; From bd01c370c9050e93fd93bd29d20eb183b95dd5c5 Mon Sep 17 00:00:00 2001 From: Alec Jiang Date: Mon, 16 Mar 2026 18:39:53 +0800 Subject: [PATCH 5/6] fix(gemini-web): respect explicit profile config over auto-discovery Skip existing Chrome auto-discovery when GEMINI_WEB_CHROME_PROFILE_DIR is explicitly set, to avoid binding to the wrong browser profile/account. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../scripts/gemini-webapi/utils/load-browser-cookies.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/skills/baoyu-danger-gemini-web/scripts/gemini-webapi/utils/load-browser-cookies.ts b/skills/baoyu-danger-gemini-web/scripts/gemini-webapi/utils/load-browser-cookies.ts index cd637c1..3d7ac76 100644 --- a/skills/baoyu-danger-gemini-web/scripts/gemini-webapi/utils/load-browser-cookies.ts +++ b/skills/baoyu-danger-gemini-web/scripts/gemini-webapi/utils/load-browser-cookies.ts @@ -265,7 +265,8 @@ export async function load_browser_cookies(domain_name: string = '', verbose: bo if (cached) return { chrome: cached }; } - const existingCookies = await fetch_cookies_from_existing_chrome(30_000, verbose); + const hasExplicitProfile = !!process.env.GEMINI_WEB_CHROME_PROFILE_DIR?.trim(); + const existingCookies = hasExplicitProfile ? null : await fetch_cookies_from_existing_chrome(30_000, verbose); if (existingCookies) { const filtered: CookieMap = {}; for (const [key, value] of Object.entries(existingCookies)) { From 462d080a0ecd1b829e5e9c13a568baf5bbf68f4b Mon Sep 17 00:00:00 2001 From: Alec Jiang Date: Mon, 16 Mar 2026 19:02:58 +0800 Subject: [PATCH 6/6] fix(gemini-web): also respect BAOYU_CHROME_PROFILE_DIR in auto-discovery skip Both GEMINI_WEB_CHROME_PROFILE_DIR and BAOYU_CHROME_PROFILE_DIR are valid profile overrides (see resolveGeminiWebChromeProfileDir). Skip existing Chrome auto-discovery when either is set. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../scripts/gemini-webapi/utils/load-browser-cookies.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skills/baoyu-danger-gemini-web/scripts/gemini-webapi/utils/load-browser-cookies.ts b/skills/baoyu-danger-gemini-web/scripts/gemini-webapi/utils/load-browser-cookies.ts index 3d7ac76..6b9a51a 100644 --- a/skills/baoyu-danger-gemini-web/scripts/gemini-webapi/utils/load-browser-cookies.ts +++ b/skills/baoyu-danger-gemini-web/scripts/gemini-webapi/utils/load-browser-cookies.ts @@ -265,7 +265,7 @@ export async function load_browser_cookies(domain_name: string = '', verbose: bo if (cached) return { chrome: cached }; } - const hasExplicitProfile = !!process.env.GEMINI_WEB_CHROME_PROFILE_DIR?.trim(); + const hasExplicitProfile = !!(process.env.GEMINI_WEB_CHROME_PROFILE_DIR?.trim() || process.env.BAOYU_CHROME_PROFILE_DIR?.trim()); const existingCookies = hasExplicitProfile ? null : await fetch_cookies_from_existing_chrome(30_000, verbose); if (existingCookies) { const filtered: CookieMap = {};