@tangle-network/agent-runtime 0.116.0 → 0.119.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.
Files changed (95) hide show
  1. package/README.md +9 -1
  2. package/dist/{activation-DuqQhee6.js → activation-DdIpwQ0k.js} +3 -3
  3. package/dist/{activation-DuqQhee6.js.map → activation-DdIpwQ0k.js.map} +1 -1
  4. package/dist/agent.d.ts +3 -64
  5. package/dist/agent.js +5 -207
  6. package/dist/agent.js.map +1 -1
  7. package/dist/{analyst-loop-BoNIG2hA.js → analyst-loop-DvSciOfB.js} +2 -2
  8. package/dist/{analyst-loop-BoNIG2hA.js.map → analyst-loop-DvSciOfB.js.map} +1 -1
  9. package/dist/analyst-loop.js +1 -1
  10. package/dist/candidate-execution/index.d.ts +3 -3
  11. package/dist/candidate-execution/index.js +5 -5
  12. package/dist/{candidate-execution-CfpJrd3o.js → candidate-execution-DDkSRPjY.js} +4 -4
  13. package/dist/{candidate-execution-CfpJrd3o.js.map → candidate-execution-DDkSRPjY.js.map} +1 -1
  14. package/dist/{environment-provider-CWsRh6Uz.d.ts → environment-provider-Bh4nX2qt.d.ts} +306 -32
  15. package/dist/{environment-provider-CCaEhA-l.js → environment-provider-Cr75N-rU.js} +141 -32
  16. package/dist/environment-provider-Cr75N-rU.js.map +1 -0
  17. package/dist/environment-provider.d.ts +1 -1
  18. package/dist/environment-provider.js +1 -1
  19. package/dist/{improvement-cycle-C1cmjvPD.js → improvement-cycle-zjR-MXwK.js} +163 -22
  20. package/dist/improvement-cycle-zjR-MXwK.js.map +1 -0
  21. package/dist/{index-C-FYUuFG.d.ts → index-B3bHsVLw.d.ts} +2 -2
  22. package/dist/{index-CYkDeM5L.d.ts → index-BLsKcxNd.d.ts} +3 -3
  23. package/dist/{index-COumPQka.d.ts → index-CGADWaa_.d.ts} +864 -437
  24. package/dist/{index-DcLMNnG5.d.ts → index-I35151Fr.d.ts} +9 -9
  25. package/dist/index.d.ts +9 -9
  26. package/dist/index.js +13 -13
  27. package/dist/index.js.map +1 -1
  28. package/dist/intelligence.d.ts +10 -9
  29. package/dist/intelligence.js +15 -9
  30. package/dist/intelligence.js.map +1 -1
  31. package/dist/kernel.d.ts +4 -4
  32. package/dist/kernel.js +9 -8
  33. package/dist/{knowledge-DOzbywZT.js → knowledge-I1e9zQGj.js} +7 -7
  34. package/dist/knowledge-I1e9zQGj.js.map +1 -0
  35. package/dist/knowledge.d.ts +1 -1
  36. package/dist/knowledge.js +1 -1
  37. package/dist/{local-harness-t6cDWDQ2.d.ts → local-harness-BIajef4A.d.ts} +29 -7
  38. package/dist/{loop-runner-bin-BFrhPLKt.d.ts → loop-runner-bin-BeG9vTdE.d.ts} +3 -3
  39. package/dist/{loop-runner-bin-BuQjc5DR.js → loop-runner-bin-L996YVPS.js} +4 -4
  40. package/dist/{loop-runner-bin-BuQjc5DR.js.map → loop-runner-bin-L996YVPS.js.map} +1 -1
  41. package/dist/loop-runner-bin.d.ts +1 -1
  42. package/dist/loop-runner-bin.js +1 -1
  43. package/dist/mcp/bin.js +43 -10
  44. package/dist/mcp/bin.js.map +1 -1
  45. package/dist/mcp/index.d.ts +5 -45
  46. package/dist/mcp/index.js +8 -212
  47. package/dist/mcp/index.js.map +1 -1
  48. package/dist/{openai-tools-_Wyp4udO.js → openai-tools-CvFEzw6f.js} +2 -2
  49. package/dist/{openai-tools-_Wyp4udO.js.map → openai-tools-CvFEzw6f.js.map} +1 -1
  50. package/dist/{prepare-BHQBb02e.js → prepare-DpV6np9e.js} +47 -34
  51. package/dist/prepare-DpV6np9e.js.map +1 -0
  52. package/dist/primeintellect/index.d.ts +1 -1
  53. package/dist/{protected-model-port-BP6Z4eau.d.ts → protected-model-port-CoJOiINX.d.ts} +11 -3
  54. package/dist/{protected-model-port-DqAH1Z2M.js → protected-model-port-vQLBRoAQ.js} +2 -2
  55. package/dist/{protected-model-port-DqAH1Z2M.js.map → protected-model-port-vQLBRoAQ.js.map} +1 -1
  56. package/dist/{redact-BEtQtvd6.d.ts → redact-PQzmE1Jn.d.ts} +4 -4
  57. package/dist/run-layout-C2FGmZ3v.js +158 -0
  58. package/dist/run-layout-C2FGmZ3v.js.map +1 -0
  59. package/dist/{runtime-Ut1pkd2n.js → runtime-D4e3GP5K.js} +147 -190
  60. package/dist/runtime-D4e3GP5K.js.map +1 -0
  61. package/dist/{sandbox-events-DeI5xX8P.js → sandbox-events-Yhd1GYWl.js} +4 -2
  62. package/dist/sandbox-events-Yhd1GYWl.js.map +1 -0
  63. package/dist/spawn-journal-IeXpidO2.js +857 -0
  64. package/dist/spawn-journal-IeXpidO2.js.map +1 -0
  65. package/dist/{structural-rollout-CVY_0hJp.js → structural-rollout-DQHO3b2Y.js} +4 -4
  66. package/dist/structural-rollout-DQHO3b2Y.js.map +1 -0
  67. package/dist/{supervise-BUR9ByF7.js → supervise-Cq50UT5-.js} +2874 -1892
  68. package/dist/supervise-Cq50UT5-.js.map +1 -0
  69. package/dist/{supervisor-BBbPBXpe.js → supervisor-CpT9yAxL.js} +2896 -983
  70. package/dist/supervisor-CpT9yAxL.js.map +1 -0
  71. package/dist/testing.js +98 -76
  72. package/dist/testing.js.map +1 -1
  73. package/dist/top-app-5unxqovu.js +1088 -0
  74. package/dist/top-app-5unxqovu.js.map +1 -0
  75. package/dist/tui/bin.d.ts +1 -0
  76. package/dist/tui/bin.js +19 -0
  77. package/dist/tui/bin.js.map +1 -0
  78. package/dist/tui/index.d.ts +209 -0
  79. package/dist/tui/index.js +2 -0
  80. package/dist/{util-Cc9g9Y-o.js → util-MVgdwuIS.js} +2 -2
  81. package/dist/{util-Cc9g9Y-o.js.map → util-MVgdwuIS.js.map} +1 -1
  82. package/dist/{workspace-archive-DXzJq7WP.js → workspace-archive-B4SkNJjw.js} +2 -2
  83. package/dist/{workspace-archive-DXzJq7WP.js.map → workspace-archive-B4SkNJjw.js.map} +1 -1
  84. package/package.json +8 -2
  85. package/dist/environment-provider-CCaEhA-l.js.map +0 -1
  86. package/dist/improvement-cycle-C1cmjvPD.js.map +0 -1
  87. package/dist/knowledge-DOzbywZT.js.map +0 -1
  88. package/dist/prepare-BHQBb02e.js.map +0 -1
  89. package/dist/runtime-Ut1pkd2n.js.map +0 -1
  90. package/dist/sandbox-events-DeI5xX8P.js.map +0 -1
  91. package/dist/spawn-journal-DCPbicXB.js +0 -457
  92. package/dist/spawn-journal-DCPbicXB.js.map +0 -1
  93. package/dist/structural-rollout-CVY_0hJp.js.map +0 -1
  94. package/dist/supervise-BUR9ByF7.js.map +0 -1
  95. package/dist/supervisor-BBbPBXpe.js.map +0 -1
@@ -0,0 +1,1088 @@
1
+ import { d as writeWorkerSteer, i as safeWorkerFile, n as legacySupervisorRunsRoot, o as supervisorRunsRoot, s as supervisorWorkersDir } from "./run-layout-C2FGmZ3v.js";
2
+ import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
3
+ import { join, resolve } from "node:path";
4
+ import { spawnSync } from "node:child_process";
5
+ import { emitKeypressEvents } from "node:readline";
6
+ //#region src/tui/top-model.ts
7
+ /**
8
+ * The read side of the supervisor-run TUI: turn the on-disk run layout into one `TopSnapshot`, and
9
+ * render that snapshot to an ANSI frame.
10
+ *
11
+ * This module never derives a run path itself. Every directory and per-worker file it reads comes
12
+ * from `../runtime/supervise/run-layout` — the same module the writers use — so the reader cannot
13
+ * drift from the layout the way an independently-versioned copy of the contract would.
14
+ *
15
+ * Extracted from the `loops` repo (`src/top-model.ts`), which held a hand-joined copy of the
16
+ * layout. Rendering is deliberately unchanged: raw ANSI, zero dependencies.
17
+ *
18
+ * @experimental
19
+ */
20
+ const emptySpend = {
21
+ iterations: 0,
22
+ tokensInput: 0,
23
+ tokensOutput: 0,
24
+ usd: 0,
25
+ ms: 0
26
+ };
27
+ const ansiPattern = /\x1b\[[0-9;?]*[ -/]*[@-~]/g;
28
+ const colors = {
29
+ reset: "\x1B[0m",
30
+ dim: "\x1B[2m",
31
+ bold: "\x1B[1m",
32
+ red: "\x1B[31m",
33
+ green: "\x1B[32m",
34
+ yellow: "\x1B[33m",
35
+ blue: "\x1B[34m",
36
+ magenta: "\x1B[35m",
37
+ cyan: "\x1B[36m",
38
+ white: "\x1B[37m",
39
+ inverse: "\x1B[7m"
40
+ };
41
+ /**
42
+ * Read every supervisor run under one workspace into a single point-in-time snapshot.
43
+ *
44
+ * Pure with respect to the process: it only reads, and every unreadable or half-written file is
45
+ * skipped rather than thrown on — an operator view must survive a writer mid-append. `now` is
46
+ * injectable so elapsed time is deterministic under test.
47
+ */
48
+ function loadTopSnapshot(rootDir, now = Date.now()) {
49
+ const root = resolve(rootDir);
50
+ return {
51
+ root,
52
+ generatedAt: now,
53
+ supervisors: [supervisorRunsRoot(root), legacySupervisorRunsRoot(root)].filter((runsRoot) => existsSync(runsRoot)).flatMap((runsRoot) => readdirSync(runsRoot).filter((entry) => isDirectory(join(runsRoot, entry))).map((entry) => {
54
+ const dir = join(runsRoot, entry);
55
+ const state = readJson(join(dir, "state.json"));
56
+ if (!isSupervisorState(state)) return void 0;
57
+ return buildSupervisorView(state, dir, readJournal(dir), readTail(join(dir, "progress.ndjson"), 8), now, readWorkerEventTails(supervisorWorkersDir(dir), 8));
58
+ })).filter((view) => view !== void 0).sort((a, b) => String(b.startedAt ?? "").localeCompare(String(a.startedAt ?? "")))
59
+ };
60
+ }
61
+ /**
62
+ * The journal file a run dir actually carries. `createFileRunContext` — this repo's own durable
63
+ * writer — names it `spawn-journal.jsonl`; `journal.jsonl` is the retired bare-event file older
64
+ * runs left behind. First existing name wins; a reader that knew only the retired name would show
65
+ * zero workers for every current run.
66
+ */
67
+ const journalFileNames = ["spawn-journal.jsonl", "journal.jsonl"];
68
+ function readJournal(eventDir) {
69
+ const file = journalFileNames.map((name) => join(eventDir, name)).find((candidate) => existsSync(candidate));
70
+ if (!file) return [];
71
+ const out = [];
72
+ let raw = "";
73
+ try {
74
+ raw = readFileSync(file, "utf8");
75
+ } catch {
76
+ return out;
77
+ }
78
+ for (const line of raw.split("\n")) {
79
+ const trimmed = line.trim();
80
+ if (!trimmed) continue;
81
+ try {
82
+ const parsed = JSON.parse(trimmed);
83
+ const event = parsed.event ?? parsed;
84
+ if (isSpawnEvent(event)) out.push(event);
85
+ } catch {}
86
+ }
87
+ return out;
88
+ }
89
+ function buildSupervisorView(state, stateDir, events, progressTail, now, workerEventTails = /* @__PURE__ */ new Map()) {
90
+ const workers = /* @__PURE__ */ new Map();
91
+ let driverSpend = emptySpend;
92
+ const journalRootId = events.find((event) => event.kind === "spawned" && !event.parent && event.label === "root")?.id;
93
+ const isRoot = (id) => id === state.id || id === journalRootId;
94
+ for (const event of events) {
95
+ if (event.kind === "spawned") {
96
+ if (isRoot(event.id) || !event.parent && event.label === "root") continue;
97
+ const worker = ensureWorker(workers, event.id, event.label ?? event.id);
98
+ worker.label = event.label ?? worker.label;
99
+ if (event.parent) worker.parent = event.parent;
100
+ if (event.runtime) worker.runtime = event.runtime;
101
+ worker.status = "running";
102
+ if (event.at) worker.startedAt = event.at;
103
+ const budget = parseBudget(event.budget);
104
+ if (budget) worker.budget = budget;
105
+ continue;
106
+ }
107
+ if (event.kind === "settled") {
108
+ if (isRoot(event.id)) continue;
109
+ const worker = ensureWorker(workers, event.id, event.id);
110
+ worker.status = event.status === "down" ? "down" : "done";
111
+ if (event.at) worker.endedAt = event.at;
112
+ if (event.outRef) worker.outRef = event.outRef;
113
+ if (event.infra !== void 0) worker.infra = event.infra;
114
+ const verdict = describeVerdict(event.verdict);
115
+ if (verdict) worker.verdict = verdict;
116
+ worker.spend = addSpend(worker.spend, parseSpend(event.spent));
117
+ continue;
118
+ }
119
+ if (event.kind === "cancelled") {
120
+ if (isRoot(event.id)) continue;
121
+ const worker = ensureWorker(workers, event.id, event.id);
122
+ worker.status = "cancelled";
123
+ if (event.at) worker.endedAt = event.at;
124
+ if (event.reason) worker.reason = event.reason;
125
+ continue;
126
+ }
127
+ if (event.kind === "metered") {
128
+ const spend = parseSpend(event.spend);
129
+ if (isRoot(event.id)) driverSpend = addSpend(driverSpend, spend);
130
+ else {
131
+ const worker = ensureWorker(workers, event.id, event.id);
132
+ worker.metered = addSpend(worker.metered, spend);
133
+ }
134
+ }
135
+ }
136
+ const workerViews = [...workers.values()].map((worker) => {
137
+ const tail = workerEventTails.get(worker.label) ?? workerEventTails.get(safeWorkerFile(worker.label)) ?? workerEventTails.get(worker.id);
138
+ worker.liveTail = tail?.lines ?? [];
139
+ if (tail?.cwd) worker.cwd = tail.cwd;
140
+ if (tail?.file) worker.eventFile = tail.file;
141
+ return finalizeWorker(worker, now);
142
+ }).sort((a, b) => compareWorker(a, b));
143
+ const totals = supervisorTotals(state, workerViews, driverSpend, now);
144
+ return {
145
+ id: state.id,
146
+ stateDir,
147
+ status: state.status,
148
+ task: state.task,
149
+ workspaceDir: state.workspaceDir,
150
+ budget: state.budget,
151
+ ...state.verifyCmd ? { verifyCmd: state.verifyCmd } : {},
152
+ ...state.workerModel ? { workerModel: state.workerModel } : {},
153
+ ...state.driverModel ? { driverModel: state.driverModel } : {},
154
+ ...state.verdict ? { verdict: state.verdict } : {},
155
+ ...state.progress ? { progress: state.progress } : {},
156
+ ...state.startedAt ? { startedAt: state.startedAt } : {},
157
+ ...state.completedAt ? { completedAt: state.completedAt } : {},
158
+ ...state.maxSandboxes !== void 0 ? { maxSandboxes: state.maxSandboxes } : {},
159
+ ...state.maxLifetimeSeconds !== void 0 ? { maxLifetimeSeconds: state.maxLifetimeSeconds } : {},
160
+ ...state.idleTimeoutSeconds !== void 0 ? { idleTimeoutSeconds: state.idleTimeoutSeconds } : {},
161
+ ...state.maxUsd !== void 0 ? { maxUsd: state.maxUsd } : {},
162
+ ...state.maxDepth !== void 0 ? { maxDepth: state.maxDepth } : {},
163
+ ...state.result?.spentUsd !== void 0 ? { resultSpentUsd: state.result.spentUsd } : {},
164
+ ...state.result?.spentTokens !== void 0 ? { resultSpentTokens: state.result.spentTokens } : {},
165
+ workers: workerViews,
166
+ progressTail: [...progressTail],
167
+ journalTail: events.slice(-12),
168
+ driverSpend,
169
+ totals
170
+ };
171
+ }
172
+ /** Render one snapshot to an ANSI frame. Use this when nothing needs to be clickable. */
173
+ function renderTopFrame(snapshot, options = {}) {
174
+ return renderTopFrameWithLayout(snapshot, options).frame;
175
+ }
176
+ /**
177
+ * Render one snapshot, returning the frame together with the row→entity map a mouse click resolves
178
+ * against. The layout is the only thing that knows which row is which run or worker, so emitting it
179
+ * alongside the text is what keeps click handling out of the renderer.
180
+ */
181
+ function renderTopFrameWithLayout(snapshot, options = {}) {
182
+ const width = Math.max(88, options.width ?? 128);
183
+ const height = Math.max(24, options.height ?? 42);
184
+ const color = options.color ?? false;
185
+ const focus = options.focus ?? "supervisors";
186
+ const mode = options.mode ?? "overview";
187
+ const selectedSupervisor = selectSupervisor(snapshot, options.selectedSupervisorId);
188
+ const selectedWorker = selectedSupervisor ? selectWorker(selectedSupervisor, options.selectedWorkerId) : void 0;
189
+ const targets = [];
190
+ const lines = [];
191
+ const push = (line = "", target) => {
192
+ if (lines.length >= height) return;
193
+ if (target) targets.push({
194
+ row: lines.length,
195
+ ...target
196
+ });
197
+ lines.push(fitAnsi(line, width));
198
+ };
199
+ const aggregate = aggregateSnapshot(snapshot);
200
+ push(`${paint("SUPERVISOR TOP", "bold", color)} ${paint(snapshot.root, "dim", color)} runs ${aggregate.supervisors} workers ${aggregate.workers} live ${aggregate.running} done ${aggregate.done} down ${aggregate.down} ${formatMoney(aggregate.usd)} tok ${formatTokens(aggregate.tokensInput)}/${formatTokens(aggregate.tokensOutput)}`);
201
+ push(`${paint("keys", "dim", color)} up/down select left/right run tab focus enter detail o overview l log s steer a shell c cancel mouse q quit`);
202
+ if (options.notice) push(paint(options.notice, "yellow", color));
203
+ if (options.steerInput?.active) push(renderSteerInput(options.steerInput, width, color));
204
+ push(rule(width, color));
205
+ if (snapshot.supervisors.length === 0) {
206
+ push(paint("NO SUPERVISORS", "yellow", color));
207
+ push(`No run state under ${supervisorRunsRoot(snapshot.root)}`);
208
+ push(`A run appears here once its driver writes state.json; re-point with: agent-runtime-top <root>`);
209
+ return {
210
+ frame: `${lines.join("\n")}\n`,
211
+ targets
212
+ };
213
+ }
214
+ push(sectionTitle("SUPERVISORS", focus === "supervisors", color));
215
+ push(` id status workers spend tok in/out latency task`);
216
+ for (const supervisor of snapshot.supervisors.slice(0, 9)) {
217
+ const selected = supervisor.id === selectedSupervisor?.id;
218
+ const status = statusText(supervisor.status, color);
219
+ const pointer = selected ? paint(">", "cyan", color) : " ";
220
+ const rowFocus = selected && focus === "supervisors" ? "inverse" : void 0;
221
+ const workerSummary = `${supervisor.totals.running}/${supervisor.totals.workers} live`;
222
+ const task = compact(supervisor.task, 34);
223
+ const line = `${pointer} ${pad(supervisor.id, 21)} ${pad(status, 12)} ${pad(workerSummary, 13)} ${pad(formatMoney(supervisor.totals.usd), 12)} ${pad(`${formatTokens(supervisor.totals.tokensInput)}/${formatTokens(supervisor.totals.tokensOutput)}`, 16)} ${pad(formatDuration(supervisor.totals.latencyMs), 12)} ${task}`;
224
+ push(rowFocus ? paint(line, rowFocus, color) : line, {
225
+ kind: "supervisor",
226
+ id: supervisor.id
227
+ });
228
+ }
229
+ if (!selectedSupervisor) return {
230
+ frame: `${lines.join("\n")}\n`,
231
+ targets
232
+ };
233
+ push();
234
+ push(sectionTitle(`WORKERS ${selectedSupervisor.id}`, focus === "workers", color));
235
+ push(` id state runtime latency cost tok in/out score detail`);
236
+ for (const worker of selectedSupervisor.workers.slice(0, 12)) {
237
+ const selected = worker.id === selectedWorker?.id;
238
+ const pointer = selected ? paint(">", "cyan", color) : " ";
239
+ const rowFocus = selected && focus === "workers" ? "inverse" : void 0;
240
+ const score = worker.verdict ?? (worker.status === "done" ? "done" : worker.status);
241
+ const detail = worker.reason ?? worker.outRef ?? worker.parent ?? "";
242
+ const spend = totalSpend(worker);
243
+ const line = `${pointer} ${pad(worker.label, 12)} ${pad(statusText(worker.status, color), 12)} ${pad(worker.runtime ?? "-", 10)} ${pad(formatDuration(worker.latencyMs), 12)} ${pad(formatMoney(spend.usd), 11)} ${pad(`${formatTokens(spend.tokensInput)}/${formatTokens(spend.tokensOutput)}`, 16)} ${pad(compact(score, 9), 10)} ${compact(detail, 30)}`;
244
+ push(rowFocus ? paint(line, rowFocus, color) : line, {
245
+ kind: "worker",
246
+ id: worker.id,
247
+ supervisorId: selectedSupervisor.id
248
+ });
249
+ }
250
+ if (selectedSupervisor.workers.length === 0) push(" no workers spawned yet");
251
+ push();
252
+ push(sectionTitle("OBSERVABILITY", false, color));
253
+ for (const line of renderStatusBars(selectedSupervisor, width, color)) push(line);
254
+ push();
255
+ for (const line of renderCostBars(selectedSupervisor, width, color)) push(line);
256
+ push();
257
+ for (const line of renderTokenBars(selectedSupervisor, width, color)) push(line);
258
+ push();
259
+ for (const line of renderLatencyHistogram(selectedSupervisor, width, color)) push(line);
260
+ push();
261
+ if (mode === "log") {
262
+ push(sectionTitle("JOURNAL TAIL", false, color));
263
+ for (const line of renderJournalTail(selectedSupervisor, width, color)) push(line);
264
+ } else {
265
+ push(sectionTitle(mode === "detail" ? "DETAIL" : "RUN SUMMARY", false, color));
266
+ for (const line of renderDetail(selectedSupervisor, selectedWorker, mode, width, color)) push(line);
267
+ }
268
+ return {
269
+ frame: `${lines.join("\n")}\n`,
270
+ targets
271
+ };
272
+ }
273
+ function ensureWorker(workers, id, label) {
274
+ const existing = workers.get(id);
275
+ if (existing) return existing;
276
+ const created = {
277
+ id,
278
+ label,
279
+ status: "running",
280
+ spend: emptySpend,
281
+ metered: emptySpend,
282
+ liveTail: []
283
+ };
284
+ workers.set(id, created);
285
+ return created;
286
+ }
287
+ function finalizeWorker(worker, now) {
288
+ const startMs = parseTime(worker.startedAt);
289
+ const endMs = parseTime(worker.endedAt) ?? (worker.status === "running" ? now : startMs);
290
+ const latencyMs = startMs !== void 0 && endMs !== void 0 ? Math.max(0, endMs - startMs) : 0;
291
+ return {
292
+ id: worker.id,
293
+ label: worker.label,
294
+ ...worker.cwd ? { cwd: worker.cwd } : {},
295
+ ...worker.eventFile ? { eventFile: worker.eventFile } : {},
296
+ ...worker.parent ? { parent: worker.parent } : {},
297
+ ...worker.runtime ? { runtime: worker.runtime } : {},
298
+ status: worker.status,
299
+ ...worker.verdict ? { verdict: worker.verdict } : {},
300
+ ...worker.infra !== void 0 ? { infra: worker.infra } : {},
301
+ ...worker.startedAt ? { startedAt: worker.startedAt } : {},
302
+ ...worker.endedAt ? { endedAt: worker.endedAt } : {},
303
+ latencyMs,
304
+ ...worker.budget ? { budget: worker.budget } : {},
305
+ spend: worker.spend,
306
+ metered: worker.metered,
307
+ liveTail: worker.liveTail,
308
+ ...worker.outRef ? { outRef: worker.outRef } : {},
309
+ ...worker.reason ? { reason: worker.reason } : {}
310
+ };
311
+ }
312
+ function supervisorTotals(state, workers, driverSpend, now) {
313
+ const workerSpends = workers.map(totalSpend);
314
+ const tokenInput = sum(workerSpends.map((spend) => spend.tokensInput)) + driverSpend.tokensInput;
315
+ const tokenOutput = sum(workerSpends.map((spend) => spend.tokensOutput)) + driverSpend.tokensOutput;
316
+ const computedUsd = sum(workerSpends.map((spend) => spend.usd)) + driverSpend.usd;
317
+ const usd = computedUsd > 0 ? computedUsd : state.result?.spentUsd ?? 0;
318
+ const started = parseTime(state.startedAt);
319
+ const completed = parseTime(state.completedAt);
320
+ const latencyMs = started !== void 0 ? Math.max(0, (completed ?? now) - started) : 0;
321
+ const latencies = workers.map((worker) => worker.latencyMs).filter((ms) => ms > 0);
322
+ return {
323
+ workers: workers.length,
324
+ running: workers.filter((worker) => worker.status === "running").length,
325
+ done: workers.filter((worker) => worker.status === "done").length,
326
+ down: workers.filter((worker) => worker.status === "down").length,
327
+ cancelled: workers.filter((worker) => worker.status === "cancelled").length,
328
+ inFlight: workers.filter((worker) => worker.status === "running").length,
329
+ settled: workers.filter((worker) => worker.status !== "running").length,
330
+ tokensInput: tokenInput,
331
+ tokensOutput: tokenOutput,
332
+ tokensTotal: tokenInput + tokenOutput,
333
+ usd,
334
+ latencyMs,
335
+ workerLatency: distribution(latencies)
336
+ };
337
+ }
338
+ function aggregateSnapshot(snapshot) {
339
+ return {
340
+ supervisors: snapshot.supervisors.length,
341
+ workers: sum(snapshot.supervisors.map((supervisor) => supervisor.totals.workers)),
342
+ running: sum(snapshot.supervisors.map((supervisor) => supervisor.totals.running)),
343
+ done: sum(snapshot.supervisors.map((supervisor) => supervisor.totals.done)),
344
+ down: sum(snapshot.supervisors.map((supervisor) => supervisor.totals.down)),
345
+ usd: sum(snapshot.supervisors.map((supervisor) => supervisor.totals.usd)),
346
+ tokensInput: sum(snapshot.supervisors.map((supervisor) => supervisor.totals.tokensInput)),
347
+ tokensOutput: sum(snapshot.supervisors.map((supervisor) => supervisor.totals.tokensOutput))
348
+ };
349
+ }
350
+ function compareWorker(a, b) {
351
+ const aStart = parseTime(a.startedAt) ?? 0;
352
+ const bStart = parseTime(b.startedAt) ?? 0;
353
+ if (a.status === "running" && b.status !== "running") return -1;
354
+ if (b.status === "running" && a.status !== "running") return 1;
355
+ return aStart - bStart || a.label.localeCompare(b.label);
356
+ }
357
+ function totalSpend(worker) {
358
+ return addSpend(worker.spend, worker.metered);
359
+ }
360
+ function addSpend(a, b) {
361
+ return {
362
+ iterations: a.iterations + b.iterations,
363
+ tokensInput: a.tokensInput + b.tokensInput,
364
+ tokensOutput: a.tokensOutput + b.tokensOutput,
365
+ usd: a.usd + b.usd,
366
+ ms: a.ms + b.ms
367
+ };
368
+ }
369
+ function parseSpend(value) {
370
+ if (!isRecord(value)) return emptySpend;
371
+ const tokens = isRecord(value.tokens) ? value.tokens : {};
372
+ return {
373
+ iterations: numberValue(value.iterations),
374
+ tokensInput: numberValue(tokens.input),
375
+ tokensOutput: numberValue(tokens.output),
376
+ usd: numberValue(value.usd),
377
+ ms: numberValue(value.ms)
378
+ };
379
+ }
380
+ function parseBudget(value) {
381
+ if (!isRecord(value)) return void 0;
382
+ const budget = {
383
+ ...typeof value.maxIterations === "number" ? { maxIterations: value.maxIterations } : {},
384
+ ...typeof value.maxTokens === "number" ? { maxTokens: value.maxTokens } : {},
385
+ ...typeof value.maxUsd === "number" ? { maxUsd: value.maxUsd } : {}
386
+ };
387
+ return Object.keys(budget).length ? budget : void 0;
388
+ }
389
+ function describeVerdict(value) {
390
+ if (!isRecord(value)) return void 0;
391
+ if (typeof value.valid === "boolean") return value.valid ? "valid" : "invalid";
392
+ if (typeof value.score === "number") return `score ${value.score.toFixed(2)}`;
393
+ }
394
+ function distribution(values) {
395
+ if (values.length === 0) return {
396
+ n: 0,
397
+ min: 0,
398
+ median: 0,
399
+ p90: 0,
400
+ max: 0
401
+ };
402
+ const sorted = [...values].sort((a, b) => a - b);
403
+ return {
404
+ n: sorted.length,
405
+ min: sorted[0] ?? 0,
406
+ median: percentile(sorted, .5),
407
+ p90: percentile(sorted, .9),
408
+ max: sorted[sorted.length - 1] ?? 0
409
+ };
410
+ }
411
+ function percentile(sorted, p) {
412
+ if (sorted.length === 0) return 0;
413
+ return sorted[Math.min(sorted.length - 1, Math.max(0, Math.ceil(sorted.length * p) - 1))] ?? 0;
414
+ }
415
+ function renderStatusBars(supervisor, width, color) {
416
+ const total = Math.max(1, supervisor.totals.workers);
417
+ const barWidth = Math.max(12, Math.min(28, width - 58));
418
+ return [`status ${pad("running", 9)} ${bar(supervisor.totals.running, total, barWidth, "#", color, "yellow")} ${supervisor.totals.running} ${pad("done", 5)} ${bar(supervisor.totals.done, total, barWidth, "#", color, "green")} ${supervisor.totals.done} ${pad("down", 5)} ${bar(supervisor.totals.down, total, barWidth, "#", color, "red")} ${supervisor.totals.down}`];
419
+ }
420
+ function renderCostBars(supervisor, width, color) {
421
+ const workers = supervisor.workers.map((worker) => ({
422
+ worker,
423
+ spend: totalSpend(worker)
424
+ })).sort((a, b) => b.spend.usd - a.spend.usd).slice(0, 6);
425
+ const maxUsd = Math.max(1e-6, ...workers.map((row) => row.spend.usd), supervisor.driverSpend.usd);
426
+ const out = [`cost total ${formatMoney(supervisor.totals.usd)} driver ${formatMoney(supervisor.driverSpend.usd)}`];
427
+ const barWidth = Math.max(18, Math.min(46, width - 38));
428
+ for (const row of workers) out.push(` ${pad(row.worker.label, 12)} ${bar(row.spend.usd, maxUsd, barWidth, "$", color, "magenta")} ${formatMoney(row.spend.usd)}`);
429
+ if (workers.length === 0) out.push(" no worker cost yet");
430
+ return out;
431
+ }
432
+ function renderTokenBars(supervisor, width, color) {
433
+ const rows = supervisor.workers.map((worker) => ({
434
+ worker,
435
+ spend: totalSpend(worker),
436
+ total: totalSpend(worker).tokensInput + totalSpend(worker).tokensOutput
437
+ })).sort((a, b) => b.total - a.total).slice(0, 6);
438
+ const maxTokens = Math.max(1, ...rows.map((row) => row.total), supervisor.driverSpend.tokensInput + supervisor.driverSpend.tokensOutput);
439
+ const out = [`tokens total in ${formatTokens(supervisor.totals.tokensInput)} / out ${formatTokens(supervisor.totals.tokensOutput)} driver ${formatTokens(supervisor.driverSpend.tokensInput)}/${formatTokens(supervisor.driverSpend.tokensOutput)}`];
440
+ const barWidth = Math.max(18, Math.min(46, width - 44));
441
+ for (const row of rows) out.push(` ${pad(row.worker.label, 12)} ${stackedTokenBar(row.spend.tokensInput, row.spend.tokensOutput, maxTokens, barWidth, color)} ${formatTokens(row.spend.tokensInput)}/${formatTokens(row.spend.tokensOutput)}`);
442
+ if (rows.length === 0) out.push(" no worker tokens yet");
443
+ return out;
444
+ }
445
+ function renderLatencyHistogram(supervisor, width, color) {
446
+ const values = supervisor.workers.map((worker) => worker.latencyMs).filter((value) => value > 0);
447
+ const summary = supervisor.totals.workerLatency;
448
+ const out = [`latency n=${summary.n} min ${formatDuration(summary.min)} p50 ${formatDuration(summary.median)} p90 ${formatDuration(summary.p90)} max ${formatDuration(summary.max)}`];
449
+ if (values.length === 0) {
450
+ out.push(" no latency samples yet");
451
+ return out;
452
+ }
453
+ const bins = histogram(values, 6);
454
+ const maxCount = Math.max(1, ...bins.map((bin) => bin.count));
455
+ const barWidth = Math.max(18, Math.min(48, width - 34));
456
+ for (const bin of bins) out.push(` ${pad(`${formatDuration(bin.min)}-${formatDuration(bin.max)}`, 15)} ${bar(bin.count, maxCount, barWidth, "#", color, "blue")} ${bin.count}`);
457
+ return out;
458
+ }
459
+ function renderDetail(supervisor, worker, mode, width, color) {
460
+ if (mode === "detail" && worker) {
461
+ const spend = totalSpend(worker);
462
+ return [
463
+ `worker ${paint(worker.label, "bold", color)} (${worker.id}) ${statusText(worker.status, color)} latency ${formatDuration(worker.latencyMs)}`,
464
+ `tokens in/out ${formatTokens(spend.tokensInput)}/${formatTokens(spend.tokensOutput)} cost ${formatMoney(spend.usd)} iterations ${spend.iterations}`,
465
+ `parent ${worker.parent ?? "-"} runtime ${worker.runtime ?? "-"} out ${worker.outRef ?? "-"}${worker.infra ? " infra" : ""}`,
466
+ `cwd ${compact(worker.cwd ?? "-", width - 5)}`,
467
+ `events ${compact(worker.eventFile ?? "-", width - 8)}`,
468
+ ...worker.reason ? [`reason ${compact(worker.reason, width - 7)}`] : [],
469
+ ...renderWorkerTail(worker, width, color)
470
+ ];
471
+ }
472
+ const caps = [
473
+ supervisor.maxSandboxes !== void 0 ? `boxes ${supervisor.maxSandboxes}` : void 0,
474
+ supervisor.maxUsd !== void 0 ? `max ${formatMoney(supervisor.maxUsd)}` : void 0,
475
+ supervisor.maxDepth !== void 0 ? `depth ${supervisor.maxDepth}` : void 0,
476
+ supervisor.maxLifetimeSeconds !== void 0 ? `ttl ${supervisor.maxLifetimeSeconds}s` : void 0
477
+ ].filter((value) => value !== void 0);
478
+ return [
479
+ `task ${compact(supervisor.task, width - 7)}`,
480
+ `workspace ${compact(supervisor.workspaceDir, width - 12)}`,
481
+ `verify ${compact(supervisor.verifyCmd ?? "-", width - 9)}`,
482
+ `models worker ${supervisor.workerModel ?? "-"} / driver ${supervisor.driverModel ?? "-"} caps ${caps.join(" | ") || "-"}`,
483
+ `progress ${compact(supervisor.progress ?? supervisor.verdict ?? supervisor.status, width - 10)}`
484
+ ];
485
+ }
486
+ function renderJournalTail(supervisor, width, color) {
487
+ if (supervisor.journalTail.length === 0) return [" no journal events yet"];
488
+ return supervisor.journalTail.map((event) => {
489
+ const at = event.at ? formatClock(event.at) : "--:--:--";
490
+ if (event.kind === "spawned") return ` ${at} ${paint("spawn", "yellow", color)} ${pad(event.label ?? event.id, 16)} ${event.id}`;
491
+ if (event.kind === "settled") {
492
+ const spend = parseSpend(event.spent);
493
+ return ` ${at} ${statusText(event.status ?? "settled", color)} ${pad(event.id, 18)} ${formatMoney(spend.usd)} ${formatTokens(spend.tokensInput)}/${formatTokens(spend.tokensOutput)}`;
494
+ }
495
+ if (event.kind === "metered") {
496
+ const spend = parseSpend(event.spend);
497
+ return ` ${at} ${paint("meter", "cyan", color)} ${pad(event.id, 18)} ${formatMoney(spend.usd)} ${formatTokens(spend.tokensInput)}/${formatTokens(spend.tokensOutput)}`;
498
+ }
499
+ return ` ${at} ${paint("cancel", "red", color)} ${pad(event.id, 18)} ${compact(event.reason ?? "", width - 32)}`;
500
+ });
501
+ }
502
+ function renderWorkerTail(worker, width, color) {
503
+ if (worker.liveTail.length === 0) return ["live events: none recorded yet"];
504
+ return ["live events:", ...worker.liveTail.slice(-5).map((line) => ` ${paint(compact(formatWorkerEvent(line), width - 2), "dim", color)}`)];
505
+ }
506
+ function renderSteerInput(input, width, color) {
507
+ const target = input.workerLabel ?? "-";
508
+ const value = compact(input.value, Math.max(10, width - target.length - 14));
509
+ return `${paint("steer", "cyan", color)} ${target} > ${value}${paint("_", "inverse", color)}`;
510
+ }
511
+ function formatWorkerEvent(line) {
512
+ try {
513
+ const parsed = JSON.parse(line);
514
+ const at = typeof parsed.at === "string" ? formatClock(parsed.at) : "--:--:--";
515
+ const kind = typeof parsed.kind === "string" ? parsed.kind : "event";
516
+ if (kind === "progress") return `${at} progress iter ${numberValue(parsed.iteration)} ${String(parsed.phase ?? "")}`;
517
+ if (kind === "started") return `${at} started ${String(parsed.cwd ?? "")}`;
518
+ if (kind === "message") {
519
+ const state = parsed.queued === true ? "queued" : "sent";
520
+ const source = typeof parsed.source === "string" && parsed.source.length > 0 ? `${parsed.source} ` : "";
521
+ const message = typeof parsed.message === "string" && parsed.message.length > 0 ? ` ${parsed.message}` : "";
522
+ return `${at} ${source}steer ${state} delivered=${String(parsed.delivered)}${message}`;
523
+ }
524
+ if (kind === "verify") return `${at} verify passed=${String(parsed.passed)} exit=${String(parsed.exitCode ?? "-")}${evidenceSnippet(parsed.outputTail, 160)}`;
525
+ if (kind === "finished") {
526
+ const branch = typeof parsed.branch === "string" ? ` branch=${parsed.branch}` : "";
527
+ const files = typeof parsed.filesChanged === "number" ? ` files=${parsed.filesChanged}` : "";
528
+ const tests = typeof parsed.testPassed === "boolean" ? ` tests=${String(parsed.testPassed)}` : "";
529
+ return `${at} finished passed=${String(parsed.passed)} patchBytes=${numberValue(parsed.patchBytes)}${files}${tests}${branch}${evidenceSnippet(parsed.evidence, 220)}`;
530
+ }
531
+ if (kind === "error") return `${at} error ${String(parsed.error ?? "")}`;
532
+ return `${at} ${kind}`;
533
+ } catch {
534
+ return line;
535
+ }
536
+ }
537
+ /** One-line the settle evidence / verify tail a worker event carries so the
538
+ * failure is legible in the tail view (the full block stays in the ndjson). */
539
+ function evidenceSnippet(value, max) {
540
+ if (typeof value !== "string") return "";
541
+ const oneLine = value.replace(/\s+/g, " ").trim();
542
+ return oneLine ? ` :: ${compact(oneLine, max)}` : "";
543
+ }
544
+ function histogram(values, count) {
545
+ const min = Math.min(...values);
546
+ const max = Math.max(...values);
547
+ if (min === max) return [{
548
+ min,
549
+ max,
550
+ count: values.length
551
+ }];
552
+ const step = (max - min) / count;
553
+ return Array.from({ length: count }, (_, index) => {
554
+ const low = min + step * index;
555
+ const high = index === count - 1 ? max : min + step * (index + 1);
556
+ return {
557
+ min: low,
558
+ max: high,
559
+ count: values.filter((value) => value >= low && (index === count - 1 ? value <= high : value < high)).length
560
+ };
561
+ }).filter((bin) => bin.count > 0);
562
+ }
563
+ function bar(value, max, width, ch, color, colorName) {
564
+ const filled = Math.max(0, Math.min(width, Math.round(value / Math.max(max, 1e-6) * width)));
565
+ return paint(ch.repeat(filled).padEnd(width, "."), colorName, color);
566
+ }
567
+ function stackedTokenBar(input, output, max, width, color) {
568
+ const inputWidth = Math.round(input / Math.max(max, 1) * width);
569
+ const outputWidth = Math.round(output / Math.max(max, 1) * width);
570
+ const clippedInput = Math.min(width, inputWidth);
571
+ const clippedOutput = Math.min(width - clippedInput, outputWidth);
572
+ const rest = Math.max(0, width - clippedInput - clippedOutput);
573
+ return `${paint("#".repeat(clippedInput), "cyan", color)}${paint("+".repeat(clippedOutput), "green", color)}${".".repeat(rest)}`;
574
+ }
575
+ function sectionTitle(title, focused, color) {
576
+ return paint(focused ? `[${title}]` : title, focused ? "cyan" : "bold", color);
577
+ }
578
+ function statusText(status, color) {
579
+ if ([
580
+ "completed",
581
+ "done",
582
+ "delivered",
583
+ "valid"
584
+ ].includes(status)) return paint(status.toUpperCase(), "green", color);
585
+ if ([
586
+ "failed",
587
+ "down",
588
+ "cancelled",
589
+ "invalid",
590
+ "error"
591
+ ].includes(status)) return paint(status.toUpperCase(), "red", color);
592
+ if ([
593
+ "running",
594
+ "spawned",
595
+ "settled"
596
+ ].includes(status)) return paint(status.toUpperCase(), "yellow", color);
597
+ return paint(status.toUpperCase(), "white", color);
598
+ }
599
+ function selectSupervisor(snapshot, requested) {
600
+ if (requested) {
601
+ const found = snapshot.supervisors.find((supervisor) => supervisor.id === requested);
602
+ if (found) return found;
603
+ }
604
+ return snapshot.supervisors[0];
605
+ }
606
+ function selectWorker(supervisor, requested) {
607
+ if (requested) {
608
+ const found = supervisor.workers.find((worker) => worker.id === requested);
609
+ if (found) return found;
610
+ }
611
+ return supervisor.workers[0];
612
+ }
613
+ function readTail(file, maxLines) {
614
+ if (!existsSync(file)) return [];
615
+ try {
616
+ return readFileSync(file, "utf8").trim().split("\n").filter(Boolean).slice(-maxLines);
617
+ } catch {
618
+ return [];
619
+ }
620
+ }
621
+ function readWorkerEventTails(dir, maxLines) {
622
+ const out = /* @__PURE__ */ new Map();
623
+ if (!existsSync(dir)) return out;
624
+ for (const entry of readdirSync(dir)) {
625
+ if (!entry.endsWith(".ndjson") || entry.endsWith(".inbox.ndjson")) continue;
626
+ const label = entry.slice(0, -7);
627
+ const file = join(dir, entry);
628
+ const lines = readTail(file, maxLines);
629
+ const cwd = workerCwd(lines);
630
+ out.set(label, {
631
+ file,
632
+ lines,
633
+ ...cwd ? { cwd } : {}
634
+ });
635
+ }
636
+ return out;
637
+ }
638
+ function workerCwd(lines) {
639
+ for (const line of [...lines].reverse()) try {
640
+ const parsed = JSON.parse(line);
641
+ if (parsed.kind === "started" && typeof parsed.cwd === "string" && parsed.cwd.trim()) return parsed.cwd;
642
+ } catch {}
643
+ }
644
+ function readJson(file) {
645
+ try {
646
+ return JSON.parse(readFileSync(file, "utf8"));
647
+ } catch {
648
+ return;
649
+ }
650
+ }
651
+ function isSupervisorState(value) {
652
+ return value !== void 0 && typeof value.id === "string" && typeof value.status === "string" && typeof value.task === "string" && typeof value.workspaceDir === "string" && typeof value.budget === "number";
653
+ }
654
+ function isSpawnEvent(value) {
655
+ return value.kind === "spawned" && typeof value.id === "string" || value.kind === "settled" && typeof value.id === "string" || value.kind === "cancelled" && typeof value.id === "string" || value.kind === "metered" && typeof value.id === "string";
656
+ }
657
+ function isRecord(value) {
658
+ return typeof value === "object" && value !== null;
659
+ }
660
+ function isDirectory(path) {
661
+ try {
662
+ return statSync(path).isDirectory();
663
+ } catch {
664
+ return false;
665
+ }
666
+ }
667
+ function numberValue(value) {
668
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
669
+ }
670
+ function parseTime(value) {
671
+ if (!value) return void 0;
672
+ const parsed = Date.parse(value);
673
+ return Number.isFinite(parsed) ? parsed : void 0;
674
+ }
675
+ function sum(values) {
676
+ return values.reduce((total, value) => total + value, 0);
677
+ }
678
+ function formatClock(value) {
679
+ const parsed = parseTime(value);
680
+ if (parsed === void 0) return "--:--:--";
681
+ return new Date(parsed).toISOString().slice(11, 19);
682
+ }
683
+ function formatMoney(value) {
684
+ if (value === 0) return "$0";
685
+ if (Math.abs(value) < .01) return `$${value.toFixed(4)}`;
686
+ return `$${value.toFixed(2)}`;
687
+ }
688
+ function formatTokens(value) {
689
+ if (value >= 1e6) return `${(value / 1e6).toFixed(1)}m`;
690
+ if (value >= 1e3) return `${(value / 1e3).toFixed(1)}k`;
691
+ return `${Math.round(value)}`;
692
+ }
693
+ function formatDuration(ms) {
694
+ if (!Number.isFinite(ms) || ms <= 0) return "0s";
695
+ if (ms < 1e3) return `${Math.round(ms)}ms`;
696
+ const seconds = ms / 1e3;
697
+ if (seconds < 60) return `${seconds.toFixed(seconds < 10 ? 1 : 0)}s`;
698
+ const minutes = seconds / 60;
699
+ if (minutes < 60) return `${minutes.toFixed(minutes < 10 ? 1 : 0)}m`;
700
+ return `${(minutes / 60).toFixed(1)}h`;
701
+ }
702
+ function compact(value, width) {
703
+ if (width <= 1) return "";
704
+ if (value.length <= width) return value;
705
+ return `${value.slice(0, width - 1)}~`;
706
+ }
707
+ function pad(value, width) {
708
+ const visible = visibleLength(value);
709
+ if (visible >= width) return fitAnsi(value, width);
710
+ return `${value}${" ".repeat(width - visible)}`;
711
+ }
712
+ function rule(width, color) {
713
+ return paint("-".repeat(width), "dim", color);
714
+ }
715
+ function paint(value, colorName, enabled) {
716
+ if (!enabled || !colorName) return value;
717
+ return `${colors[colorName]}${value}${colors.reset}`;
718
+ }
719
+ function visibleLength(value) {
720
+ return stripAnsi(value).length;
721
+ }
722
+ function stripAnsi(value) {
723
+ return value.replace(ansiPattern, "");
724
+ }
725
+ function fitAnsi(value, width) {
726
+ if (visibleLength(value) <= width) return value;
727
+ let out = "";
728
+ let visible = 0;
729
+ for (let i = 0; i < value.length; i += 1) {
730
+ const ch = value[i];
731
+ if (ch === "\x1B") {
732
+ const rest = value.slice(i);
733
+ const match = /^\x1b\[[0-9;?]*[ -/]*[@-~]/.exec(rest);
734
+ if (match) {
735
+ out += match[0];
736
+ i += match[0].length - 1;
737
+ continue;
738
+ }
739
+ }
740
+ if (visible >= width - 1) {
741
+ out += "~";
742
+ break;
743
+ }
744
+ out += ch;
745
+ visible += 1;
746
+ }
747
+ if (out.includes("\x1B[") && !out.endsWith(colors.reset)) out += colors.reset;
748
+ return out;
749
+ }
750
+ //#endregion
751
+ //#region src/tui/top-app.ts
752
+ /**
753
+ * The interactive side of the supervisor-run TUI: a keypress/mouse loop over the frames
754
+ * `./top-model` renders, plus the two operator controls that write back to the run — steer a live
755
+ * worker, and request cancellation of a run.
756
+ *
757
+ * Both controls go through the owned run layout: steers via `writeWorkerSteer` (the durable inbox
758
+ * append), cancellation via a file inside the run directory the snapshot reported. Nothing here
759
+ * joins a path from the workspace root.
760
+ *
761
+ * Extracted from the `loops` repo (`src/top.ts`). The module-level singletons are the origin's
762
+ * shape and are kept: this drives one terminal, and `runTopApp` is its one entry point.
763
+ *
764
+ * @experimental
765
+ */
766
+ let state = initialState([], process.cwd());
767
+ let snapshot = {
768
+ root: state.root,
769
+ generatedAt: 0,
770
+ supervisors: []
771
+ };
772
+ let targets = [];
773
+ let timer;
774
+ /**
775
+ * Render exactly one frame and return it. This is the non-interactive path — `--once`, a pipe, a
776
+ * test — so it never touches raw mode, the alternate screen, or `process.exit`.
777
+ */
778
+ function renderTopOnce(options = {}) {
779
+ state = initialState([...options.argv ?? process.argv.slice(2)], options.cwd ?? process.cwd());
780
+ snapshot = loadTopSnapshot(state.root);
781
+ clampSelection();
782
+ return renderTopFrameWithLayout(snapshot, {
783
+ color: state.color,
784
+ width: process.stdout.columns ?? 128,
785
+ focus: state.focus,
786
+ mode: state.mode,
787
+ ...state.selectedSupervisorId ? { selectedSupervisorId: state.selectedSupervisorId } : {},
788
+ ...state.selectedWorkerId ? { selectedWorkerId: state.selectedWorkerId } : {},
789
+ steerInput: renderSteerInputState()
790
+ }).frame;
791
+ }
792
+ /**
793
+ * Run the TUI. With a TTY on both ends and no `--once` this takes over the terminal until `q`;
794
+ * otherwise it writes a single frame to stdout and returns.
795
+ */
796
+ function runTopApp(options = {}) {
797
+ const args = [...options.argv ?? process.argv.slice(2)];
798
+ if (!process.stdout.isTTY || !process.stdin.isTTY || args.includes("--once")) {
799
+ process.stdout.write(renderTopOnce(options));
800
+ return;
801
+ }
802
+ state = initialState(args, options.cwd ?? process.cwd());
803
+ snapshot = loadTopSnapshot(state.root);
804
+ start();
805
+ }
806
+ function initialState(args, cwd) {
807
+ return {
808
+ root: resolve(args.find((arg) => !arg.startsWith("-")) ?? cwd),
809
+ color: Boolean(process.stdout.isTTY) && !args.includes("--no-color"),
810
+ focus: args.includes("--detail") ? "workers" : "supervisors",
811
+ mode: args.includes("--log") ? "log" : args.includes("--detail") ? "detail" : "overview",
812
+ selectedSupervisorId: void 0,
813
+ selectedWorkerId: void 0,
814
+ notice: void 0,
815
+ steerInput: {
816
+ active: false,
817
+ value: ""
818
+ }
819
+ };
820
+ }
821
+ function start() {
822
+ process.stdout.write("\x1B[?1049h\x1B[?25l\x1B[?1000h\x1B[?1006h");
823
+ emitKeypressEvents(process.stdin);
824
+ process.stdin.setRawMode(true);
825
+ process.stdin.resume();
826
+ process.stdin.on("keypress", onKey);
827
+ process.stdin.on("data", onData);
828
+ process.on("SIGINT", stop);
829
+ draw();
830
+ timer = setInterval(draw, 1e3);
831
+ }
832
+ function stop() {
833
+ if (timer) clearInterval(timer);
834
+ process.stdin.setRawMode(false);
835
+ process.stdout.write("\x1B[?1000l\x1B[?1006l\x1B[?25h\x1B[?1049l");
836
+ process.exit(0);
837
+ }
838
+ function pauseUi() {
839
+ if (timer) {
840
+ clearInterval(timer);
841
+ timer = void 0;
842
+ }
843
+ process.stdin.setRawMode(false);
844
+ process.stdin.pause();
845
+ process.stdout.write("\x1B[?1000l\x1B[?1006l\x1B[?25h\x1B[?1049l");
846
+ }
847
+ function resumeUi() {
848
+ process.stdout.write("\x1B[?1049h\x1B[?25l\x1B[?1000h\x1B[?1006h");
849
+ process.stdin.setRawMode(true);
850
+ process.stdin.resume();
851
+ draw();
852
+ timer = setInterval(draw, 1e3);
853
+ }
854
+ function draw() {
855
+ snapshot = loadTopSnapshot(state.root);
856
+ clampSelection();
857
+ const rendered = renderTopFrameWithLayout(snapshot, {
858
+ width: process.stdout.columns ?? 128,
859
+ height: process.stdout.rows ?? 42,
860
+ color: state.color,
861
+ focus: state.focus,
862
+ mode: state.mode,
863
+ ...state.notice ? { notice: state.notice } : {},
864
+ ...state.selectedSupervisorId ? { selectedSupervisorId: state.selectedSupervisorId } : {},
865
+ ...state.selectedWorkerId ? { selectedWorkerId: state.selectedWorkerId } : {},
866
+ steerInput: renderSteerInputState()
867
+ });
868
+ targets = rendered.targets;
869
+ process.stdout.write(`\x1b[2J\x1b[H${rendered.frame}`);
870
+ }
871
+ function onKey(str, key) {
872
+ state.notice = void 0;
873
+ if (key.ctrl && key.name === "c") stop();
874
+ if (state.steerInput.active) {
875
+ handleSteerInput(str, key);
876
+ draw();
877
+ return;
878
+ }
879
+ if (key.name === "q" || key.name === "escape") stop();
880
+ if (key.name === "tab") state.focus = state.focus === "supervisors" ? "workers" : "supervisors";
881
+ else if (key.name === "up") moveSelection(-1);
882
+ else if (key.name === "down") moveSelection(1);
883
+ else if (key.name === "left") moveSupervisor(-1);
884
+ else if (key.name === "right") moveSupervisor(1);
885
+ else if (key.name === "return") {
886
+ state.mode = "detail";
887
+ state.focus = "workers";
888
+ } else if (key.name === "o") state.mode = "overview";
889
+ else if (key.name === "l") state.mode = "log";
890
+ else if (key.name === "s") startSteerInput();
891
+ else if (key.name === "a") {
892
+ state.mode = "detail";
893
+ openWorkerShell();
894
+ } else if (key.name === "c") requestCancel();
895
+ draw();
896
+ }
897
+ function handleSteerInput(str, key) {
898
+ if (key.name === "escape") {
899
+ state.steerInput = {
900
+ active: false,
901
+ value: ""
902
+ };
903
+ return;
904
+ }
905
+ if (key.name === "return") {
906
+ submitSteerInput();
907
+ return;
908
+ }
909
+ if (key.name === "backspace") {
910
+ state.steerInput.value = state.steerInput.value.slice(0, -1);
911
+ return;
912
+ }
913
+ if (key.ctrl && key.name === "u") {
914
+ state.steerInput.value = "";
915
+ return;
916
+ }
917
+ if (key.ctrl || key.name === "tab" || key.name === "up" || key.name === "down" || key.name === "left" || key.name === "right") return;
918
+ if (!str || str === "\r" || str === "\n") return;
919
+ if ([...str].every((char) => char >= " " && char !== "")) state.steerInput.value += str;
920
+ }
921
+ function onData(data) {
922
+ const click = parseMouse(data.toString("utf8"));
923
+ if (!click) return;
924
+ const target = targets.find((candidate) => candidate.row === click.row);
925
+ if (!target) return;
926
+ state.notice = void 0;
927
+ if (target.kind === "supervisor") {
928
+ state.focus = "supervisors";
929
+ state.selectedSupervisorId = target.id;
930
+ state.selectedWorkerId = void 0;
931
+ } else {
932
+ state.focus = "workers";
933
+ state.mode = "detail";
934
+ state.selectedSupervisorId = target.supervisorId;
935
+ state.selectedWorkerId = target.id;
936
+ }
937
+ draw();
938
+ }
939
+ function startSteerInput() {
940
+ state.mode = "detail";
941
+ state.focus = "workers";
942
+ if (!selectedSupervisor()) {
943
+ state.notice = "no supervisor selected";
944
+ return;
945
+ }
946
+ if (!selectedWorker()) {
947
+ state.notice = "no worker selected";
948
+ return;
949
+ }
950
+ state.steerInput = {
951
+ active: true,
952
+ value: ""
953
+ };
954
+ }
955
+ function submitSteerInput() {
956
+ const supervisor = selectedSupervisor();
957
+ const worker = selectedWorker();
958
+ const message = state.steerInput.value;
959
+ state.steerInput = {
960
+ active: false,
961
+ value: ""
962
+ };
963
+ if (!supervisor) {
964
+ state.notice = "no supervisor selected";
965
+ return;
966
+ }
967
+ if (!worker) {
968
+ state.notice = "no worker selected";
969
+ return;
970
+ }
971
+ try {
972
+ const written = writeWorkerSteer(state.root, supervisor.id, worker.label, message, "human");
973
+ state.notice = `steer queued for ${supervisor.id}/${written.worker}`;
974
+ } catch (err) {
975
+ state.notice = `steer failed: ${err instanceof Error ? err.message : String(err)}`;
976
+ }
977
+ }
978
+ function parseMouse(raw) {
979
+ const match = /\x1b\[<(\d+);(\d+);(\d+)([mM])/.exec(raw);
980
+ if (match?.[4] !== "M") return void 0;
981
+ const col = Number(match[2]);
982
+ const row = Number(match[3]);
983
+ if (!Number.isFinite(col) || !Number.isFinite(row)) return void 0;
984
+ return {
985
+ col: col - 1,
986
+ row: row - 1
987
+ };
988
+ }
989
+ function moveSelection(delta) {
990
+ if (state.focus === "supervisors") {
991
+ moveSupervisor(delta);
992
+ return;
993
+ }
994
+ const supervisor = selectedSupervisor();
995
+ if (!supervisor || supervisor.workers.length === 0) return;
996
+ const next = wrap(Math.max(0, supervisor.workers.findIndex((worker) => worker.id === state.selectedWorkerId)) + delta, supervisor.workers.length);
997
+ const worker = supervisor.workers[next];
998
+ if (worker) state.selectedWorkerId = worker.id;
999
+ }
1000
+ function moveSupervisor(delta) {
1001
+ if (snapshot.supervisors.length === 0) return;
1002
+ const next = wrap(Math.max(0, snapshot.supervisors.findIndex((supervisor) => supervisor.id === state.selectedSupervisorId)) + delta, snapshot.supervisors.length);
1003
+ const supervisor = snapshot.supervisors[next];
1004
+ if (!supervisor) return;
1005
+ state.selectedSupervisorId = supervisor.id;
1006
+ state.selectedWorkerId = supervisor.workers[0]?.id;
1007
+ }
1008
+ function clampSelection() {
1009
+ if (snapshot.supervisors.length === 0) {
1010
+ state.selectedSupervisorId = void 0;
1011
+ state.selectedWorkerId = void 0;
1012
+ return;
1013
+ }
1014
+ const supervisor = selectedSupervisor() ?? snapshot.supervisors[0];
1015
+ if (!supervisor) return;
1016
+ state.selectedSupervisorId = supervisor.id;
1017
+ const worker = supervisor.workers.find((candidate) => candidate.id === state.selectedWorkerId) ?? supervisor.workers[0];
1018
+ state.selectedWorkerId = worker?.id;
1019
+ }
1020
+ function selectedSupervisor() {
1021
+ return snapshot.supervisors.find((supervisor) => supervisor.id === state.selectedSupervisorId);
1022
+ }
1023
+ function selectedWorker() {
1024
+ return selectedSupervisor()?.workers.find((worker) => worker.id === state.selectedWorkerId);
1025
+ }
1026
+ function renderSteerInputState() {
1027
+ const worker = selectedWorker();
1028
+ return {
1029
+ active: state.steerInput.active,
1030
+ value: state.steerInput.value,
1031
+ ...worker?.label ? { workerLabel: worker.label } : {}
1032
+ };
1033
+ }
1034
+ function openWorkerShell() {
1035
+ const worker = selectedWorker();
1036
+ if (!worker?.cwd) {
1037
+ state.notice = "no worker worktree recorded yet";
1038
+ return;
1039
+ }
1040
+ if (!existsSync(worker.cwd)) {
1041
+ state.notice = `worker worktree is gone: ${worker.cwd}`;
1042
+ return;
1043
+ }
1044
+ const shell = process.env.SHELL ?? "/bin/bash";
1045
+ state.notice = `returned from ${worker.label}`;
1046
+ pauseUi();
1047
+ spawnSync(shell, [], {
1048
+ cwd: worker.cwd,
1049
+ stdio: "inherit",
1050
+ env: {
1051
+ ...process.env,
1052
+ AGENT_SUPERVISOR_ROOT: state.root,
1053
+ AGENT_SUPERVISOR_ID: state.selectedSupervisorId ?? "",
1054
+ AGENT_WORKER_ID: worker.id,
1055
+ AGENT_WORKER_LABEL: worker.label
1056
+ }
1057
+ });
1058
+ resumeUi();
1059
+ }
1060
+ function requestCancel() {
1061
+ const supervisor = selectedSupervisor();
1062
+ if (!supervisor) {
1063
+ state.notice = "no supervisor selected";
1064
+ return;
1065
+ }
1066
+ if (supervisor.status !== "running") {
1067
+ state.notice = `${supervisor.id} is ${supervisor.status}; no cancel request written`;
1068
+ return;
1069
+ }
1070
+ try {
1071
+ mkdirSync(supervisor.stateDir, { recursive: true });
1072
+ writeFileSync(join(supervisor.stateDir, "cancel.request.json"), `${JSON.stringify({
1073
+ at: (/* @__PURE__ */ new Date()).toISOString(),
1074
+ source: "agent-runtime-top",
1075
+ reason: "operator requested cancel from TUI"
1076
+ }, null, 2)}\n`, "utf8");
1077
+ state.notice = `cancel requested for ${supervisor.id}`;
1078
+ } catch (err) {
1079
+ state.notice = `cancel request failed: ${err instanceof Error ? err.message : String(err)}`;
1080
+ }
1081
+ }
1082
+ function wrap(value, size) {
1083
+ return (value % size + size) % size;
1084
+ }
1085
+ //#endregion
1086
+ export { renderTopFrameWithLayout as a, renderTopFrame as i, runTopApp as n, loadTopSnapshot as r, renderTopOnce as t };
1087
+
1088
+ //# sourceMappingURL=top-app-5unxqovu.js.map