'use strict';
* WINDOWS AGENT-TO-AGENT MESSAGING — the argv path.
*
* On Windows a `.cmd` cannot be handed to CreateProcess, so every npm-installed
* engine CLI (OpenCode always; `claude.cmd` whenever there is no native
* `claude.exe`) used to be spawned as `cmd.exe /d /s /c "<one big string>"`. The
* hive protocol is injected on argv (`--append-system-prompt <prompt>` /
* `--prompt <prompt>`) and that prompt is MULTI-LINE, paren-heavy and quote-heavy.
* cmd.exe treats CR/LF as a statement separator before quoting is even considered,
* so the prompt was truncated at its first newline: the agent booted looking
* perfectly healthy, never received the HIVE PROTOCOL block, never learned that
* `inbox/`/`outbox/` existed — and therefore no agent ever messaged another.
*
* The fix decodes the npm shim to the interpreter + script it wraps and spawns
* THAT with an argv ARRAY, so node-pty's `argsToCommandLine` (MSDN/CRT escaping)
* runs and CreateProcess receives the command line directly — no shell parser
* anywhere. These tests pin both halves: what the shim parser accepts/refuses, and
* that the array path really does round-trip a hostile prompt.
*
* Everything here is host-independent on purpose (win32 path handling and pure
* string escaping), because the whole Windows build is authored on macOS.
*/
const test = require('node:test');
const assert = require('node:assert/strict');
const loadTs = require('./load-ts.cjs');
const { parseNpmCmdShim, buildCmdCommandLine, PtyManager } = loadTs('src/main/pty.ts');
const { argsToCommandLine } = require('node-pty/lib/windowsPtyAgent.js');
const NPM_DIR = 'C:\\Users\\Tester\\AppData\\Roaming\\npm';
const SHIM = `${NPM_DIR}\\opencode.cmd`;
const MODERN = [
'@ECHO off',
'GOTO start',
':find_dp0',
'SET dp0=%~dp0',
'EXIT /b',
':start',
'SETLOCAL',
'CALL :find_dp0',
'',
'IF EXIST "%dp0%\\node.exe" (',
' SET "_prog=%dp0%\\node.exe"',
') ELSE (',
' SET "_prog=node"',
' SET PATHEXT=%PATHEXT:;.JS;=;%',
')',
'',
'endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\\node_modules\\opencode-ai\\bin\\opencode.js" %*'
].join('\r\n');
const CLASSIC = [
'@IF EXIST "%~dp0\\node.exe" (',
' "%~dp0\\node.exe" "%~dp0\\node_modules\\opencode-ai\\bin\\opencode.js" %*',
') ELSE (',
' @SETLOCAL',
' @SET PATHEXT=%PATHEXT:;.JS;=;%',
' node "%~dp0\\node_modules\\opencode-ai\\bin\\opencode.js" %*',
')'
].join('\r\n');
const ANCIENT = '@"%~dp0\\node.exe" "%~dp0\\node_modules\\npm\\bin\\npm-cli.js" %*\r\n';
const PNPM = [
'@IF EXIST "%~dp0\\node.exe" (',
' "%~dp0\\node.exe" "%~dp0\\..\\opencode-ai\\bin\\opencode.js" %*',
') ELSE (',
' node "%~dp0\\..\\opencode-ai\\bin\\opencode.js" %*',
')'
].join('\n');
test('modern npm shim → the interpreter and script it really runs', () => {
assert.deepEqual(parseNpmCmdShim(SHIM, MODERN), {
interpreter: 'node',
scriptPath: `${NPM_DIR}\\node_modules\\opencode-ai\\bin\\opencode.js`
});
});
test('classic npm 5/6 shim resolves through its ELSE arm', () => {
assert.deepEqual(parseNpmCmdShim(SHIM, CLASSIC), {
interpreter: 'node',
scriptPath: `${NPM_DIR}\\node_modules\\opencode-ai\\bin\\opencode.js`
});
});
test('ancient single-line shim resolves', () => {
assert.deepEqual(parseNpmCmdShim(`${NPM_DIR}\\npm.cmd`, ANCIENT), {
interpreter: 'node',
scriptPath: `${NPM_DIR}\\node_modules\\npm\\bin\\npm-cli.js`
});
});
test('a `..` in the shim target is normalized, not passed through', () => {
const shim = `${NPM_DIR}\\node_modules\\.bin\\opencode.cmd`;
assert.deepEqual(parseNpmCmdShim(shim, PNPM), {
interpreter: 'node',
scriptPath: `${NPM_DIR}\\node_modules\\opencode-ai\\bin\\opencode.js`
});
});
test('LF-only content parses the same as CRLF', () => {
assert.deepEqual(
parseNpmCmdShim(SHIM, MODERN.replace(/\r\n/g, '\n')),
parseNpmCmdShim(SHIM, MODERN)
);
});
test('bun and deno shims are accepted; other interpreters are not', () => {
const bun = MODERN.replace(/node\.exe/g, 'bun.exe').replace('_prog=node', '_prog=bun');
assert.equal(parseNpmCmdShim(SHIM, bun).interpreter, 'bun');
const python = MODERN.replace(/node\.exe/g, 'python.exe').replace('_prog=node', '_prog=python');
assert.equal(parseNpmCmdShim(SHIM, python), null, 'not an interpreter we are willing to spawn');
});
test('unrecognised content returns null rather than a guess', () => {
const cases = {
'empty': '',
'not a shim at all': '@echo off\r\necho hello world\r\n',
'a real batch script with %* but no quoted target': '@echo off\r\nmytool.exe %*\r\n',
'no exec line': '@ECHO off\r\nSET dp0=%~dp0\r\n',
'unmodelled variable in the target': '"%_prog%" "%SOMETHING%\\cli.js" %*',
'%_prog% never assigned': '"%_prog%" "%dp0%\\node_modules\\x\\cli.js" %*',
'relative target': 'node "cli.js" %*',
'non-JS target': '"%_prog%" "%dp0%\\node_modules\\x\\bin\\tool.ps1" %*',
'NUL byte': `@ECHO off\0\r\n"%_prog%" "%dp0%\\a.js" %*`,
'too large to be a shim': `@ECHO off\r\n${'REM padding\r\n'.repeat(900)}"%_prog%" "%dp0%\\a.js" %*`
};
for (const [label, content] of Object.entries(cases)) {
assert.equal(parseNpmCmdShim(SHIM, content), null, `should refuse: ${label}`);
}
assert.equal(parseNpmCmdShim('', MODERN), null, 'no shim path');
assert.equal(parseNpmCmdShim(SHIM, null), null, 'non-string content');
});
test('interpreter flags between program and script are refused, never dropped', () => {
const withFlags = MODERN.replace(
'"%_prog%" "%dp0%',
'"%_prog%" --experimental-vm-modules "%dp0%'
);
assert.equal(parseNpmCmdShim(SHIM, withFlags), null);
});
test('the win32 branch is genuinely platform-gated', () => {
const mgr = new PtyManager();
const probe = mgr['resolveWindowsShimSpawn'].bind(mgr);
if (process.platform === 'win32') return;
assert.equal(probe(SHIM), null);
assert.equal(probe(`${NPM_DIR}\\opencode`), null);
});
* newlines, parentheses, embedded double quotes, a backslash run, `&`/`|`. */
const HOSTILE_PROMPT = [
'You are "Michael" (god-1), an autonomous agent in a collaborating hive.',
'Your private workspace is C:\\Users\\Tester\\.munder\\hive\\agents\\god-1.',
'',
'HIVE PROTOCOL — follow it every task:',
'1. Read EVERY file in C:\\Users\\Tester\\.munder\\hive\\agents\\god-1\\inbox (messages',
' other agents sent you) & move handled ones to inbox\\.done.',
'3. To ask another agent, write ONE message JSON into your outbox\\ (schema in',
' PROTOCOL.md) | NEVER write into another agent\'s folder.',
'Trailing backslash torture: C:\\dir\\'
].join('\n');
* Parse a Windows command line back into argv the way CommandLineToArgvW / the
* MSVC CRT does — 2n backslashes before a quote collapse to n and toggle quote
* state, 2n+1 collapse to n plus a literal quote, and ONLY space/tab delimit.
* This is the parser every child process on Windows effectively runs.
*/
function parseWindowsCommandLine(line) {
const argv = [];
let cur = '';
let inQuotes = false;
let started = false;
let i = 0;
while (i < line.length) {
const c = line[i];
if (c === '\\') {
let n = 0;
while (line[i] === '\\') { n++; i++; }
if (line[i] === '"') {
cur += '\\'.repeat(n >> 1);
if (n % 2 === 1) { cur += '"'; i++; } else { inQuotes = !inQuotes; i++; }
} else {
cur += '\\'.repeat(n);
}
started = true;
continue;
}
if (c === '"') { inQuotes = !inQuotes; started = true; i++; continue; }
if (!inQuotes && (c === ' ' || c === '\t')) {
if (started) { argv.push(cur); cur = ''; started = false; }
i++;
continue;
}
cur += c;
started = true;
i++;
}
if (started) argv.push(cur);
return argv;
}
test('ARRAY args: the whole prompt reaches the child intact', () => {
const nodeExe = 'C:\\Program Files\\nodejs\\node.exe';
const script = `${NPM_DIR}\\node_modules\\opencode-ai\\bin\\opencode.js`;
const argv = [nodeExe, script, '--prompt', HOSTILE_PROMPT];
const line = argsToCommandLine(nodeExe, argv.slice(1));
assert.deepEqual(parseWindowsCommandLine(line), argv, 'CRT round-trip must be exact');
assert.ok(line.includes('\n'), 'the newlines ride through as literal characters');
});
test('cmd.exe: the same prompt is destroyed — which is the bug', () => {
const line = buildCmdCommandLine(`${NPM_DIR}\\opencode.cmd`, ['--prompt', HOSTILE_PROMPT]);
const firstStatement = line.split('\n')[0];
assert.ok(line.includes('\n'), 'the argument really is multi-line');
assert.ok(
!firstStatement.includes('HIVE PROTOCOL'),
'the protocol block never reaches the agent — this is why Windows agents could not message each other'
);
assert.ok(
!firstStatement.includes('inbox'),
'the agent is never told its inbox exists'
);
const inner = firstStatement.slice('/d /s /c "'.length);
const atName = inner.indexOf('Michael');
assert.ok(atName > 0);
const quotesBefore = (inner.slice(0, atName).match(/"/g) || []).length;
assert.equal(
quotesBefore % 2,
0,
'the \\" produced here left cmd OUTSIDE quote state — it is not an escape, whatever the old comment claimed'
);
});
test('single-line args still go through cmd.exe unchanged (fallback preserved)', () => {
assert.equal(
buildCmdCommandLine('C:\\Program Files\\x\\tool.cmd', ['--model', 'gpt-5-codex']),
'/d /s /c ""C:\\Program Files\\x\\tool.cmd" --model gpt-5-codex"'
);
});
* DIRECT-EXECUTABLE shims — the shape that broke OpenCode on Windows.
*
* npm writes an interpreter-less shim whenever a package's `bin` target has no
* shebang, which is the norm for a compiled CLI. `opencode-ai` is exactly that:
* its bin is `./bin/opencode.exe`, a real binary. The resolver only modelled
* "interpreter + script", so it returned null for every Windows OpenCode install
* and the spawn fell back to cmd.exe — which truncates the multi-line hive
* protocol at its first newline. Agents booted looking healthy and had no idea
* they had an inbox.
*
* The shim body below is not hand-written: it is the verbatim output of npm's own
* `cmd-shim` package for a binary target.
*/
const DIRECT_EXE_SHIM = [
'@ECHO off',
'GOTO start',
':find_dp0',
'SET dp0=%~dp0',
'EXIT /b',
':start',
'SETLOCAL',
'CALL :find_dp0',
'"%dp0%\\..\\opencode-ai\\bin\\opencode.exe" %*'
].join('\r\n');
test('a direct-executable shim resolves to the binary, with no interpreter', () => {
const t = parseNpmCmdShim('C:\\Users\\r\\AppData\\Roaming\\npm\\opencode.cmd', DIRECT_EXE_SHIM);
assert.ok(t, 'the shape npm generates for a binary bin must be understood');
assert.equal(t.interpreter, null, 'null interpreter marks "run this directly"');
assert.equal(
t.scriptPath,
'C:\\Users\\r\\AppData\\Roaming\\npm\\..\\opencode-ai\\bin\\opencode.exe'
.replace(/\\[^\\]+\\\.\./, ''),
'the target is expanded and normalized to an absolute path'
);
});
test('the hive prompt survives a direct-executable shim, where cmd.exe destroys it', () => {
const prompt = 'You are "Michael" (god-1), an autonomous agent.\n\nHIVE PROTOCOL\n- inbox\n- outbox';
const line = argsToCommandLine('C:\\x\\opencode-ai\\bin\\opencode.exe', ['--prompt', prompt]);
assert.ok(line.includes('HIVE PROTOCOL'), 'the protocol block reaches the child intact');
assert.ok(line.includes('outbox'), 'including everything after the first newline');
const viaCmd = buildCmdCommandLine('C:\\x\\opencode.cmd', ['--prompt', prompt]);
assert.ok(viaCmd.split(/\r?\n/)[0].length < viaCmd.length, 'cmd.exe form is cut at a newline');
});
test('an interpreter-less shim pointing at a NON-executable is still refused', () => {
const odd = DIRECT_EXE_SHIM.replace('opencode.exe', 'opencode.txt');
assert.equal(parseNpmCmdShim('C:\\npm\\opencode.cmd', odd), null);
});