diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 5597d222b..a6e8390ed 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed broker-owned headless Chromium opening and retaining an unowned blank foreground window on Windows ([#8615](https://github.com/can1357/oh-my-pi/issues/8615)). + ## [17.3.4] - 2026-08-14 ### Changed diff --git a/packages/coding-agent/src/tools/browser/launch.ts b/packages/coding-agent/src/tools/browser/launch.ts index 4c10635e1..bcf188df6 100644 --- a/packages/coding-agent/src/tools/browser/launch.ts +++ b/packages/coding-agent/src/tools/browser/launch.ts @@ -430,8 +430,8 @@ export interface SharedBrowserLaunchSpec { * Resolve the executable and complete argv for a shared Chromium the daemon * broker spawns directly (no puppeteer inside the broker). Mirrors * `launchHeadlessBrowser` flag assembly — puppeteer's default args minus the - * stealth-suppressed set — plus `--remote-debugging-port=0` so every client - * attaches over CDP. Returns null when no Chromium executable resolves; + * stealth-suppressed set — suppresses Puppeteer's unowned startup window, and + * exposes CDP on an ephemeral port. Returns null when no executable resolves; * callers fall back to a process-local launch. */ export async function resolveSharedBrowserLaunchSpec(opts: { @@ -451,7 +451,7 @@ export async function resolveSharedBrowserLaunchSpec(opts: { }); return { executablePath, - args: [...defaults.filter(arg => !ignored.has(arg)), "--remote-debugging-port=0"], + args: [...defaults.filter(arg => !ignored.has(arg)), "--no-startup-window", "--remote-debugging-port=0"], }; } diff --git a/packages/coding-agent/test/tools/browser-launch.test.ts b/packages/coding-agent/test/tools/browser-launch.test.ts index c168a7517..e8f2d33d1 100644 --- a/packages/coding-agent/test/tools/browser-launch.test.ts +++ b/packages/coding-agent/test/tools/browser-launch.test.ts @@ -4,6 +4,7 @@ import * as fs from "node:fs"; import * as path from "node:path"; import { chromiumExecutableProbeForTest, + resolveSharedBrowserLaunchSpec, stealthIgnoreDefaultArgsForTest, systemChromiumCandidatesForTest, } from "@oh-my-pi/pi-coding-agent/tools/browser/launch"; @@ -44,6 +45,24 @@ describe("browser launch stealth defaults", () => { }); }); +describe("shared browser launch", () => { + it("suppresses the broker-owned blank startup window", async () => { + const previousExecutable = process.env.PUPPETEER_EXECUTABLE_PATH; + process.env.PUPPETEER_EXECUTABLE_PATH = "/test/chrome"; + try { + const launch = await resolveSharedBrowserLaunchSpec({ + headless: true, + userDataDir: "/test/profile", + }); + + expect(launch?.args).toContain("--no-startup-window"); + } finally { + if (previousExecutable === undefined) delete process.env.PUPPETEER_EXECUTABLE_PATH; + else process.env.PUPPETEER_EXECUTABLE_PATH = previousExecutable; + } + }); +}); + const UNGOOGLED_CHROMIUM_FLATPAK_ID = "io.github.ungoogled_software.ungoogled_chromium"; describe("system Chromium candidates", () => {