mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-07 16:35:48 +01:00
Reviewed partition of the current-master Copilot integration. Complete v30 live qualification is retained; current v31 recovery compatibility, CI and review are tracked explicitly. Co-Authored-By: Paperclip <noreply@paperclip.ing>
95 lines
5.5 KiB
JavaScript
95 lines
5.5 KiB
JavaScript
import { access, lstat, mkdir, readFile, realpath, rmdir } from "node:fs/promises";
|
|
import { dirname, join, resolve } from "node:path";
|
|
import { createRequire } from "node:module";
|
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
import { COPILOT_DISTRIBUTIONS, materializePinnedCopilotBinary } from "./materialize-copilot-binary.mjs";
|
|
|
|
const packageRoot = await realpath(fileURLToPath(new URL("../", import.meta.url)));
|
|
const owner = JSON.parse(await readFile(join(packageRoot, "package.json"), "utf8"));
|
|
if (!["@paperclipai/paperclip-runner", "@paperclipai/server"].includes(owner.name)) {
|
|
throw new Error("Copilot installer must belong to a runner package owner");
|
|
}
|
|
const vendored = owner.name === "@paperclipai/server";
|
|
const runtimeRoot = vendored ? join(packageRoot, "dist/vendor/paperclip-runner") : join(packageRoot, "dist");
|
|
|
|
/** Install only the native npm package for this platform; never execute it. */
|
|
export async function installCopilotAssets() {
|
|
const { platform, arch: architecture } = process;
|
|
const target = `${platform}-${architecture}`;
|
|
if (!Object.prototype.hasOwnProperty.call(COPILOT_DISTRIBUTIONS, target)) {
|
|
return { status: "unsupported-platform", target };
|
|
}
|
|
const verifierPath = join(runtimeRoot, "drivers/acpx/copilot-installation.js");
|
|
try { await access(verifierPath); }
|
|
catch (error) {
|
|
if (error.code !== "ENOENT") throw error;
|
|
// A source workspace installs dependencies before it compiles the verifier.
|
|
// Published packages exclude src, so a malformed npm artifact cannot use
|
|
// this path. build:typescript performs installation after compilation.
|
|
await access(join(packageRoot, vendored ? "src/services/copilot-connection-probe.ts" : "src/drivers/acpx/copilot-installation.ts"));
|
|
await access(resolve(packageRoot, vendored ? "../pnpm-workspace.yaml" : "../../pnpm-workspace.yaml"));
|
|
return { status: "source-workspace-pending-build", target };
|
|
}
|
|
const assets = join(packageRoot, "provider-assets", "copilot");
|
|
for (const directory of [dirname(assets), assets]) {
|
|
await mkdir(directory, { recursive: true, mode: 0o755 });
|
|
const stat = await lstat(directory);
|
|
if (!stat.isDirectory() || stat.isSymbolicLink() || await realpath(directory) !== directory) {
|
|
throw new Error("Copilot install directory must not redirect through links");
|
|
}
|
|
}
|
|
const destination = join(assets, target);
|
|
const originalRoot = process.env.PAPERCLIP_ACPX_PROVIDER_PACKAGE_ROOT;
|
|
const originalManifest = process.env.PAPERCLIP_ACPX_PROVIDER_PACKAGE_MANIFEST;
|
|
process.env.PAPERCLIP_ACPX_PROVIDER_PACKAGE_ROOT = packageRoot;
|
|
process.env.PAPERCLIP_ACPX_PROVIDER_PACKAGE_MANIFEST = join(packageRoot, "package.json");
|
|
try {
|
|
const { COPILOT_CLOSURE_SHA256, verifyCopilotInstallation } = await import(pathToFileURL(join(runtimeRoot, "drivers/acpx/copilot-installation.js")));
|
|
const { QUALIFIED_ACPX_PROFILES } = await import(pathToFileURL(join(runtimeRoot, "drivers/acpx/qualified-profiles.js")));
|
|
const verify = () => verifyCopilotInstallation(QUALIFIED_ACPX_PROFILES.copilot);
|
|
try { await mkdir(destination, { mode: 0o755 }); }
|
|
catch (error) {
|
|
if (error.code !== "EEXIST") throw error;
|
|
const stat = await lstat(destination);
|
|
if (!stat.isDirectory() || stat.isSymbolicLink() || await realpath(destination) !== destination) {
|
|
throw new Error("Copilot installed assets must not redirect through links");
|
|
}
|
|
await verify();
|
|
return { status: "verified-existing", target };
|
|
}
|
|
const ownedDirectory = await lstat(destination, { bigint: true });
|
|
try {
|
|
// Official native packages export their executable, not package.json.
|
|
// Resolve that path without loading or executing the native module.
|
|
const nativePackageRoot = dirname(createRequire(import.meta.url).resolve(COPILOT_DISTRIBUTIONS[target].packageName));
|
|
const materialized = materializePinnedCopilotBinary({ packageRoot: nativePackageRoot, platform, architecture, targetDirectory: destination });
|
|
if (materialized.closureSha256 !== COPILOT_CLOSURE_SHA256[target]) {
|
|
throw new Error("Copilot installation differs from the runtime closure pin");
|
|
}
|
|
await verify();
|
|
return { status: "installed", target };
|
|
} catch (error) {
|
|
// The materializer rolls back only files it created. Remove this newly
|
|
// created directory only if it is now empty; retain foreign files or
|
|
// failed verification evidence rather than deleting a directory tree.
|
|
try {
|
|
const current = await lstat(destination, { bigint: true });
|
|
if (current.isDirectory() && !current.isSymbolicLink() && current.dev === ownedDirectory.dev && current.ino === ownedDirectory.ino) await rmdir(destination);
|
|
}
|
|
catch (cleanupError) { if (!["ENOENT", "ENOTEMPTY", "EEXIST"].includes(cleanupError.code)) throw new AggregateError([error, cleanupError], "Copilot install and empty-directory cleanup failed"); }
|
|
throw error;
|
|
}
|
|
} finally {
|
|
for (const [key, value] of [["PAPERCLIP_ACPX_PROVIDER_PACKAGE_ROOT", originalRoot], ["PAPERCLIP_ACPX_PROVIDER_PACKAGE_MANIFEST", originalManifest]]) {
|
|
if (value === undefined) delete process.env[key];
|
|
else process.env[key] = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
const invokedPath = process.argv[1] ? await realpath(resolve(process.argv[1])) : null;
|
|
if (invokedPath && import.meta.url === pathToFileURL(invokedPath).href) {
|
|
const result = await installCopilotAssets();
|
|
process.stdout.write(`Copilot runtime assets: ${result.status} (${result.target})\n`);
|
|
}
|