@tangle-network/agent-eval 0.174.0 → 0.176.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +1 -1
  3. package/dist/adapters/http.d.ts +1 -1
  4. package/dist/agent-profile-cell-0gSi5ffD.js +374 -0
  5. package/dist/agent-profile-cell-0gSi5ffD.js.map +1 -0
  6. package/dist/analyst/index.d.ts +3 -3
  7. package/dist/analyst/index.js +4 -4
  8. package/dist/{benchmark-command-mZIlR-ra.js → benchmark-command-yPqjcZnC.js} +7 -7
  9. package/dist/{benchmark-command-mZIlR-ra.js.map → benchmark-command-yPqjcZnC.js.map} +1 -1
  10. package/dist/benchmarks/index.d.ts +1 -1
  11. package/dist/benchmarks/index.js +3 -3
  12. package/dist/campaign/index.d.ts +3 -3
  13. package/dist/campaign/index.js +9 -9
  14. package/dist/{campaign-BzMSCejE.js → campaign-85igdlgG.js} +12 -12
  15. package/dist/{campaign-BzMSCejE.js.map → campaign-85igdlgG.js.map} +1 -1
  16. package/dist/campaign-evidence-D8DBLqLI.js +2083 -0
  17. package/dist/campaign-evidence-D8DBLqLI.js.map +1 -0
  18. package/dist/{opencode-sqlite-eK6HW6dr.js → claude-jsonl-CxZZrDJ3.js} +9 -149
  19. package/dist/claude-jsonl-CxZZrDJ3.js.map +1 -0
  20. package/dist/cli.js +9 -2
  21. package/dist/cli.js.map +1 -1
  22. package/dist/contract/index.d.ts +4 -4
  23. package/dist/contract/index.js +9 -9
  24. package/dist/{default-registry-CrAp0pYq.js → default-registry-DBqVI4pq.js} +2 -2
  25. package/dist/{default-registry-CrAp0pYq.js.map → default-registry-DBqVI4pq.js.map} +1 -1
  26. package/dist/{define-agent-eval-V1jQyCDR.d.ts → define-agent-eval-CCbl8k2E.d.ts} +11 -4
  27. package/dist/define-agent-eval-CCbl8k2E.d.ts.map +1 -0
  28. package/dist/{define-agent-eval-ox5McL6e.js → define-agent-eval-DEMsu5eA.js} +54 -36
  29. package/dist/define-agent-eval-DEMsu5eA.js.map +1 -0
  30. package/dist/{dspy-rlm-engine-Caz2pl4L.js → dspy-rlm-engine-DqjER2sV.js} +2 -2
  31. package/dist/{dspy-rlm-engine-Caz2pl4L.js.map → dspy-rlm-engine-DqjER2sV.js.map} +1 -1
  32. package/dist/{eval-campaign-BeAjdhzC.js → eval-campaign-Cs-7MiCs.js} +4 -5
  33. package/dist/{eval-campaign-BeAjdhzC.js.map → eval-campaign-Cs-7MiCs.js.map} +1 -1
  34. package/dist/experiment/index.d.ts +3 -68
  35. package/dist/experiment/index.d.ts.map +1 -1
  36. package/dist/experiment/index.js +6 -128
  37. package/dist/experiment/index.js.map +1 -1
  38. package/dist/{attestation-XSUpbc4o.js → experiment-tracker-BKEumQug.js} +2 -96
  39. package/dist/experiment-tracker-BKEumQug.js.map +1 -0
  40. package/dist/{attestation-c1QvaBdX.d.ts → experiment-tracker-CNwqCZFD.d.ts} +2 -78
  41. package/dist/experiment-tracker-CNwqCZFD.d.ts.map +1 -0
  42. package/dist/{external-optimizer-process-CxnFL1hd.js → external-optimizer-process-Dlz8YxrT.js} +3 -3
  43. package/dist/{external-optimizer-process-CxnFL1hd.js.map → external-optimizer-process-Dlz8YxrT.js.map} +1 -1
  44. package/dist/{external-optimizer-subprocess-CQi27uEI.js → external-optimizer-subprocess-q3VzlGAO.js} +2 -2
  45. package/dist/{external-optimizer-subprocess-CQi27uEI.js.map → external-optimizer-subprocess-q3VzlGAO.js.map} +1 -1
  46. package/dist/{index-Bn-nlnSV.d.ts → index-BAAiSF3_.d.ts} +2 -2
  47. package/dist/{index-Bn-nlnSV.d.ts.map → index-BAAiSF3_.d.ts.map} +1 -1
  48. package/dist/{index-DKXuBPXf.d.ts → index-Bg6OT2Dd.d.ts} +23 -10
  49. package/dist/{index-DKXuBPXf.d.ts.map → index-Bg6OT2Dd.d.ts.map} +1 -1
  50. package/dist/{index-BTrx5s8m.d.ts → index-DBkcm_9H.d.ts} +4 -4
  51. package/dist/{index-BTrx5s8m.d.ts.map → index-DBkcm_9H.d.ts.map} +1 -1
  52. package/dist/{index-D-UdhAmg.d.ts → index-u0d1Jp4F.d.ts} +4 -2
  53. package/dist/{index-D-UdhAmg.d.ts.map → index-u0d1Jp4F.d.ts.map} +1 -1
  54. package/dist/index.d.ts +5 -5
  55. package/dist/index.js +15 -16
  56. package/dist/index.js.map +1 -1
  57. package/dist/{integrity-BWywb34E.js → integrity-DsHWCebQ.js} +11 -435
  58. package/dist/integrity-DsHWCebQ.js.map +1 -0
  59. package/dist/ledger-core/index.d.ts +2 -2
  60. package/dist/ledger-core/index.js +2 -2
  61. package/dist/{ledger-core-PIfjCbKn.js → ledger-core-Cs9f7385.js} +60 -47
  62. package/dist/{ledger-core-PIfjCbKn.js.map → ledger-core-Cs9f7385.js.map} +1 -1
  63. package/dist/{llm-judge-DmNaBrXB.js → llm-judge-DliimmRb.js} +994 -1517
  64. package/dist/llm-judge-DliimmRb.js.map +1 -0
  65. package/dist/{mint-vWOdD8Ae.js → mint-Cc1_zwRQ.js} +2 -2
  66. package/dist/{mint-vWOdD8Ae.js.map → mint-Cc1_zwRQ.js.map} +1 -1
  67. package/dist/openapi.json +1 -1
  68. package/dist/opencode-sqlite-CNw3vubS.js +145 -0
  69. package/dist/opencode-sqlite-CNw3vubS.js.map +1 -0
  70. package/dist/{produced-state-B8mw6zj9.js → produced-state-DrMqa2HD.js} +3 -2
  71. package/dist/{produced-state-B8mw6zj9.js.map → produced-state-DrMqa2HD.js.map} +1 -1
  72. package/dist/profile-cell.js +1 -268
  73. package/dist/{promotion-policy-LY9mVQ7W.js → promotion-policy-DWOm70gx.js} +2 -2
  74. package/dist/{promotion-policy-LY9mVQ7W.js.map → promotion-policy-DWOm70gx.js.map} +1 -1
  75. package/dist/{release-confidence-BsGEg_xg.js → release-confidence-BcGCclTB.js} +2 -2
  76. package/dist/{release-confidence-BsGEg_xg.js.map → release-confidence-BcGCclTB.js.map} +1 -1
  77. package/dist/report-command-DKlXfU5r.js +1528 -0
  78. package/dist/report-command-DKlXfU5r.js.map +1 -0
  79. package/dist/reporting.js +2 -2
  80. package/dist/{reward-hacking-CKW4teig.js → reward-hacking-D0XwhVWE.js} +2 -215
  81. package/dist/reward-hacking-D0XwhVWE.js.map +1 -0
  82. package/dist/rl.js +5 -4
  83. package/dist/rl.js.map +1 -1
  84. package/dist/rollout/index.js +4 -3
  85. package/dist/{rollout-C-znbbYg.js → rollout-DmoJVqrF.js} +4 -3
  86. package/dist/{rollout-C-znbbYg.js.map → rollout-DmoJVqrF.js.map} +1 -1
  87. package/dist/run-record-CR63CpHK.js +216 -0
  88. package/dist/run-record-CR63CpHK.js.map +1 -0
  89. package/dist/{run-record-ZIsR9Fif.js → run-record-DQpSf7t-.js} +2 -2
  90. package/dist/{run-record-ZIsR9Fif.js.map → run-record-DQpSf7t-.js.map} +1 -1
  91. package/dist/{semantic-concept-judge-E3s_fEjB.js → semantic-concept-judge-Dw-f7TEs.js} +3 -3
  92. package/dist/{semantic-concept-judge-E3s_fEjB.js.map → semantic-concept-judge-Dw-f7TEs.js.map} +1 -1
  93. package/dist/{sequential-B51qAYE4.js → sequential-B5gXgcyp.js} +3 -3
  94. package/dist/{sequential-B51qAYE4.js.map → sequential-B5gXgcyp.js.map} +1 -1
  95. package/dist/{skillopt-optimization-method-f7399oGb.js → skillopt-optimization-method-CV7go7ex.js} +6 -749
  96. package/dist/skillopt-optimization-method-CV7go7ex.js.map +1 -0
  97. package/dist/{statistical-heldout-Cqb73yE9.d.ts → statistical-heldout-Z9NROFFS.d.ts} +156 -3
  98. package/dist/statistical-heldout-Z9NROFFS.d.ts.map +1 -0
  99. package/dist/{summary-report-Bgh8CpNK.js → summary-report-B16xy9Kd.js} +2 -2
  100. package/dist/{summary-report-Bgh8CpNK.js.map → summary-report-B16xy9Kd.js.map} +1 -1
  101. package/dist/supervisor-run/index.d.ts +71 -6
  102. package/dist/supervisor-run/index.d.ts.map +1 -1
  103. package/dist/supervisor-run/index.js +6 -1357
  104. package/dist/supervisor-run/index.js.map +1 -1
  105. package/dist/terminal-record-Ce9_UjRz.js +539 -0
  106. package/dist/terminal-record-Ce9_UjRz.js.map +1 -0
  107. package/dist/traces.js +1 -1
  108. package/dist/{types-CoPUTiXb.d.ts → types-vUdAx2Cj.d.ts} +65 -3
  109. package/dist/types-vUdAx2Cj.d.ts.map +1 -0
  110. package/docs/public-api.md +62 -39
  111. package/docs/search-history-receipts.md +48 -1
  112. package/package.json +1 -1
  113. package/dist/attestation-XSUpbc4o.js.map +0 -1
  114. package/dist/attestation-c1QvaBdX.d.ts.map +0 -1
  115. package/dist/define-agent-eval-V1jQyCDR.d.ts.map +0 -1
  116. package/dist/define-agent-eval-ox5McL6e.js.map +0 -1
  117. package/dist/integrity-BWywb34E.js.map +0 -1
  118. package/dist/llm-judge-DmNaBrXB.js.map +0 -1
  119. package/dist/opencode-sqlite-eK6HW6dr.js.map +0 -1
  120. package/dist/power-preflight-CFXm0Vjo.js +0 -502
  121. package/dist/power-preflight-CFXm0Vjo.js.map +0 -1
  122. package/dist/pre-registration-D94b7Of5.js +0 -110
  123. package/dist/pre-registration-D94b7Of5.js.map +0 -1
  124. package/dist/profile-cell.js.map +0 -1
  125. package/dist/reward-hacking-CKW4teig.js.map +0 -1
  126. package/dist/skillopt-optimization-method-f7399oGb.js.map +0 -1
  127. package/dist/statistical-heldout-Cqb73yE9.d.ts.map +0 -1
  128. package/dist/types-CoPUTiXb.d.ts.map +0 -1
@@ -0,0 +1,1528 @@
1
+ import { o as summarizeNumberSeries } from "./descriptive-1V17A-qa.js";
2
+ import { n as findOpencodeSessionsByDirectory, r as openOpencodeDb, t as DEFAULT_OPENCODE_DB } from "./opencode-sqlite-CNw3vubS.js";
3
+ import { a as SUPERVISOR_RUN_ROLLUP_SCHEMA, c as showMeasured, d as parseJson, f as parseJsonl, i as NO_SOURCE_LIMITS, l as unavailable, m as workerSourceKey, o as SUPERVISOR_RUN_SCHEMA, p as parseSupervisorTree, r as readTerminalRecord, s as isUnavailable, u as asRecord } from "./terminal-record-Ce9_UjRz.js";
4
+ import { basename, join, resolve } from "node:path";
5
+ import { appendFile, mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
6
+ //#region src/supervisor-run/analyze.ts
7
+ /**
8
+ * The pure analyzer. Takes already-read bytes (`SupervisorRunSources`) and
9
+ * returns the report — every metric derivable from a synthetic journal string
10
+ * with no filesystem, no process, and no network. All I/O lives in a reader
11
+ * (`loops-reader.ts` is one).
12
+ */
13
+ const NO_CACHE_COUNTERS = "the journal carries no cache-token counters for this role";
14
+ const NO_CACHE_BREAKDOWN = "Runtime recorded cacheBreakdownKnown:false — the provider reported a total without splitting cache reads from writes";
15
+ /**
16
+ * Analyze already-read supervisor-run bytes. Pure and synchronous: same bytes
17
+ * in, same report out (modulo `generatedAt`, which `now` pins in tests).
18
+ */
19
+ function analyzeSupervisorRunSources(src, now = Date.now) {
20
+ const gaps = [];
21
+ const gap = (what, reason) => {
22
+ gaps.push(`${what}: ${reason}`);
23
+ return unavailable(reason);
24
+ };
25
+ const journalMissing = src.journalMissingReason ?? (src.supRunDir === null ? "no supervisor run dir under <ws>/.agent/supervisor (or legacy <ws>/.loops/supervisor)" : "journal.jsonl absent");
26
+ const haveJournal = src.journal !== null;
27
+ const tree = parseSupervisorTree(src);
28
+ const state = tree.state;
29
+ const result = parseJson(src.result);
30
+ const judge = parseJson(src.judge);
31
+ const terminal = readTerminalRecord({
32
+ state,
33
+ result,
34
+ failure: src.failure === void 0 ? void 0 : parseJson(src.failure)
35
+ });
36
+ const { rootId, workerSpawns, workerCloses, startedAt, completedAt } = tree;
37
+ const rootSpawn = rootId === null ? null : tree.spawns.find((spawn) => spawn.id === rootId) ?? null;
38
+ const spawnById = new Map(workerSpawns.map((spawn) => [spawn.id, spawn]));
39
+ const spawnsByLabel = /* @__PURE__ */ new Map();
40
+ for (const spawn of workerSpawns) {
41
+ const matches = spawnsByLabel.get(spawn.label) ?? [];
42
+ matches.push(spawn);
43
+ spawnsByLabel.set(spawn.label, matches);
44
+ }
45
+ const spawnsForSource = (worker) => {
46
+ if (worker.workerId === void 0) return spawnsByLabel.get(worker.label) ?? [];
47
+ const spawn = spawnById.get(worker.workerId);
48
+ return spawn === void 0 ? [] : [spawn];
49
+ };
50
+ const spawnForSource = (worker) => {
51
+ const matches = spawnsForSource(worker);
52
+ return matches.length === 1 ? matches[0] ?? null : null;
53
+ };
54
+ const wallSpanStart = startedAt ?? tree.firstEventAt;
55
+ const wallSpanEnd = tree.lastEventAt;
56
+ let supervisorWallMs;
57
+ let supervisorWallSource;
58
+ if (startedAt !== null && completedAt !== null && completedAt >= startedAt) {
59
+ supervisorWallMs = completedAt - startedAt;
60
+ supervisorWallSource = "stamps";
61
+ } else if (completedAt === null && wallSpanStart !== null && wallSpanEnd !== null && wallSpanEnd >= wallSpanStart) {
62
+ supervisorWallMs = wallSpanEnd - wallSpanStart;
63
+ supervisorWallSource = "journal-span";
64
+ } else {
65
+ const reason = !haveJournal ? journalMissing : "no parseable start/complete timestamps in state.json or journal";
66
+ supervisorWallMs = gap("supervisorWallMs", reason);
67
+ supervisorWallSource = unavailable(reason);
68
+ }
69
+ const wallEndAt = completedAt ?? (supervisorWallSource === "journal-span" ? wallSpanEnd : null);
70
+ const steerRows = [];
71
+ let steerQueuedTotal = 0;
72
+ let steerDeliveredTotal = 0;
73
+ let upLegMessages = 0;
74
+ if (src.workers !== null) for (const w of src.workers) {
75
+ const facts = tree.workerLogs.get(workerSourceKey(w));
76
+ const queued = facts?.steersQueued ?? null;
77
+ const delivered = facts?.steersDelivered ?? null;
78
+ upLegMessages += facts?.questions ?? 0;
79
+ if (queued !== null && delivered !== null) steerRows.push({
80
+ workerId: w.workerId ?? null,
81
+ worker: w.label,
82
+ queued,
83
+ delivered
84
+ });
85
+ if (queued !== null) steerQueuedTotal += queued;
86
+ if (delivered !== null) steerDeliveredTotal += delivered;
87
+ }
88
+ const workersGapReason = src.workersMissingReason ?? "workers/ directory absent";
89
+ const unavailableReasons = (pick) => {
90
+ const reasons = tree.workerLogRows.map((facts) => pick(facts)).filter((reason) => reason !== null);
91
+ return reasons.length === 0 ? null : `exact steer accounting unavailable for ${reasons.length} worker row(s): ${[...new Set(reasons)].join(" | ")}`;
92
+ };
93
+ const queuedGapReason = unavailableReasons((facts) => facts.steersQueuedUnavailable);
94
+ const deliveredGapReason = unavailableReasons((facts) => facts.steersDeliveredUnavailable);
95
+ const workerEventsGapReason = unavailableReasons((facts) => !facts.eventsCaptured ? "events absent" : facts.eventsInvalidRows > 0 ? "events contain malformed rows" : null);
96
+ const steers = src.workers === null ? gap("steers", workersGapReason) : queuedGapReason === null ? steerQueuedTotal : gap("steers", queuedGapReason);
97
+ const steersDelivered = src.workers === null ? unavailable(workersGapReason) : deliveredGapReason === null ? steerDeliveredTotal : unavailable(deliveredGapReason);
98
+ const steersByWorker = src.workers === null ? unavailable(workersGapReason) : queuedGapReason === null && deliveredGapReason === null ? steerRows : unavailable(queuedGapReason ?? deliveredGapReason ?? workersGapReason);
99
+ const driverSteerCalls = src.driverLog === null ? gap("driverSteerCalls", "driver.log absent") : Math.max(0, (src.driverLog.match(/supervisor_steer/g) ?? []).length - registrationMentions(src.driverLog));
100
+ const timeline = [];
101
+ for (const s of workerSpawns) if (s.at !== null) timeline.push({
102
+ at: s.at,
103
+ delta: 1
104
+ });
105
+ for (const c of workerCloses) if (c.at !== null) timeline.push({
106
+ at: c.at,
107
+ delta: -1
108
+ });
109
+ timeline.sort((a, b) => a.at - b.at || a.delta - b.delta);
110
+ let waves = 0;
111
+ const waveSizes = [];
112
+ let closedSinceWaveStart = true;
113
+ for (const step of timeline) if (step.delta === 1) {
114
+ if (closedSinceWaveStart) {
115
+ waves += 1;
116
+ waveSizes.push(0);
117
+ closedSinceWaveStart = false;
118
+ }
119
+ waveSizes[waveSizes.length - 1] = (waveSizes[waveSizes.length - 1] ?? 0) + 1;
120
+ } else closedSinceWaveStart = true;
121
+ let live = 0;
122
+ let maxConcurrency = 0;
123
+ let idleMs = 0;
124
+ let sumWorkerWallMs = 0;
125
+ let prev = startedAt;
126
+ for (const step of timeline) {
127
+ if (prev !== null && step.at >= prev) {
128
+ const span = step.at - prev;
129
+ if (live === 0) idleMs += span;
130
+ sumWorkerWallMs += span * live;
131
+ }
132
+ live += step.delta;
133
+ if (live > maxConcurrency) maxConcurrency = live;
134
+ prev = step.at;
135
+ }
136
+ if (prev !== null && wallEndAt !== null && wallEndAt >= prev) {
137
+ const span = wallEndAt - prev;
138
+ if (live === 0) idleMs += span;
139
+ sumWorkerWallMs += span * live;
140
+ }
141
+ const firstWorkerSpawnAt = workerSpawns.reduce((acc, s) => s.at === null ? acc : acc === null ? s.at : Math.min(acc, s.at), null);
142
+ const closeById = new Map(workerCloses.map((close) => [close.id, close]));
143
+ const childSpawnsByParent = /* @__PURE__ */ new Map();
144
+ for (const spawn of workerSpawns) {
145
+ if (spawn.parent === null) continue;
146
+ const siblings = childSpawnsByParent.get(spawn.parent) ?? [];
147
+ siblings.push(spawn);
148
+ childSpawnsByParent.set(spawn.parent, siblings);
149
+ }
150
+ let respawns = 0;
151
+ let observeThenRespawn = 0;
152
+ let respawnWithoutEvidence = 0;
153
+ const repeatedLabelSet = /* @__PURE__ */ new Set();
154
+ for (const siblings of childSpawnsByParent.values()) {
155
+ const labelCounts = /* @__PURE__ */ new Map();
156
+ for (const spawn of siblings) labelCounts.set(spawn.label, (labelCounts.get(spawn.label) ?? 0) + 1);
157
+ for (const [label, count] of labelCounts) if (count > 1) repeatedLabelSet.add(label);
158
+ const orderedSpawns = siblings.map((spawn, index) => ({
159
+ spawn,
160
+ index
161
+ })).filter((row) => row.spawn.at !== null).sort((a, b) => a.spawn.at - b.spawn.at || a.index - b.index);
162
+ const directCloseTimes = siblings.map((spawn) => closeById.get(spawn.id)?.at ?? null).filter((at) => at !== null).sort((a, b) => a - b);
163
+ const firstDirectClose = directCloseTimes[0] ?? null;
164
+ for (let i = 1; i < orderedSpawns.length; i += 1) {
165
+ const previous = orderedSpawns[i - 1]?.spawn.at;
166
+ const current = orderedSpawns[i]?.spawn.at;
167
+ if (previous === void 0 || current === void 0) continue;
168
+ if (firstDirectClose === null || current <= firstDirectClose) continue;
169
+ respawns += 1;
170
+ if (hasNumberBetween(directCloseTimes, previous, current)) observeThenRespawn += 1;
171
+ else respawnWithoutEvidence += 1;
172
+ }
173
+ }
174
+ const repeatedLabels = [...repeatedLabelSet];
175
+ const parentOf = new Map(tree.spawns.map((s) => [s.id, s.parent]));
176
+ let delegationDepth = 0;
177
+ for (const s of workerSpawns) {
178
+ let d = 0;
179
+ let cur = s.id;
180
+ const seen = /* @__PURE__ */ new Set();
181
+ while (cur !== null && cur !== rootId && !seen.has(cur)) {
182
+ seen.add(cur);
183
+ d += 1;
184
+ cur = parentOf.get(cur) ?? null;
185
+ }
186
+ if (d > delegationDepth) delegationDepth = d;
187
+ }
188
+ const orchestration = {
189
+ workersSpawned: haveJournal ? workerSpawns.length : gap("workersSpawned", journalMissing),
190
+ workersSettled: haveJournal ? workerCloses.filter((c) => c.kind === "settled").length : unavailable(journalMissing),
191
+ workersCancelled: haveJournal ? workerCloses.filter((c) => c.kind === "cancelled").length : unavailable(journalMissing),
192
+ steers,
193
+ steersDelivered,
194
+ steersByWorker,
195
+ driverSteerCalls,
196
+ waves: haveJournal ? waves : unavailable(journalMissing),
197
+ waveSizes: haveJournal ? waveSizes : unavailable(journalMissing),
198
+ maxConcurrency: haveJournal ? maxConcurrency : unavailable(journalMissing),
199
+ respawns: haveJournal ? respawns : unavailable(journalMissing),
200
+ repeatedLabels: haveJournal ? repeatedLabels : unavailable(journalMissing),
201
+ delegationDepth: haveJournal ? delegationDepth : unavailable(journalMissing),
202
+ timeToFirstSpawnMs: startedAt !== null && firstWorkerSpawnAt !== null ? firstWorkerSpawnAt - startedAt : haveJournal ? unavailable("no worker spawn timestamps") : unavailable(journalMissing),
203
+ supervisorWallMs,
204
+ supervisorWallSource,
205
+ idleMs: isUnavailable(supervisorWallMs) ? unavailable(supervisorWallMs.unavailable) : idleMs,
206
+ idlePct: isUnavailable(supervisorWallMs) || supervisorWallMs === 0 ? isUnavailable(supervisorWallMs) ? unavailable(supervisorWallMs.unavailable) : unavailable("supervisor wall is 0ms") : round(idleMs / supervisorWallMs * 100, 1),
207
+ workerUtilization: isUnavailable(supervisorWallMs) || supervisorWallMs === 0 ? isUnavailable(supervisorWallMs) ? unavailable(supervisorWallMs.unavailable) : unavailable("supervisor wall is 0ms") : round(sumWorkerWallMs / supervisorWallMs, 3)
208
+ };
209
+ const settledByStatus = {};
210
+ const settledVerdicts = {};
211
+ for (const c of workerCloses) {
212
+ const key = c.status ?? "unknown";
213
+ settledByStatus[key] = (settledByStatus[key] ?? 0) + 1;
214
+ if (c.verdict !== null) settledVerdicts[c.verdict] = (settledVerdicts[c.verdict] ?? 0) + 1;
215
+ }
216
+ const verdictLimit = src.limits.workerVerdicts;
217
+ const deliverablesLimit = src.limits.deliverables;
218
+ let accepted = 0;
219
+ let emptyPass = 0;
220
+ let evidenceBytes = 0;
221
+ const sourceVerdicts = [];
222
+ for (const w of src.workers ?? []) {
223
+ const f = tree.workerLogs.get(workerSourceKey(w));
224
+ if (f?.finished) evidenceBytes += f.evidenceBytes;
225
+ const spawn = spawnForSource(w);
226
+ const passed = (spawn === null ? null : closeById.get(spawn.id) ?? null)?.valid ?? f?.passed ?? null;
227
+ if (passed !== null) sourceVerdicts.push(passed);
228
+ if (passed === true) if (deliverablesLimit !== null || (w.patchBytes ?? f?.finishedPatchBytes ?? 0) > 0) accepted += 1;
229
+ else emptyPass += 1;
230
+ }
231
+ const settledCloses = workerCloses.filter((close) => close.kind === "settled");
232
+ const structuredVerdicts = settledCloses.map((close) => close.valid).filter((valid) => valid !== null);
233
+ const journalVerdictsComplete = settledCloses.length > 0 && structuredVerdicts.length === settledCloses.length;
234
+ const sourceVerdictsComplete = sourceVerdicts.length > 0 && sourceVerdicts.length >= settledCloses.length;
235
+ const rejected = journalVerdictsComplete ? structuredVerdicts.filter((valid) => !valid).length : sourceVerdictsComplete ? sourceVerdicts.filter((valid) => !valid).length : 0;
236
+ const rejectedLimit = journalVerdictsComplete || sourceVerdictsComplete ? null : settledCloses.length > 0 ? verdictLimit ?? "a settled journal verdict has no validity and no matched worker log" : verdictLimit !== null ? verdictLimit : !haveJournal && src.workers === null ? workersGapReason : null;
237
+ const decision = {
238
+ settledByStatus: haveJournal ? settledByStatus : gap("settledByStatus", journalMissing),
239
+ settledVerdicts: verdictLimit !== null ? unavailable(verdictLimit) : haveJournal ? settledVerdicts : unavailable(journalMissing),
240
+ accepted: verdictLimit !== null ? gap("accepted", verdictLimit) : src.workers === null ? unavailable(workersGapReason) : accepted,
241
+ rejected: rejectedLimit === null ? rejected : unavailable(rejectedLimit),
242
+ emptyPass: verdictLimit !== null ? gap("emptyPass", verdictLimit) : deliverablesLimit !== null ? gap("emptyPass", deliverablesLimit) : src.workers === null ? unavailable(workersGapReason) : emptyPass,
243
+ observeThenRespawn: haveJournal ? observeThenRespawn : unavailable(journalMissing),
244
+ respawnWithoutEvidence: haveJournal ? respawnWithoutEvidence : unavailable(journalMissing),
245
+ reviewActions: src.workers === null ? unavailable(workersGapReason) : queuedGapReason === null ? steerQueuedTotal + upLegMessages : unavailable(queuedGapReason),
246
+ workerEvidenceBytes: src.workers === null ? unavailable(workersGapReason) : workerEventsGapReason === null ? evidenceBytes : unavailable(workerEventsGapReason)
247
+ };
248
+ const rootChildIds = new Set(workerSpawns.filter((spawn) => spawn.parent === rootId).map((spawn) => spawn.id));
249
+ const rootChildCloses = workerCloses.filter((close) => rootChildIds.has(close.id));
250
+ const rootChildSpends = rootChildCloses.filter((close) => close.hasSpend);
251
+ const workerUsdUnknownNodes = rootChildSpends.filter((close) => !close.spend.usdKnown).map((close) => close.id);
252
+ const workerTokensUnknownNodes = rootChildSpends.filter((close) => !close.spend.tokensKnown).map((close) => close.id);
253
+ const workerTokenSpends = rootChildSpends.filter((close) => close.spend.tokensKnown);
254
+ const journalWorkerIn = workerTokenSpends.reduce((a, c) => a + c.spend.tokens.input, 0);
255
+ const journalWorkerOut = workerTokenSpends.reduce((a, c) => a + c.spend.tokens.output, 0);
256
+ const journalWorkerUsd = rootChildSpends.filter((close) => close.spend.usdKnown).reduce((a, c) => a + c.spend.usd, 0);
257
+ const usdUnknownIds = new Set(workerCloses.filter((close) => close.hasSpend && !close.spend.usdKnown).map((c) => c.id));
258
+ const workerUsdById = /* @__PURE__ */ new Map();
259
+ for (const c of workerCloses) {
260
+ if (usdUnknownIds.has(c.id)) continue;
261
+ workerUsdById.set(c.id, (workerUsdById.get(c.id) ?? 0) + c.spend.usd);
262
+ }
263
+ const labelById = new Map(workerSpawns.map((spawn) => [spawn.id, spawn.label]));
264
+ const usdUnknownLabels = new Set([...usdUnknownIds].map((id) => labelById.get(id)).filter((l) => l !== void 0));
265
+ const workerUsdByLabel = /* @__PURE__ */ new Map();
266
+ for (const close of workerCloses) {
267
+ const label = labelById.get(close.id);
268
+ if (label === void 0 || usdUnknownLabels.has(label)) continue;
269
+ workerUsdByLabel.set(label, (workerUsdByLabel.get(label) ?? 0) + close.spend.usd);
270
+ }
271
+ const brainUsdUnknownNodes = tree.brain.usdUnknownCount > 0 && rootId !== null ? [rootId] : [];
272
+ const brainTokensUnknownNodes = tree.brain.tokensUnknownCount > 0 && rootId !== null ? [rootId] : [];
273
+ const usdKnownRecords = tree.brain.usdKnownCount + rootChildSpends.filter((close) => close.spend.usdKnown).length;
274
+ const usdUnknownRecords = tree.brain.usdUnknownCount + workerUsdUnknownNodes.length;
275
+ const usdUnknownNodes = [...brainUsdUnknownNodes, ...workerUsdUnknownNodes];
276
+ const NAMED_NODE_LIMIT = 5;
277
+ const nameNodes = (nodes) => {
278
+ if (nodes.length === 0) return "";
279
+ const shown = nodes.slice(0, NAMED_NODE_LIMIT);
280
+ const rest = nodes.length - shown.length;
281
+ return ` (${shown.join(", ")}${rest === 0 ? "" : ` +${rest} more`})`;
282
+ };
283
+ const unpriced = (unknown, total, nodes) => `Runtime recorded usdKnown:false on ${unknown} of ${total} spend record(s)${nameNodes(nodes)}`;
284
+ const unreportedTokens = (unknown, total, nodes) => `Runtime recorded tokensKnown:false on ${unknown} of ${total} spend record(s)${nameNodes(nodes)}`;
285
+ const usdPartial = usdKnownRecords > 0 && usdUnknownRecords > 0;
286
+ const usdAllUnknown = usdKnownRecords === 0 && usdUnknownRecords > 0;
287
+ const brainTokensUnreported = tree.brain.tokensUnknownCount === 0 ? null : unreportedTokens(tree.brain.tokensUnknownCount, tree.brain.meteredCount, brainTokensUnknownNodes);
288
+ const brainUsdUnreported = tree.brain.usdKnownCount > 0 || tree.brain.usdUnknownCount === 0 ? null : unpriced(tree.brain.usdUnknownCount, tree.brain.meteredCount, brainUsdUnknownNodes);
289
+ const workerUsdUnreported = workerUsdUnknownNodes.length === 0 || workerUsdUnknownNodes.length < rootChildSpends.length ? null : unpriced(workerUsdUnknownNodes.length, rootChildSpends.length, workerUsdUnknownNodes);
290
+ const sq = src.harnessWorkerTokens;
291
+ const harnessGapReason = src.harnessMissingReason ?? "harness session store unavailable and journal settled spend is 0";
292
+ const workerTokenLimit = src.limits.workerTokens;
293
+ const workerTokensUnreported = workerTokensUnknownNodes.length === 0 ? null : unreportedTokens(workerTokensUnknownNodes.length, rootChildSpends.length, workerTokensUnknownNodes);
294
+ const workerIn = workerTokenLimit !== null ? gap("workers.tokensIn", workerTokenLimit) : workerTokensUnreported !== null ? gap("workers.tokensIn", workerTokensUnreported) : sq !== null ? journalWorkerIn + sq.input : haveJournal ? journalWorkerIn : gap("workers.tokensIn", harnessGapReason);
295
+ const workerOut = workerTokenLimit !== null ? unavailable(workerTokenLimit) : workerTokensUnreported !== null ? unavailable(workerTokensUnreported) : sq !== null ? journalWorkerOut + sq.output : haveJournal ? journalWorkerOut : unavailable(harnessGapReason);
296
+ const stateResult = asRecord(state?.result);
297
+ const stateUsd = typeof stateResult.spentUsd === "number" ? stateResult.spentUsd : null;
298
+ const resultSpentTotal = asRecord(result?.spentTotal);
299
+ const resultCloseUsd = typeof resultSpentTotal.usd === "number" && Number.isFinite(resultSpentTotal.usd) ? resultSpentTotal.usd : null;
300
+ const closeUsd = stateUsd ?? resultCloseUsd;
301
+ const usdLimit = src.limits.spendUsd;
302
+ const closeUsdUnreported = stateUsd === null && resultCloseUsd !== null && resultSpentTotal.usdKnown === false;
303
+ const usdUnreportedReason = unpriced(usdUnknownRecords, usdKnownRecords + usdUnknownRecords, usdUnknownNodes);
304
+ const totalUsd = usdLimit !== null ? gap("totalUsd", usdLimit) : stateUsd !== null ? round(stateUsd, 6) : !haveJournal ? gap("totalUsd", journalMissing) : usdUnknownRecords > 0 ? gap("totalUsd", usdUnreportedReason) : round(tree.brain.usd + journalWorkerUsd, 6);
305
+ const journalSpendRecords = usdKnownRecords;
306
+ const journalDerivedAvailable = usdLimit === null && haveJournal && !usdAllUnknown;
307
+ const closeRecordAvailable = usdLimit === null && closeUsd !== null && !closeUsdUnreported;
308
+ const spend = {
309
+ journalDerived: {
310
+ usd: journalDerivedAvailable ? round(tree.brain.usd + journalWorkerUsd, 6) : unavailable(usdLimit ?? (haveJournal ? usdUnreportedReason : journalMissing)),
311
+ records: journalDerivedAvailable ? journalSpendRecords : 0,
312
+ unknownRecords: usdLimit === null && haveJournal ? usdUnknownRecords : 0,
313
+ partial: journalDerivedAvailable && usdPartial,
314
+ unknownNodes: usdLimit === null && haveJournal ? usdUnknownNodes : []
315
+ },
316
+ closeRecord: {
317
+ usd: closeRecordAvailable ? round(closeUsd, 6) : unavailable(usdLimit ?? (closeUsdUnreported ? "close record incomplete: result.json spentTotal.usdKnown is false" : "no close record: neither state.json result.spentUsd nor result.json spentTotal.usd is present")),
318
+ records: closeRecordAvailable ? 1 : 0,
319
+ unknownRecords: closeUsdUnreported ? 1 : 0,
320
+ partial: false,
321
+ unknownNodes: closeUsdUnreported && rootId !== null ? [rootId] : []
322
+ }
323
+ };
324
+ const perWorker = (src.workers ?? []).map((w) => {
325
+ const f = tree.workerLogs.get(workerSourceKey(w));
326
+ const matchingSpawns = spawnsForSource(w);
327
+ const spawn = spawnForSource(w);
328
+ const close = spawn === null ? null : closeById.get(spawn.id) ?? null;
329
+ const passed = close?.valid ?? f?.passed ?? null;
330
+ const matchingRoles = new Set(matchingSpawns.map((candidate) => candidate.role));
331
+ const matchingRuntimes = new Set(matchingSpawns.map((candidate) => candidate.runtime));
332
+ const matchingProfiles = new Set(matchingSpawns.map((candidate) => candidate.profileDigest));
333
+ const journalWallMs = spawn?.at !== null && spawn?.at !== void 0 && close?.at !== null && close?.at !== void 0 && close.at >= spawn.at ? close.at - spawn.at : null;
334
+ return {
335
+ workerId: w.workerId ?? null,
336
+ worker: w.label,
337
+ role: matchingRoles.size === 1 ? matchingSpawns[0]?.role ?? null : null,
338
+ runtime: matchingRuntimes.size === 1 ? matchingSpawns[0]?.runtime ?? null : null,
339
+ profileDigest: matchingProfiles.size === 1 ? matchingSpawns[0]?.profileDigest ?? null : null,
340
+ status: close?.status ?? null,
341
+ failure: close?.reason ?? null,
342
+ infra: close?.infra ?? null,
343
+ wallMs: f?.started != null && f.finishedAt != null ? f.finishedAt - f.started : journalWallMs,
344
+ tokensIn: w.tokensIn ?? (close?.hasSpend === true && close.spend.tokensKnown ? close.spend.tokens.input : null),
345
+ tokensOut: w.tokensOut ?? (close?.hasSpend === true && close.spend.tokensKnown ? close.spend.tokens.output : null),
346
+ usd: usdLimit !== null ? null : w.workerId === void 0 ? workerUsdByLabel.get(w.label) ?? null : workerUsdById.get(w.workerId) ?? null,
347
+ patchBytes: w.patchBytes ?? f?.finishedPatchBytes ?? null,
348
+ passed,
349
+ score: close?.score ?? f?.score ?? null
350
+ };
351
+ });
352
+ const wallDistribution = summarizeNumberSeries(perWorker.map((w) => w.wallMs).filter((w) => w !== null));
353
+ const brainCalls = parseJsonl(src.brainLog);
354
+ const managerTokenLimit = src.limits.managerTokens;
355
+ const economics = {
356
+ brain: {
357
+ tokensIn: managerTokenLimit !== null ? gap("brain.tokensIn", managerTokenLimit) : !haveJournal ? gap("brain.tokensIn", journalMissing) : brainTokensUnreported !== null ? gap("brain.tokensIn", brainTokensUnreported) : tree.brain.tokensIn,
358
+ tokensOut: managerTokenLimit !== null ? unavailable(managerTokenLimit) : !haveJournal ? unavailable(journalMissing) : brainTokensUnreported !== null ? unavailable(brainTokensUnreported) : tree.brain.tokensOut,
359
+ usd: usdLimit !== null ? unavailable(usdLimit) : !haveJournal ? unavailable(journalMissing) : brainUsdUnreported !== null ? unavailable(brainUsdUnreported) : round(tree.brain.usd, 6),
360
+ cacheRead: managerTokenLimit !== null ? unavailable(managerTokenLimit) : !haveJournal ? unavailable(journalMissing) : brainTokensUnreported !== null ? unavailable(brainTokensUnreported) : !tree.brain.hasCache ? unavailable(NO_CACHE_COUNTERS) : tree.brain.cacheBreakdownKnown ? tree.brain.cacheRead : unavailable(NO_CACHE_BREAKDOWN),
361
+ cacheWrite: managerTokenLimit !== null ? unavailable(managerTokenLimit) : !haveJournal ? unavailable(journalMissing) : brainTokensUnreported !== null ? unavailable(brainTokensUnreported) : !tree.brain.hasCache ? unavailable(NO_CACHE_COUNTERS) : tree.brain.cacheBreakdownKnown ? tree.brain.cacheWrite : unavailable(NO_CACHE_BREAKDOWN),
362
+ source: managerTokenLimit ?? (haveJournal ? `journal metered events (n=${tree.brain.meteredCount})${tree.brain.usdKnownCount > 0 && tree.brain.usdUnknownCount > 0 ? ` — ${tree.brain.usdUnknownCount} unpriced` : ""}` : journalMissing)
363
+ },
364
+ brainTruncations: src.brainLog === null ? gap("brain.brainTruncations", src.brainLogMissingReason ?? (src.supRunDir === null ? "no supervisor run dir under <ws>/.agent/supervisor (or legacy <ws>/.loops/supervisor)" : "brain.jsonl absent — loops predates the brain-call tap, so truncation cannot be ruled out")) : brainCalls.filter((c) => c.finish_reason === "length").length,
365
+ workers: {
366
+ tokensIn: workerIn,
367
+ tokensOut: workerOut,
368
+ cacheRead: workerTokenLimit !== null ? unavailable(workerTokenLimit) : sq?.cacheRead !== void 0 ? sq.cacheRead : unavailable(NO_CACHE_COUNTERS),
369
+ cacheWrite: workerTokenLimit !== null ? unavailable(workerTokenLimit) : sq?.cacheWrite !== void 0 ? sq.cacheWrite : unavailable(NO_CACHE_COUNTERS),
370
+ usd: usdLimit !== null ? unavailable(usdLimit) : !haveJournal ? unavailable(journalMissing) : workerUsdUnreported !== null ? unavailable(workerUsdUnreported) : round(journalWorkerUsd, 6),
371
+ source: `${workerTokenLimit !== null ? workerTokenLimit : sq !== null ? `journal settled spend + ${sq.store} sessions (n=${sq.sessions})` : `journal settled spend only — ${src.harnessMissingReason ?? "harness session store unavailable"}`}${workerUsdUnknownNodes.length > 0 && workerUsdUnknownNodes.length < rootChildSpends.length ? ` — ${workerUsdUnknownNodes.length} unpriced` : ""}`
372
+ },
373
+ spend,
374
+ totalUsd,
375
+ totalUsdSource: usdLimit !== null ? usdLimit : stateUsd !== null ? `state.json result.spentUsd${rootChildCloses.length > 0 && journalWorkerUsd === 0 ? " — brain-priced only; worker CLI inference is unpriced (see worker token counts)" : ""}` : !haveJournal ? journalMissing : usdUnknownRecords > 0 ? usdUnreportedReason : "journal metered + settled usd",
376
+ costPerAcceptedPatchUsd: isUnavailable(totalUsd) ? unavailable(totalUsd.unavailable) : isUnavailable(decision.accepted) ? unavailable(decision.accepted.unavailable) : decision.accepted === 0 ? unavailable("no accepted worker patch (cost has no denominator)") : round(totalUsd / decision.accepted, 6),
377
+ workerWallMsDistribution: wallDistribution === null ? unavailable(src.workers === null ? workersGapReason : "no worker start/finish pairs captured") : wallDistribution,
378
+ perWorker: src.workers === null ? unavailable(workersGapReason) : perWorker
379
+ };
380
+ const patchStats = src.patch === null ? gap("patch", src.limits.deliverables ?? "delivered patch file absent") : parsePatch(src.patch);
381
+ const outcome = {
382
+ supStatus: isUnavailable(terminal.supStatus) ? gap("supStatus", terminal.supStatus.unavailable) : terminal.supStatus,
383
+ supStatusSource: terminal.supStatusSource,
384
+ supReason: terminal.supReason,
385
+ failure: terminal.failure,
386
+ supVerdict: pickString(state, "verdict") ?? pickString(result, "sup_verdict") ?? unavailable("no state.json / result.json verdict"),
387
+ delivered: typeof stateResult.delivered === "boolean" ? stateResult.delivered : typeof result?.delivered === "boolean" ? result.delivered : unavailable("no delivered flag in state.json or result.json"),
388
+ judgeResolved: judge === null ? gap("judge", "judge.json absent") : typeof judge.resolved === "boolean" ? judge.resolved : null,
389
+ judgeScore: judge === null ? unavailable("judge.json absent") : typeof judge.score === "number" ? judge.score : null,
390
+ judgePassed: judge === null ? unavailable("judge.json absent") : typeof judge.passed === "number" ? judge.passed : null,
391
+ judgeTotal: judge === null ? unavailable("judge.json absent") : typeof judge.total === "number" ? judge.total : null,
392
+ verifyPass: typeof result?.verify_pass === "boolean" ? result.verify_pass : gap("verifyPass", "result.json absent or has no verify_pass"),
393
+ verifyRc: typeof result?.verify_rc === "number" ? result.verify_rc : unavailable("result.json absent or has no verify_rc"),
394
+ patch: patchStats,
395
+ judgeSource: src.judgeSource
396
+ };
397
+ return {
398
+ schema: SUPERVISOR_RUN_SCHEMA,
399
+ runRef: src.runRef,
400
+ instanceId: src.instanceId,
401
+ arm: src.arm,
402
+ supervisorId: rootId !== null ? rootId : unavailable(journalMissing),
403
+ supervisorProfileDigest: rootSpawn?.profileDigest !== null && rootSpawn?.profileDigest !== void 0 ? rootSpawn.profileDigest : gap("supervisorProfileDigest", "root spawned event has no profile digest"),
404
+ generatedAt: new Date(now()).toISOString(),
405
+ orchestration,
406
+ decision,
407
+ economics,
408
+ outcome,
409
+ gaps,
410
+ traceCommand: src.traceCommand ?? "npx --yes @tangle-network/traces@latest analyze --harness opencode --cwd <worker-clone-cwd>"
411
+ };
412
+ }
413
+ /** Whether sorted values contain one value in the inclusive interval. */
414
+ function hasNumberBetween(sorted, low, high) {
415
+ let left = 0;
416
+ let right = sorted.length;
417
+ while (left < right) {
418
+ const middle = left + Math.floor((right - left) / 2);
419
+ if (sorted[middle] < low) left = middle + 1;
420
+ else right = middle;
421
+ }
422
+ return left < sorted.length && sorted[left] <= high;
423
+ }
424
+ /** `[driver] registered tools: …supervisor_steer…` is a banner, not an invocation. */
425
+ function registrationMentions(driverLog) {
426
+ let n = 0;
427
+ for (const line of driverLog.split("\n")) if (line.includes("registered tools:") && line.includes("supervisor_steer")) n += 1;
428
+ return n;
429
+ }
430
+ function pickString(rec, key) {
431
+ const v = rec?.[key];
432
+ return typeof v === "string" ? v : null;
433
+ }
434
+ function round(v, digits) {
435
+ const f = 10 ** digits;
436
+ return Math.round(v * f) / f;
437
+ }
438
+ /** Unified-diff stats. Counts `+++ b/<path>` targets, body +/- lines, and test-file touches. */
439
+ function parsePatch(text) {
440
+ const files = /* @__PURE__ */ new Set();
441
+ const testFiles = /* @__PURE__ */ new Set();
442
+ let added = 0;
443
+ let removed = 0;
444
+ for (const line of text.split("\n")) {
445
+ if (line.startsWith("+++ ")) {
446
+ const p = line.slice(4).trim().replace(/^b\//, "");
447
+ if (p !== "/dev/null") {
448
+ files.add(p);
449
+ if (isTestPath(p)) testFiles.add(p);
450
+ }
451
+ continue;
452
+ }
453
+ if (line.startsWith("--- ") || line.startsWith("diff --git") || line.startsWith("index ")) continue;
454
+ if (line.startsWith("+")) added += 1;
455
+ else if (line.startsWith("-")) removed += 1;
456
+ }
457
+ return {
458
+ files: files.size,
459
+ linesAdded: added,
460
+ linesRemoved: removed,
461
+ testFilesTouched: [...testFiles].sort()
462
+ };
463
+ }
464
+ function isTestPath(p) {
465
+ const base = p.split("/").pop() ?? p;
466
+ return /(^|\/)(tests?|__tests__|testing|spec)(\/|$)/.test(p) || /\.(test|spec)\.[cm]?[jt]sx?$/.test(base) || /^test_.*\.py$/.test(base) || /_test\.py$/.test(base);
467
+ }
468
+ /**
469
+ * Aggregate many supervisor-run reports. A metric no run could measure stays
470
+ * `unavailable` rather than becoming a 0-valued mean, and cells whose steer
471
+ * count was unavailable are counted separately from cells that measured zero.
472
+ */
473
+ function rollupSupervisorRuns(reports) {
474
+ const known = (vals) => vals.filter((v) => !isUnavailable(v));
475
+ const steerVals = known(reports.map((r) => r.orchestration.steers));
476
+ const waveVals = known(reports.map((r) => r.orchestration.waves));
477
+ const concVals = known(reports.map((r) => r.orchestration.maxConcurrency));
478
+ const utilVals = known(reports.map((r) => r.orchestration.workerUtilization));
479
+ const idleVals = known(reports.map((r) => r.orchestration.idlePct));
480
+ const spawnVals = known(reports.map((r) => r.orchestration.workersSpawned));
481
+ const acceptVals = known(reports.map((r) => r.decision.accepted));
482
+ const usdVals = known(reports.map((r) => r.economics.totalUsd));
483
+ const journalSpendVals = known(reports.map((r) => r.economics.spend.journalDerived.usd));
484
+ const closeSpendVals = known(reports.map((r) => r.economics.spend.closeRecord.usd));
485
+ const resolvedVals = known(reports.map((r) => r.outcome.judgeResolved));
486
+ const sum = (xs) => xs.reduce((a, b) => a + b, 0);
487
+ const mean = (xs) => xs.length === 0 ? unavailable("no cell reported this metric") : round(sum(xs) / xs.length, 3);
488
+ const perCell = reports.map((r) => ({
489
+ instanceId: r.instanceId,
490
+ arm: r.arm,
491
+ steers: r.orchestration.steers,
492
+ waves: r.orchestration.waves,
493
+ utilization: r.orchestration.workerUtilization,
494
+ idlePct: r.orchestration.idlePct,
495
+ resolved: r.outcome.judgeResolved,
496
+ usd: r.economics.totalUsd
497
+ }));
498
+ return {
499
+ schema: SUPERVISOR_RUN_ROLLUP_SCHEMA,
500
+ cells: reports.length,
501
+ steersTotal: steerVals.length === 0 ? unavailable("no cell reported a steer count") : sum(steerVals),
502
+ cellsWithSteers: steerVals.length === 0 ? unavailable("no cell reported a steer count") : steerVals.filter((n) => n > 0).length,
503
+ cellsWithUnavailableSteers: reports.filter((r) => isUnavailable(r.orchestration.steers)).length,
504
+ wavesMean: mean(waveVals),
505
+ maxConcurrencyMax: concVals.length === 0 ? unavailable("no cell reported concurrency") : Math.max(...concVals),
506
+ utilizationMean: mean(utilVals),
507
+ idlePctMean: mean(idleVals),
508
+ workersSpawnedTotal: spawnVals.length === 0 ? unavailable("no cell reported spawns") : sum(spawnVals),
509
+ acceptedTotal: acceptVals.length === 0 ? unavailable("no cell reported acceptance") : sum(acceptVals),
510
+ usdTotal: usdVals.length === 0 ? unavailable("no cell reported spend") : round(sum(usdVals), 6),
511
+ spendUsd: {
512
+ journalDerived: {
513
+ value: journalSpendVals.length === 0 ? unavailable("no cell measured journal-derived spend") : round(sum(journalSpendVals), 6),
514
+ runs: journalSpendVals.length
515
+ },
516
+ closeRecord: {
517
+ value: closeSpendVals.length === 0 ? unavailable("no cell carried a close record") : round(sum(closeSpendVals), 6),
518
+ runs: closeSpendVals.length
519
+ }
520
+ },
521
+ resolvedCount: resolvedVals.length === 0 ? unavailable("no cell reported a judge verdict") : resolvedVals.filter((v) => v === true).length,
522
+ perCell
523
+ };
524
+ }
525
+ //#endregion
526
+ //#region src/supervisor-run/render.ts
527
+ /**
528
+ * Human-readable renderings of a supervisor-run report. Zero and unavailable
529
+ * render differently on purpose (`0` vs `unavailable — <reason>`), because the
530
+ * two have driven opposite conclusions about the same architecture.
531
+ */
532
+ /**
533
+ * The block appended to a run log after every run — the answers an operator asks
534
+ * for, in the log tail, with no extra command.
535
+ */
536
+ function renderSupervisorRunHeadline(r) {
537
+ const o = r.orchestration;
538
+ const steerNote = isUnavailable(o.steers) ? `unavailable — ${o.steers.unavailable}` : o.steers === 0 ? "0 (spawn→wait→respawn only; no mid-task steering)" : `${o.steers} queued / ${showMeasured(o.steersDelivered)} delivered`;
539
+ return [
540
+ `RUN-REPORT ${r.instanceId ?? "?"} [${r.arm ?? "?"}]`,
541
+ ` status=${showMeasured(r.outcome.supStatus)} source=${showMeasured(r.outcome.supStatusSource)} reason=${showMeasured(r.outcome.supReason)} failure=${fmtFailure(r.outcome.failure)}`,
542
+ ` steers=${steerNote}`,
543
+ ` waves=${showMeasured(o.waves)} sizes=${isUnavailable(o.waveSizes) ? `unavailable — ${o.waveSizes.unavailable}` : `[${o.waveSizes.join(",")}]`} workers=${showMeasured(o.workersSpawned)} settled=${showMeasured(o.workersSettled)} cancelled=${showMeasured(o.workersCancelled)}`,
544
+ ` concurrency max=${showMeasured(o.maxConcurrency)} utilization=${showMeasured(o.workerUtilization)} idle=${fmtMs(o.idleMs)} (${showMeasured(o.idlePct)}%) wall=${fmtMs(o.supervisorWallMs)}${o.supervisorWallSource === "journal-span" ? " (journal-span lower bound)" : ""}`,
545
+ ` respawns=${showMeasured(o.respawns)} evidence→respawn=${showMeasured(r.decision.observeThenRespawn)} blind-respawn=${showMeasured(r.decision.respawnWithoutEvidence)} depth=${showMeasured(o.delegationDepth)}`,
546
+ ` accepted=${showMeasured(r.decision.accepted)} rejected=${showMeasured(r.decision.rejected)} empty-pass=${showMeasured(r.decision.emptyPass)}`,
547
+ ` brain=$${showMeasured(r.economics.brain.usd)} total=$${showMeasured(r.economics.totalUsd)} judge.resolved=${showMeasured(r.outcome.judgeResolved)} score=${showMeasured(r.outcome.judgeScore)} verify=${showMeasured(r.outcome.verifyPass)}`,
548
+ r.gaps.length > 0 ? ` gaps(${r.gaps.length}): ${r.gaps.join("; ")}` : " gaps: none"
549
+ ].join("\n");
550
+ }
551
+ /**
552
+ * The recorded error on one line; `none recorded` and `unavailable` stay
553
+ * distinct, and a throw the run outlived is labeled so it is not read as the
554
+ * outcome of the settled result beside it.
555
+ */
556
+ function fmtFailure(v) {
557
+ if (isUnavailable(v)) return `unavailable — ${v.unavailable}`;
558
+ if (v === null) return "none recorded";
559
+ const name = v.name ?? "unavailable — record has no error.name";
560
+ const message = v.message ?? "unavailable — record has no error.message";
561
+ const attempt = v.earlierAttempt ? ", earlier attempt" : "";
562
+ return `${name}: ${message} [${v.source}${v.at === null ? "" : ` at ${v.at}`}${attempt}]`;
563
+ }
564
+ function fmtMs(v) {
565
+ if (isUnavailable(v)) return `unavailable — ${v.unavailable}`;
566
+ if (v < 1e3) return `${v}ms`;
567
+ const s = v / 1e3;
568
+ if (s < 120) return `${round(s, 1)}s`;
569
+ return `${round(s / 60, 1)}min`;
570
+ }
571
+ function renderSupervisorRunMarkdown(r) {
572
+ const o = r.orchestration;
573
+ const d = r.decision;
574
+ const e = r.economics;
575
+ const out = [];
576
+ out.push(`# Run report — ${r.instanceId ?? "unknown instance"} [${r.arm ?? "unknown arm"}]`);
577
+ out.push("");
578
+ out.push("```");
579
+ out.push(renderSupervisorRunHeadline(r));
580
+ out.push("```");
581
+ out.push("");
582
+ out.push(`- Run: \`${r.runRef}\``);
583
+ out.push(`- Supervisor: \`${showMeasured(r.supervisorId)}\``);
584
+ out.push(`- Supervisor profile: \`${showMeasured(r.supervisorProfileDigest)}\``);
585
+ out.push(`- Generated: ${r.generatedAt}`);
586
+ out.push("");
587
+ out.push("## Orchestration");
588
+ out.push("");
589
+ out.push("| Metric | Value |");
590
+ out.push("|---|---|");
591
+ out.push(`| Workers spawned | ${showMeasured(o.workersSpawned)} |`);
592
+ out.push(`| Workers settled | ${showMeasured(o.workersSettled)} |`);
593
+ out.push(`| Workers cancelled | ${showMeasured(o.workersCancelled)} |`);
594
+ out.push(`| **Steers (mid-task messages to live workers)** | **${showMeasured(o.steers)}** |`);
595
+ out.push(`| Steers delivered | ${showMeasured(o.steersDelivered)} |`);
596
+ out.push(`| Outer-driver \`supervisor_steer\` calls | ${showMeasured(o.driverSteerCalls)} |`);
597
+ out.push(`| Spawn waves | ${showMeasured(o.waves)} |`);
598
+ out.push(`| Wave sizes | ${isUnavailable(o.waveSizes) ? showMeasured(o.waveSizes) : `[${o.waveSizes.join(", ")}]`} |`);
599
+ out.push(`| Max concurrency | ${showMeasured(o.maxConcurrency)} |`);
600
+ out.push(`| Respawns (after same parent's first settle) | ${showMeasured(o.respawns)} |`);
601
+ out.push(`| Repeated labels | ${isUnavailable(o.repeatedLabels) ? showMeasured(o.repeatedLabels) : o.repeatedLabels.length === 0 ? "none" : o.repeatedLabels.join(", ")} |`);
602
+ out.push(`| Delegation depth | ${showMeasured(o.delegationDepth)} |`);
603
+ out.push(`| Time to first spawn | ${fmtMs(o.timeToFirstSpawnMs)} |`);
604
+ out.push(`| Supervisor wall | ${fmtMs(o.supervisorWallMs)}${isUnavailable(o.supervisorWallSource) ? "" : ` (source: ${o.supervisorWallSource}${o.supervisorWallSource === "journal-span" ? ", lower bound" : ""})`} |`);
605
+ out.push(`| Idle (zero live workers) | ${fmtMs(o.idleMs)} (${showMeasured(o.idlePct)}%) |`);
606
+ out.push(`| Worker utilization (Σ worker wall ÷ supervisor wall) | ${showMeasured(o.workerUtilization)} |`);
607
+ out.push("");
608
+ if (!isUnavailable(o.steersByWorker) && o.steersByWorker.length > 0) {
609
+ out.push("### Steers per worker");
610
+ out.push("");
611
+ out.push("| Worker id | Label | Queued | Delivered |");
612
+ out.push("|---|---|---:|---:|");
613
+ for (const s of o.steersByWorker) out.push(`| ${s.workerId === null ? "unavailable — legacy label join" : `\`${s.workerId}\``} | \`${s.worker}\` | ${s.queued} | ${s.delivered} |`);
614
+ out.push("");
615
+ } else if (isUnavailable(o.steersByWorker)) out.push(`### Steers per worker\n\nunavailable — ${o.steersByWorker.unavailable}\n`);
616
+ out.push("## Decision quality");
617
+ out.push("");
618
+ out.push("| Metric | Value |");
619
+ out.push("|---|---|");
620
+ out.push(`| Settled by status | ${fmtCounts(d.settledByStatus)} |`);
621
+ out.push(`| Settled verdicts | ${fmtCounts(d.settledVerdicts)} |`);
622
+ out.push(`| Accepted (verify green + patch bytes) | ${showMeasured(d.accepted)} |`);
623
+ out.push(`| Rejected (verify red) | ${showMeasured(d.rejected)} |`);
624
+ out.push(`| Empty pass (green, no patch) | ${showMeasured(d.emptyPass)} |`);
625
+ out.push(`| Evidence → respawn sequences | ${showMeasured(d.observeThenRespawn)} |`);
626
+ out.push(`| Respawn with no same-parent settled evidence in front | ${showMeasured(d.respawnWithoutEvidence)} |`);
627
+ out.push(`| Review actions (steers + worker questions) | ${showMeasured(d.reviewActions)} |`);
628
+ out.push(`| Worker evidence returned | ${isUnavailable(d.workerEvidenceBytes) ? showMeasured(d.workerEvidenceBytes) : `${d.workerEvidenceBytes} bytes`} |`);
629
+ out.push("");
630
+ out.push("## Economics");
631
+ out.push("");
632
+ out.push("| Role | Tokens in | Tokens out | Cache read | Cache write | USD | Source |");
633
+ out.push("|---|---:|---:|---:|---:|---:|---|");
634
+ out.push(`| brain | ${showMeasured(e.brain.tokensIn)} | ${showMeasured(e.brain.tokensOut)} | ${showMeasured(e.brain.cacheRead)} | ${showMeasured(e.brain.cacheWrite)} | ${showMeasured(e.brain.usd)} | ${e.brain.source} |`);
635
+ out.push(`| workers | ${showMeasured(e.workers.tokensIn)} | ${showMeasured(e.workers.tokensOut)} | ${showMeasured(e.workers.cacheRead)} | ${showMeasured(e.workers.cacheWrite)} | ${showMeasured(e.workers.usd)} | ${e.workers.source} |`);
636
+ out.push("");
637
+ if (!isUnavailable(e.brainTruncations) && e.brainTruncations > 0) out.push(`- **BRAIN OUTPUT TRUNCATED: ${e.brainTruncations} completion(s) hit \`finish_reason: "length"\`** — the supervisor acted on a half-written plan. Its output ceiling is too low; see \`brain.jsonl\` for the per-call \`req_max_tokens\`.`);
638
+ else out.push(`- Brain completions truncated (finish_reason=length): ${showMeasured(e.brainTruncations)}`);
639
+ out.push(`- Total USD: ${showMeasured(e.totalUsd)} (source: ${e.totalUsdSource})`);
640
+ out.push(`- Spend measured two ways: journal-derived $${showMeasured(e.spend.journalDerived.usd)} over ${e.spend.journalDerived.records} journal record(s) (execution accounting) · close-record $${showMeasured(e.spend.closeRecord.usd)} over ${e.spend.closeRecord.records} close record(s) (billing-shaped). Divergence is a signal, not an error.`);
641
+ out.push(`- Cost per accepted patch: ${showMeasured(e.costPerAcceptedPatchUsd)}`);
642
+ if (isUnavailable(e.workerWallMsDistribution)) out.push(`- Worker wall distribution: unavailable — ${e.workerWallMsDistribution.unavailable}`);
643
+ else {
644
+ const w = e.workerWallMsDistribution;
645
+ out.push(`- Worker wall (n=${w.n}): min ${fmtMs(w.min)} / p50 ${fmtMs(w.p50)} / p90 ${fmtMs(w.p90)} / max ${fmtMs(w.max)} / Σ ${fmtMs(w.sum)}`);
646
+ }
647
+ out.push("");
648
+ if (!isUnavailable(e.perWorker) && e.perWorker.length > 0) {
649
+ out.push("| Worker id | Label | Role | Runtime | Profile digest | Status | Failure | Infra | Wall | Tokens in | Tokens out | Patch bytes | Verify passed | Score |");
650
+ out.push("|---|---|---|---|---|---|---|---|---:|---:|---:|---:|---|---:|");
651
+ for (const w of e.perWorker) out.push(`| ${w.workerId === null ? "unavailable — legacy label join" : `\`${w.workerId}\``} | \`${w.worker}\` | ${w.role ?? "unavailable — source recorded no role"} | ${w.runtime ?? "unavailable — source recorded no runtime"} | ${w.profileDigest === null ? "unavailable — source recorded no profile digest" : `\`${w.profileDigest}\``} | ${w.status ?? "unavailable — no terminal event"} | ${w.failure ?? "none recorded"} | ${w.infra ?? "unavailable"} | ${w.wallMs === null ? "unavailable — no spawn/finish pair" : fmtMs(w.wallMs)} | ${w.tokensIn ?? "unavailable — store does not attribute tokens per worker"} | ${w.tokensOut ?? "unavailable — store does not attribute tokens per worker"} | ${w.patchBytes ?? "unavailable — no worker patch file"} | ${w.passed === null ? "unavailable — no verdict" : String(w.passed)} | ${w.score ?? "unavailable — no numeric score"} |`);
652
+ out.push("");
653
+ }
654
+ out.push("## Outcome");
655
+ out.push("");
656
+ out.push("| Metric | Value |");
657
+ out.push("|---|---|");
658
+ out.push(`| Supervisor status | ${showMeasured(r.outcome.supStatus)} |`);
659
+ out.push(`| Status source | ${showMeasured(r.outcome.supStatusSource)} |`);
660
+ out.push(`| Terminal reason | ${showMeasured(r.outcome.supReason)} |`);
661
+ out.push(`| Failure | ${fmtFailure(r.outcome.failure)} |`);
662
+ out.push(`| Supervisor verdict | ${showMeasured(r.outcome.supVerdict)} |`);
663
+ out.push(`| Delivered | ${showMeasured(r.outcome.delivered)} |`);
664
+ out.push(`| Judge resolved | ${showMeasured(r.outcome.judgeResolved)} |`);
665
+ out.push(`| Judge score | ${showMeasured(r.outcome.judgeScore)} |`);
666
+ out.push(`| Judge passed / total | ${showMeasured(r.outcome.judgePassed)} / ${showMeasured(r.outcome.judgeTotal)} |`);
667
+ out.push(`| Judge source | ${r.outcome.judgeSource ?? "unavailable — no judge.json and no ledger row"} |`);
668
+ out.push(`| Verify gate | pass=${showMeasured(r.outcome.verifyPass)} rc=${showMeasured(r.outcome.verifyRc)} |`);
669
+ if (isUnavailable(r.outcome.patch)) out.push(`| Patch | unavailable — ${r.outcome.patch.unavailable} |`);
670
+ else {
671
+ const p = r.outcome.patch;
672
+ out.push(`| Patch | ${p.files} file(s), +${p.linesAdded}/-${p.linesRemoved}, test files touched: ${p.testFilesTouched.length === 0 ? "none" : p.testFilesTouched.join(", ")} |`);
673
+ }
674
+ out.push("");
675
+ out.push("## Gaps");
676
+ out.push("");
677
+ if (r.gaps.length === 0) out.push("None — every metric above is backed by a present artifact.");
678
+ else for (const g of r.gaps) out.push(`- ${g}`);
679
+ out.push("");
680
+ out.push(`> Harness-session view of the same run (model calls, stuck loops, tool errors): \`${r.traceCommand}\``);
681
+ out.push("");
682
+ return out.join("\n");
683
+ }
684
+ function fmtCounts(v) {
685
+ if (isUnavailable(v)) return showMeasured(v);
686
+ const entries = Object.entries(v);
687
+ return entries.length === 0 ? "none" : entries.map(([k, n]) => `${k}=${n}`).join(", ");
688
+ }
689
+ function renderSupervisorRollupMarkdown(rollup, title = "Round rollup") {
690
+ const out = [];
691
+ out.push(`# ${title}`);
692
+ out.push("");
693
+ out.push(`- Cells: ${rollup.cells}`);
694
+ out.push(`- **Steers across all cells: ${showMeasured(rollup.steersTotal)}** (cells with ≥1 steer: ${showMeasured(rollup.cellsWithSteers)}; cells where the steer count is unavailable: ${rollup.cellsWithUnavailableSteers})`);
695
+ out.push(`- Waves per cell (mean): ${showMeasured(rollup.wavesMean)}`);
696
+ out.push(`- Max concurrency observed: ${showMeasured(rollup.maxConcurrencyMax)}`);
697
+ out.push(`- Worker utilization (mean): ${showMeasured(rollup.utilizationMean)}`);
698
+ out.push(`- Idle share (mean): ${showMeasured(rollup.idlePctMean)}%`);
699
+ out.push(`- Workers spawned: ${showMeasured(rollup.workersSpawnedTotal)} · accepted: ${showMeasured(rollup.acceptedTotal)}`);
700
+ out.push(`- Spend: $${showMeasured(rollup.usdTotal)} · judged resolved: ${showMeasured(rollup.resolvedCount)}/${rollup.cells}`);
701
+ out.push(`- Spend measured two ways: journal-derived $${showMeasured(rollup.spendUsd.journalDerived.value)} over ${rollup.spendUsd.journalDerived.runs}/${rollup.cells} runs (execution accounting) · close-record $${showMeasured(rollup.spendUsd.closeRecord.value)} over ${rollup.spendUsd.closeRecord.runs}/${rollup.cells} runs (billing-shaped)`);
702
+ out.push("");
703
+ out.push("| Instance | Arm | Steers | Waves | Utilization | Idle % | Resolved | USD |");
704
+ out.push("|---|---|---:|---:|---:|---:|---|---:|");
705
+ for (const c of rollup.perCell) out.push(`| ${c.instanceId ?? "?"} | ${c.arm ?? "?"} | ${showMeasured(c.steers)} | ${showMeasured(c.waves)} | ${showMeasured(c.utilization)} | ${showMeasured(c.idlePct)} | ${showMeasured(c.resolved)} | ${showMeasured(c.usd)} |`);
706
+ out.push("");
707
+ return out.join("\n");
708
+ }
709
+ //#endregion
710
+ //#region src/supervisor-run/runtime-reader.ts
711
+ /**
712
+ * Reader for agent-runtime's file-backed supervision context.
713
+ *
714
+ * Runtime stores multiple recursive trees in one `spawn-journal.jsonl`.
715
+ * Each line is an envelope whose `root` identifies the local tree. A journal
716
+ * can connect a nested tree with `spawned.ownedTreeRoot`. It can also use the
717
+ * spawned child id as the nested root and repeat the spawn as a parentless
718
+ * marker when no owned tree is recorded. Descendant spawns must occur in the
719
+ * tree their parent owns. This reader removes a duplicate marker and preserves
720
+ * the other envelopes for the supervisor-run analyzer. Runtime stores profile
721
+ * identity below `identity` and does not emit Eval's role field. This boundary
722
+ * projects those fields without changing Runtime's dialect.
723
+ *
724
+ * The run's terminal record is Runtime's own: `result.json` is the
725
+ * `SupervisedResult` that `supervise()` returned, verbatim, and its `kind`
726
+ * (`winner`, `no-winner`, or whatever a later arm is called) is the status.
727
+ * `failure.json` (`{ runId, pursuitId, at, error: { name, message } }`) is the
728
+ * record Runtime writes when `supervise()` threw before a result landed. Both
729
+ * pass through as bytes; `terminal-record.ts` reads them. The reader does not
730
+ * decide which kinds count: a kind it refuses is a run that recorded its
731
+ * outcome and got reported as having none. Nothing here manufactures a loops
732
+ * `state.json` status from Runtime's documents: the analyzer names the record
733
+ * a status came from, and a synthetic legacy document would mislabel it.
734
+ *
735
+ * `usdKnown: false` / `tokensKnown: false` on ONE record is not a limit of this
736
+ * store. The store recorded every other record completely, so the flags travel
737
+ * through to the analyzer per record, which reports the measured nodes and
738
+ * names the unreported ones.
739
+ */
740
+ const JOURNAL_FILE = "spawn-journal.jsonl";
741
+ const OBSERVER_FILE = "observer.jsonl";
742
+ const RESULT_FILE = "result.json";
743
+ const FAILURE_FILE = "failure.json";
744
+ /**
745
+ * The files only Runtime's durable layer writes. Any one of them marks a
746
+ * Runtime run directory: `supervise()` opens the spawn journal on its first
747
+ * event, and `supervisePursuit` appends the observer's `before` event and, on
748
+ * a throw, the failure record before any spawn.
749
+ *
750
+ * Measured motive (discovery-lab recursive smoke r1, 2026-09-06): the first
751
+ * attempt threw on a caller input error 16 minutes before the corrected attempt
752
+ * opened the spawn journal. At that instant the directory held `observer.jsonl`
753
+ * (2 records) and the failure record and no journal, so a journal-only test
754
+ * routed it to the loops reader, which reads neither file, and the recorded
755
+ * throw was reported as no run at all.
756
+ */
757
+ const RUNTIME_RUN_DIR_MARKERS = [
758
+ JOURNAL_FILE,
759
+ OBSERVER_FILE,
760
+ FAILURE_FILE
761
+ ];
762
+ async function readMaybe$1(path) {
763
+ return readFile(path, "utf8").catch((error) => {
764
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") return null;
765
+ throw error;
766
+ });
767
+ }
768
+ function record(value) {
769
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
770
+ }
771
+ function nonEmptyString(value) {
772
+ return typeof value === "string" && value.length > 0 ? value : null;
773
+ }
774
+ function profileDigest(event) {
775
+ const direct = nonEmptyString(event.profileDigest);
776
+ if (direct !== null) return direct;
777
+ const identity = record(event.identity);
778
+ return identity === null ? null : nonEmptyString(identity.profileDigest);
779
+ }
780
+ function formatError(path, line, detail) {
781
+ return /* @__PURE__ */ new Error(`${path}:${line}: invalid Runtime spawn journal: ${detail}`);
782
+ }
783
+ function parseEnvelopeJournal(text, path) {
784
+ const begins = [];
785
+ const events = [];
786
+ const begun = /* @__PURE__ */ new Map();
787
+ for (const [index, sourceLine] of text.split("\n").entries()) {
788
+ const line = index + 1;
789
+ const trimmed = sourceLine.trim();
790
+ if (trimmed.length === 0) continue;
791
+ let parsed;
792
+ try {
793
+ parsed = JSON.parse(trimmed);
794
+ } catch {
795
+ throw formatError(path, line, "line is not JSON");
796
+ }
797
+ const envelope = record(parsed);
798
+ if (envelope === null) throw formatError(path, line, "line is not an object");
799
+ const kind = nonEmptyString(envelope.kind);
800
+ const root = nonEmptyString(envelope.root);
801
+ if (root === null) throw formatError(path, line, "root must be a non-empty string");
802
+ if (kind === "begin") {
803
+ const at = nonEmptyString(envelope.at);
804
+ if (at === null || !Number.isFinite(Date.parse(at))) throw formatError(path, line, "begin.at must be an ISO timestamp");
805
+ if (begun.has(root)) throw formatError(path, line, `tree ${JSON.stringify(root)} began twice`);
806
+ const begin = {
807
+ root,
808
+ at,
809
+ line
810
+ };
811
+ begun.set(root, begin);
812
+ begins.push(begin);
813
+ continue;
814
+ }
815
+ if (kind !== "event") throw formatError(path, line, "kind must be 'begin' or 'event'");
816
+ if (!begun.has(root)) throw formatError(path, line, `event for tree ${JSON.stringify(root)} precedes begin`);
817
+ const event = record(envelope.event);
818
+ if (event === null) throw formatError(path, line, "event must be an object");
819
+ if (nonEmptyString(event.kind) === null) throw formatError(path, line, "event.kind must be a non-empty string");
820
+ events.push({
821
+ root,
822
+ event: { ...event },
823
+ line
824
+ });
825
+ }
826
+ if (begins.length === 0) throw formatError(path, 1, "no begin record");
827
+ const parentSpawnsById = /* @__PURE__ */ new Map();
828
+ const parentSpawnsByOwnedTreeRoot = /* @__PURE__ */ new Map();
829
+ const rootMarkersByTree = /* @__PURE__ */ new Map();
830
+ for (const entry of events) {
831
+ if (entry.event.kind !== "spawned") continue;
832
+ const id = nonEmptyString(entry.event.id);
833
+ if (id === null) continue;
834
+ if (nonEmptyString(entry.event.parent) !== null) {
835
+ const ownedTreeRoot = nonEmptyString(entry.event.ownedTreeRoot);
836
+ if (ownedTreeRoot !== null) {
837
+ const owners = parentSpawnsByOwnedTreeRoot.get(ownedTreeRoot) ?? [];
838
+ owners.push(entry);
839
+ parentSpawnsByOwnedTreeRoot.set(ownedTreeRoot, owners);
840
+ } else {
841
+ const matches = parentSpawnsById.get(id) ?? [];
842
+ matches.push(entry);
843
+ parentSpawnsById.set(id, matches);
844
+ }
845
+ }
846
+ if (entry.root === id && (entry.event.parent === void 0 || entry.event.parent === null)) {
847
+ const markers = rootMarkersByTree.get(entry.root) ?? [];
848
+ markers.push(entry);
849
+ rootMarkersByTree.set(entry.root, markers);
850
+ }
851
+ }
852
+ const nestedRoots = /* @__PURE__ */ new Set();
853
+ const nestedParentSpawns = /* @__PURE__ */ new Map();
854
+ for (const begin of begins) {
855
+ const parentSpawns = [.../* @__PURE__ */ new Set([...parentSpawnsByOwnedTreeRoot.get(begin.root) ?? [], ...parentSpawnsById.get(begin.root) ?? []])].filter((entry) => entry.root !== begin.root);
856
+ if (parentSpawns.length > 1) throw formatError(path, begin.line, `tree ${JSON.stringify(begin.root)} has ${parentSpawns.length} parent spawns`);
857
+ if (parentSpawns.length === 1) {
858
+ nestedRoots.add(begin.root);
859
+ nestedParentSpawns.set(begin.root, parentSpawns[0]);
860
+ }
861
+ }
862
+ const topRoots = begins.filter((begin) => !nestedRoots.has(begin.root));
863
+ if (topRoots.length !== 1) throw formatError(path, topRoots[0]?.line ?? 1, `expected one top-level tree, found ${topRoots.length}`);
864
+ const top = topRoots[0];
865
+ for (const nestedRoot of nestedRoots) {
866
+ const markers = rootMarkersByTree.get(nestedRoot) ?? [];
867
+ if (markers.length > 1) throw formatError(path, begun.get(nestedRoot)?.line ?? 1, `nested tree ${JSON.stringify(nestedRoot)} contains ${markers.length} root markers`);
868
+ const parentSpawn = nestedParentSpawns.get(nestedRoot);
869
+ if (parentSpawn === void 0) throw formatError(path, begun.get(nestedRoot)?.line ?? 1, `nested tree ${JSON.stringify(nestedRoot)} has no parent spawn`);
870
+ const markerDigest = profileDigest(markers[0]?.event ?? {});
871
+ const parentDigest = profileDigest(parentSpawn.event);
872
+ if (markerDigest !== null && parentDigest !== null && markerDigest !== parentDigest) throw formatError(path, markers[0]?.line ?? 1, `nested tree ${JSON.stringify(nestedRoot)} disagrees with its parent profile digest`);
873
+ if (parentDigest === null && markerDigest !== null) parentSpawn.event.profileDigest = markerDigest;
874
+ }
875
+ const ownedTreeBySupervisorId = /* @__PURE__ */ new Map([[top.root, top.root]]);
876
+ for (const [nestedRoot, parentSpawn] of nestedParentSpawns) {
877
+ const supervisorId = nonEmptyString(parentSpawn.event.id);
878
+ if (supervisorId === null) continue;
879
+ const priorTree = ownedTreeBySupervisorId.get(supervisorId);
880
+ if (priorTree !== void 0 && priorTree !== nestedRoot) throw formatError(path, parentSpawn.line, `spawn ${JSON.stringify(supervisorId)} owns both ${JSON.stringify(priorTree)} and ${JSON.stringify(nestedRoot)}`);
881
+ ownedTreeBySupervisorId.set(supervisorId, nestedRoot);
882
+ }
883
+ for (const entry of events) {
884
+ if (entry.event.kind !== "spawned") continue;
885
+ const parentId = nonEmptyString(entry.event.parent);
886
+ if (parentId === null) continue;
887
+ const parentTree = ownedTreeBySupervisorId.get(parentId);
888
+ if (parentTree === void 0) throw formatError(path, entry.line, `spawn ${JSON.stringify(entry.event.id)} names parent ${JSON.stringify(parentId)}, which owns no journal tree`);
889
+ if (entry.root !== parentTree) throw formatError(path, entry.line, `spawn ${JSON.stringify(entry.event.id)} is in tree ${JSON.stringify(entry.root)}, but parent ${JSON.stringify(parentId)} owns tree ${JSON.stringify(parentTree)}`);
890
+ }
891
+ const supervisorIds = /* @__PURE__ */ new Set([top.root, ...[...nestedParentSpawns.values()].map((entry) => nonEmptyString(entry.event.id)).filter((id) => id !== null)]);
892
+ const normalized = events.filter((entry) => !(nestedRoots.has(entry.root) && entry.event.kind === "spawned" && entry.event.id === entry.root && (entry.event.parent === void 0 || entry.event.parent === null))).map((entry) => {
893
+ const event = { ...entry.event };
894
+ if (event.kind === "spawned") {
895
+ const digest = profileDigest(event);
896
+ if (event.profileDigest === void 0 && digest !== null) event.profileDigest = digest;
897
+ if (event.role === void 0) event.role = supervisorIds.has(nonEmptyString(event.id) ?? "") ? "supervisor" : "worker";
898
+ }
899
+ return {
900
+ root: entry.root,
901
+ event
902
+ };
903
+ });
904
+ if ((rootMarkersByTree.get(top.root) ?? []).length !== 1) throw formatError(path, top.line, `top-level tree ${JSON.stringify(top.root)} must contain one root marker`);
905
+ return {
906
+ root: top.root,
907
+ startedAt: top.at,
908
+ journal: `${normalized.map((entry) => JSON.stringify({
909
+ kind: "event",
910
+ root: entry.root,
911
+ event: entry.event
912
+ })).join("\n")}\n`,
913
+ events: normalized.map((entry) => entry.event)
914
+ };
915
+ }
916
+ function parseOptionalRecord(text, path) {
917
+ if (text === null) return null;
918
+ let parsed;
919
+ try {
920
+ parsed = JSON.parse(text);
921
+ } catch {
922
+ throw new Error(`${path}: invalid JSON`);
923
+ }
924
+ const value = record(parsed);
925
+ if (value === null) throw new Error(`${path}: expected a JSON object`);
926
+ return value;
927
+ }
928
+ function spendRecord(value) {
929
+ const spend = record(value);
930
+ if (spend === null) return null;
931
+ const tokens = record(spend.tokens);
932
+ if (tokens === null || typeof tokens.input !== "number" || !Number.isFinite(tokens.input) || tokens.input < 0 || typeof tokens.output !== "number" || !Number.isFinite(tokens.output) || tokens.output < 0 || typeof spend.usd !== "number" || !Number.isFinite(spend.usd) || spend.usd < 0 || spend.usdKnown !== void 0 && typeof spend.usdKnown !== "boolean") return null;
933
+ return spend;
934
+ }
935
+ function sourceLimits(root, events, workerIds) {
936
+ const rootMeters = events.filter((event) => event.kind === "metered" && event.id === root);
937
+ const invalidRootMeters = rootMeters.filter((event) => spendRecord(event.spend) === null);
938
+ const rootMeterReason = rootMeters.length === 0 ? "Runtime journal has no root metered event" : invalidRootMeters.length > 0 ? `${invalidRootMeters.length} root metered event(s) lack complete spend` : null;
939
+ const closes = events.filter((event) => workerIds.has(nonEmptyString(event.id) ?? "") && (event.kind === "settled" || event.kind === "cancelled"));
940
+ const settledById = /* @__PURE__ */ new Map();
941
+ for (const event of closes) {
942
+ const id = nonEmptyString(event.id);
943
+ if (id === null) continue;
944
+ const matches = settledById.get(id) ?? [];
945
+ matches.push(event);
946
+ settledById.set(id, matches);
947
+ }
948
+ const incompleteWorkers = [...workerIds].filter((id) => {
949
+ const terminal = settledById.get(id);
950
+ return terminal?.length !== 1 || terminal[0]?.kind !== "settled" || spendRecord(terminal[0]?.spent) === null;
951
+ });
952
+ const missingVerdicts = [...workerIds].filter((id) => {
953
+ const terminal = settledById.get(id)?.[0];
954
+ if (terminal?.kind !== "settled") return true;
955
+ return typeof record(terminal.verdict)?.valid !== "boolean";
956
+ });
957
+ return {
958
+ managerTokens: rootMeterReason,
959
+ workerTokens: incompleteWorkers.length === 0 ? null : `${incompleteWorkers.length}/${workerIds.size} child invocation(s) lack one settled spend record`,
960
+ spendUsd: rootMeterReason !== null ? rootMeterReason : incompleteWorkers.length > 0 ? "at least one child invocation lacks a settled spend record" : null,
961
+ workerVerdicts: missingVerdicts.length === 0 ? null : `${missingVerdicts.length}/${workerIds.size} child invocation(s) lack a structured validity verdict`,
962
+ deliverables: workerIds.size === 0 ? null : "Runtime FileRunContext does not retain per-child delivered patches"
963
+ };
964
+ }
965
+ /**
966
+ * Refuse a `result.json` that belongs to another run. Runtime's result carries
967
+ * its own `tree.root`; a settled record with a different root is a copied or
968
+ * misplaced file, and reading its status onto this journal misreports the run.
969
+ */
970
+ function assertResultMatchesJournal(root, result, resultPath) {
971
+ const resultKind = result === null ? null : nonEmptyString(result.kind);
972
+ if (resultKind === null || result === null) return;
973
+ const resultRoot = nonEmptyString(record(result.tree)?.root);
974
+ if (resultRoot === null) throw new Error(`${resultPath}: Runtime ${resultKind} result has no tree.root`);
975
+ if (resultRoot !== root) throw new Error(`${resultPath}: root ${JSON.stringify(resultRoot)} does not match journal root ${JSON.stringify(root)}`);
976
+ }
977
+ /**
978
+ * Refuse a `failure.json` that is not a failure record. Runtime writes
979
+ * `{ runId, pursuitId, at, error: { name, message } }`; a document without an
980
+ * `error` object is not one, and reading it as a failure invents a terminal
981
+ * state the run never recorded. The record carries no tree root, so its
982
+ * identity is not checked against the journal.
983
+ */
984
+ function assertFailureRecord(failure, failurePath) {
985
+ if (failure === null) return;
986
+ if (record(failure.error) === null) throw new Error(`${failurePath}: Runtime failure record has no error object`);
987
+ }
988
+ /**
989
+ * The journal's begin stamp in the analyzer's state-document shape. It carries
990
+ * the run identity and start instant only; the terminal status lives in
991
+ * Runtime's own `result.json` / `failure.json`, which the analyzer reads by
992
+ * name, so no legacy `status` field is fabricated here.
993
+ */
994
+ function runtimeBeginState(root, startedAt) {
995
+ return JSON.stringify({
996
+ id: root,
997
+ startedAt
998
+ });
999
+ }
1000
+ /**
1001
+ * Sources for a run dir whose spawn journal does not exist. Mirrors the
1002
+ * absent shape `readLoopsSupervisorRun` returns for a missing store: every
1003
+ * journal-dependent metric downstream reads `unavailable`, never 0.
1004
+ */
1005
+ /**
1006
+ * The run identity a terminal record names when no journal exists yet: the
1007
+ * settle record's `tree.root`, else the failure record's `runId`. Both are
1008
+ * Runtime's own `runId`, so the report names the run the record is about
1009
+ * instead of `?`.
1010
+ */
1011
+ function recordedRunId(result, failure) {
1012
+ return nonEmptyString(record(result?.tree)?.root) ?? nonEmptyString(failure?.runId);
1013
+ }
1014
+ function absentRuntimeSupervisorRun(runDir, resultText, failureText, instanceId) {
1015
+ const reason = `no Runtime spawn journal (${JOURNAL_FILE}) under ${runDir}`;
1016
+ return {
1017
+ runRef: runDir,
1018
+ instanceId,
1019
+ arm: null,
1020
+ supRunDir: null,
1021
+ journal: null,
1022
+ journalMissingReason: reason,
1023
+ brainLog: null,
1024
+ brainLogMissingReason: "Runtime FileRunContext records spend but not model completion finish reasons",
1025
+ state: null,
1026
+ progress: null,
1027
+ workers: null,
1028
+ workersMissingReason: reason,
1029
+ result: resultText,
1030
+ failure: failureText,
1031
+ judge: null,
1032
+ judgeSource: null,
1033
+ patch: null,
1034
+ driverLog: null,
1035
+ harnessWorkerTokens: null,
1036
+ harnessMissingReason: "Runtime FileRunContext has no external worker-token join",
1037
+ limits: NO_SOURCE_LIMITS,
1038
+ rootTranscriptRef: null,
1039
+ traceCommand: "unavailable — Runtime FileRunContext records no provider-session trace identity"
1040
+ };
1041
+ }
1042
+ /**
1043
+ * Read one agent-runtime `createFileRunContext(dir)` directory.
1044
+ *
1045
+ * A run dir without `spawn-journal.jsonl` returns the same absent-shaped
1046
+ * sources `readLoopsSupervisorRun` returns for a missing store: `journal` and
1047
+ * `workers` null, each with its reason, so every dependent metric reads
1048
+ * `unavailable` — never 0 and never a throw. Its `result.json` and
1049
+ * `failure.json` are still read, so a run that died before its first spawn
1050
+ * reports the failure Runtime recorded. Pass `strict: true` to throw on the
1051
+ * missing journal instead. A journal, result, or failure document that exists
1052
+ * but cannot be parsed always throws: a corrupt record is a defect, not an
1053
+ * absence.
1054
+ *
1055
+ * The reader translates storage envelopes only. It does not assign research
1056
+ * roles, interpret artifacts, or turn process completion into a quality
1057
+ * verdict.
1058
+ */
1059
+ async function readRuntimeSupervisorRun(runDir, opts = {}) {
1060
+ const journalPath = join(runDir, JOURNAL_FILE);
1061
+ const rawJournal = opts.strict === true ? await readFile(journalPath, "utf8") : await readMaybe$1(journalPath);
1062
+ const resultPath = join(runDir, RESULT_FILE);
1063
+ const failurePath = join(runDir, FAILURE_FILE);
1064
+ const resultText = await readMaybe$1(resultPath);
1065
+ const failureText = await readMaybe$1(failurePath);
1066
+ const failure = parseOptionalRecord(failureText, failurePath);
1067
+ assertFailureRecord(failure, failurePath);
1068
+ const result = parseOptionalRecord(resultText, resultPath);
1069
+ if (rawJournal === null) return absentRuntimeSupervisorRun(runDir, resultText, failureText, recordedRunId(result, failure));
1070
+ const normalized = parseEnvelopeJournal(rawJournal, journalPath);
1071
+ assertResultMatchesJournal(normalized.root, result, resultPath);
1072
+ const childSpawns = normalized.events.filter((event) => event.kind === "spawned" && nonEmptyString(event.id) !== null).filter((event) => event.id !== normalized.root);
1073
+ const workerIds = new Set(childSpawns.map((event) => nonEmptyString(event.id)).filter((id) => id !== null));
1074
+ const workers = childSpawns.map((event) => ({
1075
+ workerId: nonEmptyString(event.id),
1076
+ label: nonEmptyString(event.label) ?? String(event.id),
1077
+ events: null,
1078
+ inbox: null,
1079
+ patchBytes: null,
1080
+ transcriptRef: null,
1081
+ patchPath: null
1082
+ }));
1083
+ return {
1084
+ runRef: runDir,
1085
+ instanceId: normalized.root,
1086
+ arm: null,
1087
+ supRunDir: runDir,
1088
+ journal: normalized.journal,
1089
+ brainLog: null,
1090
+ brainLogMissingReason: "Runtime FileRunContext records spend but not model completion finish reasons",
1091
+ state: runtimeBeginState(normalized.root, normalized.startedAt),
1092
+ progress: null,
1093
+ workers,
1094
+ workersMissingReason: null,
1095
+ result: resultText,
1096
+ failure: failureText,
1097
+ judge: null,
1098
+ judgeSource: null,
1099
+ patch: null,
1100
+ driverLog: null,
1101
+ harnessWorkerTokens: null,
1102
+ harnessMissingReason: "Runtime FileRunContext has no external worker-token join",
1103
+ limits: sourceLimits(normalized.root, normalized.events, workerIds),
1104
+ rootTranscriptRef: null,
1105
+ traceCommand: "unavailable — Runtime FileRunContext records no provider-session trace identity"
1106
+ };
1107
+ }
1108
+ /** The agent-runtime file-backed layout as a `SupervisorRunReader`. */
1109
+ function runtimeSupervisorRunReader(runDir, opts = {}) {
1110
+ return {
1111
+ runRef: runDir,
1112
+ read: () => readRuntimeSupervisorRun(runDir, opts)
1113
+ };
1114
+ }
1115
+ /**
1116
+ * True when a directory holds any file only Runtime's durable layer writes:
1117
+ * the spawn journal, the observer journal, or the failure record. A run that
1118
+ * threw before its first spawn has no journal yet and is still a Runtime run.
1119
+ */
1120
+ async function isRuntimeSupervisorRunDir(runDir) {
1121
+ for (const marker of RUNTIME_RUN_DIR_MARKERS) if (await isFile(join(runDir, marker))) return true;
1122
+ return false;
1123
+ }
1124
+ async function isFile(path) {
1125
+ return stat(path).then((entry) => entry.isFile()).catch((error) => {
1126
+ if (typeof error === "object" && error !== null && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR")) return false;
1127
+ throw error;
1128
+ });
1129
+ }
1130
+ //#endregion
1131
+ //#region src/supervisor-run/loops-reader.ts
1132
+ /**
1133
+ * ONE implementation of `SupervisorRunReader`: the on-disk layout the loops
1134
+ * supervisor writes — `<runDir>/ws/.agent/supervisor/<id>/{journal.jsonl,
1135
+ * state.json, progress.ndjson, workers/*.ndjson}` alongside the run's
1136
+ * `result.json` / `judge.json` / `driver.log` / delivered patch. Runs written
1137
+ * before the `.agent` rename live under `<ws>/.loops/supervisor/<id>` and are
1138
+ * still found via fallback.
1139
+ *
1140
+ * Nothing in `analyze.ts` knows this layout exists. A different store (an
1141
+ * archive, an object bucket, a database) implements the same interface and
1142
+ * gets the same report.
1143
+ *
1144
+ * Worker token recovery reuses the rollout module's opencode reader rather
1145
+ * than opening a second sqlite path — one store client, one corruption policy.
1146
+ */
1147
+ async function readMaybe(path) {
1148
+ return readFile(path, "utf8").catch(() => null);
1149
+ }
1150
+ /**
1151
+ * Locate the (single) supervisor run dir under `<ws>/.agent/supervisor`, falling back to the
1152
+ * pre-rename `<ws>/.loops/supervisor` so runs written by older supervisors stay analyzable.
1153
+ */
1154
+ async function findSupervisorRunDirIn(ws) {
1155
+ for (const stateDir of [".agent", ".loops"]) {
1156
+ const root = join(ws, stateDir, "supervisor");
1157
+ const dirs = (await readdir(root, { withFileTypes: true }).catch(() => [])).filter((e) => e.isDirectory()).map((e) => join(root, e.name));
1158
+ if (dirs[0] !== void 0) return dirs[0];
1159
+ }
1160
+ return null;
1161
+ }
1162
+ /**
1163
+ * Read a loops supervisor run directory into source bytes. Never throws on a
1164
+ * missing artifact — an absent file becomes a `null` field, which is what makes
1165
+ * the dependent metric `unavailable` instead of 0.
1166
+ */
1167
+ async function readLoopsSupervisorRun(runDir, opts = {}) {
1168
+ const ws = opts.ws ?? join(runDir, "ws");
1169
+ const supRunDir = await findSupervisorRunDirIn(ws);
1170
+ const result = await readMaybe(join(runDir, "result.json"));
1171
+ const resultObj = parseJson(result);
1172
+ const journal = supRunDir === null ? null : await readMaybe(join(supRunDir, "journal.jsonl"));
1173
+ const journalWorkerSpawns = parseJsonl(journal).filter((event) => event.kind === "spawned" && typeof event.parent === "string" && event.role !== "supervisor").length;
1174
+ let workers = null;
1175
+ let workersMissingReason = null;
1176
+ const workerCwds = [];
1177
+ let workerStarts = 0;
1178
+ if (supRunDir === null) workersMissingReason = `no supervisor run dir under ${join(ws, ".agent", "supervisor")} (or legacy ${join(ws, ".loops", "supervisor")})`;
1179
+ else {
1180
+ const workersDir = join(supRunDir, "workers");
1181
+ const entries = await readdir(workersDir).catch(() => null);
1182
+ if (entries === null) workersMissingReason = `workers/ directory absent under ${supRunDir}`;
1183
+ else {
1184
+ const labels = [...new Set(entries.filter((f) => f.endsWith(".ndjson")).map((f) => f.replace(/\.inbox\.ndjson$/, "").replace(/\.ndjson$/, "")))].sort();
1185
+ workers = [];
1186
+ for (const label of labels) {
1187
+ const events = await readMaybe(join(workersDir, `${label}.ndjson`));
1188
+ const inbox = await readMaybe(join(workersDir, `${label}.inbox.ndjson`));
1189
+ const patch = await readMaybe(join(workersDir, `${label}.patch`));
1190
+ const startedRows = parseJsonl(events).filter((event) => event.kind === "started");
1191
+ workerStarts += startedRows.length;
1192
+ const startedIds = startedRows.map((event) => typeof event.workerId === "string" ? event.workerId : typeof event.agentId === "string" ? event.agentId : null).filter((id) => id !== null);
1193
+ const distinctStartedIds = new Set(startedIds);
1194
+ const workerId = startedRows.length > 0 && startedIds.length === startedRows.length && distinctStartedIds.size === 1 ? startedIds[0] : void 0;
1195
+ workers.push({
1196
+ ...workerId === void 0 ? {} : { workerId },
1197
+ label,
1198
+ events,
1199
+ inbox,
1200
+ patchBytes: patch === null ? null : Buffer.byteLength(patch)
1201
+ });
1202
+ for (const ev of startedRows) if (ev.kind === "started" && typeof ev.cwd === "string") workerCwds.push(ev.cwd);
1203
+ }
1204
+ }
1205
+ }
1206
+ let harnessWorkerTokens = null;
1207
+ let harnessMissingReason = null;
1208
+ let workerCwdsWithoutSessions = 0;
1209
+ if (opts.opencodeDb === null) harnessMissingReason = "opencode join disabled";
1210
+ else if (workerCwds.length === 0) harnessMissingReason = "no worker clone cwds in workers/*.ndjson (nothing to join)";
1211
+ else {
1212
+ const db = await openOpencodeDb(opts.opencodeDb ?? DEFAULT_OPENCODE_DB);
1213
+ if (db === null) harnessMissingReason = `opencode session store unreadable at ${opts.opencodeDb ?? DEFAULT_OPENCODE_DB}`;
1214
+ else try {
1215
+ const seen = /* @__PURE__ */ new Set();
1216
+ let sessions = 0;
1217
+ let input = 0;
1218
+ let output = 0;
1219
+ const distinctWorkerCwds = new Set(workerCwds);
1220
+ for (const cwd of distinctWorkerCwds) {
1221
+ const rows = findOpencodeSessionsByDirectory(db, cwd);
1222
+ if (rows.length === 0) workerCwdsWithoutSessions += 1;
1223
+ for (const row of rows) {
1224
+ if (seen.has(row.id)) continue;
1225
+ seen.add(row.id);
1226
+ sessions += 1;
1227
+ input += row.tokensInput;
1228
+ output += row.tokensOutput + row.tokensReasoning;
1229
+ }
1230
+ }
1231
+ harnessWorkerTokens = {
1232
+ store: "opencode",
1233
+ sessions,
1234
+ input,
1235
+ output
1236
+ };
1237
+ if (workerCwdsWithoutSessions > 0) harnessMissingReason = `${workerCwdsWithoutSessions}/${distinctWorkerCwds.size} worker clone cwds have no opencode session`;
1238
+ } finally {
1239
+ db.close();
1240
+ }
1241
+ }
1242
+ const workerInvocations = Math.max(journalWorkerSpawns, workerStarts);
1243
+ const workerTokenGaps = [];
1244
+ if (workerCwds.length < workerInvocations) workerTokenGaps.push(`${workerInvocations - workerCwds.length}/${workerInvocations} worker invocations have no clone cwd for the opencode token join`);
1245
+ if (workerInvocations > 0 && harnessWorkerTokens === null) workerTokenGaps.push(harnessMissingReason ?? "worker harness token join unavailable");
1246
+ else if (workerCwdsWithoutSessions > 0 && harnessMissingReason !== null) workerTokenGaps.push(harnessMissingReason);
1247
+ const workerTokenLimit = workerTokenGaps.length === 0 ? null : workerTokenGaps.join("; ");
1248
+ const patchPath = opts.patchPath ?? (typeof resultObj?.patchPath === "string" ? resultObj.patchPath : null);
1249
+ let judge = await readMaybe(join(runDir, "judge.json"));
1250
+ let judgeSource = judge === null ? null : join(runDir, "judge.json");
1251
+ if (judge === null && opts.ledgerPath !== void 0) {
1252
+ const row = await findLedgerRow(opts.ledgerPath, runDir);
1253
+ if (row !== null) {
1254
+ judge = JSON.stringify(row);
1255
+ judgeSource = `${opts.ledgerPath} (ledger row)`;
1256
+ }
1257
+ }
1258
+ return {
1259
+ runRef: runDir,
1260
+ instanceId: typeof resultObj?.iid === "string" ? resultObj.iid : instanceIdFromPath(runDir),
1261
+ arm: typeof resultObj?.arm === "string" ? resultObj.arm : basename(runDir),
1262
+ supRunDir,
1263
+ journal,
1264
+ brainLog: supRunDir === null ? null : await readMaybe(join(supRunDir, "brain.jsonl")),
1265
+ state: supRunDir === null ? null : await readMaybe(join(supRunDir, "state.json")),
1266
+ progress: supRunDir === null ? null : await readMaybe(join(supRunDir, "progress.ndjson")),
1267
+ workers,
1268
+ workersMissingReason,
1269
+ result,
1270
+ judge,
1271
+ judgeSource,
1272
+ patch: patchPath === null ? null : await readMaybe(patchPath),
1273
+ driverLog: await readMaybe(join(runDir, "driver.log")),
1274
+ harnessWorkerTokens,
1275
+ harnessMissingReason,
1276
+ limits: {
1277
+ ...NO_SOURCE_LIMITS,
1278
+ workerTokens: workerTokenLimit
1279
+ },
1280
+ traceCommand: null
1281
+ };
1282
+ }
1283
+ /** The loops on-disk layout, as a `SupervisorRunReader`. */
1284
+ function loopsSupervisorRunReader(runDir, opts = {}) {
1285
+ return {
1286
+ runRef: runDir,
1287
+ read: () => readLoopsSupervisorRun(runDir, opts)
1288
+ };
1289
+ }
1290
+ /** The ledger row whose `runDir` is this run (falling back to iid + arm match). */
1291
+ async function findLedgerRow(ledgerPath, runDir) {
1292
+ const rows = parseJsonl(await readMaybe(ledgerPath));
1293
+ const exact = rows.find((r) => r.runDir === runDir);
1294
+ if (exact !== void 0) return exact;
1295
+ const iid = instanceIdFromPath(runDir);
1296
+ const arm = basename(runDir);
1297
+ return rows.find((r) => r.iid === iid && r.arm === arm) ?? null;
1298
+ }
1299
+ /** `<outDir>/runs/<iid>/<arm>` → `<iid>`. */
1300
+ function instanceIdFromPath(runDir) {
1301
+ const parts = runDir.split("/").filter(Boolean);
1302
+ const armIdx = parts.length - 1;
1303
+ const iid = parts[armIdx - 1];
1304
+ return parts[armIdx - 2] === "runs" && iid !== void 0 ? iid : null;
1305
+ }
1306
+ /**
1307
+ * Analyze a supervisor run. Accepts a run directory (read through the loops
1308
+ * reader), any `SupervisorRunReader`, or already-read source bytes — so a
1309
+ * caller with its own store never has to touch the filesystem layout.
1310
+ */
1311
+ async function analyzeSupervisorRun(input, opts = {}) {
1312
+ if (typeof input === "string") return analyzeSupervisorRunSources(await isRuntimeSupervisorRunDir(input) ? await readRuntimeSupervisorRun(input) : await readLoopsSupervisorRun(input, opts));
1313
+ if (isReader(input)) return analyzeSupervisorRunSources(await input.read());
1314
+ return analyzeSupervisorRunSources(input);
1315
+ }
1316
+ function isReader(input) {
1317
+ return typeof input.read === "function";
1318
+ }
1319
+ /**
1320
+ * Read a completed run, write `run-report.json` + `run-report.md` beside its
1321
+ * artifacts, and append the headline block to the run log. Never throws on a
1322
+ * missing artifact — a run that produced nothing still yields a report whose
1323
+ * every metric says why.
1324
+ */
1325
+ async function writeSupervisorRunReport(runDir, opts = {}) {
1326
+ const report = analyzeSupervisorRunSources(await isRuntimeSupervisorRunDir(runDir) ? await readRuntimeSupervisorRun(runDir) : await readLoopsSupervisorRun(runDir, opts));
1327
+ const md = renderSupervisorRunMarkdown(report);
1328
+ const dest = opts.reportDir ?? runDir;
1329
+ const stem = opts.reportDir === void 0 ? "run-report" : supervisorReportStem(runDir);
1330
+ if (opts.reportDir !== void 0) await mkdir(opts.reportDir, { recursive: true }).catch(() => {});
1331
+ await writeFile(join(dest, `${stem}.json`), JSON.stringify(report, null, 1)).catch(() => {});
1332
+ await writeFile(join(dest, `${stem}.md`), md).catch(() => {});
1333
+ const headline = renderSupervisorRunHeadline(report);
1334
+ if (opts.appendHeadlineTo !== void 0) await appendFile(opts.appendHeadlineTo, `${headline}\n`).catch(() => {});
1335
+ if (opts.echo !== false) console.log(headline);
1336
+ return report;
1337
+ }
1338
+ /**
1339
+ * File stem for out-of-tree reports. Built from the run path's identifying
1340
+ * segments — candidate tag (the segment under `arm-runs/`), rep, instance, arm
1341
+ * — so two runs of the same instance from different candidates/reps never
1342
+ * overwrite each other.
1343
+ */
1344
+ function supervisorReportStem(runDir) {
1345
+ const parts = runDir.split("/").filter(Boolean);
1346
+ const arm = parts[parts.length - 1] ?? "cell";
1347
+ const iid = parts[parts.length - 2] ?? "instance";
1348
+ const rep = parts.find((p) => /^rep-\d+$/.test(p));
1349
+ const armRunsIdx = parts.indexOf("arm-runs");
1350
+ return [
1351
+ armRunsIdx >= 0 ? parts[armRunsIdx + 1] : void 0,
1352
+ rep,
1353
+ iid,
1354
+ arm
1355
+ ].filter((s) => s !== void 0 && s !== "runs").join(".").replace(/[^A-Za-z0-9._-]/g, "_");
1356
+ }
1357
+ /**
1358
+ * Best-effort wrapper for a hot path: a reporting failure must never kill a run
1359
+ * that already produced real work. Returns null and logs the reason instead.
1360
+ */
1361
+ async function writeSupervisorRunReportSafe(runDir, opts = {}) {
1362
+ try {
1363
+ return await writeSupervisorRunReport(runDir, opts);
1364
+ } catch (err) {
1365
+ console.log(`RUN-REPORT failed for ${runDir}: ${err instanceof Error ? err.message : String(err)}`);
1366
+ return null;
1367
+ }
1368
+ }
1369
+ /**
1370
+ * Report every run under an experiment `outDir` (any depth of
1371
+ * `runs/<iid>/<arm>`), write each run's report, and write the rollup at
1372
+ * `<outDir>/run-report-round.{json,md}`.
1373
+ */
1374
+ async function reportSupervisorRound(outDir, opts = {}) {
1375
+ const runDirs = await findSupervisorRunDirs(outDir);
1376
+ const reports = [];
1377
+ for (const runDir of runDirs) {
1378
+ const r = await writeSupervisorRunReportSafe(runDir, {
1379
+ ...opts,
1380
+ echo: opts.echo ?? false
1381
+ });
1382
+ if (r !== null) reports.push(r);
1383
+ }
1384
+ const rollup = rollupSupervisorRuns(reports);
1385
+ const md = renderSupervisorRollupMarkdown(rollup, opts.title ?? `Round rollup — ${basename(outDir)}`);
1386
+ const dest = opts.reportDir ?? outDir;
1387
+ if (opts.reportDir !== void 0) await mkdir(opts.reportDir, { recursive: true }).catch(() => {});
1388
+ await writeFile(join(dest, "run-report-round.json"), JSON.stringify(rollup, null, 1)).catch(() => {});
1389
+ await writeFile(join(dest, "run-report-round.md"), md).catch(() => {});
1390
+ if (opts.appendHeadlineTo !== void 0) await appendFile(opts.appendHeadlineTo, `${md}\n`).catch(() => {});
1391
+ if (opts.echo !== false) console.log(md);
1392
+ return rollup;
1393
+ }
1394
+ /**
1395
+ * Every loops or Runtime supervisor run below `root`.
1396
+ *
1397
+ * When `root` itself is one run, return no children so callers can distinguish
1398
+ * a single report from a parent-directory rollup.
1399
+ */
1400
+ async function findSupervisorRunDirs(root) {
1401
+ if (await isRuntimeSupervisorRunDir(root) || await findSupervisorRunDirIn(join(root, "ws")) !== null) return [];
1402
+ const found = [];
1403
+ const walk = async (dir, depth) => {
1404
+ if (depth > 8) return;
1405
+ const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
1406
+ for (const e of entries) {
1407
+ if (!e.isDirectory()) continue;
1408
+ if (e.name === "node_modules" || e.name === ".git") continue;
1409
+ const full = join(dir, e.name);
1410
+ if (await isRuntimeSupervisorRunDir(full) || await findSupervisorRunDirIn(join(full, "ws")) !== null) {
1411
+ found.push(full);
1412
+ continue;
1413
+ }
1414
+ await walk(full, depth + 1);
1415
+ }
1416
+ };
1417
+ await walk(root, 0);
1418
+ return found.sort();
1419
+ }
1420
+ //#endregion
1421
+ //#region src/supervisor-run/report-command.ts
1422
+ /**
1423
+ * `agent-eval supervisor-run report <runDir>` takes one run directory and
1424
+ * prints the report the module already renders. The command reads through
1425
+ * `analyzeSupervisorRun`, so a Runtime run dir and a loops run dir take the
1426
+ * same path and the status comes from the record `terminal-record.ts` names.
1427
+ *
1428
+ * Exit codes follow the other self-parsing subcommands: 2 for a usage error,
1429
+ * 1 when the directory cannot be read (missing, not a directory, or a corrupt
1430
+ * journal or terminal record), 0 after the report was written to stdout.
1431
+ */
1432
+ const FORMATS = [
1433
+ "headline",
1434
+ "markdown",
1435
+ "json"
1436
+ ];
1437
+ const SUPERVISOR_RUN_USAGE = "usage: agent-eval supervisor-run report <runDir> [--format headline|markdown|json]";
1438
+ const PROCESS_IO = {
1439
+ stdout: (text) => {
1440
+ process.stdout.write(text);
1441
+ },
1442
+ stderr: (text) => {
1443
+ process.stderr.write(text);
1444
+ }
1445
+ };
1446
+ function isFormat(value) {
1447
+ return FORMATS.includes(value);
1448
+ }
1449
+ function parseReportArgs(argv) {
1450
+ let runDir = null;
1451
+ let format = "headline";
1452
+ for (let i = 0; i < argv.length; i++) {
1453
+ const arg = argv[i];
1454
+ if (arg === "--format") {
1455
+ const raw = argv[++i];
1456
+ if (raw === void 0 || !isFormat(raw)) throw new Error(`--format expects one of ${FORMATS.join("|")}, got ${JSON.stringify(raw ?? "")}`);
1457
+ format = raw;
1458
+ } else if (arg.startsWith("--format=")) {
1459
+ const raw = arg.slice(9);
1460
+ if (!isFormat(raw)) throw new Error(`--format expects one of ${FORMATS.join("|")}, got ${JSON.stringify(raw)}`);
1461
+ format = raw;
1462
+ } else if (arg.startsWith("--")) throw new Error(`unknown flag "${arg}"`);
1463
+ else if (runDir === null) runDir = arg;
1464
+ else throw new Error(`unexpected argument "${arg}"`);
1465
+ }
1466
+ if (runDir === null) throw new Error(SUPERVISOR_RUN_USAGE);
1467
+ return {
1468
+ runDir,
1469
+ format
1470
+ };
1471
+ }
1472
+ function errorMessage(error) {
1473
+ return error instanceof Error ? error.message : String(error);
1474
+ }
1475
+ /**
1476
+ * CLI driver for `agent-eval supervisor-run`. `argv` is everything after the
1477
+ * command name, so `['report', runDir, ...flags]`. Returns the process exit code.
1478
+ */
1479
+ async function runSupervisorRunCommand(argv, io = PROCESS_IO) {
1480
+ const [verb, ...rest] = argv;
1481
+ if (verb === void 0 || verb === "--help" || verb === "-h" || verb === "help") {
1482
+ io.stdout(`${SUPERVISOR_RUN_USAGE}\n`);
1483
+ return verb === void 0 ? 2 : 0;
1484
+ }
1485
+ if (verb !== "report") {
1486
+ io.stderr(`unknown supervisor-run subcommand "${verb}"\n${SUPERVISOR_RUN_USAGE}\n`);
1487
+ return 2;
1488
+ }
1489
+ if (rest.includes("--help") || rest.includes("-h")) {
1490
+ io.stdout(`${SUPERVISOR_RUN_USAGE}\n`);
1491
+ return 0;
1492
+ }
1493
+ let args;
1494
+ try {
1495
+ args = parseReportArgs(rest);
1496
+ } catch (error) {
1497
+ io.stderr(`${errorMessage(error)}\n`);
1498
+ return 2;
1499
+ }
1500
+ const runDir = resolve(args.runDir);
1501
+ const entry = await stat(runDir).catch(() => null);
1502
+ if (entry === null || !entry.isDirectory()) {
1503
+ io.stderr(`[agent-eval] supervisor-run report: ${runDir} is not a directory\n`);
1504
+ return 1;
1505
+ }
1506
+ try {
1507
+ const report = await analyzeSupervisorRun(runDir);
1508
+ switch (args.format) {
1509
+ case "headline":
1510
+ io.stdout(`${renderSupervisorRunHeadline(report)}\n`);
1511
+ break;
1512
+ case "markdown":
1513
+ io.stdout(renderSupervisorRunMarkdown(report));
1514
+ break;
1515
+ case "json":
1516
+ io.stdout(`${JSON.stringify(report, null, 2)}\n`);
1517
+ break;
1518
+ }
1519
+ return 0;
1520
+ } catch (error) {
1521
+ io.stderr(`[agent-eval] supervisor-run report failed for ${runDir}: ${errorMessage(error)}\n`);
1522
+ return 1;
1523
+ }
1524
+ }
1525
+ //#endregion
1526
+ export { parsePatch as _, loopsSupervisorRunReader as a, writeSupervisorRunReport as c, readRuntimeSupervisorRun as d, runtimeSupervisorRunReader as f, analyzeSupervisorRunSources as g, renderSupervisorRunMarkdown as h, findSupervisorRunDirs as i, writeSupervisorRunReportSafe as l, renderSupervisorRunHeadline as m, analyzeSupervisorRun as n, readLoopsSupervisorRun as o, renderSupervisorRollupMarkdown as p, findSupervisorRunDirIn as r, reportSupervisorRound as s, runSupervisorRunCommand as t, isRuntimeSupervisorRunDir as u, rollupSupervisorRuns as v };
1527
+
1528
+ //# sourceMappingURL=report-command-DKlXfU5r.js.map