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..46a6018 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: () => ({ @@ -266,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 });