@neotx/cli 0.1.0-alpha.24 → 0.1.0-alpha.25

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 (41) hide show
  1. package/dist/child-5X5IHAKS.js +92 -0
  2. package/dist/child-5X5IHAKS.js.map +1 -0
  3. package/dist/child-mode-IB3XSUHD.js +8 -0
  4. package/dist/child-mode-IB3XSUHD.js.map +1 -0
  5. package/dist/chunk-4TQ3Q6IE.js +43 -0
  6. package/dist/chunk-4TQ3Q6IE.js.map +1 -0
  7. package/dist/chunk-6PSXZ3UV.js +46 -0
  8. package/dist/chunk-6PSXZ3UV.js.map +1 -0
  9. package/dist/chunk-V5SN5F73.js +54 -0
  10. package/dist/chunk-V5SN5F73.js.map +1 -0
  11. package/dist/daemon/child-supervisor-worker.js +136 -0
  12. package/dist/daemon/child-supervisor-worker.js.map +1 -0
  13. package/dist/daemon/supervisor-worker.js +9 -1
  14. package/dist/daemon/supervisor-worker.js.map +1 -1
  15. package/dist/daemon/worker.js +16 -3
  16. package/dist/daemon/worker.js.map +1 -1
  17. package/dist/directive-7WM2Q2UW.js +259 -0
  18. package/dist/directive-7WM2Q2UW.js.map +1 -0
  19. package/dist/do-F5XW2ELZ.js +83 -0
  20. package/dist/do-F5XW2ELZ.js.map +1 -0
  21. package/dist/index.js +8 -5
  22. package/dist/index.js.map +1 -1
  23. package/dist/{log-PTHLI7ZN.js → log-ZLIAIBZQ.js} +64 -9
  24. package/dist/log-ZLIAIBZQ.js.map +1 -0
  25. package/dist/{memory-SDZ57W2S.js → memory-CW6E65SQ.js} +112 -62
  26. package/dist/memory-CW6E65SQ.js.map +1 -0
  27. package/dist/{run-MWHIQUSY.js → run-NV762V5B.js} +56 -22
  28. package/dist/run-NV762V5B.js.map +1 -0
  29. package/dist/{supervise-XMZRNODO.js → supervise-BWIKWNHH.js} +68 -41
  30. package/dist/supervise-BWIKWNHH.js.map +1 -0
  31. package/dist/{supervisor-3RUX5SPH.js → supervisor-N4D5EWCC.js} +1 -1
  32. package/dist/tui-LSW7VVK6.js +1319 -0
  33. package/dist/tui-LSW7VVK6.js.map +1 -0
  34. package/package.json +4 -4
  35. package/dist/log-PTHLI7ZN.js.map +0 -1
  36. package/dist/memory-SDZ57W2S.js.map +0 -1
  37. package/dist/run-MWHIQUSY.js.map +0 -1
  38. package/dist/supervise-XMZRNODO.js.map +0 -1
  39. package/dist/tui-67VJ5VBA.js +0 -842
  40. package/dist/tui-67VJ5VBA.js.map +0 -1
  41. /package/dist/{supervisor-3RUX5SPH.js.map → supervisor-N4D5EWCC.js.map} +0 -0
@@ -0,0 +1,1319 @@
1
+ // src/tui/index.ts
2
+ import { render } from "ink";
3
+ import React from "react";
4
+
5
+ // src/tui/supervisor-tui.tsx
6
+ import { randomUUID } from "crypto";
7
+ import { appendFile, mkdir, readFile } from "fs/promises";
8
+ import path from "path";
9
+ import {
10
+ DecisionStore,
11
+ getFocusedSupervisorDir,
12
+ getSupervisorActivityPath,
13
+ getSupervisorChildrenPath,
14
+ getSupervisorDecisionsPath,
15
+ getSupervisorDir,
16
+ getSupervisorInboxPath,
17
+ getSupervisorStatePath,
18
+ loadGlobalConfig,
19
+ readChildrenFile,
20
+ TaskStore
21
+ } from "@neotx/core";
22
+ import { Box as Box4, Text as Text4, useApp, useInput, useStdout } from "ink";
23
+ import TextInput2 from "ink-text-input";
24
+ import { useCallback, useEffect, useRef, useState } from "react";
25
+
26
+ // src/tui/components/child-detail.tsx
27
+ import { Box, Text } from "ink";
28
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
29
+ var TYPE_ICONS = {
30
+ heartbeat: "\u2665",
31
+ decision: "\u2605",
32
+ action: "\u26A1",
33
+ error: "\u2716",
34
+ event: "\u25C6",
35
+ message: "\u2709",
36
+ thinking: "\u25C7",
37
+ plan: "\u25B8",
38
+ dispatch: "\u2197",
39
+ tool_use: "\u2298"
40
+ };
41
+ var TYPE_COLORS = {
42
+ heartbeat: "#6ee7b7",
43
+ decision: "#fbbf24",
44
+ action: "#60a5fa",
45
+ error: "#f87171",
46
+ event: "#c084fc",
47
+ message: "#67e8f9",
48
+ thinking: "#a78bfa",
49
+ plan: "#34d399",
50
+ dispatch: "#f472b6",
51
+ tool_use: "#38bdf8"
52
+ };
53
+ function formatTime(timestamp) {
54
+ return timestamp.slice(11, 19);
55
+ }
56
+ function formatTimeAgo(timestamp) {
57
+ const ms = Date.now() - new Date(timestamp).getTime();
58
+ const seconds = Math.floor(ms / 1e3);
59
+ if (seconds < 60) return `${seconds}s ago`;
60
+ const minutes = Math.floor(seconds / 60);
61
+ if (minutes < 60) return `${minutes}m ago`;
62
+ const hours = Math.floor(minutes / 60);
63
+ return `${hours}h ago`;
64
+ }
65
+ var STATUS_COLORS = {
66
+ running: "#4ade80",
67
+ blocked: "#fbbf24",
68
+ stalled: "#f97316",
69
+ complete: "#818cf8",
70
+ failed: "#f87171"
71
+ };
72
+ function ChildDetail({
73
+ handle,
74
+ activity,
75
+ maxActivityLines
76
+ }) {
77
+ const statusColor = STATUS_COLORS[handle.status] ?? "#9ca3af";
78
+ const visible = activity.slice(-maxActivityLines);
79
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
80
+ /* @__PURE__ */ jsxs(Box, { paddingX: 1, gap: 1, children: [
81
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u251C" }),
82
+ /* @__PURE__ */ jsx(Text, { color: "#c084fc", bold: true, children: handle.supervisorId }),
83
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
84
+ /* @__PURE__ */ jsx(Text, { color: statusColor, bold: true, children: handle.status.toUpperCase() }),
85
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
86
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
87
+ "$",
88
+ handle.costUsd.toFixed(2)
89
+ ] }),
90
+ handle.lastProgressAt && /* @__PURE__ */ jsxs(Fragment, { children: [
91
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
92
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: formatTimeAgo(handle.lastProgressAt) })
93
+ ] })
94
+ ] }),
95
+ /* @__PURE__ */ jsxs(Box, { paddingX: 1, gap: 1, children: [
96
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
97
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "obj:" }),
98
+ /* @__PURE__ */ jsx(Text, { wrap: "truncate", children: handle.objective })
99
+ ] }),
100
+ /* @__PURE__ */ jsxs(Box, { paddingX: 1, gap: 1, children: [
101
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u251C" }),
102
+ /* @__PURE__ */ jsx(Text, { dimColor: true, bold: true, children: "ACTIVITY" }),
103
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500".repeat(15) })
104
+ ] }),
105
+ visible.length === 0 ? /* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502 No activity yet..." }) }) : visible.map((entry, idx) => {
106
+ const icon = TYPE_ICONS[entry.type] ?? "\xB7";
107
+ const color = TYPE_COLORS[entry.type] ?? "#9ca3af";
108
+ const isLatest = idx === visible.length - 1;
109
+ const isOld = idx < visible.length - 4;
110
+ return /* @__PURE__ */ jsxs(Box, { gap: 1, paddingX: 1, children: [
111
+ /* @__PURE__ */ jsx(Text, { dimColor: isOld, children: formatTime(entry.timestamp) }),
112
+ /* @__PURE__ */ jsx(Text, { color, dimColor: isOld, bold: isLatest, children: icon }),
113
+ /* @__PURE__ */ jsx(Text, { dimColor: isOld, bold: isLatest, wrap: "truncate", children: entry.summary })
114
+ ] }, entry.id);
115
+ })
116
+ ] });
117
+ }
118
+
119
+ // src/tui/components/child-input.tsx
120
+ import { Box as Box2, Text as Text2 } from "ink";
121
+ import TextInput from "ink-text-input";
122
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
123
+ function ChildInput({
124
+ handle,
125
+ mode,
126
+ value,
127
+ onChange,
128
+ onSubmit
129
+ }) {
130
+ const isBlocked = handle.status === "blocked";
131
+ if (mode === "idle") {
132
+ return /* @__PURE__ */ jsx2(Box2, { paddingX: 1, gap: 2, flexDirection: "column", children: /* @__PURE__ */ jsxs2(Box2, { paddingX: 1, gap: 1, children: [
133
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "\u2514" }),
134
+ /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
135
+ /* @__PURE__ */ jsx2(Text2, { bold: true, children: "i" }),
136
+ " inject context"
137
+ ] }),
138
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "\xB7" }),
139
+ /* @__PURE__ */ jsxs2(Text2, { dimColor: !isBlocked, children: [
140
+ /* @__PURE__ */ jsx2(Text2, { bold: isBlocked, children: "u" }),
141
+ " unblock",
142
+ !isBlocked ? " (not blocked)" : ""
143
+ ] }),
144
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "\xB7" }),
145
+ /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
146
+ /* @__PURE__ */ jsx2(Text2, { bold: true, color: "#f87171", children: "k" }),
147
+ " ",
148
+ "kill"
149
+ ] }),
150
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "\xB7" }),
151
+ /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
152
+ /* @__PURE__ */ jsx2(Text2, { bold: true, children: "esc" }),
153
+ " back"
154
+ ] })
155
+ ] }) });
156
+ }
157
+ if (mode === "inject") {
158
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
159
+ /* @__PURE__ */ jsxs2(Box2, { paddingX: 1, gap: 1, children: [
160
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "\u2514" }),
161
+ /* @__PURE__ */ jsx2(Text2, { color: "#60a5fa", bold: true, children: "INJECT" }),
162
+ /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
163
+ "\u2192 ",
164
+ handle.supervisorId
165
+ ] })
166
+ ] }),
167
+ /* @__PURE__ */ jsxs2(Box2, { paddingX: 1, gap: 1, children: [
168
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " " }),
169
+ /* @__PURE__ */ jsx2(Text2, { color: "#60a5fa", bold: true, children: "\u276F" }),
170
+ /* @__PURE__ */ jsx2(
171
+ TextInput,
172
+ {
173
+ value,
174
+ onChange,
175
+ onSubmit,
176
+ focus: true,
177
+ placeholder: "context to inject..."
178
+ }
179
+ )
180
+ ] }),
181
+ /* @__PURE__ */ jsx2(Box2, { paddingX: 1, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " enter send \xB7 esc cancel" }) })
182
+ ] });
183
+ }
184
+ if (mode === "unblock") {
185
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
186
+ /* @__PURE__ */ jsxs2(Box2, { paddingX: 1, gap: 1, children: [
187
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "\u2514" }),
188
+ /* @__PURE__ */ jsx2(Text2, { color: "#fbbf24", bold: true, children: "UNBLOCK" }),
189
+ /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
190
+ "\u2192 ",
191
+ handle.supervisorId
192
+ ] })
193
+ ] }),
194
+ /* @__PURE__ */ jsxs2(Box2, { paddingX: 1, gap: 1, children: [
195
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " " }),
196
+ /* @__PURE__ */ jsx2(Text2, { color: "#fbbf24", bold: true, children: "\u276F" }),
197
+ /* @__PURE__ */ jsx2(
198
+ TextInput,
199
+ {
200
+ value,
201
+ onChange,
202
+ onSubmit,
203
+ focus: true,
204
+ placeholder: "your answer..."
205
+ }
206
+ )
207
+ ] }),
208
+ /* @__PURE__ */ jsx2(Box2, { paddingX: 1, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " enter send \xB7 esc cancel" }) })
209
+ ] });
210
+ }
211
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
212
+ /* @__PURE__ */ jsxs2(Box2, { paddingX: 1, gap: 1, children: [
213
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "\u2514" }),
214
+ /* @__PURE__ */ jsx2(Text2, { color: "#f87171", bold: true, children: "KILL" }),
215
+ /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
216
+ "\u2192 ",
217
+ handle.supervisorId
218
+ ] }),
219
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "\u2014 type" }),
220
+ /* @__PURE__ */ jsx2(Text2, { bold: true, color: "#f87171", children: "stop" }),
221
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "to confirm" })
222
+ ] }),
223
+ /* @__PURE__ */ jsxs2(Box2, { paddingX: 1, gap: 1, children: [
224
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " " }),
225
+ /* @__PURE__ */ jsx2(Text2, { color: "#f87171", bold: true, children: "\u276F" }),
226
+ /* @__PURE__ */ jsx2(
227
+ TextInput,
228
+ {
229
+ value,
230
+ onChange,
231
+ onSubmit,
232
+ focus: true,
233
+ placeholder: 'type "stop" to kill...'
234
+ }
235
+ )
236
+ ] }),
237
+ /* @__PURE__ */ jsx2(Box2, { paddingX: 1, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " esc cancel" }) })
238
+ ] });
239
+ }
240
+
241
+ // src/tui/components/child-list.tsx
242
+ import { Box as Box3, Text as Text3 } from "ink";
243
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
244
+ var STATUS_COLORS2 = {
245
+ running: "#4ade80",
246
+ blocked: "#fbbf24",
247
+ stalled: "#f97316",
248
+ complete: "#818cf8",
249
+ failed: "#f87171"
250
+ };
251
+ var STATUS_ICONS = {
252
+ running: "\u25CF",
253
+ blocked: "\u25C6",
254
+ stalled: "\u25CC",
255
+ complete: "\u2713",
256
+ failed: "\u2716"
257
+ };
258
+ var STATUS_LABELS = {
259
+ running: "RUN",
260
+ blocked: "BLK",
261
+ stalled: "STL",
262
+ complete: "DONE",
263
+ failed: "FAIL"
264
+ };
265
+ function truncate(s, max) {
266
+ if (s.length <= max) return s;
267
+ return `${s.slice(0, max - 1)}\u2026`;
268
+ }
269
+ function ChildRow({ handle, isSelected }) {
270
+ const color = STATUS_COLORS2[handle.status] ?? "#9ca3af";
271
+ const icon = STATUS_ICONS[handle.status] ?? "\xB7";
272
+ const label = (STATUS_LABELS[handle.status] ?? handle.status).padEnd(4);
273
+ const cost = `$${handle.costUsd.toFixed(2)}`;
274
+ const id = truncate(handle.supervisorId, 12);
275
+ const objective = truncate(handle.objective, 28);
276
+ return /* @__PURE__ */ jsxs3(Box3, { gap: 1, paddingX: 1, children: [
277
+ /* @__PURE__ */ jsx3(Text3, { color: isSelected ? "#c084fc" : "#4b5563", children: isSelected ? "\u25B6" : " " }),
278
+ /* @__PURE__ */ jsx3(Text3, { color, bold: true, children: icon }),
279
+ /* @__PURE__ */ jsx3(Text3, { color, bold: true, children: label }),
280
+ /* @__PURE__ */ jsx3(Text3, { bold: isSelected, dimColor: !isSelected, children: id }),
281
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "\xB7" }),
282
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: cost }),
283
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "\xB7" }),
284
+ /* @__PURE__ */ jsx3(Text3, { dimColor: !isSelected, wrap: "truncate", children: objective })
285
+ ] });
286
+ }
287
+ function ChildList({
288
+ handles,
289
+ selectedIndex
290
+ }) {
291
+ if (handles.length === 0) {
292
+ return /* @__PURE__ */ jsx3(Box3, { paddingX: 2, children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "No focused supervisors running" }) });
293
+ }
294
+ return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
295
+ /* @__PURE__ */ jsxs3(Box3, { paddingX: 1, gap: 1, children: [
296
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "\u251C" }),
297
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, bold: true, children: "CHILDREN" }),
298
+ /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
299
+ "(",
300
+ handles.length,
301
+ ")"
302
+ ] }),
303
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "\u2500".repeat(20) })
304
+ ] }),
305
+ handles.map((handle, idx) => /* @__PURE__ */ jsx3(ChildRow, { handle, isSelected: idx === selectedIndex }, handle.supervisorId))
306
+ ] });
307
+ }
308
+
309
+ // src/tui/supervisor-tui.tsx
310
+ import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
311
+ var MAX_VISIBLE_ENTRIES = 24;
312
+ var MAX_CHILD_ACTIVITY = 12;
313
+ var POLL_INTERVAL_MS = 1500;
314
+ var ANIMATION_TICK_MS = 400;
315
+ var SPARK_CHARS = ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
316
+ var BLOCK_FULL = "\u2588";
317
+ var BLOCK_EMPTY = "\u2591";
318
+ var PULSE_FRAMES = ["\u25C9", "\u25CE", "\u25CB", "\u25CE"];
319
+ var IDLE_FRAMES = ["\u25CC", "\u25CC", "\u25CC", "\u25CC"];
320
+ var TYPE_ICONS2 = {
321
+ heartbeat: "\u2665",
322
+ decision: "\u2605",
323
+ action: "\u26A1",
324
+ error: "\u2716",
325
+ event: "\u25C6",
326
+ message: "\u2709",
327
+ thinking: "\u25C7",
328
+ plan: "\u25B8",
329
+ dispatch: "\u2197",
330
+ tool_use: "\u2298"
331
+ };
332
+ var TYPE_COLORS2 = {
333
+ heartbeat: "#6ee7b7",
334
+ decision: "#fbbf24",
335
+ action: "#60a5fa",
336
+ error: "#f87171",
337
+ event: "#c084fc",
338
+ message: "#67e8f9",
339
+ thinking: "#a78bfa",
340
+ plan: "#34d399",
341
+ dispatch: "#f472b6",
342
+ tool_use: "#38bdf8"
343
+ };
344
+ var TYPE_LABELS = {
345
+ heartbeat: "BEAT",
346
+ decision: "DECIDE",
347
+ action: "ACTION",
348
+ error: "ERROR",
349
+ event: "EVENT",
350
+ message: "MSG",
351
+ thinking: "THINK",
352
+ plan: "PLAN",
353
+ dispatch: "SEND",
354
+ tool_use: "TOOL"
355
+ };
356
+ function formatTime2(timestamp) {
357
+ return timestamp.slice(11, 19);
358
+ }
359
+ function formatUptime(startedAt) {
360
+ const ms = Date.now() - new Date(startedAt).getTime();
361
+ const seconds = Math.floor(ms / 1e3);
362
+ const minutes = Math.floor(seconds / 60);
363
+ const hours = Math.floor(minutes / 60);
364
+ const days = Math.floor(hours / 24);
365
+ if (days > 0) return `${days}d ${hours % 24}h`;
366
+ if (hours > 0) return `${hours}h ${minutes % 60}m`;
367
+ if (minutes > 0) return `${minutes}m ${seconds % 60}s`;
368
+ return `${seconds}s`;
369
+ }
370
+ function formatTimeAgo2(timestamp) {
371
+ const ms = Date.now() - new Date(timestamp).getTime();
372
+ const seconds = Math.floor(ms / 1e3);
373
+ if (seconds < 60) return `${seconds}s ago`;
374
+ const minutes = Math.floor(seconds / 60);
375
+ if (minutes < 60) return `${minutes}m ago`;
376
+ const hours = Math.floor(minutes / 60);
377
+ return `${hours}h ago`;
378
+ }
379
+ function buildProgressBar(ratio, width) {
380
+ const clamped = Math.max(0, Math.min(1, ratio));
381
+ const filledCount = Math.round(clamped * width);
382
+ return {
383
+ filled: BLOCK_FULL.repeat(filledCount),
384
+ empty: BLOCK_EMPTY.repeat(width - filledCount)
385
+ };
386
+ }
387
+ function buildSparkline(values, width) {
388
+ if (values.length === 0) return "\u2581".repeat(width);
389
+ const recent = values.slice(-width);
390
+ const max = Math.max(...recent, 1e-3);
391
+ return recent.map((v) => {
392
+ const idx = Math.min(
393
+ Math.floor(v / max * (SPARK_CHARS.length - 1)),
394
+ SPARK_CHARS.length - 1
395
+ );
396
+ return SPARK_CHARS[idx];
397
+ }).join("");
398
+ }
399
+ function extractCostHistory(entries) {
400
+ return entries.filter((e) => e.type === "heartbeat" && e.summary.includes("complete")).map((e) => {
401
+ const detail = e.detail;
402
+ return typeof detail?.costUsd === "number" ? detail.costUsd : 0;
403
+ });
404
+ }
405
+ function useAnimationFrame() {
406
+ const [frame, setFrame] = useState(0);
407
+ useEffect(() => {
408
+ const interval = setInterval(() => setFrame((f) => f + 1), ANIMATION_TICK_MS);
409
+ return () => clearInterval(interval);
410
+ }, []);
411
+ return frame;
412
+ }
413
+ function useClock() {
414
+ const [time, setTime] = useState(() => (/* @__PURE__ */ new Date()).toLocaleTimeString());
415
+ useEffect(() => {
416
+ const interval = setInterval(() => setTime((/* @__PURE__ */ new Date()).toLocaleTimeString()), 1e3);
417
+ return () => clearInterval(interval);
418
+ }, []);
419
+ return time;
420
+ }
421
+ function Logo() {
422
+ return /* @__PURE__ */ jsxs4(Box4, { paddingX: 1, gap: 1, children: [
423
+ /* @__PURE__ */ jsx4(Text4, { color: "#c084fc", bold: true, children: "\u25C6" }),
424
+ /* @__PURE__ */ jsxs4(Text4, { bold: true, children: [
425
+ /* @__PURE__ */ jsx4(Text4, { color: "#c084fc", children: "N" }),
426
+ /* @__PURE__ */ jsx4(Text4, { color: "#a78bfa", children: "E" }),
427
+ /* @__PURE__ */ jsx4(Text4, { color: "#818cf8", children: "O" })
428
+ ] }),
429
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "SUPERVISOR" })
430
+ ] });
431
+ }
432
+ function LiveIndicator({ frame, isRunning }) {
433
+ const frames = isRunning ? PULSE_FRAMES : IDLE_FRAMES;
434
+ const dot = frames[frame % frames.length];
435
+ return /* @__PURE__ */ jsxs4(Box4, { paddingX: 1, children: [
436
+ /* @__PURE__ */ jsx4(Text4, { color: isRunning ? "#4ade80" : "#6b7280", bold: true, children: dot }),
437
+ /* @__PURE__ */ jsxs4(Text4, { color: isRunning ? "#4ade80" : "#6b7280", bold: true, children: [
438
+ " ",
439
+ isRunning ? "LIVE" : "IDLE"
440
+ ] })
441
+ ] });
442
+ }
443
+ function HeaderBar({
444
+ state,
445
+ name,
446
+ frame,
447
+ clock,
448
+ columnFocus,
449
+ childCount
450
+ }) {
451
+ if (!state) {
452
+ return /* @__PURE__ */ jsxs4(Box4, { borderStyle: "round", borderColor: "#6b7280", paddingX: 1, flexDirection: "column", children: [
453
+ /* @__PURE__ */ jsxs4(Box4, { justifyContent: "space-between", children: [
454
+ /* @__PURE__ */ jsx4(Logo, {}),
455
+ /* @__PURE__ */ jsx4(Box4, { paddingX: 1, children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: clock }) })
456
+ ] }),
457
+ /* @__PURE__ */ jsx4(Box4, { paddingX: 1, children: /* @__PURE__ */ jsxs4(Text4, { color: "#fbbf24", children: [
458
+ '\u27F3 Connecting to "',
459
+ name,
460
+ '"...'
461
+ ] }) })
462
+ ] });
463
+ }
464
+ const isRunning = state.status === "running";
465
+ return /* @__PURE__ */ jsxs4(
466
+ Box4,
467
+ {
468
+ borderStyle: "round",
469
+ borderColor: isRunning ? "#6ee7b7" : "#f87171",
470
+ paddingX: 0,
471
+ flexDirection: "column",
472
+ children: [
473
+ /* @__PURE__ */ jsxs4(Box4, { justifyContent: "space-between", children: [
474
+ /* @__PURE__ */ jsx4(Logo, {}),
475
+ /* @__PURE__ */ jsxs4(Box4, { gap: 2, children: [
476
+ childCount > 0 && /* @__PURE__ */ jsxs4(Box4, { paddingX: 1, gap: 1, children: [
477
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "focus:" }),
478
+ /* @__PURE__ */ jsx4(Text4, { color: "#c084fc", bold: true, children: columnFocus === "left" ? "ROOT" : "CHILDREN" }),
479
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "(tab to switch)" })
480
+ ] }),
481
+ /* @__PURE__ */ jsx4(LiveIndicator, { frame, isRunning }),
482
+ /* @__PURE__ */ jsx4(Box4, { paddingX: 1, children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: clock }) })
483
+ ] })
484
+ ] }),
485
+ /* @__PURE__ */ jsxs4(Box4, { paddingX: 1, gap: 1, children: [
486
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\u2502" }),
487
+ /* @__PURE__ */ jsxs4(Text4, { children: [
488
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "pid" }),
489
+ " ",
490
+ /* @__PURE__ */ jsx4(Text4, { bold: true, children: state.pid })
491
+ ] }),
492
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\xB7" }),
493
+ /* @__PURE__ */ jsxs4(Text4, { children: [
494
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "port" }),
495
+ " ",
496
+ /* @__PURE__ */ jsxs4(Text4, { bold: true, children: [
497
+ ":",
498
+ state.port
499
+ ] })
500
+ ] }),
501
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\xB7" }),
502
+ /* @__PURE__ */ jsxs4(Text4, { children: [
503
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "beats" }),
504
+ " ",
505
+ /* @__PURE__ */ jsxs4(Text4, { bold: true, color: "#6ee7b7", children: [
506
+ "\u25B2",
507
+ state.heartbeatCount
508
+ ] })
509
+ ] }),
510
+ state.lastHeartbeat && /* @__PURE__ */ jsxs4(Fragment2, { children: [
511
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\xB7" }),
512
+ /* @__PURE__ */ jsxs4(Text4, { children: [
513
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "last" }),
514
+ " ",
515
+ /* @__PURE__ */ jsx4(Text4, { children: formatTimeAgo2(state.lastHeartbeat) })
516
+ ] })
517
+ ] }),
518
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\xB7" }),
519
+ /* @__PURE__ */ jsxs4(Text4, { children: [
520
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "up" }),
521
+ " ",
522
+ /* @__PURE__ */ jsx4(Text4, { children: formatUptime(state.startedAt) })
523
+ ] })
524
+ ] })
525
+ ]
526
+ }
527
+ );
528
+ }
529
+ function BudgetPanel({
530
+ state,
531
+ dailyCap,
532
+ costHistory
533
+ }) {
534
+ if (!state) return null;
535
+ const todayCost = state.todayCostUsd ?? 0;
536
+ const totalCost = state.totalCostUsd ?? 0;
537
+ const ratio = dailyCap > 0 ? todayCost / dailyCap : 0;
538
+ const barWidth = 20;
539
+ const bar = buildProgressBar(ratio, barWidth);
540
+ const pct = Math.round(ratio * 100);
541
+ const barColor = pct < 50 ? "#4ade80" : pct < 80 ? "#fbbf24" : "#f87171";
542
+ const sparkline = buildSparkline(costHistory, 12);
543
+ return /* @__PURE__ */ jsxs4(Box4, { paddingX: 2, gap: 2, children: [
544
+ /* @__PURE__ */ jsxs4(Box4, { gap: 1, children: [
545
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "budget" }),
546
+ /* @__PURE__ */ jsx4(Text4, { color: barColor, children: bar.filled }),
547
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: bar.empty }),
548
+ /* @__PURE__ */ jsxs4(Text4, { bold: true, color: barColor, children: [
549
+ pct,
550
+ "%"
551
+ ] }),
552
+ /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
553
+ "($",
554
+ todayCost.toFixed(2),
555
+ "/$",
556
+ dailyCap,
557
+ ")"
558
+ ] })
559
+ ] }),
560
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\u2502" }),
561
+ /* @__PURE__ */ jsxs4(Box4, { gap: 1, children: [
562
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "total" }),
563
+ /* @__PURE__ */ jsxs4(Text4, { bold: true, children: [
564
+ "$",
565
+ totalCost.toFixed(2)
566
+ ] })
567
+ ] }),
568
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\u2502" }),
569
+ /* @__PURE__ */ jsxs4(Box4, { gap: 1, children: [
570
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "cost/beat" }),
571
+ /* @__PURE__ */ jsx4(Text4, { color: "#818cf8", children: sparkline })
572
+ ] })
573
+ ] });
574
+ }
575
+ function ActivityRow({
576
+ entry,
577
+ isLatest,
578
+ isOld
579
+ }) {
580
+ const icon = TYPE_ICONS2[entry.type] ?? "\xB7";
581
+ const color = TYPE_COLORS2[entry.type] ?? "#9ca3af";
582
+ const label = (TYPE_LABELS[entry.type] ?? entry.type.toUpperCase()).padEnd(7);
583
+ return /* @__PURE__ */ jsxs4(Box4, { gap: 1, paddingX: 2, children: [
584
+ /* @__PURE__ */ jsx4(Text4, { dimColor: isOld, children: isLatest ? "\u2502" : "\u2502" }),
585
+ /* @__PURE__ */ jsx4(Text4, { dimColor: isOld, children: formatTime2(entry.timestamp) }),
586
+ /* @__PURE__ */ jsx4(Text4, { color, dimColor: isOld, bold: isLatest, children: icon }),
587
+ /* @__PURE__ */ jsx4(Text4, { color, dimColor: isOld, bold: true, children: label }),
588
+ /* @__PURE__ */ jsx4(Text4, { dimColor: isOld, bold: isLatest, children: entry.summary })
589
+ ] });
590
+ }
591
+ var TASK_STATUS_COLORS = {
592
+ in_progress: "#60a5fa",
593
+ blocked: "#f87171",
594
+ pending: "#6b7280",
595
+ done: "#4ade80"
596
+ };
597
+ var TASK_STATUS_LABELS = {
598
+ in_progress: "ACTIVE",
599
+ blocked: "BLOCK",
600
+ pending: "\xB7"
601
+ };
602
+ function TaskPanel({ tasks }) {
603
+ const active = tasks.filter((t) => t.status !== "done" && t.status !== "abandoned");
604
+ const doneCount = tasks.filter((t) => t.status === "done").length;
605
+ if (tasks.length === 0) return null;
606
+ const MAX_VISIBLE = 6;
607
+ const visible = active.slice(0, MAX_VISIBLE);
608
+ const overflow = active.length - visible.length;
609
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
610
+ /* @__PURE__ */ jsxs4(Box4, { paddingX: 2, gap: 1, children: [
611
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\u251C" }),
612
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, bold: true, children: "TASKS" }),
613
+ /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
614
+ "(",
615
+ active.length,
616
+ " active, ",
617
+ doneCount,
618
+ " done)"
619
+ ] }),
620
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\u2500".repeat(30) })
621
+ ] }),
622
+ visible.map((t) => {
623
+ const status = t.status ?? "pending";
624
+ const color = TASK_STATUS_COLORS[status] ?? "#6b7280";
625
+ const label = (TASK_STATUS_LABELS[status] ?? "\xB7").padEnd(6);
626
+ const prio = t.priority ? `[${t.priority.slice(0, 3)}] ` : "";
627
+ const repo = t.scope !== "global" ? path.basename(t.scope) : "";
628
+ const run = t.runId ? `run:${t.runId.slice(0, 4)}` : "";
629
+ const meta = [repo, run].filter(Boolean).join(" ");
630
+ return /* @__PURE__ */ jsxs4(Box4, { gap: 1, paddingX: 2, children: [
631
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\u2502" }),
632
+ /* @__PURE__ */ jsx4(Text4, { color, bold: true, children: label }),
633
+ prio && /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: prio.padEnd(5) }),
634
+ /* @__PURE__ */ jsx4(Text4, { wrap: "truncate", children: t.title }),
635
+ meta && /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
636
+ "(",
637
+ meta,
638
+ ")"
639
+ ] })
640
+ ] }, t.id);
641
+ }),
642
+ overflow > 0 && /* @__PURE__ */ jsx4(Box4, { paddingX: 2, children: /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
643
+ "\u2502 ... +",
644
+ overflow,
645
+ " more pending"
646
+ ] }) })
647
+ ] });
648
+ }
649
+ function DecisionBanner({ decisions, frame }) {
650
+ if (decisions.length === 0) return null;
651
+ const pulseChars = ["\u2605", "\u2606"];
652
+ const pulse = pulseChars[frame % pulseChars.length];
653
+ return /* @__PURE__ */ jsxs4(Box4, { paddingX: 2, gap: 1, children: [
654
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\u251C" }),
655
+ /* @__PURE__ */ jsxs4(Text4, { color: "#fbbf24", bold: true, children: [
656
+ pulse,
657
+ " ",
658
+ decisions.length,
659
+ " decision",
660
+ decisions.length > 1 ? "s" : "",
661
+ " pending"
662
+ ] }),
663
+ /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
664
+ "\u2014 press ",
665
+ /* @__PURE__ */ jsx4(Text4, { bold: true, children: "tab" }),
666
+ " to review"
667
+ ] })
668
+ ] });
669
+ }
670
+ function DecisionInputPanel({
671
+ decision,
672
+ optionIndex,
673
+ isTextMode,
674
+ textInput,
675
+ onTextChange,
676
+ onSubmit,
677
+ decisionCount,
678
+ decisionIdx,
679
+ frame
680
+ }) {
681
+ const hasOptions = decision.options && decision.options.length > 0;
682
+ const pulseChars = ["\u2605", "\u2606"];
683
+ const pulse = pulseChars[frame % pulseChars.length];
684
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
685
+ /* @__PURE__ */ jsxs4(Box4, { paddingX: 2, gap: 1, children: [
686
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\u251C" }),
687
+ /* @__PURE__ */ jsxs4(Text4, { color: "#fbbf24", bold: true, children: [
688
+ pulse,
689
+ " DECISION"
690
+ ] }),
691
+ decisionCount > 1 && /* @__PURE__ */ jsxs4(Text4, { color: "#fbbf24", children: [
692
+ "(",
693
+ decisionIdx + 1,
694
+ "/",
695
+ decisionCount,
696
+ ")"
697
+ ] }),
698
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\u2500".repeat(30) })
699
+ ] }),
700
+ /* @__PURE__ */ jsxs4(Box4, { paddingX: 2, gap: 1, children: [
701
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\u2502" }),
702
+ /* @__PURE__ */ jsx4(Text4, { bold: true, wrap: "truncate-end", children: decision.question })
703
+ ] }),
704
+ decision.context && /* @__PURE__ */ jsxs4(Box4, { paddingX: 2, gap: 1, children: [
705
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\u2502" }),
706
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, wrap: "truncate-end", children: decision.context })
707
+ ] }),
708
+ hasOptions ? /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", children: (decision.options ?? []).map((opt, idx) => {
709
+ const isSelected = idx === optionIndex;
710
+ return /* @__PURE__ */ jsxs4(Box4, { paddingX: 2, gap: 1, children: [
711
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\u2502" }),
712
+ isSelected ? /* @__PURE__ */ jsxs4(Text4, { color: "#fbbf24", bold: true, children: [
713
+ "\u25B8 ",
714
+ opt.label
715
+ ] }) : /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
716
+ " ",
717
+ opt.label
718
+ ] }),
719
+ opt.description && isSelected && /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
720
+ "\u2014 ",
721
+ opt.description
722
+ ] })
723
+ ] }, opt.key);
724
+ }) }) : /* @__PURE__ */ jsxs4(Box4, { paddingX: 2, gap: 1, children: [
725
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\u2502" }),
726
+ /* @__PURE__ */ jsx4(Text4, { color: "#fbbf24", bold: true, children: "\u276F" }),
727
+ /* @__PURE__ */ jsx4(
728
+ TextInput2,
729
+ {
730
+ value: textInput,
731
+ onChange: onTextChange,
732
+ onSubmit,
733
+ focus: isTextMode,
734
+ placeholder: "type your answer..."
735
+ }
736
+ )
737
+ ] }),
738
+ /* @__PURE__ */ jsxs4(Box4, { paddingX: 2, gap: 1, children: [
739
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\u2514" }),
740
+ /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
741
+ hasOptions ? /* @__PURE__ */ jsxs4(Fragment2, { children: [
742
+ /* @__PURE__ */ jsx4(Text4, { bold: true, children: "\u2191\u2193" }),
743
+ " choose \xB7 ",
744
+ /* @__PURE__ */ jsx4(Text4, { bold: true, children: "enter" }),
745
+ " confirm"
746
+ ] }) : /* @__PURE__ */ jsxs4(Fragment2, { children: [
747
+ /* @__PURE__ */ jsx4(Text4, { bold: true, children: "enter" }),
748
+ " send"
749
+ ] }),
750
+ decisionCount > 1 && /* @__PURE__ */ jsxs4(Fragment2, { children: [
751
+ " \xB7 ",
752
+ /* @__PURE__ */ jsx4(Text4, { bold: true, children: "\u2190\u2192" }),
753
+ " prev/next"
754
+ ] }),
755
+ " \xB7 ",
756
+ /* @__PURE__ */ jsx4(Text4, { bold: true, children: "tab" }),
757
+ " chat \xB7 ",
758
+ /* @__PURE__ */ jsx4(Text4, { bold: true, children: "esc" }),
759
+ " back"
760
+ ] })
761
+ ] })
762
+ ] });
763
+ }
764
+ var ACTIVITY_TYPES = /* @__PURE__ */ new Set([
765
+ "heartbeat",
766
+ "decision",
767
+ "action",
768
+ "dispatch",
769
+ "error",
770
+ "event",
771
+ "message"
772
+ ]);
773
+ function ActivityPanel({ entries, maxVisible }) {
774
+ const filtered = entries.filter((e) => ACTIVITY_TYPES.has(e.type));
775
+ const visible = filtered.slice(-maxVisible);
776
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
777
+ /* @__PURE__ */ jsxs4(Box4, { paddingX: 2, gap: 1, children: [
778
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\u251C" }),
779
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, bold: true, children: "ACTIVITY" }),
780
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\u2500".repeat(40) })
781
+ ] }),
782
+ visible.length === 0 ? /* @__PURE__ */ jsx4(Box4, { paddingX: 2, children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\u2502 Waiting for heartbeats..." }) }) : visible.map((entry, idx) => /* @__PURE__ */ jsx4(
783
+ ActivityRow,
784
+ {
785
+ entry,
786
+ isLatest: idx === visible.length - 1,
787
+ isOld: idx < visible.length - 5
788
+ },
789
+ entry.id
790
+ )),
791
+ /* @__PURE__ */ jsx4(Box4, { paddingX: 2, children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\u2502" }) })
792
+ ] });
793
+ }
794
+ function InputPanel({
795
+ value,
796
+ onChange,
797
+ onSubmit,
798
+ lastSent,
799
+ focus
800
+ }) {
801
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
802
+ /* @__PURE__ */ jsxs4(Box4, { paddingX: 2, gap: 1, children: [
803
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\u2514" }),
804
+ /* @__PURE__ */ jsx4(Text4, { bold: true, color: "#60a5fa", children: "\u276F" }),
805
+ /* @__PURE__ */ jsx4(
806
+ TextInput2,
807
+ {
808
+ value,
809
+ onChange,
810
+ onSubmit,
811
+ focus,
812
+ placeholder: "message the supervisor..."
813
+ }
814
+ )
815
+ ] }),
816
+ /* @__PURE__ */ jsxs4(Box4, { paddingX: 2, gap: 1, children: [
817
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: " " }),
818
+ lastSent ? /* @__PURE__ */ jsxs4(Text4, { color: "#6b7280", children: [
819
+ '\u2713 "',
820
+ lastSent,
821
+ '"'
822
+ ] }) : null
823
+ ] })
824
+ ] });
825
+ }
826
+ async function readState(name) {
827
+ try {
828
+ const raw = await readFile(getSupervisorStatePath(name), "utf-8");
829
+ return JSON.parse(raw);
830
+ } catch (err) {
831
+ console.debug(
832
+ `[tui] Failed to read supervisor state for ${name}: ${err instanceof Error ? err.message : String(err)}`
833
+ );
834
+ return null;
835
+ }
836
+ }
837
+ async function readActivity(name, maxEntries) {
838
+ try {
839
+ const content = await readFile(getSupervisorActivityPath(name), "utf-8");
840
+ const lines = content.trim().split("\n").filter(Boolean);
841
+ const lastLines = lines.slice(-maxEntries);
842
+ const entries = [];
843
+ for (const line of lastLines) {
844
+ try {
845
+ entries.push(JSON.parse(line));
846
+ } catch (err) {
847
+ console.debug(
848
+ `[tui] Skipping malformed activity line: ${err instanceof Error ? err.message : String(err)}`
849
+ );
850
+ }
851
+ }
852
+ return entries;
853
+ } catch (err) {
854
+ console.debug(
855
+ `[tui] Failed to read activity for ${name}: ${err instanceof Error ? err.message : String(err)}`
856
+ );
857
+ return [];
858
+ }
859
+ }
860
+ async function readChildActivity(supervisorId, maxEntries) {
861
+ const activityPath = path.join(getFocusedSupervisorDir(supervisorId), "activity.jsonl");
862
+ try {
863
+ const content = await readFile(activityPath, "utf-8");
864
+ const lines = content.trim().split("\n").filter(Boolean);
865
+ const lastLines = lines.slice(-maxEntries);
866
+ const entries = [];
867
+ for (const line of lastLines) {
868
+ try {
869
+ entries.push(JSON.parse(line));
870
+ } catch {
871
+ }
872
+ }
873
+ return entries;
874
+ } catch {
875
+ return [];
876
+ }
877
+ }
878
+ function readTasks(name) {
879
+ try {
880
+ const dir = getSupervisorDir(name);
881
+ const store = new TaskStore(path.join(dir, "tasks.sqlite"));
882
+ const tasks = store.getTasks();
883
+ store.close();
884
+ return tasks.slice(0, 20);
885
+ } catch (err) {
886
+ console.debug(
887
+ `[tui] Failed to read tasks for ${name}: ${err instanceof Error ? err.message : String(err)}`
888
+ );
889
+ return [];
890
+ }
891
+ }
892
+ async function readDecisions(name) {
893
+ try {
894
+ const store = new DecisionStore(getSupervisorDecisionsPath(name));
895
+ return await store.pending();
896
+ } catch (err) {
897
+ console.debug(
898
+ `[tui] Failed to read decisions for ${name}: ${err instanceof Error ? err.message : String(err)}`
899
+ );
900
+ return [];
901
+ }
902
+ }
903
+ async function appendToJsonl(filePath, data) {
904
+ const dir = path.dirname(filePath);
905
+ try {
906
+ await mkdir(dir, { recursive: true });
907
+ await appendFile(filePath, `${JSON.stringify(data)}
908
+ `, "utf-8");
909
+ return true;
910
+ } catch (error) {
911
+ console.error(
912
+ `Warning: Failed to write to ${path.basename(filePath)}: ${error instanceof Error ? error.message : String(error)}`
913
+ );
914
+ return false;
915
+ }
916
+ }
917
+ async function writeToInbox(name, message) {
918
+ const inboxPath = getSupervisorInboxPath(name);
919
+ return appendToJsonl(inboxPath, message);
920
+ }
921
+ async function answerDecision(name, id, answer) {
922
+ const store = new DecisionStore(getSupervisorDecisionsPath(name));
923
+ await store.answer(id, answer);
924
+ const inboxMessage = {
925
+ id: randomUUID(),
926
+ from: "tui",
927
+ text: `decision:answer ${id} ${answer}`,
928
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
929
+ };
930
+ await writeToInbox(name, inboxMessage);
931
+ }
932
+ async function sendMessage(name, text) {
933
+ const id = randomUUID();
934
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
935
+ const message = { id, from: "tui", text, timestamp };
936
+ await writeToInbox(name, message);
937
+ const activityEntry = { id, type: "message", summary: text, timestamp };
938
+ const activityPath = getSupervisorActivityPath(name);
939
+ await appendToJsonl(activityPath, activityEntry);
940
+ }
941
+ function SupervisorTui({ name }) {
942
+ const { exit } = useApp();
943
+ const { stdout } = useStdout();
944
+ const frame = useAnimationFrame();
945
+ const clock = useClock();
946
+ const [state, setState] = useState(null);
947
+ const [entries, setEntries] = useState([]);
948
+ const [tasks, setTasks] = useState([]);
949
+ const [decisions, setDecisions] = useState([]);
950
+ const [dailyCap, setDailyCap] = useState(50);
951
+ const [input, setInput] = useState("");
952
+ const [lastSent, setLastSent] = useState("");
953
+ const [termHeight, setTermHeight] = useState(stdout?.rows ?? 30);
954
+ const [decisionIndex, setDecisionIndex] = useState(0);
955
+ const [optionIndex, setOptionIndex] = useState(0);
956
+ const [decisionAnswer, setDecisionAnswer] = useState("");
957
+ const [focusMode, setFocusMode] = useState("input");
958
+ const [children, setChildren] = useState([]);
959
+ const [selectedChildIndex, setSelectedChildIndex] = useState(0);
960
+ const [childActivity, setChildActivity] = useState([]);
961
+ const [columnFocus, setColumnFocus] = useState("left");
962
+ const [childInputMode, setChildInputMode] = useState("idle");
963
+ const [childInputValue, setChildInputValue] = useState("");
964
+ const hasChildren = children.length > 0;
965
+ const selectedChild = children[selectedChildIndex];
966
+ useEffect(() => {
967
+ function onResize() {
968
+ if (stdout) setTermHeight(stdout.rows);
969
+ }
970
+ stdout?.on("resize", onResize);
971
+ return () => {
972
+ stdout?.off("resize", onResize);
973
+ };
974
+ }, [stdout]);
975
+ useEffect(() => {
976
+ loadGlobalConfig().then((cfg) => setDailyCap(cfg.supervisor.dailyCapUsd)).catch((err) => {
977
+ console.debug("[tui] Failed to load global config:", err);
978
+ });
979
+ }, []);
980
+ const decisionIndexRef = useRef(decisionIndex);
981
+ const decisionsLengthRef = useRef(decisions.length);
982
+ useEffect(() => {
983
+ decisionIndexRef.current = decisionIndex;
984
+ }, [decisionIndex]);
985
+ useEffect(() => {
986
+ decisionsLengthRef.current = decisions.length;
987
+ }, [decisions.length]);
988
+ useEffect(() => {
989
+ let active = true;
990
+ async function poll() {
991
+ if (!active) return;
992
+ const [newState, newEntries, newDecisions, newChildren] = await Promise.all([
993
+ readState(name),
994
+ readActivity(name, MAX_VISIBLE_ENTRIES),
995
+ readDecisions(name),
996
+ readChildrenFile(getSupervisorChildrenPath(name)).catch((err) => {
997
+ if (err.code !== "ENOENT") {
998
+ console.debug(`[TUI] readChildrenFile failed: ${err}`);
999
+ }
1000
+ return [];
1001
+ })
1002
+ ]);
1003
+ if (!active) return;
1004
+ setState(newState);
1005
+ setEntries(newEntries);
1006
+ setDecisions(newDecisions);
1007
+ setSelectedChildIndex((i) => Math.min(i, Math.max(0, newChildren.length - 1)));
1008
+ setChildren(newChildren);
1009
+ setTasks(readTasks(name));
1010
+ if (newDecisions.length > 0 && decisionIndexRef.current >= newDecisions.length) {
1011
+ setDecisionIndex(0);
1012
+ }
1013
+ if (newDecisions.length > 0 && decisionsLengthRef.current === 0) {
1014
+ setFocusMode("decisions");
1015
+ }
1016
+ if (newDecisions.length === 0 && decisionsLengthRef.current > 0) {
1017
+ setFocusMode("input");
1018
+ }
1019
+ }
1020
+ poll();
1021
+ const interval = setInterval(poll, POLL_INTERVAL_MS);
1022
+ return () => {
1023
+ active = false;
1024
+ clearInterval(interval);
1025
+ };
1026
+ }, [name]);
1027
+ const selectedChildId = selectedChild?.supervisorId;
1028
+ useEffect(() => {
1029
+ if (!selectedChildId) {
1030
+ setChildActivity([]);
1031
+ return;
1032
+ }
1033
+ const childId = selectedChildId;
1034
+ let active = true;
1035
+ async function poll() {
1036
+ if (!active) return;
1037
+ const activity = await readChildActivity(childId, MAX_CHILD_ACTIVITY);
1038
+ if (!active) return;
1039
+ setChildActivity(activity);
1040
+ }
1041
+ poll();
1042
+ const interval = setInterval(poll, POLL_INTERVAL_MS);
1043
+ return () => {
1044
+ active = false;
1045
+ clearInterval(interval);
1046
+ };
1047
+ }, [selectedChildId]);
1048
+ const currentDecision = decisions[decisionIndex];
1049
+ const currentHasOptions = (currentDecision?.options?.length ?? 0) > 0;
1050
+ const submitDecisionAnswer = useCallback(
1051
+ async (answer) => {
1052
+ if (!answer.trim() || !currentDecision) return;
1053
+ try {
1054
+ await answerDecision(name, currentDecision.id, answer.trim());
1055
+ setLastSent(`Decision ${currentDecision.id.slice(4, 12)}: "${answer.trim()}"`);
1056
+ setDecisionAnswer("");
1057
+ setOptionIndex(0);
1058
+ } catch (err) {
1059
+ console.debug(
1060
+ `[tui] Failed to answer decision ${currentDecision.id}: ${err instanceof Error ? err.message : String(err)}`
1061
+ );
1062
+ }
1063
+ },
1064
+ [name, currentDecision]
1065
+ );
1066
+ const handleOptionNav = useCallback(
1067
+ (key) => {
1068
+ const options = currentDecision?.options;
1069
+ if (!options || options.length === 0) return false;
1070
+ if (key.upArrow) {
1071
+ setOptionIndex((i) => Math.max(0, i - 1));
1072
+ return true;
1073
+ }
1074
+ if (key.downArrow) {
1075
+ setOptionIndex((i) => Math.min(options.length - 1, i + 1));
1076
+ return true;
1077
+ }
1078
+ if (key.return) {
1079
+ const opt = options[optionIndex];
1080
+ if (opt) submitDecisionAnswer(opt.key);
1081
+ return true;
1082
+ }
1083
+ return false;
1084
+ },
1085
+ [currentDecision, optionIndex, submitDecisionAnswer]
1086
+ );
1087
+ const handleChildInputSubmit = useCallback(
1088
+ async (value) => {
1089
+ if (!selectedChild) return;
1090
+ const id = selectedChild.supervisorId;
1091
+ let text = null;
1092
+ if (childInputMode === "inject" && value.trim()) {
1093
+ text = `child:inject ${id} ${value.trim()}`;
1094
+ } else if (childInputMode === "unblock" && value.trim()) {
1095
+ text = `child:unblock ${id} ${value.trim()}`;
1096
+ } else if (childInputMode === "kill" && value.trim().toLowerCase() === "stop") {
1097
+ text = `child:stop ${id}`;
1098
+ }
1099
+ if (text) {
1100
+ const message = {
1101
+ id: randomUUID(),
1102
+ from: "tui",
1103
+ text,
1104
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1105
+ };
1106
+ await writeToInbox(name, message);
1107
+ setLastSent(text.slice(0, 40));
1108
+ }
1109
+ setChildInputMode("idle");
1110
+ setChildInputValue("");
1111
+ },
1112
+ [name, selectedChild, childInputMode]
1113
+ );
1114
+ const handleRightColumnKey = useCallback(
1115
+ (char, key) => {
1116
+ if (key.upArrow) {
1117
+ setSelectedChildIndex((i) => Math.max(0, i - 1));
1118
+ return;
1119
+ }
1120
+ if (key.downArrow) {
1121
+ if (children.length > 0) {
1122
+ setSelectedChildIndex((i) => Math.min(children.length - 1, i + 1));
1123
+ }
1124
+ return;
1125
+ }
1126
+ if (char === "i") {
1127
+ setChildInputMode("inject");
1128
+ return;
1129
+ }
1130
+ if (char === "u" && selectedChild?.status === "blocked") {
1131
+ setChildInputMode("unblock");
1132
+ return;
1133
+ }
1134
+ if (char === "k") {
1135
+ setChildInputMode("kill");
1136
+ }
1137
+ },
1138
+ [children.length, selectedChild]
1139
+ );
1140
+ const handleDecisionKey = useCallback(
1141
+ (key) => {
1142
+ if (currentHasOptions && handleOptionNav(key)) return;
1143
+ if (decisions.length > 1) {
1144
+ if (key.leftArrow) {
1145
+ setDecisionIndex((i) => Math.max(0, i - 1));
1146
+ setOptionIndex(0);
1147
+ } else if (key.rightArrow) {
1148
+ setDecisionIndex((i) => Math.min(decisions.length - 1, i + 1));
1149
+ setOptionIndex(0);
1150
+ }
1151
+ }
1152
+ },
1153
+ [currentHasOptions, handleOptionNav, decisions.length]
1154
+ );
1155
+ useInput((char, key) => {
1156
+ if (key.tab) {
1157
+ if (hasChildren && focusMode !== "decisions") {
1158
+ setColumnFocus((c) => c === "left" ? "right" : "left");
1159
+ setOptionIndex(0);
1160
+ } else if (decisions.length > 0) {
1161
+ setFocusMode((m) => m === "input" ? "decisions" : "input");
1162
+ setOptionIndex(0);
1163
+ }
1164
+ return;
1165
+ }
1166
+ if (key.escape) {
1167
+ if (childInputMode !== "idle") {
1168
+ setChildInputMode("idle");
1169
+ setChildInputValue("");
1170
+ } else if (columnFocus === "right") {
1171
+ setColumnFocus("left");
1172
+ } else if (focusMode === "decisions") {
1173
+ setFocusMode("input");
1174
+ } else {
1175
+ exit();
1176
+ }
1177
+ return;
1178
+ }
1179
+ if (columnFocus === "right" && childInputMode === "idle") {
1180
+ handleRightColumnKey(char, key);
1181
+ return;
1182
+ }
1183
+ if (focusMode === "decisions" && decisions.length > 0 && columnFocus === "left") {
1184
+ handleDecisionKey(key);
1185
+ }
1186
+ });
1187
+ const handleSubmit = useCallback(
1188
+ (text) => {
1189
+ if (!text.trim()) return;
1190
+ sendMessage(name, text.trim());
1191
+ setLastSent(text.trim());
1192
+ setInput("");
1193
+ },
1194
+ [name]
1195
+ );
1196
+ const costHistory = extractCostHistory(entries);
1197
+ const activeTaskCount = tasks.filter(
1198
+ (t) => t.status !== "done" && t.status !== "abandoned"
1199
+ ).length;
1200
+ const taskPanelLines = tasks.length > 0 ? Math.min(activeTaskCount, 6) + 2 : 0;
1201
+ const decisionPanelLines = focusMode === "decisions" && currentDecision ? (currentHasOptions ? currentDecision.options?.length ?? 0 : 1) + 4 : decisions.length > 0 ? 1 : 0;
1202
+ const leftActivityMaxVisible = Math.max(
1203
+ 5,
1204
+ Math.min(MAX_VISIBLE_ENTRIES, termHeight - 10 - taskPanelLines - decisionPanelLines)
1205
+ );
1206
+ const bottomPanel = focusMode === "decisions" && currentDecision && columnFocus === "left" ? /* @__PURE__ */ jsx4(
1207
+ DecisionInputPanel,
1208
+ {
1209
+ decision: currentDecision,
1210
+ optionIndex,
1211
+ isTextMode: !currentHasOptions,
1212
+ textInput: decisionAnswer,
1213
+ onTextChange: setDecisionAnswer,
1214
+ onSubmit: submitDecisionAnswer,
1215
+ decisionCount: decisions.length,
1216
+ decisionIdx: decisionIndex,
1217
+ frame
1218
+ }
1219
+ ) : /* @__PURE__ */ jsx4(
1220
+ InputPanel,
1221
+ {
1222
+ value: input,
1223
+ onChange: setInput,
1224
+ onSubmit: handleSubmit,
1225
+ lastSent,
1226
+ focus: columnFocus === "left" && focusMode === "input"
1227
+ }
1228
+ );
1229
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
1230
+ /* @__PURE__ */ jsx4(
1231
+ HeaderBar,
1232
+ {
1233
+ state,
1234
+ name,
1235
+ frame,
1236
+ clock,
1237
+ columnFocus,
1238
+ childCount: children.length
1239
+ }
1240
+ ),
1241
+ /* @__PURE__ */ jsx4(BudgetPanel, { state, dailyCap, costHistory }),
1242
+ /* @__PURE__ */ jsxs4(Box4, { flexDirection: "row", flexGrow: 1, children: [
1243
+ /* @__PURE__ */ jsxs4(
1244
+ Box4,
1245
+ {
1246
+ flexDirection: "column",
1247
+ flexGrow: 1,
1248
+ flexBasis: hasChildren ? "50%" : "100%",
1249
+ borderStyle: hasChildren && columnFocus === "left" ? "single" : void 0,
1250
+ borderColor: "#c084fc",
1251
+ children: [
1252
+ focusMode !== "decisions" && /* @__PURE__ */ jsx4(DecisionBanner, { decisions, frame }),
1253
+ /* @__PURE__ */ jsx4(TaskPanel, { tasks }),
1254
+ /* @__PURE__ */ jsx4(ActivityPanel, { entries, maxVisible: leftActivityMaxVisible }),
1255
+ bottomPanel
1256
+ ]
1257
+ }
1258
+ ),
1259
+ hasChildren && /* @__PURE__ */ jsxs4(
1260
+ Box4,
1261
+ {
1262
+ flexDirection: "column",
1263
+ flexGrow: 1,
1264
+ flexBasis: "50%",
1265
+ borderStyle: columnFocus === "right" ? "single" : void 0,
1266
+ borderColor: "#c084fc",
1267
+ children: [
1268
+ /* @__PURE__ */ jsx4(ChildList, { handles: children, selectedIndex: selectedChildIndex }),
1269
+ selectedChild && /* @__PURE__ */ jsxs4(Fragment2, { children: [
1270
+ /* @__PURE__ */ jsx4(
1271
+ ChildDetail,
1272
+ {
1273
+ handle: selectedChild,
1274
+ activity: childActivity,
1275
+ maxActivityLines: MAX_CHILD_ACTIVITY
1276
+ }
1277
+ ),
1278
+ /* @__PURE__ */ jsx4(
1279
+ ChildInput,
1280
+ {
1281
+ handle: selectedChild,
1282
+ mode: childInputMode,
1283
+ value: childInputValue,
1284
+ onChange: setChildInputValue,
1285
+ onSubmit: handleChildInputSubmit
1286
+ }
1287
+ )
1288
+ ] })
1289
+ ]
1290
+ }
1291
+ )
1292
+ ] }),
1293
+ /* @__PURE__ */ jsxs4(Box4, { paddingX: 2, gap: 1, justifyContent: "center", children: [
1294
+ /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
1295
+ /* @__PURE__ */ jsx4(Text4, { bold: true, children: "esc" }),
1296
+ " quit"
1297
+ ] }),
1298
+ hasChildren && /* @__PURE__ */ jsxs4(Fragment2, { children: [
1299
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\xB7" }),
1300
+ /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
1301
+ /* @__PURE__ */ jsx4(Text4, { bold: true, children: "tab" }),
1302
+ " switch panel"
1303
+ ] })
1304
+ ] }),
1305
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\xB7" }),
1306
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "daemon keeps running" })
1307
+ ] })
1308
+ ] });
1309
+ }
1310
+
1311
+ // src/tui/index.ts
1312
+ async function renderSupervisorTui(name) {
1313
+ const { waitUntilExit } = render(React.createElement(SupervisorTui, { name }));
1314
+ await waitUntilExit();
1315
+ }
1316
+ export {
1317
+ renderSupervisorTui
1318
+ };
1319
+ //# sourceMappingURL=tui-LSW7VVK6.js.map