@tangle-network/traces 0.23.1 → 1.0.1

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 (75) hide show
  1. package/README.md +61 -1078
  2. package/dist/analyst-model-call-JVUOYZ5V.js +20 -0
  3. package/dist/ask-2KF4KSPB.js +39 -0
  4. package/dist/check-YF6ZHPGG.js +322 -0
  5. package/dist/chunk-2XXABIRL.js +25 -0
  6. package/dist/chunk-4VKPN432.js +138 -0
  7. package/dist/chunk-6GOG5KIK.js +53 -0
  8. package/dist/chunk-7USMM4MA.js +5695 -0
  9. package/dist/chunk-CG5QHQO4.js +492 -0
  10. package/dist/chunk-CQ6UYZG6.js +1217 -0
  11. package/dist/chunk-DU534HAX.js +263 -0
  12. package/dist/chunk-FC4V25CG.js +503 -0
  13. package/dist/chunk-GLUDGUGJ.js +232 -0
  14. package/dist/chunk-GNRXKQEQ.js +120 -0
  15. package/dist/chunk-K2KH2HHD.js +228 -0
  16. package/dist/chunk-M3W6TZHQ.js +381 -0
  17. package/dist/chunk-O52XK7OB.js +19 -0
  18. package/dist/chunk-PYH3PL3N.js +62 -0
  19. package/dist/chunk-QLVP37YB.js +782 -0
  20. package/dist/chunk-QUHUMZ4W.js +759 -0
  21. package/dist/chunk-REO2JQJY.js +535 -0
  22. package/dist/chunk-RZL5GAWH.js +60 -0
  23. package/dist/chunk-TOCNGML5.js +273 -0
  24. package/dist/chunk-UQSAGXPH.js +208 -0
  25. package/dist/chunk-VFOLC4OX.js +407 -0
  26. package/dist/chunk-WF7PTDCZ.js +3921 -0
  27. package/dist/chunk-YAX72TST.js +259 -0
  28. package/dist/cli.js +2086 -2050
  29. package/dist/diff-J4RAIOXO.js +16 -0
  30. package/dist/improvement-LCCVGGUS.js +25 -0
  31. package/dist/index.d.ts +1235 -4357
  32. package/dist/index.js +53 -1204
  33. package/dist/live-XXL7VZWB.js +14 -0
  34. package/dist/mcp-EYIDKBEK.js +85 -0
  35. package/dist/report-E3G2T5TJ.js +338 -0
  36. package/dist/run-watch-RAOOAXOF.js +649 -0
  37. package/dist/supervisor-report-RMPLLBGZ.js +29 -0
  38. package/dist/supervisor-run-context-WO6VOTGV.js +30 -0
  39. package/dist/upload-3R7GRSKS.js +20 -0
  40. package/dist/verify-safe-C5ZXZOZ2.js +8 -0
  41. package/docs/analyze.md +76 -0
  42. package/docs/ask.md +88 -0
  43. package/docs/bundle.md +40 -0
  44. package/docs/check.md +77 -0
  45. package/docs/convert.md +44 -0
  46. package/docs/develop.md +32 -0
  47. package/docs/diff.md +38 -0
  48. package/docs/facts.md +114 -0
  49. package/docs/mcp.md +36 -0
  50. package/docs/migrate-1.0.md +33 -0
  51. package/docs/parsers.md +66 -0
  52. package/docs/release-1.0.0.md +147 -0
  53. package/docs/release-1.0.1.md +4 -0
  54. package/docs/sdk.md +77 -0
  55. package/docs/select.md +70 -0
  56. package/docs/skills.md +46 -0
  57. package/docs/upload.md +47 -0
  58. package/docs/watch-jsonl.v1.example.jsonl +5 -0
  59. package/docs/watch.md +66 -0
  60. package/examples/README.md +9 -0
  61. package/examples/custom-analyst.ts +65 -74
  62. package/install.sh +22 -2
  63. package/package.json +10 -9
  64. package/skills/inspect-agent-traces/SKILL.md +47 -110
  65. package/dist/chunk-7DVZCCZI.js +0 -20983
  66. package/docs/replay-verify.md +0 -123
  67. package/docs/stream-findings.svg +0 -39
  68. package/docs/trace-analysts.md +0 -643
  69. package/examples/custom-backend.ts +0 -21
  70. package/examples/external-engines.ts +0 -62
  71. package/examples/improvement-config.mjs +0 -63
  72. package/examples/observe-and-alert.ts +0 -19
  73. package/examples/register-harness.ts +0 -26
  74. package/skills/build-trace-analyst/SKILL.md +0 -76
  75. package/skills/build-trace-analyst/agents/openai.yaml +0 -4
@@ -0,0 +1,649 @@
1
+ import {
2
+ exportTraceEvidenceFile
3
+ } from "./chunk-CG5QHQO4.js";
4
+ import {
5
+ isSynthesizedSpan
6
+ } from "./chunk-UQSAGXPH.js";
7
+ import "./chunk-CQ6UYZG6.js";
8
+ import "./chunk-RZL5GAWH.js";
9
+ import "./chunk-DU534HAX.js";
10
+ import {
11
+ ZERO_ROLL,
12
+ addSpend,
13
+ isFileRunContextDir,
14
+ mergeRolls,
15
+ readRunContextSnapshot
16
+ } from "./chunk-M3W6TZHQ.js";
17
+
18
+ // src/run-watch.ts
19
+ import { stat } from "fs/promises";
20
+
21
+ // src/run-view-format.ts
22
+ var RULE = "\u2500".repeat(76);
23
+ function int(n) {
24
+ return n.toLocaleString("en-US");
25
+ }
26
+ function ms(value) {
27
+ if (value === null) return "\u2014";
28
+ if (value < 1e3) return `${value}ms`;
29
+ if (value < 6e4) return `${(value / 1e3).toFixed(1)}s`;
30
+ const minutes = Math.floor(value / 6e4);
31
+ return `${minutes}m${String(Math.round(value % 6e4 / 1e3)).padStart(2, "0")}s`;
32
+ }
33
+ function usd(roll) {
34
+ if (roll.usdKnownRecords === 0) {
35
+ return roll.usdUnknownRecords === 0 ? "no spend recorded" : `cost unknown (${roll.usdUnknownRecords} unpriced)`;
36
+ }
37
+ const priced = zeroPricedCost(roll) ? `$${roll.usd.toFixed(4)} (zero across ${roll.usdKnownRecords} priced)` : `$${roll.usd.toFixed(4)}`;
38
+ return roll.usdUnknownRecords === 0 ? priced : `${priced} + ${roll.usdUnknownRecords} unpriced`;
39
+ }
40
+ function zeroPricedCost(roll) {
41
+ return roll.usdKnownRecords > 0 && roll.usd === 0 && roll.tokensIn + roll.tokensOut > 0;
42
+ }
43
+ function tokens(roll) {
44
+ return `in ${int(roll.tokensIn)} \xB7 out ${int(roll.tokensOut)}`;
45
+ }
46
+ var STATUS_MARK = {
47
+ pending: "\xB7",
48
+ acquiring: "\u25D0",
49
+ running: "\u25B6",
50
+ done: "\u2713",
51
+ failed: "\u2717",
52
+ cancelled: "\u2298"
53
+ };
54
+ function connectors(depth, lastAtDepth) {
55
+ if (depth === 0) return { head: "\u25CF ", detail: " " };
56
+ let stem = "";
57
+ for (let level = 1; level < depth; level++) stem += lastAtDepth[level] ? " " : "\u2502 ";
58
+ const isLast = lastAtDepth[depth] ?? true;
59
+ return { head: `${stem}${isLast ? "\u2514\u2500 " : "\u251C\u2500 "}`, detail: `${stem}${isLast ? " " : "\u2502 "} ` };
60
+ }
61
+ function forEachTreeNode(nodes, parentKey, selfKey, visit) {
62
+ const lastChildOf = /* @__PURE__ */ new Map();
63
+ for (const node of nodes) lastChildOf.set(parentKey(node), selfKey(node));
64
+ const lastAtDepth = [];
65
+ for (const node of nodes) {
66
+ lastAtDepth[node.depth] = lastChildOf.get(parentKey(node)) === selfKey(node);
67
+ lastAtDepth.length = node.depth + 1;
68
+ visit(node, lastAtDepth);
69
+ }
70
+ }
71
+ function wrap(text, indent) {
72
+ const width = Math.max(40, (process.stdout.columns ?? 100) - indent - 2);
73
+ const pad = " ".repeat(indent);
74
+ const out = [];
75
+ for (const paragraph of text.split("\n")) {
76
+ let line = "";
77
+ for (const word of paragraph.split(/\s+/).filter(Boolean)) {
78
+ if (line.length > 0 && line.length + 1 + word.length > width) {
79
+ out.push(pad + line);
80
+ line = word;
81
+ } else line = line.length === 0 ? word : `${line} ${word}`;
82
+ }
83
+ out.push(pad + line);
84
+ }
85
+ return out;
86
+ }
87
+
88
+ // src/run-span-tree.ts
89
+ import { readdir } from "fs/promises";
90
+ import { join } from "path";
91
+ import {
92
+ firstNumberAttr,
93
+ LLM_COST_ATTR_KEYS,
94
+ LLM_INPUT_TOKEN_ATTR_KEYS,
95
+ LLM_OUTPUT_TOKEN_ATTR_KEYS,
96
+ SPAN_KIND_ATTR_KEYS
97
+ } from "@tangle-network/agent-eval/trace-attributes";
98
+ var AGENT_NAME_ATTR_KEYS = ["agent.name", "inference.agent_name", "gen_ai.agent.name"];
99
+ var SERVICE_NAME_ATTR_KEYS = ["service.name", "tangle.runtime"];
100
+ var AGENT_OPERATIONS = /* @__PURE__ */ new Set(["invoke_agent", "invoke_workflow"]);
101
+ var OPERATION_ATTR_KEY = "gen_ai.operation.name";
102
+ function firstStringAttr(attributes, keys) {
103
+ for (const key of keys) {
104
+ const value = attributes[key];
105
+ if (typeof value === "string" && value.length > 0) return value;
106
+ }
107
+ return null;
108
+ }
109
+ var EMPTY_SPAN_RUN_SNAPSHOT = {
110
+ present: false,
111
+ nodes: [],
112
+ maxDepth: 0,
113
+ traceIds: [],
114
+ spanCount: 0,
115
+ startMs: null,
116
+ endMs: null,
117
+ llmSpansWithoutTokens: 0,
118
+ llmSpansWithoutCost: 0,
119
+ orphanSpans: 0,
120
+ readError: null
121
+ };
122
+ function spanKind(span) {
123
+ return firstStringAttr(span.attributes, SPAN_KIND_ATTR_KEYS);
124
+ }
125
+ function isNodeBoundary(span, enclosingNodeId, enclosingAgent) {
126
+ if (span.parent_span_id === null || enclosingNodeId === null) return true;
127
+ if (spanKind(span) === "AGENT") return true;
128
+ if (AGENT_OPERATIONS.has(String(span.attributes[OPERATION_ATTR_KEY] ?? ""))) return true;
129
+ const agent = firstStringAttr(span.attributes, AGENT_NAME_ATTR_KEYS);
130
+ return agent !== null && enclosingAgent !== null && agent !== enclosingAgent;
131
+ }
132
+ function spanSpend(span) {
133
+ const cost = firstNumberAttr(span.attributes, LLM_COST_ATTR_KEYS);
134
+ return {
135
+ iterations: 0,
136
+ tokens: {
137
+ input: firstNumberAttr(span.attributes, LLM_INPUT_TOKEN_ATTR_KEYS) ?? 0,
138
+ output: firstNumberAttr(span.attributes, LLM_OUTPUT_TOKEN_ATTR_KEYS) ?? 0
139
+ },
140
+ usd: cost ?? 0,
141
+ ...cost === null ? { usdKnown: false } : {},
142
+ ms: 0
143
+ };
144
+ }
145
+ function epoch(value) {
146
+ const parsed = Date.parse(value);
147
+ return Number.isFinite(parsed) ? parsed : 0;
148
+ }
149
+ function buildSpanRunTree(spans, source) {
150
+ if (spans.length === 0) return { ...EMPTY_SPAN_RUN_SNAPSHOT, source };
151
+ const byId = /* @__PURE__ */ new Map();
152
+ for (const span of spans) byId.set(spanKey(span), span);
153
+ const childrenOf = /* @__PURE__ */ new Map();
154
+ let orphanSpans = 0;
155
+ for (const span of spans) {
156
+ const parentKey = span.parent_span_id === null ? null : byId.has(`${span.trace_id}\0${span.parent_span_id}`) ? `${span.trace_id}\0${span.parent_span_id}` : null;
157
+ if (span.parent_span_id !== null && parentKey === null) orphanSpans += 1;
158
+ const bucket = childrenOf.get(parentKey);
159
+ if (bucket) bucket.push(span);
160
+ else childrenOf.set(parentKey, [span]);
161
+ }
162
+ for (const bucket of childrenOf.values()) {
163
+ bucket.sort((a, b) => epoch(a.start_time) - epoch(b.start_time) || a.span_id.localeCompare(b.span_id));
164
+ }
165
+ const building = /* @__PURE__ */ new Map();
166
+ const order = [];
167
+ let llmSpansWithoutTokens = 0;
168
+ let llmSpansWithoutCost = 0;
169
+ const walk = (span, enclosingNodeId, enclosingAgent, depth) => {
170
+ const key = spanKey(span);
171
+ const agent = firstStringAttr(span.attributes, AGENT_NAME_ATTR_KEYS) ?? enclosingAgent;
172
+ let nodeId = enclosingNodeId;
173
+ let nodeAgent = enclosingAgent;
174
+ let childDepth = depth;
175
+ if (isNodeBoundary(span, enclosingNodeId, enclosingAgent)) {
176
+ const node = {
177
+ span,
178
+ parentNodeId: enclosingNodeId,
179
+ depth,
180
+ agent,
181
+ driver: ZERO_ROLL,
182
+ toolCalls: 0,
183
+ startMs: epoch(span.start_time),
184
+ endMs: epoch(span.end_time),
185
+ errors: [],
186
+ childNodeIds: []
187
+ };
188
+ building.set(key, node);
189
+ order.push(key);
190
+ if (enclosingNodeId !== null) building.get(enclosingNodeId)?.childNodeIds.push(key);
191
+ nodeId = key;
192
+ nodeAgent = agent;
193
+ childDepth = depth + 1;
194
+ }
195
+ const host = nodeId === null ? void 0 : building.get(nodeId);
196
+ if (host !== void 0) {
197
+ const kind = spanKind(span);
198
+ if (kind === "LLM") {
199
+ host.driver = addSpend(host.driver, spanSpend(span));
200
+ if (firstNumberAttr(span.attributes, LLM_INPUT_TOKEN_ATTR_KEYS) === null && firstNumberAttr(span.attributes, LLM_OUTPUT_TOKEN_ATTR_KEYS) === null) {
201
+ llmSpansWithoutTokens += 1;
202
+ }
203
+ if (firstNumberAttr(span.attributes, LLM_COST_ATTR_KEYS) === null) llmSpansWithoutCost += 1;
204
+ } else if (kind === "TOOL" && !isSynthesizedSpan(span.attributes)) {
205
+ host.toolCalls += 1;
206
+ }
207
+ host.startMs = Math.min(host.startMs, epoch(span.start_time));
208
+ host.endMs = Math.max(host.endMs, epoch(span.end_time));
209
+ if (span.status.code === "ERROR") {
210
+ host.errors.push(`${span.name}: ${span.status.message ?? "ERROR"}`);
211
+ }
212
+ }
213
+ for (const child of childrenOf.get(key) ?? []) walk(child, nodeId, nodeAgent, childDepth);
214
+ };
215
+ for (const root of childrenOf.get(null) ?? []) walk(root, null, null, 0);
216
+ const subtotal = /* @__PURE__ */ new Map();
217
+ const subtotalOf = (key) => {
218
+ const cached = subtotal.get(key);
219
+ if (cached !== void 0) return cached;
220
+ const node = building.get(key);
221
+ if (node === void 0) return ZERO_ROLL;
222
+ subtotal.set(key, ZERO_ROLL);
223
+ let total = node.driver;
224
+ for (const child of node.childNodeIds) total = mergeRolls(total, subtotalOf(child));
225
+ subtotal.set(key, total);
226
+ return total;
227
+ };
228
+ const nodes = order.map((key) => {
229
+ const node = building.get(key);
230
+ let children = ZERO_ROLL;
231
+ for (const child of node.childNodeIds) children = mergeRolls(children, subtotalOf(child));
232
+ return {
233
+ traceId: node.span.trace_id,
234
+ spanId: node.span.span_id,
235
+ parentNodeId: node.parentNodeId === null ? null : building.get(node.parentNodeId).span.span_id,
236
+ label: node.agent ?? node.span.name,
237
+ depth: node.depth,
238
+ role: node.childNodeIds.length > 0 ? "driver" : "leaf",
239
+ status: node.errors.length > 0 || node.span.status.code === "ERROR" ? "failed" : node.span.status.code === "UNSET" ? "running" : "done",
240
+ runtime: firstStringAttr(node.span.attributes, SERVICE_NAME_ATTR_KEYS),
241
+ driver: node.driver,
242
+ children,
243
+ toolCalls: node.toolCalls,
244
+ startMs: node.startMs,
245
+ endMs: node.endMs,
246
+ wallMs: Math.max(0, node.endMs - node.startMs),
247
+ errors: node.errors
248
+ };
249
+ });
250
+ return {
251
+ source,
252
+ present: true,
253
+ nodes,
254
+ maxDepth: nodes.reduce((max, n) => Math.max(max, n.depth), 0),
255
+ traceIds: [...new Set(spans.map((s) => s.trace_id))],
256
+ spanCount: spans.length,
257
+ startMs: nodes.reduce((min, n) => min === null ? n.startMs : Math.min(min, n.startMs), null),
258
+ endMs: nodes.reduce((max, n) => max === null ? n.endMs : Math.max(max, n.endMs), null),
259
+ llmSpansWithoutTokens,
260
+ llmSpansWithoutCost,
261
+ orphanSpans,
262
+ readError: null
263
+ };
264
+ }
265
+ function spanKey(span) {
266
+ return `${span.trace_id}\0${span.span_id}`;
267
+ }
268
+ async function readSpanRunSnapshot(path) {
269
+ try {
270
+ const exported = await exportTraceEvidenceFile(path);
271
+ return buildSpanRunTree(exported.spans, path);
272
+ } catch (error) {
273
+ return {
274
+ ...EMPTY_SPAN_RUN_SNAPSHOT,
275
+ source: path,
276
+ readError: error instanceof Error ? error.message : String(error)
277
+ };
278
+ }
279
+ }
280
+ function spanRunTotals(snapshot) {
281
+ let driver = ZERO_ROLL;
282
+ let leaves = ZERO_ROLL;
283
+ for (const node of snapshot.nodes) {
284
+ if (node.role === "driver") driver = mergeRolls(driver, node.driver);
285
+ else leaves = mergeRolls(leaves, node.driver);
286
+ }
287
+ return { driver, leaves };
288
+ }
289
+ function renderSpanRunSnapshot(snapshot) {
290
+ const lines = [];
291
+ lines.push(`spans ${snapshot.source}`);
292
+ if (snapshot.readError !== null) {
293
+ lines.push(` unreadable: ${snapshot.readError}`);
294
+ return lines.join("\n");
295
+ }
296
+ if (!snapshot.present || snapshot.nodes.length === 0) {
297
+ lines.push(" no spans yet \u2014 the emitter has written nothing this view can place");
298
+ return lines.join("\n");
299
+ }
300
+ const totals = spanRunTotals(snapshot);
301
+ lines.push(
302
+ ` ${snapshot.nodes.length} node(s) \xB7 depth ${snapshot.maxDepth} \xB7 ${int(snapshot.spanCount)} span(s) \xB7 ${snapshot.traceIds.length} trace(s) \xB7 elapsed ${ms(snapshot.endMs === null || snapshot.startMs === null ? null : snapshot.endMs - snapshot.startMs)}`
303
+ );
304
+ const delegating = snapshot.nodes.some((node) => node.role === "driver");
305
+ lines.push(` driver inference ${tokens(totals.driver)} \xB7 ${usd(totals.driver)}`);
306
+ lines.push(` leaf inference ${tokens(totals.leaves)} \xB7 ${usd(totals.leaves)}`);
307
+ if (!delegating) {
308
+ lines.push(" (no delegation in these spans: every turn belongs to one agent, so the driver half is empty)");
309
+ }
310
+ lines.push("");
311
+ forEachTreeNode(
312
+ snapshot.nodes,
313
+ (n) => n.parentNodeId,
314
+ (n) => n.spanId,
315
+ (node, lastAtDepth) => {
316
+ const { head: prefix, detail } = connectors(node.depth, lastAtDepth);
317
+ const mark = node.status === "failed" ? "\u2717" : node.status === "running" ? "\u25B6" : "\u2713";
318
+ lines.push(
319
+ [
320
+ `${prefix}${mark} ${node.label}`,
321
+ `[${node.status}]`,
322
+ node.role,
323
+ node.runtime ?? "no-runtime",
324
+ "budget not in spans",
325
+ `wall ${ms(node.wallMs)}`
326
+ ].join(" ")
327
+ );
328
+ lines.push(
329
+ `${detail}driver ${node.driver.records} turn(s) \xB7 ${tokens(node.driver)} \xB7 ${usd(node.driver)} \xB7 ${node.toolCalls} tool call(s)`
330
+ );
331
+ if (node.role === "driver") {
332
+ lines.push(
333
+ `${detail}children ${node.children.records} turn(s) \xB7 ${tokens(node.children)} \xB7 ${usd(node.children)}`
334
+ );
335
+ }
336
+ for (const error of node.errors.slice(0, 3)) lines.push(`${detail}error ${error}`);
337
+ }
338
+ );
339
+ const problems = [];
340
+ if (snapshot.llmSpansWithoutCost > 0) {
341
+ problems.push(
342
+ `${snapshot.llmSpansWithoutCost} of the LLM span(s) carry no cost attribute \u2014 that spend is UNKNOWN, not $0 (this emitter writes tokens without a price)`
343
+ );
344
+ }
345
+ if (snapshot.llmSpansWithoutTokens > 0) {
346
+ problems.push(`${snapshot.llmSpansWithoutTokens} LLM span(s) carry no token count`);
347
+ }
348
+ if (snapshot.orphanSpans > 0) {
349
+ problems.push(
350
+ `${snapshot.orphanSpans} span(s) name a parent_span_id absent from this file \u2014 re-rooted, not dropped`
351
+ );
352
+ }
353
+ problems.push(
354
+ "spans carry no authored budget in either the OpenInference or the GenAI vocabulary; point this at a run directory with a spawn journal to see ceilings and settlement"
355
+ );
356
+ lines.push("");
357
+ for (const problem of problems) lines.push(` ! ${problem}`);
358
+ return lines.join("\n");
359
+ }
360
+ var SPAN_FILE_PATTERN = /\.(?:otlp|spans|traces|openinference)\.(?:jsonl|ndjson)$|^traces?\.jsonl$|^spans\.jsonl$/i;
361
+ async function findSpanSourceFiles(dir) {
362
+ const found = [];
363
+ const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
364
+ for (const entry of entries) {
365
+ const child = join(dir, entry.name);
366
+ if (entry.isFile()) {
367
+ if (SPAN_FILE_PATTERN.test(entry.name)) found.push(child);
368
+ continue;
369
+ }
370
+ if (!entry.isDirectory()) continue;
371
+ const nested = await readdir(child, { withFileTypes: true }).catch(() => []);
372
+ for (const inner of nested) {
373
+ if (inner.isFile() && SPAN_FILE_PATTERN.test(inner.name)) found.push(join(child, inner.name));
374
+ }
375
+ }
376
+ return found.sort();
377
+ }
378
+
379
+ // src/supervisor-run-watch.ts
380
+ import { readdir as readdir2 } from "fs/promises";
381
+ import { join as join2 } from "path";
382
+ function budget(node) {
383
+ const parts = [`${int(node.budget.maxIterations)}it`, `${int(node.budget.maxTokens)}tok`];
384
+ if (node.budget.maxUsd !== void 0) parts.push(`$${node.budget.maxUsd.toFixed(2)}`);
385
+ if (node.budget.deadlineMs !== void 0) parts.push(ms(node.budget.deadlineMs));
386
+ return parts.join("/");
387
+ }
388
+ function budgetUse(node, spent) {
389
+ const parts = [];
390
+ if (node.budget.maxIterations > 0 && spent.iterations > 0) {
391
+ parts.push(`${Math.round(spent.iterations / node.budget.maxIterations * 100)}% it`);
392
+ }
393
+ const used = spent.tokensIn + spent.tokensOut;
394
+ if (node.budget.maxTokens > 0 && used > 0) {
395
+ parts.push(`${Math.round(used / node.budget.maxTokens * 100)}% tok`);
396
+ }
397
+ return parts.length === 0 ? "" : ` \xB7 ${parts.join(", ")} of budget`;
398
+ }
399
+ function renderNode(node, lastAtDepth, lines) {
400
+ const mark = STATUS_MARK[node.status] ?? "?";
401
+ const { head: prefix, detail } = connectors(node.depth, lastAtDepth);
402
+ const head = [
403
+ `${prefix}${mark} ${node.label}`,
404
+ `[${node.status}]`,
405
+ node.role,
406
+ node.runtime ?? "no-runtime",
407
+ `budget ${budget(node)}`
408
+ ];
409
+ if (node.wallMs !== null) head.push(`wall ${ms(node.wallMs)}`);
410
+ lines.push(head.join(" "));
411
+ if (node.driver.records > 0 || node.role === "supervisor") {
412
+ lines.push(
413
+ `${detail}driver ${node.driver.records} turn(s) \xB7 ${tokens(node.driver)} \xB7 ${usd(node.driver)}` + budgetUse(node, node.driver)
414
+ );
415
+ }
416
+ if (node.children.records > 0 || node.role === "supervisor") {
417
+ lines.push(
418
+ `${detail}children ${node.children.records} settled \xB7 ${tokens(node.children)} \xB7 ${usd(node.children)}`
419
+ );
420
+ }
421
+ if (node.settled.records > 0) {
422
+ lines.push(
423
+ `${detail}settled ${node.settled.iterations}it \xB7 ${tokens(node.settled)} \xB7 ${usd(node.settled)}` + budgetUse(node, node.settled)
424
+ );
425
+ }
426
+ if (node.cancelReason !== null) lines.push(`${detail}cancelled: ${node.cancelReason}`);
427
+ if (node.outRef !== null) lines.push(`${detail}out ${node.outRef}`);
428
+ }
429
+ function renderQuestion(q, lines) {
430
+ const mark = q.blocking ? "BLOCKING" : q.urgency ?? "question";
431
+ lines.push(` \u26A0 ${mark} from ${q.from ?? "unknown"}${q.status === null ? "" : ` [${q.status}]`}`);
432
+ for (const line of wrap(q.question, 4)) lines.push(line);
433
+ if (q.reason !== null) for (const line of wrap(`why: ${q.reason}`, 4)) lines.push(line);
434
+ }
435
+ function renderRunContextSnapshot(snapshot) {
436
+ const lines = [];
437
+ if (!snapshot.present) {
438
+ lines.push(`run ${snapshot.runDir}`);
439
+ lines.push(" no spawn-journal.jsonl yet \u2014 the run has not begun writing");
440
+ return lines.join("\n");
441
+ }
442
+ const totals = runTotals(snapshot);
443
+ lines.push(`run ${snapshot.runId ?? snapshot.runDir}`);
444
+ lines.push(
445
+ ` ${snapshot.nodes.length} node(s) \xB7 depth ${snapshot.maxDepth} \xB7 elapsed ${ms(elapsed(snapshot))}${snapshot.terminal ? "" : " \xB7 live"}`
446
+ );
447
+ lines.push(` driver total ${tokens(totals.driver)} \xB7 ${usd(totals.driver)}`);
448
+ lines.push(` worker total ${tokens(totals.workers)} \xB7 ${usd(totals.workers)}`);
449
+ lines.push("");
450
+ if (snapshot.nodes.length === 0) lines.push(" (no node spawned yet)");
451
+ forEachTreeNode(
452
+ snapshot.nodes,
453
+ (n) => n.parent,
454
+ (n) => n.id,
455
+ (node, lastAtDepth) => renderNode(node, lastAtDepth, lines)
456
+ );
457
+ if (snapshot.questions.length > 0) {
458
+ lines.push("");
459
+ for (const q of snapshot.questions) renderQuestion(q, lines);
460
+ }
461
+ if (snapshot.steers.length > 0) {
462
+ lines.push("");
463
+ for (const steer of snapshot.steers) {
464
+ lines.push(` \u2192 steer ${steer.messageId ?? "(no id)"} to ${steer.toWorker ?? "(no target)"}`);
465
+ for (const line of wrap(steer.instruction, 4)) lines.push(line);
466
+ }
467
+ }
468
+ const problems = [];
469
+ if (snapshot.invalidJournalRows > 0) problems.push(`${snapshot.invalidJournalRows} unreadable journal row(s)`);
470
+ if (snapshot.retiredWaitRows > 0) {
471
+ problems.push(`${snapshot.retiredWaitRows} wait-state journal row(s) from an older Runtime left out of the tree`);
472
+ }
473
+ if (snapshot.invalidCoordinationRows > 0) {
474
+ problems.push(`${snapshot.invalidCoordinationRows} unreadable coordination row(s)`);
475
+ }
476
+ if (snapshot.treeError !== null) problems.push(snapshot.treeError);
477
+ if (snapshot.partialTail) problems.push("an artifact ends mid-line \u2014 the writer is still appending");
478
+ if (zeroPricedCost(totals.driver) || zeroPricedCost(totals.workers)) {
479
+ problems.push(
480
+ "spend recorded $0 with tokens spent: usdKnown was never set false, so this reads as FREE rather than unmetered. A harness-billed brain (a CLI on a subscription) looks exactly like this."
481
+ );
482
+ }
483
+ const unsettled = snapshot.nodes.filter(
484
+ (node) => node.status !== "done" && node.status !== "failed" && node.status !== "cancelled"
485
+ );
486
+ if (snapshot.terminal && unsettled.length > 0) {
487
+ problems.push(
488
+ `${unsettled.length} node(s) have no journaled settlement (${unsettled.map((n) => n.label).join(", ")}) \u2014 a driver reports its outcome through result.json, not the spawn journal`
489
+ );
490
+ }
491
+ if (problems.length > 0) {
492
+ lines.push("");
493
+ for (const problem of problems) lines.push(` ! ${problem}`);
494
+ }
495
+ if (snapshot.result !== null) {
496
+ lines.push("");
497
+ lines.push(
498
+ `RESULT ${snapshot.result.kind ?? "unknown-kind"}${snapshot.result.reason === null ? "" : ` \xB7 ${snapshot.result.reason}`}`
499
+ );
500
+ if (snapshot.result.errorMessage !== null) {
501
+ lines.push(` driver error (${snapshot.result.errorName ?? "Error"}):`);
502
+ for (const line of wrap(snapshot.result.errorMessage, 4)) lines.push(line);
503
+ }
504
+ }
505
+ return lines.join("\n");
506
+ }
507
+ function runTotals(snapshot) {
508
+ let driver = ZERO_ROLL;
509
+ let workers = ZERO_ROLL;
510
+ for (const node of snapshot.nodes) {
511
+ driver = mergeRolls(driver, node.driver);
512
+ if (node.role === "worker") workers = mergeRolls(workers, node.settled);
513
+ }
514
+ return { driver, workers };
515
+ }
516
+ function elapsed(snapshot) {
517
+ if (snapshot.begunAt === null) return null;
518
+ const end = snapshot.terminal ? snapshot.lastEventAt ?? Date.now() : Date.now();
519
+ return Math.max(0, end - snapshot.begunAt);
520
+ }
521
+ async function resolveRunContextDir(dir) {
522
+ if (await isFileRunContextDir(dir)) return dir;
523
+ const entries = await readdir2(dir, { withFileTypes: true }).catch(() => null);
524
+ if (entries === null) throw new Error(`cannot read ${dir}`);
525
+ const candidates = [];
526
+ for (const entry of entries) {
527
+ if (!entry.isDirectory()) continue;
528
+ const child = join2(dir, entry.name);
529
+ if (await isFileRunContextDir(child)) candidates.push(child);
530
+ }
531
+ if (candidates.length === 1) return candidates[0];
532
+ if (candidates.length === 0) {
533
+ throw new Error(
534
+ `no spawn-journal.jsonl in ${dir} or any immediate subdirectory \u2014 point at a directory written by agent-runtime createFileRunContext()`
535
+ );
536
+ }
537
+ throw new Error(
538
+ `${candidates.length} run directories under ${dir}; name one:
539
+ ${candidates.join("\n ")}`
540
+ );
541
+ }
542
+
543
+ // src/run-watch.ts
544
+ async function resolveRunWatchTarget(target) {
545
+ const info = await stat(target).catch(() => null);
546
+ if (info === null) throw new Error(`cannot read ${target}`);
547
+ if (info.isFile()) return { runDir: null, spanFiles: [target], requested: target };
548
+ const spanFiles = await findSpanSourceFiles(target);
549
+ if (await isFileRunContextDir(target)) return { runDir: target, spanFiles, requested: target };
550
+ const nested = await resolveRunContextDir(target).catch(() => null);
551
+ if (nested !== null) {
552
+ const nestedSpans = await findSpanSourceFiles(nested);
553
+ return {
554
+ runDir: nested,
555
+ spanFiles: [.../* @__PURE__ */ new Set([...spanFiles, ...nestedSpans])].sort(),
556
+ requested: target
557
+ };
558
+ }
559
+ if (spanFiles.length > 0) return { runDir: null, spanFiles, requested: target };
560
+ throw new Error(
561
+ `nothing to watch in ${target} \u2014 found neither spawn-journal.jsonl (agent-runtime createFileRunContext, at this level or one below) nor an OTLP span file (*.otlp.jsonl / *.spans.jsonl / *.traces.jsonl / *.openinference.jsonl)`
562
+ );
563
+ }
564
+ async function readRunWatchSnapshot(target) {
565
+ const journal = target.runDir === null ? null : await readRunContextSnapshot(target.runDir);
566
+ const spans = await Promise.all(target.spanFiles.map((file) => readSpanRunSnapshot(file)));
567
+ return {
568
+ target,
569
+ journal,
570
+ spans,
571
+ // A span file alone never declares itself finished — an emitter can always
572
+ // append one more span — so only the journal's `result.json` ends a watch.
573
+ terminal: journal !== null && journal.terminal
574
+ };
575
+ }
576
+ function sourceHeader(snapshot) {
577
+ const lines = ["sources"];
578
+ if (snapshot.journal !== null) {
579
+ lines.push(
580
+ ` journal ${snapshot.target.runDir}/spawn-journal.jsonl \u2192 authored budget, settled spend, settlement status (authoritative)`
581
+ );
582
+ }
583
+ for (const span of snapshot.spans) {
584
+ lines.push(
585
+ ` spans ${span.source} \u2192 tree shape and per-turn detail${span.readError === null ? "" : " (unreadable)"}`
586
+ );
587
+ }
588
+ if (snapshot.journal === null) {
589
+ lines.push(" (no spawn journal here: budget and settlement are UNAVAILABLE, not zero)");
590
+ }
591
+ if (snapshot.spans.length === 0) {
592
+ lines.push(" (no OTLP span file here: per-turn shape is UNAVAILABLE, not empty)");
593
+ }
594
+ return lines;
595
+ }
596
+ function renderRunWatchSnapshot(snapshot) {
597
+ const blocks = [sourceHeader(snapshot).join("\n")];
598
+ if (snapshot.journal !== null) blocks.push(renderRunContextSnapshot(snapshot.journal));
599
+ for (const span of snapshot.spans) blocks.push(renderSpanRunSnapshot(span));
600
+ return blocks.join("\n\n");
601
+ }
602
+ async function watchRunTarget(target, opts = {}) {
603
+ const write = opts.write ?? ((text) => process.stdout.write(text));
604
+ const intervalMs = opts.intervalMs ?? 2e3;
605
+ let previous = "";
606
+ for (; ; ) {
607
+ const snapshot = await readRunWatchSnapshot(target);
608
+ const rendered = renderRunWatchSnapshot(snapshot);
609
+ if (opts.once) {
610
+ write(`${rendered}
611
+ `);
612
+ return snapshot;
613
+ }
614
+ if (rendered !== previous) {
615
+ write(`
616
+ ${RULE}
617
+ ${(/* @__PURE__ */ new Date()).toISOString()}
618
+ ${rendered}
619
+ `);
620
+ previous = rendered;
621
+ }
622
+ if (snapshot.terminal && opts.follow !== true) return snapshot;
623
+ if (aborted(opts.signal)) return snapshot;
624
+ await sleep(intervalMs, opts.signal);
625
+ if (aborted(opts.signal)) return await readRunWatchSnapshot(target);
626
+ }
627
+ }
628
+ function aborted(signal) {
629
+ return signal !== void 0 && signal.aborted;
630
+ }
631
+ function sleep(msDelay, signal) {
632
+ return new Promise((resolve) => {
633
+ const timer = setTimeout(resolve, msDelay);
634
+ signal?.addEventListener(
635
+ "abort",
636
+ () => {
637
+ clearTimeout(timer);
638
+ resolve();
639
+ },
640
+ { once: true }
641
+ );
642
+ });
643
+ }
644
+ export {
645
+ readRunWatchSnapshot,
646
+ renderRunWatchSnapshot,
647
+ resolveRunWatchTarget,
648
+ watchRunTarget
649
+ };
@@ -0,0 +1,29 @@
1
+ // src/supervisor-report.ts
2
+ import { mkdir, writeFile } from "fs/promises";
3
+ import { dirname, resolve } from "path";
4
+ async function writeSupervisorReport(runDir, out) {
5
+ const { analyzeSupervisorRun, findSupervisorRunDirs, isRuntimeSupervisorRunDir, renderSupervisorRollupMarkdown, renderSupervisorRunMarkdown, rollupSupervisorRuns } = await import("@tangle-network/agent-eval/supervisor-run");
6
+ const { fileRunContextSupervisorRunReader } = await import("./supervisor-run-context-WO6VOTGV.js");
7
+ const single = await isRuntimeSupervisorRunDir(runDir);
8
+ const runDirs = single ? [runDir] : await findSupervisorRunDirs(runDir);
9
+ if (runDirs.length === 0) {
10
+ throw new Error(
11
+ `no supervisor run found at ${runDir} \u2014 expected a Runtime run containing spawn-journal.jsonl, observer.jsonl, or failure.json, or a parent directory containing such runs`
12
+ );
13
+ }
14
+ const reports = await Promise.all(
15
+ runDirs.map((dir) => analyzeSupervisorRun(fileRunContextSupervisorRunReader(dir)))
16
+ );
17
+ const markdown = single ? renderSupervisorRunMarkdown(reports[0]) : renderSupervisorRollupMarkdown(rollupSupervisorRuns(reports), `Supervisor rollup \u2014 ${runDir}`);
18
+ if (out) {
19
+ const path = resolve(out, "report.md");
20
+ await mkdir(dirname(path), { recursive: true });
21
+ await writeFile(path, markdown, "utf8");
22
+ console.log(`supervisor report \u2192 ${path} (${runDirs.length === 1 ? "1 run" : `${runDirs.length} runs`})`);
23
+ } else {
24
+ console.log(markdown);
25
+ }
26
+ }
27
+ export {
28
+ writeSupervisorReport
29
+ };