feat(web): add source-first Python autopilot and optimize provider waits
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled

This commit is contained in:
mes123456
2026-09-08 10:59:49 +08:00
parent a69e41457a
commit edcf0fef9a
29 changed files with 10149 additions and 412 deletions

View File

@@ -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);