@tangle-network/agent-runtime 0.338.0 → 0.339.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{activation-CsbNax2X.d.ts → activation-CJIIl_Ey.d.ts} +4 -24
- package/dist/{activation-kbkAuSmY.js → activation-CVSFxis9.js} +5 -18
- package/dist/{activation-kbkAuSmY.js.map → activation-CVSFxis9.js.map} +1 -1
- package/dist/agent.d.ts +4 -4
- package/dist/agent.js +3 -3
- package/dist/{bundle-DM60AI2B.js → bundle-DVOhBTic.js} +2 -2
- package/dist/{bundle-DM60AI2B.js.map → bundle-DVOhBTic.js.map} +1 -1
- package/dist/candidate-execution/index.d.ts +4 -63
- package/dist/candidate-execution/index.js +6 -145
- package/dist/candidate-execution/index.js.map +1 -1
- package/dist/{delegate-Se1YZKMb.js → delegate-CCWE_AkU.js} +2 -2
- package/dist/{delegate-Se1YZKMb.js.map → delegate-CCWE_AkU.js.map} +1 -1
- package/dist/{driver-control-acknowledgers-T2a1Ouay.js → driver-control-acknowledgers-C53ggdJ6.js} +12 -8
- package/dist/driver-control-acknowledgers-C53ggdJ6.js.map +1 -0
- package/dist/{durable-file-B_eJr6Mb.js → durable-file-gNI9ILAn.js} +1 -3
- package/dist/durable-file-gNI9ILAn.js.map +1 -0
- package/dist/durable.d.ts +3 -35
- package/dist/durable.js +10 -10
- package/dist/durable.js.map +1 -1
- package/dist/{graph-CY0wH838.js → graph-CI9JXKqO.js} +4 -4
- package/dist/graph-CI9JXKqO.js.map +1 -0
- package/dist/{graph-registry-By0tUj4S.js → graph-registry-BnO64cn8.js} +3 -3
- package/dist/{graph-registry-By0tUj4S.js.map → graph-registry-BnO64cn8.js.map} +1 -1
- package/dist/{improve-CWGBT13K.js → improve-BOXTLWJ3.js} +8 -35
- package/dist/improve-BOXTLWJ3.js.map +1 -0
- package/dist/{improvement-adapter-D5gwwoXQ.d.ts → improvement-adapter-HUt_-cWA.d.ts} +2 -20
- package/dist/index.d.ts +9 -206
- package/dist/index.js +91 -302
- package/dist/index.js.map +1 -1
- package/dist/intelligence.d.ts +10 -142
- package/dist/intelligence.js +7 -81
- package/dist/intelligence.js.map +1 -1
- package/dist/{jsonl-file-DBv31uPi.js → jsonl-file-C9Kde_0Z.js} +2 -2
- package/dist/{jsonl-file-DBv31uPi.js.map → jsonl-file-C9Kde_0Z.js.map} +1 -1
- package/dist/kernel.d.ts +4 -3
- package/dist/kernel.js +12 -14
- package/dist/loop-runner-CWzG1bH4.d.ts +32 -0
- package/dist/loop-runner-bin.d.ts +32 -2
- package/dist/loop-runner-bin.js +93 -1
- package/dist/loop-runner-bin.js.map +1 -0
- package/dist/loop-runner-pmPIHFVY.js +59 -0
- package/dist/loop-runner-pmPIHFVY.js.map +1 -0
- package/dist/mcp/bin.js +3 -3
- package/dist/mcp/index.d.ts +99 -418
- package/dist/mcp/index.js +121 -203
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/memory-bin.js +231 -1
- package/dist/mcp/memory-bin.js.map +1 -1
- package/dist/model-policy-CyCUwHed.js.map +1 -1
- package/dist/placement.d.ts +2 -9
- package/dist/placement.js +1 -1
- package/dist/placement.js.map +1 -1
- package/dist/platform.d.ts +188 -1
- package/dist/platform.js +129 -1
- package/dist/platform.js.map +1 -1
- package/dist/{prepare-Bt0IouPq.js → prepare-bXkvPMJA.js} +8 -160
- package/dist/prepare-bXkvPMJA.js.map +1 -0
- package/dist/{profile-Do-5xO_D.js → profile-Dpei_Sq0.js} +2 -2
- package/dist/profile-Dpei_Sq0.js.map +1 -0
- package/dist/{profile-materialization-BnoEf1co.d.ts → profile-materialization-B1wm2SPT.d.ts} +3 -5
- package/dist/profiles.d.ts +2 -2
- package/dist/profiles.js +3 -40
- package/dist/profiles.js.map +1 -1
- package/dist/{protected-model-port-DGxQ8_YM.d.ts → protected-model-port-BGgpy2r9.d.ts} +3 -29
- package/dist/protected-redaction-DFCeo4vg.js.map +1 -1
- package/dist/{provision-supervisor-0jMufp4h.js → provision-supervisor-CJ6Zz6dz.js} +6 -6
- package/dist/{provision-supervisor-0jMufp4h.js.map → provision-supervisor-CJ6Zz6dz.js.map} +1 -1
- package/dist/{run-cancellation-BoW5Sg9Y.js → run-cancellation-C9oL62sW.js} +3 -7
- package/dist/{run-cancellation-BoW5Sg9Y.js.map → run-cancellation-C9oL62sW.js.map} +1 -1
- package/dist/{runtime-D37zRSs7.js → runtime-B2fjL9Oi.js} +2531 -2889
- package/dist/runtime-B2fjL9Oi.js.map +1 -0
- package/dist/{runtime-hooks-Bj6wJHlH.d.ts → runtime-hooks-nsqxSUFL.d.ts} +2 -6
- package/dist/{sandbox-profile-compat-xSoVtdWw.js → sandbox-profile-compat-BzVBFqrz.js} +2 -4
- package/dist/sandbox-profile-compat-BzVBFqrz.js.map +1 -0
- package/dist/{search-executor-DmgUm5p7.js → search-executor-zMumiak_.js} +3 -3
- package/dist/search-executor-zMumiak_.js.map +1 -0
- package/dist/{server-DQHJszfk.js → server-_4voDNrl.js} +362 -67
- package/dist/server-_4voDNrl.js.map +1 -0
- package/dist/{structural-rollout-DAvIE8mw.js → structural-rollout-DpLCXzaL.js} +80 -80
- package/dist/structural-rollout-DpLCXzaL.js.map +1 -0
- package/dist/{substrate-B0TYNrXn.js → substrate-CGDFTSRh.js} +2 -9
- package/dist/substrate-CGDFTSRh.js.map +1 -0
- package/dist/{supervise-WuPAwfPh.js → supervise-qAwXmPtD.js} +10 -57
- package/dist/supervise-qAwXmPtD.js.map +1 -0
- package/dist/{supervisor-CPG1I9UN.js → supervisor-DHrUJhEZ.js} +179 -236
- package/dist/supervisor-DHrUJhEZ.js.map +1 -0
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +13 -13
- package/dist/testing.js.map +1 -1
- package/dist/tool-loop.d.ts +1 -1
- package/dist/{tool-server-Dqer2M4_.js → tool-server-XV4aJwoQ.js} +2 -2
- package/dist/tool-server-XV4aJwoQ.js.map +1 -0
- package/dist/top-snapshot-CbZE1JsU.js +502 -0
- package/dist/top-snapshot-CbZE1JsU.js.map +1 -0
- package/dist/tui/bin.js +711 -1
- package/dist/tui/bin.js.map +1 -1
- package/dist/tui/index.d.ts +2 -70
- package/dist/tui/index.js +3 -3
- package/dist/{types-CrNWif8-.d.ts → types-CaOXqRTq.d.ts} +713 -2096
- package/dist/workspace-store/index.d.ts +2 -27
- package/dist/workspace-store/index.js +2 -2
- package/dist/workspace-store/index.js.map +1 -1
- package/dist/{workspace-streams-BZZds1Fw.d.ts → workspace-streams-BiS4ABah.d.ts} +3 -18
- package/package.json +1 -1
- package/skills/agent-graphs/references/authoring.md +3 -2
- package/dist/coder-yhVWbdWc.js +0 -40
- package/dist/coder-yhVWbdWc.js.map +0 -1
- package/dist/delegation-status-CF4D0NZ_.js +0 -358
- package/dist/delegation-status-CF4D0NZ_.js.map +0 -1
- package/dist/driver-control-acknowledgers-T2a1Ouay.js.map +0 -1
- package/dist/durable-file-B_eJr6Mb.js.map +0 -1
- package/dist/graph-CY0wH838.js.map +0 -1
- package/dist/improve-CWGBT13K.js.map +0 -1
- package/dist/integrations-6xttbPi9.d.ts +0 -190
- package/dist/integrations-dFi-ALk7.js +0 -132
- package/dist/integrations-dFi-ALk7.js.map +0 -1
- package/dist/kb-gate-C8z2juK8.d.ts +0 -80
- package/dist/kb-gate-DpaSwXVx.js +0 -80
- package/dist/kb-gate-DpaSwXVx.js.map +0 -1
- package/dist/loop-runner-bin-CFnrAHpl.d.ts +0 -164
- package/dist/loop-runner-bin-CdPlhy9g.js +0 -246
- package/dist/loop-runner-bin-CdPlhy9g.js.map +0 -1
- package/dist/memory-server-DXQ8IxGR.js +0 -234
- package/dist/memory-server-DXQ8IxGR.js.map +0 -1
- package/dist/openai-tools-DoQ32vpe.js +0 -44
- package/dist/openai-tools-DoQ32vpe.js.map +0 -1
- package/dist/openai-tools-zh2I_uD7.d.ts +0 -24
- package/dist/prepare-Bt0IouPq.js.map +0 -1
- package/dist/profile-Do-5xO_D.js.map +0 -1
- package/dist/runtime-D37zRSs7.js.map +0 -1
- package/dist/sandbox-profile-compat-xSoVtdWw.js.map +0 -1
- package/dist/search-executor-DmgUm5p7.js.map +0 -1
- package/dist/server-DQHJszfk.js.map +0 -1
- package/dist/structural-rollout-DAvIE8mw.js.map +0 -1
- package/dist/substrate-B0TYNrXn.js.map +0 -1
- package/dist/supervise-WuPAwfPh.js.map +0 -1
- package/dist/supervisor-CPG1I9UN.js.map +0 -1
- package/dist/tool-server-Dqer2M4_.js.map +0 -1
- package/dist/top-app-C55Wa7uy.js +0 -1214
- package/dist/top-app-C55Wa7uy.js.map +0 -1
package/dist/tui/bin.js
CHANGED
|
@@ -1,5 +1,715 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { n as
|
|
2
|
+
import { _ as supervisorRunsRoot, n as cancelRun, s as cancelWorker, x as writeWorkerSteer } from "../run-cancellation-C9oL62sW.js";
|
|
3
|
+
import { a as parseTime, i as parseSpend, n as loadTopSnapshot, o as totalSpend, r as numberValue, t as aggregateSnapshot } from "../top-snapshot-CbZE1JsU.js";
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
import { resolve } from "node:path";
|
|
6
|
+
import { emitKeypressEvents } from "node:readline";
|
|
7
|
+
//#region src/tui/top-frame.ts
|
|
8
|
+
const ansiPattern = /\x1b\[[0-9;?]*[ -/]*[@-~]/g;
|
|
9
|
+
const colors = {
|
|
10
|
+
reset: "\x1B[0m",
|
|
11
|
+
dim: "\x1B[2m",
|
|
12
|
+
bold: "\x1B[1m",
|
|
13
|
+
red: "\x1B[31m",
|
|
14
|
+
green: "\x1B[32m",
|
|
15
|
+
yellow: "\x1B[33m",
|
|
16
|
+
blue: "\x1B[34m",
|
|
17
|
+
magenta: "\x1B[35m",
|
|
18
|
+
cyan: "\x1B[36m",
|
|
19
|
+
white: "\x1B[37m",
|
|
20
|
+
inverse: "\x1B[7m"
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Render one snapshot, returning the frame together with the row→entity map a mouse click resolves
|
|
24
|
+
* against. The layout is the only thing that knows which row is which run or worker, so emitting it
|
|
25
|
+
* alongside the text is what keeps click handling out of the renderer.
|
|
26
|
+
*/
|
|
27
|
+
function renderTopFrameWithLayout(snapshot, options = {}) {
|
|
28
|
+
const width = Math.max(88, options.width ?? 128);
|
|
29
|
+
const height = Math.max(24, options.height ?? 42);
|
|
30
|
+
const color = options.color ?? false;
|
|
31
|
+
const focus = options.focus ?? "supervisors";
|
|
32
|
+
const mode = options.mode ?? "overview";
|
|
33
|
+
const selectedSupervisor = selectSupervisor(snapshot, options.selectedSupervisorId);
|
|
34
|
+
const selectedWorker = selectedSupervisor ? selectWorker(selectedSupervisor, options.selectedWorkerId) : void 0;
|
|
35
|
+
const targets = [];
|
|
36
|
+
const lines = [];
|
|
37
|
+
const push = (line = "", target) => {
|
|
38
|
+
if (lines.length >= height) return;
|
|
39
|
+
if (target) targets.push({
|
|
40
|
+
row: lines.length,
|
|
41
|
+
...target
|
|
42
|
+
});
|
|
43
|
+
lines.push(fitAnsi(line, width));
|
|
44
|
+
};
|
|
45
|
+
const aggregate = aggregateSnapshot(snapshot);
|
|
46
|
+
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)}`);
|
|
47
|
+
push(`${paint("keys", "dim", color)} up/down select left/right run tab focus enter detail o overview l log s steer c cancel mouse q quit`);
|
|
48
|
+
if (options.notice) push(paint(options.notice, "yellow", color));
|
|
49
|
+
if (snapshot.completeness === "partial") push(paint(`partial snapshot: ${snapshot.diagnostics.length} source${snapshot.diagnostics.length === 1 ? "" : "s"} skipped or truncated; runs loaded ${snapshot.loaded}/${snapshot.discovered}`, "yellow", color));
|
|
50
|
+
if (options.steerInput?.active) push(renderSteerInput(options.steerInput, width, color));
|
|
51
|
+
push(rule(width, color));
|
|
52
|
+
if (snapshot.supervisors.length === 0) {
|
|
53
|
+
if (snapshot.discovered > 0) {
|
|
54
|
+
push(paint("NO READABLE SUPERVISORS", "yellow", color));
|
|
55
|
+
push(`${snapshot.discovered} run director${snapshot.discovered === 1 ? "y" : "ies"} found under ${supervisorRunsRoot(snapshot.root)}, none readable`);
|
|
56
|
+
} else {
|
|
57
|
+
push(paint("NO SUPERVISORS", "yellow", color));
|
|
58
|
+
push(`No run state under ${supervisorRunsRoot(snapshot.root)}`);
|
|
59
|
+
}
|
|
60
|
+
push(`A run appears here once its driver writes state.json; re-point with: agent-runtime-top <root>`);
|
|
61
|
+
return {
|
|
62
|
+
frame: `${lines.join("\n")}\n`,
|
|
63
|
+
targets
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
push(sectionTitle("SUPERVISORS", focus === "supervisors", color));
|
|
67
|
+
push(` id status workers spend tok in/out latency task`);
|
|
68
|
+
for (const supervisor of snapshot.supervisors.slice(0, 9)) {
|
|
69
|
+
const selected = supervisor.id === selectedSupervisor?.id;
|
|
70
|
+
const status = statusText(supervisor.status, color);
|
|
71
|
+
const pointer = selected ? paint(">", "cyan", color) : " ";
|
|
72
|
+
const rowFocus = selected && focus === "supervisors" ? "inverse" : void 0;
|
|
73
|
+
const workerSummary = `${supervisor.totals.running}/${supervisor.totals.workers} live`;
|
|
74
|
+
const task = compact(supervisor.task, 34);
|
|
75
|
+
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}`;
|
|
76
|
+
push(rowFocus ? paint(line, rowFocus, color) : line, {
|
|
77
|
+
kind: "supervisor",
|
|
78
|
+
id: supervisor.id
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
if (!selectedSupervisor) return {
|
|
82
|
+
frame: `${lines.join("\n")}\n`,
|
|
83
|
+
targets
|
|
84
|
+
};
|
|
85
|
+
push();
|
|
86
|
+
push(sectionTitle(`WORKERS ${selectedSupervisor.id}`, focus === "workers", color));
|
|
87
|
+
push(` id state runtime latency cost tok in/out score detail`);
|
|
88
|
+
for (const worker of selectedSupervisor.workers.slice(0, 12)) {
|
|
89
|
+
const selected = worker.id === selectedWorker?.id;
|
|
90
|
+
const pointer = selected ? paint(">", "cyan", color) : " ";
|
|
91
|
+
const rowFocus = selected && focus === "workers" ? "inverse" : void 0;
|
|
92
|
+
const score = worker.verdict ?? (worker.status === "done" ? "done" : worker.status);
|
|
93
|
+
const detail = worker.reason ?? worker.outRef ?? worker.parent ?? "";
|
|
94
|
+
const spend = totalSpend(worker);
|
|
95
|
+
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)}`;
|
|
96
|
+
push(rowFocus ? paint(line, rowFocus, color) : line, {
|
|
97
|
+
kind: "worker",
|
|
98
|
+
id: worker.id,
|
|
99
|
+
supervisorId: selectedSupervisor.id
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
if (selectedSupervisor.workers.length === 0) push(" no workers spawned yet");
|
|
103
|
+
push();
|
|
104
|
+
push(sectionTitle("OBSERVABILITY", false, color));
|
|
105
|
+
for (const line of renderStatusBars(selectedSupervisor, width, color)) push(line);
|
|
106
|
+
push();
|
|
107
|
+
for (const line of renderCostBars(selectedSupervisor, width, color)) push(line);
|
|
108
|
+
push();
|
|
109
|
+
for (const line of renderTokenBars(selectedSupervisor, width, color)) push(line);
|
|
110
|
+
push();
|
|
111
|
+
for (const line of renderLatencyHistogram(selectedSupervisor, width, color)) push(line);
|
|
112
|
+
push();
|
|
113
|
+
if (mode === "log") {
|
|
114
|
+
push(sectionTitle("JOURNAL TAIL", false, color));
|
|
115
|
+
for (const line of renderJournalTail(selectedSupervisor, width, color)) push(line);
|
|
116
|
+
} else {
|
|
117
|
+
push(sectionTitle(mode === "detail" ? "DETAIL" : "RUN SUMMARY", false, color));
|
|
118
|
+
for (const line of renderDetail(selectedSupervisor, selectedWorker, mode, width, color)) push(line);
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
frame: `${lines.join("\n")}\n`,
|
|
122
|
+
targets
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function renderStatusBars(supervisor, width, color) {
|
|
126
|
+
const total = Math.max(1, supervisor.totals.workers);
|
|
127
|
+
const barWidth = Math.max(12, Math.min(28, width - 58));
|
|
128
|
+
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}`];
|
|
129
|
+
}
|
|
130
|
+
function renderCostBars(supervisor, width, color) {
|
|
131
|
+
const workers = supervisor.workers.map((worker) => ({
|
|
132
|
+
worker,
|
|
133
|
+
spend: totalSpend(worker)
|
|
134
|
+
})).sort((a, b) => b.spend.usd - a.spend.usd).slice(0, 6);
|
|
135
|
+
const maxUsd = Math.max(1e-6, ...workers.map((row) => row.spend.usd), supervisor.driverSpend.usd);
|
|
136
|
+
const out = [`cost total ${formatMoney(supervisor.totals.usd)} driver ${formatMoney(supervisor.driverSpend.usd)}`];
|
|
137
|
+
const barWidth = Math.max(18, Math.min(46, width - 38));
|
|
138
|
+
for (const row of workers) out.push(` ${pad(row.worker.label, 12)} ${bar(row.spend.usd, maxUsd, barWidth, "$", color, "magenta")} ${formatMoney(row.spend.usd)}`);
|
|
139
|
+
if (workers.length === 0) out.push(" no worker cost yet");
|
|
140
|
+
return out;
|
|
141
|
+
}
|
|
142
|
+
function renderTokenBars(supervisor, width, color) {
|
|
143
|
+
const rows = supervisor.workers.map((worker) => ({
|
|
144
|
+
worker,
|
|
145
|
+
spend: totalSpend(worker),
|
|
146
|
+
total: totalSpend(worker).tokensInput + totalSpend(worker).tokensOutput
|
|
147
|
+
})).sort((a, b) => b.total - a.total).slice(0, 6);
|
|
148
|
+
const maxTokens = Math.max(1, ...rows.map((row) => row.total), supervisor.driverSpend.tokensInput + supervisor.driverSpend.tokensOutput);
|
|
149
|
+
const out = [`tokens total in ${formatTokens(supervisor.totals.tokensInput)} / out ${formatTokens(supervisor.totals.tokensOutput)} driver ${formatTokens(supervisor.driverSpend.tokensInput)}/${formatTokens(supervisor.driverSpend.tokensOutput)}`];
|
|
150
|
+
const barWidth = Math.max(18, Math.min(46, width - 44));
|
|
151
|
+
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)}`);
|
|
152
|
+
if (rows.length === 0) out.push(" no worker tokens yet");
|
|
153
|
+
return out;
|
|
154
|
+
}
|
|
155
|
+
function renderLatencyHistogram(supervisor, width, color) {
|
|
156
|
+
const values = supervisor.workers.map((worker) => worker.latencyMs).filter((value) => value > 0);
|
|
157
|
+
const summary = supervisor.totals.workerLatency;
|
|
158
|
+
const out = [`latency n=${summary.n} min ${formatDuration(summary.min)} p50 ${formatDuration(summary.median)} p90 ${formatDuration(summary.p90)} max ${formatDuration(summary.max)}`];
|
|
159
|
+
if (values.length === 0) {
|
|
160
|
+
out.push(" no latency samples yet");
|
|
161
|
+
return out;
|
|
162
|
+
}
|
|
163
|
+
const bins = histogram(values, 6);
|
|
164
|
+
const maxCount = Math.max(1, ...bins.map((bin) => bin.count));
|
|
165
|
+
const barWidth = Math.max(18, Math.min(48, width - 34));
|
|
166
|
+
for (const bin of bins) out.push(` ${pad(`${formatDuration(bin.min)}-${formatDuration(bin.max)}`, 15)} ${bar(bin.count, maxCount, barWidth, "#", color, "blue")} ${bin.count}`);
|
|
167
|
+
return out;
|
|
168
|
+
}
|
|
169
|
+
function renderDetail(supervisor, worker, mode, width, color) {
|
|
170
|
+
if (mode === "detail" && worker) {
|
|
171
|
+
const spend = totalSpend(worker);
|
|
172
|
+
return [
|
|
173
|
+
`worker ${paint(worker.label, "bold", color)} (${worker.id}) ${statusText(worker.status, color)} latency ${formatDuration(worker.latencyMs)}`,
|
|
174
|
+
`tokens in/out ${formatTokens(spend.tokensInput)}/${formatTokens(spend.tokensOutput)} cost ${formatMoney(spend.usd)} iterations ${spend.iterations}`,
|
|
175
|
+
`parent ${worker.parent ?? "-"} runtime ${worker.runtime ?? "-"} out ${worker.outRef ?? "-"}${worker.infra ? " infra" : ""}`,
|
|
176
|
+
`events ${compact(worker.eventFile ?? "-", width - 8)}`,
|
|
177
|
+
...worker.reason ? [`reason ${compact(worker.reason, width - 7)}`] : [],
|
|
178
|
+
...renderWorkerTail(worker, width, color)
|
|
179
|
+
];
|
|
180
|
+
}
|
|
181
|
+
const caps = [
|
|
182
|
+
supervisor.maxSandboxes !== void 0 ? `boxes ${supervisor.maxSandboxes}` : void 0,
|
|
183
|
+
supervisor.maxUsd !== void 0 ? `max ${formatMoney(supervisor.maxUsd)}` : void 0,
|
|
184
|
+
supervisor.maxDepth !== void 0 ? `depth ${supervisor.maxDepth}` : void 0,
|
|
185
|
+
supervisor.maxLifetimeSeconds !== void 0 ? `ttl ${supervisor.maxLifetimeSeconds}s` : void 0
|
|
186
|
+
].filter((value) => value !== void 0);
|
|
187
|
+
return [
|
|
188
|
+
`task ${compact(supervisor.task, width - 7)}`,
|
|
189
|
+
`workspace ${compact(supervisor.workspaceDir, width - 12)}`,
|
|
190
|
+
`verify ${compact(supervisor.verifyCmd ?? "-", width - 9)}`,
|
|
191
|
+
`models worker ${supervisor.workerModel ?? "-"} / driver ${supervisor.driverModel ?? "-"} caps ${caps.join(" | ") || "-"}`,
|
|
192
|
+
`progress ${compact(supervisor.progress ?? supervisor.verdict ?? supervisor.status, width - 10)}`
|
|
193
|
+
];
|
|
194
|
+
}
|
|
195
|
+
function renderJournalTail(supervisor, width, color) {
|
|
196
|
+
if (supervisor.journalTail.length === 0) return [" no journal events yet"];
|
|
197
|
+
return supervisor.journalTail.map((event) => {
|
|
198
|
+
const at = event.at ? formatClock(event.at) : "--:--:--";
|
|
199
|
+
if (event.kind === "spawned") return ` ${at} ${paint("spawn", "yellow", color)} ${pad(event.label ?? event.id, 16)} ${event.id}`;
|
|
200
|
+
if (event.kind === "settled") {
|
|
201
|
+
const spend = parseSpend(event.spent);
|
|
202
|
+
return ` ${at} ${statusText(event.status ?? "settled", color)} ${pad(event.id, 18)} ${formatMoney(spend.usd)} ${formatTokens(spend.tokensInput)}/${formatTokens(spend.tokensOutput)}`;
|
|
203
|
+
}
|
|
204
|
+
if (event.kind === "metered") {
|
|
205
|
+
const spend = parseSpend(event.spend);
|
|
206
|
+
return ` ${at} ${paint("meter", "cyan", color)} ${pad(event.id, 18)} ${formatMoney(spend.usd)} ${formatTokens(spend.tokensInput)}/${formatTokens(spend.tokensOutput)}`;
|
|
207
|
+
}
|
|
208
|
+
if (event.kind === "progress") {
|
|
209
|
+
const spend = parseSpend(event.spend);
|
|
210
|
+
return ` ${at} ${paint("live", "blue", color)} ${pad(event.id, 18)} ${formatMoney(spend.usd)} ${formatTokens(spend.tokensInput)}/${formatTokens(spend.tokensOutput)}`;
|
|
211
|
+
}
|
|
212
|
+
return ` ${at} ${paint("cancel", "red", color)} ${pad(event.id, 18)} ${compact(event.reason ?? "", width - 32)}`;
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
function renderWorkerTail(worker, width, color) {
|
|
216
|
+
if (worker.liveTail.length === 0) return ["live events: none recorded yet"];
|
|
217
|
+
return ["live events:", ...worker.liveTail.slice(-5).map((line) => ` ${paint(compact(formatWorkerEvent(line), width - 2), "dim", color)}`)];
|
|
218
|
+
}
|
|
219
|
+
function renderSteerInput(input, width, color) {
|
|
220
|
+
const target = input.workerLabel ?? "-";
|
|
221
|
+
const value = compact(input.value, Math.max(10, width - target.length - 14));
|
|
222
|
+
return `${paint("steer", "cyan", color)} ${target} > ${value}${paint("_", "inverse", color)}`;
|
|
223
|
+
}
|
|
224
|
+
function formatWorkerEvent(line) {
|
|
225
|
+
try {
|
|
226
|
+
const parsed = JSON.parse(line);
|
|
227
|
+
const at = typeof parsed.at === "string" ? formatClock(parsed.at) : "--:--:--";
|
|
228
|
+
const kind = typeof parsed.kind === "string" ? parsed.kind : "event";
|
|
229
|
+
if (kind === "progress") return `${at} progress iter ${numberValue(parsed.iteration)} ${String(parsed.phase ?? "")}`;
|
|
230
|
+
if (kind === "message") {
|
|
231
|
+
const state = parsed.queued === true ? "queued" : "sent";
|
|
232
|
+
const source = typeof parsed.source === "string" && parsed.source.length > 0 ? `${parsed.source} ` : "";
|
|
233
|
+
const message = typeof parsed.message === "string" && parsed.message.length > 0 ? ` ${parsed.message}` : "";
|
|
234
|
+
return `${at} ${source}steer ${state} delivered=${String(parsed.delivered)}${message}`;
|
|
235
|
+
}
|
|
236
|
+
if (kind === "verify") return `${at} verify passed=${String(parsed.passed)} exit=${String(parsed.exitCode ?? "-")}${evidenceSnippet(parsed.outputTail, 160)}`;
|
|
237
|
+
if (kind === "finished") {
|
|
238
|
+
const branch = typeof parsed.branch === "string" ? ` branch=${parsed.branch}` : "";
|
|
239
|
+
const files = typeof parsed.filesChanged === "number" ? ` files=${parsed.filesChanged}` : "";
|
|
240
|
+
const tests = typeof parsed.testPassed === "boolean" ? ` tests=${String(parsed.testPassed)}` : "";
|
|
241
|
+
return `${at} finished passed=${String(parsed.passed)} patchBytes=${numberValue(parsed.patchBytes)}${files}${tests}${branch}${evidenceSnippet(parsed.evidence, 220)}`;
|
|
242
|
+
}
|
|
243
|
+
if (kind === "error") return `${at} error ${String(parsed.error ?? "")}`;
|
|
244
|
+
return `${at} ${kind}`;
|
|
245
|
+
} catch {
|
|
246
|
+
return line;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/** One-line the settle evidence / verify tail a worker event carries so the
|
|
250
|
+
* failure is legible in the tail view (the full block stays in the ndjson). */
|
|
251
|
+
function evidenceSnippet(value, max) {
|
|
252
|
+
if (typeof value !== "string") return "";
|
|
253
|
+
const oneLine = value.replace(/\s+/g, " ").trim();
|
|
254
|
+
return oneLine ? ` :: ${compact(oneLine, max)}` : "";
|
|
255
|
+
}
|
|
256
|
+
function histogram(values, count) {
|
|
257
|
+
const min = Math.min(...values);
|
|
258
|
+
const max = Math.max(...values);
|
|
259
|
+
if (min === max) return [{
|
|
260
|
+
min,
|
|
261
|
+
max,
|
|
262
|
+
count: values.length
|
|
263
|
+
}];
|
|
264
|
+
const step = (max - min) / count;
|
|
265
|
+
return Array.from({ length: count }, (_, index) => {
|
|
266
|
+
const low = min + step * index;
|
|
267
|
+
const high = index === count - 1 ? max : min + step * (index + 1);
|
|
268
|
+
return {
|
|
269
|
+
min: low,
|
|
270
|
+
max: high,
|
|
271
|
+
count: values.filter((value) => value >= low && (index === count - 1 ? value <= high : value < high)).length
|
|
272
|
+
};
|
|
273
|
+
}).filter((bin) => bin.count > 0);
|
|
274
|
+
}
|
|
275
|
+
function bar(value, max, width, ch, color, colorName) {
|
|
276
|
+
const filled = Math.max(0, Math.min(width, Math.round(value / Math.max(max, 1e-6) * width)));
|
|
277
|
+
return paint(ch.repeat(filled).padEnd(width, "."), colorName, color);
|
|
278
|
+
}
|
|
279
|
+
function stackedTokenBar(input, output, max, width, color) {
|
|
280
|
+
const inputWidth = Math.round(input / Math.max(max, 1) * width);
|
|
281
|
+
const outputWidth = Math.round(output / Math.max(max, 1) * width);
|
|
282
|
+
const clippedInput = Math.min(width, inputWidth);
|
|
283
|
+
const clippedOutput = Math.min(width - clippedInput, outputWidth);
|
|
284
|
+
const rest = Math.max(0, width - clippedInput - clippedOutput);
|
|
285
|
+
return `${paint("#".repeat(clippedInput), "cyan", color)}${paint("+".repeat(clippedOutput), "green", color)}${".".repeat(rest)}`;
|
|
286
|
+
}
|
|
287
|
+
function sectionTitle(title, focused, color) {
|
|
288
|
+
return paint(focused ? `[${title}]` : title, focused ? "cyan" : "bold", color);
|
|
289
|
+
}
|
|
290
|
+
function statusText(status, color) {
|
|
291
|
+
if ([
|
|
292
|
+
"completed",
|
|
293
|
+
"done",
|
|
294
|
+
"delivered",
|
|
295
|
+
"valid"
|
|
296
|
+
].includes(status)) return paint(status.toUpperCase(), "green", color);
|
|
297
|
+
if ([
|
|
298
|
+
"failed",
|
|
299
|
+
"down",
|
|
300
|
+
"cancelled",
|
|
301
|
+
"invalid",
|
|
302
|
+
"error"
|
|
303
|
+
].includes(status)) return paint(status.toUpperCase(), "red", color);
|
|
304
|
+
if ([
|
|
305
|
+
"running",
|
|
306
|
+
"spawned",
|
|
307
|
+
"settled"
|
|
308
|
+
].includes(status)) return paint(status.toUpperCase(), "yellow", color);
|
|
309
|
+
return paint(status.toUpperCase(), "white", color);
|
|
310
|
+
}
|
|
311
|
+
function selectSupervisor(snapshot, requested) {
|
|
312
|
+
if (requested) {
|
|
313
|
+
const found = snapshot.supervisors.find((supervisor) => supervisor.id === requested);
|
|
314
|
+
if (found) return found;
|
|
315
|
+
}
|
|
316
|
+
return snapshot.supervisors[0];
|
|
317
|
+
}
|
|
318
|
+
function selectWorker(supervisor, requested) {
|
|
319
|
+
if (requested) {
|
|
320
|
+
const found = supervisor.workers.find((worker) => worker.id === requested);
|
|
321
|
+
if (found) return found;
|
|
322
|
+
}
|
|
323
|
+
return supervisor.workers[0];
|
|
324
|
+
}
|
|
325
|
+
function formatClock(value) {
|
|
326
|
+
const parsed = parseTime(value);
|
|
327
|
+
if (parsed === void 0) return "--:--:--";
|
|
328
|
+
return new Date(parsed).toISOString().slice(11, 19);
|
|
329
|
+
}
|
|
330
|
+
function formatMoney(value) {
|
|
331
|
+
if (value === 0) return "$0";
|
|
332
|
+
if (Math.abs(value) < .01) return `$${value.toFixed(4)}`;
|
|
333
|
+
return `$${value.toFixed(2)}`;
|
|
334
|
+
}
|
|
335
|
+
function formatTokens(value) {
|
|
336
|
+
if (value >= 1e6) return `${(value / 1e6).toFixed(1)}m`;
|
|
337
|
+
if (value >= 1e3) return `${(value / 1e3).toFixed(1)}k`;
|
|
338
|
+
return `${Math.round(value)}`;
|
|
339
|
+
}
|
|
340
|
+
function formatDuration(ms) {
|
|
341
|
+
if (!Number.isFinite(ms) || ms <= 0) return "0s";
|
|
342
|
+
if (ms < 1e3) return `${Math.round(ms)}ms`;
|
|
343
|
+
const seconds = ms / 1e3;
|
|
344
|
+
if (seconds < 60) return `${seconds.toFixed(seconds < 10 ? 1 : 0)}s`;
|
|
345
|
+
const minutes = seconds / 60;
|
|
346
|
+
if (minutes < 60) return `${minutes.toFixed(minutes < 10 ? 1 : 0)}m`;
|
|
347
|
+
return `${(minutes / 60).toFixed(1)}h`;
|
|
348
|
+
}
|
|
349
|
+
function compact(value, width) {
|
|
350
|
+
if (width <= 1) return "";
|
|
351
|
+
if (value.length <= width) return value;
|
|
352
|
+
return `${value.slice(0, width - 1)}~`;
|
|
353
|
+
}
|
|
354
|
+
function pad(value, width) {
|
|
355
|
+
const visible = visibleLength(value);
|
|
356
|
+
if (visible >= width) return fitAnsi(value, width);
|
|
357
|
+
return `${value}${" ".repeat(width - visible)}`;
|
|
358
|
+
}
|
|
359
|
+
function rule(width, color) {
|
|
360
|
+
return paint("-".repeat(width), "dim", color);
|
|
361
|
+
}
|
|
362
|
+
function paint(value, colorName, enabled) {
|
|
363
|
+
if (!enabled || !colorName) return value;
|
|
364
|
+
return `${colors[colorName]}${value}${colors.reset}`;
|
|
365
|
+
}
|
|
366
|
+
function visibleLength(value) {
|
|
367
|
+
return stripAnsi(value).length;
|
|
368
|
+
}
|
|
369
|
+
function stripAnsi(value) {
|
|
370
|
+
return value.replace(ansiPattern, "");
|
|
371
|
+
}
|
|
372
|
+
function fitAnsi(value, width) {
|
|
373
|
+
if (visibleLength(value) <= width) return value;
|
|
374
|
+
let out = "";
|
|
375
|
+
let visible = 0;
|
|
376
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
377
|
+
const ch = value[i];
|
|
378
|
+
if (ch === "\x1B") {
|
|
379
|
+
const rest = value.slice(i);
|
|
380
|
+
const match = /^\x1b\[[0-9;?]*[ -/]*[@-~]/.exec(rest);
|
|
381
|
+
if (match) {
|
|
382
|
+
out += match[0];
|
|
383
|
+
i += match[0].length - 1;
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
if (visible >= width - 1) {
|
|
388
|
+
out += "~";
|
|
389
|
+
break;
|
|
390
|
+
}
|
|
391
|
+
out += ch;
|
|
392
|
+
visible += 1;
|
|
393
|
+
}
|
|
394
|
+
if (out.includes("\x1B[") && !out.endsWith(colors.reset)) out += colors.reset;
|
|
395
|
+
return out;
|
|
396
|
+
}
|
|
397
|
+
//#endregion
|
|
398
|
+
//#region src/tui/top-app.ts
|
|
399
|
+
/**
|
|
400
|
+
* The interactive side of the supervisor-run TUI: a keypress/mouse loop over the frames
|
|
401
|
+
* `./top-frame` renders, plus the operator controls that write back to the run — steer a live
|
|
402
|
+
* worker, cancel one worker, and request cancellation of a whole run.
|
|
403
|
+
*
|
|
404
|
+
* The controls go through the owned run layout: steers via `writeWorkerSteer` (the durable inbox
|
|
405
|
+
* append) and worker cancellation via `cancelWorker` (the acknowledged operation the runtime's
|
|
406
|
+
* turn loop applies). Run-level cancellation targets the ROOT, which has no acknowledged runtime
|
|
407
|
+
* path yet for a non-retained tree — it stays a `cancel.request.json` write inside the run
|
|
408
|
+
* directory for a host process to honor. Nothing here joins a path from the workspace root.
|
|
409
|
+
*
|
|
410
|
+
* Extracted from the `loops` repo (`src/top.ts`). The module-level singletons are the origin's
|
|
411
|
+
* shape and are kept: this drives one terminal, and `runTopApp` is its one entry point.
|
|
412
|
+
*
|
|
413
|
+
* @experimental
|
|
414
|
+
*/
|
|
415
|
+
let state = initialState([], process.cwd());
|
|
416
|
+
let snapshot = {
|
|
417
|
+
root: state.root,
|
|
418
|
+
generatedAt: 0,
|
|
419
|
+
supervisors: [],
|
|
420
|
+
completeness: "complete",
|
|
421
|
+
diagnostics: [],
|
|
422
|
+
discovered: 0,
|
|
423
|
+
loaded: 0
|
|
424
|
+
};
|
|
425
|
+
let targets = [];
|
|
426
|
+
let timer;
|
|
427
|
+
/**
|
|
428
|
+
* Render exactly one frame and return it. This is the non-interactive path — `--once`, a pipe, a
|
|
429
|
+
* test — so it never touches raw mode, the alternate screen, or `process.exit`.
|
|
430
|
+
*/
|
|
431
|
+
function renderTopOnce(options = {}) {
|
|
432
|
+
state = initialState([...options.argv ?? process.argv.slice(2)], options.cwd ?? process.cwd());
|
|
433
|
+
snapshot = loadTopSnapshot(state.root);
|
|
434
|
+
clampSelection();
|
|
435
|
+
return renderTopFrameWithLayout(snapshot, {
|
|
436
|
+
color: state.color,
|
|
437
|
+
width: process.stdout.columns ?? 128,
|
|
438
|
+
focus: state.focus,
|
|
439
|
+
mode: state.mode,
|
|
440
|
+
...state.selectedSupervisorId ? { selectedSupervisorId: state.selectedSupervisorId } : {},
|
|
441
|
+
...state.selectedWorkerId ? { selectedWorkerId: state.selectedWorkerId } : {},
|
|
442
|
+
steerInput: renderSteerInputState()
|
|
443
|
+
}).frame;
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Run the TUI. With a TTY on both ends and no `--once` this takes over the terminal until `q`;
|
|
447
|
+
* otherwise it writes a single frame to stdout and returns.
|
|
448
|
+
*/
|
|
449
|
+
function runTopApp(options = {}) {
|
|
450
|
+
const args = [...options.argv ?? process.argv.slice(2)];
|
|
451
|
+
if (!process.stdout.isTTY || !process.stdin.isTTY || args.includes("--once")) {
|
|
452
|
+
process.stdout.write(renderTopOnce(options));
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
state = initialState(args, options.cwd ?? process.cwd());
|
|
456
|
+
snapshot = loadTopSnapshot(state.root);
|
|
457
|
+
start();
|
|
458
|
+
}
|
|
459
|
+
function initialState(args, cwd) {
|
|
460
|
+
return {
|
|
461
|
+
root: resolve(args.find((arg) => !arg.startsWith("-")) ?? cwd),
|
|
462
|
+
color: Boolean(process.stdout.isTTY) && !args.includes("--no-color"),
|
|
463
|
+
focus: args.includes("--detail") ? "workers" : "supervisors",
|
|
464
|
+
mode: args.includes("--log") ? "log" : args.includes("--detail") ? "detail" : "overview",
|
|
465
|
+
selectedSupervisorId: void 0,
|
|
466
|
+
selectedWorkerId: void 0,
|
|
467
|
+
notice: void 0,
|
|
468
|
+
steerInput: {
|
|
469
|
+
active: false,
|
|
470
|
+
value: ""
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
function start() {
|
|
475
|
+
process.stdout.write("\x1B[?1049h\x1B[?25l\x1B[?1000h\x1B[?1006h");
|
|
476
|
+
emitKeypressEvents(process.stdin);
|
|
477
|
+
process.stdin.setRawMode(true);
|
|
478
|
+
process.stdin.resume();
|
|
479
|
+
process.stdin.on("keypress", onKey);
|
|
480
|
+
process.stdin.on("data", onData);
|
|
481
|
+
process.on("SIGINT", stop);
|
|
482
|
+
draw();
|
|
483
|
+
timer = setInterval(draw, 1e3);
|
|
484
|
+
}
|
|
485
|
+
function stop() {
|
|
486
|
+
if (timer) clearInterval(timer);
|
|
487
|
+
process.stdin.setRawMode(false);
|
|
488
|
+
process.stdout.write("\x1B[?1000l\x1B[?1006l\x1B[?25h\x1B[?1049l");
|
|
489
|
+
process.exit(0);
|
|
490
|
+
}
|
|
491
|
+
function draw() {
|
|
492
|
+
snapshot = loadTopSnapshot(state.root);
|
|
493
|
+
clampSelection();
|
|
494
|
+
const rendered = renderTopFrameWithLayout(snapshot, {
|
|
495
|
+
width: process.stdout.columns ?? 128,
|
|
496
|
+
height: process.stdout.rows ?? 42,
|
|
497
|
+
color: state.color,
|
|
498
|
+
focus: state.focus,
|
|
499
|
+
mode: state.mode,
|
|
500
|
+
...state.notice ? { notice: state.notice } : {},
|
|
501
|
+
...state.selectedSupervisorId ? { selectedSupervisorId: state.selectedSupervisorId } : {},
|
|
502
|
+
...state.selectedWorkerId ? { selectedWorkerId: state.selectedWorkerId } : {},
|
|
503
|
+
steerInput: renderSteerInputState()
|
|
504
|
+
});
|
|
505
|
+
targets = rendered.targets;
|
|
506
|
+
process.stdout.write(`\x1b[2J\x1b[H${rendered.frame}`);
|
|
507
|
+
}
|
|
508
|
+
function onKey(str, key) {
|
|
509
|
+
state.notice = void 0;
|
|
510
|
+
if (key.ctrl && key.name === "c") stop();
|
|
511
|
+
if (state.steerInput.active) {
|
|
512
|
+
handleSteerInput(str, key);
|
|
513
|
+
draw();
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
if (key.name === "q" || key.name === "escape") stop();
|
|
517
|
+
if (key.name === "tab") state.focus = state.focus === "supervisors" ? "workers" : "supervisors";
|
|
518
|
+
else if (key.name === "up") moveSelection(-1);
|
|
519
|
+
else if (key.name === "down") moveSelection(1);
|
|
520
|
+
else if (key.name === "left") moveSupervisor(-1);
|
|
521
|
+
else if (key.name === "right") moveSupervisor(1);
|
|
522
|
+
else if (key.name === "return") {
|
|
523
|
+
state.mode = "detail";
|
|
524
|
+
state.focus = "workers";
|
|
525
|
+
} else if (key.name === "o") state.mode = "overview";
|
|
526
|
+
else if (key.name === "l") state.mode = "log";
|
|
527
|
+
else if (key.name === "s") startSteerInput();
|
|
528
|
+
else if (key.name === "c") requestCancel();
|
|
529
|
+
draw();
|
|
530
|
+
}
|
|
531
|
+
function handleSteerInput(str, key) {
|
|
532
|
+
if (key.name === "escape") {
|
|
533
|
+
state.steerInput = {
|
|
534
|
+
active: false,
|
|
535
|
+
value: ""
|
|
536
|
+
};
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
if (key.name === "return") {
|
|
540
|
+
submitSteerInput();
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
if (key.name === "backspace") {
|
|
544
|
+
state.steerInput.value = state.steerInput.value.slice(0, -1);
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
if (key.ctrl && key.name === "u") {
|
|
548
|
+
state.steerInput.value = "";
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
if (key.ctrl || key.name === "tab" || key.name === "up" || key.name === "down" || key.name === "left" || key.name === "right") return;
|
|
552
|
+
if (!str || str === "\r" || str === "\n") return;
|
|
553
|
+
if ([...str].every((char) => char >= " " && char !== "")) state.steerInput.value += str;
|
|
554
|
+
}
|
|
555
|
+
function onData(data) {
|
|
556
|
+
const click = parseMouse(data.toString("utf8"));
|
|
557
|
+
if (!click) return;
|
|
558
|
+
const target = targets.find((candidate) => candidate.row === click.row);
|
|
559
|
+
if (!target) return;
|
|
560
|
+
state.notice = void 0;
|
|
561
|
+
if (target.kind === "supervisor") {
|
|
562
|
+
state.focus = "supervisors";
|
|
563
|
+
state.selectedSupervisorId = target.id;
|
|
564
|
+
state.selectedWorkerId = void 0;
|
|
565
|
+
} else {
|
|
566
|
+
state.focus = "workers";
|
|
567
|
+
state.mode = "detail";
|
|
568
|
+
state.selectedSupervisorId = target.supervisorId;
|
|
569
|
+
state.selectedWorkerId = target.id;
|
|
570
|
+
}
|
|
571
|
+
draw();
|
|
572
|
+
}
|
|
573
|
+
function startSteerInput() {
|
|
574
|
+
state.mode = "detail";
|
|
575
|
+
state.focus = "workers";
|
|
576
|
+
if (!selectedSupervisor()) {
|
|
577
|
+
state.notice = "no supervisor selected";
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
if (!selectedWorker()) {
|
|
581
|
+
state.notice = "no worker selected";
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
state.steerInput = {
|
|
585
|
+
active: true,
|
|
586
|
+
value: ""
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
function submitSteerInput() {
|
|
590
|
+
const supervisor = selectedSupervisor();
|
|
591
|
+
const worker = selectedWorker();
|
|
592
|
+
const message = state.steerInput.value;
|
|
593
|
+
state.steerInput = {
|
|
594
|
+
active: false,
|
|
595
|
+
value: ""
|
|
596
|
+
};
|
|
597
|
+
if (!supervisor) {
|
|
598
|
+
state.notice = "no supervisor selected";
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
if (!worker) {
|
|
602
|
+
state.notice = "no worker selected";
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
try {
|
|
606
|
+
const written = writeWorkerSteer(state.root, supervisor.id, worker.id, {
|
|
607
|
+
operationId: randomUUID(),
|
|
608
|
+
message,
|
|
609
|
+
source: "agent-runtime-top"
|
|
610
|
+
});
|
|
611
|
+
state.notice = `steer queued for ${supervisor.id}/${worker.label} (op ${written.request.operationId})`;
|
|
612
|
+
} catch (err) {
|
|
613
|
+
state.notice = `steer failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
function parseMouse(raw) {
|
|
617
|
+
const match = /\x1b\[<(\d+);(\d+);(\d+)([mM])/.exec(raw);
|
|
618
|
+
if (match?.[4] !== "M") return void 0;
|
|
619
|
+
const col = Number(match[2]);
|
|
620
|
+
const row = Number(match[3]);
|
|
621
|
+
if (!Number.isFinite(col) || !Number.isFinite(row)) return void 0;
|
|
622
|
+
return {
|
|
623
|
+
col: col - 1,
|
|
624
|
+
row: row - 1
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
function moveSelection(delta) {
|
|
628
|
+
if (state.focus === "supervisors") {
|
|
629
|
+
moveSupervisor(delta);
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
const supervisor = selectedSupervisor();
|
|
633
|
+
if (!supervisor || supervisor.workers.length === 0) return;
|
|
634
|
+
const next = wrap(Math.max(0, supervisor.workers.findIndex((worker) => worker.id === state.selectedWorkerId)) + delta, supervisor.workers.length);
|
|
635
|
+
const worker = supervisor.workers[next];
|
|
636
|
+
if (worker) state.selectedWorkerId = worker.id;
|
|
637
|
+
}
|
|
638
|
+
function moveSupervisor(delta) {
|
|
639
|
+
if (snapshot.supervisors.length === 0) return;
|
|
640
|
+
const next = wrap(Math.max(0, snapshot.supervisors.findIndex((supervisor) => supervisor.id === state.selectedSupervisorId)) + delta, snapshot.supervisors.length);
|
|
641
|
+
const supervisor = snapshot.supervisors[next];
|
|
642
|
+
if (!supervisor) return;
|
|
643
|
+
state.selectedSupervisorId = supervisor.id;
|
|
644
|
+
state.selectedWorkerId = supervisor.workers[0]?.id;
|
|
645
|
+
}
|
|
646
|
+
function clampSelection() {
|
|
647
|
+
if (snapshot.supervisors.length === 0) {
|
|
648
|
+
state.selectedSupervisorId = void 0;
|
|
649
|
+
state.selectedWorkerId = void 0;
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
const supervisor = selectedSupervisor() ?? snapshot.supervisors[0];
|
|
653
|
+
if (!supervisor) return;
|
|
654
|
+
state.selectedSupervisorId = supervisor.id;
|
|
655
|
+
const worker = supervisor.workers.find((candidate) => candidate.id === state.selectedWorkerId) ?? supervisor.workers[0];
|
|
656
|
+
state.selectedWorkerId = worker?.id;
|
|
657
|
+
}
|
|
658
|
+
function selectedSupervisor() {
|
|
659
|
+
return snapshot.supervisors.find((supervisor) => supervisor.id === state.selectedSupervisorId);
|
|
660
|
+
}
|
|
661
|
+
function selectedWorker() {
|
|
662
|
+
return selectedSupervisor()?.workers.find((worker) => worker.id === state.selectedWorkerId);
|
|
663
|
+
}
|
|
664
|
+
function renderSteerInputState() {
|
|
665
|
+
const worker = selectedWorker();
|
|
666
|
+
return {
|
|
667
|
+
active: state.steerInput.active,
|
|
668
|
+
value: state.steerInput.value,
|
|
669
|
+
...worker?.label ? { workerLabel: worker.label } : {}
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
function requestCancel() {
|
|
673
|
+
const supervisor = selectedSupervisor();
|
|
674
|
+
if (!supervisor) {
|
|
675
|
+
state.notice = "no supervisor selected";
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
if (supervisor.status !== "running") {
|
|
679
|
+
state.notice = `${supervisor.id} is ${supervisor.status}; no cancel request written`;
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
const worker = state.focus === "workers" ? selectedWorker() : void 0;
|
|
683
|
+
if (worker) {
|
|
684
|
+
if (worker.status !== "running") {
|
|
685
|
+
state.notice = `${worker.label} is ${worker.status}; nothing to cancel`;
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
try {
|
|
689
|
+
const record = cancelWorker(supervisor.stateDir, worker.id, randomUUID(), {
|
|
690
|
+
reason: "operator requested cancel from TUI",
|
|
691
|
+
source: "agent-runtime-top"
|
|
692
|
+
});
|
|
693
|
+
state.notice = record.effect === "unknown" ? `cancel queued for ${supervisor.id}/${worker.label} (op ${record.operationId}); awaiting runtime acknowledgement` : `cancel ${record.effect} for ${supervisor.id}/${worker.label} (op ${record.operationId})`;
|
|
694
|
+
} catch (err) {
|
|
695
|
+
state.notice = `cancel request failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
696
|
+
}
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
try {
|
|
700
|
+
const record = cancelRun(supervisor.stateDir, randomUUID(), {
|
|
701
|
+
reason: "operator requested cancel from TUI",
|
|
702
|
+
source: "agent-runtime-top"
|
|
703
|
+
});
|
|
704
|
+
state.notice = record.effect === "unknown" ? `run cancel queued for ${supervisor.id} (op ${record.operationId}); awaiting runtime acknowledgement` : `run cancel ${record.effect} for ${supervisor.id} (op ${record.operationId})`;
|
|
705
|
+
} catch (err) {
|
|
706
|
+
state.notice = `cancel request failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
function wrap(value, size) {
|
|
710
|
+
return (value % size + size) % size;
|
|
711
|
+
}
|
|
712
|
+
//#endregion
|
|
3
713
|
//#region src/tui/bin.ts
|
|
4
714
|
/**
|
|
5
715
|
* `agent-runtime-top` — watch the supervisor runs of one workspace.
|