This commit is contained in:
Derek 2026-07-14 21:00:22 -04:00 committed by GitHub
commit 74ade8e7fc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 20 additions and 3 deletions

View File

@ -369,6 +369,16 @@ export class BrowserManager {
console.log(`[browse] Extensions loaded from: ${extensionsDir}`);
}
// Windows: the default headless path launches chrome-headless-shell.exe — a
// CONSOLE-subsystem binary that pops a terminal window on every launch. Switch
// to Chrome's "new" headless (full chrome.exe, GUI-subsystem → no console
// window) by launching non-headless with --headless=new. Other platforms keep
// the lighter headless shell (they have no console-window problem).
if (process.platform === 'win32' && useHeadless) {
launchArgs.push('--headless=new');
useHeadless = false;
}
this.browser = await chromium.launch({
headless: useHeadless,
// On Windows, Chromium's sandbox fails when the server is spawned through

View File

@ -323,9 +323,11 @@ async function startServer(extraEnv?: Record<string, string>): Promise<ServerSta
const launcherCode =
`const{spawn}=require('child_process');` +
`spawn(process.execPath,[${JSON.stringify(NODE_SERVER_SCRIPT)}],` +
`{detached:true,stdio:['ignore','ignore','ignore'],env:Object.assign({},process.env,` +
// windowsHide: a detached spawn on Windows pops a console window unless
// explicitly hidden — this flashed on every daemon (re)start.
`{detached:true,windowsHide:true,stdio:['ignore','ignore','ignore'],env:Object.assign({},process.env,` +
`${extraEnvStr})}).unref()`;
Bun.spawnSync(['node', '-e', launcherCode], { stdio: ['ignore', 'ignore', 'ignore'] });
Bun.spawnSync(['node', '-e', launcherCode], { stdio: ['ignore', 'ignore', 'ignore'], windowsHide: true });
} else {
// macOS/Linux: Bun.spawn().unref() only removes the child from Bun's event
// loop — it does NOT call setsid(), so the spawned server stays in the

View File

@ -16,6 +16,7 @@
*/
import * as fs from 'fs';
import * as path from 'path';
import { spawn as nodeSpawn } from 'child_process';
import { safeUnlink, safeKill, isProcessAlive } from './error-handling';
import { writeSecureFile, mkdirSecure } from './file-permissions';
@ -68,7 +69,10 @@ export function spawnTerminalAgent(opts: {
}
const script = opts.scriptPath || resolveTerminalAgentScript();
if (!script || !fs.existsSync(script)) return null;
const proc = (Bun as any).spawn(['bun', 'run', script], {
// Spawn via Node's child_process (not Bun.spawn): Node's `windowsHide` reliably
// suppresses the console window on Windows, whereas Bun's does not. Same lifecycle:
// an unref'd child so it doesn't hold the parent's event loop open.
const proc = nodeSpawn('bun', ['run', script], {
cwd: opts.cwd || process.cwd(),
env: {
...process.env,
@ -77,6 +81,7 @@ export function spawnTerminalAgent(opts: {
...(opts.extraEnv || {}),
},
stdio: ['ignore', 'ignore', 'ignore'],
windowsHide: true,
});
proc.unref?.();
return proc.pid ?? null;