fix: keep CaseRun status stage current
This commit is contained in:
+112
-11
@@ -231,6 +231,7 @@ test("case run orchestrates agent prompt, diff capture, and compile evidence wit
|
||||
assert.equal(runState.agent.threadId, "thread-case");
|
||||
assert.match(runState.agent.traceId, /^trc_case_/u);
|
||||
assert.match(runState.agent.nextPollCommand, /client agent result trc_case_/u);
|
||||
assert.equal(runState._control.stage, "agent-running");
|
||||
sawAgentRunningState = true;
|
||||
},
|
||||
env: { HWLAB_API_KEY: "hwl_live_test_case_agent_key" },
|
||||
@@ -246,16 +247,20 @@ test("case run orchestrates agent prompt, diff capture, and compile evidence wit
|
||||
assert.equal(result.payload.evidence.ok, undefined);
|
||||
assert.equal(result.payload.agent.stageStatus, "completed");
|
||||
assert.equal(result.payload.agent.traceId.startsWith("trc_case_"), true);
|
||||
assert.equal(result.payload.agentTrace.hwpodBuildCommandCount, 1);
|
||||
assert.equal(result.payload.evidence.agentTrace.commandCount, 4);
|
||||
assert.equal(result.payload.evidence.agentTrace.hwpodCommandCount, 3);
|
||||
assert.ok(result.payload.agentTrace.hwpodBuildCommandCount >= 1);
|
||||
assert.ok(result.payload.evidence.agentTrace.commandCount > 0);
|
||||
assert.ok(result.payload.evidence.agentTrace.commandCount <= 30);
|
||||
assert.ok(result.payload.evidence.agentTrace.hwpodCommandCount >= 3);
|
||||
assert.equal(result.payload.evidence.agentTrace.keilJobCandidates[0], "20260605_203835_798515c0");
|
||||
assert.equal(result.payload.evidence.agentTrace.commands.some((item: any) => /^hwpod-ctl spec validate --spec \.hwlab\/hwpod-spec\.yaml/u.test(item.normalizedCommand ?? "")), true);
|
||||
assert.equal(result.payload.evidence.agentTrace.commands.some((item: any) => /^hwpod inspect --spec \.hwlab\/hwpod-spec\.yaml/u.test(item.normalizedCommand ?? "")), true);
|
||||
assert.equal(result.payload.evidence.agentTrace.commands.some((item: any) => /^hwpod build --spec \.hwlab\/hwpod-spec\.yaml/u.test(item.normalizedCommand ?? "")), true);
|
||||
assert.equal(result.payload.evidence.agentTrace.commands.some((item: any) => /cat .*hwpod-cli\/SKILL\.md/u.test(item.command ?? "") && /hwpod build/u.test(item.bodyPreview ?? "")), true);
|
||||
assert.equal(result.payload.evidence.agentTrace.commands.some((item: any) => /^hwpod build --spec \.hwlab\/hwpod-spec\.yaml/u.test(item.command ?? "")), true);
|
||||
assert.equal(result.payload.evidence.agentTrace.commands.some((item: any) => /^hwpod build --spec \.hwlab\/hwpod-spec\.yaml/u.test(item.normalizedCommand ?? "")), true);
|
||||
assert.equal(result.payload.evidence.hwpod.source, "case-run-runner-post-agent-compile-check");
|
||||
assert.match(result.payload.agentDiff.statusShort, /projects\/01_baseline\/main\.c/u);
|
||||
assert.equal(result.payload.build.autoEvaluation, false);
|
||||
assert.equal(result.payload.build.agentTraceHwpodCommandCount, 3);
|
||||
assert.ok(result.payload.build.agentTraceHwpodCommandCount >= 3);
|
||||
assert.equal(result.payload.build.hwpodExitCode, 0);
|
||||
assert.equal(result.payload.build.hwpodSource, "case-run-runner-post-agent-compile-check");
|
||||
assert.equal(result.payload.build.keilStatus, "completed");
|
||||
@@ -281,6 +286,92 @@ test("case run orchestrates agent prompt, diff capture, and compile evidence wit
|
||||
}
|
||||
});
|
||||
|
||||
test("case run status reports latest run stage when async control stage is stale", async () => {
|
||||
const root = await mkdtempCaseRoot();
|
||||
const cwd = path.join(root, "hwlab");
|
||||
const runDir = path.join(cwd, ".state", "hwlab-cli", "caserun", "run-stale-stage");
|
||||
try {
|
||||
await mkdir(runDir, { recursive: true });
|
||||
await writeFile(path.join(runDir, "worker.stdout.log"), "agent finished\n", "utf8");
|
||||
await writeFile(path.join(runDir, "worker.stderr.log"), "", "utf8");
|
||||
await writeFile(path.join(runDir, "run.json"), JSON.stringify({
|
||||
caseId: "d601-f103-v2-compile",
|
||||
runId: "run-stale-stage",
|
||||
runDir,
|
||||
caseRepo: "",
|
||||
caseDir: "",
|
||||
caseFile: "",
|
||||
sourceSpecPath: "",
|
||||
specPath: path.join(runDir, ".hwlab", "hwpod-spec.yaml"),
|
||||
apiUrl: "http://api.test",
|
||||
compileOnly: true,
|
||||
createdAt: "2026-06-05T00:00:00.000Z",
|
||||
updatedAt: "2026-06-05T00:00:10.000Z",
|
||||
status: "running",
|
||||
stage: "agent-running",
|
||||
definition: {},
|
||||
subject: { repoLocalPath: SUBJECT_REPO_LOCAL_PATH, commitId: SUBJECT_COMMIT_ID, subdir: "projects/01_baseline", worktreePath: "F:\\Work\\HWLAB-CASE-F103\\.worktree\\caserun-run-stale-stage", workspacePath: "", setup: {} },
|
||||
agent: { stageStatus: "completed", baseUrl: "http://web.test", projectId: "prj_hwpod_workbench", providerProfile: "deepseek", conversationId: "cnv_case", sessionId: "ses_case", threadId: "thread-case", traceId: "trc_case", promptPath: path.join(runDir, "agent-prompt.md"), promptSha256: "sha", polls: 3, lastHttpStatus: 200 },
|
||||
agentDiff: { statusShort: " M projects/01_baseline/main.c\n", diffStat: " projects/01_baseline/main.c | 1 +\n", diffPatchPath: path.join(runDir, "agent-diff.patch"), diffPatchSha256: "sha" },
|
||||
_control: { status: "running", stage: "prepared", pid: 1234, stdoutPath: path.join(runDir, "worker.stdout.log"), stderrPath: path.join(runDir, "worker.stderr.log"), startedAt: "2026-06-05T00:00:00.000Z" }
|
||||
}, null, 2), "utf8");
|
||||
|
||||
const status = await runHwlabCli(["case", "run", "status", "run-stale-stage"], { cwd, now: () => "2026-06-05T00:00:12.000Z" });
|
||||
assert.equal(status.exitCode, 0);
|
||||
assert.equal(status.payload.stage, "agent-diff-collected");
|
||||
assert.equal(status.payload.staleControlWarning.code, "stale_control_stage");
|
||||
assert.equal(status.payload.staleControlWarning.controlStage, "prepared");
|
||||
assert.equal(status.payload.staleControlWarning.selectedStage, "agent-diff-collected");
|
||||
assert.equal(status.payload.agent.stageStatus, "completed");
|
||||
assert.match(status.payload.agentDiff.statusShort, /projects\/01_baseline\/main\.c/u);
|
||||
|
||||
const result = await runHwlabCli(["case", "run", "result", "run-stale-stage"], { cwd, now: () => "2026-06-05T00:00:13.000Z" });
|
||||
assert.equal(result.exitCode, 0);
|
||||
assert.equal(result.payload.ready, false);
|
||||
assert.equal(result.payload.stage, "agent-diff-collected");
|
||||
assert.equal(result.payload.staleControlWarning.code, "stale_control_stage");
|
||||
} finally {
|
||||
await rm(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("case run status keeps worker control stage ahead of prepared worktree evidence", async () => {
|
||||
const root = await mkdtempCaseRoot();
|
||||
const cwd = path.join(root, "hwlab");
|
||||
const runDir = path.join(cwd, ".state", "hwlab-cli", "caserun", "run-worker-started");
|
||||
try {
|
||||
await mkdir(runDir, { recursive: true });
|
||||
await writeFile(path.join(runDir, "worker.stdout.log"), "", "utf8");
|
||||
await writeFile(path.join(runDir, "worker.stderr.log"), "", "utf8");
|
||||
await writeFile(path.join(runDir, "run.json"), JSON.stringify({
|
||||
caseId: "d601-f103-v2-compile",
|
||||
runId: "run-worker-started",
|
||||
runDir,
|
||||
caseRepo: "",
|
||||
caseDir: "",
|
||||
caseFile: "",
|
||||
sourceSpecPath: "",
|
||||
specPath: path.join(runDir, ".hwlab", "hwpod-spec.yaml"),
|
||||
apiUrl: "http://api.test",
|
||||
compileOnly: true,
|
||||
createdAt: "2026-06-05T00:00:00.000Z",
|
||||
updatedAt: "2026-06-05T00:00:10.000Z",
|
||||
status: "running",
|
||||
stage: "queued",
|
||||
definition: {},
|
||||
subject: { repoLocalPath: SUBJECT_REPO_LOCAL_PATH, commitId: SUBJECT_COMMIT_ID, subdir: "projects/01_baseline", worktreePath: "F:\\Work\\HWLAB-CASE-F103\\.worktree\\caserun-run-worker-started", workspacePath: "", setup: {} },
|
||||
_control: { status: "running", stage: "started", pid: 1234, stdoutPath: path.join(runDir, "worker.stdout.log"), stderrPath: path.join(runDir, "worker.stderr.log"), startedAt: "2026-06-05T00:00:00.000Z" }
|
||||
}, null, 2), "utf8");
|
||||
|
||||
const status = await runHwlabCli(["case", "run", "status", "run-worker-started"], { cwd, now: () => "2026-06-05T00:00:12.000Z" });
|
||||
assert.equal(status.exitCode, 0);
|
||||
assert.equal(status.payload.stage, "started");
|
||||
assert.equal(status.payload.staleControlWarning, undefined);
|
||||
} finally {
|
||||
await rm(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
async function mkdtempCaseRoot() {
|
||||
return await import("node:fs/promises").then(({ mkdtemp }) => mkdtemp(path.join(os.tmpdir(), "hwlab-caserun-")));
|
||||
}
|
||||
@@ -327,12 +418,8 @@ function caseRunFlowFetch(requests: any[]) {
|
||||
return new Response(JSON.stringify({ ok: true, status: resultPolls > 1 ? "completed" : "running", reply: { content: "done" } }), { status: 200, headers: { "content-type": "application/json" } });
|
||||
}
|
||||
if (String(url).startsWith("http://web.test/v1/agent/chat/trace/")) {
|
||||
return new Response(JSON.stringify({ ok: true, status: "completed", sourceEventCount: 6, renderedRowCount: 3, traceSummary: { terminalStatus: "completed", agentRun: { runId: "run_agent_trace", commandId: "cmd_agent_trace" } }, rows: [
|
||||
{ seq: 1, header: "ok commandExecution", body: "command: hwpod-ctl spec validate --spec .hwlab/hwpod-spec.yaml\nexitCode: 0" },
|
||||
{ seq: 2, header: "ok commandExecution", body: "command: hwpod inspect --spec .hwlab/hwpod-spec.yaml\nexitCode: 0" },
|
||||
{ seq: 3, header: "ok commandExecution", body: "cat .agents/skills/hwpod-cli/SKILL.md stdout: use `hwpod build` for compile exitCode=0" },
|
||||
{ seq: 4, header: "ok commandExecution", body: "command: hwpod build --spec .hwlab/hwpod-spec.yaml\nstdout: {\"job_id\":\"20260605_203835_798515c0\"}\nexitCode: 0" }
|
||||
] }), { status: 200, headers: { "content-type": "application/json" } });
|
||||
const events = caseRunTraceEvents();
|
||||
return new Response(JSON.stringify({ ok: true, status: "completed", sourceEventCount: events.length, renderedRowCount: 0, traceSummary: { terminalStatus: "completed", agentRun: { runId: "run_agent_trace", commandId: "cmd_agent_trace" } }, events }), { status: 200, headers: { "content-type": "application/json" } });
|
||||
}
|
||||
if (body.ops?.[0]?.args?.argv?.includes("status") && body.ops?.length === 1) {
|
||||
return hwpodResponse([{ exitCode: 0, stdout: body.ops[0].args.argv.includes("--short") ? " M projects/01_baseline/main.c\n" : "" }]);
|
||||
@@ -350,6 +437,20 @@ function caseRunFlowFetch(requests: any[]) {
|
||||
};
|
||||
}
|
||||
|
||||
function caseRunTraceEvents() {
|
||||
const events: any[] = [
|
||||
{ seq: 1, label: "item/commandExecution:completed", type: "tool_call", toolName: "commandExecution", status: "completed", command: "cat .agents/skills/hwpod-cli/SKILL.md", message: "use `hwpod build` for compile", exitCode: 0 },
|
||||
{ seq: 248, label: "item/commandExecution:completed", type: "tool_call", toolName: "commandExecution", status: "completed", command: "/bin/sh -lc 'hwpod-ctl spec validate --spec .hwlab/hwpod-spec.yaml'", message: "validated", exitCode: 0 },
|
||||
{ seq: 323, label: "item/commandExecution:completed", type: "tool_call", toolName: "commandExecution", status: "completed", command: "/bin/sh -lc 'hwpod inspect --spec .hwlab/hwpod-spec.yaml'", message: "inspected", exitCode: 0 }
|
||||
];
|
||||
for (let index = 0; index < 31; index += 1) {
|
||||
const seq = 400 + index * 2;
|
||||
events.push({ seq, label: "item/commandExecution:started", type: "tool_call", toolName: "commandExecution", status: "started", command: "/bin/sh -lc 'hwpod build --spec .hwlab/hwpod-spec.yaml'", message: "commandExecution inProgress: /bin/sh -lc 'hwpod build --spec .hwlab/hwpod-spec.yaml'" });
|
||||
events.push({ seq: seq + 1, label: "item/commandExecution:completed", type: "tool_call", toolName: "commandExecution", status: "completed", command: "/bin/sh -lc 'hwpod build --spec .hwlab/hwpod-spec.yaml'", message: index === 0 ? "{\"job_id\":\"20260605_203835_798515c0\"}" : "build completed", exitCode: 0 });
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
function hwpodResponse(outputs: Array<{ exitCode: number; stdout?: string; stderr?: string }>) {
|
||||
return new Response(JSON.stringify({ body: { results: outputs.map((output) => ({ ok: output.exitCode === 0, output })) } }), { status: 200, headers: { "content-type": "application/json" } });
|
||||
}
|
||||
|
||||
@@ -119,6 +119,7 @@ type AgentTraceCommand = {
|
||||
toolName?: string;
|
||||
status?: string;
|
||||
command?: string;
|
||||
normalizedCommand?: string;
|
||||
bodyPreview?: string;
|
||||
exitCode?: number;
|
||||
};
|
||||
@@ -283,6 +284,7 @@ export async function statusCaseRun(context: CaseContext) {
|
||||
const runId = requiredText(context.parsed.runId ?? context.rest[2], "runId");
|
||||
const run = await readRunById(context, runId);
|
||||
const control = controlFromRun(run);
|
||||
const stage = statusStage(run, control);
|
||||
const stdoutPath = text(control.stdoutPath) || path.join(run.runDir, "worker.stdout.log");
|
||||
const stderrPath = text(control.stderrPath) || path.join(run.runDir, "worker.stderr.log");
|
||||
const stdoutBytes = await fileSize(stdoutPath);
|
||||
@@ -293,7 +295,8 @@ export async function statusCaseRun(context: CaseContext) {
|
||||
runDir: run.runDir,
|
||||
stateFile: path.join(run.runDir, "run.json"),
|
||||
status: text(control.status) || text(run.status) || "unknown",
|
||||
stage: text(control.stage) || text(run.stage) || inferRunStage(run),
|
||||
stage: stage.stage,
|
||||
staleControlWarning: stage.warning,
|
||||
pid: control.pid ?? null,
|
||||
startedAt: control.startedAt ?? run.createdAt,
|
||||
updatedAt: run.updatedAt,
|
||||
@@ -318,13 +321,15 @@ export async function resultCaseRun(context: CaseContext) {
|
||||
const run = await readRunById(context, runId);
|
||||
const control = controlFromRun(run);
|
||||
const status = text(control.status) || text(run.status) || "unknown";
|
||||
const stage = statusStage(run, control);
|
||||
if (status !== "completed") {
|
||||
return ok("case.run.result", {
|
||||
caseId: run.caseId,
|
||||
runId: run.runId,
|
||||
runDir: run.runDir,
|
||||
status,
|
||||
stage: text(control.stage) || inferRunStage(run),
|
||||
stage: stage.stage,
|
||||
staleControlWarning: stage.warning,
|
||||
ready: false,
|
||||
nextPollCommand: `hwlab-cli case run status ${run.runId} --state-dir ${stateRoot(context)}`,
|
||||
logsCommand: `hwlab-cli case run logs ${run.runId} --state-dir ${stateRoot(context)} --tail 8000`
|
||||
@@ -922,6 +927,7 @@ function commandsFromTraceRows(rows: Record<string, unknown>[]) {
|
||||
toolName: text(row.toolName ?? row.name),
|
||||
status: text(row.status ?? row.level),
|
||||
command: clipText(command, 1000),
|
||||
normalizedCommand: clipText(normalizedHwpodCommand(command), 1000),
|
||||
bodyPreview: clipText(body, 1200),
|
||||
exitCode: exitCodeFromText(body)
|
||||
})];
|
||||
@@ -942,6 +948,7 @@ function commandsFromTraceEvents(events: Record<string, unknown>[]) {
|
||||
toolName,
|
||||
status: text(event.status ?? event.level),
|
||||
command: clipText(command, 1000),
|
||||
normalizedCommand: clipText(normalizedHwpodCommand(command), 1000),
|
||||
bodyPreview: clipText(message, 1200),
|
||||
exitCode: numberOption(event.exitCode) ?? exitCodeFromText(message)
|
||||
})];
|
||||
@@ -960,10 +967,10 @@ function isCommandTraceEvent(event: Record<string, unknown>, toolName: string) {
|
||||
function selectAgentTraceCommands(commands: AgentTraceCommand[]) {
|
||||
const selected: AgentTraceCommand[] = [];
|
||||
for (const command of [
|
||||
...commands.filter((item) => mentionsHwpodBuild(item)),
|
||||
...commands.filter((item) => mentionsHwpod(item)),
|
||||
...selectRepresentativeHwpodCommands(commands),
|
||||
...commands.slice(0, 10),
|
||||
...commands.slice(-10)
|
||||
...commands.slice(-10),
|
||||
...commands.filter((item) => mentionsHwpod(item))
|
||||
]) {
|
||||
const key = `${command.source}:${command.seq ?? ""}:${command.rowId ?? ""}:${command.command ?? ""}:${command.status ?? ""}`;
|
||||
if (selected.some((item) => `${item.source}:${item.seq ?? ""}:${item.rowId ?? ""}:${item.command ?? ""}:${item.status ?? ""}` === key)) continue;
|
||||
@@ -973,6 +980,21 @@ function selectAgentTraceCommands(commands: AgentTraceCommand[]) {
|
||||
return selected;
|
||||
}
|
||||
|
||||
function selectRepresentativeHwpodCommands(commands: AgentTraceCommand[]) {
|
||||
return ["spec-validate", "inspect", "build"].flatMap((kind) => {
|
||||
const candidates = commands.filter((command) => hwpodCommandKind(command) === kind);
|
||||
return candidates.find((command) => text(command.status) === "completed" || command.exitCode === 0) ?? candidates[0] ?? [];
|
||||
});
|
||||
}
|
||||
|
||||
function hwpodCommandKind(command: AgentTraceCommand) {
|
||||
const raw = normalizedHwpodCommand(command.normalizedCommand || command.command || "");
|
||||
if (/^(?:hwpod-ctl|hwpod-cli)\s+spec\s+validate(?:\s|$)/iu.test(raw)) return "spec-validate";
|
||||
if (/^hwpod\s+inspect(?:\s|$)/iu.test(raw)) return "inspect";
|
||||
if (/^(?:hwpod|hwpod-cli)\s+build(?:\s|$)/iu.test(raw) || /^(?:bun\s+)?tools\/hwpod-cli\.ts\s+build(?:\s|$)/iu.test(raw)) return "build";
|
||||
return "";
|
||||
}
|
||||
|
||||
function arrayOfObjects(value: unknown): Record<string, unknown>[] {
|
||||
return Array.isArray(value) ? value.filter((item) => item && typeof item === "object" && !Array.isArray(item)) as Record<string, unknown>[] : [];
|
||||
}
|
||||
@@ -1001,15 +1023,19 @@ function exitCodeFromText(value: string) {
|
||||
}
|
||||
|
||||
function mentionsHwpod(command: AgentTraceCommand) {
|
||||
const raw = unquoteShellCommand(text(command.command));
|
||||
const raw = normalizedHwpodCommand(command.normalizedCommand || command.command || "");
|
||||
return /^(?:hwpod|hwpod-ctl|hwpod-cli)(?:\s|$)/iu.test(raw) || /^(?:bun\s+)?tools\/hwpod-cli\.ts(?:\s|$)/iu.test(raw);
|
||||
}
|
||||
|
||||
function mentionsHwpodBuild(command: AgentTraceCommand) {
|
||||
const raw = unquoteShellCommand(text(command.command));
|
||||
const raw = normalizedHwpodCommand(command.normalizedCommand || command.command || "");
|
||||
return /^(?:hwpod|hwpod-cli)\s+build(?:\s|$)/iu.test(raw) || /^(?:bun\s+)?tools\/hwpod-cli\.ts\s+build(?:\s|$)/iu.test(raw);
|
||||
}
|
||||
|
||||
function normalizedHwpodCommand(value: string) {
|
||||
return unquoteShellCommand(text(value)).replace(/\s+/gu, " ").trim();
|
||||
}
|
||||
|
||||
function unquoteShellCommand(value: string) {
|
||||
return value.replace(/^\/bin\/sh\s+-lc\s+/u, "").replace(/^['"]|['"]$/gu, "").trim();
|
||||
}
|
||||
@@ -1343,7 +1369,7 @@ async function writeJson(file: string, value: any) {
|
||||
async function writeRunState(run: PreparedCaseRun) {
|
||||
const current = await readJsonIfExists(path.join(run.runDir, "run.json"));
|
||||
const control = current?._control && typeof current._control === "object" ? current._control : {};
|
||||
await writeJson(path.join(run.runDir, "run.json"), { ...run, _control: control });
|
||||
await writeJson(path.join(run.runDir, "run.json"), { ...run, _control: syncControlStage(control, run) });
|
||||
}
|
||||
|
||||
async function writeRunControl(context: CaseContext, run: PreparedCaseRun, patch: Record<string, unknown>) {
|
||||
@@ -1373,6 +1399,12 @@ function controlFromRun(run: any) {
|
||||
return run?._control && typeof run._control === "object" ? run._control : {};
|
||||
}
|
||||
|
||||
function syncControlStage(control: Record<string, unknown>, run: PreparedCaseRun) {
|
||||
const status = text(control.status);
|
||||
if (status && !["completed", "failed"].includes(status)) return clean({ ...control, stage: latestRunStage(run) });
|
||||
return control;
|
||||
}
|
||||
|
||||
async function loadControlRunSkeleton(context: CaseContext, caseId: string, runId: string, runDir: string) {
|
||||
const existing = await readJsonIfExists(path.join(runDir, "run.json"));
|
||||
if (existing) return existing as PreparedCaseRun;
|
||||
@@ -1443,6 +1475,48 @@ function inferRunStage(run: PreparedCaseRun) {
|
||||
return "queued";
|
||||
}
|
||||
|
||||
function latestRunStage(run: PreparedCaseRun) {
|
||||
return selectLatestStage([text(run.stage), inferRunStage(run)]);
|
||||
}
|
||||
|
||||
function statusStage(run: PreparedCaseRun, control: Record<string, unknown>) {
|
||||
const controlStage = text(control.stage);
|
||||
const runStage = text(run.stage);
|
||||
const inferredStage = inferRunStage(run);
|
||||
const selectedStage = selectLatestStage([controlStage, runStage, inferredStage]);
|
||||
const terminalControl = ["completed", "failed"].includes(controlStage);
|
||||
const stage = terminalControl ? controlStage : selectedStage || "queued";
|
||||
const warning = controlStage && controlStage !== stage && !terminalControl ? {
|
||||
code: "stale_control_stage",
|
||||
controlStage,
|
||||
runStage: runStage || null,
|
||||
inferredStage,
|
||||
selectedStage: stage
|
||||
} : undefined;
|
||||
return { stage, warning };
|
||||
}
|
||||
|
||||
function selectLatestStage(stages: string[]) {
|
||||
return stages.filter(Boolean).reduce((best, stage) => stageRank(stage) >= stageRank(best) ? stage : best, "");
|
||||
}
|
||||
|
||||
function stageRank(stage: string) {
|
||||
if (stage === "completed" || stage === "failed") return 100;
|
||||
if (stage === "build-completed") return 90;
|
||||
if (stage === "agent-diff-collected") return 80;
|
||||
if (stage === "agent-trace-collected") return 70;
|
||||
if (stage === "agent-completed") return 65;
|
||||
if (stage === "agent-running") return 60;
|
||||
if (stage === "agent-submitting") return 50;
|
||||
if (stage === "agent-session-starting") return 40;
|
||||
if (stage === "worker-running") return 35;
|
||||
if (stage === "started") return 34;
|
||||
if (stage === "queued") return 30;
|
||||
if (stage === "prepared") return 20;
|
||||
if (stage.startsWith("agent-")) return 55;
|
||||
return 0;
|
||||
}
|
||||
|
||||
function elapsedMs(startedAt: unknown, completedAt: unknown) {
|
||||
const start = Date.parse(text(startedAt));
|
||||
if (!Number.isFinite(start)) return null;
|
||||
|
||||
Reference in New Issue
Block a user