@tangle-network/traces 0.10.2 → 0.11.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.
- package/README.md +59 -2
- package/dist/{chunk-LC66I5TE.js → chunk-JCI5YXNT.js} +1162 -47
- package/dist/cli.js +79 -10
- package/dist/index.d.ts +509 -1
- package/dist/index.js +77 -3
- package/package.json +4 -2
|
@@ -14,8 +14,8 @@ function parseSince(value) {
|
|
|
14
14
|
const m = value.match(/^(\d+)\s*([mhd])$/i);
|
|
15
15
|
if (m) {
|
|
16
16
|
const unit = m[2].toLowerCase();
|
|
17
|
-
const
|
|
18
|
-
return Date.now() - Number(m[1]) *
|
|
17
|
+
const ms2 = unit === "m" ? 6e4 : unit === "h" ? 36e5 : 864e5;
|
|
18
|
+
return Date.now() - Number(m[1]) * ms2;
|
|
19
19
|
}
|
|
20
20
|
const t = Date.parse(value);
|
|
21
21
|
if (Number.isNaN(t)) throw new Error(`--since: expected 30m / 2h / 7d or an ISO date, got "${value}"`);
|
|
@@ -167,8 +167,8 @@ function serializeSpans(spans) {
|
|
|
167
167
|
async function writeOtlpFile(spans, outPath) {
|
|
168
168
|
const { mkdtemp: mkdtemp8, writeFile: writeFile9 } = await import("fs/promises");
|
|
169
169
|
const { tmpdir: tmpdir7 } = await import("os");
|
|
170
|
-
const { join:
|
|
171
|
-
const path = outPath ??
|
|
170
|
+
const { join: join28 } = await import("path");
|
|
171
|
+
const path = outPath ?? join28(await mkdtemp8(join28(tmpdir7(), "traces-")), "spans.otlp.jsonl");
|
|
172
172
|
await writeFile9(path, serializeSpans(spans), "utf8");
|
|
173
173
|
return path;
|
|
174
174
|
}
|
|
@@ -2795,13 +2795,12 @@ var ClaudeAdapter = class {
|
|
|
2795
2795
|
agents.push(agent);
|
|
2796
2796
|
}
|
|
2797
2797
|
const agentsById = /* @__PURE__ */ new Map();
|
|
2798
|
+
const ambiguousAgentIds = /* @__PURE__ */ new Set();
|
|
2798
2799
|
const childrenByParent = /* @__PURE__ */ new Map();
|
|
2799
2800
|
for (const agent of agents) {
|
|
2800
|
-
if (agentsById.has(agent.agentId))
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
agentsById.set(agent.agentId, agent);
|
|
2804
|
-
if (agent.meta.parentAgentId) {
|
|
2801
|
+
if (agentsById.has(agent.agentId)) ambiguousAgentIds.add(agent.agentId);
|
|
2802
|
+
else agentsById.set(agent.agentId, agent);
|
|
2803
|
+
if (agent.meta.parentAgentId && !ambiguousAgentIds.has(agent.meta.parentAgentId)) {
|
|
2805
2804
|
const siblings = childrenByParent.get(agent.meta.parentAgentId) ?? [];
|
|
2806
2805
|
siblings.push(agent);
|
|
2807
2806
|
childrenByParent.set(agent.meta.parentAgentId, siblings);
|
|
@@ -4873,8 +4872,8 @@ import { readdir as readdir10, stat as stat9 } from "fs/promises";
|
|
|
4873
4872
|
import { homedir as homedir9 } from "os";
|
|
4874
4873
|
import { join as join12 } from "path";
|
|
4875
4874
|
var SERVICE8 = "opencode";
|
|
4876
|
-
function isoOf(
|
|
4877
|
-
return new Date(
|
|
4875
|
+
function isoOf(ms2) {
|
|
4876
|
+
return new Date(ms2 ?? 0).toISOString();
|
|
4878
4877
|
}
|
|
4879
4878
|
function textOf7(parts) {
|
|
4880
4879
|
return capText(
|
|
@@ -5729,7 +5728,7 @@ function extractAbsolutePaths(spans) {
|
|
|
5729
5728
|
async function resolveRepoAttrs(cwd) {
|
|
5730
5729
|
if (!cwd) return {};
|
|
5731
5730
|
const attrs = { [ATTR.CWD]: cwd };
|
|
5732
|
-
const
|
|
5731
|
+
const basename15 = (() => {
|
|
5733
5732
|
const trimmed = cwd.replace(/\/+$/g, "");
|
|
5734
5733
|
const idx = trimmed.lastIndexOf("/");
|
|
5735
5734
|
const name = idx === -1 ? trimmed : trimmed.slice(idx + 1);
|
|
@@ -5751,7 +5750,7 @@ async function resolveRepoAttrs(cwd) {
|
|
|
5751
5750
|
if (commit) attrs[ATTR.GIT_COMMIT] = commit;
|
|
5752
5751
|
} catch {
|
|
5753
5752
|
}
|
|
5754
|
-
attrs[ATTR.SUBJECT_KEY] =
|
|
5753
|
+
attrs[ATTR.SUBJECT_KEY] = basename15;
|
|
5755
5754
|
return attrs;
|
|
5756
5755
|
}
|
|
5757
5756
|
async function repoCandidateForPath(path) {
|
|
@@ -6546,10 +6545,10 @@ async function readLinuxProcProcessTable() {
|
|
|
6546
6545
|
const rows = await Promise.all(
|
|
6547
6546
|
entries.filter((entry) => entry.isDirectory() && /^\d+$/.test(entry.name)).map(async (entry) => {
|
|
6548
6547
|
try {
|
|
6549
|
-
const
|
|
6550
|
-
const commandEnd =
|
|
6548
|
+
const stat16 = await readFile5(`/proc/${entry.name}/stat`, "utf8");
|
|
6549
|
+
const commandEnd = stat16.lastIndexOf(")");
|
|
6551
6550
|
if (commandEnd < 0) return void 0;
|
|
6552
|
-
const fields =
|
|
6551
|
+
const fields = stat16.slice(commandEnd + 1).trim().split(/\s+/);
|
|
6553
6552
|
const pid = Number(entry.name);
|
|
6554
6553
|
const parentPid = Number(fields[1]);
|
|
6555
6554
|
if (!Number.isSafeInteger(pid) || pid < 1 || !Number.isSafeInteger(parentPid) || parentPid < 0) {
|
|
@@ -7742,12 +7741,12 @@ function isoTime2(value) {
|
|
|
7742
7741
|
if (typeof value === "string") {
|
|
7743
7742
|
const trimmed = value.trim();
|
|
7744
7743
|
const normalized = /^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}(?:\.\d+)?$/.test(trimmed) ? `${trimmed.replace(" ", "T")}Z` : trimmed;
|
|
7745
|
-
const
|
|
7746
|
-
return Number.isFinite(
|
|
7744
|
+
const ms2 = Date.parse(normalized);
|
|
7745
|
+
return Number.isFinite(ms2) ? new Date(ms2).toISOString() : void 0;
|
|
7747
7746
|
}
|
|
7748
7747
|
if (typeof value === "number" && Number.isFinite(value)) {
|
|
7749
|
-
const
|
|
7750
|
-
return new Date(
|
|
7748
|
+
const ms2 = value < 1e10 ? value * 1e3 : value;
|
|
7749
|
+
return new Date(ms2).toISOString();
|
|
7751
7750
|
}
|
|
7752
7751
|
return void 0;
|
|
7753
7752
|
}
|
|
@@ -8446,7 +8445,7 @@ function addSessionFindings(index, findings) {
|
|
|
8446
8445
|
refs: errorSessions.slice(0, 5).map((row) => row.session.path)
|
|
8447
8446
|
});
|
|
8448
8447
|
}
|
|
8449
|
-
const largeSessions = sessions.map((row) => ({ row, tokens: row.metrics.inputTokens + row.metrics.outputTokens })).filter(({ tokens:
|
|
8448
|
+
const largeSessions = sessions.map((row) => ({ row, tokens: row.metrics.inputTokens + row.metrics.outputTokens })).filter(({ tokens: tokens3 }) => tokens3 >= 5e5).sort((a, b) => b.tokens - a.tokens);
|
|
8450
8449
|
if (largeSessions.length > 0) {
|
|
8451
8450
|
const largest = largeSessions[0]?.tokens ?? 0;
|
|
8452
8451
|
findings.push({
|
|
@@ -8455,7 +8454,7 @@ function addSessionFindings(index, findings) {
|
|
|
8455
8454
|
area: "session",
|
|
8456
8455
|
title: `${largeSessions.length} session(s) reported at least 500k summed tokens`,
|
|
8457
8456
|
impact: largest,
|
|
8458
|
-
evidence: largeSessions.slice(0, 5).map(({ row, tokens:
|
|
8457
|
+
evidence: largeSessions.slice(0, 5).map(({ row, tokens: tokens3 }) => `${sessionLabel(row)}: ${tokens3.toLocaleString()} reported summed tokens (${row.metrics.inputTokens.toLocaleString()} in, ${row.metrics.outputTokens.toLocaleString()} out)`),
|
|
8459
8458
|
next: "Check whether these sessions need shorter handoffs, smaller files, split tasks, or clearer token accounting before optimizing anything else.",
|
|
8460
8459
|
refs: largeSessions.slice(0, 5).map(({ row }) => row.session.path)
|
|
8461
8460
|
});
|
|
@@ -10134,7 +10133,7 @@ import { createHash as createHash9 } from "crypto";
|
|
|
10134
10133
|
var VERIFY_RE = /\b(test|tests|vitest|jest|pytest|go test|cargo test|tsc|typecheck|lint|biome|eslint|build|check:invariants|preflight)\b/i;
|
|
10135
10134
|
var CHANGE_RE = /\b(apply_patch|edit|write|create|delete|rename|replace|update|git apply|sed -i|perl -pi|tee\s|cat\s+>)/i;
|
|
10136
10135
|
var CLAIM_DONE_RE = /\b(done|fixed|complete|completed|implemented|merged|verified|all green|tests pass|ready|works now)\b/i;
|
|
10137
|
-
var sleep = (
|
|
10136
|
+
var sleep = (ms2, signal) => new Promise((resolve7) => {
|
|
10138
10137
|
if (signal?.aborted) {
|
|
10139
10138
|
resolve7();
|
|
10140
10139
|
return;
|
|
@@ -10146,7 +10145,7 @@ var sleep = (ms, signal) => new Promise((resolve7) => {
|
|
|
10146
10145
|
const t = setTimeout(() => {
|
|
10147
10146
|
signal?.removeEventListener("abort", onAbort);
|
|
10148
10147
|
resolve7();
|
|
10149
|
-
},
|
|
10148
|
+
}, ms2);
|
|
10150
10149
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
10151
10150
|
});
|
|
10152
10151
|
function hash(parts) {
|
|
@@ -10554,6 +10553,1085 @@ async function streamSessions(opts) {
|
|
|
10554
10553
|
}
|
|
10555
10554
|
}
|
|
10556
10555
|
|
|
10556
|
+
// src/supervisor-run-context.ts
|
|
10557
|
+
import { readFile as readFile10, stat as stat13 } from "fs/promises";
|
|
10558
|
+
import { basename as basename13, join as join22 } from "path";
|
|
10559
|
+
import { materializeTreeView } from "@tangle-network/agent-runtime/kernel";
|
|
10560
|
+
import {
|
|
10561
|
+
NO_SOURCE_LIMITS
|
|
10562
|
+
} from "@tangle-network/agent-eval/supervisor-run";
|
|
10563
|
+
var SPAWN_JOURNAL_FILE = "spawn-journal.jsonl";
|
|
10564
|
+
var COORDINATION_LOG_FILE = "coordination-log.jsonl";
|
|
10565
|
+
var RESULT_FILE = "result.json";
|
|
10566
|
+
var BLOBS_DIR = "blobs";
|
|
10567
|
+
async function readMaybe(path) {
|
|
10568
|
+
return readFile10(path, "utf8").catch(() => null);
|
|
10569
|
+
}
|
|
10570
|
+
function readJsonlLines(text2) {
|
|
10571
|
+
if (text2 === null) {
|
|
10572
|
+
return { rows: [], invalidRows: 0, partialTail: false, present: false };
|
|
10573
|
+
}
|
|
10574
|
+
const lines = text2.split("\n");
|
|
10575
|
+
const partialTail = text2.length > 0 && !text2.endsWith("\n");
|
|
10576
|
+
if (partialTail) lines.pop();
|
|
10577
|
+
const rows = [];
|
|
10578
|
+
let invalidRows = 0;
|
|
10579
|
+
for (const line of lines) {
|
|
10580
|
+
const trimmed = line.trim();
|
|
10581
|
+
if (trimmed.length === 0) continue;
|
|
10582
|
+
try {
|
|
10583
|
+
const parsed = JSON.parse(trimmed);
|
|
10584
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
10585
|
+
rows.push(parsed);
|
|
10586
|
+
} else invalidRows += 1;
|
|
10587
|
+
} catch {
|
|
10588
|
+
invalidRows += 1;
|
|
10589
|
+
}
|
|
10590
|
+
}
|
|
10591
|
+
return { rows, invalidRows, partialTail, present: true };
|
|
10592
|
+
}
|
|
10593
|
+
function asRecord(v) {
|
|
10594
|
+
return typeof v === "object" && v !== null && !Array.isArray(v) ? v : {};
|
|
10595
|
+
}
|
|
10596
|
+
function str(v) {
|
|
10597
|
+
return typeof v === "string" && v.length > 0 ? v : null;
|
|
10598
|
+
}
|
|
10599
|
+
function readRunContextJournal(text2) {
|
|
10600
|
+
const parsed = readJsonlLines(text2);
|
|
10601
|
+
let root = null;
|
|
10602
|
+
let begunAt = null;
|
|
10603
|
+
const events = [];
|
|
10604
|
+
let invalidRows = parsed.invalidRows;
|
|
10605
|
+
for (const record2 of parsed.rows) {
|
|
10606
|
+
const recordRoot = str(record2.root);
|
|
10607
|
+
if (root === null && recordRoot !== null) root = recordRoot;
|
|
10608
|
+
if (record2.kind === "begin") {
|
|
10609
|
+
if (begunAt === null) begunAt = str(record2.at);
|
|
10610
|
+
continue;
|
|
10611
|
+
}
|
|
10612
|
+
if (record2.kind !== "event") {
|
|
10613
|
+
invalidRows += 1;
|
|
10614
|
+
continue;
|
|
10615
|
+
}
|
|
10616
|
+
const event = asRecord(record2.event);
|
|
10617
|
+
if (str(event.kind) === null || str(event.id) === null) {
|
|
10618
|
+
invalidRows += 1;
|
|
10619
|
+
continue;
|
|
10620
|
+
}
|
|
10621
|
+
events.push(event);
|
|
10622
|
+
}
|
|
10623
|
+
const supervisorIds = /* @__PURE__ */ new Set();
|
|
10624
|
+
for (const event of events) {
|
|
10625
|
+
if (event.kind !== "spawned" && event.kind !== "waiting") continue;
|
|
10626
|
+
const parent = event.parent;
|
|
10627
|
+
if (typeof parent === "string" && parent.length > 0) supervisorIds.add(parent);
|
|
10628
|
+
}
|
|
10629
|
+
return {
|
|
10630
|
+
root,
|
|
10631
|
+
begunAt,
|
|
10632
|
+
events,
|
|
10633
|
+
supervisorIds,
|
|
10634
|
+
invalidRows,
|
|
10635
|
+
partialTail: parsed.partialTail,
|
|
10636
|
+
present: parsed.present
|
|
10637
|
+
};
|
|
10638
|
+
}
|
|
10639
|
+
function inferRole(id, parent, supervisorIds) {
|
|
10640
|
+
if (parent === null || parent === void 0) return "supervisor";
|
|
10641
|
+
return supervisorIds.has(id) ? "supervisor" : "worker";
|
|
10642
|
+
}
|
|
10643
|
+
var EMPTY_COORDINATION = {
|
|
10644
|
+
present: false,
|
|
10645
|
+
steers: [],
|
|
10646
|
+
deliveries: [],
|
|
10647
|
+
questions: [],
|
|
10648
|
+
invalidRows: 0,
|
|
10649
|
+
partialTail: false
|
|
10650
|
+
};
|
|
10651
|
+
function readCoordinationLog(text2) {
|
|
10652
|
+
const parsed = readJsonlLines(text2);
|
|
10653
|
+
if (!parsed.present) return EMPTY_COORDINATION;
|
|
10654
|
+
const steers = [];
|
|
10655
|
+
const deliveries = [];
|
|
10656
|
+
const questions = [];
|
|
10657
|
+
let invalidRows = parsed.invalidRows;
|
|
10658
|
+
for (const row of parsed.rows) {
|
|
10659
|
+
const record2 = asRecord(row.record);
|
|
10660
|
+
const event = asRecord(Object.keys(record2).length > 0 ? record2.event : row.event);
|
|
10661
|
+
const at = typeof record2.at === "number" ? record2.at : parseInstant(row.at);
|
|
10662
|
+
switch (event.type) {
|
|
10663
|
+
case "steer": {
|
|
10664
|
+
const down = asRecord(event.down);
|
|
10665
|
+
steers.push({
|
|
10666
|
+
messageId: str(down.messageId),
|
|
10667
|
+
toWorker: str(down.toWorker),
|
|
10668
|
+
instruction: typeof down.instruction === "string" ? down.instruction : "",
|
|
10669
|
+
deliveryMode: str(down.deliveryMode),
|
|
10670
|
+
at
|
|
10671
|
+
});
|
|
10672
|
+
break;
|
|
10673
|
+
}
|
|
10674
|
+
case "delivery": {
|
|
10675
|
+
const receipt = asRecord(event.receipt);
|
|
10676
|
+
deliveries.push({
|
|
10677
|
+
messageId: str(receipt.messageId),
|
|
10678
|
+
delivered: typeof receipt.delivered === "boolean" ? receipt.delivered : null
|
|
10679
|
+
});
|
|
10680
|
+
break;
|
|
10681
|
+
}
|
|
10682
|
+
case "question": {
|
|
10683
|
+
const q = asRecord(event.question);
|
|
10684
|
+
const urgency = str(q.urgency);
|
|
10685
|
+
questions.push({
|
|
10686
|
+
id: str(q.id),
|
|
10687
|
+
from: str(q.from),
|
|
10688
|
+
question: typeof q.question === "string" ? q.question : "",
|
|
10689
|
+
reason: str(q.reason),
|
|
10690
|
+
urgency,
|
|
10691
|
+
status: str(q.status),
|
|
10692
|
+
blocking: urgency === "blocks-run",
|
|
10693
|
+
at: typeof q.openedAt === "number" ? q.openedAt : at
|
|
10694
|
+
});
|
|
10695
|
+
break;
|
|
10696
|
+
}
|
|
10697
|
+
case "settled":
|
|
10698
|
+
break;
|
|
10699
|
+
default:
|
|
10700
|
+
if (str(event.type) === null) invalidRows += 1;
|
|
10701
|
+
}
|
|
10702
|
+
}
|
|
10703
|
+
return {
|
|
10704
|
+
present: true,
|
|
10705
|
+
steers,
|
|
10706
|
+
deliveries,
|
|
10707
|
+
questions,
|
|
10708
|
+
invalidRows,
|
|
10709
|
+
partialTail: parsed.partialTail
|
|
10710
|
+
};
|
|
10711
|
+
}
|
|
10712
|
+
function parseInstant(v) {
|
|
10713
|
+
if (typeof v === "number" && Number.isFinite(v)) return v;
|
|
10714
|
+
if (typeof v !== "string") return null;
|
|
10715
|
+
const t = Date.parse(v);
|
|
10716
|
+
return Number.isFinite(t) ? t : null;
|
|
10717
|
+
}
|
|
10718
|
+
async function readFileRunContext(runDir, opts = {}) {
|
|
10719
|
+
const journalText = await readMaybe(join22(runDir, SPAWN_JOURNAL_FILE));
|
|
10720
|
+
const journal = readRunContextJournal(journalText);
|
|
10721
|
+
const coordination = readCoordinationLog(await readMaybe(join22(runDir, COORDINATION_LOG_FILE)));
|
|
10722
|
+
const result = await readMaybe(join22(runDir, RESULT_FILE));
|
|
10723
|
+
return {
|
|
10724
|
+
runRef: runDir,
|
|
10725
|
+
instanceId: opts.instanceId !== void 0 ? opts.instanceId : journal.root,
|
|
10726
|
+
arm: opts.arm !== void 0 ? opts.arm : basename13(runDir),
|
|
10727
|
+
// The journal is the store, and it lives directly in the run directory —
|
|
10728
|
+
// this layout has no separate supervisor sub-directory.
|
|
10729
|
+
supRunDir: journal.present ? runDir : null,
|
|
10730
|
+
journal: journal.present ? flattenJournal(journal) : null,
|
|
10731
|
+
// Per-brain-call taps and progress streams are loops artifacts; this layout
|
|
10732
|
+
// writes neither, so truncation genuinely cannot be ruled out.
|
|
10733
|
+
brainLog: null,
|
|
10734
|
+
state: journal.present ? runState(journal, result) : null,
|
|
10735
|
+
progress: null,
|
|
10736
|
+
...workerSources(journal, coordination),
|
|
10737
|
+
result,
|
|
10738
|
+
judge: null,
|
|
10739
|
+
judgeSource: null,
|
|
10740
|
+
patch: null,
|
|
10741
|
+
driverLog: null,
|
|
10742
|
+
harnessWorkerTokens: null,
|
|
10743
|
+
harnessMissingReason: "the file run context journals each child\u2019s own settled spend, so no external harness join is used",
|
|
10744
|
+
limits: sourceLimits(journal, coordination),
|
|
10745
|
+
rootTranscriptRef: journal.present ? join22(runDir, SPAWN_JOURNAL_FILE) : null,
|
|
10746
|
+
traceCommand: `traces watch ${runDir}`
|
|
10747
|
+
};
|
|
10748
|
+
}
|
|
10749
|
+
function fileRunContextSupervisorRunReader(runDir, opts = {}) {
|
|
10750
|
+
return { runRef: runDir, read: () => readFileRunContext(runDir, opts) };
|
|
10751
|
+
}
|
|
10752
|
+
async function isFileRunContextDir(runDir) {
|
|
10753
|
+
return stat13(join22(runDir, SPAWN_JOURNAL_FILE)).then((s) => s.isFile()).catch(() => false);
|
|
10754
|
+
}
|
|
10755
|
+
function flattenJournal(journal) {
|
|
10756
|
+
return journal.events.map((event) => {
|
|
10757
|
+
if (event.kind !== "spawned") return JSON.stringify(event);
|
|
10758
|
+
return JSON.stringify({
|
|
10759
|
+
...event,
|
|
10760
|
+
role: inferRole(event.id, event.parent, journal.supervisorIds)
|
|
10761
|
+
});
|
|
10762
|
+
}).join("\n");
|
|
10763
|
+
}
|
|
10764
|
+
function runState(journal, resultText) {
|
|
10765
|
+
const result = parseJsonObject(resultText);
|
|
10766
|
+
const state = {};
|
|
10767
|
+
if (journal.begunAt !== null) state.startedAt = journal.begunAt;
|
|
10768
|
+
if (result !== null) {
|
|
10769
|
+
const kind = str(result.kind);
|
|
10770
|
+
const reason = str(result.reason);
|
|
10771
|
+
if (kind !== null) state.status = kind;
|
|
10772
|
+
if (reason !== null) state.verdict = reason;
|
|
10773
|
+
const spentTotal = asRecord(result.spentTotal);
|
|
10774
|
+
if (typeof spentTotal.usd === "number" && spentTotal.usdKnown !== false) {
|
|
10775
|
+
state.result = { spentUsd: spentTotal.usd };
|
|
10776
|
+
}
|
|
10777
|
+
}
|
|
10778
|
+
return JSON.stringify(state);
|
|
10779
|
+
}
|
|
10780
|
+
function parseJsonObject(text2) {
|
|
10781
|
+
if (text2 === null) return null;
|
|
10782
|
+
try {
|
|
10783
|
+
const parsed = JSON.parse(text2);
|
|
10784
|
+
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : null;
|
|
10785
|
+
} catch {
|
|
10786
|
+
return null;
|
|
10787
|
+
}
|
|
10788
|
+
}
|
|
10789
|
+
function workerSources(journal, coordination) {
|
|
10790
|
+
if (!journal.present) {
|
|
10791
|
+
return { workers: null, workersMissingReason: `${SPAWN_JOURNAL_FILE} absent` };
|
|
10792
|
+
}
|
|
10793
|
+
const view = safeTreeView(journal.events);
|
|
10794
|
+
if (view === null) {
|
|
10795
|
+
return { workers: null, workersMissingReason: "spawn journal could not be folded into a tree" };
|
|
10796
|
+
}
|
|
10797
|
+
const deliveredById = /* @__PURE__ */ new Map();
|
|
10798
|
+
for (const receipt of coordination.deliveries) {
|
|
10799
|
+
if (receipt.messageId !== null) deliveredById.set(receipt.messageId, receipt.delivered);
|
|
10800
|
+
}
|
|
10801
|
+
const spendById = settledSpendById(journal.events);
|
|
10802
|
+
const workers = [];
|
|
10803
|
+
for (const node of view.nodes) {
|
|
10804
|
+
if (node.parent === void 0) continue;
|
|
10805
|
+
const steers = coordination.steers.filter((s) => s.toWorker === node.id);
|
|
10806
|
+
const questions = coordination.questions.filter((q) => q.from === node.id);
|
|
10807
|
+
const spend = spendById.get(node.id) ?? null;
|
|
10808
|
+
workers.push({
|
|
10809
|
+
workerId: node.id,
|
|
10810
|
+
label: node.label,
|
|
10811
|
+
events: coordination.present ? [
|
|
10812
|
+
...steers.map(
|
|
10813
|
+
(s) => JSON.stringify({
|
|
10814
|
+
kind: "message",
|
|
10815
|
+
requestId: s.messageId,
|
|
10816
|
+
delivered: s.messageId === null ? null : deliveredById.get(s.messageId) ?? null
|
|
10817
|
+
})
|
|
10818
|
+
),
|
|
10819
|
+
...questions.map((q) => JSON.stringify({ kind: "message", direction: "up", id: q.id }))
|
|
10820
|
+
].join("\n") : null,
|
|
10821
|
+
inbox: coordination.present ? steers.map((s) => JSON.stringify({ id: s.messageId, message: s.instruction })).join("\n") : null,
|
|
10822
|
+
// Content-addressed result blobs are not patches; this layout retains no
|
|
10823
|
+
// per-worker diff, so byte counts would be a fabricated zero.
|
|
10824
|
+
patchBytes: null,
|
|
10825
|
+
transcriptRef: null,
|
|
10826
|
+
patchPath: null,
|
|
10827
|
+
tokensIn: spend?.tokens.input ?? null,
|
|
10828
|
+
tokensOut: spend?.tokens.output ?? null
|
|
10829
|
+
});
|
|
10830
|
+
}
|
|
10831
|
+
return { workers, workersMissingReason: null };
|
|
10832
|
+
}
|
|
10833
|
+
function settledSpendById(events) {
|
|
10834
|
+
const out = /* @__PURE__ */ new Map();
|
|
10835
|
+
for (const event of events) {
|
|
10836
|
+
if (event.kind !== "settled") continue;
|
|
10837
|
+
out.set(event.id, event.spent);
|
|
10838
|
+
}
|
|
10839
|
+
return out;
|
|
10840
|
+
}
|
|
10841
|
+
function sourceLimits(journal, coordination) {
|
|
10842
|
+
const unpriced = unpricedNodes(journal.events);
|
|
10843
|
+
const verdicts = journal.events.filter(
|
|
10844
|
+
(event) => event.kind === "settled" && event.verdict !== void 0
|
|
10845
|
+
).length;
|
|
10846
|
+
const settlements = journal.events.filter((event) => event.kind === "settled").length;
|
|
10847
|
+
return {
|
|
10848
|
+
...NO_SOURCE_LIMITS,
|
|
10849
|
+
// Both are journaled per node: `metered` for a driver's own inference,
|
|
10850
|
+
// `settled.spent` for what a child consumed.
|
|
10851
|
+
managerTokens: null,
|
|
10852
|
+
workerTokens: null,
|
|
10853
|
+
spendUsd: unpriced.length === 0 ? null : `${unpriced.length} spend record(s) carry usdKnown:false (${unpriced.slice(0, 4).join(", ")}${unpriced.length > 4 ? ", \u2026" : ""}) \u2014 the run\u2019s dollar total is unknown, not $0`,
|
|
10854
|
+
workerVerdicts: settlements === 0 || verdicts > 0 ? null : "no settled event carried a verdict; this layout records a done/down status only",
|
|
10855
|
+
deliverables: `${BLOBS_DIR}/ holds content-addressed result artifacts, not patches`
|
|
10856
|
+
};
|
|
10857
|
+
}
|
|
10858
|
+
function unpricedNodes(events) {
|
|
10859
|
+
const ids = [];
|
|
10860
|
+
for (const event of events) {
|
|
10861
|
+
const spend = event.kind === "settled" ? event.spent : event.kind === "metered" ? event.spend : null;
|
|
10862
|
+
if (spend?.usdKnown === false) ids.push(event.id);
|
|
10863
|
+
}
|
|
10864
|
+
return [...new Set(ids)];
|
|
10865
|
+
}
|
|
10866
|
+
var ZERO_ROLL = {
|
|
10867
|
+
records: 0,
|
|
10868
|
+
iterations: 0,
|
|
10869
|
+
tokensIn: 0,
|
|
10870
|
+
tokensOut: 0,
|
|
10871
|
+
usd: 0,
|
|
10872
|
+
usdKnownRecords: 0,
|
|
10873
|
+
usdUnknownRecords: 0
|
|
10874
|
+
};
|
|
10875
|
+
function addSpend(roll, spend) {
|
|
10876
|
+
const known = spend.usdKnown !== false;
|
|
10877
|
+
return {
|
|
10878
|
+
records: roll.records + 1,
|
|
10879
|
+
iterations: roll.iterations + (spend.iterations ?? 0),
|
|
10880
|
+
tokensIn: roll.tokensIn + (spend.tokens?.input ?? 0),
|
|
10881
|
+
tokensOut: roll.tokensOut + (spend.tokens?.output ?? 0),
|
|
10882
|
+
usd: roll.usd + (known ? spend.usd ?? 0 : 0),
|
|
10883
|
+
usdKnownRecords: roll.usdKnownRecords + (known ? 1 : 0),
|
|
10884
|
+
usdUnknownRecords: roll.usdUnknownRecords + (known ? 0 : 1)
|
|
10885
|
+
};
|
|
10886
|
+
}
|
|
10887
|
+
function mergeRolls(a, b) {
|
|
10888
|
+
return {
|
|
10889
|
+
records: a.records + b.records,
|
|
10890
|
+
iterations: a.iterations + b.iterations,
|
|
10891
|
+
tokensIn: a.tokensIn + b.tokensIn,
|
|
10892
|
+
tokensOut: a.tokensOut + b.tokensOut,
|
|
10893
|
+
usd: a.usd + b.usd,
|
|
10894
|
+
usdKnownRecords: a.usdKnownRecords + b.usdKnownRecords,
|
|
10895
|
+
usdUnknownRecords: a.usdUnknownRecords + b.usdUnknownRecords
|
|
10896
|
+
};
|
|
10897
|
+
}
|
|
10898
|
+
async function readRunContextSnapshot(runDir) {
|
|
10899
|
+
const journal = readRunContextJournal(await readMaybe(join22(runDir, SPAWN_JOURNAL_FILE)));
|
|
10900
|
+
const coordination = readCoordinationLog(await readMaybe(join22(runDir, COORDINATION_LOG_FILE)));
|
|
10901
|
+
const resultText = await readMaybe(join22(runDir, RESULT_FILE));
|
|
10902
|
+
const result = parseResult(resultText);
|
|
10903
|
+
const view = safeTreeView(journal.events);
|
|
10904
|
+
const nodes = view === null ? [] : orderNodes(view, journal);
|
|
10905
|
+
return {
|
|
10906
|
+
runDir,
|
|
10907
|
+
runId: journal.root,
|
|
10908
|
+
present: journal.present,
|
|
10909
|
+
nodes,
|
|
10910
|
+
maxDepth: nodes.reduce((max, n) => Math.max(max, n.depth), 0),
|
|
10911
|
+
begunAt: parseInstant(journal.begunAt),
|
|
10912
|
+
lastEventAt: journal.events.reduce((latest, event) => {
|
|
10913
|
+
const at = parseInstant(event.at);
|
|
10914
|
+
return at === null ? latest : latest === null ? at : Math.max(latest, at);
|
|
10915
|
+
}, null),
|
|
10916
|
+
questions: coordination.questions,
|
|
10917
|
+
steers: coordination.steers,
|
|
10918
|
+
result,
|
|
10919
|
+
terminal: resultText !== null,
|
|
10920
|
+
invalidJournalRows: journal.invalidRows,
|
|
10921
|
+
invalidCoordinationRows: coordination.invalidRows,
|
|
10922
|
+
partialTail: journal.partialTail || coordination.partialTail,
|
|
10923
|
+
treeError: view === null && journal.present ? "spawn journal could not be folded into a tree (a settle or cancel names a node that was never spawned)" : null
|
|
10924
|
+
};
|
|
10925
|
+
}
|
|
10926
|
+
function safeTreeView(events) {
|
|
10927
|
+
if (events.length === 0) return null;
|
|
10928
|
+
try {
|
|
10929
|
+
return materializeTreeView([...events]);
|
|
10930
|
+
} catch {
|
|
10931
|
+
return null;
|
|
10932
|
+
}
|
|
10933
|
+
}
|
|
10934
|
+
function orderNodes(view, journal) {
|
|
10935
|
+
const driver = /* @__PURE__ */ new Map();
|
|
10936
|
+
const settled = /* @__PURE__ */ new Map();
|
|
10937
|
+
const childRoll = /* @__PURE__ */ new Map();
|
|
10938
|
+
const spawnedAt = /* @__PURE__ */ new Map();
|
|
10939
|
+
const settledAt = /* @__PURE__ */ new Map();
|
|
10940
|
+
const cancelReason = /* @__PURE__ */ new Map();
|
|
10941
|
+
const parentOf = new Map(view.nodes.map((n) => [n.id, n.parent]));
|
|
10942
|
+
for (const event of journal.events) {
|
|
10943
|
+
const at = parseInstant(event.at);
|
|
10944
|
+
if (event.kind === "spawned" || event.kind === "waiting") {
|
|
10945
|
+
spawnedAt.set(event.id, at);
|
|
10946
|
+
} else if (event.kind === "metered") {
|
|
10947
|
+
driver.set(event.id, addSpend(driver.get(event.id) ?? ZERO_ROLL, event.spend));
|
|
10948
|
+
} else if (event.kind === "settled") {
|
|
10949
|
+
settled.set(event.id, addSpend(settled.get(event.id) ?? ZERO_ROLL, event.spent));
|
|
10950
|
+
settledAt.set(event.id, at);
|
|
10951
|
+
const parent = parentOf.get(event.id);
|
|
10952
|
+
if (parent !== void 0) {
|
|
10953
|
+
childRoll.set(parent, addSpend(childRoll.get(parent) ?? ZERO_ROLL, event.spent));
|
|
10954
|
+
}
|
|
10955
|
+
} else if (event.kind === "cancelled") {
|
|
10956
|
+
settledAt.set(event.id, at);
|
|
10957
|
+
cancelReason.set(event.id, event.reason);
|
|
10958
|
+
} else if (event.kind === "woken") {
|
|
10959
|
+
settledAt.set(event.id, at);
|
|
10960
|
+
}
|
|
10961
|
+
}
|
|
10962
|
+
const byParent = /* @__PURE__ */ new Map();
|
|
10963
|
+
for (const node of view.nodes) {
|
|
10964
|
+
const key = node.parent ?? null;
|
|
10965
|
+
const bucket = byParent.get(key);
|
|
10966
|
+
if (bucket) bucket.push(node);
|
|
10967
|
+
else byParent.set(key, [node]);
|
|
10968
|
+
}
|
|
10969
|
+
const out = [];
|
|
10970
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10971
|
+
const walk = (id, depth) => {
|
|
10972
|
+
if (seen.has(id)) return;
|
|
10973
|
+
seen.add(id);
|
|
10974
|
+
const node = view.nodes.find((n) => n.id === id);
|
|
10975
|
+
if (node === void 0) return;
|
|
10976
|
+
const spawned = spawnedAt.get(id) ?? null;
|
|
10977
|
+
const closed = settledAt.get(id) ?? null;
|
|
10978
|
+
out.push({
|
|
10979
|
+
id: node.id,
|
|
10980
|
+
parent: node.parent ?? null,
|
|
10981
|
+
label: node.label,
|
|
10982
|
+
depth,
|
|
10983
|
+
role: inferRole(node.id, node.parent, journal.supervisorIds),
|
|
10984
|
+
status: node.status,
|
|
10985
|
+
runtime: node.runtime ?? null,
|
|
10986
|
+
budget: node.budget,
|
|
10987
|
+
driver: driver.get(id) ?? ZERO_ROLL,
|
|
10988
|
+
children: childRoll.get(id) ?? ZERO_ROLL,
|
|
10989
|
+
settled: settled.get(id) ?? ZERO_ROLL,
|
|
10990
|
+
spawnedAt: spawned,
|
|
10991
|
+
settledAt: closed,
|
|
10992
|
+
wallMs: spawned !== null && closed !== null ? closed - spawned : null,
|
|
10993
|
+
outRef: node.outRef ?? null,
|
|
10994
|
+
cancelReason: cancelReason.get(id) ?? null
|
|
10995
|
+
});
|
|
10996
|
+
for (const child of byParent.get(id) ?? []) walk(child.id, depth + 1);
|
|
10997
|
+
};
|
|
10998
|
+
walk(view.root, 0);
|
|
10999
|
+
for (const node of view.nodes) if (!seen.has(node.id)) walk(node.id, 0);
|
|
11000
|
+
return out;
|
|
11001
|
+
}
|
|
11002
|
+
function parseResult(text2) {
|
|
11003
|
+
const result = parseJsonObject(text2);
|
|
11004
|
+
if (result === null) return null;
|
|
11005
|
+
const error = asRecord(result.error);
|
|
11006
|
+
return {
|
|
11007
|
+
kind: str(result.kind),
|
|
11008
|
+
reason: str(result.reason),
|
|
11009
|
+
errorName: str(error.name),
|
|
11010
|
+
errorMessage: str(error.message)
|
|
11011
|
+
};
|
|
11012
|
+
}
|
|
11013
|
+
|
|
11014
|
+
// src/run-view-format.ts
|
|
11015
|
+
var RULE = "\u2500".repeat(76);
|
|
11016
|
+
function int(n) {
|
|
11017
|
+
return n.toLocaleString("en-US");
|
|
11018
|
+
}
|
|
11019
|
+
function ms(value) {
|
|
11020
|
+
if (value === null) return "\u2014";
|
|
11021
|
+
if (value < 1e3) return `${value}ms`;
|
|
11022
|
+
if (value < 6e4) return `${(value / 1e3).toFixed(1)}s`;
|
|
11023
|
+
const minutes = Math.floor(value / 6e4);
|
|
11024
|
+
return `${minutes}m${String(Math.round(value % 6e4 / 1e3)).padStart(2, "0")}s`;
|
|
11025
|
+
}
|
|
11026
|
+
function usd(roll) {
|
|
11027
|
+
if (roll.usdKnownRecords === 0) {
|
|
11028
|
+
return roll.usdUnknownRecords === 0 ? "no spend recorded" : `cost unknown (${roll.usdUnknownRecords} unpriced)`;
|
|
11029
|
+
}
|
|
11030
|
+
const priced = zeroPricedCost(roll) ? `$${roll.usd.toFixed(4)} (zero across ${roll.usdKnownRecords} priced)` : `$${roll.usd.toFixed(4)}`;
|
|
11031
|
+
return roll.usdUnknownRecords === 0 ? priced : `${priced} + ${roll.usdUnknownRecords} unpriced`;
|
|
11032
|
+
}
|
|
11033
|
+
function zeroPricedCost(roll) {
|
|
11034
|
+
return roll.usdKnownRecords > 0 && roll.usd === 0 && roll.tokensIn + roll.tokensOut > 0;
|
|
11035
|
+
}
|
|
11036
|
+
function tokens2(roll) {
|
|
11037
|
+
return `in ${int(roll.tokensIn)} \xB7 out ${int(roll.tokensOut)}`;
|
|
11038
|
+
}
|
|
11039
|
+
var STATUS_MARK = {
|
|
11040
|
+
pending: "\xB7",
|
|
11041
|
+
acquiring: "\u25D0",
|
|
11042
|
+
running: "\u25B6",
|
|
11043
|
+
waiting: "\u23F8",
|
|
11044
|
+
done: "\u2713",
|
|
11045
|
+
failed: "\u2717",
|
|
11046
|
+
cancelled: "\u2298"
|
|
11047
|
+
};
|
|
11048
|
+
function connectors(depth, lastAtDepth) {
|
|
11049
|
+
if (depth === 0) return { head: "\u25CF ", detail: " " };
|
|
11050
|
+
let stem = "";
|
|
11051
|
+
for (let level = 1; level < depth; level++) stem += lastAtDepth[level] ? " " : "\u2502 ";
|
|
11052
|
+
const isLast = lastAtDepth[depth] ?? true;
|
|
11053
|
+
return { head: `${stem}${isLast ? "\u2514\u2500 " : "\u251C\u2500 "}`, detail: `${stem}${isLast ? " " : "\u2502 "} ` };
|
|
11054
|
+
}
|
|
11055
|
+
function forEachTreeNode(nodes, parentKey, selfKey, visit) {
|
|
11056
|
+
const lastChildOf = /* @__PURE__ */ new Map();
|
|
11057
|
+
for (const node of nodes) lastChildOf.set(parentKey(node), selfKey(node));
|
|
11058
|
+
const lastAtDepth = [];
|
|
11059
|
+
for (const node of nodes) {
|
|
11060
|
+
lastAtDepth[node.depth] = lastChildOf.get(parentKey(node)) === selfKey(node);
|
|
11061
|
+
lastAtDepth.length = node.depth + 1;
|
|
11062
|
+
visit(node, lastAtDepth);
|
|
11063
|
+
}
|
|
11064
|
+
}
|
|
11065
|
+
function wrap(text2, indent) {
|
|
11066
|
+
const width = Math.max(40, (process.stdout.columns ?? 100) - indent - 2);
|
|
11067
|
+
const pad = " ".repeat(indent);
|
|
11068
|
+
const out = [];
|
|
11069
|
+
for (const paragraph of text2.split("\n")) {
|
|
11070
|
+
let line = "";
|
|
11071
|
+
for (const word of paragraph.split(/\s+/).filter(Boolean)) {
|
|
11072
|
+
if (line.length > 0 && line.length + 1 + word.length > width) {
|
|
11073
|
+
out.push(pad + line);
|
|
11074
|
+
line = word;
|
|
11075
|
+
} else line = line.length === 0 ? word : `${line} ${word}`;
|
|
11076
|
+
}
|
|
11077
|
+
out.push(pad + line);
|
|
11078
|
+
}
|
|
11079
|
+
return out;
|
|
11080
|
+
}
|
|
11081
|
+
|
|
11082
|
+
// src/run-span-tree.ts
|
|
11083
|
+
import { readdir as readdir15 } from "fs/promises";
|
|
11084
|
+
import { join as join23 } from "path";
|
|
11085
|
+
import {
|
|
11086
|
+
firstNumberAttr as firstNumberAttr2,
|
|
11087
|
+
LLM_COST_ATTR_KEYS as LLM_COST_ATTR_KEYS2,
|
|
11088
|
+
LLM_INPUT_TOKEN_ATTR_KEYS as LLM_INPUT_TOKEN_ATTR_KEYS2,
|
|
11089
|
+
LLM_OUTPUT_TOKEN_ATTR_KEYS as LLM_OUTPUT_TOKEN_ATTR_KEYS2,
|
|
11090
|
+
SPAN_KIND_ATTR_KEYS
|
|
11091
|
+
} from "@tangle-network/agent-eval/trace-attributes";
|
|
11092
|
+
var AGENT_NAME_ATTR_KEYS = ["agent.name", "inference.agent_name", "gen_ai.agent.name"];
|
|
11093
|
+
var SERVICE_NAME_ATTR_KEYS = ["service.name", "tangle.runtime"];
|
|
11094
|
+
var AGENT_OPERATIONS = /* @__PURE__ */ new Set(["invoke_agent", "invoke_workflow"]);
|
|
11095
|
+
var OPERATION_ATTR_KEY = "gen_ai.operation.name";
|
|
11096
|
+
function firstStringAttr(attributes, keys) {
|
|
11097
|
+
for (const key of keys) {
|
|
11098
|
+
const value = attributes[key];
|
|
11099
|
+
if (typeof value === "string" && value.length > 0) return value;
|
|
11100
|
+
}
|
|
11101
|
+
return null;
|
|
11102
|
+
}
|
|
11103
|
+
var EMPTY_SPAN_RUN_SNAPSHOT = {
|
|
11104
|
+
present: false,
|
|
11105
|
+
nodes: [],
|
|
11106
|
+
maxDepth: 0,
|
|
11107
|
+
traceIds: [],
|
|
11108
|
+
spanCount: 0,
|
|
11109
|
+
startMs: null,
|
|
11110
|
+
endMs: null,
|
|
11111
|
+
llmSpansWithoutTokens: 0,
|
|
11112
|
+
llmSpansWithoutCost: 0,
|
|
11113
|
+
orphanSpans: 0,
|
|
11114
|
+
readError: null
|
|
11115
|
+
};
|
|
11116
|
+
function spanKind3(span2) {
|
|
11117
|
+
return firstStringAttr(span2.attributes, SPAN_KIND_ATTR_KEYS);
|
|
11118
|
+
}
|
|
11119
|
+
function isNodeBoundary(span2, enclosingNodeId, enclosingAgent) {
|
|
11120
|
+
if (span2.parent_span_id === null || enclosingNodeId === null) return true;
|
|
11121
|
+
if (spanKind3(span2) === "AGENT") return true;
|
|
11122
|
+
if (AGENT_OPERATIONS.has(String(span2.attributes[OPERATION_ATTR_KEY] ?? ""))) return true;
|
|
11123
|
+
const agent = firstStringAttr(span2.attributes, AGENT_NAME_ATTR_KEYS);
|
|
11124
|
+
return agent !== null && enclosingAgent !== null && agent !== enclosingAgent;
|
|
11125
|
+
}
|
|
11126
|
+
function spanSpend(span2) {
|
|
11127
|
+
const cost = firstNumberAttr2(span2.attributes, LLM_COST_ATTR_KEYS2);
|
|
11128
|
+
return {
|
|
11129
|
+
iterations: 0,
|
|
11130
|
+
tokens: {
|
|
11131
|
+
input: firstNumberAttr2(span2.attributes, LLM_INPUT_TOKEN_ATTR_KEYS2) ?? 0,
|
|
11132
|
+
output: firstNumberAttr2(span2.attributes, LLM_OUTPUT_TOKEN_ATTR_KEYS2) ?? 0
|
|
11133
|
+
},
|
|
11134
|
+
usd: cost ?? 0,
|
|
11135
|
+
...cost === null ? { usdKnown: false } : {},
|
|
11136
|
+
ms: 0
|
|
11137
|
+
};
|
|
11138
|
+
}
|
|
11139
|
+
function epoch(value) {
|
|
11140
|
+
const parsed = Date.parse(value);
|
|
11141
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
11142
|
+
}
|
|
11143
|
+
function buildSpanRunTree(spans, source) {
|
|
11144
|
+
if (spans.length === 0) return { ...EMPTY_SPAN_RUN_SNAPSHOT, source };
|
|
11145
|
+
const byId = /* @__PURE__ */ new Map();
|
|
11146
|
+
for (const span2 of spans) byId.set(spanKey(span2), span2);
|
|
11147
|
+
const childrenOf = /* @__PURE__ */ new Map();
|
|
11148
|
+
let orphanSpans = 0;
|
|
11149
|
+
for (const span2 of spans) {
|
|
11150
|
+
const parentKey = span2.parent_span_id === null ? null : byId.has(`${span2.trace_id}\0${span2.parent_span_id}`) ? `${span2.trace_id}\0${span2.parent_span_id}` : null;
|
|
11151
|
+
if (span2.parent_span_id !== null && parentKey === null) orphanSpans += 1;
|
|
11152
|
+
const bucket = childrenOf.get(parentKey);
|
|
11153
|
+
if (bucket) bucket.push(span2);
|
|
11154
|
+
else childrenOf.set(parentKey, [span2]);
|
|
11155
|
+
}
|
|
11156
|
+
for (const bucket of childrenOf.values()) {
|
|
11157
|
+
bucket.sort((a, b) => epoch(a.start_time) - epoch(b.start_time) || a.span_id.localeCompare(b.span_id));
|
|
11158
|
+
}
|
|
11159
|
+
const building = /* @__PURE__ */ new Map();
|
|
11160
|
+
const order = [];
|
|
11161
|
+
let llmSpansWithoutTokens = 0;
|
|
11162
|
+
let llmSpansWithoutCost = 0;
|
|
11163
|
+
const walk = (span2, enclosingNodeId, enclosingAgent, depth) => {
|
|
11164
|
+
const key = spanKey(span2);
|
|
11165
|
+
const agent = firstStringAttr(span2.attributes, AGENT_NAME_ATTR_KEYS) ?? enclosingAgent;
|
|
11166
|
+
let nodeId = enclosingNodeId;
|
|
11167
|
+
let nodeAgent = enclosingAgent;
|
|
11168
|
+
let childDepth = depth;
|
|
11169
|
+
if (isNodeBoundary(span2, enclosingNodeId, enclosingAgent)) {
|
|
11170
|
+
const node = {
|
|
11171
|
+
span: span2,
|
|
11172
|
+
parentNodeId: enclosingNodeId,
|
|
11173
|
+
depth,
|
|
11174
|
+
agent,
|
|
11175
|
+
driver: ZERO_ROLL,
|
|
11176
|
+
toolCalls: 0,
|
|
11177
|
+
startMs: epoch(span2.start_time),
|
|
11178
|
+
endMs: epoch(span2.end_time),
|
|
11179
|
+
errors: [],
|
|
11180
|
+
childNodeIds: []
|
|
11181
|
+
};
|
|
11182
|
+
building.set(key, node);
|
|
11183
|
+
order.push(key);
|
|
11184
|
+
if (enclosingNodeId !== null) building.get(enclosingNodeId)?.childNodeIds.push(key);
|
|
11185
|
+
nodeId = key;
|
|
11186
|
+
nodeAgent = agent;
|
|
11187
|
+
childDepth = depth + 1;
|
|
11188
|
+
}
|
|
11189
|
+
const host = nodeId === null ? void 0 : building.get(nodeId);
|
|
11190
|
+
if (host !== void 0) {
|
|
11191
|
+
const kind = spanKind3(span2);
|
|
11192
|
+
if (kind === "LLM") {
|
|
11193
|
+
host.driver = addSpend(host.driver, spanSpend(span2));
|
|
11194
|
+
if (firstNumberAttr2(span2.attributes, LLM_INPUT_TOKEN_ATTR_KEYS2) === null && firstNumberAttr2(span2.attributes, LLM_OUTPUT_TOKEN_ATTR_KEYS2) === null) {
|
|
11195
|
+
llmSpansWithoutTokens += 1;
|
|
11196
|
+
}
|
|
11197
|
+
if (firstNumberAttr2(span2.attributes, LLM_COST_ATTR_KEYS2) === null) llmSpansWithoutCost += 1;
|
|
11198
|
+
} else if (kind === "TOOL") host.toolCalls += 1;
|
|
11199
|
+
host.startMs = Math.min(host.startMs, epoch(span2.start_time));
|
|
11200
|
+
host.endMs = Math.max(host.endMs, epoch(span2.end_time));
|
|
11201
|
+
if (span2.status.code === "ERROR") {
|
|
11202
|
+
host.errors.push(`${span2.name}: ${span2.status.message ?? "ERROR"}`);
|
|
11203
|
+
}
|
|
11204
|
+
}
|
|
11205
|
+
for (const child of childrenOf.get(key) ?? []) walk(child, nodeId, nodeAgent, childDepth);
|
|
11206
|
+
};
|
|
11207
|
+
for (const root of childrenOf.get(null) ?? []) walk(root, null, null, 0);
|
|
11208
|
+
const subtotal = /* @__PURE__ */ new Map();
|
|
11209
|
+
const subtotalOf = (key) => {
|
|
11210
|
+
const cached = subtotal.get(key);
|
|
11211
|
+
if (cached !== void 0) return cached;
|
|
11212
|
+
const node = building.get(key);
|
|
11213
|
+
if (node === void 0) return ZERO_ROLL;
|
|
11214
|
+
subtotal.set(key, ZERO_ROLL);
|
|
11215
|
+
let total = node.driver;
|
|
11216
|
+
for (const child of node.childNodeIds) total = mergeRolls(total, subtotalOf(child));
|
|
11217
|
+
subtotal.set(key, total);
|
|
11218
|
+
return total;
|
|
11219
|
+
};
|
|
11220
|
+
const nodes = order.map((key) => {
|
|
11221
|
+
const node = building.get(key);
|
|
11222
|
+
let children = ZERO_ROLL;
|
|
11223
|
+
for (const child of node.childNodeIds) children = mergeRolls(children, subtotalOf(child));
|
|
11224
|
+
return {
|
|
11225
|
+
traceId: node.span.trace_id,
|
|
11226
|
+
spanId: node.span.span_id,
|
|
11227
|
+
parentNodeId: node.parentNodeId === null ? null : building.get(node.parentNodeId).span.span_id,
|
|
11228
|
+
label: node.agent ?? node.span.name,
|
|
11229
|
+
depth: node.depth,
|
|
11230
|
+
role: node.childNodeIds.length > 0 ? "driver" : "leaf",
|
|
11231
|
+
status: node.errors.length > 0 || node.span.status.code === "ERROR" ? "failed" : node.span.status.code === "UNSET" ? "running" : "done",
|
|
11232
|
+
runtime: firstStringAttr(node.span.attributes, SERVICE_NAME_ATTR_KEYS),
|
|
11233
|
+
driver: node.driver,
|
|
11234
|
+
children,
|
|
11235
|
+
toolCalls: node.toolCalls,
|
|
11236
|
+
startMs: node.startMs,
|
|
11237
|
+
endMs: node.endMs,
|
|
11238
|
+
wallMs: Math.max(0, node.endMs - node.startMs),
|
|
11239
|
+
errors: node.errors
|
|
11240
|
+
};
|
|
11241
|
+
});
|
|
11242
|
+
return {
|
|
11243
|
+
source,
|
|
11244
|
+
present: true,
|
|
11245
|
+
nodes,
|
|
11246
|
+
maxDepth: nodes.reduce((max, n) => Math.max(max, n.depth), 0),
|
|
11247
|
+
traceIds: [...new Set(spans.map((s) => s.trace_id))],
|
|
11248
|
+
spanCount: spans.length,
|
|
11249
|
+
startMs: nodes.reduce((min, n) => min === null ? n.startMs : Math.min(min, n.startMs), null),
|
|
11250
|
+
endMs: nodes.reduce((max, n) => max === null ? n.endMs : Math.max(max, n.endMs), null),
|
|
11251
|
+
llmSpansWithoutTokens,
|
|
11252
|
+
llmSpansWithoutCost,
|
|
11253
|
+
orphanSpans,
|
|
11254
|
+
readError: null
|
|
11255
|
+
};
|
|
11256
|
+
}
|
|
11257
|
+
function spanKey(span2) {
|
|
11258
|
+
return `${span2.trace_id}\0${span2.span_id}`;
|
|
11259
|
+
}
|
|
11260
|
+
async function readSpanRunSnapshot(path) {
|
|
11261
|
+
try {
|
|
11262
|
+
const exported = await exportTraceEvidenceFile(path);
|
|
11263
|
+
return buildSpanRunTree(exported.spans, path);
|
|
11264
|
+
} catch (error) {
|
|
11265
|
+
return {
|
|
11266
|
+
...EMPTY_SPAN_RUN_SNAPSHOT,
|
|
11267
|
+
source: path,
|
|
11268
|
+
readError: error instanceof Error ? error.message : String(error)
|
|
11269
|
+
};
|
|
11270
|
+
}
|
|
11271
|
+
}
|
|
11272
|
+
function spanRunTotals(snapshot) {
|
|
11273
|
+
let driver = ZERO_ROLL;
|
|
11274
|
+
let leaves = ZERO_ROLL;
|
|
11275
|
+
for (const node of snapshot.nodes) {
|
|
11276
|
+
if (node.role === "driver") driver = mergeRolls(driver, node.driver);
|
|
11277
|
+
else leaves = mergeRolls(leaves, node.driver);
|
|
11278
|
+
}
|
|
11279
|
+
return { driver, leaves };
|
|
11280
|
+
}
|
|
11281
|
+
function renderSpanRunSnapshot(snapshot) {
|
|
11282
|
+
const lines = [];
|
|
11283
|
+
lines.push(`spans ${snapshot.source}`);
|
|
11284
|
+
if (snapshot.readError !== null) {
|
|
11285
|
+
lines.push(` unreadable: ${snapshot.readError}`);
|
|
11286
|
+
return lines.join("\n");
|
|
11287
|
+
}
|
|
11288
|
+
if (!snapshot.present || snapshot.nodes.length === 0) {
|
|
11289
|
+
lines.push(" no spans yet \u2014 the emitter has written nothing this view can place");
|
|
11290
|
+
return lines.join("\n");
|
|
11291
|
+
}
|
|
11292
|
+
const totals = spanRunTotals(snapshot);
|
|
11293
|
+
lines.push(
|
|
11294
|
+
` ${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)}`
|
|
11295
|
+
);
|
|
11296
|
+
const delegating = snapshot.nodes.some((node) => node.role === "driver");
|
|
11297
|
+
lines.push(` driver inference ${tokens2(totals.driver)} \xB7 ${usd(totals.driver)}`);
|
|
11298
|
+
lines.push(` leaf inference ${tokens2(totals.leaves)} \xB7 ${usd(totals.leaves)}`);
|
|
11299
|
+
if (!delegating) {
|
|
11300
|
+
lines.push(" (no delegation in these spans: every turn belongs to one agent, so the driver half is empty)");
|
|
11301
|
+
}
|
|
11302
|
+
lines.push("");
|
|
11303
|
+
forEachTreeNode(
|
|
11304
|
+
snapshot.nodes,
|
|
11305
|
+
(n) => n.parentNodeId,
|
|
11306
|
+
(n) => n.spanId,
|
|
11307
|
+
(node, lastAtDepth) => {
|
|
11308
|
+
const { head: prefix, detail } = connectors(node.depth, lastAtDepth);
|
|
11309
|
+
const mark = node.status === "failed" ? "\u2717" : node.status === "running" ? "\u25B6" : "\u2713";
|
|
11310
|
+
lines.push(
|
|
11311
|
+
[
|
|
11312
|
+
`${prefix}${mark} ${node.label}`,
|
|
11313
|
+
`[${node.status}]`,
|
|
11314
|
+
node.role,
|
|
11315
|
+
node.runtime ?? "no-runtime",
|
|
11316
|
+
"budget not in spans",
|
|
11317
|
+
`wall ${ms(node.wallMs)}`
|
|
11318
|
+
].join(" ")
|
|
11319
|
+
);
|
|
11320
|
+
lines.push(
|
|
11321
|
+
`${detail}driver ${node.driver.records} turn(s) \xB7 ${tokens2(node.driver)} \xB7 ${usd(node.driver)} \xB7 ${node.toolCalls} tool call(s)`
|
|
11322
|
+
);
|
|
11323
|
+
if (node.role === "driver") {
|
|
11324
|
+
lines.push(
|
|
11325
|
+
`${detail}children ${node.children.records} turn(s) \xB7 ${tokens2(node.children)} \xB7 ${usd(node.children)}`
|
|
11326
|
+
);
|
|
11327
|
+
}
|
|
11328
|
+
for (const error of node.errors.slice(0, 3)) lines.push(`${detail}error ${error}`);
|
|
11329
|
+
}
|
|
11330
|
+
);
|
|
11331
|
+
const problems = [];
|
|
11332
|
+
if (snapshot.llmSpansWithoutCost > 0) {
|
|
11333
|
+
problems.push(
|
|
11334
|
+
`${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)`
|
|
11335
|
+
);
|
|
11336
|
+
}
|
|
11337
|
+
if (snapshot.llmSpansWithoutTokens > 0) {
|
|
11338
|
+
problems.push(`${snapshot.llmSpansWithoutTokens} LLM span(s) carry no token count`);
|
|
11339
|
+
}
|
|
11340
|
+
if (snapshot.orphanSpans > 0) {
|
|
11341
|
+
problems.push(
|
|
11342
|
+
`${snapshot.orphanSpans} span(s) name a parent_span_id absent from this file \u2014 re-rooted, not dropped`
|
|
11343
|
+
);
|
|
11344
|
+
}
|
|
11345
|
+
problems.push(
|
|
11346
|
+
"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"
|
|
11347
|
+
);
|
|
11348
|
+
lines.push("");
|
|
11349
|
+
for (const problem of problems) lines.push(` ! ${problem}`);
|
|
11350
|
+
return lines.join("\n");
|
|
11351
|
+
}
|
|
11352
|
+
var SPAN_FILE_PATTERN = /\.(?:otlp|spans|traces|openinference)\.(?:jsonl|ndjson)$|^traces?\.jsonl$|^spans\.jsonl$/i;
|
|
11353
|
+
async function findSpanSourceFiles(dir) {
|
|
11354
|
+
const found = [];
|
|
11355
|
+
const entries = await readdir15(dir, { withFileTypes: true }).catch(() => []);
|
|
11356
|
+
for (const entry of entries) {
|
|
11357
|
+
const child = join23(dir, entry.name);
|
|
11358
|
+
if (entry.isFile()) {
|
|
11359
|
+
if (SPAN_FILE_PATTERN.test(entry.name)) found.push(child);
|
|
11360
|
+
continue;
|
|
11361
|
+
}
|
|
11362
|
+
if (!entry.isDirectory()) continue;
|
|
11363
|
+
const nested = await readdir15(child, { withFileTypes: true }).catch(() => []);
|
|
11364
|
+
for (const inner of nested) {
|
|
11365
|
+
if (inner.isFile() && SPAN_FILE_PATTERN.test(inner.name)) found.push(join23(child, inner.name));
|
|
11366
|
+
}
|
|
11367
|
+
}
|
|
11368
|
+
return found.sort();
|
|
11369
|
+
}
|
|
11370
|
+
|
|
11371
|
+
// src/supervisor-run-watch.ts
|
|
11372
|
+
import { readdir as readdir16 } from "fs/promises";
|
|
11373
|
+
import { join as join24 } from "path";
|
|
11374
|
+
function budget(node) {
|
|
11375
|
+
const parts = [`${int(node.budget.maxIterations)}it`, `${int(node.budget.maxTokens)}tok`];
|
|
11376
|
+
if (node.budget.maxUsd !== void 0) parts.push(`$${node.budget.maxUsd.toFixed(2)}`);
|
|
11377
|
+
if (node.budget.deadlineMs !== void 0) parts.push(ms(node.budget.deadlineMs));
|
|
11378
|
+
return parts.join("/");
|
|
11379
|
+
}
|
|
11380
|
+
function budgetUse(node, spent) {
|
|
11381
|
+
const parts = [];
|
|
11382
|
+
if (node.budget.maxIterations > 0 && spent.iterations > 0) {
|
|
11383
|
+
parts.push(`${Math.round(spent.iterations / node.budget.maxIterations * 100)}% it`);
|
|
11384
|
+
}
|
|
11385
|
+
const used = spent.tokensIn + spent.tokensOut;
|
|
11386
|
+
if (node.budget.maxTokens > 0 && used > 0) {
|
|
11387
|
+
parts.push(`${Math.round(used / node.budget.maxTokens * 100)}% tok`);
|
|
11388
|
+
}
|
|
11389
|
+
return parts.length === 0 ? "" : ` \xB7 ${parts.join(", ")} of budget`;
|
|
11390
|
+
}
|
|
11391
|
+
function renderNode(node, lastAtDepth, lines) {
|
|
11392
|
+
const mark = STATUS_MARK[node.status] ?? "?";
|
|
11393
|
+
const { head: prefix, detail } = connectors(node.depth, lastAtDepth);
|
|
11394
|
+
const head = [
|
|
11395
|
+
`${prefix}${mark} ${node.label}`,
|
|
11396
|
+
`[${node.status}]`,
|
|
11397
|
+
node.role,
|
|
11398
|
+
node.runtime ?? "no-runtime",
|
|
11399
|
+
`budget ${budget(node)}`
|
|
11400
|
+
];
|
|
11401
|
+
if (node.wallMs !== null) head.push(`wall ${ms(node.wallMs)}`);
|
|
11402
|
+
lines.push(head.join(" "));
|
|
11403
|
+
if (node.driver.records > 0 || node.role === "supervisor") {
|
|
11404
|
+
lines.push(
|
|
11405
|
+
`${detail}driver ${node.driver.records} turn(s) \xB7 ${tokens2(node.driver)} \xB7 ${usd(node.driver)}` + budgetUse(node, node.driver)
|
|
11406
|
+
);
|
|
11407
|
+
}
|
|
11408
|
+
if (node.children.records > 0 || node.role === "supervisor") {
|
|
11409
|
+
lines.push(
|
|
11410
|
+
`${detail}children ${node.children.records} settled \xB7 ${tokens2(node.children)} \xB7 ${usd(node.children)}`
|
|
11411
|
+
);
|
|
11412
|
+
}
|
|
11413
|
+
if (node.settled.records > 0) {
|
|
11414
|
+
lines.push(
|
|
11415
|
+
`${detail}settled ${node.settled.iterations}it \xB7 ${tokens2(node.settled)} \xB7 ${usd(node.settled)}` + budgetUse(node, node.settled)
|
|
11416
|
+
);
|
|
11417
|
+
}
|
|
11418
|
+
if (node.cancelReason !== null) lines.push(`${detail}cancelled: ${node.cancelReason}`);
|
|
11419
|
+
if (node.outRef !== null) lines.push(`${detail}out ${node.outRef}`);
|
|
11420
|
+
}
|
|
11421
|
+
function renderQuestion(q, lines) {
|
|
11422
|
+
const mark = q.blocking ? "BLOCKING" : q.urgency ?? "question";
|
|
11423
|
+
lines.push(` \u26A0 ${mark} from ${q.from ?? "unknown"}${q.status === null ? "" : ` [${q.status}]`}`);
|
|
11424
|
+
for (const line of wrap(q.question, 4)) lines.push(line);
|
|
11425
|
+
if (q.reason !== null) for (const line of wrap(`why: ${q.reason}`, 4)) lines.push(line);
|
|
11426
|
+
}
|
|
11427
|
+
function renderRunContextSnapshot(snapshot) {
|
|
11428
|
+
const lines = [];
|
|
11429
|
+
if (!snapshot.present) {
|
|
11430
|
+
lines.push(`run ${snapshot.runDir}`);
|
|
11431
|
+
lines.push(" no spawn-journal.jsonl yet \u2014 the run has not begun writing");
|
|
11432
|
+
return lines.join("\n");
|
|
11433
|
+
}
|
|
11434
|
+
const totals = runTotals(snapshot);
|
|
11435
|
+
lines.push(`run ${snapshot.runId ?? snapshot.runDir}`);
|
|
11436
|
+
lines.push(
|
|
11437
|
+
` ${snapshot.nodes.length} node(s) \xB7 depth ${snapshot.maxDepth} \xB7 elapsed ${ms(elapsed(snapshot))}${snapshot.terminal ? "" : " \xB7 live"}`
|
|
11438
|
+
);
|
|
11439
|
+
lines.push(` driver total ${tokens2(totals.driver)} \xB7 ${usd(totals.driver)}`);
|
|
11440
|
+
lines.push(` worker total ${tokens2(totals.workers)} \xB7 ${usd(totals.workers)}`);
|
|
11441
|
+
lines.push("");
|
|
11442
|
+
if (snapshot.nodes.length === 0) lines.push(" (no node spawned yet)");
|
|
11443
|
+
forEachTreeNode(
|
|
11444
|
+
snapshot.nodes,
|
|
11445
|
+
(n) => n.parent,
|
|
11446
|
+
(n) => n.id,
|
|
11447
|
+
(node, lastAtDepth) => renderNode(node, lastAtDepth, lines)
|
|
11448
|
+
);
|
|
11449
|
+
if (snapshot.questions.length > 0) {
|
|
11450
|
+
lines.push("");
|
|
11451
|
+
for (const q of snapshot.questions) renderQuestion(q, lines);
|
|
11452
|
+
}
|
|
11453
|
+
if (snapshot.steers.length > 0) {
|
|
11454
|
+
lines.push("");
|
|
11455
|
+
for (const steer of snapshot.steers) {
|
|
11456
|
+
lines.push(` \u2192 steer ${steer.messageId ?? "(no id)"} to ${steer.toWorker ?? "(no target)"}`);
|
|
11457
|
+
for (const line of wrap(steer.instruction, 4)) lines.push(line);
|
|
11458
|
+
}
|
|
11459
|
+
}
|
|
11460
|
+
const problems = [];
|
|
11461
|
+
if (snapshot.invalidJournalRows > 0) problems.push(`${snapshot.invalidJournalRows} unreadable journal row(s)`);
|
|
11462
|
+
if (snapshot.invalidCoordinationRows > 0) {
|
|
11463
|
+
problems.push(`${snapshot.invalidCoordinationRows} unreadable coordination row(s)`);
|
|
11464
|
+
}
|
|
11465
|
+
if (snapshot.treeError !== null) problems.push(snapshot.treeError);
|
|
11466
|
+
if (snapshot.partialTail) problems.push("an artifact ends mid-line \u2014 the writer is still appending");
|
|
11467
|
+
if (zeroPricedCost(totals.driver) || zeroPricedCost(totals.workers)) {
|
|
11468
|
+
problems.push(
|
|
11469
|
+
"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."
|
|
11470
|
+
);
|
|
11471
|
+
}
|
|
11472
|
+
const unsettled = snapshot.nodes.filter(
|
|
11473
|
+
(node) => node.status !== "done" && node.status !== "failed" && node.status !== "cancelled"
|
|
11474
|
+
);
|
|
11475
|
+
if (snapshot.terminal && unsettled.length > 0) {
|
|
11476
|
+
problems.push(
|
|
11477
|
+
`${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`
|
|
11478
|
+
);
|
|
11479
|
+
}
|
|
11480
|
+
if (problems.length > 0) {
|
|
11481
|
+
lines.push("");
|
|
11482
|
+
for (const problem of problems) lines.push(` ! ${problem}`);
|
|
11483
|
+
}
|
|
11484
|
+
if (snapshot.result !== null) {
|
|
11485
|
+
lines.push("");
|
|
11486
|
+
lines.push(
|
|
11487
|
+
`RESULT ${snapshot.result.kind ?? "unknown-kind"}${snapshot.result.reason === null ? "" : ` \xB7 ${snapshot.result.reason}`}`
|
|
11488
|
+
);
|
|
11489
|
+
if (snapshot.result.errorMessage !== null) {
|
|
11490
|
+
lines.push(` driver error (${snapshot.result.errorName ?? "Error"}):`);
|
|
11491
|
+
for (const line of wrap(snapshot.result.errorMessage, 4)) lines.push(line);
|
|
11492
|
+
}
|
|
11493
|
+
}
|
|
11494
|
+
return lines.join("\n");
|
|
11495
|
+
}
|
|
11496
|
+
function runTotals(snapshot) {
|
|
11497
|
+
let driver = ZERO_ROLL;
|
|
11498
|
+
let workers = ZERO_ROLL;
|
|
11499
|
+
for (const node of snapshot.nodes) {
|
|
11500
|
+
driver = mergeRolls(driver, node.driver);
|
|
11501
|
+
if (node.role === "worker") workers = mergeRolls(workers, node.settled);
|
|
11502
|
+
}
|
|
11503
|
+
return { driver, workers };
|
|
11504
|
+
}
|
|
11505
|
+
function elapsed(snapshot) {
|
|
11506
|
+
if (snapshot.begunAt === null) return null;
|
|
11507
|
+
const end = snapshot.terminal ? snapshot.lastEventAt ?? Date.now() : Date.now();
|
|
11508
|
+
return Math.max(0, end - snapshot.begunAt);
|
|
11509
|
+
}
|
|
11510
|
+
async function resolveRunContextDir(dir) {
|
|
11511
|
+
if (await isFileRunContextDir(dir)) return dir;
|
|
11512
|
+
const entries = await readdir16(dir, { withFileTypes: true }).catch(() => null);
|
|
11513
|
+
if (entries === null) throw new Error(`cannot read ${dir}`);
|
|
11514
|
+
const candidates = [];
|
|
11515
|
+
for (const entry of entries) {
|
|
11516
|
+
if (!entry.isDirectory()) continue;
|
|
11517
|
+
const child = join24(dir, entry.name);
|
|
11518
|
+
if (await isFileRunContextDir(child)) candidates.push(child);
|
|
11519
|
+
}
|
|
11520
|
+
if (candidates.length === 1) return candidates[0];
|
|
11521
|
+
if (candidates.length === 0) {
|
|
11522
|
+
throw new Error(
|
|
11523
|
+
`no spawn-journal.jsonl in ${dir} or any immediate subdirectory \u2014 point at a directory written by agent-runtime createFileRunContext()`
|
|
11524
|
+
);
|
|
11525
|
+
}
|
|
11526
|
+
throw new Error(
|
|
11527
|
+
`${candidates.length} run directories under ${dir}; name one:
|
|
11528
|
+
${candidates.join("\n ")}`
|
|
11529
|
+
);
|
|
11530
|
+
}
|
|
11531
|
+
|
|
11532
|
+
// src/run-watch.ts
|
|
11533
|
+
import { stat as stat14 } from "fs/promises";
|
|
11534
|
+
async function resolveRunWatchTarget(target) {
|
|
11535
|
+
const info = await stat14(target).catch(() => null);
|
|
11536
|
+
if (info === null) throw new Error(`cannot read ${target}`);
|
|
11537
|
+
if (info.isFile()) return { runDir: null, spanFiles: [target], requested: target };
|
|
11538
|
+
const spanFiles = await findSpanSourceFiles(target);
|
|
11539
|
+
if (await isFileRunContextDir(target)) return { runDir: target, spanFiles, requested: target };
|
|
11540
|
+
const nested = await resolveRunContextDir(target).catch(() => null);
|
|
11541
|
+
if (nested !== null) {
|
|
11542
|
+
const nestedSpans = await findSpanSourceFiles(nested);
|
|
11543
|
+
return {
|
|
11544
|
+
runDir: nested,
|
|
11545
|
+
spanFiles: [.../* @__PURE__ */ new Set([...spanFiles, ...nestedSpans])].sort(),
|
|
11546
|
+
requested: target
|
|
11547
|
+
};
|
|
11548
|
+
}
|
|
11549
|
+
if (spanFiles.length > 0) return { runDir: null, spanFiles, requested: target };
|
|
11550
|
+
throw new Error(
|
|
11551
|
+
`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)`
|
|
11552
|
+
);
|
|
11553
|
+
}
|
|
11554
|
+
async function readRunWatchSnapshot(target) {
|
|
11555
|
+
const journal = target.runDir === null ? null : await readRunContextSnapshot(target.runDir);
|
|
11556
|
+
const spans = await Promise.all(target.spanFiles.map((file) => readSpanRunSnapshot(file)));
|
|
11557
|
+
return {
|
|
11558
|
+
target,
|
|
11559
|
+
journal,
|
|
11560
|
+
spans,
|
|
11561
|
+
// A span file alone never declares itself finished — an emitter can always
|
|
11562
|
+
// append one more span — so only the journal's `result.json` ends a watch.
|
|
11563
|
+
terminal: journal !== null && journal.terminal
|
|
11564
|
+
};
|
|
11565
|
+
}
|
|
11566
|
+
function sourceHeader(snapshot) {
|
|
11567
|
+
const lines = ["sources"];
|
|
11568
|
+
if (snapshot.journal !== null) {
|
|
11569
|
+
lines.push(
|
|
11570
|
+
` journal ${snapshot.target.runDir}/spawn-journal.jsonl \u2192 authored budget, settled spend, settlement status (authoritative)`
|
|
11571
|
+
);
|
|
11572
|
+
}
|
|
11573
|
+
for (const span2 of snapshot.spans) {
|
|
11574
|
+
lines.push(
|
|
11575
|
+
` spans ${span2.source} \u2192 tree shape and per-turn detail${span2.readError === null ? "" : " (unreadable)"}`
|
|
11576
|
+
);
|
|
11577
|
+
}
|
|
11578
|
+
if (snapshot.journal === null) {
|
|
11579
|
+
lines.push(" (no spawn journal here: budget and settlement are UNAVAILABLE, not zero)");
|
|
11580
|
+
}
|
|
11581
|
+
if (snapshot.spans.length === 0) {
|
|
11582
|
+
lines.push(" (no OTLP span file here: per-turn shape is UNAVAILABLE, not empty)");
|
|
11583
|
+
}
|
|
11584
|
+
return lines;
|
|
11585
|
+
}
|
|
11586
|
+
function renderRunWatchSnapshot(snapshot) {
|
|
11587
|
+
const blocks = [sourceHeader(snapshot).join("\n")];
|
|
11588
|
+
if (snapshot.journal !== null) blocks.push(renderRunContextSnapshot(snapshot.journal));
|
|
11589
|
+
for (const span2 of snapshot.spans) blocks.push(renderSpanRunSnapshot(span2));
|
|
11590
|
+
return blocks.join("\n\n");
|
|
11591
|
+
}
|
|
11592
|
+
async function watchRunTarget(target, opts = {}) {
|
|
11593
|
+
const write = opts.write ?? ((text2) => process.stdout.write(text2));
|
|
11594
|
+
const intervalMs = opts.intervalMs ?? 2e3;
|
|
11595
|
+
let previous = "";
|
|
11596
|
+
for (; ; ) {
|
|
11597
|
+
const snapshot = await readRunWatchSnapshot(target);
|
|
11598
|
+
const rendered = renderRunWatchSnapshot(snapshot);
|
|
11599
|
+
if (opts.once) {
|
|
11600
|
+
write(`${rendered}
|
|
11601
|
+
`);
|
|
11602
|
+
return snapshot;
|
|
11603
|
+
}
|
|
11604
|
+
if (rendered !== previous) {
|
|
11605
|
+
write(`
|
|
11606
|
+
${RULE}
|
|
11607
|
+
${(/* @__PURE__ */ new Date()).toISOString()}
|
|
11608
|
+
${rendered}
|
|
11609
|
+
`);
|
|
11610
|
+
previous = rendered;
|
|
11611
|
+
}
|
|
11612
|
+
if (snapshot.terminal && opts.follow !== true) return snapshot;
|
|
11613
|
+
if (aborted(opts.signal)) return snapshot;
|
|
11614
|
+
await sleep2(intervalMs, opts.signal);
|
|
11615
|
+
if (aborted(opts.signal)) return await readRunWatchSnapshot(target);
|
|
11616
|
+
}
|
|
11617
|
+
}
|
|
11618
|
+
function aborted(signal) {
|
|
11619
|
+
return signal !== void 0 && signal.aborted;
|
|
11620
|
+
}
|
|
11621
|
+
function sleep2(msDelay, signal) {
|
|
11622
|
+
return new Promise((resolve7) => {
|
|
11623
|
+
const timer = setTimeout(resolve7, msDelay);
|
|
11624
|
+
signal?.addEventListener(
|
|
11625
|
+
"abort",
|
|
11626
|
+
() => {
|
|
11627
|
+
clearTimeout(timer);
|
|
11628
|
+
resolve7();
|
|
11629
|
+
},
|
|
11630
|
+
{ once: true }
|
|
11631
|
+
);
|
|
11632
|
+
});
|
|
11633
|
+
}
|
|
11634
|
+
|
|
10557
11635
|
// src/observer.ts
|
|
10558
11636
|
function loopKey(loop) {
|
|
10559
11637
|
return `${loop.sessionId}:${loop.toolName}:${loop.argHash}`;
|
|
@@ -11014,9 +12092,9 @@ async function collectSessionSelection(groups, options = {}) {
|
|
|
11014
12092
|
}
|
|
11015
12093
|
|
|
11016
12094
|
// src/session-index.ts
|
|
11017
|
-
import { mkdtemp as mkdtemp7, readdir as
|
|
12095
|
+
import { mkdtemp as mkdtemp7, readdir as readdir17, readFile as readFile11, stat as stat15, writeFile as writeFile7 } from "fs/promises";
|
|
11018
12096
|
import { tmpdir as tmpdir6 } from "os";
|
|
11019
|
-
import { dirname as dirname7, join as
|
|
12097
|
+
import { dirname as dirname7, join as join25 } from "path";
|
|
11020
12098
|
function indexRow(record2) {
|
|
11021
12099
|
return {
|
|
11022
12100
|
session: record2.session,
|
|
@@ -11071,7 +12149,7 @@ function buildSessionIndex(records, opts = {}) {
|
|
|
11071
12149
|
}
|
|
11072
12150
|
async function pathStat2(path) {
|
|
11073
12151
|
try {
|
|
11074
|
-
return await
|
|
12152
|
+
return await stat15(path);
|
|
11075
12153
|
} catch {
|
|
11076
12154
|
return null;
|
|
11077
12155
|
}
|
|
@@ -11089,7 +12167,7 @@ async function findContextRoot(cwd) {
|
|
|
11089
12167
|
if (!s) return null;
|
|
11090
12168
|
if (!s.isDirectory()) current = dirname7(current);
|
|
11091
12169
|
for (; ; ) {
|
|
11092
|
-
if (await pathStat2(
|
|
12170
|
+
if (await pathStat2(join25(current, ".git")) || await dirExists(join25(current, ".evolve")) || await fileExists(join25(current, "AGENTS.md")) || await fileExists(join25(current, "CLAUDE.md"))) {
|
|
11093
12171
|
return current;
|
|
11094
12172
|
}
|
|
11095
12173
|
const parent = dirname7(current);
|
|
@@ -11132,7 +12210,7 @@ async function summarizeFile(path, kind) {
|
|
|
11132
12210
|
const base = { kind, path, bytes: Number(s.size), mtimeMs: Number(s.mtimeMs) };
|
|
11133
12211
|
if (!path.endsWith(".md") && !path.endsWith(".jsonl")) return base;
|
|
11134
12212
|
try {
|
|
11135
|
-
const text2 = await
|
|
12213
|
+
const text2 = await readFile11(path, "utf8");
|
|
11136
12214
|
const lines = countLines(text2);
|
|
11137
12215
|
const jsonl2 = path.endsWith(".jsonl") ? summarizeJsonl(text2) : void 0;
|
|
11138
12216
|
return {
|
|
@@ -11151,13 +12229,13 @@ async function walkMarkdown(root, kind, maxFiles = 100) {
|
|
|
11151
12229
|
if (out.length >= maxFiles) return;
|
|
11152
12230
|
let entries;
|
|
11153
12231
|
try {
|
|
11154
|
-
entries = await
|
|
12232
|
+
entries = await readdir17(dir, { withFileTypes: true });
|
|
11155
12233
|
} catch {
|
|
11156
12234
|
return;
|
|
11157
12235
|
}
|
|
11158
12236
|
for (const entry of entries) {
|
|
11159
12237
|
if (out.length >= maxFiles) return;
|
|
11160
|
-
const path =
|
|
12238
|
+
const path = join25(dir, entry.name);
|
|
11161
12239
|
if (entry.isDirectory()) {
|
|
11162
12240
|
await walk(path);
|
|
11163
12241
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
@@ -11172,20 +12250,20 @@ async function walkMarkdown(root, kind, maxFiles = 100) {
|
|
|
11172
12250
|
async function collectContextRoot(root) {
|
|
11173
12251
|
const files = [];
|
|
11174
12252
|
for (const name of ["AGENTS.md", "CLAUDE.md", "GEMINI.md"]) {
|
|
11175
|
-
const summary = await summarizeFile(
|
|
12253
|
+
const summary = await summarizeFile(join25(root, name), "instruction-doc");
|
|
11176
12254
|
if (summary) files.push(summary);
|
|
11177
12255
|
}
|
|
11178
|
-
const evolve =
|
|
12256
|
+
const evolve = join25(root, ".evolve");
|
|
11179
12257
|
for (const name of ["skill-runs.jsonl", "governor.jsonl", "experiments.jsonl"]) {
|
|
11180
|
-
const summary = await summarizeFile(
|
|
12258
|
+
const summary = await summarizeFile(join25(evolve, name), "evolve-jsonl");
|
|
11181
12259
|
if (summary) files.push(summary);
|
|
11182
12260
|
}
|
|
11183
12261
|
for (const name of ["scorecard.json"]) {
|
|
11184
|
-
const summary = await summarizeFile(
|
|
12262
|
+
const summary = await summarizeFile(join25(evolve, name), "evolve-json");
|
|
11185
12263
|
if (summary) files.push(summary);
|
|
11186
12264
|
}
|
|
11187
|
-
files.push(...await walkMarkdown(
|
|
11188
|
-
files.push(...await walkMarkdown(
|
|
12265
|
+
files.push(...await walkMarkdown(join25(evolve, "reflections"), "reflection"));
|
|
12266
|
+
files.push(...await walkMarkdown(join25(evolve, "handoffs"), "handoff"));
|
|
11189
12267
|
return { root, files: files.sort((a, b) => a.path.localeCompare(b.path)) };
|
|
11190
12268
|
}
|
|
11191
12269
|
async function collectContextIndex(records) {
|
|
@@ -11231,19 +12309,19 @@ function serializeSessionIndex(index) {
|
|
|
11231
12309
|
`;
|
|
11232
12310
|
}
|
|
11233
12311
|
async function writeSessionIndexFile(index, outPath) {
|
|
11234
|
-
const path = outPath ??
|
|
12312
|
+
const path = outPath ?? join25(await mkdtemp7(join25(tmpdir6(), "traces-index-")), "session-index.json");
|
|
11235
12313
|
await writeFile7(path, serializeSessionIndex(index), "utf8");
|
|
11236
12314
|
return path;
|
|
11237
12315
|
}
|
|
11238
12316
|
|
|
11239
12317
|
// src/upload-state.ts
|
|
11240
|
-
import { mkdir as mkdir5, readFile as
|
|
12318
|
+
import { mkdir as mkdir5, readFile as readFile12, writeFile as writeFile8 } from "fs/promises";
|
|
11241
12319
|
import { homedir as homedir12 } from "os";
|
|
11242
|
-
import { dirname as dirname8, join as
|
|
12320
|
+
import { dirname as dirname8, join as join26 } from "path";
|
|
11243
12321
|
import { contentHash } from "@tangle-network/agent-eval";
|
|
11244
12322
|
function stateFile() {
|
|
11245
|
-
const base = process.env.XDG_STATE_HOME ??
|
|
11246
|
-
return
|
|
12323
|
+
const base = process.env.XDG_STATE_HOME ?? join26(homedir12(), ".local", "state");
|
|
12324
|
+
return join26(base, "tangle-traces", "uploaded.json");
|
|
11247
12325
|
}
|
|
11248
12326
|
function uploadKey(harness, sessionId) {
|
|
11249
12327
|
return `${harness}:${sessionId}`;
|
|
@@ -11256,7 +12334,7 @@ function outboundHash(events, identity) {
|
|
|
11256
12334
|
}
|
|
11257
12335
|
async function loadState() {
|
|
11258
12336
|
try {
|
|
11259
|
-
return JSON.parse(await
|
|
12337
|
+
return JSON.parse(await readFile12(stateFile(), "utf8"));
|
|
11260
12338
|
} catch {
|
|
11261
12339
|
return {};
|
|
11262
12340
|
}
|
|
@@ -11272,10 +12350,10 @@ function alreadyUploaded(state, harness, sessionId, hash2) {
|
|
|
11272
12350
|
}
|
|
11273
12351
|
|
|
11274
12352
|
// src/upload.ts
|
|
11275
|
-
import { readFile as
|
|
12353
|
+
import { readFile as readFile13 } from "fs/promises";
|
|
11276
12354
|
import { createRequire } from "module";
|
|
11277
12355
|
import { hostname as hostname2 } from "os";
|
|
11278
|
-
import { basename as
|
|
12356
|
+
import { basename as basename14, join as join27 } from "path";
|
|
11279
12357
|
import { hostedClientFromEnv } from "@tangle-network/agent-eval/hosted";
|
|
11280
12358
|
import { REDACTION_VERSION } from "@tangle-network/agent-eval/traces";
|
|
11281
12359
|
var require2 = createRequire(import.meta.url);
|
|
@@ -11299,7 +12377,7 @@ async function planUpload(opts = {}) {
|
|
|
11299
12377
|
async function gitBranch(cwd) {
|
|
11300
12378
|
if (!cwd) return void 0;
|
|
11301
12379
|
try {
|
|
11302
|
-
const head = await
|
|
12380
|
+
const head = await readFile13(join27(cwd, ".git", "HEAD"), "utf8");
|
|
11303
12381
|
const m = head.match(/^ref:\s*refs\/heads\/(.+)$/m);
|
|
11304
12382
|
return m?.[1]?.trim();
|
|
11305
12383
|
} catch {
|
|
@@ -11309,7 +12387,7 @@ async function gitBranch(cwd) {
|
|
|
11309
12387
|
async function sessionMeta(item) {
|
|
11310
12388
|
const meta = {
|
|
11311
12389
|
[ATTR.HARNESS]: item.ref.harness,
|
|
11312
|
-
[ATTR.SESSION_FILE]:
|
|
12390
|
+
[ATTR.SESSION_FILE]: basename14(item.ref.path),
|
|
11313
12391
|
[ATTR.HOST]: hostname2(),
|
|
11314
12392
|
[ATTR.UPLOADER]: `tangle-traces@${TRACES_VERSION}`,
|
|
11315
12393
|
[ATTR.REDACTION_VERSION]: REDACTION_VERSION,
|
|
@@ -11582,6 +12660,43 @@ export {
|
|
|
11582
12660
|
traceStreamEventsFromSpans,
|
|
11583
12661
|
serializeTraceStreamEvent,
|
|
11584
12662
|
streamSessions,
|
|
12663
|
+
SPAWN_JOURNAL_FILE,
|
|
12664
|
+
COORDINATION_LOG_FILE,
|
|
12665
|
+
RESULT_FILE,
|
|
12666
|
+
BLOBS_DIR,
|
|
12667
|
+
readRunContextJournal,
|
|
12668
|
+
inferRole,
|
|
12669
|
+
readCoordinationLog,
|
|
12670
|
+
readFileRunContext,
|
|
12671
|
+
fileRunContextSupervisorRunReader,
|
|
12672
|
+
isFileRunContextDir,
|
|
12673
|
+
ZERO_ROLL,
|
|
12674
|
+
addSpend,
|
|
12675
|
+
mergeRolls,
|
|
12676
|
+
readRunContextSnapshot,
|
|
12677
|
+
RULE,
|
|
12678
|
+
int,
|
|
12679
|
+
ms,
|
|
12680
|
+
usd,
|
|
12681
|
+
zeroPricedCost,
|
|
12682
|
+
tokens2 as tokens,
|
|
12683
|
+
STATUS_MARK,
|
|
12684
|
+
connectors,
|
|
12685
|
+
forEachTreeNode,
|
|
12686
|
+
wrap,
|
|
12687
|
+
EMPTY_SPAN_RUN_SNAPSHOT,
|
|
12688
|
+
buildSpanRunTree,
|
|
12689
|
+
readSpanRunSnapshot,
|
|
12690
|
+
spanRunTotals,
|
|
12691
|
+
renderSpanRunSnapshot,
|
|
12692
|
+
findSpanSourceFiles,
|
|
12693
|
+
renderRunContextSnapshot,
|
|
12694
|
+
runTotals,
|
|
12695
|
+
resolveRunContextDir,
|
|
12696
|
+
resolveRunWatchTarget,
|
|
12697
|
+
readRunWatchSnapshot,
|
|
12698
|
+
renderRunWatchSnapshot,
|
|
12699
|
+
watchRunTarget,
|
|
11585
12700
|
watchSessions,
|
|
11586
12701
|
collectSessionWorkflow,
|
|
11587
12702
|
collectSessionSelection,
|