@tangle-network/agent-runtime 0.353.3 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{activation-CFS31SWI.d.ts → activation-BuK2H7qx.d.ts} +2 -2
- package/dist/{activation-DTgC0RvI.js → activation-ZQWz04u8.js} +4 -4
- package/dist/{activation-DTgC0RvI.js.map → activation-ZQWz04u8.js.map} +1 -1
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +4 -3
- package/dist/agent.js.map +1 -1
- package/dist/{bundle-DeDkJbsr.js → bundle-PDYNXIEf.js} +2 -2
- package/dist/{bundle-DeDkJbsr.js.map → bundle-PDYNXIEf.js.map} +1 -1
- package/dist/candidate-execution/index.js +4 -4
- package/dist/{coordination-manager-D-J0Osf4.js → coordination-manager-BZLp9qpE.js} +8 -363
- package/dist/coordination-manager-BZLp9qpE.js.map +1 -0
- package/dist/coordination-map-routing-CUvwHAtv.js +362 -0
- package/dist/coordination-map-routing-CUvwHAtv.js.map +1 -0
- package/dist/{delegate-DfRQSGVk.js → delegate-C-MUU6ML.js} +2 -2
- package/dist/{delegate-DfRQSGVk.js.map → delegate-C-MUU6ML.js.map} +1 -1
- package/dist/durable-file-CksURHlH.js +1 -0
- package/dist/durable-file-DstVeHcj.js +228 -0
- package/dist/durable-file-DstVeHcj.js.map +1 -0
- package/dist/durable.d.ts +1 -1
- package/dist/durable.js +10 -8
- package/dist/durable.js.map +1 -1
- package/dist/{graph-BtGsqcOF.js → graph-CM-6CV_K.js} +6 -5
- package/dist/{graph-BtGsqcOF.js.map → graph-CM-6CV_K.js.map} +1 -1
- package/dist/graph-registry-72DlMnUl.js +585 -0
- package/dist/graph-registry-72DlMnUl.js.map +1 -0
- package/dist/graph-registry-CAX15LO6.js +1 -0
- package/dist/{improve-CL-Dsf63.js → improve-BuZ-I8DP.js} +9 -8
- package/dist/{improve-CL-Dsf63.js.map → improve-BuZ-I8DP.js.map} +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +12 -11
- package/dist/index.js.map +1 -1
- package/dist/intelligence.d.ts +2 -2
- package/dist/intelligence.js +7 -7
- package/dist/{journal-store-BJPMUydp.js → journal-store-BpCOCDoO.js} +208 -635
- package/dist/journal-store-BpCOCDoO.js.map +1 -0
- package/dist/jsonl-file-BBP9Dz9w.js +429 -0
- package/dist/jsonl-file-BBP9Dz9w.js.map +1 -0
- package/dist/kernel.d.ts +1 -1
- package/dist/kernel.js +13 -12
- package/dist/{loop-runner-B1Xpgp2w.js → loop-runner-BWD6v1rd.js} +2 -2
- package/dist/{loop-runner-B1Xpgp2w.js.map → loop-runner-BWD6v1rd.js.map} +1 -1
- package/dist/{loop-runner-D9rcTNf2.d.ts → loop-runner-BpLTU_J4.d.ts} +2 -2
- package/dist/loop-runner-bin.d.ts +1 -1
- package/dist/loop-runner-bin.js +1 -1
- package/dist/mcp/bin.js +3 -3
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.js +5 -5
- package/dist/{prepare-XWwsOh8O.js → prepare-Bv6X2Wnx.js} +4 -4
- package/dist/{prepare-XWwsOh8O.js.map → prepare-Bv6X2Wnx.js.map} +1 -1
- package/dist/{profile-C01SQZKa.js → profile-Dir2A1_2.js} +2 -2
- package/dist/{profile-C01SQZKa.js.map → profile-Dir2A1_2.js.map} +1 -1
- package/dist/profiles.d.ts +1 -1
- package/dist/{provision-supervisor-CzCFHTxe.js → provision-supervisor-BWEeWag1.js} +8 -6
- package/dist/{provision-supervisor-CzCFHTxe.js.map → provision-supervisor-BWEeWag1.js.map} +1 -1
- package/dist/{run-cancellation-CoJRKvih.js → run-cancellation-Cpm-BIUu.js} +3 -2
- package/dist/{run-cancellation-CoJRKvih.js.map → run-cancellation-Cpm-BIUu.js.map} +1 -1
- package/dist/{runtime-DmdhgaTH.js → runtime-Cnr3213E.js} +18 -15
- package/dist/{runtime-DmdhgaTH.js.map → runtime-Cnr3213E.js.map} +1 -1
- package/dist/{sandbox-profile-compat-CXjozdWL.js → sandbox-profile-compat-DRiz9kME.js} +2 -2
- package/dist/{sandbox-profile-compat-CXjozdWL.js.map → sandbox-profile-compat-DRiz9kME.js.map} +1 -1
- package/dist/{search-executor-Djo4ssci.js → search-executor-__X1VKnU.js} +5 -5
- package/dist/{search-executor-Djo4ssci.js.map → search-executor-__X1VKnU.js.map} +1 -1
- package/dist/{server-DAanPY7z.js → server-CntFSTpW.js} +4 -4
- package/dist/{server-DAanPY7z.js.map → server-CntFSTpW.js.map} +1 -1
- package/dist/{structural-rollout-qqsbQW8M.js → structural-rollout-s8W19Mws.js} +5 -5
- package/dist/{structural-rollout-qqsbQW8M.js.map → structural-rollout-s8W19Mws.js.map} +1 -1
- package/dist/{supervise-DKwnVruI.js → supervise-Cl0F57m9.js} +13 -601
- package/dist/supervise-Cl0F57m9.js.map +1 -0
- package/dist/{supervision-discovery-CewADdEs.js → supervision-discovery-C3t0Xw-T.js} +5 -158
- package/dist/supervision-discovery-C3t0Xw-T.js.map +1 -0
- package/dist/{supervisor-C3JhrVVU.js → supervisor-L7u3pQ1j.js} +84 -140
- package/dist/supervisor-L7u3pQ1j.js.map +1 -0
- package/dist/task-queue-DbpZAp6C.js +49 -0
- package/dist/task-queue-DbpZAp6C.js.map +1 -0
- package/dist/task-queue.js +1 -49
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +13 -13
- package/dist/top-web-CkXPPXjI.js +1114 -0
- package/dist/top-web-CkXPPXjI.js.map +1 -0
- package/dist/tui/bin.js +4 -3
- package/dist/tui/bin.js.map +1 -1
- package/dist/tui/index.d.ts +22 -2
- package/dist/tui/index.js +3 -3
- package/dist/{types-DTHxh4X9.d.ts → types-BfnQq-_k.d.ts} +17 -21
- package/dist/{util-ebWMWzY0.js → util-BX20UJ9X.js} +2 -2
- package/dist/{util-ebWMWzY0.js.map → util-BX20UJ9X.js.map} +1 -1
- package/package.json +11 -11
- package/skills/agent-graphs/references/authoring.md +1 -0
- package/dist/coordination-manager-D-J0Osf4.js.map +0 -1
- package/dist/journal-store-BJPMUydp.js.map +0 -1
- package/dist/supervise-DKwnVruI.js.map +0 -1
- package/dist/supervision-discovery-CewADdEs.js.map +0 -1
- package/dist/supervisor-C3JhrVVU.js.map +0 -1
- package/dist/task-queue.js.map +0 -1
- package/dist/top-snapshot-DcV7VG4m.js +0 -520
- package/dist/top-snapshot-DcV7VG4m.js.map +0 -1
|
@@ -0,0 +1,1114 @@
|
|
|
1
|
+
import { i as FileResultBlobStore, r as spawnJournalRecordEvents, t as FileSpawnJournal, u as FileObserverJournal } from "./journal-store-BpCOCDoO.js";
|
|
2
|
+
import { o as readCommittedJsonLines, r as parseCommittedJsonLines } from "./jsonl-file-BBP9Dz9w.js";
|
|
3
|
+
import { _ as safeWorkerFile, b as supervisorWorkersDir, f as legacySupervisorRunsRoot, y as supervisorRunsRoot } from "./run-cancellation-Cpm-BIUu.js";
|
|
4
|
+
import { n as projectPursuit, t as discoverDurableSupervisionRun } from "./supervision-discovery-C3t0Xw-T.js";
|
|
5
|
+
import { createRequire } from "node:module";
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
7
|
+
import { closeSync, existsSync, openSync, readFileSync, readSync, readdirSync, statSync, watch } from "node:fs";
|
|
8
|
+
import { basename, dirname, extname, join, resolve, sep } from "node:path";
|
|
9
|
+
import { readFile, realpath, stat } from "node:fs/promises";
|
|
10
|
+
import { createServer } from "node:http";
|
|
11
|
+
// src/tui/top-transcript.ts
|
|
12
|
+
/** Read selected history on demand, leaving the live topology snapshot bounded and fast. */
|
|
13
|
+
async function transcriptHistory(worker, runDir, node) {
|
|
14
|
+
const rows = [];
|
|
15
|
+
const identity = node?.identity;
|
|
16
|
+
if (runDir && typeof identity?.taskDigest === "string" && worker.startedAt) {
|
|
17
|
+
const task = await new FileResultBlobStore(join(runDir, "blobs")).get(identity.taskDigest);
|
|
18
|
+
if (task !== void 0) rows.unshift({
|
|
19
|
+
at: worker.startedAt,
|
|
20
|
+
event: {
|
|
21
|
+
kind: "notice",
|
|
22
|
+
provenance: "delegated-input",
|
|
23
|
+
source: `parent ${node?.parentId ?? "run caller"}; task ${identity.taskDigest}${typeof identity.profileDigest === "string" ? `; profile ${identity.profileDigest}` : ""}; Runtime FileResultBlobStore`,
|
|
24
|
+
text: typeof task === "string" ? task : JSON.stringify(task, null, 2)
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
if (!worker.eventFile) {
|
|
29
|
+
const native = transcriptRows(worker);
|
|
30
|
+
if (!native.length && worker.startedAt) rows.push({
|
|
31
|
+
at: worker.startedAt,
|
|
32
|
+
event: {
|
|
33
|
+
kind: "notice",
|
|
34
|
+
provenance: "capture-unavailable",
|
|
35
|
+
text: "Native conversation was not captured for this agent.",
|
|
36
|
+
source: "Runtime recorded capture"
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
return [...rows, ...native];
|
|
40
|
+
}
|
|
41
|
+
const lines = [];
|
|
42
|
+
for await (const row of readCommittedJsonLines(worker.eventFile)) {
|
|
43
|
+
lines.push(JSON.stringify(row));
|
|
44
|
+
if (lines.length > 200) lines.shift();
|
|
45
|
+
}
|
|
46
|
+
return [...rows, ...transcriptRows({
|
|
47
|
+
...worker,
|
|
48
|
+
liveTail: lines
|
|
49
|
+
})];
|
|
50
|
+
}
|
|
51
|
+
/** Present the recorded tail through sandbox-ui's existing agent-stream contract. */
|
|
52
|
+
function transcriptRows(worker) {
|
|
53
|
+
const rows = [];
|
|
54
|
+
for (const line of worker.liveTail) {
|
|
55
|
+
let value;
|
|
56
|
+
try {
|
|
57
|
+
value = JSON.parse(line);
|
|
58
|
+
} catch {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const at = typeof value.timestamp === "string" ? value.timestamp : typeof value.at === "string" ? value.at : void 0;
|
|
62
|
+
if (!at || !Number.isFinite(Date.parse(at))) continue;
|
|
63
|
+
const event = value.event;
|
|
64
|
+
if (event?.kind) {
|
|
65
|
+
const kind = {
|
|
66
|
+
text_delta: "text",
|
|
67
|
+
reasoning_delta: "reasoning",
|
|
68
|
+
tool_call: "tool-call",
|
|
69
|
+
tool_result: "tool-result"
|
|
70
|
+
}[event.kind] ?? event.kind;
|
|
71
|
+
rows.push({
|
|
72
|
+
at,
|
|
73
|
+
event: {
|
|
74
|
+
...event,
|
|
75
|
+
kind
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const message = value.message;
|
|
81
|
+
if (!message) continue;
|
|
82
|
+
const parts = typeof message.content === "string" ? [{
|
|
83
|
+
type: "text",
|
|
84
|
+
text: message.content
|
|
85
|
+
}] : message.content ?? [];
|
|
86
|
+
for (const part of parts) if (part.type === "text" && typeof part.text === "string") rows.push({
|
|
87
|
+
at,
|
|
88
|
+
event: {
|
|
89
|
+
kind: message.role === "user" ? "user" : "text",
|
|
90
|
+
text: part.text
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
else if (part.type === "thinking" && typeof part.thinking === "string") rows.push({
|
|
94
|
+
at,
|
|
95
|
+
event: {
|
|
96
|
+
kind: "reasoning",
|
|
97
|
+
text: part.thinking
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
else if (part.type === "tool_use") rows.push({
|
|
101
|
+
at,
|
|
102
|
+
event: {
|
|
103
|
+
kind: "tool-call",
|
|
104
|
+
toolCallId: String(part.id),
|
|
105
|
+
toolName: String(part.name),
|
|
106
|
+
args: part.input
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
else if (part.type === "tool_result") rows.push({
|
|
110
|
+
at,
|
|
111
|
+
event: {
|
|
112
|
+
kind: "tool-result",
|
|
113
|
+
toolCallId: String(part.tool_use_id),
|
|
114
|
+
result: part.content
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
return rows;
|
|
119
|
+
}
|
|
120
|
+
/** The bounded native/normalized tail shared by snapshot adapters and selected streams. */
|
|
121
|
+
function readTranscriptTail(file) {
|
|
122
|
+
const fd = openSync(file, "r");
|
|
123
|
+
try {
|
|
124
|
+
const size = statSync(file).size;
|
|
125
|
+
const buffer = Buffer.alloc(Math.min(size, 65536));
|
|
126
|
+
readSync(fd, buffer, 0, buffer.length, Math.max(0, size - buffer.length));
|
|
127
|
+
const lines = buffer.toString().split("\n");
|
|
128
|
+
lines.pop();
|
|
129
|
+
if (size > buffer.length) lines.shift();
|
|
130
|
+
return lines.filter(Boolean).slice(-200);
|
|
131
|
+
} finally {
|
|
132
|
+
closeSync(fd);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function transcriptTail(worker) {
|
|
136
|
+
return transcriptRows(worker.eventFile ? {
|
|
137
|
+
...worker,
|
|
138
|
+
liveTail: readTranscriptTail(worker.eventFile)
|
|
139
|
+
} : worker);
|
|
140
|
+
}
|
|
141
|
+
// src/tui/workflow-journal.ts
|
|
142
|
+
/** Native workflows have no Runtime accounting. Translate recorded lifecycle only. */
|
|
143
|
+
function workflowJournal(runDir, text) {
|
|
144
|
+
const rows = parseCommittedJsonLines(text, "journal.jsonl");
|
|
145
|
+
const events = [];
|
|
146
|
+
const tails = /* @__PURE__ */ new Map();
|
|
147
|
+
let first;
|
|
148
|
+
let last;
|
|
149
|
+
for (const row of rows) {
|
|
150
|
+
if (typeof row.agentId !== "string" || !/^[\w-]+$/.test(row.agentId)) continue;
|
|
151
|
+
const id = row.agentId;
|
|
152
|
+
if (row.type === "started") {
|
|
153
|
+
const file = join(runDir, `agent-${id}.jsonl`);
|
|
154
|
+
let start;
|
|
155
|
+
let end;
|
|
156
|
+
try {
|
|
157
|
+
const fd = openSync(file, "r");
|
|
158
|
+
try {
|
|
159
|
+
const size = statSync(file).size;
|
|
160
|
+
const prefix = Buffer.alloc(Math.min(size, 16384));
|
|
161
|
+
readSync(fd, prefix, 0, prefix.length, 0);
|
|
162
|
+
start = /"timestamp":"([^"]+)"/.exec(prefix.toString())?.[1];
|
|
163
|
+
const lines = readTranscriptTail(file).slice(-8);
|
|
164
|
+
end = [...lines.join("\n").matchAll(/"timestamp":"([^"]+)"/g)].at(-1)?.[1];
|
|
165
|
+
tails.set(id, {
|
|
166
|
+
file,
|
|
167
|
+
lines
|
|
168
|
+
});
|
|
169
|
+
} finally {
|
|
170
|
+
closeSync(fd);
|
|
171
|
+
}
|
|
172
|
+
} catch (error) {
|
|
173
|
+
if (error.code !== "ENOENT") throw error;
|
|
174
|
+
}
|
|
175
|
+
if (start && (!first || start < first)) first = start;
|
|
176
|
+
if (end && (!last || end > last)) last = end;
|
|
177
|
+
events.push({
|
|
178
|
+
kind: "spawned",
|
|
179
|
+
id,
|
|
180
|
+
label: typeof row.label === "string" ? row.label : id,
|
|
181
|
+
runtime: "claude-code",
|
|
182
|
+
...start ? { at: start } : {}
|
|
183
|
+
});
|
|
184
|
+
events.push({
|
|
185
|
+
kind: "metered",
|
|
186
|
+
id,
|
|
187
|
+
spend: {
|
|
188
|
+
usdKnown: false,
|
|
189
|
+
tokensKnown: false
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
} else if (row.type === "result" || row.type === "error" || row.type === "cancelled") {
|
|
193
|
+
const line = tails.get(id)?.lines.at(-1);
|
|
194
|
+
const at = line ? /"timestamp":"([^"]+)"/.exec(line)?.[1] : void 0;
|
|
195
|
+
events.push(row.type === "cancelled" ? {
|
|
196
|
+
kind: "cancelled",
|
|
197
|
+
id,
|
|
198
|
+
...at ? { at } : {}
|
|
199
|
+
} : {
|
|
200
|
+
kind: "settled",
|
|
201
|
+
id,
|
|
202
|
+
status: row.type === "error" ? "down" : "done",
|
|
203
|
+
...at ? { at } : {},
|
|
204
|
+
spent: {
|
|
205
|
+
usdKnown: false,
|
|
206
|
+
tokensKnown: false
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
id: basename(runDir),
|
|
213
|
+
events,
|
|
214
|
+
tails,
|
|
215
|
+
first,
|
|
216
|
+
last
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
// src/tui/top-snapshot.ts
|
|
220
|
+
/**
|
|
221
|
+
* Read-only operator snapshots over the writer-owned supervisor layout and explicit durable runs.
|
|
222
|
+
* Workspace reads preserve legacy files; durable reads use discovery, journals and the canonical
|
|
223
|
+
* pursuit projection for model identity, failure and accounting evidence. Run totals count once.
|
|
224
|
+
* ANSI presentation lives in `./top-frame`.
|
|
225
|
+
*
|
|
226
|
+
* @experimental
|
|
227
|
+
*/
|
|
228
|
+
const emptySpend = {
|
|
229
|
+
iterations: 0,
|
|
230
|
+
tokensInput: 0,
|
|
231
|
+
tokensOutput: 0,
|
|
232
|
+
usd: 0,
|
|
233
|
+
ms: 0
|
|
234
|
+
};
|
|
235
|
+
function loadTopSnapshot(rootDir, input = Date.now()) {
|
|
236
|
+
if (typeof input !== "number" && input.format === "journal") return loadJournalTopSnapshot(rootDir, input.runDir, input.now ?? Date.now());
|
|
237
|
+
if (typeof input !== "number" && input.format === "workflow") {
|
|
238
|
+
const root = resolve(rootDir), dir = resolve(input.runDir), now = input.now ?? Date.now();
|
|
239
|
+
const read = readSourceFile(join(dir, "journal.jsonl"), "journal", "journal.jsonl");
|
|
240
|
+
if (read.value === void 0) return Promise.resolve(topSnapshot(root, now, [], read.diagnostics.length ? [...read.diagnostics] : [{
|
|
241
|
+
source: "journal",
|
|
242
|
+
path: "journal.jsonl",
|
|
243
|
+
reason: "missing"
|
|
244
|
+
}]));
|
|
245
|
+
try {
|
|
246
|
+
const journal = workflowJournal(dir, read.value);
|
|
247
|
+
const supervisor = buildSupervisorView({
|
|
248
|
+
id: journal.id,
|
|
249
|
+
task: journal.id,
|
|
250
|
+
status: "unobserved",
|
|
251
|
+
workspaceDir: root,
|
|
252
|
+
budget: 0,
|
|
253
|
+
budgetKnown: false,
|
|
254
|
+
...journal.first ? { startedAt: journal.first } : {}
|
|
255
|
+
}, dir, journal.events, [], now, journal.tails);
|
|
256
|
+
return Promise.resolve(topSnapshot(root, now, [{
|
|
257
|
+
...supervisor,
|
|
258
|
+
driverSpend: {
|
|
259
|
+
...supervisor.driverSpend,
|
|
260
|
+
usdKnown: false,
|
|
261
|
+
tokensKnown: false
|
|
262
|
+
},
|
|
263
|
+
totals: {
|
|
264
|
+
...supervisor.totals,
|
|
265
|
+
usdKnown: false,
|
|
266
|
+
tokensKnown: false
|
|
267
|
+
}
|
|
268
|
+
}]));
|
|
269
|
+
} catch {
|
|
270
|
+
return Promise.resolve(topSnapshot(root, now, [], [{
|
|
271
|
+
source: "journal",
|
|
272
|
+
path: "journal.jsonl",
|
|
273
|
+
reason: "invalid-state"
|
|
274
|
+
}]));
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (typeof input !== "number") return loadDurableTopSnapshot(rootDir, input.runDir, input.now ?? Date.now());
|
|
278
|
+
const now = input;
|
|
279
|
+
const root = resolve(rootDir);
|
|
280
|
+
const supervisors = [];
|
|
281
|
+
const diagnostics = [];
|
|
282
|
+
let discovered = 0;
|
|
283
|
+
const report = (runId, sources) => {
|
|
284
|
+
for (const source of sources) diagnostics.push({
|
|
285
|
+
...source,
|
|
286
|
+
runId
|
|
287
|
+
});
|
|
288
|
+
};
|
|
289
|
+
for (const runsRoot of [supervisorRunsRoot(root), legacySupervisorRunsRoot(root)]) {
|
|
290
|
+
if (!existsSync(runsRoot)) continue;
|
|
291
|
+
for (const entry of readdirSync(runsRoot)) {
|
|
292
|
+
const dir = join(runsRoot, entry);
|
|
293
|
+
if (!isDirectory(dir)) continue;
|
|
294
|
+
discovered += 1;
|
|
295
|
+
const state = readSupervisorState(dir);
|
|
296
|
+
report(entry, state.diagnostics);
|
|
297
|
+
if (!state.value) continue;
|
|
298
|
+
const journal = readJournal(dir);
|
|
299
|
+
const progress = readSourceFile(join(dir, "progress.ndjson"), "progress", "progress.ndjson");
|
|
300
|
+
const workerEvents = readWorkerEventTails(supervisorWorkersDir(dir), 8);
|
|
301
|
+
report(entry, [
|
|
302
|
+
...journal.diagnostics,
|
|
303
|
+
...progress.diagnostics,
|
|
304
|
+
...workerEvents.diagnostics
|
|
305
|
+
]);
|
|
306
|
+
supervisors.push(buildSupervisorView(state.value, dir, journal.value, tailLines(progress.value, 8), now, workerEvents.value));
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
supervisors.sort((a, b) => String(b.startedAt ?? "").localeCompare(String(a.startedAt ?? "")));
|
|
310
|
+
return topSnapshot(root, now, supervisors, diagnostics, discovered);
|
|
311
|
+
}
|
|
312
|
+
function topSnapshot(root, now, supervisors, diagnostics = [], discovered = 1, pursuit) {
|
|
313
|
+
return {
|
|
314
|
+
root,
|
|
315
|
+
generatedAt: now,
|
|
316
|
+
supervisors,
|
|
317
|
+
completeness: diagnostics.length ? "partial" : "complete",
|
|
318
|
+
diagnostics,
|
|
319
|
+
discovered,
|
|
320
|
+
loaded: supervisors.length,
|
|
321
|
+
...pursuit ? { pursuit } : {}
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
async function loadDurableTopSnapshot(rootDir, runDir, now) {
|
|
325
|
+
const root = resolve(rootDir);
|
|
326
|
+
let pursuit;
|
|
327
|
+
let source = "observer-journal";
|
|
328
|
+
let path = "observer.jsonl";
|
|
329
|
+
try {
|
|
330
|
+
if (typeof runDir !== "string" || !runDir.trim()) throw new TypeError("runDir must be non-empty");
|
|
331
|
+
const journal = await FileObserverJournal.open(join(resolve(runDir), path));
|
|
332
|
+
if (!journal) return topSnapshot(root, now, [], [{
|
|
333
|
+
source,
|
|
334
|
+
path,
|
|
335
|
+
reason: "missing"
|
|
336
|
+
}]);
|
|
337
|
+
pursuit = projectPursuit(await journal.read());
|
|
338
|
+
source = "journal";
|
|
339
|
+
path = ".";
|
|
340
|
+
const discovery = await discoverDurableSupervisionRun(runDir);
|
|
341
|
+
const latest = new Map(pursuit.runs.map((run) => [run.runId, run]));
|
|
342
|
+
path = "spawn-journal.jsonl";
|
|
343
|
+
const spawnJournal = new FileSpawnJournal(discovery.spawnJournalPath);
|
|
344
|
+
return topSnapshot(root, now, await Promise.all([...latest.values()].map(async (run) => {
|
|
345
|
+
const binding = discovery.roots.find((id) => id === run.runId);
|
|
346
|
+
if (!binding && existsSync(discovery.spawnJournalPath)) throw new Error("observer run has no discovered spawn root");
|
|
347
|
+
const events = binding ? await spawnJournal.loadTree(binding) ?? [] : [];
|
|
348
|
+
const rootSpawn = events.find((event) => event.kind === "spawned" && event.id === binding);
|
|
349
|
+
const budget = rootSpawn?.kind === "spawned" ? rootSpawn.budget?.maxTokens : void 0;
|
|
350
|
+
return buildSupervisorView({
|
|
351
|
+
id: run.runId,
|
|
352
|
+
status: run.status,
|
|
353
|
+
task: run.runId,
|
|
354
|
+
workspaceDir: root,
|
|
355
|
+
budget: budget ?? 0,
|
|
356
|
+
budgetKnown: budget !== void 0,
|
|
357
|
+
startedAt: new Date(run.firstObservedAt).toISOString(),
|
|
358
|
+
...run.settledAt === void 0 ? {} : { completedAt: new Date(run.settledAt).toISOString() }
|
|
359
|
+
}, discovery.runDir, events.filter(isSpawnEvent), run.error ? [run.error] : [], now, void 0, run);
|
|
360
|
+
})), [], 1, pursuit);
|
|
361
|
+
} catch (error) {
|
|
362
|
+
const reason = isRecord(error) && typeof error.code === "string" ? "unreadable" : "invalid-state";
|
|
363
|
+
return topSnapshot(root, now, [], [{
|
|
364
|
+
source,
|
|
365
|
+
path,
|
|
366
|
+
reason
|
|
367
|
+
}], 1, pursuit);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
/** Old Discovery and factory roots may predate observer.jsonl; absence never establishes known totals. */
|
|
371
|
+
async function loadJournalTopSnapshot(rootDir, runDir, now) {
|
|
372
|
+
const root = resolve(rootDir), dir = resolve(runDir);
|
|
373
|
+
try {
|
|
374
|
+
const discovery = await discoverDurableSupervisionRun(dir);
|
|
375
|
+
const journal = new FileSpawnJournal(discovery.spawnJournalPath);
|
|
376
|
+
return topSnapshot(root, now, await Promise.all(discovery.roots.map(async (id, i) => {
|
|
377
|
+
const events = await journal.loadTree(id) ?? [];
|
|
378
|
+
const terminal = events.filter((event) => event.id === id && (event.kind === "settled" || event.kind === "cancelled")).at(-1);
|
|
379
|
+
const first = events.find((event) => event.kind === "spawned" && event.id === id);
|
|
380
|
+
const budget = first?.kind === "spawned" ? first.budget?.maxTokens : void 0;
|
|
381
|
+
const tails = readWorkerEventTails(supervisorWorkersDir(dir), 8);
|
|
382
|
+
const view = buildSupervisorView({
|
|
383
|
+
id,
|
|
384
|
+
task: id,
|
|
385
|
+
workspaceDir: root,
|
|
386
|
+
status: terminal?.kind === "settled" ? terminal.status : terminal?.kind === "cancelled" ? "cancelled" : "running",
|
|
387
|
+
budget: budget ?? 0,
|
|
388
|
+
budgetKnown: budget !== void 0,
|
|
389
|
+
...discovery.rootsBegunAt[i] ? { startedAt: discovery.rootsBegunAt[i] } : {},
|
|
390
|
+
...terminal?.at ? { completedAt: terminal.at } : {}
|
|
391
|
+
}, dir, events.filter(isSpawnEvent), [], now, tails.value);
|
|
392
|
+
return {
|
|
393
|
+
...view,
|
|
394
|
+
totals: {
|
|
395
|
+
...view.totals,
|
|
396
|
+
usdKnown: false,
|
|
397
|
+
tokensKnown: false
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
})), [{
|
|
401
|
+
source: "observer-journal",
|
|
402
|
+
path: "observer.jsonl",
|
|
403
|
+
reason: "missing"
|
|
404
|
+
}], discovery.roots.length);
|
|
405
|
+
} catch {
|
|
406
|
+
return topSnapshot(root, now, [], [{
|
|
407
|
+
source: "journal",
|
|
408
|
+
path: "spawn-journal.jsonl",
|
|
409
|
+
reason: "invalid-state"
|
|
410
|
+
}]);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
function observerSpend(spend, gaps = []) {
|
|
414
|
+
const parsed = parseSpend(spend);
|
|
415
|
+
return {
|
|
416
|
+
...parsed,
|
|
417
|
+
usdKnown: spend !== void 0 && parsed.usdKnown !== false && !gaps.includes("usd"),
|
|
418
|
+
tokensKnown: spend !== void 0 && parsed.tokensKnown !== false && !gaps.includes("tokens")
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* The journal file a run dir actually carries. `createFileRunContext` — this repo's own durable
|
|
423
|
+
* writer — names it `spawn-journal.jsonl`; `journal.jsonl` is the retired bare-event file older
|
|
424
|
+
* runs left behind. First existing name wins; a reader that knew only the retired name would show
|
|
425
|
+
* zero workers for every current run.
|
|
426
|
+
*/
|
|
427
|
+
const journalFileNames = ["spawn-journal.jsonl", "journal.jsonl"];
|
|
428
|
+
function readJournal(eventDir) {
|
|
429
|
+
const path = journalFileNames.find((candidate) => existsSync(join(eventDir, candidate)));
|
|
430
|
+
if (!path) return {
|
|
431
|
+
value: [],
|
|
432
|
+
diagnostics: []
|
|
433
|
+
};
|
|
434
|
+
const read = readSourceFile(join(eventDir, path), "journal", path);
|
|
435
|
+
if (read.value === void 0) return {
|
|
436
|
+
value: [],
|
|
437
|
+
diagnostics: read.diagnostics
|
|
438
|
+
};
|
|
439
|
+
const out = [];
|
|
440
|
+
let partial = false;
|
|
441
|
+
for (const line of read.value.split("\n")) {
|
|
442
|
+
const trimmed = line.trim();
|
|
443
|
+
if (!trimmed) continue;
|
|
444
|
+
try {
|
|
445
|
+
const parsed = JSON.parse(trimmed);
|
|
446
|
+
const events = parsed.kind === "event" || parsed.kind === "events" ? spawnJournalRecordEvents(parsed) : [parsed];
|
|
447
|
+
for (const event of events) if (isSpawnEvent(event)) out.push(event);
|
|
448
|
+
} catch {
|
|
449
|
+
partial = true;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
return {
|
|
453
|
+
value: out,
|
|
454
|
+
diagnostics: partial ? [{
|
|
455
|
+
source: "journal",
|
|
456
|
+
path,
|
|
457
|
+
reason: "partial-json"
|
|
458
|
+
}] : []
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
function buildSupervisorView(state, stateDir, events, progressTail, now, workerEventTails = /* @__PURE__ */ new Map(), accounting) {
|
|
462
|
+
const workers = /* @__PURE__ */ new Map();
|
|
463
|
+
let driverSpend = emptySpend;
|
|
464
|
+
const journalRootId = events.find((event) => event.kind === "spawned" && !event.parent && event.label === "root")?.id;
|
|
465
|
+
const isRoot = (id) => id === state.id || id === journalRootId;
|
|
466
|
+
for (const event of events) {
|
|
467
|
+
if (isRoot(event.id) || event.kind === "spawned" && !event.parent && event.label === "root") {
|
|
468
|
+
if (event.kind === "metered" && event.accountingOnly !== true) driverSpend = addSpend(driverSpend, parseSpend(event.spend));
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
const worker = ensureWorker(workers, event.id);
|
|
472
|
+
if (event.kind === "spawned") {
|
|
473
|
+
worker.label = event.label ?? worker.label;
|
|
474
|
+
if (event.parent) worker.parent = event.parent;
|
|
475
|
+
if (event.runtime) worker.runtime = event.runtime;
|
|
476
|
+
worker.status = "running";
|
|
477
|
+
if (event.at) worker.startedAt = event.at;
|
|
478
|
+
const budget = parseBudget(event.budget);
|
|
479
|
+
if (budget) worker.budget = budget;
|
|
480
|
+
} else if (event.kind === "settled" || event.kind === "cancelled") {
|
|
481
|
+
if (event.at) worker.endedAt = event.at;
|
|
482
|
+
if (event.kind === "settled") {
|
|
483
|
+
worker.status = event.status === "down" ? "down" : "done";
|
|
484
|
+
if (event.outRef) worker.outRef = event.outRef;
|
|
485
|
+
if (event.infra !== void 0) worker.infra = event.infra;
|
|
486
|
+
const verdict = describeVerdict(event.verdict);
|
|
487
|
+
if (verdict) worker.verdict = verdict;
|
|
488
|
+
} else {
|
|
489
|
+
worker.status = "cancelled";
|
|
490
|
+
if (event.reason) worker.reason = event.reason;
|
|
491
|
+
}
|
|
492
|
+
if (event.kind === "settled" || event.spent !== void 0) {
|
|
493
|
+
worker.spend = addSpend(worker.spend, parseSpend(event.spent));
|
|
494
|
+
worker.metered = emptySpend;
|
|
495
|
+
}
|
|
496
|
+
} else if (event.kind === "progress") worker.metered = parseSpend(event.spend);
|
|
497
|
+
else if (event.accountingOnly !== true) worker.metered = addSpend(worker.metered, parseSpend(event.spend));
|
|
498
|
+
}
|
|
499
|
+
if (accounting) {
|
|
500
|
+
driverSpend = observerSpend(accounting.totals.exclusiveByNode[accounting.runId]);
|
|
501
|
+
for (const worker of workers.values()) {
|
|
502
|
+
worker.spend = observerSpend(accounting.totals.exclusiveByNode[worker.id], accounting.spendGaps?.find((gap) => gap.id === worker.id)?.channels);
|
|
503
|
+
worker.metered = emptySpend;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
const workerViews = [...workers.values()].map((worker) => {
|
|
507
|
+
const tail = workerEventTails.get(worker.label) ?? workerEventTails.get(safeWorkerFile(worker.label)) ?? workerEventTails.get(safeWorkerFile(worker.id)) ?? workerEventTails.get(worker.id);
|
|
508
|
+
worker.liveTail = tail?.lines ?? [];
|
|
509
|
+
if (tail?.file) worker.eventFile = tail.file;
|
|
510
|
+
return finalizeWorker(worker, now);
|
|
511
|
+
}).sort((a, b) => compareWorker(a, b));
|
|
512
|
+
const totals = supervisorTotals(state, workerViews, driverSpend, now);
|
|
513
|
+
if (accounting) {
|
|
514
|
+
const spend = observerSpend(accounting.totals.inclusive, accounting.spendGaps?.flatMap((gap) => gap.channels));
|
|
515
|
+
Object.assign(totals, spend, { tokensTotal: spend.tokensInput + spend.tokensOutput });
|
|
516
|
+
}
|
|
517
|
+
return {
|
|
518
|
+
id: state.id,
|
|
519
|
+
stateDir,
|
|
520
|
+
status: state.status,
|
|
521
|
+
task: state.task,
|
|
522
|
+
workspaceDir: state.workspaceDir,
|
|
523
|
+
budget: state.budget,
|
|
524
|
+
...state.budgetKnown === void 0 ? {} : { budgetKnown: state.budgetKnown },
|
|
525
|
+
...selectFields(state, [
|
|
526
|
+
"verifyCmd",
|
|
527
|
+
"workerModel",
|
|
528
|
+
"driverModel",
|
|
529
|
+
"verdict",
|
|
530
|
+
"progress",
|
|
531
|
+
"startedAt",
|
|
532
|
+
"completedAt"
|
|
533
|
+
], Boolean),
|
|
534
|
+
...selectFields(state, [
|
|
535
|
+
"maxSandboxes",
|
|
536
|
+
"maxLifetimeSeconds",
|
|
537
|
+
"idleTimeoutSeconds",
|
|
538
|
+
"maxUsd",
|
|
539
|
+
"maxDepth"
|
|
540
|
+
], (value) => value !== void 0),
|
|
541
|
+
...state.result?.spentUsd !== void 0 ? { resultSpentUsd: state.result.spentUsd } : {},
|
|
542
|
+
...state.result?.spentTokens !== void 0 ? { resultSpentTokens: state.result.spentTokens } : {},
|
|
543
|
+
workers: workerViews,
|
|
544
|
+
progressTail: [...progressTail],
|
|
545
|
+
journalTail: events.slice(-12),
|
|
546
|
+
driverSpend,
|
|
547
|
+
totals
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
function selectFields(value, keys, include) {
|
|
551
|
+
const selected = {};
|
|
552
|
+
for (const key of keys) if (include(value[key])) selected[key] = value[key];
|
|
553
|
+
return selected;
|
|
554
|
+
}
|
|
555
|
+
function ensureWorker(workers, id) {
|
|
556
|
+
const existing = workers.get(id);
|
|
557
|
+
if (existing) return existing;
|
|
558
|
+
const created = {
|
|
559
|
+
id,
|
|
560
|
+
label: id,
|
|
561
|
+
status: "running",
|
|
562
|
+
spend: emptySpend,
|
|
563
|
+
metered: emptySpend,
|
|
564
|
+
liveTail: []
|
|
565
|
+
};
|
|
566
|
+
workers.set(id, created);
|
|
567
|
+
return created;
|
|
568
|
+
}
|
|
569
|
+
function finalizeWorker(worker, now) {
|
|
570
|
+
const startMs = parseTime(worker.startedAt);
|
|
571
|
+
const endMs = parseTime(worker.endedAt) ?? (worker.status === "running" ? now : startMs);
|
|
572
|
+
const latencyMs = startMs !== void 0 && endMs !== void 0 ? Math.max(0, endMs - startMs) : 0;
|
|
573
|
+
return {
|
|
574
|
+
...worker,
|
|
575
|
+
latencyMs
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
function supervisorTotals(state, workers, driverSpend, now) {
|
|
579
|
+
const spend = workers.map(totalSpend).reduce(addSpend, driverSpend);
|
|
580
|
+
const counts = {
|
|
581
|
+
running: 0,
|
|
582
|
+
done: 0,
|
|
583
|
+
down: 0,
|
|
584
|
+
cancelled: 0
|
|
585
|
+
};
|
|
586
|
+
for (const worker of workers) counts[worker.status] += 1;
|
|
587
|
+
const started = parseTime(state.startedAt);
|
|
588
|
+
const completed = parseTime(state.completedAt);
|
|
589
|
+
const latencyMs = started !== void 0 ? Math.max(0, (completed ?? now) - started) : 0;
|
|
590
|
+
const latencies = workers.map((worker) => worker.latencyMs).filter((ms) => ms > 0);
|
|
591
|
+
return {
|
|
592
|
+
workers: workers.length,
|
|
593
|
+
...counts,
|
|
594
|
+
...spend.usdKnown === false ? { usdKnown: false } : {},
|
|
595
|
+
...spend.tokensKnown === false ? { tokensKnown: false } : {},
|
|
596
|
+
inFlight: counts.running,
|
|
597
|
+
settled: workers.length - counts.running,
|
|
598
|
+
tokensInput: spend.tokensInput,
|
|
599
|
+
tokensOutput: spend.tokensOutput,
|
|
600
|
+
tokensTotal: spend.tokensInput + spend.tokensOutput,
|
|
601
|
+
usd: spend.usd > 0 ? spend.usd : state.result?.spentUsd ?? 0,
|
|
602
|
+
latencyMs,
|
|
603
|
+
workerLatency: distribution(latencies)
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
function aggregateSnapshot(snapshot) {
|
|
607
|
+
const complete = snapshot.completeness === "complete";
|
|
608
|
+
return {
|
|
609
|
+
usdKnown: complete && snapshot.supervisors.every((row) => row.totals.usdKnown !== false),
|
|
610
|
+
tokensKnown: complete && snapshot.supervisors.every((row) => row.totals.tokensKnown !== false),
|
|
611
|
+
supervisors: snapshot.supervisors.length,
|
|
612
|
+
workers: sum(snapshot.supervisors.map((supervisor) => supervisor.totals.workers)),
|
|
613
|
+
running: sum(snapshot.supervisors.map((supervisor) => supervisor.totals.running)),
|
|
614
|
+
done: sum(snapshot.supervisors.map((supervisor) => supervisor.totals.done)),
|
|
615
|
+
down: sum(snapshot.supervisors.map((supervisor) => supervisor.totals.down)),
|
|
616
|
+
usd: sum(snapshot.supervisors.map((supervisor) => supervisor.totals.usd)),
|
|
617
|
+
tokensInput: sum(snapshot.supervisors.map((supervisor) => supervisor.totals.tokensInput)),
|
|
618
|
+
tokensOutput: sum(snapshot.supervisors.map((supervisor) => supervisor.totals.tokensOutput))
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
function compareWorker(a, b) {
|
|
622
|
+
const aStart = parseTime(a.startedAt) ?? 0;
|
|
623
|
+
const bStart = parseTime(b.startedAt) ?? 0;
|
|
624
|
+
if (a.status === "running" && b.status !== "running") return -1;
|
|
625
|
+
if (b.status === "running" && a.status !== "running") return 1;
|
|
626
|
+
return aStart - bStart || a.label.localeCompare(b.label);
|
|
627
|
+
}
|
|
628
|
+
function totalSpend(worker) {
|
|
629
|
+
return addSpend(worker.spend, worker.metered);
|
|
630
|
+
}
|
|
631
|
+
function addSpend(a, b) {
|
|
632
|
+
return {
|
|
633
|
+
iterations: a.iterations + b.iterations,
|
|
634
|
+
tokensInput: a.tokensInput + b.tokensInput,
|
|
635
|
+
tokensOutput: a.tokensOutput + b.tokensOutput,
|
|
636
|
+
usd: a.usd + b.usd,
|
|
637
|
+
ms: a.ms + b.ms,
|
|
638
|
+
...a.usdKnown === false || b.usdKnown === false ? { usdKnown: false } : {},
|
|
639
|
+
...a.tokensKnown === false || b.tokensKnown === false ? { tokensKnown: false } : {}
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
function parseSpend(value) {
|
|
643
|
+
if (!isRecord(value)) return emptySpend;
|
|
644
|
+
const tokens = isRecord(value.tokens) ? value.tokens : {};
|
|
645
|
+
return {
|
|
646
|
+
...value.usdKnown === false ? { usdKnown: false } : {},
|
|
647
|
+
...value.tokensKnown === false || tokens.tokensKnown === false ? { tokensKnown: false } : {},
|
|
648
|
+
iterations: numberValue(value.iterations),
|
|
649
|
+
tokensInput: numberValue(tokens.input),
|
|
650
|
+
tokensOutput: numberValue(tokens.output),
|
|
651
|
+
usd: numberValue(value.usd),
|
|
652
|
+
ms: numberValue(value.ms)
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
function parseBudget(value) {
|
|
656
|
+
if (!isRecord(value)) return void 0;
|
|
657
|
+
const budget = {
|
|
658
|
+
...typeof value.maxIterations === "number" ? { maxIterations: value.maxIterations } : {},
|
|
659
|
+
...typeof value.maxTokens === "number" ? { maxTokens: value.maxTokens } : {},
|
|
660
|
+
...typeof value.maxUsd === "number" ? { maxUsd: value.maxUsd } : {}
|
|
661
|
+
};
|
|
662
|
+
return Object.keys(budget).length ? budget : void 0;
|
|
663
|
+
}
|
|
664
|
+
function describeVerdict(value) {
|
|
665
|
+
if (!isRecord(value)) return void 0;
|
|
666
|
+
if (typeof value.valid === "boolean") return value.valid ? "valid" : "invalid";
|
|
667
|
+
if (typeof value.score === "number") return `score ${value.score.toFixed(2)}`;
|
|
668
|
+
}
|
|
669
|
+
function distribution(values) {
|
|
670
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
671
|
+
return {
|
|
672
|
+
n: sorted.length,
|
|
673
|
+
min: sorted[0] ?? 0,
|
|
674
|
+
median: percentile(sorted, .5),
|
|
675
|
+
p90: percentile(sorted, .9),
|
|
676
|
+
max: sorted[sorted.length - 1] ?? 0
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
function percentile(sorted, p) {
|
|
680
|
+
if (sorted.length === 0) return 0;
|
|
681
|
+
return sorted[Math.min(sorted.length - 1, Math.max(0, Math.ceil(sorted.length * p) - 1))] ?? 0;
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Read one source file under a run directory. An absent file is not a defect on its own — the
|
|
685
|
+
* writer may not have reached it yet — so it reads as `undefined` with no diagnostic; only a
|
|
686
|
+
* failed read raises one. `path` stays run-relative and never carries file contents.
|
|
687
|
+
*/
|
|
688
|
+
function readSourceFile(file, source, path) {
|
|
689
|
+
if (!existsSync(file)) return {
|
|
690
|
+
value: void 0,
|
|
691
|
+
diagnostics: []
|
|
692
|
+
};
|
|
693
|
+
try {
|
|
694
|
+
return {
|
|
695
|
+
value: readFileSync(file, "utf8"),
|
|
696
|
+
diagnostics: []
|
|
697
|
+
};
|
|
698
|
+
} catch {
|
|
699
|
+
return {
|
|
700
|
+
value: void 0,
|
|
701
|
+
diagnostics: [{
|
|
702
|
+
source,
|
|
703
|
+
path,
|
|
704
|
+
reason: "unreadable"
|
|
705
|
+
}]
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
function tailLines(raw, maxLines) {
|
|
710
|
+
if (raw === void 0) return [];
|
|
711
|
+
return raw.trim().split("\n").filter(Boolean).slice(-maxLines);
|
|
712
|
+
}
|
|
713
|
+
function readWorkerEventTails(dir, maxLines) {
|
|
714
|
+
const out = /* @__PURE__ */ new Map();
|
|
715
|
+
if (!existsSync(dir)) return {
|
|
716
|
+
value: out,
|
|
717
|
+
diagnostics: []
|
|
718
|
+
};
|
|
719
|
+
const workersDir = basename(dir);
|
|
720
|
+
let entries;
|
|
721
|
+
try {
|
|
722
|
+
entries = readdirSync(dir);
|
|
723
|
+
} catch {
|
|
724
|
+
return {
|
|
725
|
+
value: out,
|
|
726
|
+
diagnostics: [{
|
|
727
|
+
source: "worker-tail",
|
|
728
|
+
path: workersDir,
|
|
729
|
+
reason: "unreadable"
|
|
730
|
+
}]
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
const diagnostics = [];
|
|
734
|
+
for (const entry of entries) {
|
|
735
|
+
if (!entry.endsWith(".ndjson") || entry.endsWith(".inbox.ndjson")) continue;
|
|
736
|
+
const file = join(dir, entry);
|
|
737
|
+
const read = readSourceFile(file, "worker-tail", join(workersDir, entry));
|
|
738
|
+
diagnostics.push(...read.diagnostics);
|
|
739
|
+
const lines = tailLines(read.value, maxLines);
|
|
740
|
+
out.set(entry.slice(0, -7), {
|
|
741
|
+
file,
|
|
742
|
+
lines
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
return {
|
|
746
|
+
value: out,
|
|
747
|
+
diagnostics
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
function readSupervisorState(dir) {
|
|
751
|
+
const path = "state.json";
|
|
752
|
+
const read = readSourceFile(join(dir, path), "supervisor-state", path);
|
|
753
|
+
if (read.diagnostics.length > 0) return {
|
|
754
|
+
value: void 0,
|
|
755
|
+
diagnostics: read.diagnostics
|
|
756
|
+
};
|
|
757
|
+
const failed = (reason) => ({
|
|
758
|
+
value: void 0,
|
|
759
|
+
diagnostics: [{
|
|
760
|
+
source: "supervisor-state",
|
|
761
|
+
path,
|
|
762
|
+
reason
|
|
763
|
+
}]
|
|
764
|
+
});
|
|
765
|
+
if (read.value === void 0) return failed("missing");
|
|
766
|
+
try {
|
|
767
|
+
const parsed = JSON.parse(read.value);
|
|
768
|
+
return isSupervisorState(parsed) ? {
|
|
769
|
+
value: parsed,
|
|
770
|
+
diagnostics: []
|
|
771
|
+
} : failed("invalid-state");
|
|
772
|
+
} catch {
|
|
773
|
+
return failed("partial-json");
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
function isSupervisorState(value) {
|
|
777
|
+
return isRecord(value) && Object.entries({
|
|
778
|
+
id: "string",
|
|
779
|
+
status: "string",
|
|
780
|
+
task: "string",
|
|
781
|
+
workspaceDir: "string",
|
|
782
|
+
budget: "number"
|
|
783
|
+
}).every(([key, type]) => typeof value[key] === type);
|
|
784
|
+
}
|
|
785
|
+
function isSpawnEvent(value) {
|
|
786
|
+
return isRecord(value) && typeof value.id === "string" && typeof value.kind === "string" && [
|
|
787
|
+
"spawned",
|
|
788
|
+
"settled",
|
|
789
|
+
"cancelled",
|
|
790
|
+
"metered",
|
|
791
|
+
"progress"
|
|
792
|
+
].includes(value.kind);
|
|
793
|
+
}
|
|
794
|
+
function isRecord(value) {
|
|
795
|
+
return typeof value === "object" && value !== null;
|
|
796
|
+
}
|
|
797
|
+
function isDirectory(path) {
|
|
798
|
+
try {
|
|
799
|
+
return statSync(path).isDirectory();
|
|
800
|
+
} catch {
|
|
801
|
+
return false;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
function numberValue(value) {
|
|
805
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
806
|
+
}
|
|
807
|
+
function parseTime(value) {
|
|
808
|
+
if (!value) return void 0;
|
|
809
|
+
const parsed = Date.parse(value);
|
|
810
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
811
|
+
}
|
|
812
|
+
function sum(values) {
|
|
813
|
+
return values.reduce((total, value) => total + value, 0);
|
|
814
|
+
}
|
|
815
|
+
// src/tui/top-web.ts
|
|
816
|
+
/** Read-only web transport over the same snapshots as the terminal. No execution or writes. */
|
|
817
|
+
async function serveTopWeb(options) {
|
|
818
|
+
if (!options.roots.length) throw new Error("At least one --root is required");
|
|
819
|
+
const roots = [...new Set(await Promise.all(options.roots.map((root) => realpath(root))))];
|
|
820
|
+
const assets = await realpath(options.assets ?? dirname(createRequire(import.meta.url).resolve("@drewstone/agent-record/workspace-viewer.js")));
|
|
821
|
+
for (const file of ["workspace-viewer.js", "styles.css"]) await stat(resolve(assets, file));
|
|
822
|
+
const host = options.host ?? "127.0.0.1";
|
|
823
|
+
const clients = /* @__PURE__ */ new Set();
|
|
824
|
+
const transcripts = /* @__PURE__ */ new Set();
|
|
825
|
+
const snapshots = /* @__PURE__ */ new Map();
|
|
826
|
+
const digests = /* @__PURE__ */ new Map();
|
|
827
|
+
const watchers = [];
|
|
828
|
+
let sequence = 0;
|
|
829
|
+
let reading = false;
|
|
830
|
+
let dirty = true;
|
|
831
|
+
let closed = false;
|
|
832
|
+
let scheduled;
|
|
833
|
+
const send = (client, frame) => {
|
|
834
|
+
if (client.writableLength > 1048576) return client.destroy();
|
|
835
|
+
client.write(`id: ${frame.sequence}\nevent: snapshot\ndata: ${JSON.stringify(frame)}\n\n`);
|
|
836
|
+
};
|
|
837
|
+
const refresh = async () => {
|
|
838
|
+
if (reading || closed) return;
|
|
839
|
+
reading = true;
|
|
840
|
+
dirty = false;
|
|
841
|
+
try {
|
|
842
|
+
const changed = [];
|
|
843
|
+
for (const root of roots) {
|
|
844
|
+
const snapshot = existsSync(resolve(root, "observer.jsonl")) ? await loadTopSnapshot(root, { runDir: root }) : existsSync(resolve(root, "spawn-journal.jsonl")) ? await loadTopSnapshot(root, {
|
|
845
|
+
runDir: root,
|
|
846
|
+
format: "journal"
|
|
847
|
+
}) : existsSync(resolve(root, "journal.jsonl")) ? await loadTopSnapshot(root, {
|
|
848
|
+
runDir: root,
|
|
849
|
+
format: "workflow"
|
|
850
|
+
}) : await loadRunsRoot(root);
|
|
851
|
+
const digest = createHash("sha256").update(JSON.stringify(snapshot, (key, value) => key === "generatedAt" || key === "latencyMs" ? void 0 : value)).digest("hex");
|
|
852
|
+
snapshots.set(root, snapshot);
|
|
853
|
+
if (digests.get(root) !== digest) {
|
|
854
|
+
changed.push(snapshot);
|
|
855
|
+
digests.set(root, digest);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
if (changed.length) {
|
|
859
|
+
const frame = {
|
|
860
|
+
sequence: ++sequence,
|
|
861
|
+
observedAt: Date.now(),
|
|
862
|
+
reset: false,
|
|
863
|
+
snapshots: changed
|
|
864
|
+
};
|
|
865
|
+
for (const client of clients) send(client, frame);
|
|
866
|
+
}
|
|
867
|
+
for (const update of transcripts) update();
|
|
868
|
+
} finally {
|
|
869
|
+
reading = false;
|
|
870
|
+
if (dirty && !closed) schedule();
|
|
871
|
+
}
|
|
872
|
+
};
|
|
873
|
+
const schedule = () => {
|
|
874
|
+
dirty = true;
|
|
875
|
+
if (!scheduled && !closed) scheduled = setTimeout(() => {
|
|
876
|
+
scheduled = void 0;
|
|
877
|
+
refresh().catch((error) => {
|
|
878
|
+
for (const client of clients) client.write(`event: unavailable\ndata: ${JSON.stringify({ message: String(error) })}\n\n`);
|
|
879
|
+
});
|
|
880
|
+
}, 16);
|
|
881
|
+
};
|
|
882
|
+
await refresh();
|
|
883
|
+
for (const root of roots) watchers.push(watch(root, { recursive: true }, schedule));
|
|
884
|
+
const reconcile = setInterval(schedule, 500);
|
|
885
|
+
const heartbeat = setInterval(() => {
|
|
886
|
+
for (const client of clients) client.write(": alive\n\n");
|
|
887
|
+
}, 15e3);
|
|
888
|
+
const server = createServer((request, response) => {
|
|
889
|
+
(async () => {
|
|
890
|
+
const base = `http://${request.headers.host}`;
|
|
891
|
+
const incomingHost = new URL(base).hostname.replace(/^\[|\]$/g, "");
|
|
892
|
+
if (!request.headers.host || ![
|
|
893
|
+
host,
|
|
894
|
+
"127.0.0.1",
|
|
895
|
+
"localhost",
|
|
896
|
+
"::1"
|
|
897
|
+
].includes(incomingHost) || request.headers.origin && request.headers.origin !== base) {
|
|
898
|
+
response.writeHead(403).end();
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
902
|
+
response.writeHead(405, { Allow: "GET, HEAD" }).end();
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
905
|
+
const path = new URL(request.url ?? "/", base).pathname;
|
|
906
|
+
response.setHeader("Cache-Control", "no-store");
|
|
907
|
+
response.setHeader("X-Content-Type-Options", "nosniff");
|
|
908
|
+
if (path === "/api/top/events") {
|
|
909
|
+
if (request.method === "HEAD") {
|
|
910
|
+
response.writeHead(200, { "Content-Type": "text/event-stream" }).end();
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
response.writeHead(200, {
|
|
914
|
+
"Content-Type": "text/event-stream",
|
|
915
|
+
"X-Accel-Buffering": "no"
|
|
916
|
+
});
|
|
917
|
+
response.flushHeaders();
|
|
918
|
+
clients.add(response);
|
|
919
|
+
send(response, {
|
|
920
|
+
sequence,
|
|
921
|
+
observedAt: Date.now(),
|
|
922
|
+
reset: true,
|
|
923
|
+
snapshots: [...snapshots.values()]
|
|
924
|
+
});
|
|
925
|
+
response.on("close", () => clients.delete(response));
|
|
926
|
+
} else if (/^\/api\/top\/agents\/\d+\/[^/]+\/[^/]+\/stream$/.test(path)) {
|
|
927
|
+
const [, , , , rootIndex, runId, workerId] = path.split("/").map(decodeURIComponent);
|
|
928
|
+
const root = roots[Number(rootIndex)];
|
|
929
|
+
const worker = () => {
|
|
930
|
+
const run = root ? snapshots.get(root)?.supervisors.find((run) => run.id === runId) : void 0;
|
|
931
|
+
const node = run?.workers.find((node) => node.id === workerId);
|
|
932
|
+
if (node || !run || run.id !== workerId) return node;
|
|
933
|
+
return {
|
|
934
|
+
id: run.id,
|
|
935
|
+
label: "root",
|
|
936
|
+
...root && existsSync(resolve(root, "root-stream.jsonl")) ? { eventFile: resolve(root, "root-stream.jsonl") } : {},
|
|
937
|
+
status: run.status === "cancelled" ? "cancelled" : run.status === "done" ? "done" : run.status === "down" ? "down" : "running",
|
|
938
|
+
startedAt: run.startedAt,
|
|
939
|
+
endedAt: run.completedAt,
|
|
940
|
+
latencyMs: run.totals.latencyMs,
|
|
941
|
+
spend: run.driverSpend,
|
|
942
|
+
metered: {
|
|
943
|
+
iterations: 0,
|
|
944
|
+
tokensInput: 0,
|
|
945
|
+
tokensOutput: 0,
|
|
946
|
+
usd: 0,
|
|
947
|
+
ms: 0
|
|
948
|
+
},
|
|
949
|
+
liveTail: run.progressTail
|
|
950
|
+
};
|
|
951
|
+
};
|
|
952
|
+
if (!worker()) {
|
|
953
|
+
response.writeHead(404).end();
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
let history = await transcriptHistory(worker(), root, snapshots.get(root)?.pursuit?.nodes.find((node) => node.runId === runId && node.id === workerId));
|
|
957
|
+
response.writeHead(200, {
|
|
958
|
+
"Content-Type": "text/event-stream",
|
|
959
|
+
"X-Accel-Buffering": "no"
|
|
960
|
+
});
|
|
961
|
+
response.flushHeaders();
|
|
962
|
+
if (request.method === "HEAD") {
|
|
963
|
+
response.end();
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
const generation = Date.now();
|
|
967
|
+
response.write(`event: reset\ndata: ${JSON.stringify({ generation })}\n\n`);
|
|
968
|
+
const seen = /* @__PURE__ */ new Set();
|
|
969
|
+
let lastSeq = 0;
|
|
970
|
+
const update = () => {
|
|
971
|
+
const node = worker();
|
|
972
|
+
if (!node || response.writableLength > 1048576) {
|
|
973
|
+
response.destroy();
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
976
|
+
const events = (history ?? transcriptTail(node)).flatMap((row) => {
|
|
977
|
+
const key = createHash("sha256").update(JSON.stringify(row)).digest("hex");
|
|
978
|
+
if (seen.has(key)) return [];
|
|
979
|
+
seen.add(key);
|
|
980
|
+
if (seen.size > 2e3) seen.delete(seen.values().next().value);
|
|
981
|
+
return [{
|
|
982
|
+
...row,
|
|
983
|
+
seq: ++lastSeq
|
|
984
|
+
}];
|
|
985
|
+
});
|
|
986
|
+
history = void 0;
|
|
987
|
+
const alive = node.status === "running";
|
|
988
|
+
response.write(`event: rows\ndata: ${JSON.stringify({
|
|
989
|
+
generation,
|
|
990
|
+
events,
|
|
991
|
+
lastSeq,
|
|
992
|
+
alive,
|
|
993
|
+
window: "recorded-tail"
|
|
994
|
+
})}\n\n`);
|
|
995
|
+
if (!alive) {
|
|
996
|
+
response.write("event: end\ndata: {}\n\n");
|
|
997
|
+
response.end();
|
|
998
|
+
}
|
|
999
|
+
};
|
|
1000
|
+
transcripts.add(update);
|
|
1001
|
+
response.on("close", () => transcripts.delete(update));
|
|
1002
|
+
update();
|
|
1003
|
+
} else if (path === "/api/top") {
|
|
1004
|
+
response.writeHead(200, { "Content-Type": "application/json" });
|
|
1005
|
+
response.end(JSON.stringify({
|
|
1006
|
+
sequence,
|
|
1007
|
+
observedAt: Date.now(),
|
|
1008
|
+
reset: true,
|
|
1009
|
+
snapshots: [...snapshots.values()]
|
|
1010
|
+
}));
|
|
1011
|
+
} else if (path === "/") {
|
|
1012
|
+
response.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
1013
|
+
response.end("<!doctype html><html><head><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"><title>Live agents</title><link rel=\"stylesheet\" href=\"/assets/styles.css\"></head><body><div id=\"agent-workspace\" data-mode=\"live\" data-api=\"/api/top\" data-theme=\"dark\"></div><script type=\"module\" src=\"/assets/workspace-viewer.js\"><\/script></body></html>");
|
|
1014
|
+
} else if (path.startsWith("/assets/")) {
|
|
1015
|
+
const file = await realpath(resolve(assets, decodeURIComponent(path.slice(8))));
|
|
1016
|
+
if (!file.startsWith(assets + sep)) {
|
|
1017
|
+
response.writeHead(403).end();
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
const kind = {
|
|
1021
|
+
".js": "text/javascript",
|
|
1022
|
+
".css": "text/css",
|
|
1023
|
+
".woff2": "font/woff2"
|
|
1024
|
+
}[extname(file)];
|
|
1025
|
+
if (!kind) {
|
|
1026
|
+
response.writeHead(404).end();
|
|
1027
|
+
return;
|
|
1028
|
+
}
|
|
1029
|
+
response.writeHead(200, { "Content-Type": kind });
|
|
1030
|
+
response.end(request.method === "HEAD" ? void 0 : await readFile(file));
|
|
1031
|
+
} else response.writeHead(404).end();
|
|
1032
|
+
})().catch((error) => {
|
|
1033
|
+
if (!response.headersSent) response.writeHead(500, { "Content-Type": "application/json" });
|
|
1034
|
+
response.end(JSON.stringify({ error: String(error) }));
|
|
1035
|
+
});
|
|
1036
|
+
});
|
|
1037
|
+
let closing;
|
|
1038
|
+
const close = () => closing ??= (async () => {
|
|
1039
|
+
closed = true;
|
|
1040
|
+
if (scheduled) clearTimeout(scheduled);
|
|
1041
|
+
clearInterval(reconcile);
|
|
1042
|
+
clearInterval(heartbeat);
|
|
1043
|
+
for (const watcher of watchers) watcher.close();
|
|
1044
|
+
for (const client of clients) client.end();
|
|
1045
|
+
transcripts.clear();
|
|
1046
|
+
server.closeAllConnections();
|
|
1047
|
+
await new Promise((done, reject) => server.close((error) => error && (!("code" in error) || error.code !== "ERR_SERVER_NOT_RUNNING") ? reject(error) : done()));
|
|
1048
|
+
})();
|
|
1049
|
+
try {
|
|
1050
|
+
await new Promise((done, reject) => {
|
|
1051
|
+
server.once("error", reject);
|
|
1052
|
+
server.listen(options.port ?? 0, host, done);
|
|
1053
|
+
});
|
|
1054
|
+
} catch (error) {
|
|
1055
|
+
await close();
|
|
1056
|
+
throw error;
|
|
1057
|
+
}
|
|
1058
|
+
const address = server.address();
|
|
1059
|
+
if (!address || typeof address === "string") throw new Error("Web server has no TCP address");
|
|
1060
|
+
return {
|
|
1061
|
+
url: `http://${host.includes(":") ? `[${host}]` : host}:${address.port}/`,
|
|
1062
|
+
close
|
|
1063
|
+
};
|
|
1064
|
+
}
|
|
1065
|
+
/** CLI options stay on agent-runtime-top; the renderer is supplied by its installed owner. */
|
|
1066
|
+
async function runTopWeb(argv) {
|
|
1067
|
+
const values = (flag) => argv.flatMap((arg, i) => arg === flag ? [argv[i + 1] ?? ""] : []);
|
|
1068
|
+
const roots = values("--root");
|
|
1069
|
+
const port = Number(values("--port")[0] ?? 0);
|
|
1070
|
+
if (!Number.isInteger(port) || port < 0 || port > 65535) throw new Error("Invalid --port");
|
|
1071
|
+
const web = await serveTopWeb({
|
|
1072
|
+
roots: roots.length ? roots : [process.cwd()],
|
|
1073
|
+
...values("--assets")[0] ? { assets: values("--assets")[0] } : {},
|
|
1074
|
+
port,
|
|
1075
|
+
...values("--host")[0] ? { host: values("--host")[0] } : {}
|
|
1076
|
+
});
|
|
1077
|
+
process.stdout.write(`${JSON.stringify({
|
|
1078
|
+
event: "ready",
|
|
1079
|
+
url: web.url,
|
|
1080
|
+
pid: process.pid
|
|
1081
|
+
})}\n`);
|
|
1082
|
+
const stop = () => {
|
|
1083
|
+
web.close().catch((error) => {
|
|
1084
|
+
process.stderr.write(`${String(error)}\n`);
|
|
1085
|
+
process.exitCode = 1;
|
|
1086
|
+
});
|
|
1087
|
+
};
|
|
1088
|
+
process.once("SIGINT", stop);
|
|
1089
|
+
process.once("SIGTERM", stop);
|
|
1090
|
+
return web;
|
|
1091
|
+
}
|
|
1092
|
+
/** A journal root holds one `<runId>/observer.jsonl` per retained run beside any workspace runs. */
|
|
1093
|
+
async function loadRunsRoot(root) {
|
|
1094
|
+
const workspace = loadTopSnapshot(root);
|
|
1095
|
+
const runIds = readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && existsSync(resolve(root, entry.name, "observer.jsonl"))).map((entry) => entry.name);
|
|
1096
|
+
if (!runIds.length) return workspace;
|
|
1097
|
+
const runs = await Promise.all(runIds.map((runId) => loadTopSnapshot(root, { runDir: resolve(root, runId) })));
|
|
1098
|
+
const diagnostics = [...workspace.diagnostics, ...runs.flatMap((run, i) => run.diagnostics.map((d) => ({
|
|
1099
|
+
...d,
|
|
1100
|
+
runId: runIds[i]
|
|
1101
|
+
})))];
|
|
1102
|
+
const supervisors = [...workspace.supervisors, ...runs.flatMap((run) => run.supervisors)];
|
|
1103
|
+
return {
|
|
1104
|
+
...workspace,
|
|
1105
|
+
supervisors,
|
|
1106
|
+
diagnostics,
|
|
1107
|
+
completeness: diagnostics.length ? "partial" : "complete",
|
|
1108
|
+
discovered: workspace.discovered + runIds.length,
|
|
1109
|
+
loaded: supervisors.length
|
|
1110
|
+
};
|
|
1111
|
+
}
|
|
1112
|
+
export { numberValue as a, totalSpend as c, loadTopSnapshot as i, serveTopWeb as n, parseSpend as o, aggregateSnapshot as r, parseTime as s, runTopWeb as t };
|
|
1113
|
+
|
|
1114
|
+
//# sourceMappingURL=top-web-CkXPPXjI.js.map
|