fix(runner): resolve source and consumer provider dependencies

Bind the vendored source runner to the shared pnpm store and delegate pinned Claude platform dependencies to the published server manifest. Keep runtime version, slot, and executable digest admission intact.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-11 15:01:47 -05:00
1 parent 2d81789c14
commit d6a2741834
7 files changed
+295 -98

No files matched your search

+5
View File
@@ -1460,6 +1460,11 @@ a successful provider run additionally verifies credentials and model access.
The server must install and bundle `@agentclientprotocol/claude-agent-acp`
at the qualified version, including the patched bridge and pinned SDK. The
vendor dependency check covers this dynamic lookup as well as ordinary imports.
Source builds bind the vendored runner to the workspace's pnpm store while
keeping the server manifest as its provider authority. Published npm bundles
retain the pinned SDK JavaScript and declare its native packages as server
optional dependencies, so npm installs the executable for the consumer's
platform. Runtime admission still checks the exact version and executable digest.
A missing provider package is reported as **Runtime missing**, with automatic
retries stopped. Reinstall or rebuild Paperclip with its provider dependencies,
restart the server, and run the agent's runtime test before retrying the task.
@@ -54,6 +54,62 @@ afterEach(async () => {
});
describe("ACPX installation integrity", () => {
it.each([["linux", "x64"], ["darwin", "arm64"], ["darwin", "x64"]] as const)(
"retains Claude executable admission for a published bundle on %s %s", async (os, cpu) => {
const parent = await realpath(await mkdtemp(join(tmpdir(), "paperclip-claude-npm-admission-")));
temporaryDirectories.push(parent);
const root = join(parent, "node_modules/@paperclipai/server");
const bridge = join(root, "node_modules/@agentclientprotocol/claude-agent-acp");
const runtime = join(bridge, "node_modules/@anthropic-ai/claude-agent-sdk");
const name = `@anthropic-ai/claude-agent-sdk-${os}-${cpu}`;
const platformRoot = join(parent, "node_modules", name);
const optionalDependencies = { [name]: "0.3.286" };
const runtimeMetadata = { name: "@anthropic-ai/claude-agent-sdk", version: "0.3.286" };
const platformMetadata = { name, version: "0.3.286", os: [os], cpu: [cpu] };
await Promise.all([mkdir(runtime, { recursive: true }), mkdir(platformRoot, { recursive: true })]);
const command = 'process.stdout.write("unexecuted fixture");';
const reset = () => Promise.all([
writeFile(join(root, "package.json"), JSON.stringify({ name: "@paperclipai/server", optionalDependencies })),
writeFile(join(runtime, "package.json"), JSON.stringify(runtimeMetadata)),
writeFile(join(platformRoot, "package.json"), JSON.stringify(platformMetadata)),
]);
await reset();
await Promise.all([
writeFile(join(bridge, "package.json"), JSON.stringify({ name: "@agentclientprotocol/claude-agent-acp", version: "0.73.0", bin: "server.js" })),
writeFile(join(bridge, "server.js"), command),
writeFile(join(platformRoot, "claude"), "unqualified native bytes", { mode: 0o755 }),
]);
const profile = { ...resolveQualifiedAcpxProfile("claude", "claude-opus-5-5"), commandDigest: `sha256:${createHash("sha256").update(command).digest("hex")}` };
const resolver = createAcpxPackageJsonResolver(root);
expect(resolver(name, join(runtime, "package.json"))).toBe(join(platformRoot, "package.json"));
expect(() => resolver(name)).toThrow("outside the selected provider root");
for (const [file, metadata] of [
[join(root, "package.json"), { name: "@paperclipai/server", optionalDependencies: { [name]: "latest" } }],
[join(runtime, "package.json"), { ...runtimeMetadata, version: "0.3.285" }],
[join(runtime, "package.json"), { ...runtimeMetadata, optionalDependencies: {} }],
[join(platformRoot, "package.json"), { ...platformMetadata, name: "unqualified-provider" }],
[join(platformRoot, "package.json"), { ...platformMetadata, version: "0.3.285" }],
[join(platformRoot, "package.json"), { ...platformMetadata, cpu: ["ia32"] }],
] as const) {
await writeFile(file, JSON.stringify(metadata));
expect(() => resolver(name, join(runtime, "package.json"))).toThrow("outside the selected provider root");
await reset();
}
const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue(os);
const archSpy = vi.spyOn(process, "arch", "get").mockReturnValue(cpu);
try {
await expect(verifyQualifiedAcpxInstallation(profile, (...args) => resolver(...args))).rejects.toThrow("runtime omitted its verified platform executable package");
await expect(verifyQualifiedAcpxInstallation(profile, resolver)).rejects.toThrow(/digest mismatch/);
vi.stubEnv("PAPERCLIP_ACPX_PROVIDER_PACKAGE_ROOT", root);
vi.stubEnv("PAPERCLIP_ACPX_PROVIDER_PACKAGE_MANIFEST", undefined);
await expect(verifyQualifiedAcpxInstallation(profile)).rejects.toThrow(/digest mismatch/);
} finally {
vi.unstubAllEnvs();
platformSpy.mockRestore();
archSpy.mockRestore();
}
},
);
it("explains a missing Claude ACP installation without leaking the resolver stack", async () => {
const missingPackage = Object.assign(new Error(
"Cannot find module '@agentclientprotocol/claude-agent-acp/package.json'\nRequire stack: /srv/private/server.js",
@@ -354,7 +354,7 @@ export function createAcpxPackageJsonResolver(
resolvePackageJsonFromIssuer(packageName, canonicalIssuer),
);
if (!pathIsInside(canonicalNodeModules, packageJsonPath)
&& !isDeclaredHoistedCodexPlatform(canonicalRoot, canonicalManifest, canonicalIssuer, packageName, packageJsonPath)) {
&& !isDeclaredHoistedProviderPlatform(canonicalRoot, canonicalManifest, canonicalIssuer, packageName, packageJsonPath)) {
throw new Error(
`ACPX provider package ${packageName} resolves outside the selected provider root`,
);
@@ -365,25 +365,35 @@ export function createAcpxPackageJsonResolver(
return resolver;
}
function hasPublishedCodexPlatformDeclaration(resolver: AcpxPackageJsonResolver, runtimeManifest: string): boolean {
function hasPublishedProviderPlatformDeclaration(
resolver: AcpxPackageJsonResolver,
runtimeManifest: string,
qualified: { runtimePackageName: string; runtimePackageVersion: string; packageName: string; dependencyDeclaration: string },
): boolean {
const authority = rootedPackageResolvers.get(resolver);
if (!authority || !pathIsInside(authority.root, runtimeManifest)) return false;
const selected = readResolverPackageJson(authority.manifest);
const runtime = readResolverPackageJson(runtimeManifest);
const declarations = selected.optionalDependencies as Record<string, unknown> | undefined;
return selected.name === "@paperclipai/server"
&& runtime.name === QUALIFIED_CODEX_LINUX_X64_RUNTIME.runtimePackageName
&& runtime.version === QUALIFIED_CODEX_LINUX_X64_RUNTIME.runtimePackageVersion
&& runtime.name === qualified.runtimePackageName
&& runtime.version === qualified.runtimePackageVersion
&& runtime.optionalDependencies === undefined
&& declarations?.[QUALIFIED_CODEX_LINUX_X64_RUNTIME.packageName] === QUALIFIED_CODEX_LINUX_X64_RUNTIME.dependencyDeclaration;
&& declarations?.[qualified.packageName] === qualified.dependencyDeclaration;
}
// npm installs an optional platform alias outside a bundled server graph.
// Admit only the qualified executable package through its declared npm slot;
// all executable bytes still pass the existing digest/no-follow admission.
function isDeclaredHoistedCodexPlatform(root: string, manifest: string, issuer: string, packageName: string, packageJsonPath: string): boolean {
const qualified = QUALIFIED_CODEX_LINUX_X64_RUNTIME;
if (packageName !== qualified.packageName || issuer === manifest) return false;
function isDeclaredHoistedProviderPlatform(root: string, manifest: string, issuer: string, packageName: string, packageJsonPath: string): boolean {
const platformQualification = [
{ qualified: QUALIFIED_CODEX_LINUX_X64_RUNTIME, os: "linux", cpu: "x64", alias: true },
{ qualified: QUALIFIED_CLAUDE_LINUX_X64_RUNTIME, os: "linux", cpu: "x64", alias: false },
{ qualified: QUALIFIED_CLAUDE_DARWIN_RUNTIMES.arm64, os: "darwin", cpu: "arm64", alias: false },
{ qualified: QUALIFIED_CLAUDE_DARWIN_RUNTIMES.x64, os: "darwin", cpu: "x64", alias: false },
].find(value => value.qualified.packageName === packageName);
if (!platformQualification || issuer === manifest) return false;
const { qualified, os, cpu, alias } = platformQualification;
try {
const selected = readResolverPackageJson(manifest);
const runtime = readResolverPackageJson(issuer);
@@ -404,10 +414,10 @@ function isDeclaredHoistedCodexPlatform(root: string, manifest: string, issuer:
});
if (!declaredSlot) return false;
const platform = readResolverPackageJson(packageJsonPath);
return (platform.name === qualified.packageName || platform.name === qualified.runtimePackageName)
return (platform.name === qualified.packageName || (alias && platform.name === qualified.runtimePackageName))
&& platform.version === qualified.packageVersion
&& Array.isArray(platform.os) && platform.os.includes("linux")
&& Array.isArray(platform.cpu) && platform.cpu.includes("x64");
&& Array.isArray(platform.os) && platform.os.includes(os)
&& Array.isArray(platform.cpu) && platform.cpu.includes(cpu);
} catch { return false; }
}
@@ -633,9 +643,9 @@ export async function verifyQualifiedAcpxInstallation(
profile: QualifiedAcpxProfile,
resolvePackageJson: AcpxPackageJsonResolver = defaultPackageJsonResolver,
): Promise<VerifiedAcpxInstallation> {
// Keep the selected package authority available when its bundled Codex
// Keep the selected package authority available when its bundled provider
// wrapper delegates platform installation to the published server manifest.
if (profile.agent === "codex" && resolvePackageJson === defaultPackageJsonResolver && process.env.PAPERCLIP_ACPX_PROVIDER_PACKAGE_ROOT !== undefined) {
if ((profile.agent === "codex" || profile.agent === "claude") && resolvePackageJson === defaultPackageJsonResolver && process.env.PAPERCLIP_ACPX_PROVIDER_PACKAGE_ROOT !== undefined) {
resolvePackageJson = createAcpxPackageJsonResolver(
process.env.PAPERCLIP_ACPX_PROVIDER_PACKAGE_ROOT,
process.env.PAPERCLIP_ACPX_PROVIDER_PACKAGE_MANIFEST,
@@ -1034,8 +1044,8 @@ async function verifyQualifiedRuntimeExecutable(input: {
const ownDeclaration = typeof optionalDependencies === "object"
&& optionalDependencies !== null && !Array.isArray(optionalDependencies)
&& (optionalDependencies as Record<string, unknown>)[qualification.packageName] === qualification.dependencyDeclaration;
const publishedDeclaration = input.profile.agent === "codex" && optionalDependencies === undefined
&& hasPublishedCodexPlatformDeclaration(input.resolvePackageJson, input.runtimePackageJsonPath);
const publishedDeclaration = optionalDependencies === undefined
&& hasPublishedProviderPlatformDeclaration(input.resolvePackageJson, input.runtimePackageJsonPath, qualification);
if (!ownDeclaration && !publishedDeclaration) {
throw new Error(
`ACPX ${input.profile.agent} runtime omitted its verified platform executable package`,
@@ -1633,6 +1633,23 @@ it("uses the public server npm package as the authority for vendored sidecars",
});
});
it.each([false, true])("binds a vendored source server to its pnpm dependency root (self-rooted: %s)", async selfRooted => {
const workspace = await mkdtemp(join(tmpdir(), "paperclip-vendored-source-"));
const server = join(workspace, "server");
await mkdir(join(workspace, "node_modules", ".pnpm"), { recursive: true });
await mkdir(join(server, "node_modules", ...(selfRooted ? [".pnpm"] : [])), { recursive: true });
await writeFile(join(workspace, "package.json"), JSON.stringify({ name: "paperclip", private: true }));
await writeFile(join(workspace, "pnpm-workspace.yaml"), "packages:\n - server\n");
await writeFile(join(server, "package.json"), JSON.stringify({ name: "@paperclipai/server" }));
try {
expect(runnerdLaunchProfileInternals.acpxProviderPackageAuthority(
join(server, "dist/vendor/paperclip-runner/cli/acpx-runtime-sidecar.cjs"),
)).toEqual({ root: selfRooted ? server : workspace, manifest: join(server, "package.json") });
} finally {
await rm(workspace, { recursive: true, force: true });
}
});
it("keeps a self-rooted pnpm deployment inside its dependency authority", async () => {
const deploymentRoot = await mkdtemp(
join(tmpdir(), "paperclip-deployed-provider-root-"),
@@ -3030,6 +3030,20 @@ function acpxProviderPackageAuthority(
basename(resolve(cliDirectory, "../..")) === "vendor" &&
basename(resolve(cliDirectory, "../../..")) === "dist") {
const serverRoot = resolve(cliDirectory, "../../../..");
const workspaceRoot = dirname(serverRoot);
// Source/Docker builds share the workspace's pnpm store. Published npm
// bundles and self-rooted pnpm deployments retain their server authority.
if (basename(serverRoot) === "server" &&
!existsSync(resolve(serverRoot, "node_modules/.pnpm")) &&
existsSync(resolve(workspaceRoot, "node_modules/.pnpm")) &&
existsSync(resolve(workspaceRoot, "pnpm-workspace.yaml")) &&
existsSync(resolve(workspaceRoot, "package.json"))) {
const workspace = JSON.parse(readFileSync(resolve(workspaceRoot, "package.json"), "utf8"));
const server = JSON.parse(readFileSync(resolve(serverRoot, "package.json"), "utf8"));
if (workspace.name === "paperclip" && workspace.private === true && server.name === "@paperclipai/server") {
return { root: workspaceRoot, manifest: resolve(serverRoot, "package.json") };
}
}
return { root: serverRoot, manifest: resolve(serverRoot, "package.json") };
}
if (
+83 -3
View File
@@ -2,6 +2,7 @@ import assert from "node:assert/strict";
import { execFileSync } from "node:child_process";
import {
existsSync,
cpSync,
lstatSync,
mkdirSync,
mkdtempSync,
@@ -56,6 +57,33 @@ function esbuildFixture(t) {
}
const codexTargets = ["linux-x64", "linux-arm64", "darwin-x64", "darwin-arm64", "win32-x64", "win32-arm64"];
const codexOptional = Object.fromEntries(codexTargets.map(target => [`@openai/codex-${target}`, `npm:@openai/codex@0.160.0-${target}`]));
const claudeTargets = [...codexTargets, "linux-x64-musl", "linux-arm64-musl"];
const claudeOptional = Object.fromEntries(claudeTargets.map(target => [`@anthropic-ai/claude-agent-sdk-${target}`, "0.3.286"]));
function claudeFixture(t) {
const directory = realpathSync(mkdtempSync(join(tmpdir(), "paperclip-bundled-claude-")));
t.after(() => rmSync(directory, { recursive: true, force: true }));
const graph = join(directory, "node_modules");
const writePackage = (location, metadata) => {
mkdirSync(location, { recursive: true });
writeFileSync(join(location, "package.json"), JSON.stringify(metadata));
return location;
};
const bridge = writePackage(join(graph, "@agentclientprotocol/claude-agent-acp"), {
name: "@agentclientprotocol/claude-agent-acp", version: "0.73.0", dependencies: { "@anthropic-ai/claude-agent-sdk": "0.3.286" },
});
writeFileSync(join(bridge, "index.js"), "export {};\n");
const runtime = writePackage(join(graph, "@anthropic-ai/claude-agent-sdk"), {
name: "@anthropic-ai/claude-agent-sdk", version: "0.3.286", exports: { ".": "./sdk.mjs" }, optionalDependencies: claudeOptional,
});
writeFileSync(join(runtime, "sdk.mjs"), "// official JavaScript fixture, never executed\n");
const producer = writePackage(join(graph, "@anthropic-ai/claude-agent-sdk-linux-x64"), {
name: "@anthropic-ai/claude-agent-sdk-linux-x64", version: "0.3.286", os: ["linux"], cpu: ["x64"],
});
writeFileSync(join(producer, "claude"), "producer native fixture, never executed");
const profiles = [{ agent: "claude", ...profileData.profiles.claude }];
const publish = { name: "paperclip-claude-pack-fixture", version: "1.0.0", dependencies: { "@agentclientprotocol/claude-agent-acp": "0.73.0" }, bundleDependencies: ["@agentclientprotocol/claude-agent-acp"] };
return { directory, graph, bridge, runtime, producer, profiles, publish, writePackage };
}
function codexFixture(t) {
const directory = realpathSync(mkdtempSync(join(tmpdir(), "paperclip-bundled-codex-")));
t.after(() => rmSync(directory, { recursive: true, force: true }));
@@ -195,7 +223,7 @@ test("published Codex bridge uses its qualified producer closure without consume
createBundledInstallManifest(published, serverPackage.bundleDependencies),
serverPackage.bundleDependencies, rootPackage, profileData,
);
assert.equal(profiles.length, 1);
assert.deepEqual(profiles.map(profile => profile.agent), ["codex", "claude"]);
for (const profile of profiles) {
assert.equal(published.dependencies[profile.agentServerPackage], profile.agentServerVersion);
assert.ok(published.bundleDependencies.includes(profile.agentServerPackage));
@@ -214,6 +242,58 @@ test("published Codex bridge uses its qualified producer closure without consume
assert.throws(() => configureBundledProviderOverrides(conflict, serverPackage.bundleDependencies, rootPackage, profileData), /conflicting producer override/);
});
test("Linux-produced Claude bundles retain the SDK and install native packages for the consumer platform", (t) => {
const { directory, bridge, runtime, producer, profiles, publish } = claudeFixture(t);
const bridgeBefore = readFileSync(join(bridge, "package.json"), "utf8");
const staged = stageBundledProviderOptionalDependencies(directory, publish, profiles);
assert.deepEqual(staged.optionalDependencies, claudeOptional);
assert.equal(existsSync(producer), false);
assert.equal(JSON.parse(readFileSync(join(runtime, "package.json"), "utf8")).optionalDependencies, undefined);
assert.equal(readFileSync(join(bridge, "package.json"), "utf8"), bridgeBefore);
assert.deepEqual(stageBundledProviderOptionalDependencies(directory, staged, profiles), staged);
writeFileSync(join(directory, "package.json"), JSON.stringify(staged));
const packs = JSON.parse(execFileSync("npm", ["pack", "--ignore-scripts", "--offline", "--json"], {
cwd: directory, encoding: "utf8", timeout: 30_000,
env: { ...process.env, HOME: directory, npm_config_cache: join(directory, "cache"), npm_config_update_notifier: "false" },
}));
const paths = packs[0].files.map(file => file.path);
assert.ok(paths.some(path => path.endsWith("@anthropic-ai/claude-agent-sdk/sdk.mjs")));
assert.equal(paths.some(path => /@anthropic-ai\/claude-agent-sdk-(?:linux|darwin|win32)-/.test(path)), false);
});
test("Claude bundle validation rejects unqualified declarations before removing native payloads", (t) => {
const { directory, runtime, producer, profiles, publish } = claudeFixture(t);
const file = join(runtime, "package.json"), original = JSON.parse(readFileSync(file, "utf8"));
for (const metadata of [
{ ...original, version: "0.3.285" },
{ ...original, optionalDependencies: { ...claudeOptional, "@anthropic-ai/claude-agent-sdk-darwin-arm64": "latest" } },
{ ...original, optionalDependencies: {} },
]) {
writeFileSync(file, JSON.stringify(metadata));
assert.throws(() => stageBundledProviderOptionalDependencies(directory, publish, profiles), /version mismatch|not qualified/);
assert.equal(existsSync(producer), true);
}
});
test("mixed provider bundles validate both runtimes before removing either producer executable", (t) => {
const codex = codexFixture(t), claude = claudeFixture(t);
for (const name of ["@agentclientprotocol/claude-agent-acp", "@anthropic-ai/claude-agent-sdk", "@anthropic-ai/claude-agent-sdk-linux-x64"]) {
cpSync(join(claude.graph, name), join(codex.graph, name), { recursive: true });
}
const profiles = [...codex.profiles, ...claude.profiles];
const publish = { ...codex.publish, dependencies: { ...codex.publish.dependencies, ...claude.publish.dependencies },
bundleDependencies: [...codex.publish.bundleDependencies, ...claude.publish.bundleDependencies] };
const file = join(codex.graph, "@anthropic-ai/claude-agent-sdk/package.json");
const metadata = JSON.parse(readFileSync(file, "utf8"));
writeFileSync(file, JSON.stringify({ ...metadata, version: "0.3.285" }));
assert.throws(() => stageBundledProviderOptionalDependencies(codex.directory, publish, profiles), /Claude runtime version mismatch/);
assert.equal(existsSync(codex.producer), true);
assert.deepEqual(JSON.parse(readFileSync(join(codex.runtime, "package.json"), "utf8")).optionalDependencies, codexOptional);
writeFileSync(file, JSON.stringify(metadata));
const staged = stageBundledProviderOptionalDependencies(codex.directory, publish, profiles);
assert.deepEqual(staged.optionalDependencies, { ...codexOptional, ...claudeOptional });
});
test("Codex bundles retain pinned JavaScript and delegate official consumer platform aliases without native payloads", (t) => {
const { directory, graph, bridge, runtime, producer, nested, profiles, publish } = codexFixture(t);
const before = [bridge, runtime].map(path => readFileSync(join(path, "package.json"), "utf8"));
@@ -543,7 +623,7 @@ mkdir -p "$destination/node_modules/.pnpm"
set -euo pipefail
printf 'npm %s\\n' "$*" >> "$FAKE_CALL_LOG"
[ "$*" = "install --omit=dev --ignore-scripts --no-audit --no-fund" ]
node -e 'const fs = require("node:fs"); const pkg = require("./package.json"); if ("devDependencies" in pkg) process.exit(1); for (const [name, version] of Object.entries(pkg.dependencies)) { const dir = "node_modules/" + name; fs.mkdirSync(dir + "/dist", { recursive: true }); fs.writeFileSync(dir + "/package.json", JSON.stringify({ name, version })); } for (const [bridge, runtimes] of Object.entries(pkg.overrides ?? {})) { for (const [name, version] of Object.entries(runtimes)) { const dir = "node_modules/" + name; fs.mkdirSync(dir, { recursive: true }); const native = name === "@openai/codex"; const optionalDependencies = native ? Object.fromEntries(["linux-x64","linux-arm64","darwin-x64","darwin-arm64","win32-x64","win32-arm64"].map(target => [name+"-"+target,"npm:"+name+"@"+version+"-"+target])) : undefined; fs.writeFileSync(dir + "/package.json", JSON.stringify({ name, version, optionalDependencies })); } }'
node -e 'const fs = require("node:fs"); const pkg = require("./package.json"); if ("devDependencies" in pkg) process.exit(1); for (const [name, version] of Object.entries(pkg.dependencies)) { const dir = "node_modules/" + name; fs.mkdirSync(dir + "/dist", { recursive: true }); fs.writeFileSync(dir + "/package.json", JSON.stringify({ name, version })); } for (const [bridge, runtimes] of Object.entries(pkg.overrides ?? {})) { for (const [name, version] of Object.entries(runtimes)) { const dir = "node_modules/" + name; fs.mkdirSync(dir, { recursive: true }); const native = name === "@openai/codex"; const targets = ["linux-x64","linux-arm64","darwin-x64","darwin-arm64","win32-x64","win32-arm64"]; const optionalDependencies = native ? Object.fromEntries(targets.map(target => [name+"-"+target,"npm:"+name+"@"+version+"-"+target])) : name === "@anthropic-ai/claude-agent-sdk" ? Object.fromEntries([...targets,"linux-x64-musl","linux-arm64-musl"].map(target => [name+"-"+target,version])) : undefined; fs.writeFileSync(dir + "/package.json", JSON.stringify({ name, version, optionalDependencies })); } }'
node -e 'const fs=require("node:fs");fs.cpSync(process.env.FAKE_NATIVE_ARTIFACTS+"/@openai/codex-linux-x64","node_modules/@openai/codex-linux-x64",{recursive:true});fs.mkdirSync("node_modules/@openai/codex/vendor",{recursive:true});fs.writeFileSync("node_modules/@openai/codex/vendor/codex","producer fallback fixture");'
mkdir -p node_modules/acpx/dist
node -e 'const fs=require("node:fs");fs.mkdirSync("node_modules/esbuild",{recursive:true});fs.writeFileSync("node_modules/esbuild/package.json",JSON.stringify({name:"esbuild",version:"0.28.2",scripts:{postinstall:"node install.js"},optionalDependencies:Object.fromEntries(["linux-x64","darwin-arm64","darwin-x64"].map(target=>["@esbuild/"+target,"0.28.2"]))}));fs.mkdirSync("node_modules/@esbuild/linux-x64",{recursive:true});fs.writeFileSync("node_modules/@esbuild/linux-x64/package.json",JSON.stringify({name:"@esbuild/linux-x64",version:"0.28.2",os:["linux"],cpu:["x64"]}));'
@@ -603,7 +683,7 @@ printf 'patched spawnEnvironment runtime\\n' > "$target/dist/runtime.js"
assert.equal(lstatSync(stagedAcpxDir).isSymbolicLink(), false);
assert.equal(existsSync(join(destinationDir, "node_modules/.pnpm")), false);
const stagedManifest = JSON.parse(readFileSync(join(destinationDir, "package.json"), "utf8"));
for (const [name, version] of Object.entries(codexOptional)) {
for (const [name, version] of Object.entries({ ...codexOptional, ...claudeOptional })) {
assert.equal(stagedManifest.optionalDependencies[name], version);
assert.equal(existsSync(join(destinationDir, "node_modules", name)), false);
assert.equal(stagedManifest.bundleDependencies.includes(name), false);
+95 -80
View File
@@ -80,9 +80,7 @@ export function configureBundledProviderOverrides(installManifest, bundledDepend
...(agent === "claude" ? { "@agentclientprotocol/sdk": "1.4.0", zod: "4.4.3" } : {}),
};
result.overrides = { ...result.overrides, [npmSelector]: overrides };
// Codex delegates its platform aliases below. Claude retains the SDK
// platform dependencies in its ordinary bundled provider closure.
if (agent === "codex") selected.push({ agent, ...profile });
selected.push({ agent, ...profile });
}
return { installManifest: result, profiles: selected };
}
@@ -92,88 +90,105 @@ const inside = (root, candidate) => {
return value !== "" && value !== ".." && !value.startsWith(`..${sep}`) && !isAbsolute(value);
};
// Bundle the pinned JavaScript closure, never the producer's Codex executable.
// Bundle the pinned JavaScript closure, never the producer's executable.
// npm installs the official optional package for the consumer's own platform.
export function stageBundledProviderOptionalDependencies(destinationDir, publishManifest, profiles) {
const graphRoot = resolve(destinationDir, "node_modules");
if (realpathSync(graphRoot) !== graphRoot) throw new Error("Bundled Codex graph must be a canonical owned directory");
const result = structuredClone(publishManifest), remove = new Set(), optional = {}, normalize = new Map();
const profile = profiles.find(value => value.agent === "codex");
if (profiles.length !== 1 || !profile) throw new Error("Bundled provider graph must contain its qualified Codex profile");
const targets = ["linux-x64", "linux-arm64", "darwin-x64", "darwin-arm64", "win32-x64", "win32-arm64"];
const declarations = Object.fromEntries(targets.map(target => [`${profile.agentRuntimePackage}-${target}`,
`npm:${profile.agentRuntimePackage}@${profile.agentRuntimeVersion}-${target}`]));
const ownedDirectory = directory => {
const stat = lstatSync(directory);
if (!stat.isDirectory() || stat.isSymbolicLink() || !inside(graphRoot, realpathSync(directory))) {
throw new Error("Bundled Codex dependency escapes its producer graph");
}
};
const manifestAt = directory => {
const path = resolve(directory, "package.json"), stat = lstatSync(path);
if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1 || stat.size > 256 * 1024) {
throw new Error("Bundled Codex dependency manifest must be a bounded regular file");
}
return JSON.parse(readFileSync(path, "utf8"));
};
const bridgeDirectory = resolve(graphRoot, profile.agentServerPackage);
ownedDirectory(bridgeDirectory);
const bridge = manifestAt(bridgeDirectory);
if (bridge.name !== profile.agentServerPackage || bridge.version !== profile.agentServerVersion) throw new Error("Bundled Codex bridge version mismatch");
const issuer = createRequire(resolve(bridgeDirectory, "package.json"));
const runtimeManifest = realpathSync(issuer.resolve(`${profile.agentRuntimePackage}/package.json`));
if (!inside(graphRoot, runtimeManifest)) throw new Error("Bundled Codex runtime escapes its producer graph");
let runtimeCount = 0, packageCount = 0;
const pending = [{ directory: graphRoot, depth: 0 }];
while (pending.length) {
const { directory, depth } = pending.pop();
if (depth > 64) throw new Error("Bundled Codex graph exceeds its depth limit");
const packages = [];
for (const entry of readdirSync(directory)) {
if (entry.startsWith(".")) continue;
const candidate = resolve(directory, entry); ownedDirectory(candidate);
if (entry.startsWith("@")) {
for (const child of readdirSync(candidate)) {
const scoped = resolve(candidate, child); ownedDirectory(scoped); packages.push(scoped);
}
} else packages.push(candidate);
}
for (const packageDirectory of packages) {
if (++packageCount > 20_000) throw new Error("Bundled Codex graph exceeds its package limit");
const metadata = manifestAt(packageDirectory);
const binding = basename(dirname(packageDirectory)) === "@openai" ? `@openai/${basename(packageDirectory)}` : null;
if (binding?.startsWith("@openai/codex-")) {
const target = binding.slice("@openai/codex-".length), [os, cpu] = target.split("-");
if (!Object.hasOwn(declarations, binding) || ![binding, profile.agentRuntimePackage].includes(metadata.name)
|| metadata.version !== `${profile.agentRuntimeVersion}-${target}` || !metadata.os?.includes(os) || !metadata.cpu?.includes(cpu)) {
throw new Error("Bundled Codex installed platform identity mismatch");
}
remove.add(packageDirectory);
continue;
}
const nested = resolve(packageDirectory, "node_modules");
if (lstatExists(nested)) { ownedDirectory(nested); pending.push({ directory: nested, depth: depth + 1 }); }
if (binding !== profile.agentRuntimePackage) continue;
if (metadata.name !== profile.agentRuntimePackage || metadata.version !== profile.agentRuntimeVersion) throw new Error("Bundled Codex runtime version mismatch");
runtimeCount++;
const delegated = metadata.optionalDependencies === undefined
&& Object.entries(declarations).every(([name, specifier]) => result.optionalDependencies?.[name] === specifier);
if (!delegated && (Object.keys(metadata.optionalDependencies ?? {}).length !== targets.length
|| targets.some(target => metadata.optionalDependencies?.[`${profile.agentRuntimePackage}-${target}`] !== declarations[`${profile.agentRuntimePackage}-${target}`]))) {
throw new Error("Bundled Codex platform declaration is not qualified");
}
if (!delegated) {
const normalized = { ...metadata }; delete normalized.optionalDependencies;
normalize.set(resolve(packageDirectory, "package.json"), normalized);
}
const vendor = resolve(packageDirectory, "vendor");
if (lstatExists(vendor)) { ownedDirectory(vendor); remove.add(vendor); }
}
if (!profiles.length || new Set(profiles.map(profile => profile.agent)).size !== profiles.length) {
throw new Error("Bundled provider graph must contain distinct qualified profiles");
}
if (!runtimeCount) throw new Error("Bundled Codex runtime omitted its platform declarations");
for (const [name, specifier] of Object.entries(declarations)) {
if (result.optionalDependencies?.[name] !== undefined && result.optionalDependencies[name] !== specifier) throw new Error(`Bundled Codex platform conflicts with published dependency: ${name}`);
optional[name] = specifier;
for (const profile of profiles) {
const expected = profile.agent === "codex" ? ["@agentclientprotocol/codex-acp", "@openai/codex"]
: profile.agent === "claude" ? ["@agentclientprotocol/claude-agent-acp", "@anthropic-ai/claude-agent-sdk"] : null;
if (!expected || profile.agentServerPackage !== expected[0] || profile.agentRuntimePackage !== expected[1]) {
throw new Error("Bundled provider graph contains an unknown provider profile");
}
const label = profile.agent === "codex" ? "Codex" : "Claude";
const targets = ["linux-x64", "linux-arm64", "darwin-x64", "darwin-arm64", "win32-x64", "win32-arm64",
...(profile.agent === "claude" ? ["linux-x64-musl", "linux-arm64-musl"] : [])];
const declarations = Object.fromEntries(targets.map(target => [`${profile.agentRuntimePackage}-${target}`,
profile.agent === "codex" ? `npm:${profile.agentRuntimePackage}@${profile.agentRuntimeVersion}-${target}` : profile.agentRuntimeVersion]));
const ownedDirectory = directory => {
const stat = lstatSync(directory);
if (!stat.isDirectory() || stat.isSymbolicLink() || !inside(graphRoot, realpathSync(directory))) {
throw new Error(`Bundled ${label} dependency escapes its producer graph`);
}
};
const manifestAt = directory => {
const path = resolve(directory, "package.json"), stat = lstatSync(path);
if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1 || stat.size > 256 * 1024) {
throw new Error(`Bundled ${label} dependency manifest must be a bounded regular file`);
}
return JSON.parse(readFileSync(path, "utf8"));
};
const bridgeDirectory = resolve(graphRoot, profile.agentServerPackage);
ownedDirectory(bridgeDirectory);
const bridge = manifestAt(bridgeDirectory);
if (bridge.name !== profile.agentServerPackage || bridge.version !== profile.agentServerVersion) throw new Error(`Bundled ${label} bridge version mismatch`);
const issuer = createRequire(resolve(bridgeDirectory, "package.json"));
let runtimeManifest;
try { runtimeManifest = issuer.resolve(`${profile.agentRuntimePackage}/package.json`); }
catch (error) {
if (error.code !== "ERR_PACKAGE_PATH_NOT_EXPORTED") throw error;
runtimeManifest = resolve(dirname(issuer.resolve(profile.agentRuntimePackage)), "package.json");
}
runtimeManifest = realpathSync(runtimeManifest);
if (!inside(graphRoot, runtimeManifest)) throw new Error(`Bundled ${label} runtime escapes its producer graph`);
let runtimeCount = 0, packageCount = 0;
const pending = [{ directory: graphRoot, depth: 0 }];
while (pending.length) {
const { directory, depth } = pending.pop();
if (depth > 64) throw new Error(`Bundled ${label} graph exceeds its depth limit`);
const packages = [];
for (const entry of readdirSync(directory)) {
if (entry.startsWith(".")) continue;
const candidate = resolve(directory, entry); ownedDirectory(candidate);
if (entry.startsWith("@")) {
for (const child of readdirSync(candidate)) {
const scoped = resolve(candidate, child); ownedDirectory(scoped); packages.push(scoped);
}
} else packages.push(candidate);
}
for (const packageDirectory of packages) {
if (++packageCount > 20_000) throw new Error(`Bundled ${label} graph exceeds its package limit`);
const metadata = manifestAt(packageDirectory);
const scope = basename(dirname(packageDirectory));
const binding = scope.startsWith("@") ? `${scope}/${basename(packageDirectory)}` : basename(packageDirectory);
if (binding.startsWith(`${profile.agentRuntimePackage}-`)) {
const target = binding.slice(profile.agentRuntimePackage.length + 1), [os, cpu] = target.split("-");
if (!Object.hasOwn(declarations, binding) || !(metadata.name === binding || (profile.agent === "codex" && metadata.name === profile.agentRuntimePackage))
|| metadata.version !== (profile.agent === "codex" ? `${profile.agentRuntimeVersion}-${target}` : profile.agentRuntimeVersion) || !metadata.os?.includes(os) || !metadata.cpu?.includes(cpu)) {
throw new Error(`Bundled ${label} installed platform identity mismatch`);
}
remove.add(packageDirectory);
continue;
}
const nested = resolve(packageDirectory, "node_modules");
if (lstatExists(nested)) { ownedDirectory(nested); pending.push({ directory: nested, depth: depth + 1 }); }
if (binding !== profile.agentRuntimePackage) continue;
if (metadata.name !== profile.agentRuntimePackage || metadata.version !== profile.agentRuntimeVersion) throw new Error(`Bundled ${label} runtime version mismatch`);
runtimeCount++;
const delegated = metadata.optionalDependencies === undefined
&& Object.entries(declarations).every(([name, specifier]) => result.optionalDependencies?.[name] === specifier);
if (!delegated && (Object.keys(metadata.optionalDependencies ?? {}).length !== targets.length
|| targets.some(target => metadata.optionalDependencies?.[`${profile.agentRuntimePackage}-${target}`] !== declarations[`${profile.agentRuntimePackage}-${target}`]))) {
throw new Error(`Bundled ${label} platform declaration is not qualified`);
}
if (!delegated) {
const normalized = { ...metadata }; delete normalized.optionalDependencies;
normalize.set(resolve(packageDirectory, "package.json"), normalized);
}
const vendor = resolve(packageDirectory, "vendor");
if (profile.agent === "codex" && lstatExists(vendor)) { ownedDirectory(vendor); remove.add(vendor); }
}
}
if (!runtimeCount) throw new Error(`Bundled ${label} runtime omitted its platform declarations`);
for (const [name, specifier] of Object.entries(declarations)) {
if (result.optionalDependencies?.[name] !== undefined && result.optionalDependencies[name] !== specifier) throw new Error(`Bundled ${label} platform conflicts with published dependency: ${name}`);
optional[name] = specifier;
}
}
// A bundled wrapper's optional edges make npm treat missing platform slots
// as bundled too. Delegate those exact declarations to the unbundled server
@@ -183,7 +198,7 @@ export function stageBundledProviderOptionalDependencies(destinationDir, publish
for (const directory of remove) rmSync(directory, { recursive: true, force: true });
result.optionalDependencies = { ...result.optionalDependencies, ...optional };
for (const field of ["bundleDependencies", "bundledDependencies"]) {
if (Array.isArray(result[field])) result[field] = result[field].filter(name => !Object.hasOwn(declarations, name));
if (Array.isArray(result[field])) result[field] = result[field].filter(name => !Object.hasOwn(optional, name));
}
delete result.paperclipProviderArtifacts;
return result;