From fb83b26adda9412520049e6cf6b5d8a6971bd1b3 Mon Sep 17 00:00:00 2001 From: waml <14224835+waml@users.noreply.github.com> Date: Sun, 17 May 2026 20:31:25 -0700 Subject: [PATCH 1/2] Fix Windows Chrome CDP spawn --- CHANGELOG.md | 6 ++++++ chrome-cdp-backend.mjs | 9 +++++---- package-lock.json | 4 ++-- package.json | 2 +- tests/chrome-cdp-backend.test.mjs | 8 +++++++- 5 files changed, 21 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49a65ab..8c8acf0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 0.2.4 - 2026-05-17 + +### Fixed +- Fixed Windows Chrome CDP startup by spawning the Chrome/Edge/Brave executable directly without a shell. +- Added a regression test to keep Chrome CDP spawn options shell-free on every platform. + ## 0.2.3 - 2026-05-17 ### Fixed diff --git a/chrome-cdp-backend.mjs b/chrome-cdp-backend.mjs index 57d993e..e84f609 100644 --- a/chrome-cdp-backend.mjs +++ b/chrome-cdp-backend.mjs @@ -131,6 +131,10 @@ export function buildChromeLaunchArgs({ debugPort, userDataDir, profileName = nu return args; } +export function chromeSpawnOptions() { + return { stdio: 'ignore' }; +} + async function readJson(url) { const response = await fetch(url, { headers: { accept: 'application/json' } }); if (!response.ok) { @@ -550,10 +554,7 @@ export class ChromeCdpBrowserBackend { profileName: this.profileName, startUrl: 'about:blank' }); - this.chromeProcess = spawn(executable, args, { - stdio: 'ignore', - shell: process.platform === 'win32' - }); + this.chromeProcess = spawn(executable, args, chromeSpawnOptions()); this.chromeProcess.unref?.(); let version; diff --git a/package-lock.json b/package-lock.json index 766e16f..eb8dc13 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@agentify/desktop", - "version": "0.2.3", + "version": "0.2.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@agentify/desktop", - "version": "0.2.3", + "version": "0.2.4", "license": "MPL-2.0", "dependencies": { "@modelcontextprotocol/sdk": "^1.29.0", diff --git a/package.json b/package.json index 7020cbf..a9f21c6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@agentify/desktop", - "version": "0.2.3", + "version": "0.2.4", "description": "Agentify Desktop control center and MCP server for local AI web sessions", "license": "MPL-2.0", "type": "module", diff --git a/tests/chrome-cdp-backend.test.mjs b/tests/chrome-cdp-backend.test.mjs index 55717ac..0c53077 100644 --- a/tests/chrome-cdp-backend.test.mjs +++ b/tests/chrome-cdp-backend.test.mjs @@ -4,7 +4,7 @@ import os from 'node:os'; import path from 'node:path'; import fs from 'node:fs/promises'; -import { ChromeCdpBrowserBackend, ChromeCdpConnection } from '../chrome-cdp-backend.mjs'; +import { ChromeCdpBrowserBackend, ChromeCdpConnection, chromeSpawnOptions } from '../chrome-cdp-backend.mjs'; class MockWebSocket { constructor() { @@ -84,6 +84,12 @@ test('chrome-cdp-backend: pending commands reject when websocket closes', async await assert.rejects(async () => await pending, /chrome_cdp_disconnected/); }); +test('chrome-cdp-backend: Chrome spawn does not use shell on any platform', () => { + const opts = chromeSpawnOptions(); + assert.equal(opts.stdio, 'ignore'); + assert.equal(Object.hasOwn(opts, 'shell'), false); +}); + test('chrome-cdp-backend: connect rejects if websocket closes before open', async () => { const conn = new ChromeCdpConnection('ws://example.test/devtools/browser/1', { wsFactory: () => ({ From 4ac04dfeb1237f65a96d1d622fc2a08dd28a8404 Mon Sep 17 00:00:00 2001 From: waml <14224835+waml@users.noreply.github.com> Date: Sun, 17 May 2026 20:34:09 -0700 Subject: [PATCH 2/2] Make Chrome CDP cleanup test Windows portable --- tests/chrome-cdp-backend.test.mjs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/chrome-cdp-backend.test.mjs b/tests/chrome-cdp-backend.test.mjs index 0c53077..46a6018 100644 --- a/tests/chrome-cdp-backend.test.mjs +++ b/tests/chrome-cdp-backend.test.mjs @@ -272,12 +272,15 @@ test('chrome-cdp-backend: session close is best-effort when closeTarget fails', test('chrome-cdp-backend: start cleans up spawned chrome process when CDP connect fails', async () => { const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'agentify-chrome-start-fail-')); - const scriptPath = path.join(tmpDir, 'fake-chrome.sh'); - await fs.writeFile(scriptPath, '#!/bin/sh\nsleep 30\n', { encoding: 'utf8', mode: 0o755 }); + let executablePath = process.execPath; + if (process.platform !== 'win32') { + executablePath = path.join(tmpDir, 'fake-chrome.sh'); + await fs.writeFile(executablePath, '#!/bin/sh\nsleep 30\n', { encoding: 'utf8', mode: 0o755 }); + } const backend = new ChromeCdpBrowserBackend({ stateDir: tmpDir, - executablePath: scriptPath, + executablePath, debugPort: 45999 });