feat(web): add source-first Python autopilot and optimize provider waits
This commit is contained in:
@@ -31,9 +31,39 @@ if (!Number.isSafeInteger(pollMs) || pollMs < 0) throw new Error("--poll-ms must
|
||||
if (!Number.isSafeInteger(pushRetries) || pushRetries < 0) throw new Error("--push-retries must be a non-negative integer");
|
||||
if (pushEnabled && !commitEnabled) throw new Error("--push requires --commit");
|
||||
|
||||
// A preflight must neither claim ownership nor rotate queue/checkpoint files.
|
||||
if (preflightOnly) {
|
||||
try {
|
||||
const pointer = currentTask();
|
||||
if (!pointer) throw new Error("queue pointer is missing; completion is unproven");
|
||||
parentManifest(pointer);
|
||||
const check = spawnSync(process.execPath, [abs("tools/web/check-task-context.mjs")], { cwd: root, encoding: "utf8" });
|
||||
if (check.status !== 0) throw new Error("task context validation failed");
|
||||
const readiness = terminalTask(pointer.task)
|
||||
? { ok: true, terminal: true, reason: "accepted_terminal_manifest" }
|
||||
: sourceMappingReadiness(pointer);
|
||||
process.stdout.write(`${JSON.stringify({ task: pointer.task, ...readiness })}\n`);
|
||||
process.exit(readiness.ok ? 0 : 2);
|
||||
}
|
||||
catch (error) {
|
||||
process.stderr.write(`scheduler-preflight-blocked: ${error.message}\n`);
|
||||
process.exit(2);
|
||||
}
|
||||
}
|
||||
|
||||
fs.mkdirSync(abs(controlRoot), { recursive: true });
|
||||
let lockHandle;
|
||||
const acquireLock = () => {
|
||||
const pythonOwnerPath = abs(`${controlRoot}/autopilot/owner.lock`);
|
||||
if (fs.existsSync(pythonOwnerPath)) {
|
||||
const owner = JSON.parse(fs.readFileSync(pythonOwnerPath, "utf8"));
|
||||
let alive = false;
|
||||
if (Number.isSafeInteger(owner.pid) && owner.pid > 0) {
|
||||
try { process.kill(owner.pid, 0); alive = true; }
|
||||
catch (error) { if (error.code !== "ESRCH") alive = true; }
|
||||
}
|
||||
if (alive) throw new Error("the Python autopilot already owns the project");
|
||||
}
|
||||
try {
|
||||
return fs.openSync(abs(lockPath), "wx");
|
||||
}
|
||||
@@ -186,7 +216,13 @@ function terminalTask(task) {
|
||||
const index = JSON.parse(read("tests/golden/WBV2/task-index.json"));
|
||||
const manifestPath = `tests/golden/${task}/manifest.json`;
|
||||
const manifest = fs.existsSync(abs(manifestPath)) ? JSON.parse(read(manifestPath)) : null;
|
||||
return index.activeTask === null && manifest?.task === task && manifest.status === "done" && manifest.nextTask === null;
|
||||
return index.activeTask === null && manifest?.task === task && manifest.status === "done" && manifest.nextTask === null
|
||||
&& manifest.acceptanceComplete === true && manifest.verificationDeferred !== true
|
||||
&& Object.keys(manifest.artifacts ?? {}).length > 0
|
||||
&& Object.values(manifest.artifacts).every((artifact) => typeof artifact.path === "string"
|
||||
&& typeof artifact.sha256 === "string" && !path.isAbsolute(artifact.path)
|
||||
&& !artifact.path.split("/").includes("..")
|
||||
&& crypto.createHash("sha256").update(fs.readFileSync(abs(artifact.path))).digest("hex") === artifact.sha256);
|
||||
}
|
||||
catch {
|
||||
return false;
|
||||
@@ -319,7 +355,7 @@ try {
|
||||
// guardian/supervisor chain.
|
||||
try {
|
||||
const index = JSON.parse(read("tests/golden/WBV2/task-index.json"));
|
||||
if (index.activeTask === null && index.entries?.[before.task]) {
|
||||
if (index.activeTask === null && index.entries?.[before.task] && terminalTask(before.task)) {
|
||||
const completedBatchInfo = { ...batchInfo(before), completed: true };
|
||||
latestBatch = completedBatchInfo;
|
||||
const planned = preflightOnly ? { terminal: true } : planNextBatch(before);
|
||||
|
||||
Reference in New Issue
Block a user