Files
PaperClipAI/scripts/dev-runner-native-binary.mjs
T
DottaandPaperclip 058af8f1b6 fix: preserve release artifacts during dev builds and display imported policy
Keep source-built daemons separate from source-bound release targets and select
fresh compatible source output only inside the development supervisor. Preserve
release manifests, running executable inodes, and explicit operator overrides.

Show the existing translated Codex permission policy in import controls while
preserving runner-only override payloads. Record real Apple Silicon task and
paused import proof alongside exact qualification sources.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-12 09:32:28 -05:00

90 lines
3.3 KiB
JavaScript

import { readFileSync, readdirSync, statSync } from "node:fs";
import path from "node:path";
import { runnerBinaryTarget } from "../packages/paperclip-runner/src/live/runner-binary.ts";
export function resolveNativeRunnerRequirement({ exitCode, stdout }) {
if (exitCode !== 0) {
return { nativeRunnerRequired: true, valid: false };
}
const jsonLines = stdout
.split(/\r?\n/)
.map((line) => line.trim())
.filter((line) => line.startsWith("{"));
for (let index = jsonLines.length - 1; index >= 0; index -= 1) {
try {
const payload = JSON.parse(jsonLines[index]);
if (typeof payload?.nativeRunnerRequired === "boolean") {
return { nativeRunnerRequired: payload.nativeRunnerRequired, valid: true };
}
} catch {
// Keep scanning earlier JSON-looking output from pnpm and runtime logging.
}
}
// An unknown state must not strand a persisted native run without runnerd.
return { nativeRunnerRequired: true, valid: false };
}
function newestMtimeMs(target) {
const stat = statSync(target, { throwIfNoEntry: false });
if (!stat) return 0;
if (!stat.isDirectory()) return stat.mtimeMs;
let newest = stat.mtimeMs;
for (const entry of readdirSync(target)) {
const childNewest = newestMtimeMs(path.join(target, entry));
if (childNewest > newest) newest = childNewest;
}
return newest;
}
export function resolveNativeRunnerDevBinary({
repoRoot,
platform = process.platform,
architecture = process.arch,
}) {
const executable = platform === "win32" ? "paperclip-runnerd.exe" : "paperclip-runnerd";
const packageRoot = path.join(repoRoot, "packages", "paperclip-runner");
const target = path.join(packageRoot, "dist", "bin", `${platform}-${architecture}`, executable);
const flat = path.join(packageRoot, "dist", "bin", executable);
const targetStat = statSync(target, { throwIfNoEntry: false });
const flatStat = statSync(flat, { throwIfNoEntry: false });
// Release targets remain authoritative unless a newer source build matches
// this host. In particular, a build-host flat artifact cannot mask a remotely
// staged Mac target or force a local Cargo build.
if (flatStat?.isFile() && (!targetStat?.isFile() || flatStat.mtimeMs > targetStat.mtimeMs)
&& runnerBinaryTarget(readFileSync(flat)) === `${platform}-${architecture}`) {
return { binary: flat, sourceBuild: true };
}
return targetStat?.isFile() ? { binary: target, sourceBuild: false } : null;
}
export function paperclipRunnerBinaryNeedsBuild({
repoRoot,
nativeRunnerRequired,
configuredBinary,
platform = process.platform,
architecture = process.arch,
}) {
if (!nativeRunnerRequired) return false;
if (configuredBinary?.trim()) return false;
const selected = resolveNativeRunnerDevBinary({ repoRoot, platform, architecture });
if (!selected) return true;
const binaryStat = statSync(selected.binary);
const packageRoot = path.join(repoRoot, "packages", "paperclip-runner");
const runnerRoot = path.join(packageRoot, "runner");
const buildInputs = [
path.join(runnerRoot, "Cargo.toml"),
path.join(runnerRoot, "Cargo.lock"),
path.join(runnerRoot, ".cargo"),
path.join(runnerRoot, "rust-toolchain"),
path.join(runnerRoot, "rust-toolchain.toml"),
path.join(runnerRoot, "crates"),
];
return buildInputs.some((input) => newestMtimeMs(input) > binaryStat.mtimeMs);
}