@neotx/cli 0.1.0-alpha.22 → 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 (46) 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/{decision-PNZ2S2TU.js → decision-T2526ITK.js} +35 -2
  18. package/dist/decision-T2526ITK.js.map +1 -0
  19. package/dist/directive-7WM2Q2UW.js +259 -0
  20. package/dist/directive-7WM2Q2UW.js.map +1 -0
  21. package/dist/do-F5XW2ELZ.js +83 -0
  22. package/dist/do-F5XW2ELZ.js.map +1 -0
  23. package/dist/health-SWQ6V4H5.js +72 -0
  24. package/dist/health-SWQ6V4H5.js.map +1 -0
  25. package/dist/index.js +10 -6
  26. package/dist/index.js.map +1 -1
  27. package/dist/{log-PTHLI7ZN.js → log-ZLIAIBZQ.js} +64 -9
  28. package/dist/log-ZLIAIBZQ.js.map +1 -0
  29. package/dist/{memory-SDZ57W2S.js → memory-CW6E65SQ.js} +112 -62
  30. package/dist/memory-CW6E65SQ.js.map +1 -0
  31. package/dist/{run-MWHIQUSY.js → run-NV762V5B.js} +56 -22
  32. package/dist/run-NV762V5B.js.map +1 -0
  33. package/dist/{supervise-XMZRNODO.js → supervise-BWIKWNHH.js} +68 -41
  34. package/dist/supervise-BWIKWNHH.js.map +1 -0
  35. package/dist/{supervisor-3RUX5SPH.js → supervisor-N4D5EWCC.js} +1 -1
  36. package/dist/tui-LSW7VVK6.js +1319 -0
  37. package/dist/tui-LSW7VVK6.js.map +1 -0
  38. package/package.json +4 -4
  39. package/dist/decision-PNZ2S2TU.js.map +0 -1
  40. package/dist/log-PTHLI7ZN.js.map +0 -1
  41. package/dist/memory-SDZ57W2S.js.map +0 -1
  42. package/dist/run-MWHIQUSY.js.map +0 -1
  43. package/dist/supervise-XMZRNODO.js.map +0 -1
  44. package/dist/tui-67VJ5VBA.js +0 -842
  45. package/dist/tui-67VJ5VBA.js.map +0 -1
  46. /package/dist/{supervisor-3RUX5SPH.js.map → supervisor-N4D5EWCC.js.map} +0 -0
@@ -1,842 +0,0 @@
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
- getSupervisorActivityPath,
12
- getSupervisorDecisionsPath,
13
- getSupervisorDir,
14
- getSupervisorInboxPath,
15
- getSupervisorStatePath,
16
- loadGlobalConfig,
17
- MemoryStore
18
- } from "@neotx/core";
19
- import { Box, Text, useApp, useInput, useStdout } from "ink";
20
- import TextInput from "ink-text-input";
21
- import { useCallback, useEffect, useState } from "react";
22
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
23
- var MAX_VISIBLE_ENTRIES = 24;
24
- var POLL_INTERVAL_MS = 1500;
25
- var ANIMATION_TICK_MS = 400;
26
- var SPARK_CHARS = ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
27
- var BLOCK_FULL = "\u2588";
28
- var BLOCK_EMPTY = "\u2591";
29
- var PULSE_FRAMES = ["\u25C9", "\u25CE", "\u25CB", "\u25CE"];
30
- var IDLE_FRAMES = ["\u25CC", "\u25CC", "\u25CC", "\u25CC"];
31
- var TYPE_ICONS = {
32
- heartbeat: "\u2665",
33
- decision: "\u2605",
34
- action: "\u26A1",
35
- error: "\u2716",
36
- event: "\u25C6",
37
- message: "\u2709",
38
- thinking: "\u25C7",
39
- plan: "\u25B8",
40
- dispatch: "\u2197",
41
- tool_use: "\u2298"
42
- };
43
- var TYPE_COLORS = {
44
- heartbeat: "#6ee7b7",
45
- decision: "#fbbf24",
46
- action: "#60a5fa",
47
- error: "#f87171",
48
- event: "#c084fc",
49
- message: "#67e8f9",
50
- thinking: "#a78bfa",
51
- plan: "#34d399",
52
- dispatch: "#f472b6",
53
- tool_use: "#38bdf8"
54
- };
55
- var TYPE_LABELS = {
56
- heartbeat: "BEAT",
57
- decision: "DECIDE",
58
- action: "ACTION",
59
- error: "ERROR",
60
- event: "EVENT",
61
- message: "MSG",
62
- thinking: "THINK",
63
- plan: "PLAN",
64
- dispatch: "SEND",
65
- tool_use: "TOOL"
66
- };
67
- function formatTime(timestamp) {
68
- return timestamp.slice(11, 19);
69
- }
70
- function formatUptime(startedAt) {
71
- const ms = Date.now() - new Date(startedAt).getTime();
72
- const seconds = Math.floor(ms / 1e3);
73
- const minutes = Math.floor(seconds / 60);
74
- const hours = Math.floor(minutes / 60);
75
- const days = Math.floor(hours / 24);
76
- if (days > 0) return `${days}d ${hours % 24}h`;
77
- if (hours > 0) return `${hours}h ${minutes % 60}m`;
78
- if (minutes > 0) return `${minutes}m ${seconds % 60}s`;
79
- return `${seconds}s`;
80
- }
81
- function formatTimeAgo(timestamp) {
82
- const ms = Date.now() - new Date(timestamp).getTime();
83
- const seconds = Math.floor(ms / 1e3);
84
- if (seconds < 60) return `${seconds}s ago`;
85
- const minutes = Math.floor(seconds / 60);
86
- if (minutes < 60) return `${minutes}m ago`;
87
- const hours = Math.floor(minutes / 60);
88
- return `${hours}h ago`;
89
- }
90
- function buildProgressBar(ratio, width) {
91
- const clamped = Math.max(0, Math.min(1, ratio));
92
- const filledCount = Math.round(clamped * width);
93
- return {
94
- filled: BLOCK_FULL.repeat(filledCount),
95
- empty: BLOCK_EMPTY.repeat(width - filledCount)
96
- };
97
- }
98
- function buildSparkline(values, width) {
99
- if (values.length === 0) return "\u2581".repeat(width);
100
- const recent = values.slice(-width);
101
- const max = Math.max(...recent, 1e-3);
102
- return recent.map((v) => {
103
- const idx = Math.min(
104
- Math.floor(v / max * (SPARK_CHARS.length - 1)),
105
- SPARK_CHARS.length - 1
106
- );
107
- return SPARK_CHARS[idx];
108
- }).join("");
109
- }
110
- function extractCostHistory(entries) {
111
- return entries.filter((e) => e.type === "heartbeat" && e.summary.includes("complete")).map((e) => {
112
- const detail = e.detail;
113
- return typeof detail?.costUsd === "number" ? detail.costUsd : 0;
114
- });
115
- }
116
- function useAnimationFrame() {
117
- const [frame, setFrame] = useState(0);
118
- useEffect(() => {
119
- const interval = setInterval(() => setFrame((f) => f + 1), ANIMATION_TICK_MS);
120
- return () => clearInterval(interval);
121
- }, []);
122
- return frame;
123
- }
124
- function useClock() {
125
- const [time, setTime] = useState(() => (/* @__PURE__ */ new Date()).toLocaleTimeString());
126
- useEffect(() => {
127
- const interval = setInterval(() => setTime((/* @__PURE__ */ new Date()).toLocaleTimeString()), 1e3);
128
- return () => clearInterval(interval);
129
- }, []);
130
- return time;
131
- }
132
- function Logo() {
133
- return /* @__PURE__ */ jsxs(Box, { paddingX: 1, gap: 1, children: [
134
- /* @__PURE__ */ jsx(Text, { color: "#c084fc", bold: true, children: "\u25C6" }),
135
- /* @__PURE__ */ jsxs(Text, { bold: true, children: [
136
- /* @__PURE__ */ jsx(Text, { color: "#c084fc", children: "N" }),
137
- /* @__PURE__ */ jsx(Text, { color: "#a78bfa", children: "E" }),
138
- /* @__PURE__ */ jsx(Text, { color: "#818cf8", children: "O" })
139
- ] }),
140
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "SUPERVISOR" })
141
- ] });
142
- }
143
- function LiveIndicator({ frame, isRunning }) {
144
- const frames = isRunning ? PULSE_FRAMES : IDLE_FRAMES;
145
- const dot = frames[frame % frames.length];
146
- return /* @__PURE__ */ jsxs(Box, { paddingX: 1, children: [
147
- /* @__PURE__ */ jsx(Text, { color: isRunning ? "#4ade80" : "#6b7280", bold: true, children: dot }),
148
- /* @__PURE__ */ jsxs(Text, { color: isRunning ? "#4ade80" : "#6b7280", bold: true, children: [
149
- " ",
150
- isRunning ? "LIVE" : "IDLE"
151
- ] })
152
- ] });
153
- }
154
- function HeaderBar({
155
- state,
156
- name,
157
- frame,
158
- clock
159
- }) {
160
- if (!state) {
161
- return /* @__PURE__ */ jsxs(Box, { borderStyle: "round", borderColor: "#6b7280", paddingX: 1, flexDirection: "column", children: [
162
- /* @__PURE__ */ jsxs(Box, { justifyContent: "space-between", children: [
163
- /* @__PURE__ */ jsx(Logo, {}),
164
- /* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: clock }) })
165
- ] }),
166
- /* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsxs(Text, { color: "#fbbf24", children: [
167
- '\u27F3 Connecting to "',
168
- name,
169
- '"...'
170
- ] }) })
171
- ] });
172
- }
173
- const isRunning = state.status === "running";
174
- return /* @__PURE__ */ jsxs(
175
- Box,
176
- {
177
- borderStyle: "round",
178
- borderColor: isRunning ? "#6ee7b7" : "#f87171",
179
- paddingX: 0,
180
- flexDirection: "column",
181
- children: [
182
- /* @__PURE__ */ jsxs(Box, { justifyContent: "space-between", children: [
183
- /* @__PURE__ */ jsx(Logo, {}),
184
- /* @__PURE__ */ jsxs(Box, { gap: 2, children: [
185
- /* @__PURE__ */ jsx(LiveIndicator, { frame, isRunning }),
186
- /* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: clock }) })
187
- ] })
188
- ] }),
189
- /* @__PURE__ */ jsxs(Box, { paddingX: 1, gap: 1, children: [
190
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
191
- /* @__PURE__ */ jsxs(Text, { children: [
192
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "pid" }),
193
- " ",
194
- /* @__PURE__ */ jsx(Text, { bold: true, children: state.pid })
195
- ] }),
196
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
197
- /* @__PURE__ */ jsxs(Text, { children: [
198
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "port" }),
199
- " ",
200
- /* @__PURE__ */ jsxs(Text, { bold: true, children: [
201
- ":",
202
- state.port
203
- ] })
204
- ] }),
205
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
206
- /* @__PURE__ */ jsxs(Text, { children: [
207
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "beats" }),
208
- " ",
209
- /* @__PURE__ */ jsxs(Text, { bold: true, color: "#6ee7b7", children: [
210
- "\u25B2",
211
- state.heartbeatCount
212
- ] })
213
- ] }),
214
- state.lastHeartbeat && /* @__PURE__ */ jsxs(Fragment, { children: [
215
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
216
- /* @__PURE__ */ jsxs(Text, { children: [
217
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "last" }),
218
- " ",
219
- /* @__PURE__ */ jsx(Text, { children: formatTimeAgo(state.lastHeartbeat) })
220
- ] })
221
- ] }),
222
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
223
- /* @__PURE__ */ jsxs(Text, { children: [
224
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "up" }),
225
- " ",
226
- /* @__PURE__ */ jsx(Text, { children: formatUptime(state.startedAt) })
227
- ] })
228
- ] })
229
- ]
230
- }
231
- );
232
- }
233
- function BudgetPanel({
234
- state,
235
- dailyCap,
236
- costHistory
237
- }) {
238
- if (!state) return null;
239
- const todayCost = state.todayCostUsd ?? 0;
240
- const totalCost = state.totalCostUsd ?? 0;
241
- const ratio = dailyCap > 0 ? todayCost / dailyCap : 0;
242
- const barWidth = 20;
243
- const bar = buildProgressBar(ratio, barWidth);
244
- const pct = Math.round(ratio * 100);
245
- const barColor = pct < 50 ? "#4ade80" : pct < 80 ? "#fbbf24" : "#f87171";
246
- const sparkline = buildSparkline(costHistory, 12);
247
- return /* @__PURE__ */ jsxs(Box, { paddingX: 2, gap: 2, children: [
248
- /* @__PURE__ */ jsxs(Box, { gap: 1, children: [
249
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "budget" }),
250
- /* @__PURE__ */ jsx(Text, { color: barColor, children: bar.filled }),
251
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: bar.empty }),
252
- /* @__PURE__ */ jsxs(Text, { bold: true, color: barColor, children: [
253
- pct,
254
- "%"
255
- ] }),
256
- /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
257
- "($",
258
- todayCost.toFixed(2),
259
- "/$",
260
- dailyCap,
261
- ")"
262
- ] })
263
- ] }),
264
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
265
- /* @__PURE__ */ jsxs(Box, { gap: 1, children: [
266
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "total" }),
267
- /* @__PURE__ */ jsxs(Text, { bold: true, children: [
268
- "$",
269
- totalCost.toFixed(2)
270
- ] })
271
- ] }),
272
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
273
- /* @__PURE__ */ jsxs(Box, { gap: 1, children: [
274
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "cost/beat" }),
275
- /* @__PURE__ */ jsx(Text, { color: "#818cf8", children: sparkline })
276
- ] })
277
- ] });
278
- }
279
- function ActivityRow({
280
- entry,
281
- isLatest,
282
- isOld
283
- }) {
284
- const icon = TYPE_ICONS[entry.type] ?? "\xB7";
285
- const color = TYPE_COLORS[entry.type] ?? "#9ca3af";
286
- const label = (TYPE_LABELS[entry.type] ?? entry.type.toUpperCase()).padEnd(7);
287
- return /* @__PURE__ */ jsxs(Box, { gap: 1, paddingX: 2, children: [
288
- /* @__PURE__ */ jsx(Text, { dimColor: isOld, children: isLatest ? "\u2502" : "\u2502" }),
289
- /* @__PURE__ */ jsx(Text, { dimColor: isOld, children: formatTime(entry.timestamp) }),
290
- /* @__PURE__ */ jsx(Text, { color, dimColor: isOld, bold: isLatest, children: icon }),
291
- /* @__PURE__ */ jsx(Text, { color, dimColor: isOld, bold: true, children: label }),
292
- /* @__PURE__ */ jsx(Text, { dimColor: isOld, bold: isLatest, children: entry.summary })
293
- ] });
294
- }
295
- var TASK_STATUS_COLORS = {
296
- in_progress: "#60a5fa",
297
- blocked: "#f87171",
298
- pending: "#6b7280",
299
- done: "#4ade80"
300
- };
301
- var TASK_STATUS_LABELS = {
302
- in_progress: "ACTIVE",
303
- blocked: "BLOCK",
304
- pending: "\xB7"
305
- };
306
- function TaskPanel({ tasks }) {
307
- const active = tasks.filter((t) => t.outcome !== "done" && t.outcome !== "abandoned");
308
- const doneCount = tasks.filter((t) => t.outcome === "done").length;
309
- if (tasks.length === 0) return null;
310
- const MAX_VISIBLE = 6;
311
- const visible = active.slice(0, MAX_VISIBLE);
312
- const overflow = active.length - visible.length;
313
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
314
- /* @__PURE__ */ jsxs(Box, { paddingX: 2, gap: 1, children: [
315
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u251C" }),
316
- /* @__PURE__ */ jsx(Text, { dimColor: true, bold: true, children: "TASKS" }),
317
- /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
318
- "(",
319
- active.length,
320
- " active, ",
321
- doneCount,
322
- " done)"
323
- ] }),
324
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500".repeat(30) })
325
- ] }),
326
- visible.map((t) => {
327
- const status = t.outcome ?? "pending";
328
- const color = TASK_STATUS_COLORS[status] ?? "#6b7280";
329
- const label = (TASK_STATUS_LABELS[status] ?? "\xB7").padEnd(6);
330
- const prio = t.severity ? `[${t.severity.slice(0, 3)}] ` : "";
331
- const repo = t.scope !== "global" ? path.basename(t.scope) : "";
332
- const run = t.runId ? `run:${t.runId.slice(0, 4)}` : "";
333
- const meta = [repo, run].filter(Boolean).join(" ");
334
- return /* @__PURE__ */ jsxs(Box, { gap: 1, paddingX: 2, children: [
335
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
336
- /* @__PURE__ */ jsx(Text, { color, bold: true, children: label }),
337
- prio && /* @__PURE__ */ jsx(Text, { dimColor: true, children: prio.padEnd(5) }),
338
- /* @__PURE__ */ jsx(Text, { wrap: "truncate", children: t.content }),
339
- meta && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
340
- "(",
341
- meta,
342
- ")"
343
- ] })
344
- ] }, t.id);
345
- }),
346
- overflow > 0 && /* @__PURE__ */ jsx(Box, { paddingX: 2, children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
347
- "\u2502 ... +",
348
- overflow,
349
- " more pending"
350
- ] }) })
351
- ] });
352
- }
353
- function DecisionBanner({ decisions, frame }) {
354
- if (decisions.length === 0) return null;
355
- const pulseChars = ["\u2605", "\u2606"];
356
- const pulse = pulseChars[frame % pulseChars.length];
357
- return /* @__PURE__ */ jsxs(Box, { paddingX: 2, gap: 1, children: [
358
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u251C" }),
359
- /* @__PURE__ */ jsxs(Text, { color: "#fbbf24", bold: true, children: [
360
- pulse,
361
- " ",
362
- decisions.length,
363
- " decision",
364
- decisions.length > 1 ? "s" : "",
365
- " pending"
366
- ] }),
367
- /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
368
- "\u2014 press ",
369
- /* @__PURE__ */ jsx(Text, { bold: true, children: "tab" }),
370
- " to review"
371
- ] })
372
- ] });
373
- }
374
- function DecisionInputPanel({
375
- decision,
376
- optionIndex,
377
- isTextMode,
378
- textInput,
379
- onTextChange,
380
- onSubmit,
381
- decisionCount,
382
- decisionIdx,
383
- frame
384
- }) {
385
- const hasOptions = decision.options && decision.options.length > 0;
386
- const pulseChars = ["\u2605", "\u2606"];
387
- const pulse = pulseChars[frame % pulseChars.length];
388
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
389
- /* @__PURE__ */ jsxs(Box, { paddingX: 2, gap: 1, children: [
390
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u251C" }),
391
- /* @__PURE__ */ jsxs(Text, { color: "#fbbf24", bold: true, children: [
392
- pulse,
393
- " DECISION"
394
- ] }),
395
- decisionCount > 1 && /* @__PURE__ */ jsxs(Text, { color: "#fbbf24", children: [
396
- "(",
397
- decisionIdx + 1,
398
- "/",
399
- decisionCount,
400
- ")"
401
- ] }),
402
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500".repeat(30) })
403
- ] }),
404
- /* @__PURE__ */ jsxs(Box, { paddingX: 2, gap: 1, children: [
405
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
406
- /* @__PURE__ */ jsx(Text, { bold: true, wrap: "truncate-end", children: decision.question })
407
- ] }),
408
- decision.context && /* @__PURE__ */ jsxs(Box, { paddingX: 2, gap: 1, children: [
409
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
410
- /* @__PURE__ */ jsx(Text, { dimColor: true, wrap: "truncate-end", children: decision.context })
411
- ] }),
412
- hasOptions ? /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: (decision.options ?? []).map((opt, idx) => {
413
- const isSelected = idx === optionIndex;
414
- return /* @__PURE__ */ jsxs(Box, { paddingX: 2, gap: 1, children: [
415
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
416
- isSelected ? /* @__PURE__ */ jsxs(Text, { color: "#fbbf24", bold: true, children: [
417
- "\u25B8 ",
418
- opt.label
419
- ] }) : /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
420
- " ",
421
- opt.label
422
- ] }),
423
- opt.description && isSelected && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
424
- "\u2014 ",
425
- opt.description
426
- ] })
427
- ] }, opt.key);
428
- }) }) : /* @__PURE__ */ jsxs(Box, { paddingX: 2, gap: 1, children: [
429
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
430
- /* @__PURE__ */ jsx(Text, { color: "#fbbf24", bold: true, children: "\u276F" }),
431
- /* @__PURE__ */ jsx(
432
- TextInput,
433
- {
434
- value: textInput,
435
- onChange: onTextChange,
436
- onSubmit,
437
- focus: isTextMode,
438
- placeholder: "type your answer..."
439
- }
440
- )
441
- ] }),
442
- /* @__PURE__ */ jsxs(Box, { paddingX: 2, gap: 1, children: [
443
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2514" }),
444
- /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
445
- hasOptions ? /* @__PURE__ */ jsxs(Fragment, { children: [
446
- /* @__PURE__ */ jsx(Text, { bold: true, children: "\u2191\u2193" }),
447
- " choose \xB7 ",
448
- /* @__PURE__ */ jsx(Text, { bold: true, children: "enter" }),
449
- " confirm"
450
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
451
- /* @__PURE__ */ jsx(Text, { bold: true, children: "enter" }),
452
- " send"
453
- ] }),
454
- decisionCount > 1 && /* @__PURE__ */ jsxs(Fragment, { children: [
455
- " \xB7 ",
456
- /* @__PURE__ */ jsx(Text, { bold: true, children: "\u2190\u2192" }),
457
- " prev/next"
458
- ] }),
459
- " \xB7 ",
460
- /* @__PURE__ */ jsx(Text, { bold: true, children: "tab" }),
461
- " chat \xB7 ",
462
- /* @__PURE__ */ jsx(Text, { bold: true, children: "esc" }),
463
- " back"
464
- ] })
465
- ] })
466
- ] });
467
- }
468
- var ACTIVITY_TYPES = /* @__PURE__ */ new Set([
469
- "heartbeat",
470
- "decision",
471
- "action",
472
- "dispatch",
473
- "error",
474
- "event",
475
- "message"
476
- ]);
477
- function ActivityPanel({ entries, termHeight }) {
478
- const maxVisible = Math.max(5, Math.min(MAX_VISIBLE_ENTRIES, termHeight - 10));
479
- const filtered = entries.filter((e) => ACTIVITY_TYPES.has(e.type));
480
- const visible = filtered.slice(-maxVisible);
481
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
482
- /* @__PURE__ */ jsxs(Box, { paddingX: 2, gap: 1, children: [
483
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u251C" }),
484
- /* @__PURE__ */ jsx(Text, { dimColor: true, bold: true, children: "ACTIVITY" }),
485
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500".repeat(40) })
486
- ] }),
487
- visible.length === 0 ? /* @__PURE__ */ jsx(Box, { paddingX: 2, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502 Waiting for heartbeats..." }) }) : visible.map((entry, idx) => /* @__PURE__ */ jsx(
488
- ActivityRow,
489
- {
490
- entry,
491
- isLatest: idx === visible.length - 1,
492
- isOld: idx < visible.length - 5
493
- },
494
- entry.id
495
- )),
496
- /* @__PURE__ */ jsx(Box, { paddingX: 2, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }) })
497
- ] });
498
- }
499
- function InputPanel({
500
- value,
501
- onChange,
502
- onSubmit,
503
- lastSent,
504
- focus
505
- }) {
506
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
507
- /* @__PURE__ */ jsxs(Box, { paddingX: 2, gap: 1, children: [
508
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2514" }),
509
- /* @__PURE__ */ jsx(Text, { bold: true, color: "#60a5fa", children: "\u276F" }),
510
- /* @__PURE__ */ jsx(
511
- TextInput,
512
- {
513
- value,
514
- onChange,
515
- onSubmit,
516
- focus,
517
- placeholder: "message the supervisor..."
518
- }
519
- )
520
- ] }),
521
- /* @__PURE__ */ jsxs(Box, { paddingX: 2, gap: 1, children: [
522
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: " " }),
523
- lastSent ? /* @__PURE__ */ jsxs(Text, { color: "#6b7280", children: [
524
- '\u2713 "',
525
- lastSent,
526
- '"'
527
- ] }) : null
528
- ] })
529
- ] });
530
- }
531
- function Footer({ hasDecisions }) {
532
- return /* @__PURE__ */ jsxs(Box, { paddingX: 2, gap: 1, justifyContent: "center", children: [
533
- /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
534
- /* @__PURE__ */ jsx(Text, { bold: true, children: "esc" }),
535
- " quit"
536
- ] }),
537
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
538
- /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
539
- /* @__PURE__ */ jsx(Text, { bold: true, children: "enter" }),
540
- " send"
541
- ] }),
542
- hasDecisions && /* @__PURE__ */ jsxs(Fragment, { children: [
543
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
544
- /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
545
- /* @__PURE__ */ jsx(Text, { bold: true, children: "tab" }),
546
- " decisions"
547
- ] })
548
- ] }),
549
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
550
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "daemon keeps running" })
551
- ] });
552
- }
553
- async function readState(name) {
554
- try {
555
- const raw = await readFile(getSupervisorStatePath(name), "utf-8");
556
- return JSON.parse(raw);
557
- } catch (err) {
558
- console.debug(
559
- `[tui] Failed to read supervisor state for ${name}: ${err instanceof Error ? err.message : String(err)}`
560
- );
561
- return null;
562
- }
563
- }
564
- async function readActivity(name, maxEntries) {
565
- try {
566
- const content = await readFile(getSupervisorActivityPath(name), "utf-8");
567
- const lines = content.trim().split("\n").filter(Boolean);
568
- const lastLines = lines.slice(-maxEntries);
569
- const entries = [];
570
- for (const line of lastLines) {
571
- try {
572
- entries.push(JSON.parse(line));
573
- } catch (err) {
574
- console.debug(
575
- `[tui] Skipping malformed activity line: ${err instanceof Error ? err.message : String(err)}`
576
- );
577
- }
578
- }
579
- return entries;
580
- } catch (err) {
581
- console.debug(
582
- `[tui] Failed to read activity for ${name}: ${err instanceof Error ? err.message : String(err)}`
583
- );
584
- return [];
585
- }
586
- }
587
- function readTasks(name) {
588
- try {
589
- const dir = getSupervisorDir(name);
590
- const store = new MemoryStore(path.join(dir, "memory.sqlite"));
591
- const tasks = store.query({ types: ["task"], limit: 20, sortBy: "createdAt" });
592
- store.close();
593
- return tasks;
594
- } catch (err) {
595
- console.debug(
596
- `[tui] Failed to read tasks for ${name}: ${err instanceof Error ? err.message : String(err)}`
597
- );
598
- return [];
599
- }
600
- }
601
- async function readDecisions(name) {
602
- try {
603
- const store = new DecisionStore(getSupervisorDecisionsPath(name));
604
- return await store.pending();
605
- } catch (err) {
606
- console.debug(
607
- `[tui] Failed to read decisions for ${name}: ${err instanceof Error ? err.message : String(err)}`
608
- );
609
- return [];
610
- }
611
- }
612
- async function appendToJsonl(filePath, data) {
613
- const dir = path.dirname(filePath);
614
- try {
615
- await mkdir(dir, { recursive: true });
616
- await appendFile(filePath, `${JSON.stringify(data)}
617
- `, "utf-8");
618
- return true;
619
- } catch (error) {
620
- console.error(
621
- `Warning: Failed to write to ${path.basename(filePath)}: ${error instanceof Error ? error.message : String(error)}`
622
- );
623
- return false;
624
- }
625
- }
626
- async function writeToInbox(name, message) {
627
- const inboxPath = getSupervisorInboxPath(name);
628
- return appendToJsonl(inboxPath, message);
629
- }
630
- async function answerDecision(name, id, answer) {
631
- const store = new DecisionStore(getSupervisorDecisionsPath(name));
632
- await store.answer(id, answer);
633
- const inboxMessage = {
634
- id: randomUUID(),
635
- from: "tui",
636
- text: `decision:answer ${id} ${answer}`,
637
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
638
- };
639
- await writeToInbox(name, inboxMessage);
640
- }
641
- async function sendMessage(name, text) {
642
- const id = randomUUID();
643
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
644
- const message = { id, from: "tui", text, timestamp };
645
- await writeToInbox(name, message);
646
- const activityEntry = { id, type: "message", summary: text, timestamp };
647
- const activityPath = getSupervisorActivityPath(name);
648
- await appendToJsonl(activityPath, activityEntry);
649
- }
650
- function SupervisorTui({ name }) {
651
- const { exit } = useApp();
652
- const { stdout } = useStdout();
653
- const frame = useAnimationFrame();
654
- const clock = useClock();
655
- const [state, setState] = useState(null);
656
- const [entries, setEntries] = useState([]);
657
- const [tasks, setTasks] = useState([]);
658
- const [decisions, setDecisions] = useState([]);
659
- const [dailyCap, setDailyCap] = useState(50);
660
- const [input, setInput] = useState("");
661
- const [lastSent, setLastSent] = useState("");
662
- const [termHeight, setTermHeight] = useState(stdout?.rows ?? 30);
663
- const [decisionIndex, setDecisionIndex] = useState(0);
664
- const [optionIndex, setOptionIndex] = useState(0);
665
- const [decisionAnswer, setDecisionAnswer] = useState("");
666
- const [focusMode, setFocusMode] = useState("input");
667
- useEffect(() => {
668
- function onResize() {
669
- if (stdout) setTermHeight(stdout.rows);
670
- }
671
- stdout?.on("resize", onResize);
672
- return () => {
673
- stdout?.off("resize", onResize);
674
- };
675
- }, [stdout]);
676
- useEffect(() => {
677
- loadGlobalConfig().then((cfg) => setDailyCap(cfg.supervisor.dailyCapUsd)).catch((err) => {
678
- console.debug("[tui] Failed to load global config:", err);
679
- });
680
- }, []);
681
- useEffect(() => {
682
- let active = true;
683
- async function poll() {
684
- if (!active) return;
685
- const [newState, newEntries, newDecisions] = await Promise.all([
686
- readState(name),
687
- readActivity(name, MAX_VISIBLE_ENTRIES),
688
- readDecisions(name)
689
- ]);
690
- if (!active) return;
691
- setState(newState);
692
- setEntries(newEntries);
693
- setDecisions(newDecisions);
694
- setTasks(readTasks(name));
695
- if (newDecisions.length > 0 && decisionIndex >= newDecisions.length) {
696
- setDecisionIndex(0);
697
- }
698
- if (newDecisions.length > 0 && decisions.length === 0) {
699
- setFocusMode("decisions");
700
- }
701
- if (newDecisions.length === 0 && decisions.length > 0) {
702
- setFocusMode("input");
703
- }
704
- }
705
- poll();
706
- const interval = setInterval(poll, POLL_INTERVAL_MS);
707
- return () => {
708
- active = false;
709
- clearInterval(interval);
710
- };
711
- }, [name, decisionIndex, decisions.length]);
712
- const currentDecision = decisions[decisionIndex];
713
- const currentHasOptions = (currentDecision?.options?.length ?? 0) > 0;
714
- const submitDecisionAnswer = useCallback(
715
- async (answer) => {
716
- if (!answer.trim() || !currentDecision) return;
717
- try {
718
- await answerDecision(name, currentDecision.id, answer.trim());
719
- setLastSent(`Decision ${currentDecision.id.slice(4, 12)}: "${answer.trim()}"`);
720
- setDecisionAnswer("");
721
- setOptionIndex(0);
722
- } catch (err) {
723
- console.debug(
724
- `[tui] Failed to answer decision ${currentDecision.id}: ${err instanceof Error ? err.message : String(err)}`
725
- );
726
- }
727
- },
728
- [name, currentDecision]
729
- );
730
- const handleOptionNav = useCallback(
731
- (key) => {
732
- const options = currentDecision?.options;
733
- if (!options || options.length === 0) return false;
734
- if (key.upArrow) {
735
- setOptionIndex((i) => Math.max(0, i - 1));
736
- return true;
737
- }
738
- if (key.downArrow) {
739
- setOptionIndex((i) => Math.min(options.length - 1, i + 1));
740
- return true;
741
- }
742
- if (key.return) {
743
- const opt = options[optionIndex];
744
- if (opt) submitDecisionAnswer(opt.key);
745
- return true;
746
- }
747
- return false;
748
- },
749
- [currentDecision, optionIndex, submitDecisionAnswer]
750
- );
751
- useInput((_char, key) => {
752
- if (key.tab && decisions.length > 0) {
753
- setFocusMode((m) => m === "input" ? "decisions" : "input");
754
- setOptionIndex(0);
755
- return;
756
- }
757
- if (key.escape) {
758
- if (focusMode === "decisions") {
759
- setFocusMode("input");
760
- } else {
761
- exit();
762
- }
763
- return;
764
- }
765
- if (focusMode !== "decisions" || decisions.length === 0) return;
766
- if (currentHasOptions && handleOptionNav(key)) return;
767
- if (decisions.length > 1) {
768
- if (key.leftArrow) {
769
- setDecisionIndex((i) => Math.max(0, i - 1));
770
- setOptionIndex(0);
771
- } else if (key.rightArrow) {
772
- setDecisionIndex((i) => Math.min(decisions.length - 1, i + 1));
773
- setOptionIndex(0);
774
- }
775
- }
776
- });
777
- const handleSubmit = useCallback(
778
- (text) => {
779
- if (!text.trim()) return;
780
- sendMessage(name, text.trim());
781
- setLastSent(text.trim());
782
- setInput("");
783
- },
784
- [name]
785
- );
786
- const costHistory = extractCostHistory(entries);
787
- const activeTaskCount = tasks.filter(
788
- (t) => t.outcome !== "done" && t.outcome !== "abandoned"
789
- ).length;
790
- const taskPanelLines = tasks.length > 0 ? Math.min(activeTaskCount, 6) + 2 : 0;
791
- const decisionPanelLines = focusMode === "decisions" && currentDecision ? (currentHasOptions ? currentDecision.options?.length ?? 0 : 1) + 4 : decisions.length > 0 ? 1 : 0;
792
- const bottomPanel = focusMode === "decisions" && currentDecision ? /* @__PURE__ */ jsx(
793
- DecisionInputPanel,
794
- {
795
- decision: currentDecision,
796
- optionIndex,
797
- isTextMode: !currentHasOptions,
798
- textInput: decisionAnswer,
799
- onTextChange: setDecisionAnswer,
800
- onSubmit: submitDecisionAnswer,
801
- decisionCount: decisions.length,
802
- decisionIdx: decisionIndex,
803
- frame
804
- }
805
- ) : /* @__PURE__ */ jsxs(Fragment, { children: [
806
- /* @__PURE__ */ jsx(
807
- InputPanel,
808
- {
809
- value: input,
810
- onChange: setInput,
811
- onSubmit: handleSubmit,
812
- lastSent,
813
- focus: focusMode === "input"
814
- }
815
- ),
816
- /* @__PURE__ */ jsx(Footer, { hasDecisions: decisions.length > 0 })
817
- ] });
818
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
819
- /* @__PURE__ */ jsx(HeaderBar, { state, name, frame, clock }),
820
- /* @__PURE__ */ jsx(BudgetPanel, { state, dailyCap, costHistory }),
821
- focusMode !== "decisions" && /* @__PURE__ */ jsx(DecisionBanner, { decisions, frame }),
822
- /* @__PURE__ */ jsx(TaskPanel, { tasks }),
823
- /* @__PURE__ */ jsx(
824
- ActivityPanel,
825
- {
826
- entries,
827
- termHeight: termHeight - taskPanelLines - decisionPanelLines
828
- }
829
- ),
830
- bottomPanel
831
- ] });
832
- }
833
-
834
- // src/tui/index.ts
835
- async function renderSupervisorTui(name) {
836
- const { waitUntilExit } = render(React.createElement(SupervisorTui, { name }));
837
- await waitUntilExit();
838
- }
839
- export {
840
- renderSupervisorTui
841
- };
842
- //# sourceMappingURL=tui-67VJ5VBA.js.map