VoidBot/scripts/run-repo-face-birth.mjs
2026-05-18 11:13:01 +01:00

178 lines
4.7 KiB
JavaScript

import { createWriteStream } from "node:fs";
import { mkdir, writeFile } from "node:fs/promises";
import { dirname, resolve } from "node:path";
import { spawn } from "node:child_process";
const args = parseArgs(process.argv.slice(2));
const identityId = requireArg("identity-id");
const repoPath = resolve(requireArg("repo"));
const epiphanyAgentRoot = resolve(requireArg("epiphany-agent-root"));
const artifactDir = resolve(requireArg("artifact-dir"));
const stateDir = resolve(requireArg("state-dir"));
const statusPath = resolve(requireArg("status-path"));
const logPath = resolve(requireArg("log-path"));
const mode = args.mode ?? "plan";
const executor = args.executor ?? "codex-exec";
await main();
async function main() {
await mkdir(artifactDir, { recursive: true });
await mkdir(stateDir, { recursive: true });
await mkdir(dirname(statusPath), { recursive: true });
await mkdir(dirname(logPath), { recursive: true });
const startedAt = new Date().toISOString();
await writeStatus({
status: "running",
identityId,
repoPath,
artifactDir,
logPath,
startedAt,
updatedAt: startedAt,
});
const log = createWriteStream(logPath, { flags: "a", encoding: "utf8" });
try {
log.write(`[${startedAt}] starting repo Face birth for ${identityId}\n`);
const scoutDir = resolve(artifactDir, "terrain");
const runnerDir = resolve(artifactDir, "runner");
await mkdir(scoutDir, { recursive: true });
await mkdir(runnerDir, { recursive: true });
await runCommand(
"cargo",
[
"run",
"--quiet",
"--bin",
"epiphany-repo-personality",
"--",
"scout",
"--root",
repoPath,
"--artifact-dir",
scoutDir,
],
resolve(epiphanyAgentRoot, "epiphany-core"),
log,
);
await runCommand(
"cargo",
[
"run",
"--quiet",
"--bin",
"epiphany-repo-birth-runner",
"--",
"--repo",
repoPath,
"--baseline",
resolve(scoutDir, "baseline.msgpack"),
"--artifact-dir",
runnerDir,
"--init-store",
resolve(stateDir, "repo-initialization.msgpack"),
"--agent-store",
resolve(stateDir, "agents.msgpack"),
"--heartbeat-store",
resolve(stateDir, "agent-heartbeats.msgpack"),
"--runtime-store",
resolve(stateDir, "runtime-spine.msgpack"),
"--mode",
mode,
"--executor",
executor,
],
resolve(epiphanyAgentRoot, "epiphany-core"),
log,
);
const completedAt = new Date().toISOString();
await writeStatus({
status: "completed",
identityId,
repoPath,
artifactDir,
scoutDir,
runnerDir,
summaryPath: resolve(runnerDir, "birth-runner-summary.json"),
logPath,
startedAt,
completedAt,
updatedAt: completedAt,
});
log.write(`[${completedAt}] completed repo Face birth for ${identityId}\n`);
} catch (error) {
const failedAt = new Date().toISOString();
const message = error instanceof Error ? error.message : String(error);
await writeStatus({
status: "failed",
identityId,
repoPath,
artifactDir,
logPath,
error: message,
startedAt,
failedAt,
updatedAt: failedAt,
});
log.write(`[${failedAt}] failed repo Face birth for ${identityId}: ${message}\n`);
process.exitCode = 1;
} finally {
log.end();
}
}
async function runCommand(command, commandArgs, cwd, log) {
log.write(`\n> ${command} ${commandArgs.join(" ")}\n`);
await new Promise((resolvePromise, reject) => {
const child = spawn(command, commandArgs, {
cwd,
shell: false,
windowsHide: true,
});
child.stdout.on("data", (chunk) => log.write(chunk));
child.stderr.on("data", (chunk) => log.write(chunk));
child.on("error", reject);
child.on("close", (code) => {
if (code === 0) {
resolvePromise();
} else {
reject(new Error(`${command} exited with ${code}`));
}
});
});
}
async function writeStatus(payload) {
await writeFile(statusPath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
}
function parseArgs(argv) {
const parsed = {};
for (let index = 0; index < argv.length; index += 1) {
const token = argv[index];
if (!token.startsWith("--")) {
continue;
}
const key = token.slice(2);
const next = argv[index + 1];
if (!next || next.startsWith("--")) {
parsed[key] = "true";
continue;
}
parsed[key] = next;
index += 1;
}
return parsed;
}
function requireArg(name) {
const value = args[name];
if (typeof value !== "string" || value.trim().length === 0) {
throw new Error(`Missing --${name}.`);
}
return value;
}