fix: stage complete installed Codex runtime dependencies

Preserve prepared Git installation assets and consumer lifecycle behavior. Bundle the qualified Codex graph and host artifacts without changing agent defaults.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-08 07:01:54 -05:00
1 parent 71cd0a2621
commit 5fa4abaead
10 files changed
+1209 -19

No files matched your search

+9 -1
View File
@@ -63,7 +63,7 @@ WORKDIR /app
# dependency, and rustup does not — without it every build script dies on
# "linker `cc` not found".
RUN apt-get update \
&& apt-get install -y --no-install-recommends gcc libc6-dev pkg-config \
&& apt-get install -y --no-install-recommends gcc libc6-dev pkg-config patch \
&& rm -rf /var/lib/apt/lists/*
ENV RUSTUP_HOME=/usr/local/rustup \
CARGO_HOME=/usr/local/cargo \
@@ -143,6 +143,14 @@ ENV NODE_OPTIONS=--max-old-space-size=4096
RUN pnpm --filter @paperclipai/server build
RUN test -f server/dist/index.js || (echo "ERROR: server build output missing" && exit 1)
RUN rm -rf packages/paperclip-runner/runner/target
# Vendored Codex ACPX execution binds package authority to server/node_modules.
# pnpm links into the workspace store cannot satisfy that boundary. Reuse the
# public-package producer to materialize only this image's existing qualified
# native target, with dependency scripts disabled and runtime authority intact.
# ARM64 remains usable through legacy adapters while native qualification is
# limited to the source-owned supported targets.
ARG TARGETARCH
RUN node scripts/prepare-bundled-package.mjs --docker-provider-graph /app/server "${TARGETARCH}"
# Remote OpenCode and ACPX runs require a controller-owned provider pack to
# verify the sandbox installation or stage matching assets. Ship it in the
+422 -6
View File
@@ -28,6 +28,7 @@ import { resolveCliVersion } from "../version.js";
import { systemdServiceName } from "../services/service-manager.js";
const ORIGINAL_ENV = { ...process.env };
const SERVER_PACKAGE_FILES = (JSON.parse(fs.readFileSync(new URL("../../../server/package.json", import.meta.url), "utf8")) as { files: string[] }).files;
describe("managed install commands", () => {
let root: string;
@@ -105,7 +106,7 @@ describe("managed install commands", () => {
expect(runCommand.mock.calls[0]?.[0]).toBe(process.execPath);
});
const createGitCheckoutRunCommand = (sha: string) =>
const createGitCheckoutRunCommand = (sha: string, { bundledServer = false }: { bundledServer?: boolean } = {}) =>
vi.fn(async (file: string, args: string[], _options?: Parameters<CommandRunner>[2]) => {
if (file === "curl" && !args.includes("--output")) return { stdout: JSON.stringify({ sha }), stderr: "" };
if (file === "curl") { fs.writeFileSync(args[args.indexOf("--output") + 1], "archive"); return { stdout: "", stderr: "" }; }
@@ -114,19 +115,45 @@ describe("managed install commands", () => {
const packages = [
{ dir: "packages/shared", name: "@paperclipai/shared", packageJson: { name: "@paperclipai/shared", version: "0.3.1" } },
{ dir: "packages/db", name: "@paperclipai/db", packageJson: { name: "@paperclipai/db", version: "0.3.1", dependencies: { "@paperclipai/shared": "workspace:*" }, bundleDependencies: ["embedded-postgres"] } },
{ dir: "server", name: "@paperclipai/server", packageJson: { name: "@paperclipai/server", version: "0.3.1", dependencies: { "@paperclipai/db": "workspace:*" } } },
{ dir: "server", name: "@paperclipai/server", packageJson: { name: "@paperclipai/server", version: "0.3.1", dependencies: { "@paperclipai/db": "workspace:*" }, ...(bundledServer ? { bundleDependencies: ["acpx"], files: SERVER_PACKAGE_FILES } : {}) } },
];
fs.mkdirSync(path.join(checkout, "cli"), { recursive: true });
fs.writeFileSync(path.join(checkout, "cli", "package.json"), JSON.stringify({ version: "0.3.1" }));
fs.writeFileSync(path.join(checkout, "pnpm-lock.yaml"), "source lockfile awaiting refresh\n");
fs.mkdirSync(path.join(checkout, "scripts"), { recursive: true });
fs.writeFileSync(path.join(checkout, "scripts", "release-package-manifest.json"), JSON.stringify(packages.map(({ dir, name }) => ({ dir, name }))));
for (const workspacePackage of packages) {
fs.mkdirSync(path.join(checkout, workspacePackage.dir), { recursive: true });
fs.writeFileSync(path.join(checkout, workspacePackage.dir, "package.json"), JSON.stringify(workspacePackage.packageJson));
}
const skill = path.join(checkout, "skills", "paperclip");
fs.mkdirSync(path.join(skill, "scripts"), { recursive: true });
fs.writeFileSync(path.join(skill, "SKILL.md"), "Skill from this Git checkout\n");
fs.writeFileSync(path.join(skill, "scripts", "helper.mjs"), "export const sameCheckout = true;\n");
for (const packageDir of ["server", "packages/adapters/claude-local", "packages/adapters/codex-local"]) {
const stale = path.join(checkout, packageDir, "skills");
fs.mkdirSync(stale, { recursive: true });
fs.writeFileSync(path.join(stale, "stale.md"), "stale package-local skills\n");
}
return { stdout: "", stderr: "" };
}
if (file === "corepack") {
if (args.includes("build")) {
const dist = path.join(_options!.cwd as string, "server", "dist");
fs.mkdirSync(dist, { recursive: true });
fs.writeFileSync(path.join(dist, "index.js"), "export const builtServer = true;\n");
}
if (args.includes("prepare:ui-dist")) {
const ui = path.join(_options!.cwd as string, "server", "ui-dist");
fs.mkdirSync(ui, { recursive: true });
fs.writeFileSync(path.join(ui, "index.html"), "<html>Built from this Git checkout</html>");
}
if (args.includes("--resolution-only")) {
fs.writeFileSync(path.join(_options!.cwd as string, "pnpm-lock.yaml"), "resolved checkout lockfile\n");
}
if (args.includes("--frozen-lockfile")) {
expect(fs.readFileSync(path.join(_options!.cwd as string, "pnpm-lock.yaml"), "utf8")).toBe("resolved checkout lockfile\n");
}
if (args.includes("pack")) {
const destination = args[args.indexOf("--pack-destination") + 1];
const packageDir = args[args.indexOf("--dir") + 1];
@@ -137,14 +164,32 @@ describe("managed install commands", () => {
}
if (file === "bash") return { stdout: "", stderr: "" };
if (file === "npm" && args[0] === "pack") {
const packageName = args[1]?.includes("workspace-package-") ? "paperclipai-db" : "paperclipai";
const packageName = args[1]?.includes("workspace-package-")
? (JSON.parse(fs.readFileSync(path.join(args[1], "package.json"), "utf8")) as { name: string }).name.replace("@", "").replace("/", "-")
: "paperclipai";
if (packageName === "paperclipai-server") {
expect(fs.readFileSync(path.join(args[1], "ui-dist", "index.html"), "utf8"))
.toBe("<html>Built from this Git checkout</html>");
expect(fs.readFileSync(path.join(args[1], "skills", "paperclip", "SKILL.md"), "utf8"))
.toBe("Skill from this Git checkout\n");
expect(fs.readFileSync(path.join(args[1], "dist", "index.js"), "utf8"))
.toBe("export const builtServer = true;\n");
}
fs.writeFileSync(path.join(args[args.indexOf("--pack-destination") + 1], `${packageName}-0.3.1.tgz`), "package");
return { stdout: "", stderr: "" };
}
if (file === "npm" && args[0] === "install") { const prefix = args[args.indexOf("--prefix") + 1]; const packageRoot = path.join(prefix, "node_modules", "paperclipai"); fs.mkdirSync(path.join(packageRoot, "dist"), { recursive: true }); fs.writeFileSync(path.join(packageRoot, "package.json"), JSON.stringify({ version: "0.3.1" })); fs.writeFileSync(path.join(packageRoot, "dist", "index.js"), "#!/usr/bin/env node\n"); return { stdout: "", stderr: "" }; }
if (file === process.execPath && args[0]?.endsWith("prepare-bundled-package.mjs")) {
const packageJson = JSON.parse(fs.readFileSync(path.join(args[1], "package.json"), "utf8")) as { name: string; version: string; files?: string[] };
fs.mkdirSync(args[2], { recursive: true });
fs.writeFileSync(path.join(args[2], "package.json"), JSON.stringify({ name: "@paperclipai/db", version: "0.3.1" }));
if (packageJson.name === "@paperclipai/server") {
// Match the real bundler's complete declared-file copy. Missing
// runtime assets must fail before packing an incomplete payload.
for (const entry of packageJson.files ?? []) {
fs.cpSync(path.join(args[1], entry), path.join(args[2], entry), { recursive: true });
}
}
fs.writeFileSync(path.join(args[2], "package.json"), JSON.stringify(packageJson));
return { stdout: "", stderr: "" };
}
if (file === process.execPath) return { stdout: "0.3.1\n", stderr: "" };
@@ -160,7 +205,17 @@ describe("managed install commands", () => {
expect(manifest).toMatchObject({ source: "git", repo: "HenkDz/paperclip", ref: "master", sha });
expect(manifest?.payloadPath).toContain(path.join("git", sha.slice(0, 12)));
expect(runCommand.mock.calls.filter(([command, args]) => command === "curl" && args.includes("--output"))).toHaveLength(1);
expect(runCommand.mock.calls.filter(([command, args]) => command === "corepack" && args[1] === "install")).toHaveLength(1);
const pnpmInstallCalls = runCommand.mock.calls.filter(([command, args]) => command === "corepack" && args[1] === "install");
expect(pnpmInstallCalls.map(([, args]) => args)).toEqual([
["pnpm", "install", "--resolution-only", "--ignore-scripts", "--no-frozen-lockfile"],
["pnpm", "install", "--frozen-lockfile"],
]);
const checkoutPath = runCommand.mock.calls.find(([command]) => command === "tar")?.[1].at(-1);
expect(checkoutPath).toContain(path.join("installs", "git", `.${sha.slice(0, 12)}.tmp-`));
for (const [, , options] of pnpmInstallCalls) expect(options?.cwd).toBe(checkoutPath);
expect(runCommand.mock.calls.indexOf(pnpmInstallCalls[1])).toBeLessThan(
runCommand.mock.calls.findIndex(([command]) => command === "bash"),
);
expect(runCommand.mock.calls.filter(([command, args]) => command === "corepack" && args.includes("pack"))).toHaveLength(2);
expect(runCommand.mock.calls.filter(([command, args]) => command === process.execPath && args[0]?.endsWith("prepare-bundled-package.mjs"))).toHaveLength(1);
expect(runCommand.mock.calls.filter(([command, args]) => command === "npm" && args[0] === "pack")).toHaveLength(2);
@@ -168,6 +223,367 @@ describe("managed install commands", () => {
expect(installCall?.[1].filter((arg) => arg.endsWith(".tgz"))).toHaveLength(4);
});
it("cleans a failed Git checkout lockfile resolution and preserves the active install", async () => {
const previousSha = "1".repeat(40);
await installCommand({ ref: previousSha, yes: true }, { runCommand: createGitCheckoutRunCommand(previousSha) });
const paths = resolveInstallStorePaths();
const previousManifest = fs.readFileSync(paths.manifestPath, "utf8");
const previousTarget = fs.readlinkSync(paths.currentPath);
const previousShim = fs.readFileSync(paths.shimPath, "utf8");
const sha = "2".repeat(40);
const checkoutCommands = createGitCheckoutRunCommand(sha);
let failedCheckout: string | undefined;
const runCommand = vi.fn(async (file: string, args: string[], options?: Parameters<CommandRunner>[2]) => {
if (file === "corepack" && args.includes("--resolution-only")) {
failedCheckout = options?.cwd as string;
expect(fs.existsSync(paths.lockPath)).toBe(true);
fs.writeFileSync(path.join(failedCheckout, "pnpm-lock.yaml"), "partial failed resolution\n");
throw new Error("dependency lockfile resolution failed");
}
return checkoutCommands(file, args, options);
});
await expect(installCommand({ ref: sha, yes: true }, { runCommand }))
.rejects.toThrow("dependency lockfile resolution failed");
expect(failedCheckout).toBeDefined();
expect(fs.existsSync(path.dirname(failedCheckout!))).toBe(false);
expect(fs.readdirSync(path.join(paths.installsRoot, "git"))).toEqual([previousSha.slice(0, 12)]);
expect(runCommand.mock.calls.some(([, args]) => args.includes("--frozen-lockfile"))).toBe(false);
expect(runCommand.mock.calls.some(([file]) => file === "bash" || file === "npm")).toBe(false);
expect(fs.existsSync(paths.lockPath)).toBe(false);
expect(fs.readFileSync(paths.manifestPath, "utf8")).toBe(previousManifest);
expect(fs.readlinkSync(paths.currentPath)).toBe(previousTarget);
expect(fs.readFileSync(paths.shimPath, "utf8")).toBe(previousShim);
expect(fs.existsSync(readInstallManifest(paths)!.payloadPath)).toBe(true);
});
it("builds bundled Git server UI from its checkout before staging the declared files", async () => {
process.env.NODE_ENV = "production";
const sha = "3".repeat(40);
const runCommand = createGitCheckoutRunCommand(sha, { bundledServer: true });
await expect(installGitPayload("paperclipai/paperclip", sha, runCommand, resolveInstallStorePaths()))
.resolves.toMatchObject({ version: "0.3.1", reused: false });
const uiCalls = runCommand.mock.calls.filter(([file, args]) => file === "corepack" && args.includes("prepare:ui-dist"));
expect(uiCalls).toHaveLength(1);
expect(uiCalls[0][1]).toEqual(["pnpm", "--dir", "server", "prepare:ui-dist"]);
expect(uiCalls[0][2]?.env).not.toHaveProperty("NODE_ENV");
const serverStage = runCommand.mock.calls.findIndex(([file, args]) =>
file === process.execPath && args[0]?.endsWith("prepare-bundled-package.mjs") && args[1]?.endsWith(path.sep + "server"));
expect(serverStage).toBeGreaterThan(runCommand.mock.calls.indexOf(uiCalls[0]));
expect(runCommand.mock.calls[serverStage][2]?.cwd).toBe(uiCalls[0][2]?.cwd);
});
it("packs the built Git stage without source-only hooks and preserves consumer install hooks", async () => {
const sha = "9".repeat(40);
const checkoutCommands = createGitCheckoutRunCommand(sha, { bundledServer: true });
let packedFiles: string[] = [];
let negativeControlPassed = false;
const npmEnv = {
...process.env,
PATH: ORIGINAL_ENV.PATH,
npm_config_cache: path.join(root, "npm-cache"),
npm_config_userconfig: path.join(root, ".npmrc"),
npm_config_offline: "true",
npm_config_ignore_scripts: "false",
};
fs.writeFileSync(npmEnv.npm_config_userconfig, "");
const runCommand = vi.fn(async (file: string, args: string[], options?: Parameters<CommandRunner>[2]) => {
if (file === process.execPath && args[0]?.endsWith("prepare-bundled-package.mjs") && args[1]?.endsWith(path.sep + "server")) {
const result = await checkoutCommands(file, args, options);
const stagedManifest = path.join(args[2], "package.json");
const packageJson = JSON.parse(fs.readFileSync(stagedManifest, "utf8"));
// The real bundler preserves scripts but stages only declared runtime files.
packageJson.scripts = {
prepack: "node ../scripts/source-prepack.cjs",
postpack: "node ../scripts/source-postpack.cjs",
postinstall: "node dist/consumer-install.cjs",
};
packageJson.dependencies["@paperclipai/db"] = "0.3.1";
fs.writeFileSync(stagedManifest, JSON.stringify(packageJson));
fs.writeFileSync(path.join(args[2], "dist", "consumer-install.cjs"), "process.stdout.write('consumer hook retained');\n");
for (const hook of ["source-prepack", "source-postpack"]) {
fs.writeFileSync(path.join(options!.cwd as string, "scripts", `${hook}.cjs`), "process.stdout.write('source workspace hook');\n");
}
return result;
}
if (file === "npm" && args[0] === "pack" && args[1]?.includes("workspace-package-")) {
const manifest = JSON.parse(fs.readFileSync(path.join(args[1], "package.json"), "utf8")) as { name: string };
if (manifest.name === "@paperclipai/server") {
const realOptions = { ...options, env: npmEnv, timeout: 20_000 };
// Removing the lifecycle boundary must reproduce the actual staged
// packaging failure, without any provider, build, or network access.
await expect(runCommandWithDiagnostics("npm", [...args.filter((arg) => arg !== "--ignore-scripts"), "--json"], realOptions))
.rejects.toThrow("source-prepack.cjs");
negativeControlPassed = true;
const result = await runCommandWithDiagnostics("npm", [...args, "--json"], realOptions);
const packed = JSON.parse(result.stdout) as Array<{ files: Array<{ path: string }> }>;
packedFiles = packed[0].files.map((entry) => entry.path);
expect(fs.existsSync(path.join(args[1], "ui-dist", "index.html"))).toBe(true);
expect(JSON.parse(fs.readFileSync(path.join(args[1], "package.json"), "utf8")).scripts.postinstall)
.toBe("node dist/consumer-install.cjs");
return result;
}
}
return checkoutCommands(file, args, options);
});
await expect(installGitPayload("paperclipai/paperclip", sha, runCommand, resolveInstallStorePaths()))
.resolves.toMatchObject({ version: "0.3.1", reused: false });
expect(negativeControlPassed).toBe(true);
expect(packedFiles).toEqual(expect.arrayContaining([
"dist/index.js", "dist/consumer-install.cjs", "ui-dist/index.html",
"skills/paperclip/SKILL.md", "skills/paperclip/scripts/helper.mjs",
]));
const consumerInstall = runCommand.mock.calls.find(([file, args]) => file === "npm" && args[0] === "install");
expect(consumerInstall?.[1]).not.toContain("--ignore-scripts");
expect(consumerInstall?.[2]?.env?.npm_config_ignore_scripts).toBeUndefined();
const sourcePack = runCommand.mock.calls.find(([file, args]) => file === "npm" && args[0] === "pack" && args[1] === "--pack-destination");
expect(sourcePack?.[1]).not.toContain("--ignore-scripts");
});
it("installs mixed-version Git packages offline with the dependency's own SDK version and consumer hooks", async () => {
const sha = "f".repeat(40);
const checkoutCommands = createGitCheckoutRunCommand(sha, { bundledServer: true });
const { materializePublishManifest } = await import(new URL("../../../scripts/prepare-bundled-package.mjs", import.meta.url).href);
const npmEnv = {
...process.env,
PATH: ORIGINAL_ENV.PATH,
npm_config_cache: path.join(root, "npm-cache"),
npm_config_userconfig: path.join(root, ".npmrc"),
npm_config_offline: "true",
npm_config_ignore_scripts: "false",
};
fs.writeFileSync(npmEnv.npm_config_userconfig, "");
let checkout: string;
let consumerInstalled = false;
let packedSdkReferences: Record<string, string> = {};
const runCommand = vi.fn(async (file: string, args: string[], options?: Parameters<CommandRunner>[2]) => {
if (file === "tar") {
const result = await checkoutCommands(file, args, options);
checkout = args[args.indexOf("-C") + 1];
const sdk = path.join(checkout, "packages/plugins/sdk");
fs.mkdirSync(path.join(sdk, "dist"), { recursive: true });
fs.writeFileSync(path.join(sdk, "dist/index.cjs"), "module.exports.ownVersion = '1.0.0';\n");
fs.writeFileSync(path.join(sdk, "prepare.cjs"), "require('node:fs').writeFileSync('source-pack-hook.txt', 'source hook ran');\n");
fs.writeFileSync(path.join(sdk, "package.json"), JSON.stringify({
name: "@paperclipai/plugin-sdk", version: "1.0.0", main: "dist/index.cjs", files: ["dist"],
dependencies: { "@paperclipai/shared": "0.3.1" }, scripts: { prepack: "node prepare.cjs" },
}));
const manifestPath = path.join(checkout, "scripts/release-package-manifest.json");
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
manifest.push({ dir: "packages/plugins/sdk", name: "@paperclipai/plugin-sdk" });
fs.writeFileSync(manifestPath, JSON.stringify(manifest));
const serverManifestPath = path.join(checkout, "server/package.json");
const serverManifest = JSON.parse(fs.readFileSync(serverManifestPath, "utf8"));
serverManifest.dependencies["@paperclipai/plugin-sdk"] = "workspace:*";
serverManifest.dependencies["@paperclipai/shared"] = "~0.3.0";
serverManifest.optionalDependencies = { "@paperclipai/db": "workspace:~" };
serverManifest.peerDependencies = { "@paperclipai/plugin-sdk": "workspace:^" };
fs.writeFileSync(serverManifestPath, JSON.stringify(serverManifest));
const dbManifestPath = path.join(checkout, "packages/db/package.json");
const dbManifest = JSON.parse(fs.readFileSync(dbManifestPath, "utf8"));
dbManifest.version = "0.2.0";
fs.writeFileSync(dbManifestPath, JSON.stringify(dbManifest));
fs.mkdirSync(path.join(checkout, "cli/dist"), { recursive: true });
fs.writeFileSync(path.join(checkout, "cli/dist/index.js"), "console.log('0.3.1');\n");
fs.writeFileSync(path.join(checkout, "cli/package.json"), JSON.stringify({
name: "paperclipai", version: "0.3.1", files: ["dist"], dependencies: { "@paperclipai/server": "0.3.1" },
}));
return result;
}
if (file === process.execPath && args[0]?.endsWith("prepare-bundled-package.mjs")) {
const result = await checkoutCommands(file, args, options);
const stagedManifestPath = path.join(args[2], "package.json");
// Use the real release manifest translator, including its owning-package
// workspace normalization. The installer must correct the Git-only case.
const manifest = materializePublishManifest(JSON.parse(fs.readFileSync(stagedManifestPath, "utf8")));
if (manifest.name === "@paperclipai/server") {
manifest.scripts = {
prepack: "node ../scripts/source-prepack-does-not-exist.cjs",
postpack: "node ../scripts/source-postpack-does-not-exist.cjs",
postinstall: "node dist/consumer-install.cjs",
};
fs.writeFileSync(path.join(args[2], "dist/consumer-install.cjs"),
"require('node:fs').writeFileSync('consumer-hook.txt', require('@paperclipai/plugin-sdk').ownVersion);\n");
}
fs.writeFileSync(stagedManifestPath, JSON.stringify(manifest));
return result;
}
if (file === "corepack" && args.includes("pack")) {
const source = path.join(checkout, args[args.indexOf("--dir") + 1]);
return runCommandWithDiagnostics("npm", ["pack", "--pack-destination", args[args.indexOf("--pack-destination") + 1], "--json"],
{ cwd: source, env: npmEnv, timeout: 20_000 });
}
if (file === "npm" && args[0] === "pack") {
if (args[1]?.includes("workspace-package-")) {
const manifest = JSON.parse(fs.readFileSync(path.join(args[1], "package.json"), "utf8"));
if (manifest.name === "@paperclipai/server") {
packedSdkReferences = Object.fromEntries(["dependencies", "optionalDependencies", "peerDependencies"]
.map((section) => [section, manifest[section]["@paperclipai/plugin-sdk"]]));
}
}
return runCommandWithDiagnostics(file, [...args, "--json"], { ...options, env: npmEnv, timeout: 20_000 });
}
if (file === "npm" && args[0] === "install") {
expect(args).not.toContain("--ignore-scripts");
expect(args.filter((arg) => arg.endsWith(".tgz"))).toHaveLength(5);
expect(args.some((arg) => arg.endsWith("paperclipai-plugin-sdk-1.0.0.tgz"))).toBe(true);
expect(fs.readFileSync(path.join(checkout, "packages/plugins/sdk/source-pack-hook.txt"), "utf8")).toBe("source hook ran");
const sourceManifest = JSON.parse(fs.readFileSync(path.join(checkout, "server/package.json"), "utf8"));
expect(sourceManifest.dependencies["@paperclipai/plugin-sdk"]).toBe("workspace:*");
expect(sourceManifest.optionalDependencies["@paperclipai/db"]).toBe("workspace:~");
expect(sourceManifest.peerDependencies["@paperclipai/plugin-sdk"]).toBe("workspace:^");
let result;
try {
result = await runCommandWithDiagnostics(file, args, { ...options, env: npmEnv, timeout: 20_000 });
} catch (error) {
throw new Error(`Offline consumer failed with SDK 1.0.0 archive and packed SDK references ${JSON.stringify(packedSdkReferences)}: ${String(error)}`, { cause: error });
}
const installedServer = path.join(args[args.indexOf("--prefix") + 1], "node_modules/@paperclipai/server");
const installed = JSON.parse(fs.readFileSync(path.join(installedServer, "package.json"), "utf8"));
expect(installed.dependencies["@paperclipai/plugin-sdk"]).toBe("1.0.0");
expect(installed.dependencies["@paperclipai/shared"]).toBe("~0.3.0");
expect(installed.dependencies["@paperclipai/db"]).toBe("0.2.0");
expect(installed.optionalDependencies["@paperclipai/db"]).toBe("~0.2.0");
expect(installed.peerDependencies["@paperclipai/plugin-sdk"]).toBe("^1.0.0");
expect(fs.readFileSync(path.join(installedServer, "consumer-hook.txt"), "utf8")).toBe("1.0.0");
consumerInstalled = true;
return result;
}
if (file === process.execPath && args.at(-1) === "--version") {
return runCommandWithDiagnostics(file, args, { ...options, env: npmEnv, timeout: 20_000 });
}
return checkoutCommands(file, args, options);
});
await expect(installGitPayload("paperclipai/paperclip", sha, runCommand, resolveInstallStorePaths()))
.resolves.toMatchObject({ version: "0.3.1", reused: false });
expect(consumerInstalled).toBe(true);
}, 30_000);
it.each(["source build", "staged pack", "consumer install"])("preserves the active Git install after a failed %s", async (phase) => {
const previousSha = "a".repeat(40);
await installCommand({ ref: previousSha, yes: true }, { runCommand: createGitCheckoutRunCommand(previousSha) });
const paths = resolveInstallStorePaths();
const previousManifest = fs.readFileSync(paths.manifestPath, "utf8");
const previousTarget = fs.readlinkSync(paths.currentPath);
const previousShim = fs.readFileSync(paths.shimPath, "utf8");
const sha = "e".repeat(40);
const checkoutCommands = createGitCheckoutRunCommand(sha, { bundledServer: true });
let failedCheckout: string | undefined;
const runCommand = vi.fn(async (file: string, args: string[], options?: Parameters<CommandRunner>[2]) => {
if (file === "tar") failedCheckout = args[args.indexOf("-C") + 1];
const failedPhase = phase === "source build"
? file === "corepack" && args.includes("-r") && args.includes("build")
: phase === "staged pack"
? file === "npm" && args[0] === "pack" && args[1]?.includes("workspace-package-")
: file === "npm" && args[0] === "install";
if (failedPhase) throw new Error(`${phase} failed`);
return checkoutCommands(file, args, options);
});
await expect(installCommand({ ref: sha, yes: true }, { runCommand })).rejects.toThrow(`${phase} failed`);
expect(failedCheckout).toBeDefined();
expect(fs.existsSync(path.dirname(failedCheckout!))).toBe(false);
expect(fs.readdirSync(path.join(paths.installsRoot, "git"))).toEqual([previousSha.slice(0, 12)]);
if (phase === "source build") expect(runCommand.mock.calls.some(([file]) => file === "npm")).toBe(false);
if (phase === "staged pack") expect(runCommand.mock.calls.some(([file, args]) => file === "npm" && args[0] === "install")).toBe(false);
expect(fs.existsSync(paths.lockPath)).toBe(false);
expect(fs.readFileSync(paths.manifestPath, "utf8")).toBe(previousManifest);
expect(fs.readlinkSync(paths.currentPath)).toBe(previousTarget);
expect(fs.readFileSync(paths.shimPath, "utf8")).toBe(previousShim);
expect(fs.existsSync(readInstallManifest(paths)!.payloadPath)).toBe(true);
});
it("stages same-checkout runtime skills in the release packages before bundled Git packaging", async () => {
const sha = "6".repeat(40);
const checkoutCommands = createGitCheckoutRunCommand(sha, { bundledServer: true });
let checkedSkills = false;
const runCommand = vi.fn(async (file: string, args: string[], options?: Parameters<CommandRunner>[2]) => {
if (file === process.execPath && args[0]?.endsWith("prepare-bundled-package.mjs")) {
const checkout = options!.cwd as string;
for (const packageDir of ["server", "packages/adapters/claude-local", "packages/adapters/codex-local"]) {
const skills = path.join(checkout, packageDir, "skills");
expect(fs.readdirSync(skills)).toEqual(["paperclip"]);
for (const file of ["SKILL.md", "scripts/helper.mjs"]) {
expect(fs.readFileSync(path.join(skills, "paperclip", file), "utf8"))
.toBe(fs.readFileSync(path.join(checkout, "skills", "paperclip", file), "utf8"));
}
}
// Other adapters' optional skills keep their established pack behavior.
expect(fs.existsSync(path.join(checkout, "packages/adapters/cursor-local/skills"))).toBe(false);
checkedSkills = true;
}
return checkoutCommands(file, args, options);
});
await expect(installGitPayload("paperclipai/paperclip", sha, runCommand, resolveInstallStorePaths()))
.resolves.toMatchObject({ version: "0.3.1", reused: false });
expect(checkedSkills).toBe(true);
});
it("cleans missing Git runtime skills and preserves the active installed payload", async () => {
const previousSha = "7".repeat(40);
await installCommand({ ref: previousSha, yes: true }, { runCommand: createGitCheckoutRunCommand(previousSha) });
const paths = resolveInstallStorePaths();
const previousManifest = fs.readFileSync(paths.manifestPath, "utf8");
const previousTarget = fs.readlinkSync(paths.currentPath);
const previousShim = fs.readFileSync(paths.shimPath, "utf8");
const sha = "8".repeat(40);
const checkoutCommands = createGitCheckoutRunCommand(sha, { bundledServer: true });
let failedCheckout: string | undefined;
const runCommand = vi.fn(async (file: string, args: string[], options?: Parameters<CommandRunner>[2]) => {
const result = await checkoutCommands(file, args, options);
if (file === "tar") {
failedCheckout = args[args.indexOf("-C") + 1];
fs.rmSync(path.join(failedCheckout, "skills"), { recursive: true });
}
return result;
});
await expect(installCommand({ ref: sha, yes: true }, { runCommand }))
.rejects.toThrow(/ENOENT.*skills/);
expect(failedCheckout).toBeDefined();
expect(fs.existsSync(path.dirname(failedCheckout!))).toBe(false);
expect(fs.readdirSync(path.join(paths.installsRoot, "git"))).toEqual([previousSha.slice(0, 12)]);
expect(runCommand.mock.calls.some(([file, args]) => file === "npm" || (file === process.execPath && args[0]?.endsWith("prepare-bundled-package.mjs")))).toBe(false);
expect(fs.existsSync(paths.lockPath)).toBe(false);
expect(fs.readFileSync(paths.manifestPath, "utf8")).toBe(previousManifest);
expect(fs.readlinkSync(paths.currentPath)).toBe(previousTarget);
expect(fs.readFileSync(paths.shimPath, "utf8")).toBe(previousShim);
expect(fs.existsSync(readInstallManifest(paths)!.payloadPath)).toBe(true);
});
it("cleans a failed Git UI build and preserves the active installed payload", async () => {
const previousSha = "4".repeat(40);
await installCommand({ ref: previousSha, yes: true }, { runCommand: createGitCheckoutRunCommand(previousSha) });
const paths = resolveInstallStorePaths();
const previousManifest = fs.readFileSync(paths.manifestPath, "utf8");
const previousTarget = fs.readlinkSync(paths.currentPath);
const previousShim = fs.readFileSync(paths.shimPath, "utf8");
const sha = "5".repeat(40);
const checkoutCommands = createGitCheckoutRunCommand(sha, { bundledServer: true });
let failedCheckout: string | undefined;
const runCommand = vi.fn(async (file: string, args: string[], options?: Parameters<CommandRunner>[2]) => {
if (file === "corepack" && args.includes("prepare:ui-dist")) {
failedCheckout = options?.cwd as string;
throw new Error("UI build failed");
}
return checkoutCommands(file, args, options);
});
await expect(installCommand({ ref: sha, yes: true }, { runCommand })).rejects.toThrow("UI build failed");
expect(failedCheckout).toBeDefined();
expect(fs.existsSync(path.dirname(failedCheckout!))).toBe(false);
expect(fs.readdirSync(path.join(paths.installsRoot, "git"))).toEqual([previousSha.slice(0, 12)]);
expect(runCommand.mock.calls.some(([file]) => file === "npm")).toBe(false);
expect(fs.existsSync(paths.lockPath)).toBe(false);
expect(fs.readFileSync(paths.manifestPath, "utf8")).toBe(previousManifest);
expect(fs.readlinkSync(paths.currentPath)).toBe(previousTarget);
expect(fs.readFileSync(paths.shimPath, "utf8")).toBe(previousShim);
expect(fs.existsSync(readInstallManifest(paths)!.payloadPath)).toBe(true);
});
it("builds git checkouts with NODE_ENV cleared so ambient production mode keeps devDependencies", async () => {
process.env.NODE_ENV = "production";
const sha = "d".repeat(40);
@@ -178,7 +594,7 @@ describe("managed install commands", () => {
file === "corepack" ||
(file === "npm" && args[0] === "pack") ||
(file === process.execPath && args[0]?.endsWith("prepare-bundled-package.mjs")));
expect(buildCalls).toHaveLength(9);
expect(buildCalls).toHaveLength(11);
for (const call of buildCalls) {
const env = call[2]?.env;
expect(env, `${call[0]} ${call[1].join(" ")} must run with an explicit env`).toBeDefined();
+44 -2
View File
@@ -275,19 +275,61 @@ export async function installGitPayload(repo: string, sha: string, runCommand: C
await runGitHubCurl(["--fail", "--silent", "--show-error", "--location", "--output", archivePath, `https://codeload.github.com/${repo}/tar.gz/${sha}`], runCommand, { maxBuffer: 4 * 1024 * 1024 });
await runCommand("tar", ["-xzf", archivePath, "--strip-components=1", "-C", checkoutPath], { maxBuffer: 4 * 1024 * 1024 });
await runCommand("corepack", ["enable", "pnpm", "--install-directory", pnpmShimDir], { cwd: checkoutPath, env: buildEnv(), maxBuffer: 4 * 1024 * 1024 });
// Exact source commits can precede the lockfile bot. Resolve only inside this
// downloaded checkout, without lifecycle scripts, before its frozen install.
await runCommand("corepack", ["pnpm", "install", "--resolution-only", "--ignore-scripts", "--no-frozen-lockfile"], { cwd: checkoutPath, env: buildEnv(), maxBuffer: 32 * 1024 * 1024 });
await runCommand("corepack", ["pnpm", "install", "--frozen-lockfile"], { cwd: checkoutPath, env: buildEnv(), maxBuffer: 32 * 1024 * 1024 });
await runCommand("bash", ["scripts/build-npm.sh", "--skip-checks", "--skip-typecheck"], { cwd: checkoutPath, env: buildEnv(), maxBuffer: 32 * 1024 * 1024 });
await runCommand("corepack", ["pnpm", "-r", "--filter", "@paperclipai/server...", "--if-present", "run", "build"], { cwd: checkoutPath, env: buildEnv(), maxBuffer: 32 * 1024 * 1024 });
// Bundled package staging bypasses the server's prepack hook. Produce its
// declared UI files from this same checkout before staging that package.
await runCommand("corepack", ["pnpm", "--dir", "server", "prepare:ui-dist"], { cwd: checkoutPath, env: buildEnv(), maxBuffer: 32 * 1024 * 1024 });
// Match release.sh's runtime-skill preparation. These packages declare
// skills separately from their build output; bundled staging bypasses it.
for (const packageDir of ["server", "packages/adapters/claude-local", "packages/adapters/codex-local"]) {
const destination = path.join(checkoutPath, packageDir, "skills");
fs.rmSync(destination, { recursive: true, force: true });
fs.cpSync(path.join(checkoutPath, "skills"), destination, { recursive: true });
}
const metadata = JSON.parse(fs.readFileSync(path.join(checkoutPath, "cli", "package.json"), "utf8")) as { version: string };
const workspacePackages = resolveGitInstallWorkspacePackages(checkoutPath);
const workspaceVersions = new Map(workspacePackages.map((entry) => {
const packageJson = JSON.parse(fs.readFileSync(path.join(checkoutPath, entry.dir, "package.json"), "utf8")) as { version?: unknown };
if (typeof packageJson.version !== "string" || !EXACT_VERSION_PATTERN.test(packageJson.version)) {
throw new Error(`Git install cannot stage ${entry.name}: its package version must be an exact version.`);
}
return [entry.name, packageJson.version] as const;
}));
for (const [index, workspacePackage] of workspacePackages.entries()) {
const packageDir = path.join(checkoutPath, workspacePackage.dir);
const packageJson = JSON.parse(fs.readFileSync(path.join(packageDir, "package.json"), "utf8")) as { bundleDependencies?: string[]; bundledDependencies?: string[] };
const packageJson = JSON.parse(fs.readFileSync(path.join(packageDir, "package.json"), "utf8")) as {
bundleDependencies?: string[]; bundledDependencies?: string[];
dependencies?: Record<string, string>; optionalDependencies?: Record<string, string>; peerDependencies?: Record<string, string>;
};
const bundledDependencies = packageJson.bundleDependencies ?? packageJson.bundledDependencies ?? [];
if (bundledDependencies.length > 0) {
const stagedPackage = path.join(stagingRoot, `workspace-package-${index}`);
await runCommand(process.execPath, [path.join(checkoutPath, "scripts", "prepare-bundled-package.mjs"), packageDir, stagedPackage], { cwd: checkoutPath, env: buildEnv(), maxBuffer: 32 * 1024 * 1024 });
await runCommand("npm", ["pack", stagedPackage, "--pack-destination", stagingRoot], { cwd: checkoutPath, env: buildEnv(), maxBuffer: 16 * 1024 * 1024 });
// Release packaging gives workspace packages one version. A Git checkout
// can retain independent versions, so bind each staged workspace reference
// to that dependency's actual package rather than the owning package.
const stagedManifestPath = path.join(stagedPackage, "package.json");
const stagedManifest = JSON.parse(fs.readFileSync(stagedManifestPath, "utf8")) as typeof packageJson;
for (const section of ["dependencies", "optionalDependencies", "peerDependencies"] as const) {
for (const [name, specifier] of Object.entries(packageJson[section] ?? {})) {
if (!specifier.startsWith("workspace:")) continue;
const version = workspaceVersions.get(name);
if (!version) throw new Error(`Git install cannot stage workspace dependency ${name}: its package version is missing.`);
const range = specifier.slice("workspace:".length);
const prefix = range === "^" || range === "~" ? range : "";
stagedManifest[section] = { ...stagedManifest[section], [name]: `${prefix}${version}` };
}
}
fs.writeFileSync(stagedManifestPath, `${JSON.stringify(stagedManifest, null, 2)}\n`);
// The stage contains compiled runtime files, not the source workspace.
// Match release packaging: source-only prepack/postpack hooks must not
// rebuild it or remove its prepared assets. Consumer install hooks stay enabled.
await runCommand("npm", ["pack", stagedPackage, "--pack-destination", stagingRoot, "--ignore-scripts"], { cwd: checkoutPath, env: buildEnv(), maxBuffer: 16 * 1024 * 1024 });
} else {
await runCommand("corepack", ["pnpm", "--dir", workspacePackage.dir, "pack", "--pack-destination", stagingRoot], { cwd: checkoutPath, env: buildEnv({ PAPERCLIP_RELEASE_REUSE_UI_DIST: "1" }), maxBuffer: 32 * 1024 * 1024 });
}
+6
View File
@@ -26,6 +26,12 @@ GitHub Actions owns `pnpm-lock.yaml`.
- Pull request CI validates dependency resolution when manifests change.
- Pushes to `master` regenerate `pnpm-lock.yaml` with `pnpm install --lockfile-only --no-frozen-lockfile`, commit it back if needed, and then run verification with `--frozen-lockfile`.
`paperclipai install --ref` resolves dependencies inside its disposable downloaded
checkout before the frozen install. It builds the UI and stages runtime skills
from that checkout, then packs the prepared files with source packaging hooks
disabled. Consumer installation hooks remain enabled. A preparation failure
leaves the current installed version active.
## Trusted PR Workflow
The PR caller uses `paperclipai/paperclip/.github/workflows/pr-trusted.yml@master`.
@@ -0,0 +1,64 @@
# Runner defaults: staged delivery
Updated 2026-10-08.
## Outcome and ownership
Users choose a harness. New agents use Paperclip Runner where the harness and
execution target are qualified. Existing agents keep their recorded runner.
The task owns implementation, verification, review fixes, and reviewable PRs.
Merging and deployment require separate authorization.
The original implementation is preserved on `codex/default-agent-runner` at
`171d841295808ca185a258aaeb10b3dee68bb2b9`. Its public review is
[PR #15422](https://github.com/paperclipai/paperclip/pull/15422).
The user approved splitting that work into four useful steps:
1. Packaged runtime reliability and Codex prerequisites, without changing defaults.
2. Codex defaults across creation, onboarding, hiring, imports, and the UI.
3. Claude defaults and authentication.
4. The remaining supported harness defaults and provider-specific fixes.
## Current slice
Canonical branch: `codex/runner-packaging-prerequisites`.
Base: `71cd0a2621615182274a977d6cc969b9ab920b92`.
PR, tested commit, and CI links: pending the first slice's commit and publication.
This slice fixes the installed dependency graph, Codex executable resolution,
isolated browser login, Git install staging, and the release asset transfer needed
for Linux and macOS packages. It reuses the existing install sandbox, release
verification workflow, tests, and runner binary assembler.
It does not change agent defaults, the harness picker, provider qualification,
database schema, experimental feature policy, or existing agent configuration.
The large execution and setup changes stay in the later slices.
## Evidence and gates
| Gate | Status | Evidence |
| --- | --- | --- |
| Preserve original implementation | Passed | Original branch and commit above |
| Extract only first-slice behavior | Passed | 30 files; separate branch; independent packaging/release review |
| Installer and packaging tests | Passed | Existing installer suite: 26 tests; existing packaging suite: 24 tests |
| Login and transport controls | Passed | Login: 13/13; focused Codex/transport selection: 21 passed, 185 outside the focused filter |
| Release-transfer controls | Passed | Release workflow, transfer, and sandbox suites: 38/38; actionlint and Node/shell syntax checks passed |
| Token gates | Passed | All three commands in `check:token-gates` passed |
| Package and module contracts | Passed | Release package manifest and feature module boundary checks passed |
| Clean installed Codex version probe | Pending | Existing Linux npm consumer sandbox in CI |
| Full checks and fresh review | Pending | Require green checks on the final PR head |
| All-harness live onboarding and cloud qualification | Deferred | Owned by later slices; prior evidence does not prove this revision |
Heavy builds run in hosted CI. No local Docker or Rust build is planned.
No provider-backed runs or new disposable environments are needed for this
slice. The original $250 cost ceiling and cleanup obligations still apply.
Existing login credentials and running user previews must remain untouched.
Focused test commands use Node 24 and a single worker: `node --test
--test-concurrency=1 scripts/acpx-patch-packaging.test.mjs` and, from `cli/`,
`node ../node_modules/vitest/vitest.mjs run src/__tests__/install-command.test.ts
--maxWorkers=1 --no-file-parallelism`. These tests include real offline npm pack
and installation controls; they do not claim a published release or provider task.
Next action: publish the first PR and resolve its review and check failures on
the final candidate.
+14
View File
@@ -58,6 +58,20 @@ checks installed dependency pins and agreement with Cursor's distribution manife
and immutable release attestation (`cursor-contract.json`). Cursor's per-platform
closure pins are generated from `cursor-distributions.json`.
Packaged Codex startup and browser login resolve the Codex CLI from the installed
ACP bridge dependency graph. They verify the qualified version and the executable
path before launch. An explicit execution command still takes precedence. A
missing or mismatched bundled runtime returns an installation error; it does not
borrow a different Codex installation from `PATH`. Linux ARM64 retains its
existing legacy login path because native execution is not qualified there.
Release packages stage the qualified Codex dependency graph and native platform
artifacts. The Linux artifact digest is recorded in
`src/drivers/acpx/qualified-runtime-artifacts.json`. macOS payloads must match the
pinned package version and host architecture. Release assembly also checks the
source revision and runner binary identities before publication. This packaging
does not change agent defaults or the selected runner of an existing agent.
Every ACPX harness accepts an explicit caller-selected model without a Paperclip
model allowlist. The adapter sends that ID unchanged and verifies the provider's
effective model before prompting. An incomplete discovery catalog does not block
@@ -0,0 +1,17 @@
{
"schema": "paperclip.acpx-runtime-artifacts.v1",
"codex": {
"platforms": {
"linux-x64": {
"runtimePackageName": "@openai/codex",
"runtimePackageVersion": "0.160.0",
"packageName": "@openai/codex-linux-x64",
"packageVersion": "0.160.0-linux-x64",
"dependencyDeclaration": "npm:@openai/codex@0.160.0-linux-x64",
"relativeExecutable": "vendor/x86_64-unknown-linux-musl/bin/codex",
"executableDigest": "sha256:12eb3e81114588aca3b7998f4f19e8997b056aca08e57a7ca7c8a3ec8c652aad",
"environmentVariable": "CODEX_PATH"
}
}
}
}
+280 -6
View File
@@ -1,12 +1,15 @@
import assert from "node:assert/strict";
import { execFileSync } from "node:child_process";
import { createHash } from "node:crypto";
import {
existsSync,
lstatSync,
mkdirSync,
mkdtempSync,
readFileSync,
realpathSync,
rmSync,
symlinkSync,
writeFileSync,
} from "node:fs";
import { readFile } from "node:fs/promises";
@@ -20,11 +23,58 @@ import cliEsbuildConfig from "../cli/esbuild.config.mjs";
import { bundledCliNpmDependencies } from "./cli-bundled-npm-dependencies.mjs";
import {
createBundledInstallManifest,
configureBundledProviderOverrides,
dockerBundledProviderTarget,
inspectBundledNativeArtifact,
materializePublishManifest,
mergeBundledProviderGraph,
selectBundledDependencyPatches,
stageBundledEsbuildOptionalDependencies,
stageBundledProviderOptionalDependencies,
} from "./prepare-bundled-package.mjs";
const rootPackage = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8"));
const profileData = JSON.parse(await readFile(new URL("../packages/paperclip-runner/acpx-profiles.json", import.meta.url), "utf8"));
const runtimeData = JSON.parse(await readFile(new URL("../packages/paperclip-runner/src/drivers/acpx/qualified-runtime-artifacts.json", import.meta.url), "utf8"));
const nativeTargets = ["linux-x64", "darwin-arm64", "darwin-x64"];
function esbuildFixture(t) {
const directory = realpathSync(mkdtempSync(join(tmpdir(), "paperclip-bundled-esbuild-")));
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 acpx = writePackage(join(graph, "acpx"), { name: "acpx", version: "0.13.1" });
const esbuild = writePackage(join(acpx, "node_modules/esbuild"), {
name: "esbuild", version: "0.28.2", scripts: { postinstall: "node install.js" },
optionalDependencies: Object.fromEntries([...nativeTargets, "win32-x64"].map(target => [`@esbuild/${target}`, "0.28.2"])),
});
const producerBinary = writePackage(join(graph, "@esbuild/linux-x64"), {
name: "@esbuild/linux-x64", version: "0.28.2", os: ["linux"], cpu: ["x64"],
});
writePackage(join(graph, "unrelated-linux-x64"), { name: "unrelated-linux-x64", version: "1.0.0" });
const publish = { name: "@paperclipai/adapter-utils", version: "0.3.1", bundleDependencies: ["acpx"],
dependencies: { acpx: "0.13.1" }, optionalDependencies: { unrelated: "1.0.0" } };
return { directory, graph, esbuild, producerBinary, publish, writePackage };
}
function nativeFixture(directory, profile, target, data) {
const [os, cpu] = target.split("-");
const name = `${profile.agentRuntimePackage}-${target}`;
const packageRoot = join(directory, name);
mkdirSync(packageRoot, { recursive: true });
writeFileSync(join(packageRoot, "package.json"), JSON.stringify({ name, version: `${profile.agentRuntimeVersion}-${target}`, os: [os], cpu: [cpu] }));
const qualification = data[profile.agent].platforms[target];
const executable = qualification?.relativeExecutable ?? `vendor/${cpu === "arm64" ? "aarch64" : "x86_64"}-apple-darwin/bin/codex`;
const bytes = Buffer.alloc(32);
if (os === "linux") { bytes.set([0x7f, 0x45, 0x4c, 0x46, 2, 1]); bytes.writeUInt16LE(62, 18); }
else { bytes.writeUInt32LE(0xfeedfacf, 0); bytes.writeUInt32LE(cpu === "arm64" ? 0x0100000c : 0x01000007, 4); }
mkdirSync(join(packageRoot, executable, ".."), { recursive: true });
writeFileSync(join(packageRoot, executable), bytes, { mode: 0o755 });
if (qualification) qualification.executableDigest = `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
return packageRoot;
}
const adapterUtilsPackage = JSON.parse(
await readFile(new URL("../packages/adapter-utils/package.json", import.meta.url), "utf8"),
);
@@ -40,6 +90,7 @@ const dbPackage = JSON.parse(
const releaseScript = await readFile(new URL("./release.sh", import.meta.url), "utf8");
const releaseLib = await readFile(new URL("./release-lib.sh", import.meta.url), "utf8");
const buildNpmScript = await readFile(new URL("./build-npm.sh", import.meta.url), "utf8");
const acpxRuntimePatch = await readFile(
new URL("../patches/acpx@0.13.1.patch", import.meta.url),
"utf8",
@@ -124,6 +175,186 @@ test("published packages preserve the patched ACPX runtime", () => {
assert.equal(cliEsbuildConfig.external.includes("acpx"), false);
});
test("published Codex bridge uses its qualified producer closure without consumer overrides", () => {
const published = materializePublishManifest(serverPackage);
const { installManifest, profiles } = configureBundledProviderOverrides(
createBundledInstallManifest(published, serverPackage.bundleDependencies),
serverPackage.bundleDependencies, rootPackage, profileData,
);
assert.equal(profiles.length, 1);
for (const profile of profiles) {
assert.equal(published.dependencies[profile.agentServerPackage], profile.agentServerVersion);
assert.ok(published.bundleDependencies.includes(profile.agentServerPackage));
assert.equal(installManifest.overrides[`${profile.agentServerPackage}@${profile.agentServerVersion}`][profile.agentRuntimePackage], profile.agentRuntimeVersion);
}
assert.equal(published.overrides, undefined);
assert.equal(installManifest.overrides.rollup, undefined);
const bad = structuredClone(rootPackage);
bad.pnpm.overrides["@agentclientprotocol/codex-acp@1.6.2>@openai/codex"] = "0.156.1";
assert.throws(() => configureBundledProviderOverrides(installManifest, serverPackage.bundleDependencies, bad, profileData), /runtime override must match/);
const unpinned = createBundledInstallManifest(published, serverPackage.bundleDependencies);
unpinned.dependencies["@agentclientprotocol/codex-acp"] = "^1.6.2";
assert.throws(() => configureBundledProviderOverrides(unpinned, serverPackage.bundleDependencies, rootPackage, profileData), /exact qualified profile/);
const conflict = createBundledInstallManifest(published, serverPackage.bundleDependencies);
conflict.overrides = { "@agentclientprotocol/codex-acp@1.6.2": { "@openai/codex": "0.156.1" } };
assert.throws(() => configureBundledProviderOverrides(conflict, serverPackage.bundleDependencies, rootPackage, profileData), /conflicting producer override/);
});
test("Linux-produced Codex bridge tarballs retain all three contained artifacts and preserve runtime declarations", (t) => {
const destination = mkdtempSync(join(tmpdir(), "paperclip-bundled-provider-"));
t.after(() => rmSync(destination, { recursive: true, force: true }));
const graph = join(destination, "node_modules");
const artifacts = join(destination, "artifacts"), data = structuredClone(runtimeData);
mkdirSync(artifacts);
const { profiles } = configureBundledProviderOverrides(
createBundledInstallManifest(serverPackage, serverPackage.bundleDependencies), serverPackage.bundleDependencies, rootPackage, profileData,
);
const writePackage = (name, metadata) => {
const directory = join(graph, name);
mkdirSync(directory, { recursive: true });
writeFileSync(join(directory, "package.json"), JSON.stringify({ name, ...metadata }));
return directory;
};
for (const profile of profiles) {
writePackage(profile.agentServerPackage, { version: profile.agentServerVersion,
dependencies: { [profile.agentRuntimePackage]: profile.agentRuntimeVersion } });
const optional = Object.fromEntries(nativeTargets.map(target => [`${profile.agentRuntimePackage}-${target}`,
`npm:@openai/codex@${profile.agentRuntimeVersion}-${target}`]));
writePackage(profile.agentRuntimePackage, { version: profile.agentRuntimeVersion, optionalDependencies: optional });
writePackage(`${profile.agentRuntimePackage}-linux-x64`, { version: "0.160.0-linux-x64" });
for (const target of nativeTargets) nativeFixture(artifacts, profile, target, data);
}
writePackage("unrelated-linux-x64", { version: "1.0.0" });
const before = readFileSync(join(graph, "@agentclientprotocol/codex-acp/package.json"), "utf8");
const stage = manifest => stageBundledProviderOptionalDependencies(destination, manifest, profiles, data, { nativeArtifactsDirectory: artifacts });
const staged = stage(serverPackage);
assert.equal(staged.overrides, undefined);
assert.equal(staged.optionalDependencies["@openai/codex-darwin-arm64"], "npm:@openai/codex@0.160.0-darwin-arm64");
for (const profile of profiles) {
for (const target of nativeTargets) {
assert.equal(existsSync(join(graph, `${profile.agentRuntimePackage}-${target}`)), true);
assert.ok(staged.bundleDependencies.includes(`${profile.agentRuntimePackage}-${target}`));
}
assert.equal(existsSync(join(graph, profile.agentRuntimePackage, "package.json")), true);
}
assert.equal(existsSync(join(graph, "unrelated-linux-x64")), true);
assert.equal(readFileSync(join(graph, "@agentclientprotocol/codex-acp/package.json"), "utf8"), before);
const sdkPath = join(graph, "@openai/codex/package.json");
const sdk = JSON.parse(readFileSync(sdkPath, "utf8"));
writeFileSync(sdkPath, JSON.stringify({ ...sdk, version: "0.156.1" }));
assert.throws(() => stage(serverPackage), /runtime version mismatch/);
writeFileSync(sdkPath, JSON.stringify(sdk));
assert.throws(() => stage({ ...serverPackage,
optionalDependencies: { "@openai/codex-darwin-arm64": "npm:@openai/codex@0.156.1-darwin-arm64" } }), /conflicts with published/);
writeFileSync(sdkPath, JSON.stringify({ ...sdk, optionalDependencies: { "@openai/codex-untrusted": "0.160.0" } }));
assert.throws(() => stage(serverPackage), /not qualified/);
writeFileSync(sdkPath, JSON.stringify(sdk));
const codex = profiles.find(value => value.agent === "codex");
const artifact = join(artifacts, "@openai/codex-linux-x64");
assert.equal(inspectBundledNativeArtifact(artifact, codex, "linux-x64", data).target, "linux-x64");
writeFileSync(join(artifact, "package.json"), JSON.stringify({ name: "@openai/codex-linux-x64", version: "0.160.0-linux-x64", os: ["darwin"], cpu: ["x64"] }));
assert.throws(() => inspectBundledNativeArtifact(artifact, codex, "linux-x64", data), /identity mismatch/);
});
test("Linux-produced esbuild bundles publish exact consumer platform dependencies and retain their hooks", (t) => {
const { directory, graph, esbuild, producerBinary, publish, writePackage } = esbuildFixture(t);
const original = readFileSync(join(esbuild, "package.json"), "utf8");
const issuer = createRequire(join(esbuild, "package.json"));
// The original producer-only topology cannot resolve a Mac binary. The
// public optional declaration lets npm obtain it before offline hooks run.
assert.throws(() => issuer.resolve("@esbuild/darwin-arm64/bin/esbuild"), /Cannot find module/);
const staged = stageBundledEsbuildOptionalDependencies(directory, publish);
for (const target of [...nativeTargets, "win32-x64"]) assert.equal(staged.optionalDependencies[`@esbuild/${target}`], "0.28.2");
assert.equal(staged.optionalDependencies.unrelated, "1.0.0");
assert.deepEqual(staged.bundleDependencies, ["acpx"]);
assert.deepEqual(staged.dependencies, publish.dependencies);
assert.equal(publish.optionalDependencies["@esbuild/darwin-arm64"], undefined);
assert.equal(existsSync(producerBinary), false);
assert.equal(existsSync(join(graph, "unrelated-linux-x64")), true);
assert.equal(readFileSync(join(esbuild, "package.json"), "utf8"), original);
assert.deepEqual(stageBundledEsbuildOptionalDependencies(directory, staged), staged);
for (const target of ["darwin-arm64", "darwin-x64"]) {
const artifact = writePackage(join(graph, `@esbuild/${target}`), {
name: `@esbuild/${target}`, version: staged.optionalDependencies[`@esbuild/${target}`], os: ["darwin"], cpu: [target.slice(7)],
});
mkdirSync(join(artifact, "bin")); writeFileSync(join(artifact, "bin/esbuild"), "consumer-selected fixture; never executed");
assert.equal(issuer.resolve(`@esbuild/${target}/bin/esbuild`), join(artifact, "bin/esbuild"));
}
});
test("bundled esbuild rejects conflicting published or nested platform versions without removing the producer", (t) => {
const { directory, graph, producerBinary, publish, writePackage } = esbuildFixture(t);
assert.throws(() => stageBundledEsbuildOptionalDependencies(directory, {
...publish, optionalDependencies: { "@esbuild/darwin-arm64": "0.27.0" },
}), /platform version conflicts/);
assert.equal(existsSync(producerBinary), true);
writePackage(join(graph, "esbuild"), { name: "esbuild", version: "0.27.0",
optionalDependencies: Object.fromEntries(nativeTargets.map(target => [`@esbuild/${target}`, "0.27.0"])) });
assert.throws(() => stageBundledEsbuildOptionalDependencies(directory, publish), /platform version conflicts|installed platform identity mismatch/);
assert.equal(existsSync(producerBinary), true);
});
test("bundled esbuild rejects missing targets, altered declarations and incorrect platform identity", (t) => {
const { directory, esbuild, producerBinary, publish } = esbuildFixture(t);
const path = join(esbuild, "package.json"), metadata = JSON.parse(readFileSync(path, "utf8"));
writeFileSync(path, JSON.stringify({ ...metadata, optionalDependencies: { "@esbuild/linux-x64": "0.28.2" } }));
assert.throws(() => stageBundledEsbuildOptionalDependencies(directory, publish), /omitted a supported platform/);
writeFileSync(path, JSON.stringify({ ...metadata, optionalDependencies: { ...metadata.optionalDependencies, "@esbuild/win32-x64": "^0.28.2" } }));
assert.throws(() => stageBundledEsbuildOptionalDependencies(directory, publish), /exact package version/);
writeFileSync(path, JSON.stringify(metadata));
writeFileSync(join(producerBinary, "package.json"), JSON.stringify({ name: "@esbuild/linux-x64", version: "0.27.0" }));
assert.throws(() => stageBundledEsbuildOptionalDependencies(directory, publish), /installed platform identity mismatch/);
assert.equal(existsSync(producerBinary), true);
});
test("bundled esbuild cannot follow a platform payload or package namespace outside its producer graph", (t) => {
const { directory, graph, producerBinary, publish, writePackage } = esbuildFixture(t);
const external = writePackage(join(directory, "outside"), { name: "@esbuild/linux-x64", version: "0.28.2" });
rmSync(producerBinary, { recursive: true }); symlinkSync(external, producerBinary, "dir");
assert.throws(() => stageBundledEsbuildOptionalDependencies(directory, publish), /escapes its producer graph/);
assert.equal(readFileSync(join(external, "package.json"), "utf8").includes("0.28.2"), true);
rmSync(join(graph, "@esbuild"), { recursive: true }); symlinkSync(external, join(graph, "@esbuild"), "dir");
assert.throws(() => stageBundledEsbuildOptionalDependencies(directory, publish), /escapes its producer graph/);
});
test("Codex runtime artifact data preserves the existing Linux qualification without adding targets", () => {
assert.deepEqual(Object.keys(runtimeData).sort(), ["codex", "schema"]);
assert.deepEqual(Object.keys(runtimeData.codex.platforms), ["linux-x64"]);
assert.equal(runtimeData.codex.platforms["linux-x64"].executableDigest, "sha256:12eb3e81114588aca3b7998f4f19e8997b056aca08e57a7ca7c8a3ec8c652aad");
assert.equal(runtimeData.codex.platforms["linux-x64"].environmentVariable, "CODEX_PATH");
});
test("Docker provider graphs use qualified targets and contain only native entry bindings without changing server dependencies", (t) => {
assert.equal(dockerBundledProviderTarget("amd64"), "linux-x64");
assert.equal(dockerBundledProviderTarget("arm64"), null, "Unqualified native ARM64 must not break legacy images");
assert.throws(() => dockerBundledProviderTarget("x64"), /declared supported target/);
const missing = structuredClone(runtimeData); delete missing.codex.platforms["linux-x64"];
assert.equal(dockerBundledProviderTarget("amd64", missing), null, "The existing Codex qualification contract is required");
const fixture = realpathSync(mkdtempSync(join(tmpdir(), "paperclip-docker-provider-graph-")));
t.after(() => rmSync(fixture, { recursive: true, force: true }));
const server = join(fixture, "server"), staged = join(fixture, "staged"), store = join(fixture, "workspace-store");
for (const directory of [server, staged, store]) mkdirSync(directory);
mkdirSync(join(server, "node_modules/@agentclientprotocol"), { recursive: true });
writeFileSync(join(server, "package.json"), JSON.stringify(serverPackage));
for (const name of ["@agentclientprotocol/codex-acp", "zod"]) {
const directory = join(staged, "node_modules", name); mkdirSync(directory, { recursive: true });
writeFileSync(join(directory, "package.json"), JSON.stringify({ name, version: serverPackage.dependencies[name] ?? "4.4.3" }));
const previous = join(store, name); mkdirSync(previous, { recursive: true });
writeFileSync(join(previous, "package.json"), JSON.stringify({ name, version: "previous-version" }));
symlinkSync(previous, join(server, "node_modules", name));
}
writeFileSync(join(staged, "package.json"), JSON.stringify({ name: serverPackage.name }));
const before = readFileSync(join(server, "node_modules/zod/package.json"), "utf8");
mergeBundledProviderGraph(staged, server);
assert.equal(readFileSync(join(server, "node_modules/zod/package.json"), "utf8"), before, "Application dependencies retain the existing workspace binding");
for (const name of ["@agentclientprotocol/codex-acp"]) {
assert.equal(lstatSync(join(server, "node_modules", name)).isSymbolicLink(), true);
assert.equal(realpathSync(join(server, "node_modules", name)), join(server, "node_modules/.paperclip-native-providers/node_modules", name));
assert.equal(JSON.parse(readFileSync(join(store, name, "package.json"))).version, "previous-version", "Workspace store must remain untouched");
}
assert.throws(() => mergeBundledProviderGraph(staged, server), /destination already exists/);
});
test("Paperclip Runner pins the qualified ACPX host callbacks", () => {
assert.equal(rootPackage.pnpm.patchedDependencies["acpx@0.13.1"], "patches/acpx@0.13.1.patch");
assert.equal(
@@ -285,11 +516,14 @@ test("bundled package patch selection rejects an unpatched installed version", (
});
test("server package staging applies every bundled runtime patch and preserves the vendored runner", (t) => {
const fixtureDir = mkdtempSync(join(tmpdir(), "paperclip-bundled-stage-"));
const fixtureDir = realpathSync(mkdtempSync(join(tmpdir(), "paperclip-bundled-stage-")));
const sourceDir = join(fixtureDir, "source");
const destinationDir = join(fixtureDir, "destination");
const binDir = join(fixtureDir, "bin");
const callLog = join(fixtureDir, "calls.log");
const fixtureSourceRoot = join(fixtureDir, "repo");
const fixtureArtifacts = join(fixtureDir, "native-artifacts");
const fixtureRuntimeData = structuredClone(runtimeData);
mkdirSync(sourceDir);
mkdirSync(join(sourceDir, "dist"));
writeFileSync(join(sourceDir, "dist", "index.js"), "export {};\n");
@@ -300,6 +534,18 @@ test("server package staging applies every bundled runtime patch and preserves t
JSON.stringify({ ...serverPackage, files: ["dist"] }),
);
writeFileSync(callLog, "");
mkdirSync(fixtureArtifacts);
for (const [agent, profile] of Object.entries(profileData.profiles).filter(([agent]) => agent === "codex")) {
for (const target of nativeTargets) nativeFixture(fixtureArtifacts, { agent, ...profile }, target, fixtureRuntimeData);
}
mkdirSync(join(fixtureSourceRoot, "packages/paperclip-runner/src/drivers/acpx"), { recursive: true });
writeFileSync(join(fixtureSourceRoot, "package.json"), JSON.stringify(rootPackage));
writeFileSync(join(fixtureSourceRoot, "packages/paperclip-runner/acpx-profiles.json"), JSON.stringify(profileData));
writeFileSync(join(fixtureSourceRoot, "packages/paperclip-runner/src/drivers/acpx/qualified-runtime-artifacts.json"), JSON.stringify(fixtureRuntimeData));
mkdirSync(join(fixtureSourceRoot, "patches"));
for (const patchPath of Object.values(rootPackage.pnpm.patchedDependencies)) {
writeFileSync(join(fixtureSourceRoot, patchPath), readFileSync(new URL(`../${patchPath}`, import.meta.url)));
}
t.after(() => rmSync(fixtureDir, { recursive: true, force: true }));
const writeExecutable = (name, body) => {
@@ -320,9 +566,11 @@ mkdir -p "$destination/node_modules/.pnpm"
`#!/usr/bin/env bash
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 })); }'
[ "$*" = "install --omit=dev --ignore-scripts --no-audit --no-fund" ] || [ "$*" = "install --force --omit=dev --ignore-scripts --no-audit --no-fund" ]
node -e 'const fs = require("node:fs"); const path=require("node:path"); const pkg = require("./package.json"); if ("devDependencies" in pkg) process.exit(1); if(pkg.name === "paperclip-native-artifact-staging"){for(const name of Object.keys(pkg.dependencies)){fs.cpSync(path.join(process.env.FAKE_NATIVE_ARTIFACTS,name),path.join("node_modules",name),{recursive:true});}process.exit(0);} 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","darwin-arm64","darwin-x64"].map(target => [name+"-"+target,"npm:"+name+"@"+version+"-"+target])) : undefined; fs.writeFileSync(dir + "/package.json", JSON.stringify({ name, version, optionalDependencies })); } }'
if [ -f package.json ] && node -e 'process.exit(require("./package.json").name === "paperclip-native-artifact-staging" ? 0 : 1)'; then exit 0; fi
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"]}));'
printf 'unpatched runtime\\n' > node_modules/acpx/dist/runtime.js
printf '{"name":"acpx","version":"0.13.1"}\\n' > node_modules/acpx/package.json
`,
@@ -356,9 +604,11 @@ printf 'patched spawnEnvironment runtime\\n' > "$target/dist/runtime.js"
execFileSync(
process.execPath,
[
new URL("./prepare-bundled-package.mjs", import.meta.url).pathname,
"--input-type=module", "-e",
`import { prepareBundledPackage } from ${JSON.stringify(new URL("./prepare-bundled-package.mjs", import.meta.url).href)}; prepareBundledPackage(process.argv[1], process.argv[2], { sourceRoot: process.argv[3] });`,
sourceDir,
destinationDir,
fixtureSourceRoot,
],
{
env: {
@@ -366,6 +616,7 @@ printf 'patched spawnEnvironment runtime\\n' > "$target/dist/runtime.js"
PATH: `${binDir}:${process.env.PATH}`,
FAKE_CALL_LOG: callLog,
FAKE_SOURCE_PACKAGE: join(sourceDir, "package.json"),
FAKE_NATIVE_ARTIFACTS: fixtureArtifacts,
},
stdio: "pipe",
},
@@ -375,6 +626,11 @@ printf 'patched spawnEnvironment runtime\\n' > "$target/dist/runtime.js"
assert.equal(lstatSync(stagedAcpxDir).isDirectory(), true);
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"));
assert.equal(stagedManifest.optionalDependencies["@esbuild/darwin-arm64"], "0.28.2");
assert.equal(stagedManifest.optionalDependencies["@esbuild/darwin-x64"], "0.28.2");
assert.equal(existsSync(join(destinationDir, "node_modules/@esbuild/linux-x64")), false);
assert.equal(JSON.parse(readFileSync(join(destinationDir, "node_modules/esbuild/package.json"))).scripts.postinstall, "node install.js");
assert.match(
readFileSync(join(stagedAcpxDir, "dist/runtime.js"), "utf8"),
/spawnEnvironment/,
@@ -383,13 +639,14 @@ printf 'patched spawnEnvironment runtime\\n' > "$target/dist/runtime.js"
readFileSync(callLog, "utf8"),
/patch -p1 --forward -d .*node_modules\/acpx/,
);
const patchedBundledDependencies = serverPackage.bundleDependencies.filter(name => rootPackage.pnpm.patchedDependencies[`${name}@${serverPackage.dependencies[name]}`]);
assert.equal(
readFileSync(callLog, "utf8")
.split("\n")
.filter((line) => line.startsWith("patch ")).length,
serverPackage.bundleDependencies.length,
patchedBundledDependencies.length,
);
for (const name of serverPackage.bundleDependencies) {
for (const name of patchedBundledDependencies) {
const specifier = `${name}@${serverPackage.dependencies[name]}`;
const patchPath = rootPackage.pnpm.patchedDependencies[specifier];
assert.equal(
@@ -401,6 +658,23 @@ printf 'patched spawnEnvironment runtime\\n' > "$target/dist/runtime.js"
`${specifier} receives its own full configured patch`,
);
}
const imageServer = join(fixtureDir, "image-server");
mkdirSync(join(imageServer, "node_modules"), { recursive: true });
writeFileSync(join(imageServer, "package.json"), JSON.stringify(serverPackage));
writeFileSync(join(imageServer, "node_modules/unrelated-marker"), "keep existing application graph");
const imageReceipt = JSON.parse(execFileSync(process.execPath, ["--input-type=module", "-e",
`import { materializeDockerProviderGraph } from ${JSON.stringify(new URL("./prepare-bundled-package.mjs", import.meta.url).href)};
console.log(JSON.stringify(materializeDockerProviderGraph(process.argv[1], 'amd64', { sourceRoot: process.argv[2] })));`,
imageServer, fixtureSourceRoot], { env: { ...process.env, PATH: `${binDir}:${process.env.PATH}`,
FAKE_CALL_LOG: callLog, FAKE_NATIVE_ARTIFACTS: fixtureArtifacts }, encoding: "utf8" }).trim().split("\n").at(-1));
assert.equal(imageReceipt.materialized, true);
assert.equal(imageReceipt.artifacts.artifacts.length, 1, "Image must stage only its existing qualified Linux target");
assert.equal(imageReceipt.artifacts.artifacts.every(artifact => artifact.target === "linux-x64"), true);
assert.equal(readFileSync(join(imageServer, "node_modules/unrelated-marker"), "utf8"), "keep existing application graph");
for (const name of ["@agentclientprotocol/codex-acp"]) {
assert.match(realpathSync(join(imageServer, "node_modules", name)), /image-server\/node_modules\/\.paperclip-native-providers\/node_modules/);
}
});
test("bundled package dry runs preview without querying published versions", () => {
+351 -4
View File
@@ -1,11 +1,17 @@
#!/usr/bin/env node
import { execFileSync } from "node:child_process";
import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { resolve } from "node:path";
import { createHash } from "node:crypto";
import { chmodSync, cpSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
import { createRequire } from "node:module";
import { basename, dirname, isAbsolute, relative, resolve, sep } from "node:path";
import { tmpdir } from "node:os";
import { fileURLToPath } from "node:url";
const repoRoot = resolve(fileURLToPath(new URL("..", import.meta.url)));
const runtimeDataPath = "packages/paperclip-runner/src/drivers/acpx/qualified-runtime-artifacts.json";
const nativeTargets = ["linux-x64", "darwin-arm64", "darwin-x64"];
const digest = bytes => `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
export function materializePublishManifest(pkg) {
const publishConfig = pkg.publishConfig ?? {};
@@ -48,6 +54,332 @@ export function createBundledInstallManifest(publishManifest, bundledDependencie
return installManifest;
}
// npm consumers cannot inherit the workspace's pnpm overrides or patches.
// Materialize the already-qualified Codex bridge closure in the tarball,
// with overrides confined to the temporary, scripts-disabled producer graph.
export function configureBundledProviderOverrides(installManifest, bundledDependencies, rootPackage, profileData,
runtimeData = JSON.parse(readFileSync(resolve(repoRoot, runtimeDataPath), "utf8"))) {
const result = structuredClone(installManifest);
const selected = [];
for (const [agent, serverPackage, runtimePackage] of [
["codex", "@agentclientprotocol/codex-acp", "@openai/codex"],
]) {
if (!bundledDependencies.includes(serverPackage)) continue;
const profile = profileData?.profiles?.[agent];
if (profileData?.schema !== "paperclip.acpx-profiles.v1" || profile?.agentServerPackage !== serverPackage
|| profile.agentRuntimePackage !== runtimePackage || !/^\d+\.\d+\.\d+$/.test(profile.agentServerVersion ?? "")
|| !/^\d+\.\d+\.\d+$/.test(profile.agentRuntimeVersion ?? "")
|| result.dependencies?.[serverPackage] !== profile.agentServerVersion) {
throw new Error(`Bundled ${agent} bridge must match its exact qualified profile`);
}
const selector = `${serverPackage}@${profile.agentServerVersion}>${runtimePackage}`;
if (rootPackage.pnpm?.overrides?.[selector] !== profile.agentRuntimeVersion) {
throw new Error(`Bundled ${agent} runtime override must match its qualified profile`);
}
const npmSelector = `${serverPackage}@${profile.agentServerVersion}`;
if (result.overrides?.[npmSelector] !== undefined) {
throw new Error(`Bundled ${agent} runtime has a conflicting producer override`);
}
const overrides = { [runtimePackage]: profile.agentRuntimeVersion };
result.overrides = { ...result.overrides, [npmSelector]: overrides };
selected.push({ agent, ...profile });
}
return { installManifest: result, profiles: selected };
}
const inside = (root, candidate) => {
const value = relative(root, candidate);
return value !== "" && value !== ".." && !value.startsWith(`..${sep}`) && !isAbsolute(value);
};
export function inspectBundledNativeArtifact(directory, profile, target, runtimeData) {
if (profile.agent !== "codex" || !nativeTargets.includes(target) || runtimeData?.schema !== "paperclip.acpx-runtime-artifacts.v1") throw new Error("Bundled native artifact has no supported target contract");
const rootStat = lstatSync(directory), manifestStat = lstatSync(resolve(directory, "package.json"));
if (!rootStat.isDirectory() || rootStat.isSymbolicLink() || !manifestStat.isFile() || manifestStat.nlink !== 1 || manifestStat.size > 256 * 1024) {
throw new Error("Bundled native artifact manifest is not a bounded regular file");
}
const [platform, architecture] = target.split("-");
const metadata = JSON.parse(readFileSync(resolve(directory, "package.json"), "utf8"));
const name = `${profile.agentRuntimePackage}-${target}`;
const version = `${profile.agentRuntimeVersion}-${target}`;
if (![name, profile.agentRuntimePackage].includes(metadata.name) || metadata.version !== version
|| !Array.isArray(metadata.os) || !metadata.os.includes(platform) || !Array.isArray(metadata.cpu) || !metadata.cpu.includes(architecture)
|| Object.keys(metadata.dependencies ?? {}).length || Object.keys(metadata.optionalDependencies ?? {}).length || Object.keys(metadata.scripts ?? {}).length) {
throw new Error(`Bundled ${profile.agent} native artifact identity mismatch: ${target}`);
}
const qualification = runtimeData?.[profile.agent]?.platforms?.[target];
if (!qualification && !(profile.agent === "codex" && platform === "darwin")) throw new Error("Bundled native artifact omitted its existing executable qualification");
const relativeExecutable = qualification?.relativeExecutable ?? (profile.agent === "codex" && platform === "darwin"
? `vendor/${architecture === "arm64" ? "aarch64" : "x86_64"}-apple-darwin/bin/codex` : undefined);
if (!relativeExecutable || relativeExecutable.split("/").some(value => !value || value === "..") || isAbsolute(relativeExecutable)) {
throw new Error("Bundled native artifact has no recognized executable");
}
const entries = [];
let bytes = 0;
const walk = (root, prefix = "") => {
if (!lstatSync(root).isDirectory() || lstatSync(root).isSymbolicLink()) throw new Error("Bundled native artifact contains a linked directory");
for (const entry of readdirSync(root).sort()) {
const path = prefix ? `${prefix}/${entry}` : entry;
if (/[\u0000-\u001f\u007f\\]/.test(path)) throw new Error("Bundled native artifact contains an unsafe path");
const file = resolve(root, entry), stat = lstatSync(file);
if (stat.isDirectory()) walk(file, path);
else {
if (!stat.isFile() || stat.nlink !== 1 || stat.mode & 0o7000) throw new Error("Bundled native artifact contains links or special files");
bytes += stat.size;
if (bytes > 1024 * 1024 * 1024 || entries.length >= 4096) throw new Error("Bundled native artifact exceeds size limits");
entries.push({ path, size: stat.size, sha256: digest(readFileSync(file)), executable: Boolean(stat.mode & 0o111) });
}
}
};
walk(directory);
const executable = entries.find(entry => entry.path === relativeExecutable);
if (!executable?.executable) throw new Error("Bundled native artifact has no executable payload");
const header = readFileSync(resolve(directory, relativeExecutable));
const correctArchitecture = platform === "linux"
? header.length >= 20 && header.subarray(0, 4).equals(Buffer.from([0x7f, 0x45, 0x4c, 0x46])) && header[4] === 2 && header[5] === 1 && header.readUInt16LE(18) === 62
: header.length >= 8 && header.readUInt32LE(0) === 0xfeedfacf && header.readUInt32LE(4) === (architecture === "arm64" ? 0x0100000c : 0x01000007);
if (!correctArchitecture) throw new Error(`Bundled native artifact architecture mismatch: ${target}`);
if (qualification && (qualification.packageName !== name || qualification.packageVersion !== version
|| qualification.runtimePackageName !== profile.agentRuntimePackage || qualification.runtimePackageVersion !== profile.agentRuntimeVersion
|| qualification.executableDigest !== executable.sha256)) throw new Error(`Bundled native artifact qualified digest mismatch: ${name}`);
return { agent: profile.agent, target, packageName: name, packageVersion: version, size: bytes,
executable: relativeExecutable, executableDigest: executable.sha256, qualification: qualification ? "existing executable hash" : "native Codex package/version; ACPX qualification unchanged",
closureDigest: digest(JSON.stringify(entries)), entries };
}
export function stageBundledProviderOptionalDependencies(destinationDir, publishManifest, profiles,
runtimeData = JSON.parse(readFileSync(resolve(repoRoot, runtimeDataPath), "utf8")), { nativeArtifactsDirectory, targets = nativeTargets } = {}) {
if (!Array.isArray(targets) || targets.length === 0 || new Set(targets).size !== targets.length
|| targets.some(target => !nativeTargets.includes(target))) throw new Error("Invalid bundled native target selection");
const result = structuredClone(publishManifest);
const graphRoot = realpathSync(resolve(destinationDir, "node_modules"));
const artifacts = [], nativeDependencies = {};
for (const profile of profiles) {
const bridgeManifestPath = resolve(graphRoot, profile.agentServerPackage, "package.json");
const bridge = JSON.parse(readFileSync(bridgeManifestPath, "utf8"));
if (bridge.name !== profile.agentServerPackage || bridge.version !== profile.agentServerVersion) {
throw new Error(`Bundled ${profile.agent} bridge version mismatch`);
}
const bridgeRequire = createRequire(bridgeManifestPath);
const runtimeManifestPath = realpathSync(bridgeRequire.resolve(`${profile.agentRuntimePackage}/package.json`));
if (!inside(graphRoot, runtimeManifestPath)) throw new Error("Bundled Codex runtime escapes its producer graph");
const runtime = JSON.parse(readFileSync(runtimeManifestPath, "utf8"));
if (runtime.name !== profile.agentRuntimePackage || runtime.version !== profile.agentRuntimeVersion) {
throw new Error(`Bundled ${profile.agent} runtime version mismatch`);
}
const optional = Object.entries(runtime.optionalDependencies ?? {});
if (optional.length === 0) throw new Error(`Bundled ${profile.agent} runtime omitted platform artifacts`);
const runtimeRequire = createRequire(runtimeManifestPath);
for (const [name, specifier] of optional) {
const suffix = name.slice(profile.agentRuntimePackage.length + 1);
const recognized = profile.agent === "codex" && name.startsWith(`${profile.agentRuntimePackage}-`) && /^(linux|darwin|win32)-(x64|arm64)$/.test(suffix);
const version = `${runtime.version}-${suffix}`;
const expected = `npm:${runtime.name}@${version}`;
if (!recognized || specifier !== expected) throw new Error(`Bundled ${profile.agent} platform artifact is not qualified: ${name}`);
if (result.optionalDependencies?.[name] !== undefined && result.optionalDependencies[name] !== expected) {
throw new Error(`Bundled ${profile.agent} platform artifact conflicts with published dependency: ${name}`);
}
if (targets.includes(suffix)) {
result.optionalDependencies = { ...result.optionalDependencies, [name]: expected };
nativeDependencies[name] = expected;
}
// Remove only recognized producer-platform payloads before staging the
// bounded supported target set. Preserve the original runtime manifest.
for (const lookup of runtimeRequire.resolve.paths(name) ?? []) {
const candidate = resolve(lookup, name);
if (!inside(graphRoot, candidate) || !existsSync(candidate)) continue;
if (lstatSync(candidate).isSymbolicLink() || !inside(graphRoot, realpathSync(candidate))) {
throw new Error(`Bundled ${profile.agent} platform artifact escapes its producer graph`);
}
const artifact = JSON.parse(readFileSync(resolve(candidate, "package.json"), "utf8"));
if (![name, runtime.name].includes(artifact.name) || artifact.version !== version) {
throw new Error(`Bundled ${profile.agent} installed platform artifact version mismatch: ${name}`);
}
rmSync(candidate, { recursive: true, force: true });
}
}
}
if (Object.keys(nativeDependencies).length !== profiles.length * targets.length) throw new Error("Bundled provider omitted a supported native target");
const temporary = mkdtempSync(resolve(destinationDir, ".paperclip-native-artifacts-"));
try {
writeFileSync(resolve(temporary, "package.json"), JSON.stringify({ private: true, name: "paperclip-native-artifact-staging", version: "1.0.0", dependencies: nativeDependencies }));
if (!nativeArtifactsDirectory) execFileSync("npm", ["install", "--force", "--omit=dev", "--ignore-scripts", "--no-audit", "--no-fund"],
{ cwd: temporary, stdio: "inherit", timeout: 180_000 });
for (const profile of profiles) for (const target of targets) {
const name = `${profile.agentRuntimePackage}-${target}`;
const input = resolve(nativeArtifactsDirectory ?? resolve(temporary, "node_modules"), name);
const artifact = inspectBundledNativeArtifact(input, profile, target, runtimeData);
const output = resolve(graphRoot, name);
if (existsSync(output)) throw new Error("Bundled native artifact destination already exists");
cpSync(input, output, { recursive: true, dereference: false });
for (const entry of artifact.entries) chmodSync(resolve(output, entry.path), entry.executable ? 0o755 : 0o644);
const copied = inspectBundledNativeArtifact(output, profile, target, runtimeData);
if (copied.closureDigest !== artifact.closureDigest) throw new Error("Bundled native artifact changed during staging");
artifacts.push(copied);
result.bundleDependencies = [...new Set([...(result.bundleDependencies ?? result.bundledDependencies ?? []), name])];
}
const total = artifacts.reduce((size, artifact) => size + artifact.size, 0);
if (total > 2 * 1024 * 1024 * 1024) throw new Error("Bundled native provider payload exceeds release size limit");
result.paperclipProviderArtifacts = { schema: "paperclip.bundled-provider-artifacts.v1", providerCalls: 0, lifecycleScriptsRun: false,
totalBytes: total, artifacts: artifacts.map(({ entries, ...artifact }) => artifact) };
} finally { rmSync(temporary, { recursive: true, force: true }); }
return result;
}
export function dockerBundledProviderTarget(architecture,
runtimeData = JSON.parse(readFileSync(resolve(repoRoot, runtimeDataPath), "utf8"))) {
const architectures = { amd64: "x64", arm64: "arm64" };
if (!Object.hasOwn(architectures, architecture) || runtimeData?.schema !== "paperclip.acpx-runtime-artifacts.v1") {
throw new Error("Docker provider materialization requires a declared supported target architecture and qualification contract");
}
const target = `linux-${architectures[architecture]}`;
// A Docker target selects an already-qualified artifact; it cannot qualify
// a new platform. Unqualified native targets retain their normal legacy CLI.
return runtimeData.codex?.platforms?.[target] ? target : null;
}
export function mergeBundledProviderGraph(stagedDirectory, serverDirectory) {
for (const directory of [stagedDirectory, serverDirectory]) {
if (!isAbsolute(directory) || resolve(directory) !== directory || directory === "/" || realpathSync(directory) !== directory) {
throw new Error("Docker provider graph requires canonical owned package directories");
}
}
const input = resolve(stagedDirectory, "node_modules"), output = resolve(serverDirectory, "node_modules");
if (!existsSync(output)) mkdirSync(output);
if (realpathSync(input) !== input || realpathSync(output) !== output) throw new Error("Docker provider node_modules cannot escape its package");
const names = ["@agentclientprotocol/codex-acp"];
const serverMetadata = JSON.parse(readFileSync(resolve(serverDirectory, "package.json"), "utf8"));
for (const name of names) {
const source = resolve(input, name), stat = lstatSync(source);
const metadata = JSON.parse(readFileSync(resolve(source, "package.json"), "utf8"));
if (!stat.isDirectory() || stat.isSymbolicLink() || realpathSync(source) !== source
|| metadata.name !== name || metadata.version !== serverMetadata.dependencies?.[name]) {
throw new Error("Docker provider bridge identity mismatch");
}
}
// Keep the provider closure nested and physically inside the server's own
// authority. Changing unrelated direct server bindings (for example zod)
// would change application behavior even when no native agent is running.
const owned = resolve(output, ".paperclip-native-providers");
if (lstatExists(owned)) throw new Error("Docker provider graph destination already exists");
cpSync(stagedDirectory, owned, { recursive: true, dereference: false, verbatimSymlinks: true });
chmodSync(owned, 0o755);
for (const name of names) {
const destination = resolve(output, name), namespace = dirname(destination);
if (!existsSync(namespace)) mkdirSync(namespace);
if (realpathSync(namespace) !== namespace) throw new Error("Docker provider namespace cannot resolve outside the server");
if (lstatExists(destination)) {
const stat = lstatSync(destination);
// pnpm's store is untouched; remove only its server-local package link.
if (!stat.isSymbolicLink()) throw new Error("Docker provider entry binding must be a workspace package link");
rmSync(destination);
}
symlinkSync(relative(namespace, resolve(owned, "node_modules", name)), destination, "dir");
if (!inside(output, realpathSync(destination))) throw new Error("Docker provider bridge escaped its server authority");
}
}
function lstatExists(path) {
try { lstatSync(path); return true; }
catch (error) { if (error.code === "ENOENT") return false; throw error; }
}
// A Linux-produced bundle contains esbuild's JavaScript but only the producer's
// optional executable. Expose its exact platform declarations to the consumer,
// just as embedded-postgres does below, before retaining the original hooks.
export function stageBundledEsbuildOptionalDependencies(destinationDir, publishManifest) {
const graphRoot = resolve(destinationDir, "node_modules");
if (realpathSync(graphRoot) !== graphRoot) throw new Error("Bundled esbuild graph must be a canonical owned directory");
const result = structuredClone(publishManifest), pending = [{ directory: graphRoot, depth: 0 }];
const optional = {}, remove = new Set();
let packageCount = 0;
const ownedDirectory = directory => {
const stat = lstatSync(directory);
if (!stat.isDirectory() || stat.isSymbolicLink() || !inside(graphRoot, realpathSync(directory))) {
throw new Error("Bundled esbuild dependency escapes its producer graph");
}
};
const manifestAt = directory => {
const file = resolve(directory, "package.json"), stat = lstatSync(file);
if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1 || stat.size > 256 * 1024) {
throw new Error("Bundled esbuild dependency manifest must be a bounded regular file");
}
return JSON.parse(readFileSync(file, "utf8"));
};
while (pending.length) {
const { directory, depth } = pending.pop();
if (depth > 64) throw new Error("Bundled esbuild dependency 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 esbuild dependency graph exceeds its package limit");
const metadata = manifestAt(packageDirectory);
const nested = resolve(packageDirectory, "node_modules");
if (lstatExists(nested)) { ownedDirectory(nested); pending.push({ directory: nested, depth: depth + 1 }); }
if (basename(packageDirectory) !== "esbuild") continue;
if (metadata.name !== "esbuild" || !/^\d+\.\d+\.\d+$/.test(metadata.version ?? "")) {
throw new Error("Bundled esbuild package identity mismatch");
}
const declarations = metadata.optionalDependencies ?? {};
if (nativeTargets.some(target => declarations[`@esbuild/${target}`] !== metadata.version)) {
throw new Error("Bundled esbuild omitted a supported platform declaration");
}
const issuer = createRequire(resolve(packageDirectory, "package.json"));
for (const [name, version] of Object.entries(declarations)) {
if (!/^@esbuild\/[a-z0-9]+-[a-z0-9]+$/.test(name) || version !== metadata.version) {
throw new Error("Bundled esbuild platform declaration must match its exact package version");
}
if ((optional[name] !== undefined && optional[name] !== version)
|| (result.optionalDependencies?.[name] !== undefined && result.optionalDependencies[name] !== version)) {
throw new Error(`Bundled esbuild platform version conflicts with published dependency: ${name}`);
}
optional[name] = version;
for (const lookup of issuer.resolve.paths(name) ?? []) {
const candidate = resolve(lookup, name);
if (!inside(graphRoot, candidate) || !lstatExists(candidate)) continue;
ownedDirectory(candidate);
const installed = manifestAt(candidate);
if (installed.name !== name || installed.version !== version) {
throw new Error(`Bundled esbuild installed platform identity mismatch: ${name}`);
}
remove.add(candidate);
}
}
}
}
for (const directory of remove) rmSync(directory, { recursive: true, force: true });
if (Object.keys(optional).length) result.optionalDependencies = { ...result.optionalDependencies, ...optional };
return result;
}
export function materializeDockerProviderGraph(serverDirectory, architecture, { sourceRoot = repoRoot } = {}) {
const runtimeData = JSON.parse(readFileSync(resolve(sourceRoot, runtimeDataPath), "utf8"));
const target = dockerBundledProviderTarget(architecture, runtimeData);
if (!target) return { target: `linux-${architecture === "arm64" ? "arm64" : "x64"}`, materialized: false, reason: "Native providers are not qualified for this target; legacy installation preserved" };
const metadata = JSON.parse(readFileSync(resolve(serverDirectory, "package.json"), "utf8"));
const names = ["@agentclientprotocol/codex-acp"];
const temporary = realpathSync(mkdtempSync(resolve(tmpdir(), "paperclip-docker-provider-graph-")));
try {
const source = resolve(temporary, "source"), staged = resolve(temporary, "staged"); mkdirSync(source);
writeFileSync(resolve(source, "package.json"), JSON.stringify({ name: metadata.name, version: metadata.version, type: "module", files: [],
dependencies: Object.fromEntries(names.map(name => [name, metadata.dependencies?.[name]])), bundleDependencies: names }));
prepareBundledPackage(source, staged, { sourceRoot, targets: [target] });
mergeBundledProviderGraph(staged, serverDirectory);
const stagedMetadata = JSON.parse(readFileSync(resolve(staged, "package.json"), "utf8"));
return { target, materialized: true, artifacts: stagedMetadata.paperclipProviderArtifacts, providerCalls: 0, lifecycleScriptsRun: false };
} finally { rmSync(temporary, { recursive: true, force: true }); }
}
function patchedDependencyPackageName(specifier) {
const versionSeparator = specifier.lastIndexOf("@");
const packageNameEnd = specifier.startsWith("@") ? specifier.indexOf("/") : 0;
@@ -136,7 +468,7 @@ export function applyBundledDependencyPatches(destinationDir, bundledDependencie
}
}
export function prepareBundledPackage(sourceDir, destinationDir, { sourceRoot = repoRoot } = {}) {
export function prepareBundledPackage(sourceDir, destinationDir, { sourceRoot = repoRoot, targets = nativeTargets } = {}) {
const sourcePackagePath = resolve(sourceDir, "package.json");
const sourcePackage = JSON.parse(readFileSync(sourcePackagePath, "utf8"));
const bundledDependencies = sourcePackage.bundleDependencies ?? sourcePackage.bundledDependencies ?? [];
@@ -157,7 +489,13 @@ export function prepareBundledPackage(sourceDir, destinationDir, { sourceRoot =
const deployedPackagePath = resolve(destinationDir, "package.json");
const publishManifest = materializePublishManifest(sourcePackage);
const installManifest = createBundledInstallManifest(publishManifest, bundledDependencies);
const rootPackage = JSON.parse(readFileSync(resolve(sourceRoot, "package.json"), "utf8"));
const profileData = bundledDependencies.includes("@agentclientprotocol/codex-acp")
? JSON.parse(readFileSync(resolve(sourceRoot, "packages/paperclip-runner/acpx-profiles.json"), "utf8")) : undefined;
const runtimeData = profileData ? JSON.parse(readFileSync(resolve(sourceRoot, runtimeDataPath), "utf8")) : undefined;
const { installManifest, profiles } = configureBundledProviderOverrides(
createBundledInstallManifest(publishManifest, bundledDependencies), bundledDependencies, rootPackage, profileData, runtimeData,
);
writeFileSync(deployedPackagePath, `${JSON.stringify(installManifest, null, 2)}\n`);
execFileSync(
@@ -167,6 +505,7 @@ export function prepareBundledPackage(sourceDir, destinationDir, { sourceRoot =
);
writeFileSync(deployedPackagePath, `${JSON.stringify(publishManifest, null, 2)}\n`);
applyBundledDependencyPatches(destinationDir, bundledDependencies, sourceRoot);
if (profiles.length) writeFileSync(deployedPackagePath, `${JSON.stringify(stageBundledProviderOptionalDependencies(destinationDir, publishManifest, profiles, runtimeData, { targets }), null, 2)}\n`);
if (bundledDependencies.includes("acpx")) {
const acpxPackage = JSON.parse(
@@ -210,9 +549,17 @@ export function prepareBundledPackage(sourceDir, destinationDir, { sourceRoot =
writeFileSync(deployedPackagePath, `${JSON.stringify(stagedPackage, null, 2)}\n`);
rmSync(resolve(destinationDir, "node_modules/@embedded-postgres"), { recursive: true, force: true });
}
writeFileSync(deployedPackagePath, `${JSON.stringify(stageBundledEsbuildOptionalDependencies(destinationDir,
JSON.parse(readFileSync(deployedPackagePath, "utf8"))), null, 2)}\n`);
}
if (process.argv[1] === fileURLToPath(import.meta.url)) {
if (process.argv[2] === "--docker-provider-graph") {
const [, serverDirectory, architecture, ...extra] = process.argv.slice(2);
if (!serverDirectory || !architecture || extra.length) throw new Error("Usage: prepare-bundled-package.mjs --docker-provider-graph <server-directory> <TARGETARCH>");
console.log(JSON.stringify(materializeDockerProviderGraph(resolve(serverDirectory), architecture)));
process.exit(0);
}
const [sourceDir, destinationDir] = process.argv.slice(2);
if (!sourceDir || !destinationDir) {
console.error("Usage: prepare-bundled-package.mjs <source-dir> <destination-dir>");
+2
View File
@@ -44,6 +44,7 @@
"typecheck": "pnpm run prepare:runner-vendor && pnpm --filter @paperclipai/plugin-sdk ensure-build-deps && tsc --noEmit"
},
"dependencies": {
"@agentclientprotocol/codex-acp": "1.6.2",
"ai": "7.0.130",
"@ai-sdk/openai": "4.0.86",
"@openrouter/ai-sdk-provider": "3.1.0",
@@ -107,6 +108,7 @@
"zod": "^4.4.3"
},
"bundleDependencies": [
"@agentclientprotocol/codex-acp",
"@chat-adapter/discord",
"@chat-adapter/github",
"@chat-adapter/slack",