Files
PaperClipAI/packages/paperclip-runner/test/acpx-codex-package-contract.test.mjs
941a3fa991 Pin Copilot native dependencies with the maintained lockfile refresh (#15572)
Pin the three optional GitHub Copilot 1.0.88 native packages for Runner and server using the maintained lockfile workflow. Synchronize the package contract and bound initial render readiness in the deliberately throttled browser fixture. Current-head CI and focused checks pass.

Co-Authored-By: Dotta <cryppadotta@users.noreply.github.com>
Co-Authored-By: lockfile-bot <lockfile-bot@users.noreply.github.com>
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-08 09:37:53 -05:00

219 lines
8.1 KiB
JavaScript

import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { readFile } from "node:fs/promises";
import { createRequire } from "node:module";
import { dirname, resolve } from "node:path";
import test from "node:test";
const runnerPackage = JSON.parse(
await readFile(new URL("../package.json", import.meta.url), "utf8"),
);
const rootPackage = JSON.parse(
await readFile(new URL("../../../package.json", import.meta.url), "utf8"),
);
const workspace = await readFile(
new URL("../../../pnpm-workspace.yaml", import.meta.url),
"utf8",
);
const acpxPatch = await readFile(
new URL("../../../patches/acpx@0.13.1.patch", import.meta.url),
"utf8",
);
const codexPatch = await readFile(
new URL(
"../../../patches/@agentclientprotocol__codex-acp@1.6.2.patch",
import.meta.url,
),
"utf8",
);
const claudePatch = await readFile(
new URL(
"../../../patches/@agentclientprotocol__claude-agent-acp@0.73.0.patch",
import.meta.url,
),
"utf8",
);
const profiles = JSON.parse(await readFile(new URL("../acpx-profiles.json", import.meta.url), "utf8"));
const runnerdProfiles = await readFile(new URL("../runner/crates/runner-core/src/generated_acpx_profiles.rs", import.meta.url), "utf8");
const providerPackBuilder = await readFile(
new URL("../scripts/build-provider-pack.mjs", import.meta.url),
"utf8",
);
const nativeSessionExecutor = await readFile(
new URL(
"../../../server/src/services/native-runtime/native-session-executor.ts",
import.meta.url,
),
"utf8",
);
test("the runner pins every qualified ACPX production dependency", () => {
assert.equal(runnerPackage.dependencies["@openai/codex"], "0.160.0");
assert.equal(runnerPackage.dependencies["@anthropic-ai/claude-agent-sdk"], undefined);
assert.equal(rootPackage.pnpm.overrides["@agentclientprotocol/codex-acp@1.6.2>@openai/codex"], runnerPackage.dependencies["@openai/codex"]);
assert.equal(rootPackage.pnpm.overrides["@agentclientprotocol/claude-agent-acp@0.73.0>@anthropic-ai/claude-agent-sdk"], "0.3.286");
assert.deepEqual(runnerPackage.optionalDependencies, {
"@github/copilot-darwin-arm64": "1.0.88",
"@github/copilot-darwin-x64": "1.0.88",
"@github/copilot-linux-x64": "1.0.88",
});
assert.equal(runnerPackage.dependencies.node, undefined);
assert.equal(runnerPackage.dependencies.acpx, "0.13.1");
assert.equal(
runnerPackage.dependencies["@agentclientprotocol/codex-acp"],
"1.6.2",
);
assert.equal(
runnerPackage.dependencies["@agentclientprotocol/claude-agent-acp"],
"0.73.0",
);
});
test("the patched Codex ACP executable digest stays aligned across launch boundaries", async () => {
const digest = profiles.profiles.codex.commandDigest;
const packagePath = createRequire(import.meta.url).resolve("@agentclientprotocol/codex-acp/package.json");
const installed = JSON.parse(await readFile(packagePath, "utf8"));
const executable = await readFile(resolve(dirname(packagePath), installed.bin["codex-acp"]));
assert.equal(digest, `sha256:${createHash("sha256").update(executable).digest("hex")}`,
"the identity binds installed executable bytes, not the patch file");
assert.match(runnerdProfiles, new RegExp(`"codex"[\\s\\S]*?${digest}`));
assert.match(providerPackBuilder, /codex:\s*profiles\.profiles\.codex\.commandDigest/);
assert.match(nativeSessionExecutor, /codex:\s*QUALIFIED_ACPX_PROFILES\.codex\.commandDigest/);
});
test("the package exposes only the reviewed runner CLI binaries", () => {
assert.deepEqual(runnerPackage.bin, {
"paperclip-runner-eval-session": "./dist/cli/eval-session.js",
"paperclip-runner-codex-proxy": "./dist/cli/codex-app-server-unix-proxy.js",
"paperclip-runner-acpx-sidecar": "./dist/cli/acpx-runtime-sidecar.js",
"paperclip-runner-opencode-proxy":
"./dist/cli/opencode-app-server-proxy.js",
});
});
test("old and new pnpm configuration both apply the exact runtime patches", () => {
assert.equal(
rootPackage.pnpm.patchedDependencies["acpx@0.13.1"],
"patches/acpx@0.13.1.patch",
);
assert.equal(
rootPackage.pnpm.patchedDependencies[
"@agentclientprotocol/claude-agent-acp@0.73.0"
],
"patches/@agentclientprotocol__claude-agent-acp@0.73.0.patch",
);
assert.equal(
rootPackage.pnpm.patchedDependencies[
"@agentclientprotocol/codex-acp@1.6.2"
],
"patches/@agentclientprotocol__codex-acp@1.6.2.patch",
);
assert.match(workspace, /acpx@0\.13\.1: patches\/acpx@0\.13\.1\.patch/);
assert.match(
workspace,
/codex-acp@1\.6\.2["']: patches\/@agentclientprotocol__codex-acp@1\.6\.2\.patch/,
);
assert.match(
workspace,
/claude-agent-acp@0\.73\.0["']: patches\/@agentclientprotocol__claude-agent-acp@0\.73\.0\.patch/,
);
assert.equal(rootPackage.pnpm.patchedDependencies["node@24.11.0"], undefined);
assert.doesNotMatch(workspace, /node@24\.11\.0:/);
assert.match(
providerPackBuilder,
/copyFileSync\(process\.execPath, stableNodeCommand\)/,
);
assert.match(codexPatch, /\+ "@openai\/codex": "0\.160\.0"/);
});
test("the ACPX patch preserves launch-only state and verified spawning", () => {
for (const token of [
"spawnEnvironment",
"spawnCwd",
"spawnAgent",
"SpawnOptionsWithoutStdio",
"this.options.spawnAgent",
]) {
assert.match(acpxPatch, new RegExp(token));
}
});
test("the ACPX patch fails closed on an invalid spawn environment", () => {
for (const token of [
"isPlainStringEnvironment",
"Object.getPrototypeOf(value)",
'Object.values(value).every((entry) => typeof entry === "string")',
"spawnEnvironment !== void 0",
"sourceEnvironment = spawnEnvironment()",
"ACPX spawn environment must be a plain record of string values",
]) {
assert.match(
acpxPatch,
new RegExp(token.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")),
);
}
assert.doesNotMatch(acpxPatch, /spawnEnvironment\?\.\(\)/);
assert.doesNotMatch(
acpxPatch,
/spawnEnvironment \? \{ \.\.\.spawnEnvironment \} : \{ \.\.\.process\.env \}/,
);
});
test("authentication rejects invalid isolated environments without host fallback", () => {
const addedSource = acpxPatch.split("\n")
.filter((line) => line.startsWith("+") && !line.startsWith("+++"))
.map((line) => line.slice(1)).join("\n");
const start = addedSource.indexOf("function isPlainStringEnvironment(value)");
const end = addedSource.indexOf("function buildAgentEnvironment(", start);
assert.ok(start >= 0 && end > start);
const hostEnvironment = { XAI_API_KEY: "host-credential-must-not-leak" };
const resolveEnvironment = new Function(
"process", `${addedSource.slice(start, end)}; return resolveAgentEnvironment;`,
)({ env: hostEnvironment });
for (const invalid of [undefined, null, [], "invalid", { XAI_API_KEY: 1 }]) {
assert.throws(() => resolveEnvironment(() => invalid), TypeError);
}
const isolated = {};
assert.equal(resolveEnvironment(() => isolated), isolated);
assert.equal(resolveEnvironment(() => isolated).XAI_API_KEY, undefined);
assert.equal(resolveEnvironment(undefined), hostEnvironment);
assert.match(addedSource, /readEnvCredential\(method\.id, resolveAgentEnvironment\(this\.options\.spawnEnvironment\)\)/);
assert.match(addedSource, /resolveAgentEnvironment\(this\.options\.spawnEnvironment\)\)\.XAI_API_KEY/);
});
test("the Codex patch enforces isolated instructions, tools, and skills", () => {
for (const token of [
"PAPERCLIP_ACPX_ISOLATED_CONTEXT",
"baseInstructions",
"rawInput: { serverName: params.serverName }",
'"features.apps": false',
"process.env.CODEX_HOME",
]) {
assert.match(
codexPatch,
new RegExp(token.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")),
);
}
});
test("the Codex patch keeps MCP tool approvals on the governed permission channel", () => {
assert.match(
codexPatch,
/!context\.isToolApproval && this\.shouldUseAcpElicitation\(params\)/,
);
});
test("the Claude patch removes ambient project and local configuration", () => {
for (const token of [
"PAPERCLIP_ACPX_ISOLATED_CONTEXT",
'settingSources: ["user"]',
"userProvidedOptions?.mcpServers",
]) {
assert.match(
claudePatch,
new RegExp(token.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")),
);
}
});