From c0af2eb0e0caf0212ef9b6e2951d41f1ddb913a0 Mon Sep 17 00:00:00 2001 From: Dotta Date: Thu, 3 Sep 2026 04:48:12 -0500 Subject: [PATCH] fix(runner): reuse qualified provider Node --- packages/paperclip-runner/package.json | 5 --- .../scripts/build-provider-pack.mjs | 35 ++++++++++++------- .../test/acpx-codex-package-contract.test.mjs | 11 +++--- ...agentclientprotocol__codex-acp@1.6.2.patch | 12 +++++++ 4 files changed, 39 insertions(+), 24 deletions(-) diff --git a/packages/paperclip-runner/package.json b/packages/paperclip-runner/package.json index 7338a9fa06..38478ea72d 100644 --- a/packages/paperclip-runner/package.json +++ b/packages/paperclip-runner/package.json @@ -154,7 +154,6 @@ "dependencies": { "@agentclientprotocol/claude-agent-acp": "0.70.0", "@agentclientprotocol/codex-acp": "1.6.2", - "@openai/codex": "0.148.0", "acpx": "0.13.1", "ajv": "^8.20.0", "json-schema-to-ts": "^3.1.1", @@ -162,10 +161,6 @@ "react-markdown": "^10.1.0", "remark-gfm": "^4.0.1" }, - "optionalDependencies": { - "@openai/codex-linux-x64": "npm:@openai/codex@0.148.0-linux-x64", - "node-linux-x64": "24.11.0" - }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" diff --git a/packages/paperclip-runner/scripts/build-provider-pack.mjs b/packages/paperclip-runner/scripts/build-provider-pack.mjs index 0ced556372..b616674819 100644 --- a/packages/paperclip-runner/scripts/build-provider-pack.mjs +++ b/packages/paperclip-runner/scripts/build-provider-pack.mjs @@ -4,6 +4,7 @@ import { chmodSync, copyFileSync, existsSync, + mkdirSync, mkdtempSync, readdirSync, readFileSync, @@ -126,21 +127,31 @@ try { throw new Error(`pnpm deploy failed with exit code ${deployed.status}`); } - // The generic `node` npm package runs an install-time downloader. Depend on - // the immutable Linux x64 artifact directly, then expose it at the stable - // pack-owned path consumed by the verified launch contract. - const packagedNodeRoot = join( - temporaryRoot, - "node_modules", - "node-linux-x64", - ); + // Reuse the already-qualified build interpreter instead of introducing a + // package-manager lifecycle hook or a second binary supply chain. The pack + // manifest binds the copied bytes, platform, architecture, and minimum + // version before any provider is launched. + const minimumNodeVersion = [24, 11, 0]; + const actualNodeVersion = process.versions.node.split(".").map(Number); + if ( + actualNodeVersion[0] < minimumNodeVersion[0] || + (actualNodeVersion[0] === minimumNodeVersion[0] && + (actualNodeVersion[1] < minimumNodeVersion[1] || + (actualNodeVersion[1] === minimumNodeVersion[1] && + actualNodeVersion[2] < minimumNodeVersion[2]))) + ) { + throw new Error("Provider pack build Node is older than 24.11.0"); + } const stableNodeRoot = join(temporaryRoot, "node_modules", "node"); - if (!existsSync(packagedNodeRoot) || existsSync(stableNodeRoot)) { + if (existsSync(stableNodeRoot)) { throw new Error( - "Provider pack requires the pinned node-linux-x64 artifact and an unclaimed stable Node path", + "Provider pack deployment unexpectedly claimed the stable Node path", ); } - renameSync(packagedNodeRoot, stableNodeRoot); + const stableNodeCommand = join(stableNodeRoot, "bin", "node"); + mkdirSync(dirname(stableNodeCommand), { recursive: true, mode: 0o755 }); + copyFileSync(process.execPath, stableNodeCommand); + chmodSync(stableNodeCommand, 0o755); // pnpm's generated .bin shims embed the temporary deployment directory in // NODE_PATH. That makes an otherwise identical provider pack hash differ on @@ -250,7 +261,7 @@ try { }).status !== 0; const payload = { pins: { - nodeMinimum: "24.11.0", + nodeMinimum: minimumNodeVersion.join("."), codex: "0.148.0", opencode: "1.18.17", acpx: "0.13.1", diff --git a/packages/paperclip-runner/test/acpx-codex-package-contract.test.mjs b/packages/paperclip-runner/test/acpx-codex-package-contract.test.mjs index 733cc3593e..1593610c31 100644 --- a/packages/paperclip-runner/test/acpx-codex-package-contract.test.mjs +++ b/packages/paperclip-runner/test/acpx-codex-package-contract.test.mjs @@ -54,12 +54,8 @@ const nativeSessionExecutor = await readFile( ); test("the runner pins every qualified ACPX production dependency", () => { - assert.equal(runnerPackage.dependencies["@openai/codex"], "0.148.0"); - assert.equal( - runnerPackage.optionalDependencies["@openai/codex-linux-x64"], - "npm:@openai/codex@0.148.0-linux-x64", - ); - assert.equal(runnerPackage.optionalDependencies["node-linux-x64"], "24.11.0"); + assert.equal(runnerPackage.dependencies["@openai/codex"], undefined); + assert.equal(runnerPackage.optionalDependencies, undefined); assert.equal(runnerPackage.dependencies.node, undefined); assert.equal(runnerPackage.dependencies.acpx, "0.13.1"); assert.equal( @@ -133,8 +129,9 @@ test("old and new pnpm configuration both apply the exact runtime patches", () = assert.doesNotMatch(workspace, /node@24\.11\.0:/); assert.match( providerPackBuilder, - /renameSync\(packagedNodeRoot, stableNodeRoot\)/, + /copyFileSync\(process\.execPath, stableNodeCommand\)/, ); + assert.match(codexPatch, /\+ "@openai\/codex": "0\.148\.0"/); }); test("the ACPX patch preserves launch-only state and verified spawning", () => { diff --git a/patches/@agentclientprotocol__codex-acp@1.6.2.patch b/patches/@agentclientprotocol__codex-acp@1.6.2.patch index e9355a2901..efe631113b 100644 --- a/patches/@agentclientprotocol__codex-acp@1.6.2.patch +++ b/patches/@agentclientprotocol__codex-acp@1.6.2.patch @@ -1,3 +1,15 @@ +diff --git a/package.json b/package.json +--- a/package.json ++++ b/package.json +@@ -65,7 +65,7 @@ + }, + "dependencies": { + "@agentclientprotocol/sdk": "^1.3.0", +- "@openai/codex": "^0.148.0", ++ "@openai/codex": "0.148.0", + "diff": "^9.0.0", + "open": "^11.0.0", + "vscode-jsonrpc": "^9.0.1", diff --git a/dist/index.js b/dist/index.js --- a/dist/index.js +++ b/dist/index.js