Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 86 additions & 5 deletions scripts/cross-environment.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import { mkdtempSync, mkdirSync, writeFileSync } from "node:fs";
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
import os from "node:os";
import path from "node:path";

Expand Down Expand Up @@ -184,10 +184,91 @@ function skip(reason: string): void {
// ---------------------------------------------------------------------------
{
assert.equal(openCodeCommand(), "opencode", "OpenCode keeps one executable name across desktop platforms");
const windowsLaunch = openCodeLaunch(["run", "safe & literal"], "win32", { SystemRoot: "C:\\Windows" });
assert.match(windowsLaunch.command, /WindowsPowerShell\\v1\.0\\powershell\.exe$/i, "Windows runs npm's opencode.cmd shim through PowerShell");
assert.equal(windowsLaunch.input, JSON.stringify(["run", "safe & literal"]), "Windows shell wrapper keeps chat input out of command syntax");
assert.match(windowsLaunch.args.at(-1) ?? "", /\[Console\]::In\.ReadToEnd\(\)/, "Windows reads OpenCode argv from stdin so long prompts do not exceed its command-line limit");
const hostileArgs = ["run", "--format", "json"];
const hostileInput = `${"x".repeat(40_000)}
review 😀
quotes: "double" 'single'
slashes: C:\\Program Files\\OpenCode\\bin
shell: ; rm -rf ~ | $(evil) && echo "%PATH%" > pwned <'quote\``;
const simulatedShim = "C:\\npm\\opencode.cmd";
const simulatedTarget = "C:\\npm\\node_modules\\opencode-ai\\bin\\opencode.exe";
const windowsLaunch = openCodeLaunch(
hostileArgs,
"win32",
{ Path: "C:\\npm", PATHEXT: ".COM;.EXE;.BAT;.CMD" },
{
statFile: (candidate) => candidate === simulatedShim,
resolveWindowsShim: (candidate) => {
assert.equal(candidate, simulatedShim);
return { command: simulatedTarget, fixedArgs: [] };
},
},
);
assert.equal(
windowsLaunch.command,
simulatedTarget,
"Windows resolves the npm command shim to its native package target",
);
assert.deepEqual(
windowsLaunch.args,
hostileArgs,
"Windows keeps the complete option argv as direct child-process data",
);
assert.doesNotMatch(
windowsLaunch.command,
/\.(?:cmd|bat|ps1)$/i,
"Windows never executes a shell shim between Cave and OpenCode stdin",
);

if (process.platform === "win32") {
// Execute the REAL npm .cmd resolution → direct native process chain. Cave
// reads the shim only to prove its package target, then bypasses every
// shell re-parse boundary while a >40K prompt travels over stdin.
const openCodeDir = mkdtempSync(path.join(os.tmpdir(), "opencode-conf-launch-"));
try {
writeFileSync(
path.join(openCodeDir, "opencode-shim.mjs"),
[
"let input = \"\";",
"process.stdin.setEncoding(\"utf8\");",
"for await (const chunk of process.stdin) input += chunk;",
"console.log(JSON.stringify({ args: process.argv.slice(2), input }));",
].join("\n"),
);
writeFileSync(
path.join(openCodeDir, "opencode.cmd"),
'"%dp0%\\node.exe" "%dp0%\\opencode-shim.mjs" %*\r\n',
);
const pathKey = Object.keys(process.env).find((key) => key.toLowerCase() === "path") ?? "Path";
const inheritedPath = process.env[pathKey] ?? "";
const env = {
...process.env,
[pathKey]: `${openCodeDir};${inheritedPath}`,
};
const realLaunch = openCodeLaunch(hostileArgs, process.platform, env);
const result = spawnSync(realLaunch.command, realLaunch.args, {
env,
encoding: "utf8",
input: hostileInput,
windowsHide: true,
});
assert.equal(
result.status,
0,
`Windows OpenCode launch failed:\n${result.stderr}\n${result.stdout}`,
);
assert.deepEqual(
JSON.parse(result.stdout.trim()),
{ args: hostileArgs, input: hostileInput },
"the resolved direct process preserves option argv and >40K multiline Unicode stdin exactly",
);
} finally {
rmSync(openCodeDir, { recursive: true, force: true });
}
} else {
skip("OpenCode Windows shim resolution: requires a Windows host (matrix runs it on windows-latest)");
}

assert.equal(openCodeNeedsTmpRuntimeDir("win32", {}), false, "Windows does not receive an XDG runtime directory");
assert.equal(openCodeNeedsTmpRuntimeDir("linux", {}), true, "headless Linux receives /tmp for OpenCode runtime files");
assert.equal(openCodeNeedsTmpRuntimeDir("linux", { XDG_RUNTIME_DIR: "/run/user/1000" }), false, "native Linux preserves its XDG runtime directory");
Expand Down
2 changes: 2 additions & 0 deletions scripts/run-tests.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1051,6 +1051,7 @@ export const SUITES = {
"src/app/api/chat/send/chat-send-capabilities.test.ts",
"src/app/api/chat/send/route-opencode.integration.test.ts",
"src/app/api/chat/send/route-grok-compatibility.integration.test.ts",
"src/app/api/chat/send/route-opencode-preflight.integration.test.ts",
"src/app/api/chat/send/route-runtime-availability.integration.test.ts",
"src/app/api/chat/send/route-codex-runtime-availability.integration.test.ts",
"src/app/api/chat/send/offline-queue.test.ts",
Expand Down Expand Up @@ -1325,6 +1326,7 @@ const ALIAS_LOADER = new Set([
"src/app/api/chat/send/chat-send-capabilities.test.ts",
"src/app/api/chat/send/route-opencode.integration.test.ts",
"src/app/api/chat/send/route-grok-compatibility.integration.test.ts",
"src/app/api/chat/send/route-opencode-preflight.integration.test.ts",
"src/app/api/chat/send/route-runtime-availability.integration.test.ts",
"src/app/api/chat/send/route-codex-runtime-availability.integration.test.ts",
"src/lib/familiar-workspace-sessions.test.ts",
Expand Down
26 changes: 21 additions & 5 deletions src/app/api/chat/send/chat-send-capabilities.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// @ts-nocheck
import assert from "node:assert/strict";
import { mkdirSync, mkdtempSync, rmSync, utimesSync, writeFileSync } from "node:fs";
import { mkdirSync, mkdtempSync, readFileSync, rmSync, utimesSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
import {
Expand All @@ -17,8 +17,21 @@ import {
parseOpenCodeRunCapabilitiesHelp,
} from "./chat-send-capabilities.ts";

const capabilitiesSource = readFileSync(new URL("./chat-send-capabilities.ts", import.meta.url), "utf8");

assert.match(
capabilitiesSource,
/probeOpenCodeRunContract[\s\S]*?evaluateRuntimeAvailability\(openCodeAvailabilityProbe\(helpLaunch, env\)\)\.state !== "ready"[\s\S]*?evaluateRuntimeAvailability\(openCodeAvailabilityProbe\(versionLaunch, env\)\)\.state !== "ready"[\s\S]*?probeOutput\(helpLaunch\.command/,
"run-contract probes passively verify both exact launch plans and required files before spawning",
);
assert.match(
capabilitiesSource,
/openCodeRunSupportsModel[\s\S]*?evaluateRuntimeAvailability\(openCodeAvailabilityProbe\(launch, env\)\)\.state !== "ready"[\s\S]*?probeHelp\(/,
"the model-flag probe passively verifies its exact launch plan and required files before spawning",
);

assert.equal(openCodeCapabilityProbeTimeoutMs("linux"), 2_500, "non-Windows capability probes retain the short bounded deadline");
assert.equal(openCodeCapabilityProbeTimeoutMs("win32"), 6_000, "Windows PowerShell/npm launchers receive a bounded cold-start allowance");
assert.equal(openCodeCapabilityProbeTimeoutMs("win32"), 6_000, "Windows native/npm launches receive a bounded cold-start allowance");
assert.equal(openCodeProbeCleanupGraceMs(), 1_000, "timed-out probe cleanup has a short final deadline so chat can fall back");
assert.equal(openCodeVerifiedCapabilityFallbackTtlMs(), 60_000, "only a short-lived verified OpenCode contract can survive a transient probe failure");
assert.equal(openCodeVerifiedCapabilityFallbackLimit(), 64, "verified capability evidence remains bounded across scoped launches");
Expand Down Expand Up @@ -112,13 +125,16 @@ if (process.platform === "win32") {
const packageBin = path.join(identityRoot, "node_modules", "opencode-ai", "bin");
mkdirSync(localBin, { recursive: true });
mkdirSync(packageBin, { recursive: true });
writeFileSync(path.join(localBin, "opencode.cmd"), "@echo off\r\n");
writeFileSync(path.join(localBin, "opencode.exe"), "direct-executable");
const packageExecutable = path.join(packageBin, "opencode.exe");
writeFileSync(packageExecutable, "package-target-one");
writeFileSync(
path.join(localBin, "opencode.cmd"),
'"%dp0%\\..\\opencode-ai\\bin\\opencode.exe" %*\r\n',
);
const cmdIdentity = await openCodeCapabilityLaunchIdentity({ PATH: localBin, PATHEXT: ".CMD;.EXE" }, "win32");
const exeIdentity = await openCodeCapabilityLaunchIdentity({ PATH: localBin, PATHEXT: ".EXE;.CMD" }, "win32");
assert.notEqual(cmdIdentity, exeIdentity, "PATHEXT order selects and fingerprints only the actual Windows launcher");
assert.notEqual(cmdIdentity, exeIdentity, "PATHEXT order selects and fingerprints only the exact shell-free target");
const timestamp = new Date("2026-01-01T00:00:00.000Z");
utimesSync(packageExecutable, timestamp, timestamp);
writeFileSync(packageExecutable, "package-target-two");
Expand Down Expand Up @@ -146,7 +162,7 @@ assert.equal(evictedFallback.probeStatus, "unavailable", "bounded evidence evict
assert.deepEqual(
openCodeProbeTreeKillCommand(4242, "win32"),
{ command: "taskkill.exe", args: ["/PID", "4242", "/T", "/F"] },
"timed-out Windows probes terminate their launcher tree rather than only PowerShell",
"timed-out Windows probes terminate the complete native process tree",
);
assert.equal(openCodeProbeTreeKillCommand(4242, "linux"), null, "non-Windows probes retain process-local termination");

Expand Down
Loading
Loading