@tangle-network/agent-runtime 2.3.4 → 2.3.7

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 (49) hide show
  1. package/dist/{activation-SA2R6STL.js → activation-CDPbi8JA.js} +2 -2
  2. package/dist/{activation-SA2R6STL.js.map → activation-CDPbi8JA.js.map} +1 -1
  3. package/dist/agent.js +3 -3
  4. package/dist/{coordination-manager-BxnEJXsv.js → coordination-manager-DNrb7zMg.js} +4 -4
  5. package/dist/{coordination-manager-BxnEJXsv.js.map → coordination-manager-DNrb7zMg.js.map} +1 -1
  6. package/dist/{delegate-Drwq3e6S.js → delegate-oqIu6L-E.js} +2 -2
  7. package/dist/{delegate-Drwq3e6S.js.map → delegate-oqIu6L-E.js.map} +1 -1
  8. package/dist/durable.js +6 -6
  9. package/dist/{graph-Bio7TWRa.js → graph-FLVwpg79.js} +5 -5
  10. package/dist/{graph-Bio7TWRa.js.map → graph-FLVwpg79.js.map} +1 -1
  11. package/dist/{improve-BaVcOScf.js → improve-BCjhWbVk.js} +5 -5
  12. package/dist/{improve-BaVcOScf.js.map → improve-BCjhWbVk.js.map} +1 -1
  13. package/dist/index.js +8 -8
  14. package/dist/intelligence.js +3 -3
  15. package/dist/kernel.js +11 -11
  16. package/dist/{loop-runner-w2R6GUSp.js → loop-runner-BV0n-WTM.js} +2 -2
  17. package/dist/{loop-runner-w2R6GUSp.js.map → loop-runner-BV0n-WTM.js.map} +1 -1
  18. package/dist/loop-runner-bin.js +1 -1
  19. package/dist/mcp/bin.js +3 -3
  20. package/dist/mcp/index.js +4 -4
  21. package/dist/{provision-supervisor-DG4E0Viy.js → provision-supervisor-Pd95wA1X.js} +4 -4
  22. package/dist/{provision-supervisor-DG4E0Viy.js.map → provision-supervisor-Pd95wA1X.js.map} +1 -1
  23. package/dist/{root-stream-To4qAc0e.js → root-stream-Bsh5WFcg.js} +2 -2
  24. package/dist/{root-stream-To4qAc0e.js.map → root-stream-Bsh5WFcg.js.map} +1 -1
  25. package/dist/{run-cancellation-BhP0Nw6G.js → run-cancellation-CzQZUlHr.js} +2 -2
  26. package/dist/{run-cancellation-BhP0Nw6G.js.map → run-cancellation-CzQZUlHr.js.map} +1 -1
  27. package/dist/{runtime-DVwFHx7i.js → runtime-BlAFJYEs.js} +11 -11
  28. package/dist/{runtime-DVwFHx7i.js.map → runtime-BlAFJYEs.js.map} +1 -1
  29. package/dist/{search-executor-DRzb2WwZ.js → search-executor-CMPbMvpr.js} +3 -3
  30. package/dist/{search-executor-DRzb2WwZ.js.map → search-executor-CMPbMvpr.js.map} +1 -1
  31. package/dist/{server-BNsxRqPY.js → server-DxaNVGx-.js} +3 -3
  32. package/dist/{server-BNsxRqPY.js.map → server-DxaNVGx-.js.map} +1 -1
  33. package/dist/{structural-rollout-C49iL5Cn.js → structural-rollout-BUqZsIhX.js} +3 -3
  34. package/dist/{structural-rollout-C49iL5Cn.js.map → structural-rollout-BUqZsIhX.js.map} +1 -1
  35. package/dist/{supervise-BwUx_gjv.js → supervise-Dm0Goys6.js} +5 -5
  36. package/dist/{supervise-BwUx_gjv.js.map → supervise-Dm0Goys6.js.map} +1 -1
  37. package/dist/{supervision-discovery-CVpmTroK.js → supervision-discovery-DmDWHwLu.js} +2 -2
  38. package/dist/{supervision-discovery-CVpmTroK.js.map → supervision-discovery-DmDWHwLu.js.map} +1 -1
  39. package/dist/{supervisor-ByarsrwH.js → supervisor-B8beIEqH.js} +23 -37
  40. package/dist/supervisor-B8beIEqH.js.map +1 -0
  41. package/dist/testing.js +11 -11
  42. package/dist/{top-web-Dh1i-KLd.js → top-web-CUDlOKu0.js} +245 -233
  43. package/dist/top-web-CUDlOKu0.js.map +1 -0
  44. package/dist/tui/bin.js +3 -3
  45. package/dist/tui/index.d.ts +11 -8
  46. package/dist/tui/index.js +2 -2
  47. package/package.json +1 -1
  48. package/dist/supervisor-ByarsrwH.js.map +0 -1
  49. package/dist/top-web-Dh1i-KLd.js.map +0 -1
@@ -1,12 +1,14 @@
1
- import { D as FileResultBlobStore, E as spawnJournalRecordEvents, N as FileObserverJournal, _ as supervisorRunsRoot, h as safeWorkerFile, r as nodeStreamFile, u as legacySupervisorRunsRoot, v as supervisorWorkersDir, w as FileSpawnJournal } from "./root-stream-To4qAc0e.js";
2
- import { o as readCommittedJsonLines, r as parseCommittedJsonLines } from "./jsonl-file-BBP9Dz9w.js";
3
- import { n as projectPursuit, t as discoverDurableSupervisionRun } from "./supervision-discovery-CVpmTroK.js";
1
+ import { C as FileSpawnJournal, E as FileResultBlobStore, Jt as recordValue, M as FileObserverJournal, T as spawnJournalRecordEvents, _ as supervisorRunsRoot, h as safeWorkerFile, r as nodeStreamFile, t as ROOT_STREAM_FILE, u as legacySupervisorRunsRoot } from "./root-stream-Bsh5WFcg.js";
2
+ import { r as parseCommittedJsonLines } from "./jsonl-file-BBP9Dz9w.js";
3
+ import { n as projectPursuit, t as discoverDurableSupervisionRun } from "./supervision-discovery-DmDWHwLu.js";
4
4
  import { createRequire } from "node:module";
5
+ import { isRecord } from "@tangle-network/agent-core";
5
6
  import { createHash } from "node:crypto";
6
- import { closeSync, existsSync, openSync, readFileSync, readSync, readdirSync, statSync, watch } from "node:fs";
7
+ import { closeSync, existsSync, fstatSync, openSync, readFileSync, readSync, readdirSync, statSync, watch } from "node:fs";
7
8
  import { basename, dirname, extname, join, resolve, sep } from "node:path";
8
9
  import { readFile, realpath, stat } from "node:fs/promises";
9
10
  import { createServer } from "node:http";
11
+ import { StringDecoder } from "node:string_decoder";
10
12
  // src/tui/top-transcript.ts
11
13
  /** Read selected history on demand, leaving the live topology snapshot bounded and fast. */
12
14
  async function transcriptHistory(worker, runDir, node) {
@@ -51,15 +53,7 @@ async function transcriptHistory(worker, runDir, node) {
51
53
  });
52
54
  return [...rows, ...native];
53
55
  }
54
- const lines = [];
55
- for await (const row of readCommittedJsonLines(worker.eventFile)) {
56
- lines.push(JSON.stringify(row));
57
- if (lines.length > 200) lines.shift();
58
- }
59
- return [...rows, ...transcriptRows({
60
- ...worker,
61
- liveTail: lines
62
- })];
56
+ return [...rows, ...transcriptRows({ liveTail: readTranscriptTail(worker.eventFile) })];
63
57
  }
64
58
  /**
65
59
  * A settled worker's retained Router conversation as progress lines, for an executor that ran its
@@ -69,33 +63,16 @@ async function transcriptHistory(worker, runDir, node) {
69
63
  async function retainedTranscriptLines(worker, runDir) {
70
64
  const at = worker.endedAt;
71
65
  if (worker.transcript?.harness !== "router" || at === void 0) return [];
72
- const artifact = await new FileResultBlobStore(join(runDir, "blobs")).get(worker.transcript.ref);
73
- const line = (event) => JSON.stringify({
74
- at,
75
- event
76
- });
77
- return (artifact?.files ?? []).flatMap((file) => (typeof file.content === "string" ? file.content.split("\n") : []).flatMap((text) => {
78
- let message;
66
+ return ((await new FileResultBlobStore(join(runDir, "blobs")).get(worker.transcript.ref))?.files ?? []).flatMap((file) => (typeof file.content === "string" ? file.content.split("\n") : []).flatMap((text) => {
79
67
  try {
80
- message = JSON.parse(text);
68
+ const message = recordValue(JSON.parse(text));
69
+ return message?.role === "assistant" || message?.role === "tool" ? [JSON.stringify({
70
+ at,
71
+ message
72
+ })] : [];
81
73
  } catch {
82
74
  return [];
83
75
  }
84
- if (message.role === "tool") return [line({
85
- kind: "tool_result",
86
- toolCallId: message.tool_call_id,
87
- result: message.content
88
- })];
89
- if (message.role !== "assistant") return [];
90
- return [...typeof message.content === "string" && message.content.length > 0 ? [line({
91
- kind: "text_delta",
92
- text: message.content
93
- })] : [], ...(message.tool_calls ?? []).map((call) => line({
94
- kind: "tool_call",
95
- toolCallId: call.id,
96
- toolName: call.function?.name,
97
- args: call.function?.arguments
98
- }))];
99
76
  }));
100
77
  }
101
78
  /** Present the recorded tail through sandbox-ui's existing agent-stream contract. */
@@ -104,83 +81,83 @@ function transcriptRows(worker) {
104
81
  for (const line of worker.liveTail) {
105
82
  let value;
106
83
  try {
107
- value = JSON.parse(line);
84
+ value = recordValue(JSON.parse(line));
108
85
  } catch {
109
86
  continue;
110
87
  }
111
- const at = typeof value.timestamp === "string" ? value.timestamp : typeof value.at === "string" ? value.at : void 0;
112
- if (!at || !Number.isFinite(Date.parse(at))) continue;
113
- const event = value.event;
114
- if (event?.kind) {
88
+ const timestamp = value?.timestamp ?? value?.at;
89
+ const time = typeof timestamp === "string" ? Date.parse(timestamp) : timestamp;
90
+ if (typeof time !== "number" || !Number.isFinite(new Date(time).getTime())) continue;
91
+ const at = new Date(time).toISOString();
92
+ const push = (event) => rows.push({
93
+ at,
94
+ event
95
+ });
96
+ const event = recordValue(value?.event);
97
+ if (typeof event?.kind === "string") {
115
98
  const kind = {
116
99
  text_delta: "text",
117
100
  reasoning_delta: "reasoning",
118
101
  tool_call: "tool-call",
119
102
  tool_result: "tool-result"
120
103
  }[event.kind] ?? event.kind;
121
- rows.push({
122
- at,
123
- event: {
124
- ...event,
125
- kind
126
- }
104
+ push({
105
+ ...event,
106
+ kind
127
107
  });
128
108
  continue;
129
109
  }
130
- const message = value.message;
110
+ const message = recordValue(value?.message);
131
111
  if (!message) continue;
112
+ if (message.role === "tool") {
113
+ push({
114
+ kind: "tool-result",
115
+ toolCallId: String(message.tool_call_id),
116
+ result: message.content
117
+ });
118
+ continue;
119
+ }
120
+ for (const item of Array.isArray(message.tool_calls) ? message.tool_calls : []) {
121
+ const call = recordValue(item), fn = recordValue(call?.function);
122
+ if (call && fn) push({
123
+ kind: "tool-call",
124
+ toolCallId: String(call.id),
125
+ toolName: String(fn.name),
126
+ args: fn.arguments
127
+ });
128
+ }
132
129
  const parts = typeof message.content === "string" ? [{
133
130
  type: "text",
134
131
  text: message.content
135
- }] : message.content ?? [];
136
- for (const part of parts) if (part.type === "text" && typeof part.text === "string") rows.push({
137
- at,
138
- event: {
132
+ }] : Array.isArray(message.content) ? message.content : [];
133
+ for (const part of parts) {
134
+ if (!recordValue(part)) continue;
135
+ if (part.type === "text" && typeof part.text === "string") push({
139
136
  kind: message.role === "user" ? "user" : "text",
140
137
  text: part.text
141
- }
142
- });
143
- else if (part.type === "thinking" && typeof part.thinking === "string") rows.push({
144
- at,
145
- event: {
138
+ });
139
+ else if (part.type === "thinking" && typeof part.thinking === "string") push({
146
140
  kind: "reasoning",
147
141
  text: part.thinking
148
- }
149
- });
150
- else if (part.type === "tool_use") rows.push({
151
- at,
152
- event: {
142
+ });
143
+ else if (part.type === "tool_use") push({
153
144
  kind: "tool-call",
154
145
  toolCallId: String(part.id),
155
146
  toolName: String(part.name),
156
147
  args: part.input
157
- }
158
- });
159
- else if (part.type === "tool_result") rows.push({
160
- at,
161
- event: {
148
+ });
149
+ else if (part.type === "tool_result") push({
162
150
  kind: "tool-result",
163
151
  toolCallId: String(part.tool_use_id),
164
152
  result: part.content
165
- }
166
- });
153
+ });
154
+ }
167
155
  }
168
156
  return rows;
169
157
  }
170
158
  /** The bounded native/normalized tail shared by snapshot adapters and selected streams. */
171
159
  function readTranscriptTail(file) {
172
- const fd = openSync(file, "r");
173
- try {
174
- const size = statSync(file).size;
175
- const buffer = Buffer.alloc(Math.min(size, 65536));
176
- readSync(fd, buffer, 0, buffer.length, Math.max(0, size - buffer.length));
177
- const lines = buffer.toString().split("\n");
178
- lines.pop();
179
- if (size > buffer.length) lines.shift();
180
- return lines.filter(Boolean).slice(-200);
181
- } finally {
182
- closeSync(fd);
183
- }
160
+ return readRecordedEvents(file, 200).lines;
184
161
  }
185
162
  function transcriptTail(worker) {
186
163
  return transcriptRows(worker.eventFile ? {
@@ -188,6 +165,89 @@ function transcriptTail(worker) {
188
165
  liveTail: readTranscriptTail(worker.eventFile)
189
166
  } : worker);
190
167
  }
168
+ const cursors = /* @__PURE__ */ new Map();
169
+ const activityKinds = /* @__PURE__ */ new Set([
170
+ "text",
171
+ "reasoning",
172
+ "user",
173
+ "tool-call",
174
+ "tool-result",
175
+ "interaction",
176
+ "child_task"
177
+ ]);
178
+ /** Read appended UTF-8 bytes once; complete EOF JSON follows the durable reader contract. */
179
+ function readRecordedEvents(file, maxLines = 8) {
180
+ const fd = openSync(file, "r");
181
+ try {
182
+ const stat = fstatSync(fd);
183
+ let cursor = cursors.get(file);
184
+ if (!cursor || cursor.dev !== stat.dev || cursor.ino !== stat.ino || stat.size < cursor.offset) {
185
+ cursor = {
186
+ dev: stat.dev,
187
+ ino: stat.ino,
188
+ offset: 0,
189
+ pending: "",
190
+ decoder: new StringDecoder("utf8"),
191
+ times: [],
192
+ lines: [],
193
+ result: {
194
+ file,
195
+ lines: [],
196
+ activity: {}
197
+ }
198
+ };
199
+ cursors.set(file, cursor);
200
+ }
201
+ if (stat.size === cursor.offset) return {
202
+ ...cursor.result,
203
+ lines: cursor.result.lines.slice(-maxLines)
204
+ };
205
+ const lines = [...cursor.lines];
206
+ const buffer = Buffer.alloc(Math.min(65536, stat.size - cursor.offset));
207
+ while (cursor.offset < stat.size) {
208
+ const bytes = readSync(fd, buffer, 0, Math.min(buffer.length, stat.size - cursor.offset), cursor.offset);
209
+ if (!bytes) break;
210
+ cursor.offset += bytes;
211
+ const rows = (cursor.pending + cursor.decoder.write(buffer.subarray(0, bytes))).split("\n");
212
+ cursor.pending = rows.pop();
213
+ for (const line of rows) {
214
+ if (!line.trim()) continue;
215
+ lines.push(line);
216
+ if (lines.length > 200) lines.shift();
217
+ const activity = transcriptRows({ liveTail: [line] }).find((row) => activityKinds.has(row.event.kind));
218
+ if (activity) cursor.times.push(Date.parse(activity.at));
219
+ }
220
+ }
221
+ cursor.lines = lines;
222
+ let pending = [];
223
+ if (cursor.pending.trim()) try {
224
+ JSON.parse(cursor.pending);
225
+ pending = [cursor.pending];
226
+ } catch {}
227
+ const final = transcriptRows({ liveTail: pending }).find((row) => activityKinds.has(row.event.kind));
228
+ const times = (final ? [...cursor.times, Date.parse(final.at)] : cursor.times).sort((a, b) => a - b);
229
+ const gaps = times.slice(1).map((at, i) => at - times[i]).sort((a, b) => a - b);
230
+ const mid = Math.floor(gaps.length / 2);
231
+ cursor.result = {
232
+ file,
233
+ lines: [...lines, ...pending].slice(-200),
234
+ ...times.length ? { firstActivityAt: times[0] } : {},
235
+ activity: {
236
+ ...times.length ? { lastActivityAt: times.at(-1) } : {},
237
+ ...gaps.length ? { medianEventGapMs: (gaps[Math.floor((gaps.length - 1) / 2)] + gaps[mid]) / 2 } : {}
238
+ }
239
+ };
240
+ return {
241
+ ...cursor.result,
242
+ lines: cursor.result.lines.slice(-maxLines)
243
+ };
244
+ } catch (error) {
245
+ cursors.delete(file);
246
+ throw error;
247
+ } finally {
248
+ closeSync(fd);
249
+ }
250
+ }
191
251
  // src/tui/workflow-journal.ts
192
252
  /** Native workflows have no Runtime accounting. Translate recorded lifecycle only. */
193
253
  function workflowJournal(runDir, text) {
@@ -204,21 +264,10 @@ function workflowJournal(runDir, text) {
204
264
  let start;
205
265
  let end;
206
266
  try {
207
- const fd = openSync(file, "r");
208
- try {
209
- const size = statSync(file).size;
210
- const prefix = Buffer.alloc(Math.min(size, 16384));
211
- readSync(fd, prefix, 0, prefix.length, 0);
212
- start = /"timestamp":"([^"]+)"/.exec(prefix.toString())?.[1];
213
- const lines = readTranscriptTail(file).slice(-8);
214
- end = [...lines.join("\n").matchAll(/"timestamp":"([^"]+)"/g)].at(-1)?.[1];
215
- tails.set(id, {
216
- file,
217
- lines
218
- });
219
- } finally {
220
- closeSync(fd);
221
- }
267
+ const recorded = readRecordedEvents(file);
268
+ tails.set(id, recorded);
269
+ if (recorded.firstActivityAt !== void 0) start = new Date(recorded.firstActivityAt).toISOString();
270
+ if (recorded.activity.lastActivityAt !== void 0) end = new Date(recorded.activity.lastActivityAt).toISOString();
222
271
  } catch (error) {
223
272
  if (error.code !== "ENOENT") throw error;
224
273
  }
@@ -240,12 +289,14 @@ function workflowJournal(runDir, text) {
240
289
  }
241
290
  });
242
291
  } else if (row.type === "result" || row.type === "error" || row.type === "cancelled") {
243
- const line = tails.get(id)?.lines.at(-1);
244
- const at = line ? /"timestamp":"([^"]+)"/.exec(line)?.[1] : void 0;
292
+ const activity = tails.get(id)?.activity.lastActivityAt;
293
+ const at = activity === void 0 ? void 0 : new Date(activity).toISOString();
294
+ const reason = typeof row.reason === "string" ? { reason: row.reason } : {};
245
295
  events.push(row.type === "cancelled" ? {
246
296
  kind: "cancelled",
247
297
  id,
248
- ...at ? { at } : {}
298
+ ...at ? { at } : {},
299
+ ...reason
249
300
  } : {
250
301
  kind: "settled",
251
302
  id,
@@ -254,7 +305,8 @@ function workflowJournal(runDir, text) {
254
305
  spent: {
255
306
  usdKnown: false,
256
307
  tokensKnown: false
257
- }
308
+ },
309
+ ...reason
258
310
  });
259
311
  }
260
312
  }
@@ -267,12 +319,8 @@ function workflowJournal(runDir, text) {
267
319
  };
268
320
  }
269
321
  // src/tui/top-snapshot.ts
270
- /**
271
- * Read-only operator snapshots over the writer-owned supervisor layout and explicit durable runs.
272
- * Workspace reads preserve legacy files; durable reads use discovery, journals and the canonical
273
- * pursuit projection for model identity, failure and accounting evidence. Run totals count once.
322
+ /** Read-only topology, activity and accounting over writer-owned workspace and durable journals.
274
323
  * ANSI presentation lives in `./top-frame`.
275
- *
276
324
  * @experimental
277
325
  */
278
326
  const emptySpend = {
@@ -283,7 +331,7 @@ const emptySpend = {
283
331
  ms: 0
284
332
  };
285
333
  function loadTopSnapshot(rootDir, input = Date.now()) {
286
- if (typeof input !== "number" && input.format === "journal") return loadJournalTopSnapshot(rootDir, input.runDir, input.now ?? Date.now());
334
+ if (typeof input !== "number" && input.format === "journal") return loadDurableTopSnapshot(rootDir, input.runDir, input.now ?? Date.now(), false);
287
335
  if (typeof input !== "number" && input.format === "workflow") {
288
336
  const root = resolve(rootDir), dir = resolve(input.runDir), now = input.now ?? Date.now();
289
337
  const read = readSourceFile(join(dir, "journal.jsonl"), "journal", "journal.jsonl");
@@ -347,7 +395,7 @@ function loadTopSnapshot(rootDir, input = Date.now()) {
347
395
  if (!state.value) continue;
348
396
  const journal = readJournal(dir);
349
397
  const progress = readSourceFile(join(dir, "progress.ndjson"), "progress", "progress.ndjson");
350
- const workerEvents = readWorkerEventTails(supervisorWorkersDir(dir), 8);
398
+ const workerEvents = readWorkerEventTails(dir);
351
399
  report(entry, [
352
400
  ...journal.diagnostics,
353
401
  ...progress.diagnostics,
@@ -371,78 +419,52 @@ function topSnapshot(root, now, supervisors, diagnostics = [], discovered = 1, p
371
419
  ...pursuit ? { pursuit } : {}
372
420
  };
373
421
  }
374
- async function loadDurableTopSnapshot(rootDir, runDir, now) {
422
+ /** Observer-backed and older spawn-only runs share topology; missing accounting stays unknown. */
423
+ async function loadDurableTopSnapshot(rootDir, runDir, now, observed = true) {
375
424
  const root = resolve(rootDir);
376
425
  let pursuit;
377
- let source = "observer-journal";
378
- let path = "observer.jsonl";
426
+ let source = observed ? "observer-journal" : "journal";
427
+ let path = observed ? "observer.jsonl" : "spawn-journal.jsonl";
379
428
  try {
380
429
  if (typeof runDir !== "string" || !runDir.trim()) throw new TypeError("runDir must be non-empty");
381
- const journal = await FileObserverJournal.open(join(resolve(runDir), path));
382
- if (!journal) return topSnapshot(root, now, [], [{
383
- source,
384
- path,
385
- reason: "missing"
386
- }]);
387
- pursuit = projectPursuit(await journal.read());
430
+ if (observed) {
431
+ const journal = await FileObserverJournal.open(join(resolve(runDir), path));
432
+ if (!journal) return topSnapshot(root, now, [], [{
433
+ source,
434
+ path,
435
+ reason: "missing"
436
+ }]);
437
+ pursuit = projectPursuit(await journal.read());
438
+ }
388
439
  source = "journal";
389
440
  path = ".";
390
441
  const discovery = await discoverDurableSupervisionRun(runDir);
391
- const latest = new Map(pursuit.runs.map((run) => [run.runId, run]));
392
442
  path = "spawn-journal.jsonl";
393
- const spawnJournal = new FileSpawnJournal(discovery.spawnJournalPath);
394
- const rows = [...latest.values()].filter((run) => !discovery.roots.some((id) => run.runId.startsWith(`${id}/`)));
395
- return topSnapshot(root, now, await Promise.all(rows.map(async (run) => {
396
- const binding = discovery.roots.find((id) => id === run.runId);
397
- if (!binding && existsSync(discovery.spawnJournalPath)) throw new Error("observer run has no discovered spawn root");
398
- const events = binding ? await spawnJournal.loadTree(binding) ?? [] : [];
399
- const rootSpawn = events.find((event) => event.kind === "spawned" && event.id === binding);
400
- const budget = rootSpawn?.kind === "spawned" ? rootSpawn.budget?.maxTokens : void 0;
401
- const terminal = events.filter((e) => e.id === binding && (e.kind === "settled" || e.kind === "cancelled")).at(-1);
402
- const settledAt = run.settledAt === void 0 ? terminal?.at : new Date(run.settledAt).toISOString();
403
- return buildSupervisorView({
404
- id: run.runId,
405
- status: run.status !== "running" || terminal === void 0 ? run.status : terminal.kind === "settled" ? terminal.status : "cancelled",
406
- task: run.runId,
407
- workspaceDir: root,
408
- budget: budget ?? 0,
409
- budgetKnown: budget !== void 0,
410
- startedAt: new Date(run.firstObservedAt).toISOString(),
411
- ...settledAt === void 0 ? {} : { completedAt: settledAt }
412
- }, discovery.runDir, events.filter(isSpawnEvent), run.error ? [run.error] : [], now, void 0, run);
413
- })), [], 1, pursuit);
414
- } catch (error) {
415
- const reason = isRecord(error) && typeof error.code === "string" ? "unreadable" : "invalid-state";
416
- return topSnapshot(root, now, [], [{
417
- source,
418
- path,
419
- reason
420
- }], 1, pursuit);
421
- }
422
- }
423
- /** Old Discovery and factory roots may predate observer.jsonl; absence never establishes known totals. */
424
- async function loadJournalTopSnapshot(rootDir, runDir, now) {
425
- const root = resolve(rootDir), dir = resolve(runDir);
426
- try {
427
- const discovery = await discoverDurableSupervisionRun(dir);
428
443
  const journal = new FileSpawnJournal(discovery.spawnJournalPath);
429
- return topSnapshot(root, now, await Promise.all(discovery.roots.map(async (id, i) => {
430
- const events = await journal.loadTree(id) ?? [];
444
+ const latest = new Map(pursuit?.runs.map((run) => [run.runId, run]));
445
+ const ids = pursuit ? [...latest.keys()].filter((id) => !discovery.roots.some((parent) => id.startsWith(`${parent}/`))) : discovery.roots;
446
+ const tails = readWorkerEventTails(discovery.runDir);
447
+ return topSnapshot(root, now, await Promise.all(ids.map(async (id) => {
448
+ const accounting = latest.get(id);
449
+ const binding = discovery.roots.indexOf(id);
450
+ if (binding < 0 && existsSync(discovery.spawnJournalPath)) throw new Error("observer run has no discovered spawn root");
451
+ const events = binding < 0 ? [] : await journal.loadTree(id) ?? [];
452
+ const spawn = events.find((event) => event.kind === "spawned" && event.id === id);
453
+ const budget = spawn?.kind === "spawned" ? spawn.budget?.maxTokens : void 0;
431
454
  const terminal = events.filter((event) => event.id === id && (event.kind === "settled" || event.kind === "cancelled")).at(-1);
432
- const first = events.find((event) => event.kind === "spawned" && event.id === id);
433
- const budget = first?.kind === "spawned" ? first.budget?.maxTokens : void 0;
434
- const tails = readWorkerEventTails(supervisorWorkersDir(dir), 8);
455
+ const startedAt = accounting ? new Date(accounting.firstObservedAt).toISOString() : discovery.rootsBegunAt[binding];
456
+ const completedAt = accounting?.settledAt === void 0 ? terminal?.at : new Date(accounting.settledAt).toISOString();
435
457
  const view = buildSupervisorView({
436
458
  id,
437
459
  task: id,
438
460
  workspaceDir: root,
439
- status: terminal?.kind === "settled" ? terminal.status : terminal?.kind === "cancelled" ? "cancelled" : "running",
461
+ status: accounting && accounting.status !== "running" ? accounting.status : terminal?.kind === "settled" ? terminal.status : terminal ? "cancelled" : "running",
440
462
  budget: budget ?? 0,
441
463
  budgetKnown: budget !== void 0,
442
- ...discovery.rootsBegunAt[i] ? { startedAt: discovery.rootsBegunAt[i] } : {},
443
- ...terminal?.at ? { completedAt: terminal.at } : {}
444
- }, dir, events.filter(isSpawnEvent), [], now, tails.value);
445
- return {
464
+ ...startedAt == null ? {} : { startedAt },
465
+ ...completedAt === void 0 ? {} : { completedAt }
466
+ }, discovery.runDir, events.filter(isSpawnEvent), accounting?.error ? [accounting.error] : [], now, tails.value, accounting);
467
+ return accounting ? view : {
446
468
  ...view,
447
469
  totals: {
448
470
  ...view.totals,
@@ -450,17 +472,18 @@ async function loadJournalTopSnapshot(rootDir, runDir, now) {
450
472
  tokensKnown: false
451
473
  }
452
474
  };
453
- })), [{
475
+ })), [...!observed ? [{
454
476
  source: "observer-journal",
455
477
  path: "observer.jsonl",
456
478
  reason: "missing"
457
- }], discovery.roots.length);
458
- } catch {
479
+ }] : [], ...tails.diagnostics], observed ? 1 : ids.length, pursuit);
480
+ } catch (error) {
481
+ const reason = isRecord(error) && typeof error.code === "string" ? "unreadable" : "invalid-state";
459
482
  return topSnapshot(root, now, [], [{
460
- source: "journal",
461
- path: "spawn-journal.jsonl",
462
- reason: "invalid-state"
463
- }]);
483
+ source,
484
+ path,
485
+ reason
486
+ }], 1, pursuit);
464
487
  }
465
488
  }
466
489
  function observerSpend(spend, gaps = []) {
@@ -471,12 +494,7 @@ function observerSpend(spend, gaps = []) {
471
494
  tokensKnown: spend !== void 0 && parsed.tokensKnown !== false && !gaps.includes("tokens")
472
495
  };
473
496
  }
474
- /**
475
- * The journal file a run dir actually carries. `createFileRunContext` — this repo's own durable
476
- * writer — names it `spawn-journal.jsonl`; `journal.jsonl` is the retired bare-event file older
477
- * runs left behind. First existing name wins; a reader that knew only the retired name would show
478
- * zero workers for every current run.
479
- */
497
+ /** Current writer-owned journal first; retain the retired name for recorded runs. */
480
498
  const journalFileNames = ["spawn-journal.jsonl", "journal.jsonl"];
481
499
  function readJournal(eventDir) {
482
500
  const path = journalFileNames.find((candidate) => existsSync(join(eventDir, candidate)));
@@ -522,11 +540,17 @@ function retainedTranscript(event) {
522
540
  harness
523
541
  } : void 0;
524
542
  }
543
+ /** The canonical spawn receipt owns the cause; the public journal view stays compatible. */
544
+ function recordedReason(event) {
545
+ const reason = event?.reason;
546
+ return typeof reason === "string" ? reason : void 0;
547
+ }
525
548
  function buildSupervisorView(state, stateDir, events, progressTail, now, workerEventTails = /* @__PURE__ */ new Map(), accounting) {
526
549
  const workers = /* @__PURE__ */ new Map();
527
550
  let driverSpend = emptySpend;
528
551
  const journalRootId = events.find((event) => event.kind === "spawned" && !event.parent && event.label === "root")?.id;
529
552
  const isRoot = (id) => id === state.id || id === journalRootId;
553
+ const rootReason = recordedReason(events.filter((event) => isRoot(event.id) && (event.kind === "settled" || event.kind === "cancelled")).at(-1));
530
554
  for (const event of events) {
531
555
  if (isRoot(event.id) || event.kind === "spawned" && !event.parent && event.label === "root") {
532
556
  if (event.kind === "metered" && event.accountingOnly !== true) driverSpend = addSpend(driverSpend, parseSpend(event.spend));
@@ -543,6 +567,8 @@ function buildSupervisorView(state, stateDir, events, progressTail, now, workerE
543
567
  if (budget) worker.budget = budget;
544
568
  } else if (event.kind === "settled" || event.kind === "cancelled") {
545
569
  if (event.at) worker.endedAt = event.at;
570
+ const reason = recordedReason(event);
571
+ if (reason !== void 0) worker.reason = reason;
546
572
  if (event.kind === "settled") {
547
573
  worker.status = event.status === "down" ? "down" : "done";
548
574
  if (event.outRef) worker.outRef = event.outRef;
@@ -551,10 +577,7 @@ function buildSupervisorView(state, stateDir, events, progressTail, now, workerE
551
577
  if (event.infra !== void 0) worker.infra = event.infra;
552
578
  const verdict = describeVerdict(event.verdict);
553
579
  if (verdict) worker.verdict = verdict;
554
- } else {
555
- worker.status = "cancelled";
556
- if (event.reason) worker.reason = event.reason;
557
- }
580
+ } else worker.status = "cancelled";
558
581
  if (event.kind === "settled" || event.spent !== void 0) {
559
582
  worker.spend = addSpend(worker.spend, parseSpend(event.spent));
560
583
  worker.metered = emptySpend;
@@ -570,12 +593,11 @@ function buildSupervisorView(state, stateDir, events, progressTail, now, workerE
570
593
  }
571
594
  }
572
595
  const workerViews = [...workers.values()].map((worker) => {
573
- const tail = workerEventTails.get(worker.label) ?? workerEventTails.get(safeWorkerFile(worker.label)) ?? workerEventTails.get(safeWorkerFile(worker.id)) ?? workerEventTails.get(worker.id);
596
+ const tail = workerEventTails.get(join(stateDir, nodeStreamFile(worker.id))) ?? workerEventTails.get(worker.label) ?? workerEventTails.get(safeWorkerFile(worker.label)) ?? workerEventTails.get(safeWorkerFile(worker.id)) ?? workerEventTails.get(worker.id);
574
597
  worker.liveTail = tail?.lines ?? [];
575
- if (tail?.file) worker.eventFile = tail.file;
576
- else {
577
- const stream = join(stateDir, nodeStreamFile(worker.id));
578
- if (existsSync(stream)) worker.eventFile = stream;
598
+ if (tail) {
599
+ worker.eventFile = tail.file;
600
+ Object.assign(worker, tail.activity);
579
601
  }
580
602
  return finalizeWorker(worker, now);
581
603
  }).sort((a, b) => compareWorker(a, b));
@@ -587,6 +609,8 @@ function buildSupervisorView(state, stateDir, events, progressTail, now, workerE
587
609
  return {
588
610
  id: state.id,
589
611
  stateDir,
612
+ ...rootReason === void 0 ? {} : { reason: rootReason },
613
+ ...workerEventTails.get(join(stateDir, ROOT_STREAM_FILE))?.activity,
590
614
  status: state.status,
591
615
  task: state.task,
592
616
  workspaceDir: state.workspaceDir,
@@ -724,11 +748,11 @@ function parseSpend(value) {
724
748
  }
725
749
  function parseBudget(value) {
726
750
  if (!isRecord(value)) return void 0;
727
- const budget = {
728
- ...typeof value.maxIterations === "number" ? { maxIterations: value.maxIterations } : {},
729
- ...typeof value.maxTokens === "number" ? { maxTokens: value.maxTokens } : {},
730
- ...typeof value.maxUsd === "number" ? { maxUsd: value.maxUsd } : {}
731
- };
751
+ const budget = selectFields(value, [
752
+ "maxIterations",
753
+ "maxTokens",
754
+ "maxUsd"
755
+ ], (entry) => typeof entry === "number");
732
756
  return Object.keys(budget).length ? budget : void 0;
733
757
  }
734
758
  function describeVerdict(value) {
@@ -750,11 +774,7 @@ function percentile(sorted, p) {
750
774
  if (sorted.length === 0) return 0;
751
775
  return sorted[Math.min(sorted.length - 1, Math.max(0, Math.ceil(sorted.length * p) - 1))] ?? 0;
752
776
  }
753
- /**
754
- * Read one source file under a run directory. An absent file is not a defect on its own — the
755
- * writer may not have reached it yet — so it reads as `undefined` with no diagnostic; only a
756
- * failed read raises one. `path` stays run-relative and never carries file contents.
757
- */
777
+ /** Missing files can predate their writer; failed reads report a path-only diagnostic. */
758
778
  function readSourceFile(file, source, path) {
759
779
  if (!existsSync(file)) return {
760
780
  value: void 0,
@@ -780,37 +800,33 @@ function tailLines(raw, maxLines) {
780
800
  if (raw === void 0) return [];
781
801
  return raw.trim().split("\n").filter(Boolean).slice(-maxLines);
782
802
  }
783
- function readWorkerEventTails(dir, maxLines) {
803
+ function readWorkerEventTails(dir) {
784
804
  const out = /* @__PURE__ */ new Map();
785
- if (!existsSync(dir)) return {
786
- value: out,
787
- diagnostics: []
805
+ const diagnostics = [];
806
+ const read = (path, key = join(dir, path)) => {
807
+ try {
808
+ if (existsSync(join(dir, path))) out.set(key, readRecordedEvents(join(dir, path)));
809
+ } catch {
810
+ diagnostics.push({
811
+ source: "worker-tail",
812
+ path,
813
+ reason: "unreadable"
814
+ });
815
+ }
788
816
  };
789
- const workersDir = basename(dir);
790
- let entries;
791
- try {
792
- entries = readdirSync(dir);
793
- } catch {
794
- return {
795
- value: out,
796
- diagnostics: [{
817
+ read(ROOT_STREAM_FILE);
818
+ for (const folder of ["workers", "streams"]) {
819
+ if (!existsSync(join(dir, folder))) continue;
820
+ try {
821
+ for (const entry of readdirSync(join(dir, folder))) if (folder === "workers" && entry.endsWith(".ndjson") && !entry.endsWith(".inbox.ndjson")) read(join(folder, entry), entry.slice(0, -7));
822
+ else if (folder === "streams" && entry.endsWith(".jsonl")) read(join(folder, entry));
823
+ } catch {
824
+ diagnostics.push({
797
825
  source: "worker-tail",
798
- path: workersDir,
826
+ path: folder,
799
827
  reason: "unreadable"
800
- }]
801
- };
802
- }
803
- const diagnostics = [];
804
- for (const entry of entries) {
805
- if (!entry.endsWith(".ndjson") || entry.endsWith(".inbox.ndjson")) continue;
806
- const file = join(dir, entry);
807
- const read = readSourceFile(file, "worker-tail", join(workersDir, entry));
808
- diagnostics.push(...read.diagnostics);
809
- const lines = tailLines(read.value, maxLines);
810
- out.set(entry.slice(0, -7), {
811
- file,
812
- lines
813
- });
828
+ });
829
+ }
814
830
  }
815
831
  return {
816
832
  value: out,
@@ -844,13 +860,12 @@ function readSupervisorState(dir) {
844
860
  }
845
861
  }
846
862
  function isSupervisorState(value) {
847
- return isRecord(value) && Object.entries({
848
- id: "string",
849
- status: "string",
850
- task: "string",
851
- workspaceDir: "string",
852
- budget: "number"
853
- }).every(([key, type]) => typeof value[key] === type);
863
+ return isRecord(value) && [
864
+ "id",
865
+ "status",
866
+ "task",
867
+ "workspaceDir"
868
+ ].every((key) => typeof value[key] === "string") && typeof value.budget === "number";
854
869
  }
855
870
  function isSpawnEvent(value) {
856
871
  return isRecord(value) && typeof value.id === "string" && typeof value.kind === "string" && [
@@ -861,9 +876,6 @@ function isSpawnEvent(value) {
861
876
  "progress"
862
877
  ].includes(value.kind);
863
878
  }
864
- function isRecord(value) {
865
- return typeof value === "object" && value !== null;
866
- }
867
879
  function isDirectory(path) {
868
880
  try {
869
881
  return statSync(path).isDirectory();
@@ -1195,4 +1207,4 @@ async function loadRunsRoot(root) {
1195
1207
  }
1196
1208
  export { numberValue as a, totalSpend as c, loadTopSnapshot as i, serveTopWeb as n, parseSpend as o, aggregateSnapshot as r, parseTime as s, runTopWeb as t };
1197
1209
 
1198
- //# sourceMappingURL=top-web-Dh1i-KLd.js.map
1210
+ //# sourceMappingURL=top-web-CUDlOKu0.js.map