import * as fs from "node:fs"; import type { RunStatus } from "./status.ts"; /** Stable semantic outcome carried through scheduler batching/restart. */ export type WorkerOutcomeCode = | "complete" | "complete_result_missing" | "stopped_final_race" | "stopped_final" | "stopped_partial" | "stopped_late_tool" | "stopped_no_result" | "failed_partial" | "failed_late_tool" | "failed_no_result" | "timed_out_partial" | "timed_out_late_tool" | "timed_out_no_result" | "paused" | "orphaned" | "unknown" | "active"; export interface WorkerOutcome { code: WorkerOutcomeCode; /** Compact qualifier for list/status rows. */ qualifier: string; /** Whether the next useful action is reading result.md. */ readResult: boolean; /** A known in-flight tool returned after stop/terminalization began. */ lateToolEvidence: boolean; } /** * Nullable/stale status metadata is only a claim. Human/model projections use the * canonical run path and require that it names a readable regular file. */ export function readableResultAvailable(canonicalResultPath: string): boolean { let fd: number | undefined; try { fd = fs.openSync(canonicalResultPath, "r"); return fs.fstatSync(fd).isFile(); } catch { return false; } finally { if (fd !== undefined) { try { fs.closeSync(fd); } catch { // Readability classification is best-effort; a close failure cannot make // an unreadable result available. } } } } function outcome( code: WorkerOutcomeCode, qualifier: string, readResult: boolean, lateToolEvidence: boolean, ): WorkerOutcome { return { code, qualifier: `${qualifier}${lateToolEvidence && code !== "stopped_late_tool" && code !== "failed_late_tool" && code !== "timed_out_late_tool" ? "; late tool evidence preserved" : ""}`, readResult, lateToolEvidence, }; } export function classifyWorkerOutcome(status: RunStatus, resultAvailable: boolean): WorkerOutcome { const latestFinal = status.finalAssistantTextPresent === true; const duringTerminalization = status.assistantEvidence?.observedDuringTerminalization === true; const lateTools = (status.lateToolResults?.length ?? 0) > 0; switch (status.state) { case "complete": return resultAvailable ? outcome("complete", "result available", true, lateTools) : outcome("complete_result_missing", "result unavailable; record inconsistent", false, lateTools); case "stopped": if (resultAvailable && latestFinal && duringTerminalization) { return outcome("stopped_final_race", "final answer preserved during stop", true, lateTools); } if (resultAvailable && latestFinal) { return outcome("stopped_final", "final answer preserved", true, lateTools); } if (resultAvailable) return outcome("stopped_partial", "partial output preserved", true, lateTools); if (lateTools) return outcome("stopped_late_tool", "no final answer; late tool evidence preserved", false, true); return outcome("stopped_no_result", "no final answer", false, false); case "failed": if (resultAvailable) return outcome("failed_partial", "failed; partial output preserved", true, lateTools); if (lateTools) return outcome("failed_late_tool", "failed; no final answer; late tool evidence preserved", false, true); return outcome("failed_no_result", "failed; no result", false, false); case "timedOut": if (resultAvailable) return outcome("timed_out_partial", "timed out; partial output preserved", true, lateTools); if (lateTools) return outcome("timed_out_late_tool", "timed out; no final answer; late tool evidence preserved", false, true); return outcome("timed_out_no_result", "timed out; no result", false, false); case "paused": return outcome("paused", "paused; resumable checkpoint", resultAvailable, lateTools); case "orphaned": return outcome("orphaned", "orphaned; lifecycle unresolved", resultAvailable, lateTools); case "unknown": return outcome("unknown", "state unknown; lifecycle unresolved", resultAvailable, lateTools); default: return outcome("active", status.state, false, lateTools); } } export function completionOutcomeText(code: WorkerOutcomeCode, lateToolEvidence = false): string { let text: string; switch (code) { case "complete": text = "completed with a result"; break; case "complete_result_missing": text = "is recorded complete, but its result is unavailable; the record is inconsistent"; break; case "stopped_final_race": text = "stopped, but a final answer was preserved during the stop race"; break; case "stopped_final": text = "stopped with a preserved final answer"; break; case "stopped_partial": text = "stopped with preserved output of uncertain completeness"; break; case "stopped_late_tool": text = "stopped without a final answer; a tool result arrived during shutdown"; break; case "stopped_no_result": text = "stopped without a final answer"; break; case "failed_partial": text = "failed with preserved partial output"; break; case "failed_late_tool": text = "failed without a final answer; a tool result arrived during shutdown"; break; case "failed_no_result": text = "failed without a result"; break; case "timed_out_partial": text = "timed out with preserved partial output"; break; case "timed_out_late_tool": text = "timed out without a final answer; a tool result arrived during shutdown"; break; case "timed_out_no_result": text = "timed out without a result"; break; case "paused": text = "is paused at a resumable checkpoint"; break; case "orphaned": text = "is orphaned and its lifecycle outcome is unresolved"; break; case "unknown": text = "has an unknown lifecycle outcome"; break; case "active": text = "is still active"; break; } return lateToolEvidence && !["stopped_late_tool", "failed_late_tool", "timed_out_late_tool"].includes(code) ? `${text}; a tool result also arrived during shutdown` : text; } export function resultDispositionText(outcome: WorkerOutcome): string | undefined { let disposition: string | undefined; switch (outcome.code) { case "stopped_final_race": disposition = "The stopped verdict remains authoritative; this final answer was preserved during terminalization."; break; case "stopped_final": disposition = "The stopped verdict remains authoritative; a final answer is preserved."; break; case "stopped_partial": disposition = "This is preserved output of uncertain completeness, not proof that the stopped task completed."; break; case "failed_partial": disposition = "This is partial evidence from a failed run, not proof of success."; break; case "timed_out_partial": disposition = "This is partial evidence from a timed-out run, not proof of success."; break; case "paused": disposition = "This output belongs to a resumable paused checkpoint."; break; case "orphaned": case "unknown": disposition = "Treat this output as unverified evidence because the lifecycle outcome is unresolved."; break; default: break; } if (!outcome.lateToolEvidence) return disposition; const late = "A known in-flight tool also returned during shutdown; inspect trace.log for its bounded evidence."; return disposition === undefined ? late : `${disposition}\n${late}`; } export function noResultOutcomeText(outcome: WorkerOutcome): string { switch (outcome.code) { case "stopped_late_tool": return "It has no final answer, but a tool result arrived during shutdown and is preserved in recent events."; case "stopped_no_result": return "It stopped without a final answer."; case "failed_no_result": return "It failed without producing a result."; case "failed_late_tool": return "It failed without a final answer, but a tool result arrived during shutdown and is preserved in recent events."; case "timed_out_no_result": return "It timed out without producing a result."; case "timed_out_late_tool": return "It timed out without a final answer, but a tool result arrived during shutdown and is preserved in recent events."; case "complete_result_missing": return "It is recorded complete, but result.md is unavailable; inspect diagnostics for an inconsistent record."; default: return "No stored result is available for this run."; } }