@obversa/runtime 0.1.0 → 0.2.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.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+ import { readRecordFile, summarizeRecord, renderRecord } from '../chunk-XDMDT67Q.js';
3
+
4
+ // src/bin/record.ts
5
+ var args = process.argv.slice(2);
6
+ var path = args.find((a) => !a.startsWith("--"));
7
+ var flags = args.filter((a) => a.startsWith("--"));
8
+ var json = flags.includes("--json");
9
+ if (!path || flags.some((f) => f !== "--json")) {
10
+ process.stderr.write("usage: obversa-record <path> [--json]\n");
11
+ process.exitCode = 1;
12
+ } else {
13
+ const { events, unreadable } = readRecordFile(path);
14
+ process.stdout.write(
15
+ json ? `${JSON.stringify(summarizeRecord(events), null, 2)}
16
+ ` : renderRecord(events)
17
+ );
18
+ if (unreadable > 0) {
19
+ process.stderr.write(`obversa-record: ${unreadable} line(s) could not be read as JSON
20
+ `);
21
+ }
22
+ }
23
+ //# sourceMappingURL=record.js.map
24
+ //# sourceMappingURL=record.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/bin/record.ts"],"names":[],"mappings":";;;;AAGA,IAAM,IAAA,GAAO,OAAA,CAAQ,IAAA,CAAK,KAAA,CAAM,CAAC,CAAA;AACjC,IAAM,IAAA,GAAO,KAAK,IAAA,CAAK,CAAC,MAAM,CAAC,CAAA,CAAE,UAAA,CAAW,IAAI,CAAC,CAAA;AACjD,IAAM,KAAA,GAAQ,KAAK,MAAA,CAAO,CAAC,MAAM,CAAA,CAAE,UAAA,CAAW,IAAI,CAAC,CAAA;AACnD,IAAM,IAAA,GAAO,KAAA,CAAM,QAAA,CAAS,QAAQ,CAAA;AAEpC,IAAI,CAAC,QAAQ,KAAA,CAAM,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,KAAM,QAAQ,CAAA,EAAG;AAC9C,EAAA,OAAA,CAAQ,MAAA,CAAO,MAAM,yCAAyC,CAAA;AAC9D,EAAA,OAAA,CAAQ,QAAA,GAAW,CAAA;AACrB,CAAA,MAAO;AACL,EAAA,MAAM,EAAE,MAAA,EAAQ,UAAA,EAAW,GAAI,eAAe,IAAI,CAAA;AAClD,EAAA,OAAA,CAAQ,MAAA,CAAO,KAAA;AAAA,IACb,IAAA,GAAO,GAAG,IAAA,CAAK,SAAA,CAAU,gBAAgB,MAAM,CAAA,EAAG,IAAA,EAAM,CAAC,CAAC;AAAA,CAAA,GAAO,aAAa,MAAM;AAAA,GACtF;AACA,EAAA,IAAI,aAAa,CAAA,EAAG;AAClB,IAAA,OAAA,CAAQ,MAAA,CAAO,KAAA,CAAM,CAAA,gBAAA,EAAmB,UAAU,CAAA;AAAA,CAAsC,CAAA;AAAA,EAC1F;AACF","file":"record.js","sourcesContent":["#!/usr/bin/env node\nimport { readRecordFile, renderRecord, summarizeRecord } from '../runtime/render-record.js';\n\nconst args = process.argv.slice(2);\nconst path = args.find((a) => !a.startsWith('--'));\nconst flags = args.filter((a) => a.startsWith('--'));\nconst json = flags.includes('--json');\n\nif (!path || flags.some((f) => f !== '--json')) {\n process.stderr.write('usage: obversa-record <path> [--json]\\n');\n process.exitCode = 1;\n} else {\n const { events, unreadable } = readRecordFile(path);\n process.stdout.write(\n json ? `${JSON.stringify(summarizeRecord(events), null, 2)}\\n` : renderRecord(events),\n );\n if (unreadable > 0) {\n process.stderr.write(`obversa-record: ${unreadable} line(s) could not be read as JSON\\n`);\n }\n}\n"]}
@@ -0,0 +1,598 @@
1
+ import { mkdirSync, writeFileSync, readFileSync, appendFileSync } from 'fs';
2
+ import { randomBytes } from 'crypto';
3
+ import { homedir } from 'os';
4
+ import { join } from 'path';
5
+
6
+ // src/runtime/supervisor.ts
7
+ var NOISE = /* @__PURE__ */ new Set([
8
+ "engine:text",
9
+ "engine:thinking"
10
+ ]);
11
+ function runsHome() {
12
+ return join(process.env.OBVERSA_HOME ?? join(homedir(), ".obversa"), "runs");
13
+ }
14
+ function slug(value) {
15
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 32) || "run";
16
+ }
17
+ function newRunId(title) {
18
+ return `${slug(title)}-${randomBytes(3).toString("hex")}`;
19
+ }
20
+ function startSupervisor(input) {
21
+ const dir = join(runsHome(), input.runId);
22
+ const eventsPath = join(dir, "events.jsonl");
23
+ const proofsPath = join(dir, "proofs.jsonl");
24
+ const statusPath = join(dir, "status.json");
25
+ mkdirSync(dir, { recursive: true });
26
+ writeBestEffort(eventsPath, "");
27
+ writeBestEffort(proofsPath, "");
28
+ const startedAt = Date.now();
29
+ const status = {
30
+ runId: input.runId,
31
+ pid: process.pid,
32
+ cwd: input.cwd,
33
+ title: input.title,
34
+ startedAt,
35
+ updatedAt: startedAt,
36
+ status: "running",
37
+ shape: input.shape,
38
+ live: {
39
+ path: [],
40
+ iteration: 0,
41
+ usage: {
42
+ inputTokens: 0,
43
+ outputTokens: 0,
44
+ cacheReadInputTokens: 0,
45
+ cacheCreationInputTokens: 0,
46
+ calls: 0,
47
+ unmeasuredCalls: 0
48
+ }
49
+ }
50
+ };
51
+ const active = /* @__PURE__ */ new Map();
52
+ let proofCount = 0;
53
+ const writeStatus = () => {
54
+ status.updatedAt = Date.now();
55
+ writeBestEffort(statusPath, JSON.stringify(status, null, 2));
56
+ };
57
+ const refreshCurrent = () => {
58
+ status.live.active = active.size ? Object.fromEntries(active.entries()) : void 0;
59
+ status.live.current = [...active.values()].sort((a, b) => {
60
+ const deadline = (a.deadlineAt ?? Number.POSITIVE_INFINITY) - (b.deadlineAt ?? Number.POSITIVE_INFINITY);
61
+ return deadline || b.startedAt - a.startedAt;
62
+ })[0];
63
+ };
64
+ const activeKey = (kind, path) => `${kind}:${path.join("\0")}`;
65
+ writeStatus();
66
+ const sink = (event) => {
67
+ if (!NOISE.has(event.kind)) appendBestEffort(eventsPath, event);
68
+ switch (event.kind) {
69
+ case "run:end":
70
+ status.live.lastOutcome = outcomeSummary(event.outcome);
71
+ break;
72
+ case "loop:iteration":
73
+ status.live.path = event.path;
74
+ status.live.iteration = event.iteration;
75
+ break;
76
+ case "loop:condition":
77
+ status.live.lastGate = {
78
+ which: event.which,
79
+ met: event.result.met,
80
+ confidence: event.result.confidence,
81
+ reason: event.result.reason
82
+ };
83
+ break;
84
+ case "condition:result":
85
+ status.live.lastGate = {
86
+ which: event.label,
87
+ met: event.result.met,
88
+ confidence: event.result.confidence,
89
+ reason: event.result.reason
90
+ };
91
+ break;
92
+ case "dag:node": {
93
+ const path = [...event.path, event.node];
94
+ status.live.path = path;
95
+ if (event.phase === "start") {
96
+ active.set(activeKey("dag-node", path), {
97
+ kind: "dag-node",
98
+ path,
99
+ node: event.node,
100
+ startedAt: event.ts,
101
+ timeoutMs: event.timeoutMs,
102
+ deadlineAt: event.timeoutMs ? event.ts + event.timeoutMs : void 0
103
+ });
104
+ } else {
105
+ active.delete(activeKey("dag-node", path));
106
+ }
107
+ refreshCurrent();
108
+ break;
109
+ }
110
+ case "job:start": {
111
+ const path = [...event.path, event.label];
112
+ active.set(activeKey("job", path), {
113
+ kind: "job",
114
+ path: event.path,
115
+ label: event.label,
116
+ startedAt: event.ts,
117
+ timeoutMs: event.timeoutMs,
118
+ deadlineAt: event.timeoutMs ? event.ts + event.timeoutMs : void 0
119
+ });
120
+ refreshCurrent();
121
+ break;
122
+ }
123
+ case "loop:end":
124
+ case "dag:end":
125
+ case "job:end":
126
+ status.live.path = event.path;
127
+ status.live.lastOutcome = outcomeSummary(event.outcome);
128
+ if (event.kind === "job:end") {
129
+ active.delete(activeKey("job", [...event.path, event.label]));
130
+ refreshCurrent();
131
+ }
132
+ break;
133
+ case "engine:usage":
134
+ status.live.usage.calls += 1;
135
+ if (event.usage.kind === "unknown") {
136
+ status.live.usage.unmeasuredCalls += 1;
137
+ } else {
138
+ status.live.usage.inputTokens += event.usage.inputTokens;
139
+ status.live.usage.outputTokens += event.usage.outputTokens;
140
+ status.live.usage.cacheReadInputTokens += event.usage.cacheReadInputTokens ?? 0;
141
+ status.live.usage.cacheCreationInputTokens += event.usage.cacheCreationInputTokens ?? 0;
142
+ }
143
+ break;
144
+ case "proof": {
145
+ const proof = {
146
+ name: event.name,
147
+ path: event.path,
148
+ artifact: event.artifact
149
+ };
150
+ proofCount += 1;
151
+ appendBestEffort(proofsPath, proof);
152
+ status.evidence = {
153
+ count: proofCount,
154
+ indexPath: proofsPath,
155
+ latest: proof
156
+ };
157
+ break;
158
+ }
159
+ }
160
+ if (!NOISE.has(event.kind)) writeStatus();
161
+ };
162
+ const finish = (outcome) => {
163
+ status.status = outcome.status;
164
+ status.endedAt = Date.now();
165
+ status.live.current = void 0;
166
+ status.live.active = void 0;
167
+ status.live.lastOutcome = outcomeSummary(outcome);
168
+ writeStatus();
169
+ };
170
+ return { runId: input.runId, dir, sink, finish };
171
+ }
172
+ function outcomeSummary(outcome) {
173
+ return {
174
+ status: outcome.status,
175
+ summary: outcome.summary,
176
+ late: outcome.late
177
+ };
178
+ }
179
+ function writeBestEffort(path, text) {
180
+ try {
181
+ writeFileSync(path, text);
182
+ } catch {
183
+ }
184
+ }
185
+ function appendBestEffort(path, value) {
186
+ try {
187
+ appendFileSync(path, `${JSON.stringify(value)}
188
+ `);
189
+ } catch {
190
+ }
191
+ }
192
+ function runningTotal(totals) {
193
+ const read = totals.cacheReadInputTokens ?? 0;
194
+ const unmeasured = totals.unmeasuredCalls ?? 0;
195
+ const missing = unmeasured > 0 ? `, usage unknown on ${unmeasured} call${unmeasured === 1 ? "" : "s"}` : "";
196
+ const base = `${totals.inputTokens}/${totals.outputTokens} tok`;
197
+ return `${base}${read > 0 ? `, ${read} tok from cache` : ""}${missing}`;
198
+ }
199
+ function toLine(value) {
200
+ return value.replace(/[\u0000-\u001f\u007f]+/g, " ");
201
+ }
202
+ function formatEvent(event, totals) {
203
+ return toLine(renderEvent(event, totals));
204
+ }
205
+ function renderEvent(event, totals) {
206
+ const at = event.path.length ? `${event.path.join(" \u203A ")} ` : "";
207
+ switch (event.kind) {
208
+ case "run:start":
209
+ return "\u25B8 run";
210
+ case "run:end":
211
+ return `\u25C2 run ${event.outcome.status}${event.outcome.late ? " late" : ""} (${runningTotal(event.usage)})`;
212
+ case "loop:start":
213
+ return `${at}\u25B8 loop${event.max ? ` (max ${event.max})` : ""}`;
214
+ case "dag:start":
215
+ return `${at}\u25B8 dag (${event.nodes.length} nodes)`;
216
+ case "loop:iteration":
217
+ return `${at}\xB7 iteration ${event.iteration}`;
218
+ case "loop:condition":
219
+ return `${at}\xB7 ${event.which} ${event.result.met ? "met" : "not met"}: ${event.result.reason}`;
220
+ case "condition:result":
221
+ return `${at}\xB7 ${event.label} ${event.result.met ? "met" : "not met"}: ${event.result.reason}`;
222
+ case "loop:review":
223
+ return `${at}\xB7 review: ${event.outcome.status}${event.outcome.late ? " late" : ""}`;
224
+ case "loop:end":
225
+ return `${at}\u25C2 ${event.outcome.status}${event.outcome.late ? " late" : ""} (${event.iterations} iter)`;
226
+ case "dag:node":
227
+ return `${at}\xB7 node ${event.node}: ${event.phase}${event.outcome ? ` (${event.outcome.status}${event.outcome.late ? " late" : ""})` : ""}`;
228
+ case "dag:kickback":
229
+ return `${at}\u21A9 kickback ${event.accepted ? "accepted" : "rejected"} ${event.from} -> ${event.to} [${event.count}/${event.limit}]: ${event.reason}${event.note ? ` (${event.note})` : ""}`;
230
+ case "dag:end":
231
+ return `${at}\u25C2 dag ${event.outcome.status}${event.outcome.late ? " late" : ""}`;
232
+ case "job:start":
233
+ return `${at}\u2022 ${event.label}`;
234
+ case "advisor:consult":
235
+ return `${at}\u25C7 advisor ${event.label} #${event.call}: ${event.question}`;
236
+ case "proof":
237
+ return `${at}\u25C8 proof ${event.name}: ${event.artifact.title ?? event.artifact.path ?? event.artifact.kind}`;
238
+ case "job:end": {
239
+ const summary = event.outcome.summary ? ` ${event.outcome.summary}` : "";
240
+ return `${at}\u2022 ${event.label}: ${event.outcome.status}${event.outcome.late ? " late" : ""}${summary}`;
241
+ }
242
+ case "engine:text":
243
+ case "engine:thinking":
244
+ return "";
245
+ case "engine:tool":
246
+ return `${at} tool ${event.name} ${event.phase}${event.target ? ` ${event.target}` : ""}`;
247
+ case "engine:usage": {
248
+ const call = event.usage.kind === "unknown" ? "usage unknown" : `${event.usage.inputTokens}/${event.usage.outputTokens} tok`;
249
+ return `${at} ${event.model}: ${call}${totals ? ` (run ${runningTotal(totals)})` : ""}`;
250
+ }
251
+ case "refine:judge":
252
+ return `${at}\u25C6 ${event.reason}`;
253
+ case "loop:stall":
254
+ return `${at}\u23F9 stalled after ${event.report.iterations.length} no-progress iterations: ${event.report.reason}`;
255
+ case "limit:wait":
256
+ return `${at}\u23F8 limit ${event.code}: waiting ${Math.round(event.waitMs / 1e3)}s`;
257
+ case "limit:pause":
258
+ return `${at}\u23F8 paused (${event.code}): ${event.reason}`;
259
+ case "log":
260
+ return `${at}${event.message}`;
261
+ case "error":
262
+ return `${at}\u2717 ${event.code}: ${event.message}`;
263
+ default:
264
+ return `${at}${event.kind}`;
265
+ }
266
+ }
267
+ var DEFAULT_WIDTH = 100;
268
+ function firstLine(value) {
269
+ for (const raw of value.split("\n")) {
270
+ const line = toLine(raw).trim().replace(/^(?:[-*]|\d+\.)\s+/, "");
271
+ if (line.length > 0) return line;
272
+ }
273
+ return "";
274
+ }
275
+ function outcomeOf(outcome) {
276
+ return outcome ? { status: outcome.status, summary: outcome.summary } : null;
277
+ }
278
+ function summarizeRecord(events) {
279
+ const firstDag = events.find((e) => e.kind === "dag:start");
280
+ const rootDepth = firstDag && firstDag.kind === "dag:start" ? firstDag.path.length : 0;
281
+ let name = null;
282
+ let startedAt = null;
283
+ let endedAt = null;
284
+ let outcome = null;
285
+ let usage = null;
286
+ let monitor = null;
287
+ const nodes = [];
288
+ const lines = [];
289
+ const measured = {
290
+ inputTokens: 0,
291
+ outputTokens: 0,
292
+ cacheReadInputTokens: 0,
293
+ unmeasuredCalls: 0
294
+ };
295
+ const nodeFor = (name_) => {
296
+ let n = nodes.find((x) => x.node === name_);
297
+ if (!n) {
298
+ n = { node: name_, runs: [] };
299
+ nodes.push(n);
300
+ }
301
+ return n;
302
+ };
303
+ const runFor = (nodeName, attempt) => {
304
+ const n = nodeFor(nodeName);
305
+ let r = n.runs.find((x) => x.attempt === attempt);
306
+ if (!r) {
307
+ r = {
308
+ attempt,
309
+ startedAt: null,
310
+ endedAt: null,
311
+ outcome: null,
312
+ kickback: null,
313
+ engineCalls: [],
314
+ tools: [],
315
+ lines: []
316
+ };
317
+ n.runs.push(r);
318
+ n.runs.sort((a, b) => a.attempt - b.attempt);
319
+ }
320
+ return r;
321
+ };
322
+ const latestRun = (nodeName) => {
323
+ const n = nodeFor(nodeName);
324
+ const last = n.runs[n.runs.length - 1];
325
+ if (last) return last;
326
+ return runFor(nodeName, 1);
327
+ };
328
+ for (const event of events) {
329
+ if (startedAt === null) startedAt = event.ts;
330
+ if (event.kind === "dag:node" && event.path.length === rootDepth) {
331
+ const run = runFor(event.node, event.attempt ?? 1);
332
+ if (event.phase === "start") {
333
+ run.startedAt = event.ts;
334
+ } else {
335
+ run.endedAt = event.ts;
336
+ if (event.phase === "skip") {
337
+ run.outcome = { status: "skipped", summary: event.outcome?.summary };
338
+ } else if (event.outcome) {
339
+ run.outcome = outcomeOf(event.outcome) ?? run.outcome;
340
+ if (event.outcome.status === "paused" && event.outcome.summary && !run.lines.some((l) => l.kind === "paused")) {
341
+ run.lines.push({ kind: "paused", text: `Paused: ${event.outcome.summary}` });
342
+ }
343
+ }
344
+ }
345
+ continue;
346
+ }
347
+ if (event.path.length > rootDepth) {
348
+ const run = latestRun(event.path[rootDepth] ?? "");
349
+ switch (event.kind) {
350
+ case "engine:usage": {
351
+ if (event.usage.kind === "reported") {
352
+ run.engineCalls.push({
353
+ model: event.model,
354
+ inputTokens: event.usage.inputTokens,
355
+ outputTokens: event.usage.outputTokens
356
+ });
357
+ measured.inputTokens += event.usage.inputTokens;
358
+ measured.outputTokens += event.usage.outputTokens;
359
+ measured.cacheReadInputTokens += event.usage.cacheReadInputTokens ?? 0;
360
+ } else {
361
+ run.engineCalls.push({ model: event.model, inputTokens: null, outputTokens: null });
362
+ measured.unmeasuredCalls += 1;
363
+ }
364
+ break;
365
+ }
366
+ case "engine:tool": {
367
+ if (event.phase === "use") {
368
+ const existing = run.tools.find((t) => t.name === event.name);
369
+ if (existing) {
370
+ existing.count += 1;
371
+ if (event.target !== void 0 && !existing.targets.includes(event.target)) {
372
+ existing.targets.push(event.target);
373
+ }
374
+ } else {
375
+ run.tools.push({
376
+ name: event.name,
377
+ count: 1,
378
+ targets: event.target !== void 0 ? [event.target] : []
379
+ });
380
+ }
381
+ }
382
+ break;
383
+ }
384
+ case "limit:pause":
385
+ run.lines.push({ kind: event.kind, text: `Paused (${event.code}): ${event.reason}` });
386
+ break;
387
+ case "dag:node":
388
+ if (event.phase !== "start") {
389
+ const status = event.phase === "skip" ? "skipped" : event.outcome?.status ?? "no end recorded";
390
+ const summary = event.outcome?.summary ? firstLine(event.outcome.summary) : "";
391
+ const label = [...event.path.slice(rootDepth + 1), event.node].join("/");
392
+ run.lines.push({ kind: event.kind, text: `${label}: ${status}${summary ? `, ${summary}` : ""}` });
393
+ }
394
+ break;
395
+ case "log":
396
+ run.lines.push({ kind: event.kind, text: `log: ${event.message}` });
397
+ break;
398
+ case "error":
399
+ run.lines.push({ kind: event.kind, text: `error ${event.code}: ${event.message}` });
400
+ break;
401
+ case "job:start":
402
+ case "job:end":
403
+ case "engine:text":
404
+ case "engine:thinking":
405
+ case "dag:start":
406
+ case "dag:end":
407
+ break;
408
+ default:
409
+ run.lines.push({ kind: event.kind, text: event.kind });
410
+ }
411
+ continue;
412
+ }
413
+ switch (event.kind) {
414
+ case "run:start":
415
+ if (name === null && event.runId) name = event.runId;
416
+ break;
417
+ case "run:end":
418
+ endedAt = event.ts;
419
+ outcome = outcomeOf(event.outcome);
420
+ usage = {
421
+ inputTokens: event.usage.inputTokens,
422
+ outputTokens: event.usage.outputTokens,
423
+ cacheReadInputTokens: event.usage.cacheReadInputTokens ?? 0,
424
+ unmeasuredCalls: event.usage.unmeasuredCalls ?? 0
425
+ };
426
+ break;
427
+ case "dag:start":
428
+ if (name === null && event.path.length > 0) name = event.path[0] ?? null;
429
+ break;
430
+ case "dag:end":
431
+ break;
432
+ case "monitor":
433
+ monitor = event.url;
434
+ break;
435
+ case "dag:kickback":
436
+ if (event.accepted) {
437
+ const run = runFor(event.to, event.count + 1);
438
+ run.kickback = {
439
+ from: event.from,
440
+ to: event.to,
441
+ reason: event.reason,
442
+ count: event.count,
443
+ limit: event.limit,
444
+ accepted: true,
445
+ note: event.note
446
+ };
447
+ } else {
448
+ latestRun(event.to).lines.push({
449
+ kind: event.kind,
450
+ text: `Kickback from ${event.from} rejected${event.note ? ` (${event.note})` : ""}: ${event.reason}`
451
+ });
452
+ }
453
+ break;
454
+ case "log":
455
+ lines.push({ kind: event.kind, text: `log: ${event.message}` });
456
+ break;
457
+ case "error":
458
+ lines.push({ kind: event.kind, text: `error ${event.code}: ${event.message}` });
459
+ break;
460
+ default:
461
+ lines.push({ kind: event.kind, text: event.kind });
462
+ }
463
+ }
464
+ if (name === null && firstDag && firstDag.kind === "dag:start" && firstDag.path.length > 0) {
465
+ name = firstDag.path[0] ?? null;
466
+ }
467
+ return {
468
+ name,
469
+ startedAt,
470
+ endedAt,
471
+ outcome,
472
+ usage: usage ?? measured,
473
+ monitor,
474
+ nodes: nodes.map((n) => ({
475
+ node: n.node,
476
+ runs: n.runs.map((r) => ({
477
+ attempt: r.attempt,
478
+ startedAt: r.startedAt,
479
+ endedAt: r.endedAt,
480
+ outcome: r.outcome,
481
+ kickback: r.kickback,
482
+ engineCalls: r.engineCalls,
483
+ tools: r.tools.map((t) => ({ name: t.name, count: t.count, targets: t.targets })),
484
+ lines: r.lines
485
+ }))
486
+ })),
487
+ lines
488
+ };
489
+ }
490
+ var commas = (n) => `${n}`.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
491
+ function clockTime(ts) {
492
+ return new Date(ts).toISOString().slice(11, 19);
493
+ }
494
+ function headerTime(ts) {
495
+ return `${new Date(ts).toISOString().slice(0, 19).replace("T", " ")} UTC`;
496
+ }
497
+ function formatDuration(ms) {
498
+ if (ms < 1e3) return `${ms} ms`;
499
+ const s = Math.floor(ms / 1e3);
500
+ if (s < 60) return `${s}s`;
501
+ const m = Math.floor(s / 60);
502
+ if (m < 60) return `${m}m ${s % 60}s`;
503
+ return `${Math.floor(m / 60)}h ${m % 60}m`;
504
+ }
505
+ function fit(text, width) {
506
+ return text.length > width ? `${text.slice(0, width)}\u2026` : text;
507
+ }
508
+ function renderRecord(events, options) {
509
+ const width = options?.lineWidth ?? DEFAULT_WIDTH;
510
+ const summary = summarizeRecord(events);
511
+ const out = [];
512
+ out.push(`# ${summary.name ?? "run"}`);
513
+ out.push("");
514
+ if (summary.startedAt !== null && summary.endedAt === null) {
515
+ out.push(`- Started ${headerTime(summary.startedAt)}. No end recorded.`);
516
+ } else if (summary.startedAt !== null && summary.endedAt !== null) {
517
+ out.push(
518
+ `- Started ${headerTime(summary.startedAt)}, ended ${headerTime(summary.endedAt)} after ${formatDuration(summary.endedAt - summary.startedAt)}.`
519
+ );
520
+ if (summary.outcome) {
521
+ const text = summary.outcome.summary ? `${summary.outcome.status}. ${fit(firstLine(summary.outcome.summary), width)}` : `${summary.outcome.status}.`;
522
+ out.push(`- Outcome: ${text}`);
523
+ }
524
+ }
525
+ const usageParts = [`${commas(summary.usage.inputTokens)} in, ${commas(summary.usage.outputTokens)} out`];
526
+ if (summary.usage.cacheReadInputTokens > 0) {
527
+ usageParts.push(`${commas(summary.usage.cacheReadInputTokens)} read from cache`);
528
+ }
529
+ let tokensLine = `- Tokens: ${usageParts.join(", ")}.`;
530
+ if (summary.usage.unmeasuredCalls > 0) {
531
+ tokensLine = `- Tokens: ${usageParts.join(", ")}, usage unknown on ${summary.usage.unmeasuredCalls} calls.`;
532
+ }
533
+ out.push(tokensLine);
534
+ if (summary.monitor) out.push(`- Monitor: ${summary.monitor}`);
535
+ for (const line of summary.lines) out.push(`- ${fit(line.text, width)}`);
536
+ for (const node of summary.nodes) {
537
+ out.push("");
538
+ out.push(`## ${node.node}`);
539
+ for (const run of node.runs) {
540
+ out.push("");
541
+ if (run.kickback) {
542
+ out.push(
543
+ `Kickback from ${run.kickback.from}, ${run.kickback.count} of ${run.kickback.limit}: ${run.kickback.reason}`
544
+ );
545
+ out.push("");
546
+ }
547
+ const status = run.endedAt === null ? "no end recorded" : run.outcome?.status ?? "no outcome";
548
+ const parts = [`### Run ${run.attempt}: ${status}`];
549
+ if (run.startedAt !== null && run.endedAt !== null) {
550
+ parts.push(formatDuration(run.endedAt - run.startedAt));
551
+ }
552
+ if (run.startedAt !== null) parts.push(`started ${clockTime(run.startedAt)}`);
553
+ else if (run.endedAt !== null) parts.push(`at ${clockTime(run.endedAt)}`);
554
+ out.push(parts.join(", "));
555
+ if (run.outcome?.summary) {
556
+ out.push("");
557
+ out.push(fit(firstLine(run.outcome.summary), width));
558
+ }
559
+ if (run.engineCalls.length > 0 || run.tools.length > 0 || run.lines.length > 0) {
560
+ out.push("");
561
+ for (const call of run.engineCalls) {
562
+ out.push(
563
+ call.inputTokens === null || call.outputTokens === null ? `- ${call.model}: usage unknown` : `- ${call.model}: ${commas(call.inputTokens)} in, ${commas(call.outputTokens)} out`
564
+ );
565
+ }
566
+ if (run.tools.length > 0) {
567
+ out.push(
568
+ `- ${run.tools.map((t) => `${t.name} \xD7${t.count}${t.targets.length > 0 ? `: ${t.targets.join(", ")}` : ""}`).join(", ")}`
569
+ );
570
+ }
571
+ for (const line of run.lines) {
572
+ const uncut = line.kind === "paused" || line.kind === "limit:pause";
573
+ out.push(`- ${uncut ? line.text : fit(line.text, width)}`);
574
+ }
575
+ }
576
+ }
577
+ }
578
+ return `${out.join("\n")}
579
+ `;
580
+ }
581
+ function readRecordFile(path) {
582
+ const text = readFileSync(path, "utf8");
583
+ const events = [];
584
+ let unreadable = 0;
585
+ for (const line of text.split(/\r?\n/)) {
586
+ if (line.trim().length === 0) continue;
587
+ try {
588
+ events.push(JSON.parse(line));
589
+ } catch {
590
+ unreadable += 1;
591
+ }
592
+ }
593
+ return { events, unreadable };
594
+ }
595
+
596
+ export { formatEvent, newRunId, readRecordFile, renderRecord, runningTotal, startSupervisor, summarizeRecord };
597
+ //# sourceMappingURL=chunk-XDMDT67Q.js.map
598
+ //# sourceMappingURL=chunk-XDMDT67Q.js.map