sisyphi 0.1.21 → 0.1.23

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 (60) hide show
  1. package/dist/chunk-KQBSC5KY.js +31 -0
  2. package/dist/chunk-KQBSC5KY.js.map +1 -0
  3. package/dist/{chunk-LTAW6OWS.js → chunk-YGBGKMTF.js} +31 -6
  4. package/dist/chunk-YGBGKMTF.js.map +1 -0
  5. package/dist/chunk-ZE2SKB4B.js +35 -0
  6. package/dist/chunk-ZE2SKB4B.js.map +1 -0
  7. package/dist/cli.js +638 -51
  8. package/dist/cli.js.map +1 -1
  9. package/dist/daemon.js +915 -289
  10. package/dist/daemon.js.map +1 -1
  11. package/dist/paths-FYYSBD27.js +58 -0
  12. package/dist/paths-FYYSBD27.js.map +1 -0
  13. package/dist/templates/CLAUDE.md +21 -20
  14. package/dist/templates/agent-plugin/agents/CLAUDE.md +2 -0
  15. package/dist/templates/agent-plugin/agents/debug.md +1 -0
  16. package/dist/templates/agent-plugin/agents/operator.md +1 -2
  17. package/dist/templates/agent-plugin/agents/plan.md +86 -55
  18. package/dist/templates/agent-plugin/agents/review-plan.md +1 -0
  19. package/dist/templates/agent-plugin/agents/spec-draft.md +1 -0
  20. package/dist/templates/agent-plugin/hooks/hooks.json +19 -1
  21. package/dist/templates/agent-plugin/hooks/intercept-send-message.sh +1 -1
  22. package/dist/templates/agent-plugin/hooks/require-submit.sh +24 -0
  23. package/dist/templates/agent-suffix.md +18 -0
  24. package/dist/templates/dashboard-claude.md +38 -0
  25. package/dist/templates/orchestrator-base.md +270 -0
  26. package/dist/templates/orchestrator-impl.md +116 -0
  27. package/dist/templates/orchestrator-planning.md +131 -0
  28. package/dist/templates/orchestrator-plugin/hooks/hooks.json +1 -15
  29. package/dist/templates/orchestrator-plugin/skills/git-management/SKILL.md +1 -1
  30. package/dist/templates/orchestrator-plugin/skills/orchestration/SKILL.md +4 -16
  31. package/dist/templates/orchestrator-plugin/skills/orchestration/task-patterns.md +22 -23
  32. package/dist/templates/orchestrator-plugin/skills/orchestration/workflow-examples.md +11 -11
  33. package/dist/tui.js +3236 -0
  34. package/dist/tui.js.map +1 -0
  35. package/package.json +5 -1
  36. package/templates/CLAUDE.md +21 -20
  37. package/templates/agent-plugin/agents/CLAUDE.md +2 -0
  38. package/templates/agent-plugin/agents/debug.md +1 -0
  39. package/templates/agent-plugin/agents/operator.md +1 -2
  40. package/templates/agent-plugin/agents/plan.md +86 -55
  41. package/templates/agent-plugin/agents/review-plan.md +1 -0
  42. package/templates/agent-plugin/agents/spec-draft.md +1 -0
  43. package/templates/agent-plugin/hooks/hooks.json +19 -1
  44. package/templates/agent-plugin/hooks/intercept-send-message.sh +1 -1
  45. package/templates/agent-plugin/hooks/require-submit.sh +24 -0
  46. package/templates/agent-suffix.md +18 -0
  47. package/templates/dashboard-claude.md +38 -0
  48. package/templates/orchestrator-base.md +270 -0
  49. package/templates/orchestrator-impl.md +116 -0
  50. package/templates/orchestrator-planning.md +131 -0
  51. package/templates/orchestrator-plugin/hooks/hooks.json +1 -15
  52. package/templates/orchestrator-plugin/skills/git-management/SKILL.md +1 -1
  53. package/templates/orchestrator-plugin/skills/orchestration/SKILL.md +4 -16
  54. package/templates/orchestrator-plugin/skills/orchestration/task-patterns.md +22 -23
  55. package/templates/orchestrator-plugin/skills/orchestration/workflow-examples.md +11 -11
  56. package/dist/chunk-LTAW6OWS.js.map +0 -1
  57. package/dist/templates/orchestrator-plugin/scripts/block-task.sh +0 -11
  58. package/dist/templates/orchestrator.md +0 -173
  59. package/templates/orchestrator-plugin/scripts/block-task.sh +0 -11
  60. package/templates/orchestrator.md +0 -173
package/dist/tui.js ADDED
@@ -0,0 +1,3236 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ loadConfig
4
+ } from "./chunk-KQBSC5KY.js";
5
+ import {
6
+ computeActiveTimeMs
7
+ } from "./chunk-ZE2SKB4B.js";
8
+ import {
9
+ globalDir,
10
+ goalPath,
11
+ logsDir,
12
+ roadmapPath,
13
+ sessionDir,
14
+ socketPath
15
+ } from "./chunk-YGBGKMTF.js";
16
+
17
+ // src/tui/index.tsx
18
+ import { render } from "ink";
19
+
20
+ // src/tui/App.tsx
21
+ import { useState as useState4, useEffect as useEffect3, useCallback as useCallback2, useMemo as useMemo4, useRef as useRef3 } from "react";
22
+ import { Box as Box13, Text as Text13, useApp, useStdout } from "ink";
23
+
24
+ // src/tui/components/SessionTree.tsx
25
+ import { Box, Text } from "ink";
26
+
27
+ // src/tui/lib/tree-render.ts
28
+ function renderTreePrefix(node, nodes, index) {
29
+ if (node.depth === 0) {
30
+ return node.expandable ? node.expanded ? "\u25BC " : "\u25B8 " : " ";
31
+ }
32
+ const parts = [];
33
+ for (let d = 1; d < node.depth; d++) {
34
+ parts.push(isAncestorLastSibling(nodes, index, d) ? " " : "\u2502 ");
35
+ }
36
+ parts.push(isLastSibling(nodes, index) ? "\u2514\u2500" : "\u251C\u2500");
37
+ if (node.expandable) {
38
+ parts.push(node.expanded ? "\u25BC " : "\u25B8 ");
39
+ } else {
40
+ parts.push(" ");
41
+ }
42
+ return parts.join("");
43
+ }
44
+ function isLastSibling(nodes, index) {
45
+ const depth = nodes[index].depth;
46
+ for (let i = index + 1; i < nodes.length; i++) {
47
+ if (nodes[i].depth === depth) return false;
48
+ if (nodes[i].depth < depth) return true;
49
+ }
50
+ return true;
51
+ }
52
+ function isAncestorLastSibling(nodes, index, depth) {
53
+ for (let i = index - 1; i >= 0; i--) {
54
+ if (nodes[i].depth === depth) {
55
+ return isLastSibling(nodes, i);
56
+ }
57
+ if (nodes[i].depth < depth) return true;
58
+ }
59
+ return true;
60
+ }
61
+
62
+ // src/tui/lib/format.ts
63
+ function formatDuration(startOrMs, endIso) {
64
+ let totalMs;
65
+ if (typeof startOrMs === "number") {
66
+ totalMs = startOrMs;
67
+ } else {
68
+ const start = new Date(startOrMs).getTime();
69
+ const end = endIso ? new Date(endIso).getTime() : Date.now();
70
+ totalMs = end - start;
71
+ }
72
+ const totalSeconds = Math.floor(totalMs / 1e3);
73
+ if (totalSeconds < 0) return "0s";
74
+ const hours = Math.floor(totalSeconds / 3600);
75
+ const minutes = Math.floor(totalSeconds % 3600 / 60);
76
+ const seconds = totalSeconds % 60;
77
+ if (hours > 0) return `${hours}h${minutes}m`;
78
+ if (minutes > 0) return `${minutes}m${seconds}s`;
79
+ return `${seconds}s`;
80
+ }
81
+ function formatTimeAgo(iso) {
82
+ const diff = Date.now() - new Date(iso).getTime();
83
+ const minutes = Math.floor(diff / 6e4);
84
+ const hours = Math.floor(minutes / 60);
85
+ if (hours > 0) return `${hours}h ago`;
86
+ if (minutes > 0) return `${minutes}m ago`;
87
+ return "just now";
88
+ }
89
+ function formatTime(iso) {
90
+ const d = new Date(iso);
91
+ return `${d.getHours().toString().padStart(2, "0")}:${d.getMinutes().toString().padStart(2, "0")}`;
92
+ }
93
+ function truncate(text, max) {
94
+ if (max < 4) return text.slice(0, max);
95
+ if (text.length <= max) return text;
96
+ const cut = text.lastIndexOf(" ", max - 1);
97
+ const breakAt = cut > max * 0.6 ? cut : max - 1;
98
+ return text.slice(0, breakAt) + "\u2026";
99
+ }
100
+ function stripMarkdown(text) {
101
+ return text.replace(/^#{1,6}\s+/gm, "").replace(/\*\*(.+?)\*\*/g, "$1").replace(/\*(.+?)\*/g, "$1").replace(/~~(.+?)~~/g, "$1").replace(/`{1,3}[^`]*`{1,3}/g, "").replace(/^[-*+]\s+/gm, "").replace(/^\d+[.)]\s+/gm, "").replace(/\[(.+?)\]\(.+?\)/g, "$1").replace(/^>\s+/gm, "").replace(/---+/g, "").replace(/\n+/g, " ").replace(/\s+/g, " ").trim();
102
+ }
103
+ function extractFirstSentence(text, maxLen) {
104
+ const lines = text.split("\n");
105
+ for (const line of lines) {
106
+ const trimmed = line.trim();
107
+ if (!trimmed) continue;
108
+ if (trimmed.startsWith("#")) continue;
109
+ if (trimmed.startsWith("---")) continue;
110
+ if (trimmed.startsWith("```")) continue;
111
+ if (trimmed.startsWith("|")) continue;
112
+ if (trimmed.length < 5) continue;
113
+ const cleaned = stripMarkdown(trimmed);
114
+ if (cleaned.length < 5) continue;
115
+ const periodIdx = cleaned.indexOf(". ");
116
+ if (periodIdx > 10 && periodIdx < maxLen) {
117
+ return cleaned.slice(0, periodIdx + 1);
118
+ }
119
+ return truncate(cleaned, maxLen);
120
+ }
121
+ const fallback = stripMarkdown(text);
122
+ return truncate(fallback, maxLen);
123
+ }
124
+ function durationColor(startOrMs, endIso) {
125
+ let totalMs;
126
+ if (typeof startOrMs === "number") {
127
+ totalMs = startOrMs;
128
+ } else {
129
+ const start = new Date(startOrMs).getTime();
130
+ const end = endIso ? new Date(endIso).getTime() : Date.now();
131
+ totalMs = end - start;
132
+ }
133
+ if (totalMs < 10 * 60 * 1e3) return "";
134
+ if (totalMs < 30 * 60 * 1e3) return "yellow";
135
+ return "red";
136
+ }
137
+ function statusColor(status) {
138
+ switch (status) {
139
+ case "active":
140
+ case "running":
141
+ return "green";
142
+ case "completed":
143
+ return "cyan";
144
+ case "paused":
145
+ return "yellow";
146
+ case "killed":
147
+ case "crashed":
148
+ return "red";
149
+ case "lost":
150
+ return "gray";
151
+ default:
152
+ return "white";
153
+ }
154
+ }
155
+ function statusIndicator(status) {
156
+ switch (status) {
157
+ case "active":
158
+ return "\u25B6";
159
+ case "completed":
160
+ return "\u2713";
161
+ case "paused":
162
+ return "\u23F8";
163
+ default:
164
+ return "\xB7";
165
+ }
166
+ }
167
+ function agentStatusIcon(status) {
168
+ switch (status) {
169
+ case "running":
170
+ return "\u25B6";
171
+ case "completed":
172
+ return "\u2713";
173
+ case "killed":
174
+ return "\u2715";
175
+ case "crashed":
176
+ return "!";
177
+ case "lost":
178
+ return "?";
179
+ default:
180
+ return "\xB7";
181
+ }
182
+ }
183
+ function agentTypeColor(agentType) {
184
+ if (!agentType) return void 0;
185
+ const t = agentType.toLowerCase();
186
+ if (t.includes("research")) return "blue";
187
+ if (t.includes("implement") || t.includes("code")) return "green";
188
+ if (t.includes("review") || t.includes("test")) return "magenta";
189
+ if (t.includes("plan")) return "yellow";
190
+ return void 0;
191
+ }
192
+ function divider(width, char = "\u2500") {
193
+ return char.repeat(Math.max(0, width));
194
+ }
195
+ function stripFrontmatter(content) {
196
+ if (!content.startsWith("---")) return content;
197
+ const end = content.indexOf("\n---", 3);
198
+ if (end === -1) return content;
199
+ return content.slice(end + 4).trimStart();
200
+ }
201
+ function cleanMarkdown(line) {
202
+ return line.replace(/\*\*(.+?)\*\*/g, "$1").replace(/\*(.+?)\*/g, "$1").replace(/~~(.+?)~~/g, "$1").replace(/`(.+?)`/g, "$1").replace(/\[(.+?)\]\(.+?\)/g, "$1");
203
+ }
204
+ function wrapText(text, width) {
205
+ if (width <= 0) return text.split("\n");
206
+ const result = [];
207
+ for (const rawLine of text.split("\n")) {
208
+ if (rawLine.length <= width) {
209
+ result.push(rawLine);
210
+ continue;
211
+ }
212
+ let remaining = rawLine;
213
+ while (remaining.length > width) {
214
+ let breakAt = remaining.lastIndexOf(" ", width);
215
+ if (breakAt <= 0) breakAt = width;
216
+ result.push(remaining.slice(0, breakAt));
217
+ remaining = remaining.slice(breakAt).trimStart();
218
+ }
219
+ if (remaining) result.push(remaining);
220
+ }
221
+ return result;
222
+ }
223
+
224
+ // src/tui/components/SessionTree.tsx
225
+ import { jsx, jsxs } from "react/jsx-runtime";
226
+ function SessionTree({ nodes, cursorIndex, width, height, focused }) {
227
+ if (nodes.length === 0) {
228
+ return /* @__PURE__ */ jsxs(
229
+ Box,
230
+ {
231
+ flexDirection: "column",
232
+ width,
233
+ borderStyle: "round",
234
+ borderColor: focused ? "yellow" : "gray",
235
+ paddingX: 1,
236
+ children: [
237
+ /* @__PURE__ */ jsx(Text, { bold: true, children: " Sessions " }),
238
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No sessions found." }),
239
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Press [n] to create one." })
240
+ ]
241
+ }
242
+ );
243
+ }
244
+ const maxVisible = Math.max(1, height - 3);
245
+ const halfVisible = Math.floor(maxVisible / 2);
246
+ const scrollOffset = Math.max(
247
+ 0,
248
+ Math.min(cursorIndex - halfVisible, nodes.length - maxVisible)
249
+ );
250
+ const visible = nodes.slice(scrollOffset, scrollOffset + maxVisible);
251
+ const contentWidth = width - 4;
252
+ return /* @__PURE__ */ jsxs(
253
+ Box,
254
+ {
255
+ flexDirection: "column",
256
+ width,
257
+ borderStyle: "round",
258
+ borderColor: focused ? "yellow" : "gray",
259
+ paddingX: 1,
260
+ children: [
261
+ scrollOffset > 0 && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
262
+ " \u2191 ",
263
+ scrollOffset,
264
+ " more"
265
+ ] }),
266
+ visible.map((node, i) => {
267
+ const realIdx = scrollOffset + i;
268
+ const isSelected = realIdx === cursorIndex;
269
+ const prefix = renderTreePrefix(node, nodes, realIdx);
270
+ const { icon, label, meta, color, dim, metaColor } = renderNodeContent(
271
+ node,
272
+ contentWidth - prefix.length
273
+ );
274
+ return /* @__PURE__ */ jsxs(Text, { inverse: isSelected && focused, bold: isSelected, children: [
275
+ prefix,
276
+ /* @__PURE__ */ jsx(Text, { color, dimColor: dim, children: icon }),
277
+ icon ? " " : "",
278
+ /* @__PURE__ */ jsx(Text, { dimColor: dim, children: label }),
279
+ meta ? /* @__PURE__ */ jsxs(Text, { color: metaColor, dimColor: !metaColor, children: [
280
+ " ",
281
+ meta
282
+ ] }) : null
283
+ ] }, node.id);
284
+ }),
285
+ scrollOffset + maxVisible < nodes.length && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
286
+ " \u2193 ",
287
+ nodes.length - scrollOffset - maxVisible,
288
+ " more"
289
+ ] })
290
+ ]
291
+ }
292
+ );
293
+ }
294
+ function renderNodeContent(node, maxWidth) {
295
+ switch (node.type) {
296
+ case "session": {
297
+ const icon = statusIndicator(node.status);
298
+ const color = statusColor(node.status);
299
+ const dim = node.status === "completed";
300
+ const cyclePart = node.cycleCount > 0 ? `C${node.cycleCount}` : "";
301
+ const dur = formatDuration(node.createdAt, node.completedAt);
302
+ const agopart = node.status === "completed" && node.completedAt ? formatTimeAgo(node.completedAt) : "";
303
+ const meta = [cyclePart, dur, agopart].filter(Boolean).join(" ");
304
+ const maxTask = Math.max(8, maxWidth - meta.length - 4);
305
+ return { icon, label: truncate(node.task, maxTask), meta, color, dim };
306
+ }
307
+ case "cycle": {
308
+ const isRunning = !node.completedAt;
309
+ const dur = node.completedAt ? formatDuration(node.timestamp, node.completedAt) : "running";
310
+ const agents = `${node.agentCount} agent${node.agentCount !== 1 ? "s" : ""}`;
311
+ const mode = node.mode ? ` \xB7 ${node.mode}` : "";
312
+ return {
313
+ icon: isRunning ? "\u25CF" : "\u25CB",
314
+ label: `C${node.cycleNumber}`,
315
+ meta: `${dur} \xB7 ${agents}${mode}`,
316
+ color: isRunning ? "green" : "gray",
317
+ dim: !isRunning
318
+ };
319
+ }
320
+ case "agent": {
321
+ const icon = agentStatusIcon(node.status);
322
+ const color = statusColor(node.status);
323
+ const dur = formatDuration(node.spawnedAt, node.completedAt);
324
+ const durClr = durationColor(node.spawnedAt, node.completedAt) || void 0;
325
+ const dim = node.status === "completed";
326
+ const displayName = node.name !== node.agentId ? node.name : node.agentType;
327
+ const maxLabel = Math.max(8, maxWidth - dur.length - 4);
328
+ return {
329
+ icon,
330
+ label: truncate(displayName, maxLabel),
331
+ meta: dur,
332
+ color,
333
+ dim,
334
+ metaColor: durClr
335
+ };
336
+ }
337
+ case "report": {
338
+ const badge = node.reportType === "final" ? "FINAL" : "UPDATE";
339
+ const time = formatTime(node.timestamp);
340
+ return {
341
+ icon: node.reportType === "final" ? "\u25C6" : "\u25C7",
342
+ label: `${badge} ${time}`,
343
+ meta: "",
344
+ color: node.reportType === "final" ? "cyan" : "yellow",
345
+ dim: false
346
+ };
347
+ }
348
+ case "messages":
349
+ return {
350
+ icon: "\u2709",
351
+ label: `Messages (${node.count})`,
352
+ meta: "",
353
+ color: "yellow",
354
+ dim: false
355
+ };
356
+ case "message": {
357
+ const maxLabel = Math.max(8, maxWidth - 8);
358
+ return {
359
+ icon: "\xB7",
360
+ label: truncate(`${node.source}: ${node.summary}`, maxLabel),
361
+ meta: formatTime(node.timestamp),
362
+ color: "gray",
363
+ dim: true
364
+ };
365
+ }
366
+ }
367
+ }
368
+
369
+ // src/tui/components/SessionDetail.tsx
370
+ import { useMemo } from "react";
371
+ import { Box as Box3, Text as Text3 } from "ink";
372
+
373
+ // src/tui/components/PlanView.tsx
374
+ import { Box as Box2, Text as Text2 } from "ink";
375
+ import { jsx as jsx2 } from "react/jsx-runtime";
376
+ function buildPlanLines(content, maxLines, width) {
377
+ const clean = stripFrontmatter(content);
378
+ if (!clean.trim()) return [];
379
+ const contentWidth = width - 4;
380
+ const lines = [];
381
+ const rawLines = clean.split("\n");
382
+ for (const rawLine of rawLines) {
383
+ if (lines.length >= maxLines) break;
384
+ const trimmed = rawLine.trim();
385
+ if (trimmed === "---") continue;
386
+ const headerMatch = rawLine.match(/^(#{1,6})\s+(.+)/);
387
+ if (headerMatch) {
388
+ const level = headerMatch[1].length;
389
+ const headerText = cleanMarkdown(headerMatch[2]);
390
+ const indent = " ".repeat(Math.max(0, level - 1));
391
+ if (lines.length > 0) lines.push({ text: " " });
392
+ lines.push({
393
+ text: ` ${indent}${headerText}`,
394
+ bold: true,
395
+ color: level <= 2 ? "white" : void 0
396
+ });
397
+ continue;
398
+ }
399
+ if (!trimmed) {
400
+ if (lines.length > 0 && lines[lines.length - 1].text !== "") {
401
+ lines.push({ text: " " });
402
+ }
403
+ continue;
404
+ }
405
+ const listMatch = trimmed.match(/^(\d+)[.)]\s+(.+)/);
406
+ if (listMatch) {
407
+ const cleaned2 = `${listMatch[1]}. ${cleanMarkdown(listMatch[2])}`;
408
+ const wrapped2 = wrapText(cleaned2, contentWidth - 6);
409
+ for (const wl of wrapped2) {
410
+ if (lines.length >= maxLines) break;
411
+ lines.push({ text: ` ${wl}`, dim: true });
412
+ }
413
+ continue;
414
+ }
415
+ const checkboxMatch = trimmed.match(/^- \[( |x|X)\] (.+)/);
416
+ if (checkboxMatch) {
417
+ const checked = checkboxMatch[1] !== " ";
418
+ const checkboxText = cleanMarkdown(checkboxMatch[2]);
419
+ const icon = checked ? "\u2611" : "\u2610";
420
+ const wrapped2 = wrapText(`${icon} ${checkboxText}`, contentWidth - 6);
421
+ for (const wl of wrapped2) {
422
+ if (lines.length >= maxLines) break;
423
+ lines.push({ text: ` ${wl}`, dim: true, color: checked ? "green" : void 0 });
424
+ }
425
+ continue;
426
+ }
427
+ const bulletMatch = trimmed.match(/^[-*+]\s+(.+)/);
428
+ if (bulletMatch) {
429
+ const cleaned2 = `\xB7 ${cleanMarkdown(bulletMatch[1])}`;
430
+ const wrapped2 = wrapText(cleaned2, contentWidth - 6);
431
+ for (const wl of wrapped2) {
432
+ if (lines.length >= maxLines) break;
433
+ lines.push({ text: ` ${wl}`, dim: true });
434
+ }
435
+ continue;
436
+ }
437
+ const cleaned = cleanMarkdown(trimmed);
438
+ const wrapped = wrapText(cleaned, contentWidth - 4);
439
+ for (const wl of wrapped) {
440
+ if (lines.length >= maxLines) break;
441
+ lines.push({ text: ` ${wl}`, dim: true });
442
+ }
443
+ }
444
+ const totalContentLines = rawLines.filter((l) => l.trim()).length;
445
+ if (lines.length >= maxLines && totalContentLines > maxLines) {
446
+ lines[lines.length - 1] = { text: " \u2026 [p] open in editor", dim: true };
447
+ }
448
+ return lines;
449
+ }
450
+
451
+ // src/tui/components/SessionDetail.tsx
452
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
453
+ function seg(text, opts) {
454
+ return { text, ...opts };
455
+ }
456
+ function simple(text, opts) {
457
+ return [seg(text, opts)];
458
+ }
459
+ function buildLines(session, planContent, goalContent, logsContent, width, paneAlive) {
460
+ const lines = [];
461
+ const contentWidth = width - 4;
462
+ const agents = session.agents ?? [];
463
+ const cycles = session.orchestratorCycles ?? [];
464
+ const messages = session.messages ?? [];
465
+ const isDead = session.status === "active" && !paneAlive;
466
+ const conflicts = agents.filter((a) => a.mergeStatus === "conflict");
467
+ const goalText = goalContent ? cleanMarkdown(stripFrontmatter(goalContent).trim()) : session.task;
468
+ goalText.split("\n").flatMap((l) => wrapText(l, contentWidth - 2)).forEach((line, i) => {
469
+ lines.push(simple(`${i === 0 ? " " : " "}${line}`, { bold: true }));
470
+ });
471
+ const lastCycle = cycles.length > 0 ? cycles[cycles.length - 1] : null;
472
+ const cycleNum = lastCycle?.cycle ?? 0;
473
+ const mode = lastCycle?.mode ?? "";
474
+ const runningAgents = agents.filter((a) => a.status === "running").length;
475
+ const completedAgents = agents.filter((a) => a.status === "completed").length;
476
+ const elapsed = formatDuration(session.createdAt, session.completedAt);
477
+ const activeMs = computeActiveTimeMs(session);
478
+ const activeTime = formatDuration(activeMs);
479
+ const modeColor = mode === "planning" ? "blue" : mode === "implementation" ? "green" : "cyan";
480
+ lines.push([
481
+ seg(" "),
482
+ seg(isDead ? "\u2715 dead" : session.status, {
483
+ color: statusColor(isDead ? "crashed" : session.status)
484
+ }),
485
+ seg(` \xB7 cycle ${cycleNum}`, { dim: true }),
486
+ ...mode ? [seg(" (", { dim: true }), seg(mode, { color: modeColor }), seg(")", { dim: true })] : [],
487
+ seg(` \xB7 ${elapsed} \xB7 `, { dim: true }),
488
+ seg(`${runningAgents} running`, { color: "green" }),
489
+ seg(" \xB7 ", { dim: true }),
490
+ seg(`${completedAgents} done`, { color: "cyan" }),
491
+ seg(` \xB7 ${activeTime} active`, { dim: true })
492
+ ]);
493
+ if (isDead) {
494
+ lines.push([
495
+ seg(" "),
496
+ seg(" \u2715 DEAD ", { color: "red", bold: true }),
497
+ seg(" tmux window closed \u2014 kill or resume", { color: "red" })
498
+ ]);
499
+ }
500
+ if (conflicts.length > 0) {
501
+ lines.push(
502
+ simple(
503
+ ` \u26A0 ${conflicts.length} merge conflict${conflicts.length > 1 ? "s" : ""}`,
504
+ { color: "red", bold: true }
505
+ )
506
+ );
507
+ }
508
+ lines.push(simple(" "));
509
+ lines.push([
510
+ seg(" \u258E \u25C8 PLAN", { color: "yellow", bold: true })
511
+ ]);
512
+ const planLines = buildPlanLines(planContent, 99999, width);
513
+ if (planLines.length === 0) {
514
+ lines.push(simple(" orchestrator will create one", { dim: true, italic: true }));
515
+ } else {
516
+ for (const pl of planLines) {
517
+ lines.push(simple(pl.text, { bold: pl.bold, dim: pl.dim, color: pl.color }));
518
+ }
519
+ }
520
+ if (session.status === "completed" && session.completionReport) {
521
+ lines.push(simple(" "));
522
+ lines.push([seg(" \u258E \u2713 COMPLETION", { color: "cyan", bold: true })]);
523
+ wrapText(cleanMarkdown(session.completionReport), contentWidth - 6).forEach(
524
+ (l) => {
525
+ lines.push(simple(` ${l}`, { dim: true }));
526
+ }
527
+ );
528
+ }
529
+ lines.push(simple(" "));
530
+ lines.push([
531
+ seg(" \u258E \u27F3 CYCLES", { color: "blue", bold: true }),
532
+ seg(` (${cycles.length})`, { dim: true })
533
+ ]);
534
+ const pushMsgLine = (msg, connector) => {
535
+ const time = formatTime(msg.timestamp);
536
+ const label = msg.source.type === "user" ? "You" : msg.source.type === "agent" ? msg.source.agentId : "\u2699 system";
537
+ const labelColor = msg.source.type === "user" ? "yellow" : msg.source.type === "agent" ? "cyan" : "gray";
538
+ const maxContent = Math.max(10, contentWidth - label.length - 18);
539
+ lines.push([
540
+ seg(` ${connector} `, { dim: true }),
541
+ seg(`[${time}] `, { dim: true }),
542
+ seg(`${label} `, { color: labelColor, bold: true }),
543
+ seg(truncate(msg.summary || msg.content, maxContent), { dim: true })
544
+ ]);
545
+ };
546
+ if (cycles.length === 0) {
547
+ lines.push(simple(" waiting for orchestrator\u2026", { dim: true, italic: true }));
548
+ } else {
549
+ const sortedMsgs = [...messages].sort(
550
+ (a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
551
+ );
552
+ const reversedCycles = [...cycles].reverse();
553
+ for (let i = 0; i < reversedCycles.length; i++) {
554
+ const cycle = reversedCycles[i];
555
+ const olderCycle = reversedCycles[i + 1];
556
+ const isRunning = !cycle.completedAt;
557
+ const isNewest = i === 0;
558
+ const isSecond = i === 1;
559
+ const dot = isRunning ? "\u25CF" : "\u25CB";
560
+ const dotColor = isRunning ? "green" : isNewest ? "white" : "gray";
561
+ const rowDim = !isRunning && !isNewest && !isSecond;
562
+ const duration = isRunning ? "running" : formatDuration(cycle.timestamp, cycle.completedAt);
563
+ const n = cycle.agentsSpawned.length;
564
+ const startTime = formatTime(cycle.timestamp);
565
+ const cycleAgents = agents.filter((a) => cycle.agentsSpawned.includes(a.id));
566
+ const agentDetail = cycleAgents.length === 1 ? truncate(cycleAgents[0].agentType || cycleAgents[0].name, 14) : cycleAgents.length === 2 ? cycleAgents.map((a) => truncate(a.agentType || a.name, 10)).join(" + ") : `${n} agent${n !== 1 ? "s" : ""}`;
567
+ const row2 = [
568
+ seg(` ${dot} `, { color: dotColor }),
569
+ seg(`C${cycle.cycle}`, { bold: isRunning || isNewest, dim: rowDim }),
570
+ seg(" ", {}),
571
+ ...isRunning ? [seg("running", { color: "green", bold: true })] : [seg(duration, { dim: rowDim })],
572
+ seg(" \xB7 ", { dim: true }),
573
+ seg(agentDetail, { dim: rowDim }),
574
+ ...cycle.mode ? [seg(" \xB7 ", { dim: true }), seg(cycle.mode, { color: cycle.mode === "planning" ? "blue" : cycle.mode === "implementation" ? "green" : "cyan" })] : [],
575
+ seg(` ${startTime}`, { dim: true })
576
+ ];
577
+ lines.push(row2);
578
+ const cycleTime = new Date(cycle.timestamp).getTime();
579
+ const olderCycleTime = olderCycle ? new Date(olderCycle.timestamp).getTime() : 0;
580
+ const cycleMsgs = sortedMsgs.filter((m) => {
581
+ const t = new Date(m.timestamp).getTime();
582
+ return t < cycleTime && t >= olderCycleTime;
583
+ });
584
+ cycleMsgs.forEach((msg, mi) => {
585
+ pushMsgLine(msg, mi < cycleMsgs.length - 1 ? "\u251C\u25B8" : "\u2514\u25B8");
586
+ });
587
+ }
588
+ const firstCycleTime = new Date(reversedCycles[reversedCycles.length - 1].timestamp).getTime();
589
+ const preMsgs = sortedMsgs.filter((m) => new Date(m.timestamp).getTime() < firstCycleTime);
590
+ preMsgs.forEach((msg, mi) => {
591
+ pushMsgLine(msg, mi < preMsgs.length - 1 ? "\u251C\u25B8" : "\u2514\u25B8");
592
+ });
593
+ }
594
+ return lines;
595
+ }
596
+ function SessionDetail({
597
+ session,
598
+ planContent,
599
+ goalContent,
600
+ logsContent,
601
+ width,
602
+ height,
603
+ paneAlive,
604
+ scrollOffset = 0,
605
+ focused = false
606
+ }) {
607
+ if (!session) {
608
+ return /* @__PURE__ */ jsx3(
609
+ Box3,
610
+ {
611
+ flexDirection: "column",
612
+ width,
613
+ borderStyle: "round",
614
+ borderColor: "gray",
615
+ paddingX: 1,
616
+ justifyContent: "center",
617
+ alignItems: "center",
618
+ children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "Select a session to view details" })
619
+ }
620
+ );
621
+ }
622
+ const allLines = useMemo(
623
+ () => buildLines(session, planContent, goalContent, logsContent, width, paneAlive),
624
+ [session, planContent, goalContent, logsContent, width, paneAlive]
625
+ );
626
+ const innerHeight = height - 2;
627
+ const hasOverflow = allLines.length > innerHeight;
628
+ const viewableHeight = hasOverflow ? innerHeight - 1 : innerHeight;
629
+ const maxScroll = Math.max(0, allLines.length - viewableHeight);
630
+ const effectiveOffset = Math.min(scrollOffset, maxScroll);
631
+ const visible = allLines.slice(effectiveOffset, effectiveOffset + viewableHeight);
632
+ const padCount = viewableHeight - visible.length;
633
+ const scrollPct = maxScroll > 0 ? Math.round(effectiveOffset / maxScroll * 100) : 100;
634
+ return /* @__PURE__ */ jsxs2(
635
+ Box3,
636
+ {
637
+ flexDirection: "column",
638
+ width,
639
+ borderStyle: "round",
640
+ borderColor: focused ? "blue" : "gray",
641
+ paddingX: 1,
642
+ children: [
643
+ visible.map((line, i) => /* @__PURE__ */ jsx3(Text3, { children: line.map((s, j) => /* @__PURE__ */ jsx3(
644
+ Text3,
645
+ {
646
+ color: s.color,
647
+ bold: s.bold,
648
+ dimColor: s.dim,
649
+ italic: s.italic,
650
+ children: s.text
651
+ },
652
+ j
653
+ )) }, effectiveOffset + i)),
654
+ padCount > 0 && Array.from({ length: padCount }, (_, i) => /* @__PURE__ */ jsx3(Text3, { children: " " }, `pad-${i}`)),
655
+ hasOverflow && /* @__PURE__ */ jsxs2(Text3, { dimColor: true, children: [
656
+ " ",
657
+ "[tab] scroll \xB7 ",
658
+ scrollPct,
659
+ "% \xB7 ",
660
+ allLines.length,
661
+ " lines"
662
+ ] })
663
+ ]
664
+ }
665
+ );
666
+ }
667
+
668
+ // src/tui/components/LogsPanel.tsx
669
+ import { useMemo as useMemo2 } from "react";
670
+ import { Box as Box4, Text as Text4 } from "ink";
671
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
672
+ function seg2(text, opts) {
673
+ return { text, ...opts };
674
+ }
675
+ function buildLines2(cycleLogs, width) {
676
+ const lines = [];
677
+ const contentWidth = width - 4;
678
+ if (cycleLogs.length === 0) {
679
+ return lines;
680
+ }
681
+ const sorted = [...cycleLogs].sort((a, b) => b.cycle - a.cycle);
682
+ for (const { cycle, content } of sorted) {
683
+ lines.push([seg2(` Cycle ${cycle}`, { color: "blue", bold: true })]);
684
+ const cleaned = cleanMarkdown(stripFrontmatter(content)).trim();
685
+ if (cleaned) {
686
+ for (const rawLine of cleaned.split("\n")) {
687
+ const wrapped = wrapText(rawLine, contentWidth - 2);
688
+ for (const wl of wrapped) {
689
+ lines.push([seg2(` ${wl}`, { dim: true })]);
690
+ }
691
+ }
692
+ }
693
+ lines.push([seg2(" ")]);
694
+ }
695
+ return lines;
696
+ }
697
+ function LogsPanel({
698
+ cycleLogs,
699
+ width,
700
+ height,
701
+ scrollOffset = 0,
702
+ focused = false
703
+ }) {
704
+ if (cycleLogs.length === 0) {
705
+ return /* @__PURE__ */ jsx4(
706
+ Box4,
707
+ {
708
+ flexDirection: "column",
709
+ width,
710
+ borderStyle: "round",
711
+ borderColor: focused ? "blue" : "gray",
712
+ paddingX: 1,
713
+ justifyContent: "center",
714
+ alignItems: "center",
715
+ children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "No logs" })
716
+ }
717
+ );
718
+ }
719
+ const allLines = useMemo2(
720
+ () => buildLines2(cycleLogs, width),
721
+ [cycleLogs, width]
722
+ );
723
+ const innerHeight = height - 2;
724
+ const hasOverflow = allLines.length > innerHeight;
725
+ const viewableHeight = hasOverflow ? innerHeight - 1 : innerHeight;
726
+ const maxScroll = Math.max(0, allLines.length - viewableHeight);
727
+ const effectiveOffset = Math.min(scrollOffset, maxScroll);
728
+ const visible = allLines.slice(effectiveOffset, effectiveOffset + viewableHeight);
729
+ const padCount = viewableHeight - visible.length;
730
+ const scrollPct = maxScroll > 0 ? Math.round(effectiveOffset / maxScroll * 100) : 100;
731
+ return /* @__PURE__ */ jsxs3(
732
+ Box4,
733
+ {
734
+ flexDirection: "column",
735
+ width,
736
+ borderStyle: "round",
737
+ borderColor: focused ? "blue" : "gray",
738
+ paddingX: 1,
739
+ children: [
740
+ visible.map((line, i) => /* @__PURE__ */ jsx4(Text4, { children: line.map((s, j) => /* @__PURE__ */ jsx4(Text4, { color: s.color, bold: s.bold, dimColor: s.dim, children: s.text }, j)) }, effectiveOffset + i)),
741
+ padCount > 0 && Array.from({ length: padCount }, (_, i) => /* @__PURE__ */ jsx4(Text4, { children: " " }, `pad-${i}`)),
742
+ hasOverflow && /* @__PURE__ */ jsxs3(Text4, { dimColor: true, children: [
743
+ " ",
744
+ "[tab] scroll \xB7 ",
745
+ scrollPct,
746
+ "% \xB7 ",
747
+ allLines.length,
748
+ " lines"
749
+ ] })
750
+ ]
751
+ }
752
+ );
753
+ }
754
+
755
+ // src/tui/components/CycleDetail.tsx
756
+ import { Box as Box5, Text as Text5 } from "ink";
757
+ import { Fragment, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
758
+ function CycleDetail({ cycle, agents, width, height }) {
759
+ const contentWidth = width - 4;
760
+ const dur = cycle.completedAt ? formatDuration(cycle.timestamp, cycle.completedAt) : "running";
761
+ const isRunning = !cycle.completedAt;
762
+ const cycleAgents = agents.filter((a) => cycle.agentsSpawned.includes(a.id));
763
+ const headerLines = 4;
764
+ const agentLinesBudget = cycleAgents.reduce((sum, a) => {
765
+ let n = 1;
766
+ if (a.instruction) n++;
767
+ if (a.status === "completed" && a.reports.length > 0) n++;
768
+ return sum + n;
769
+ }, 0);
770
+ const agentSectionLines = Math.max(1, agentLinesBudget) + 2;
771
+ const promptAvailable = height - headerLines - agentSectionLines - 6;
772
+ return /* @__PURE__ */ jsxs4(
773
+ Box5,
774
+ {
775
+ flexDirection: "column",
776
+ width,
777
+ borderStyle: "round",
778
+ borderColor: "gray",
779
+ paddingX: 1,
780
+ children: [
781
+ /* @__PURE__ */ jsxs4(Text5, { bold: true, children: [
782
+ " Cycle ",
783
+ cycle.cycle
784
+ ] }),
785
+ /* @__PURE__ */ jsxs4(Box5, { children: [
786
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: " " }),
787
+ /* @__PURE__ */ jsx5(Text5, { color: isRunning ? "green" : "gray", children: isRunning ? "running" : "completed" }),
788
+ /* @__PURE__ */ jsxs4(Text5, { dimColor: true, children: [
789
+ " \xB7 ",
790
+ dur,
791
+ " \xB7 ",
792
+ cycleAgents.length,
793
+ " agent",
794
+ cycleAgents.length !== 1 ? "s" : "",
795
+ cycle.mode ? " \xB7 " : ""
796
+ ] }),
797
+ cycle.mode && /* @__PURE__ */ jsx5(Text5, { color: cycle.mode === "planning" ? "blue" : cycle.mode === "implementation" ? "green" : "cyan", children: cycle.mode })
798
+ ] }),
799
+ /* @__PURE__ */ jsxs4(Text5, { dimColor: true, children: [
800
+ " ",
801
+ formatTime(cycle.timestamp),
802
+ cycle.completedAt ? ` \u2192 ${formatTime(cycle.completedAt)}` : ""
803
+ ] }),
804
+ /* @__PURE__ */ jsx5(Text5, { children: " " }),
805
+ /* @__PURE__ */ jsxs4(Text5, { color: "green", bold: true, children: [
806
+ " ",
807
+ "\u258E \u229E AGENTS"
808
+ ] }),
809
+ cycleAgents.length === 0 ? /* @__PURE__ */ jsxs4(Text5, { dimColor: true, italic: true, children: [
810
+ " ",
811
+ "orchestrator spawning agents\u2026"
812
+ ] }) : cycleAgents.map((agent) => {
813
+ const nameLabel = agent.name !== agent.id ? agent.name : agent.agentType;
814
+ const instrPreview = agent.instruction.split("\n")[0] || "";
815
+ const latestReport = agent.reports.length > 0 ? agent.reports[agent.reports.length - 1] : null;
816
+ const reportSummary = latestReport && agent.status === "completed" ? extractFirstSentence(latestReport.summary, contentWidth - 14) : null;
817
+ return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
818
+ /* @__PURE__ */ jsxs4(Box5, { children: [
819
+ /* @__PURE__ */ jsx5(Text5, { children: " " }),
820
+ /* @__PURE__ */ jsx5(Text5, { color: statusColor(agent.status), children: agentStatusIcon(agent.status) }),
821
+ /* @__PURE__ */ jsxs4(Text5, { bold: true, children: [
822
+ " ",
823
+ agent.id
824
+ ] }),
825
+ /* @__PURE__ */ jsxs4(Text5, { color: agentTypeColor(agent.agentType), dimColor: !agentTypeColor(agent.agentType), children: [
826
+ " ",
827
+ truncate(nameLabel, contentWidth - 30)
828
+ ] }),
829
+ (() => {
830
+ const dur2 = formatDuration(agent.spawnedAt, agent.completedAt);
831
+ const durColor = durationColor(agent.spawnedAt, agent.completedAt) || void 0;
832
+ return /* @__PURE__ */ jsxs4(Text5, { dimColor: true, children: [
833
+ " \xB7 ",
834
+ agent.status,
835
+ " \xB7 ",
836
+ /* @__PURE__ */ jsx5(Text5, { color: durColor, children: dur2 })
837
+ ] });
838
+ })()
839
+ ] }),
840
+ instrPreview && /* @__PURE__ */ jsxs4(Text5, { dimColor: true, children: [
841
+ " ",
842
+ truncate(instrPreview, contentWidth - 10)
843
+ ] }),
844
+ reportSummary && /* @__PURE__ */ jsxs4(Text5, { dimColor: true, children: [
845
+ " ",
846
+ /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: "\u21B3" }),
847
+ " ",
848
+ reportSummary
849
+ ] })
850
+ ] }, agent.id);
851
+ }),
852
+ cycle.nextPrompt && /* @__PURE__ */ jsxs4(Fragment, { children: [
853
+ /* @__PURE__ */ jsx5(Text5, { children: " " }),
854
+ /* @__PURE__ */ jsxs4(Text5, { color: "yellow", bold: true, children: [
855
+ " ",
856
+ "\u258E \u25B7 NEXT PROMPT"
857
+ ] }),
858
+ wrapText(cycle.nextPrompt, contentWidth - 6).slice(0, Math.max(3, promptAvailable)).map((line, i) => /* @__PURE__ */ jsxs4(Text5, { dimColor: true, children: [
859
+ " ",
860
+ line
861
+ ] }, i))
862
+ ] })
863
+ ]
864
+ }
865
+ );
866
+ }
867
+
868
+ // src/tui/components/AgentDetail.tsx
869
+ import { Box as Box6, Text as Text6 } from "ink";
870
+ import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
871
+ function AgentDetail({ agent, reportBlocks, width, height }) {
872
+ const contentWidth = width - 4;
873
+ const dur = formatDuration(agent.spawnedAt, agent.completedAt);
874
+ const icon = agentStatusIcon(agent.status);
875
+ const color = statusColor(agent.status);
876
+ const nameLabel = agent.name !== agent.id ? agent.name : agent.agentType;
877
+ const metaLines = 3;
878
+ const alertLines = (agent.killedReason ? 1 : 0) + (agent.mergeStatus === "conflict" && agent.mergeDetails ? 2 : 0);
879
+ const timestampLines = 1 + (agent.completedAt ? 1 : 0) + (agent.paneId ? 1 : 0) + (agent.worktreePath ? 1 : 0) + (agent.branchName ? 1 : 0) + 2;
880
+ const reportHeader = agent.reports.length > 0 ? 2 : 0;
881
+ const fixedLines = metaLines + alertLines + timestampLines + reportHeader + 4;
882
+ const hasResolvedReports = reportBlocks && reportBlocks.length > 0;
883
+ const availableForContent = Math.max(6, height - fixedLines);
884
+ let instrMaxLines;
885
+ let reportMaxLines;
886
+ if (hasResolvedReports) {
887
+ instrMaxLines = Math.max(4, Math.floor(availableForContent * 0.35));
888
+ reportMaxLines = Math.max(4, availableForContent - instrMaxLines);
889
+ } else {
890
+ const summaryLines = agent.reports.length;
891
+ instrMaxLines = Math.max(4, availableForContent - summaryLines - 1);
892
+ reportMaxLines = summaryLines;
893
+ }
894
+ return /* @__PURE__ */ jsxs5(
895
+ Box6,
896
+ {
897
+ flexDirection: "column",
898
+ width,
899
+ borderStyle: "round",
900
+ borderColor: "gray",
901
+ paddingX: 1,
902
+ children: [
903
+ /* @__PURE__ */ jsxs5(Text6, { bold: true, children: [
904
+ " ",
905
+ /* @__PURE__ */ jsx6(Text6, { color, children: icon }),
906
+ " ",
907
+ agent.id,
908
+ " \xB7 ",
909
+ nameLabel
910
+ ] }),
911
+ /* @__PURE__ */ jsxs5(Box6, { children: [
912
+ /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: " " }),
913
+ /* @__PURE__ */ jsx6(Text6, { color, children: agent.status }),
914
+ /* @__PURE__ */ jsxs5(Text6, { dimColor: true, children: [
915
+ " \xB7 ",
916
+ dur,
917
+ " \xB7 ",
918
+ agent.agentType
919
+ ] }),
920
+ agent.mergeStatus && /* @__PURE__ */ jsxs5(Fragment2, { children: [
921
+ /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: " \xB7 " }),
922
+ agent.mergeStatus === "merged" && /* @__PURE__ */ jsx6(Text6, { color: "green", children: "\u2295 merged" }),
923
+ agent.mergeStatus === "pending" && /* @__PURE__ */ jsx6(Text6, { color: "yellow", children: "\u25CC pending" }),
924
+ agent.mergeStatus === "no-changes" && /* @__PURE__ */ jsx6(Text6, { color: "gray", children: "\u2205 no changes" }),
925
+ agent.mergeStatus === "conflict" && /* @__PURE__ */ jsx6(Text6, { color: "red", children: "\u26A0 conflict" }),
926
+ !["merged", "pending", "no-changes", "conflict"].includes(agent.mergeStatus) && /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: agent.mergeStatus })
927
+ ] })
928
+ ] }),
929
+ agent.killedReason && /* @__PURE__ */ jsxs5(Text6, { color: "red", children: [
930
+ " ",
931
+ "\u26A0 ",
932
+ agent.killedReason
933
+ ] }),
934
+ agent.mergeStatus === "conflict" && agent.mergeDetails && /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", children: wrapText(agent.mergeDetails, contentWidth - 6).map((line, i) => /* @__PURE__ */ jsxs5(Text6, { color: "red", children: [
935
+ " ",
936
+ "\u26A0 ",
937
+ line
938
+ ] }, i)) }),
939
+ /* @__PURE__ */ jsx6(Text6, { children: " " }),
940
+ /* @__PURE__ */ jsxs5(Text6, { color: "white", bold: true, children: [
941
+ " ",
942
+ "\u258E \u25B7 INSTRUCTION"
943
+ ] }),
944
+ wrapText(agent.instruction, contentWidth - 6).slice(0, instrMaxLines).map((line, i) => /* @__PURE__ */ jsxs5(Text6, { dimColor: true, children: [
945
+ " ",
946
+ line
947
+ ] }, i)),
948
+ agent.reports.length > 0 && /* @__PURE__ */ jsxs5(Fragment2, { children: [
949
+ /* @__PURE__ */ jsx6(Text6, { children: " " }),
950
+ /* @__PURE__ */ jsxs5(Text6, { color: "cyan", bold: true, children: [
951
+ " ",
952
+ "\u258E \u25C7 REPORTS (",
953
+ agent.reports.length,
954
+ ")"
955
+ ] }),
956
+ hasResolvedReports ? /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", children: reportBlocks.slice(0, Math.min(reportBlocks.length, 3)).map((block, i) => {
957
+ const badge = block.type === "final" ? "FINAL" : "UPDATE";
958
+ const badgeColor = block.type === "final" ? "cyan" : "yellow";
959
+ const linesPerReport = Math.max(2, Math.floor(reportMaxLines / Math.min(reportBlocks.length, 3)) - 2);
960
+ return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
961
+ i > 0 && /* @__PURE__ */ jsx6(Text6, { children: " " }),
962
+ /* @__PURE__ */ jsxs5(Box6, { children: [
963
+ /* @__PURE__ */ jsx6(Text6, { children: " " }),
964
+ /* @__PURE__ */ jsx6(Text6, { color: badgeColor, bold: block.type === "final", children: badge }),
965
+ /* @__PURE__ */ jsxs5(Text6, { dimColor: true, children: [
966
+ " ",
967
+ formatTime(block.timestamp)
968
+ ] })
969
+ ] }),
970
+ wrapText(cleanMarkdown(block.content.trim()), contentWidth - 10).slice(0, linesPerReport).map((line, j) => /* @__PURE__ */ jsxs5(Text6, { dimColor: true, children: [
971
+ " ",
972
+ line
973
+ ] }, j))
974
+ ] }, i);
975
+ }) }) : agent.reports.slice(0, reportMaxLines).map((report, i) => {
976
+ const badge = report.type === "final" ? "FINAL" : "UPDATE";
977
+ const badgeColor = report.type === "final" ? "cyan" : "yellow";
978
+ return /* @__PURE__ */ jsxs5(Box6, { children: [
979
+ /* @__PURE__ */ jsx6(Text6, { children: " " }),
980
+ /* @__PURE__ */ jsx6(Text6, { color: badgeColor, bold: report.type === "final", children: badge }),
981
+ /* @__PURE__ */ jsxs5(Text6, { dimColor: true, children: [
982
+ " ",
983
+ formatTime(report.timestamp),
984
+ " ",
985
+ report.summary.split("\n")[0]
986
+ ] })
987
+ ] }, i);
988
+ })
989
+ ] }),
990
+ /* @__PURE__ */ jsx6(Text6, { children: " " }),
991
+ /* @__PURE__ */ jsxs5(Text6, { color: "gray", bold: true, children: [
992
+ " ",
993
+ "\u258E \u25E6 META"
994
+ ] }),
995
+ /* @__PURE__ */ jsxs5(Text6, { dimColor: true, children: [
996
+ " ",
997
+ "Spawned: ",
998
+ formatTime(agent.spawnedAt)
999
+ ] }),
1000
+ agent.completedAt && /* @__PURE__ */ jsxs5(Text6, { dimColor: true, children: [
1001
+ " ",
1002
+ "Completed: ",
1003
+ formatTime(agent.completedAt)
1004
+ ] }),
1005
+ agent.paneId && /* @__PURE__ */ jsxs5(Text6, { dimColor: true, children: [
1006
+ " ",
1007
+ "Pane: ",
1008
+ agent.paneId
1009
+ ] }),
1010
+ agent.worktreePath && /* @__PURE__ */ jsxs5(Text6, { dimColor: true, children: [
1011
+ " ",
1012
+ "Worktree: ",
1013
+ agent.worktreePath
1014
+ ] }),
1015
+ agent.branchName && /* @__PURE__ */ jsxs5(Text6, { dimColor: true, children: [
1016
+ " ",
1017
+ "Branch: ",
1018
+ agent.branchName
1019
+ ] })
1020
+ ]
1021
+ }
1022
+ );
1023
+ }
1024
+
1025
+ // src/tui/components/ReportView.tsx
1026
+ import { useState, useMemo as useMemo3 } from "react";
1027
+ import { Box as Box7, Text as Text7, useInput } from "ink";
1028
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1029
+ function ReportView({ agent, reportBlocks, width, height, onClose }) {
1030
+ const [scrollOffset, setScrollOffset] = useState(0);
1031
+ const contentWidth = width - 6;
1032
+ const lines = useMemo3(() => {
1033
+ const lines2 = [];
1034
+ if (reportBlocks.length === 0) {
1035
+ lines2.push({ text: "", dim: true });
1036
+ lines2.push({ text: " No reports submitted yet.", dim: true });
1037
+ lines2.push({ text: "", dim: true });
1038
+ return lines2;
1039
+ }
1040
+ for (let i = 0; i < reportBlocks.length; i++) {
1041
+ const report = reportBlocks[i];
1042
+ const time = formatTime(report.timestamp);
1043
+ if (i > 0) {
1044
+ lines2.push({ text: "" });
1045
+ lines2.push({ text: ` ${divider(contentWidth - 2, "\xB7")}`, dim: true });
1046
+ lines2.push({ text: "" });
1047
+ }
1048
+ const badge = report.type === "final" ? "FINAL" : "UPDATE";
1049
+ const badgeColor = report.type === "final" ? "cyan" : "yellow";
1050
+ lines2.push({
1051
+ text: ` ${badge} ${time}`,
1052
+ color: badgeColor,
1053
+ bold: report.type === "final"
1054
+ });
1055
+ lines2.push({ text: "" });
1056
+ const wrapped = wrapText(report.content.trim(), contentWidth - 4);
1057
+ for (const line of wrapped) {
1058
+ lines2.push({ text: ` ${line}` });
1059
+ }
1060
+ }
1061
+ lines2.push({ text: "" });
1062
+ return lines2;
1063
+ }, [reportBlocks, contentWidth]);
1064
+ const viewableHeight = height - 7;
1065
+ const maxScroll = Math.max(0, lines.length - viewableHeight);
1066
+ useInput((input, key) => {
1067
+ if (key.escape || key.return) {
1068
+ onClose();
1069
+ return;
1070
+ }
1071
+ if (key.upArrow) {
1072
+ setScrollOffset((o) => Math.max(0, o - 1));
1073
+ return;
1074
+ }
1075
+ if (key.downArrow) {
1076
+ setScrollOffset((o) => Math.min(maxScroll, o + 1));
1077
+ return;
1078
+ }
1079
+ if (input === "[" || key.upArrow && key.shift) {
1080
+ setScrollOffset((o) => Math.max(0, o - Math.floor(viewableHeight / 2)));
1081
+ return;
1082
+ }
1083
+ if (input === "]" || key.downArrow && key.shift) {
1084
+ setScrollOffset((o) => Math.min(maxScroll, o + Math.floor(viewableHeight / 2)));
1085
+ return;
1086
+ }
1087
+ });
1088
+ const visible = lines.slice(scrollOffset, scrollOffset + viewableHeight);
1089
+ const scrollPct = maxScroll > 0 ? Math.round(scrollOffset / maxScroll * 100) : 100;
1090
+ const totalReports = agent.reports.length;
1091
+ const icon = agentStatusIcon(agent.status);
1092
+ const color = statusColor(agent.status);
1093
+ const dur = formatDuration(agent.spawnedAt, agent.completedAt);
1094
+ return /* @__PURE__ */ jsxs6(
1095
+ Box7,
1096
+ {
1097
+ flexDirection: "column",
1098
+ width,
1099
+ height,
1100
+ borderStyle: "round",
1101
+ borderColor: "cyan",
1102
+ paddingX: 1,
1103
+ children: [
1104
+ /* @__PURE__ */ jsxs6(Text7, { bold: true, children: [
1105
+ " ",
1106
+ /* @__PURE__ */ jsx7(Text7, { color, children: icon }),
1107
+ " ",
1108
+ agent.id,
1109
+ " ",
1110
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "\xB7" }),
1111
+ " ",
1112
+ agent.name !== agent.id ? agent.name : agent.agentType
1113
+ ] }),
1114
+ /* @__PURE__ */ jsxs6(Text7, { dimColor: true, children: [
1115
+ " ",
1116
+ agent.status,
1117
+ " \xB7 ",
1118
+ dur,
1119
+ " \xB7 ",
1120
+ agent.agentType,
1121
+ " \xB7 ",
1122
+ totalReports,
1123
+ " report",
1124
+ totalReports !== 1 ? "s" : ""
1125
+ ] }),
1126
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " " + divider(contentWidth - 2) }),
1127
+ /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", flexGrow: 1, children: visible.map((line, i) => /* @__PURE__ */ jsx7(
1128
+ Text7,
1129
+ {
1130
+ color: line.color,
1131
+ bold: line.bold,
1132
+ dimColor: line.dim,
1133
+ children: line.text
1134
+ },
1135
+ scrollOffset + i
1136
+ )) }),
1137
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " " + divider(contentWidth - 2) }),
1138
+ /* @__PURE__ */ jsxs6(Text7, { dimColor: true, children: [
1139
+ " ",
1140
+ "[esc] back [\u2191\u2193] scroll [",
1141
+ "] page",
1142
+ " ",
1143
+ /* @__PURE__ */ jsxs6(Text7, { children: [
1144
+ scrollPct,
1145
+ "%"
1146
+ ] }),
1147
+ maxScroll > 0 && /* @__PURE__ */ jsxs6(Text7, { dimColor: true, children: [
1148
+ " \xB7 ",
1149
+ lines.length,
1150
+ " lines"
1151
+ ] })
1152
+ ] })
1153
+ ]
1154
+ }
1155
+ );
1156
+ }
1157
+
1158
+ // src/tui/components/MessageLog.tsx
1159
+ import { Box as Box8, Text as Text8 } from "ink";
1160
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
1161
+ function sourceLabel(msg) {
1162
+ if (msg.source.type === "user") return "You";
1163
+ if (msg.source.type === "agent") return msg.source.agentId;
1164
+ return "system";
1165
+ }
1166
+ function sourceColor(msg) {
1167
+ if (msg.source.type === "user") return "yellow";
1168
+ if (msg.source.type === "agent") return "cyan";
1169
+ return "gray";
1170
+ }
1171
+ function MessageLog({ messages, maxMessages, width }) {
1172
+ if (messages.length === 0) {
1173
+ return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
1174
+ /* @__PURE__ */ jsx8(Text8, { bold: true, dimColor: true, children: "Messages" }),
1175
+ /* @__PURE__ */ jsx8(Text8, { dimColor: true, italic: true, children: " No messages" })
1176
+ ] });
1177
+ }
1178
+ const recent = messages.slice(-maxMessages);
1179
+ const contentWidth = Math.max(10, width - 20);
1180
+ return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
1181
+ /* @__PURE__ */ jsx8(Text8, { bold: true, dimColor: true, children: "Messages" }),
1182
+ recent.map((msg) => {
1183
+ const time = formatTime(msg.timestamp);
1184
+ const label = sourceLabel(msg);
1185
+ const color = sourceColor(msg);
1186
+ const content = truncate(msg.summary || msg.content, contentWidth);
1187
+ return /* @__PURE__ */ jsxs7(Text8, { children: [
1188
+ " ",
1189
+ /* @__PURE__ */ jsxs7(Text8, { dimColor: true, children: [
1190
+ "[",
1191
+ time,
1192
+ "]"
1193
+ ] }),
1194
+ " ",
1195
+ /* @__PURE__ */ jsxs7(Text8, { color, bold: true, children: [
1196
+ label,
1197
+ ":"
1198
+ ] }),
1199
+ " ",
1200
+ content
1201
+ ] }, msg.id);
1202
+ })
1203
+ ] });
1204
+ }
1205
+
1206
+ // src/tui/components/InputBar.tsx
1207
+ import { useState as useState2, useEffect, useRef } from "react";
1208
+ import { Box as Box9, Text as Text9, useInput as useInput2 } from "ink";
1209
+ import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
1210
+ var INPUT_MODES = /* @__PURE__ */ new Set([
1211
+ "resume",
1212
+ "continue",
1213
+ "rollback",
1214
+ "delete-confirm",
1215
+ "spawn-agent",
1216
+ "search",
1217
+ "message-agent",
1218
+ "shell-command"
1219
+ ]);
1220
+ var PROMPTS = {
1221
+ resume: "resume instructions (optional)",
1222
+ continue: "new direction (optional)",
1223
+ rollback: "cycle number",
1224
+ "delete-confirm": "type 'yes' to confirm delete:",
1225
+ "spawn-agent": "agent instruction:",
1226
+ search: "filter:",
1227
+ "message-agent": "message:",
1228
+ "shell-command": "$ "
1229
+ };
1230
+ var OPTIONAL_INPUT = /* @__PURE__ */ new Set(["resume", "continue", "search"]);
1231
+ function InputBar({ mode, defaultText, onSubmit, onCancel }) {
1232
+ const [text, setText] = useState2("");
1233
+ const [cursorPos, setCursorPos] = useState2(0);
1234
+ const prevMode = useRef(mode);
1235
+ useEffect(() => {
1236
+ if (mode !== prevMode.current) {
1237
+ if (INPUT_MODES.has(mode) && defaultText) {
1238
+ setText(defaultText);
1239
+ setCursorPos(defaultText.length);
1240
+ } else {
1241
+ setText("");
1242
+ setCursorPos(0);
1243
+ }
1244
+ }
1245
+ prevMode.current = mode;
1246
+ }, [mode, defaultText]);
1247
+ useInput2(
1248
+ (input, key) => {
1249
+ if (key.return) {
1250
+ if (OPTIONAL_INPUT.has(mode) || text.trim()) {
1251
+ onSubmit(text.trim());
1252
+ setText("");
1253
+ setCursorPos(0);
1254
+ }
1255
+ return;
1256
+ }
1257
+ if (key.escape) {
1258
+ setText("");
1259
+ setCursorPos(0);
1260
+ onCancel();
1261
+ return;
1262
+ }
1263
+ if (key.leftArrow) {
1264
+ setCursorPos((p) => Math.max(0, p - 1));
1265
+ return;
1266
+ }
1267
+ if (key.rightArrow) {
1268
+ setCursorPos((p) => Math.min(text.length, p + 1));
1269
+ return;
1270
+ }
1271
+ if (key.ctrl && input === "a") {
1272
+ setCursorPos(0);
1273
+ return;
1274
+ }
1275
+ if (key.ctrl && input === "e") {
1276
+ setCursorPos(text.length);
1277
+ return;
1278
+ }
1279
+ if (key.ctrl && input === "k") {
1280
+ setText((t) => t.slice(0, cursorPos));
1281
+ return;
1282
+ }
1283
+ if (key.ctrl && input === "u") {
1284
+ setText((t) => t.slice(cursorPos));
1285
+ setCursorPos(0);
1286
+ return;
1287
+ }
1288
+ if (key.backspace || key.delete) {
1289
+ if (cursorPos > 0) {
1290
+ setText((t) => t.slice(0, cursorPos - 1) + t.slice(cursorPos));
1291
+ setCursorPos((p) => p - 1);
1292
+ }
1293
+ return;
1294
+ }
1295
+ if (input && !key.ctrl && !key.meta) {
1296
+ setText((t) => t.slice(0, cursorPos) + input + t.slice(cursorPos));
1297
+ setCursorPos((p) => p + input.length);
1298
+ }
1299
+ },
1300
+ { isActive: INPUT_MODES.has(mode) }
1301
+ );
1302
+ if (mode === "navigate") {
1303
+ return /* @__PURE__ */ jsx9(Box9, { paddingX: 1, children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Press [m] to message orchestrator, [n] for new session" }) });
1304
+ }
1305
+ if (mode === "report-detail" || mode === "leader" || mode === "copy-menu" || mode === "help" || !INPUT_MODES.has(mode)) {
1306
+ return null;
1307
+ }
1308
+ const prompt = PROMPTS[mode] ?? mode;
1309
+ return /* @__PURE__ */ jsxs8(Box9, { paddingX: 1, children: [
1310
+ /* @__PURE__ */ jsxs8(Text9, { color: "yellow", children: [
1311
+ prompt,
1312
+ " > "
1313
+ ] }),
1314
+ /* @__PURE__ */ jsx9(Text9, { children: text.slice(0, cursorPos) }),
1315
+ /* @__PURE__ */ jsx9(Text9, { inverse: true, children: cursorPos < text.length ? text[cursorPos] : " " }),
1316
+ /* @__PURE__ */ jsx9(Text9, { children: text.slice(cursorPos + 1) }),
1317
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: " (enter to send, esc to cancel)" })
1318
+ ] });
1319
+ }
1320
+
1321
+ // src/tui/components/StatusLine.tsx
1322
+ import { Box as Box10, Text as Text10 } from "ink";
1323
+ import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
1324
+ function StatusLine({ mode, detailFocused = false, logsFocused = false, showLogs = false }) {
1325
+ if (mode === "report-detail") {
1326
+ return null;
1327
+ }
1328
+ if (mode === "leader") {
1329
+ return /* @__PURE__ */ jsxs9(Box10, { paddingX: 1, children: [
1330
+ /* @__PURE__ */ jsx10(Text10, { color: "magenta", bold: true, children: "LEADER" }),
1331
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: " press a command key or [esc] to cancel" })
1332
+ ] });
1333
+ }
1334
+ if (mode === "copy-menu") {
1335
+ return /* @__PURE__ */ jsxs9(Box10, { paddingX: 1, children: [
1336
+ /* @__PURE__ */ jsx10(Text10, { color: "cyan", bold: true, children: "COPY" }),
1337
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: " [p] path [C] context [l] logs [s] session ID [esc] cancel" })
1338
+ ] });
1339
+ }
1340
+ if (mode === "help") {
1341
+ return /* @__PURE__ */ jsxs9(Box10, { paddingX: 1, children: [
1342
+ /* @__PURE__ */ jsx10(Text10, { color: "yellow", bold: true, children: "HELP" }),
1343
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: " [esc] or [?] to dismiss" })
1344
+ ] });
1345
+ }
1346
+ if (mode === "delete-confirm") {
1347
+ return /* @__PURE__ */ jsxs9(Box10, { paddingX: 1, children: [
1348
+ /* @__PURE__ */ jsx10(Text10, { color: "red", bold: true, children: "DELETE" }),
1349
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: " type 'yes' to confirm, [esc] to cancel" })
1350
+ ] });
1351
+ }
1352
+ if (mode === "spawn-agent") {
1353
+ return /* @__PURE__ */ jsxs9(Box10, { paddingX: 1, children: [
1354
+ /* @__PURE__ */ jsx10(Text10, { color: "green", bold: true, children: "SPAWN" }),
1355
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: " enter agent instruction, [esc] to cancel" })
1356
+ ] });
1357
+ }
1358
+ if (mode === "search") {
1359
+ return /* @__PURE__ */ jsxs9(Box10, { paddingX: 1, children: [
1360
+ /* @__PURE__ */ jsx10(Text10, { color: "blue", bold: true, children: "SEARCH" }),
1361
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: " type to filter, enter to apply, [esc] to cancel" })
1362
+ ] });
1363
+ }
1364
+ if (mode === "message-agent") {
1365
+ return /* @__PURE__ */ jsxs9(Box10, { paddingX: 1, children: [
1366
+ /* @__PURE__ */ jsx10(Text10, { color: "cyan", bold: true, children: "MESSAGE" }),
1367
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: " enter message for agent, [esc] to cancel" })
1368
+ ] });
1369
+ }
1370
+ if (mode === "shell-command") {
1371
+ return /* @__PURE__ */ jsxs9(Box10, { paddingX: 1, children: [
1372
+ /* @__PURE__ */ jsx10(Text10, { color: "magenta", bold: true, children: "SHELL" }),
1373
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: " enter command, [esc] to cancel" })
1374
+ ] });
1375
+ }
1376
+ if (mode !== "navigate") {
1377
+ return /* @__PURE__ */ jsx10(Box10, { paddingX: 1, children: /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "[enter] send [esc] cancel" }) });
1378
+ }
1379
+ if (logsFocused) {
1380
+ return /* @__PURE__ */ jsxs9(Box10, { paddingX: 1, children: [
1381
+ /* @__PURE__ */ jsx10(Text10, { bold: true, children: "[\u2191\u2193]" }),
1382
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: " scroll " }),
1383
+ /* @__PURE__ */ jsx10(Text10, { bold: true, children: "[\u2190/tab]" }),
1384
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: " back " }),
1385
+ /* @__PURE__ */ jsx10(Text10, { bold: true, children: "[l]" }),
1386
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "ogs hide " }),
1387
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "\u2502 " }),
1388
+ /* @__PURE__ */ jsx10(Text10, { bold: true, children: "[m]" }),
1389
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "sg " }),
1390
+ /* @__PURE__ */ jsx10(Text10, { bold: true, children: "[k]" }),
1391
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "ill " }),
1392
+ /* @__PURE__ */ jsx10(Text10, { bold: true, children: "[g]" }),
1393
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "oal " }),
1394
+ /* @__PURE__ */ jsx10(Text10, { bold: true, children: "[n]" }),
1395
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "ew " }),
1396
+ /* @__PURE__ */ jsx10(Text10, { bold: true, children: "[p]" }),
1397
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "lan " }),
1398
+ /* @__PURE__ */ jsx10(Text10, { bold: true, children: "[w]" }),
1399
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "indow " }),
1400
+ /* @__PURE__ */ jsx10(Text10, { bold: true, children: "[R]" }),
1401
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "esume " }),
1402
+ /* @__PURE__ */ jsx10(Text10, { bold: true, children: "[q]" }),
1403
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "uit" })
1404
+ ] });
1405
+ }
1406
+ if (detailFocused) {
1407
+ return /* @__PURE__ */ jsxs9(Box10, { paddingX: 1, children: [
1408
+ /* @__PURE__ */ jsx10(Text10, { bold: true, children: "[\u2191\u2193]" }),
1409
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: " scroll " }),
1410
+ /* @__PURE__ */ jsx10(Text10, { bold: true, children: "[\u2190/tab]" }),
1411
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: " back " }),
1412
+ /* @__PURE__ */ jsx10(Text10, { bold: true, children: "[l]" }),
1413
+ /* @__PURE__ */ jsxs9(Text10, { dimColor: true, children: [
1414
+ showLogs ? "ogs hide" : "ogs show",
1415
+ " "
1416
+ ] }),
1417
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "\u2502 " }),
1418
+ /* @__PURE__ */ jsx10(Text10, { bold: true, children: "[m]" }),
1419
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "sg " }),
1420
+ /* @__PURE__ */ jsx10(Text10, { bold: true, children: "[k]" }),
1421
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "ill " }),
1422
+ /* @__PURE__ */ jsx10(Text10, { bold: true, children: "[g]" }),
1423
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "oal " }),
1424
+ /* @__PURE__ */ jsx10(Text10, { bold: true, children: "[n]" }),
1425
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "ew " }),
1426
+ /* @__PURE__ */ jsx10(Text10, { bold: true, children: "[p]" }),
1427
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "lan " }),
1428
+ /* @__PURE__ */ jsx10(Text10, { bold: true, children: "[w]" }),
1429
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "indow " }),
1430
+ /* @__PURE__ */ jsx10(Text10, { bold: true, children: "[R]" }),
1431
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "esume " }),
1432
+ /* @__PURE__ */ jsx10(Text10, { bold: true, children: "[q]" }),
1433
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "uit" })
1434
+ ] });
1435
+ }
1436
+ return /* @__PURE__ */ jsxs9(Box10, { paddingX: 1, children: [
1437
+ /* @__PURE__ */ jsx10(Text10, { bold: true, children: "[\u2191\u2193]" }),
1438
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: " navigate " }),
1439
+ /* @__PURE__ */ jsx10(Text10, { bold: true, children: "[\u2190\u2192]" }),
1440
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: " collapse/expand " }),
1441
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "\u2502 " }),
1442
+ /* @__PURE__ */ jsx10(Text10, { bold: true, children: "[space]" }),
1443
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: " leader " }),
1444
+ /* @__PURE__ */ jsx10(Text10, { bold: true, children: "[tab]" }),
1445
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: " detail " }),
1446
+ /* @__PURE__ */ jsx10(Text10, { bold: true, children: "[l]" }),
1447
+ /* @__PURE__ */ jsxs9(Text10, { dimColor: true, children: [
1448
+ showLogs ? "ogs hide" : "ogs show",
1449
+ " "
1450
+ ] }),
1451
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "\u2502 " }),
1452
+ /* @__PURE__ */ jsx10(Text10, { bold: true, children: "[m]" }),
1453
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "sg " }),
1454
+ /* @__PURE__ */ jsx10(Text10, { bold: true, children: "[k]" }),
1455
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "ill " }),
1456
+ /* @__PURE__ */ jsx10(Text10, { bold: true, children: "[n]" }),
1457
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "ew " }),
1458
+ /* @__PURE__ */ jsx10(Text10, { bold: true, children: "[R]" }),
1459
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "esume " }),
1460
+ /* @__PURE__ */ jsx10(Text10, { bold: true, children: "[q]" }),
1461
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "uit" })
1462
+ ] });
1463
+ }
1464
+
1465
+ // src/tui/components/LeaderOverlay.tsx
1466
+ import { Box as Box11, Text as Text11 } from "ink";
1467
+ import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
1468
+ var LEADER_WIDTH = 26;
1469
+ var LEADER_HEIGHT = 16;
1470
+ var COPY_HEIGHT = 9;
1471
+ var INNER = LEADER_WIDTH - 2;
1472
+ function pad(s) {
1473
+ return s.padEnd(INNER);
1474
+ }
1475
+ function LeaderOverlay({ mode, rows, cols }) {
1476
+ if (mode === "leader") {
1477
+ return /* @__PURE__ */ jsxs10(
1478
+ Box11,
1479
+ {
1480
+ position: "absolute",
1481
+ marginTop: rows - LEADER_HEIGHT - 2,
1482
+ marginLeft: cols - LEADER_WIDTH - 1,
1483
+ width: LEADER_WIDTH,
1484
+ flexDirection: "column",
1485
+ borderStyle: "round",
1486
+ borderColor: "magenta",
1487
+ children: [
1488
+ /* @__PURE__ */ jsx11(Text11, { bold: true, color: "magenta", children: pad(" LEADER") }),
1489
+ /* @__PURE__ */ jsx11(Text11, { children: pad(" ") }),
1490
+ /* @__PURE__ */ jsx11(Text11, { children: pad(" y copy menu") }),
1491
+ /* @__PURE__ */ jsx11(Text11, { children: pad(" d delete session") }),
1492
+ /* @__PURE__ */ jsx11(Text11, { children: pad(" l daemon logs") }),
1493
+ /* @__PURE__ */ jsx11(Text11, { children: pad(" o open session dir") }),
1494
+ /* @__PURE__ */ jsx11(Text11, { children: pad(" a spawn agent") }),
1495
+ /* @__PURE__ */ jsx11(Text11, { children: pad(" m message agent") }),
1496
+ /* @__PURE__ */ jsx11(Text11, { children: pad(" / search") }),
1497
+ /* @__PURE__ */ jsx11(Text11, { children: pad(" ! shell command") }),
1498
+ /* @__PURE__ */ jsx11(Text11, { children: pad(" ? help") }),
1499
+ /* @__PURE__ */ jsx11(Text11, { children: pad(" 1-9 jump to session") }),
1500
+ /* @__PURE__ */ jsx11(Text11, { children: pad(" ") }),
1501
+ /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: pad(" esc dismiss") })
1502
+ ]
1503
+ }
1504
+ );
1505
+ }
1506
+ if (mode === "copy-menu") {
1507
+ return /* @__PURE__ */ jsxs10(
1508
+ Box11,
1509
+ {
1510
+ position: "absolute",
1511
+ marginTop: rows - COPY_HEIGHT - 2,
1512
+ marginLeft: cols - LEADER_WIDTH - 1,
1513
+ width: LEADER_WIDTH,
1514
+ flexDirection: "column",
1515
+ borderStyle: "round",
1516
+ borderColor: "cyan",
1517
+ children: [
1518
+ /* @__PURE__ */ jsx11(Text11, { bold: true, color: "cyan", children: pad(" COPY") }),
1519
+ /* @__PURE__ */ jsx11(Text11, { children: pad(" ") }),
1520
+ /* @__PURE__ */ jsx11(Text11, { children: pad(" p session path") }),
1521
+ /* @__PURE__ */ jsx11(Text11, { children: pad(" C LLM context") }),
1522
+ /* @__PURE__ */ jsx11(Text11, { children: pad(" l logs content") }),
1523
+ /* @__PURE__ */ jsx11(Text11, { children: pad(" s session ID") }),
1524
+ /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: pad(" esc cancel") })
1525
+ ]
1526
+ }
1527
+ );
1528
+ }
1529
+ return null;
1530
+ }
1531
+
1532
+ // src/tui/components/HelpOverlay.tsx
1533
+ import { Box as Box12, Text as Text12 } from "ink";
1534
+ import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
1535
+ var HELP_WIDTH = 62;
1536
+ var INNER2 = HELP_WIDTH - 2;
1537
+ function pad2(s) {
1538
+ return s.padEnd(INNER2);
1539
+ }
1540
+ function row(left, right) {
1541
+ const col = Math.floor(INNER2 / 2);
1542
+ return (left.padEnd(col) + right).padEnd(INNER2);
1543
+ }
1544
+ function HelpOverlay({ mode, rows, cols }) {
1545
+ if (mode !== "help") return null;
1546
+ const marginLeft = Math.max(0, Math.floor((cols - HELP_WIDTH) / 2));
1547
+ const lines = [
1548
+ row(" \u2191\u2193 move cursor", " \u2190\u2192 collapse/expand"),
1549
+ row(" tab switch pane", " enter expand/open report"),
1550
+ "",
1551
+ row(" n new session", " k kill session/agent"),
1552
+ row(" R resume session", " C continue session"),
1553
+ row(" b rollback cycle", " x restart agent"),
1554
+ row(" r re-run agent", " j jump to pane"),
1555
+ row(" m message orch.", " g edit goal"),
1556
+ row(" p open roadmap", " w go to window"),
1557
+ row(" l toggle logs", " q quit"),
1558
+ "",
1559
+ row(" space \u2192 y copy submenu", " space \u2192 d delete session"),
1560
+ row(" space \u2192 l tail logs", " space \u2192 o open dir"),
1561
+ row(" space \u2192 / search", " space \u2192 a spawn agent"),
1562
+ row(" space \u2192 m msg agent", " space \u2192 ! shell"),
1563
+ row(" space \u2192 ? help", " space \u2192 1-9 jump"),
1564
+ "",
1565
+ row(" y \u2192 p session path", " y \u2192 C LLM context"),
1566
+ row(" y \u2192 l logs content", " y \u2192 s session ID")
1567
+ ];
1568
+ const overlayHeight = Math.min(lines.length + 4, rows - 2);
1569
+ return /* @__PURE__ */ jsxs11(
1570
+ Box12,
1571
+ {
1572
+ position: "absolute",
1573
+ marginTop: Math.max(0, Math.floor((rows - overlayHeight) / 2)),
1574
+ marginLeft,
1575
+ width: HELP_WIDTH,
1576
+ flexDirection: "column",
1577
+ borderStyle: "round",
1578
+ borderColor: "yellow",
1579
+ children: [
1580
+ /* @__PURE__ */ jsx12(Text12, { bold: true, color: "yellow", children: pad2(" KEYBINDINGS (esc or ? to close)") }),
1581
+ /* @__PURE__ */ jsx12(Text12, { children: pad2(" ") }),
1582
+ lines.map((line, i) => {
1583
+ if (line === "") return /* @__PURE__ */ jsx12(Text12, { children: pad2(" ") }, i);
1584
+ return /* @__PURE__ */ jsx12(Text12, { children: pad2(line) }, i);
1585
+ }),
1586
+ /* @__PURE__ */ jsx12(Text12, { children: pad2(" ") })
1587
+ ]
1588
+ }
1589
+ );
1590
+ }
1591
+
1592
+ // src/tui/hooks/usePolling.ts
1593
+ import { useState as useState3, useEffect as useEffect2, useRef as useRef2, useCallback } from "react";
1594
+
1595
+ // src/tui/lib/client.ts
1596
+ import { connect } from "net";
1597
+ function send(request) {
1598
+ const sock = socketPath();
1599
+ return new Promise((resolve, reject) => {
1600
+ const socket = connect(sock);
1601
+ let data = "";
1602
+ const timeout = setTimeout(() => {
1603
+ socket.destroy();
1604
+ reject(new Error("Request timed out"));
1605
+ }, 5e3);
1606
+ socket.on("connect", () => {
1607
+ socket.write(JSON.stringify(request) + "\n");
1608
+ });
1609
+ socket.on("data", (chunk) => {
1610
+ data += chunk.toString();
1611
+ const idx = data.indexOf("\n");
1612
+ if (idx !== -1) {
1613
+ clearTimeout(timeout);
1614
+ const line = data.slice(0, idx);
1615
+ socket.destroy();
1616
+ try {
1617
+ resolve(JSON.parse(line));
1618
+ } catch {
1619
+ reject(new Error(`Invalid JSON from daemon`));
1620
+ }
1621
+ }
1622
+ });
1623
+ socket.on("error", (err) => {
1624
+ clearTimeout(timeout);
1625
+ reject(err);
1626
+ });
1627
+ });
1628
+ }
1629
+
1630
+ // src/tui/hooks/usePolling.ts
1631
+ import { readFileSync as readFileSync2, existsSync, readdirSync } from "fs";
1632
+ import { join as join2 } from "path";
1633
+
1634
+ // src/tui/lib/tmux.ts
1635
+ import { execSync } from "child_process";
1636
+ import { join } from "path";
1637
+ import { readFileSync, writeFileSync, mkdtempSync, rmSync } from "fs";
1638
+ import { tmpdir } from "os";
1639
+ var EXEC_ENV = {
1640
+ ...process.env,
1641
+ PATH: `/opt/homebrew/bin:/usr/local/bin:${process.env["PATH"] ?? "/usr/bin:/bin"}`
1642
+ };
1643
+ function execSafe(cmd) {
1644
+ try {
1645
+ return execSync(cmd, { encoding: "utf-8", env: EXEC_ENV, stdio: ["pipe", "pipe", "pipe"] }).trim();
1646
+ } catch {
1647
+ return null;
1648
+ }
1649
+ }
1650
+ function shellQuote(s) {
1651
+ return `'${s.replace(/'/g, "'\\''")}'`;
1652
+ }
1653
+ function selectWindow(windowId) {
1654
+ execSafe(`tmux select-window -t "${windowId}"`);
1655
+ }
1656
+ function selectPane(paneId) {
1657
+ execSafe(`tmux select-pane -t "${paneId}"`);
1658
+ }
1659
+ function windowExists(windowId) {
1660
+ return execSafe(`tmux display-message -t "${windowId}" -p "#{window_id}"`) !== null;
1661
+ }
1662
+ function openCompanionPopup(cwd2) {
1663
+ const templatePath = join(import.meta.dirname, "templates", "dashboard-claude.md");
1664
+ let template;
1665
+ try {
1666
+ template = readFileSync(templatePath, "utf-8");
1667
+ } catch {
1668
+ template = `You are a Sisyphus dashboard companion. Help the user manage multi-agent sessions.
1669
+ Project: ${cwd2}
1670
+ Run \`sisyphus list\` and \`sisyphus status\` to see current state.`;
1671
+ }
1672
+ const rendered = template.replace(/\{\{CWD\}\}/g, cwd2);
1673
+ const promptPath = join(globalDir(), "dashboard-companion-prompt.md");
1674
+ writeFileSync(promptPath, rendered, "utf-8");
1675
+ execSync(
1676
+ `tmux display-popup -E -w 80% -h 80% -d ${shellQuote(cwd2)} ${shellQuote(`claude --dangerously-skip-permissions --system-prompt "$(cat ${shellQuote(promptPath)})"`)}`,
1677
+ { stdio: "inherit", env: EXEC_ENV }
1678
+ );
1679
+ }
1680
+ var TERMINAL_EDITORS = /* @__PURE__ */ new Set(["nvim", "vim", "vi", "nano", "emacs", "micro", "helix", "hx", "joe", "ne", "kak"]);
1681
+ function switchToSession(sessionName) {
1682
+ execSafe(`tmux switch-client -t "${sessionName}"`);
1683
+ }
1684
+ function editInPopup(cwd2, editor, opts) {
1685
+ const tmpDir = mkdtempSync(join(tmpdir(), "sisyphus-"));
1686
+ const filePath = join(tmpDir, "input.md");
1687
+ try {
1688
+ writeFileSync(filePath, opts?.content ? opts.content : "", "utf-8");
1689
+ openEditorPopup(cwd2, editor, filePath, opts?.size);
1690
+ const result = readFileSync(filePath, "utf-8").trim();
1691
+ return result || null;
1692
+ } finally {
1693
+ rmSync(tmpDir, { recursive: true, force: true });
1694
+ }
1695
+ }
1696
+ function openLogPopup() {
1697
+ execSync(
1698
+ `tmux display-popup -E -w 90% -h 80% ${shellQuote("tail -f ~/.sisyphus/daemon.log")}`,
1699
+ { stdio: "inherit", env: EXEC_ENV }
1700
+ );
1701
+ }
1702
+ function openShellPopup(cwd2, command) {
1703
+ execSync(
1704
+ `tmux display-popup -E -w 90% -h 80% -d ${shellQuote(cwd2)} ${shellQuote(`sh -c '${command.replace(/'/g, "'\\''")}; echo; echo "Press enter to close"; read'`)}`,
1705
+ { stdio: "inherit", env: EXEC_ENV }
1706
+ );
1707
+ }
1708
+ function openInFileManager(path) {
1709
+ execSync(`open ${shellQuote(path)}`, { stdio: "inherit", env: EXEC_ENV });
1710
+ }
1711
+ function openEditorPopup(cwd2, editor, filePath, size) {
1712
+ const { w = "90%", h = "90%" } = size ?? {};
1713
+ const editorBin = editor.split(/\s+/)[0].split("/").pop();
1714
+ if (TERMINAL_EDITORS.has(editorBin)) {
1715
+ execSync(
1716
+ `tmux display-popup -E -w ${w} -h ${h} -d ${shellQuote(cwd2)} ${shellQuote(`${editor} ${shellQuote(filePath)}`)}`,
1717
+ { stdio: "inherit", env: EXEC_ENV }
1718
+ );
1719
+ } else {
1720
+ execSync(`${editor} ${shellQuote(filePath)}`, { stdio: "inherit", cwd: cwd2, env: EXEC_ENV });
1721
+ }
1722
+ }
1723
+
1724
+ // src/tui/hooks/usePolling.ts
1725
+ function usePolling(cwd2, selectedSessionId, intervalMs = 2500) {
1726
+ const [state, setState] = useState3({
1727
+ sessions: [],
1728
+ selectedSession: null,
1729
+ planContent: "",
1730
+ goalContent: "",
1731
+ logsContent: "",
1732
+ logsCycles: [],
1733
+ paneAlive: true,
1734
+ error: null
1735
+ });
1736
+ const selectedIdRef = useRef2(selectedSessionId);
1737
+ selectedIdRef.current = selectedSessionId;
1738
+ const mountedRef = useRef2(true);
1739
+ const poll = useCallback(async () => {
1740
+ try {
1741
+ const listRes = await send({ type: "list", cwd: cwd2 });
1742
+ if (!mountedRef.current) return;
1743
+ const sessions = listRes.ok ? listRes.data?.sessions ?? [] : [];
1744
+ let selectedSession = null;
1745
+ let planContent = "";
1746
+ let goalContent = "";
1747
+ let logsContent = "";
1748
+ let logsCycles = [];
1749
+ let paneAlive = true;
1750
+ if (selectedIdRef.current) {
1751
+ const statusRes = await send({ type: "status", sessionId: selectedIdRef.current, cwd: cwd2 });
1752
+ if (mountedRef.current && statusRes.ok) {
1753
+ selectedSession = statusRes.data?.session ?? null;
1754
+ }
1755
+ if (selectedSession?.tmuxWindowId) {
1756
+ try {
1757
+ paneAlive = windowExists(selectedSession.tmuxWindowId);
1758
+ } catch {
1759
+ paneAlive = false;
1760
+ }
1761
+ }
1762
+ try {
1763
+ const pp = roadmapPath(cwd2, selectedIdRef.current);
1764
+ if (existsSync(pp)) {
1765
+ planContent = readFileSync2(pp, "utf-8");
1766
+ }
1767
+ } catch {
1768
+ }
1769
+ try {
1770
+ const gp = goalPath(cwd2, selectedIdRef.current);
1771
+ if (existsSync(gp)) {
1772
+ goalContent = readFileSync2(gp, "utf-8");
1773
+ }
1774
+ } catch {
1775
+ }
1776
+ try {
1777
+ const ld = logsDir(cwd2, selectedIdRef.current);
1778
+ if (existsSync(ld)) {
1779
+ const files = readdirSync(ld).filter((f) => f.startsWith("cycle-")).sort();
1780
+ logsCycles = files.map((f) => {
1781
+ const match = f.match(/cycle-(\d+)\.md$/);
1782
+ const cycle = match ? parseInt(match[1], 10) : 0;
1783
+ const content = readFileSync2(join2(ld, f), "utf-8");
1784
+ return { cycle, content };
1785
+ });
1786
+ logsContent = logsCycles.map((c) => c.content).join("\n");
1787
+ }
1788
+ } catch {
1789
+ }
1790
+ }
1791
+ if (mountedRef.current) {
1792
+ setState({ sessions, selectedSession, planContent, goalContent, logsContent, logsCycles, paneAlive, error: null });
1793
+ }
1794
+ } catch (err) {
1795
+ if (mountedRef.current) {
1796
+ setState((prev) => ({ ...prev, error: err.message }));
1797
+ }
1798
+ }
1799
+ }, [cwd2]);
1800
+ useEffect2(() => {
1801
+ mountedRef.current = true;
1802
+ poll();
1803
+ const interval = setInterval(poll, intervalMs);
1804
+ return () => {
1805
+ mountedRef.current = false;
1806
+ clearInterval(interval);
1807
+ };
1808
+ }, [cwd2, intervalMs, poll]);
1809
+ useEffect2(() => {
1810
+ if (selectedSessionId != null) {
1811
+ poll();
1812
+ }
1813
+ }, [selectedSessionId, poll]);
1814
+ return state;
1815
+ }
1816
+
1817
+ // src/tui/hooks/useKeybindings.ts
1818
+ import { useInput as useInput3 } from "ink";
1819
+ function useKeybindings(handlers, isActive) {
1820
+ useInput3(
1821
+ (input, key) => {
1822
+ if (key.upArrow) {
1823
+ handlers.onMoveUp();
1824
+ return;
1825
+ }
1826
+ if (key.downArrow) {
1827
+ handlers.onMoveDown();
1828
+ return;
1829
+ }
1830
+ if (key.leftArrow) {
1831
+ handlers.onLeft();
1832
+ return;
1833
+ }
1834
+ if (key.rightArrow) {
1835
+ handlers.onRight();
1836
+ return;
1837
+ }
1838
+ if (key.return) {
1839
+ handlers.onEnter();
1840
+ return;
1841
+ }
1842
+ if (key.tab) {
1843
+ handlers.onTab();
1844
+ return;
1845
+ }
1846
+ if (input === " ") {
1847
+ handlers.onSpace();
1848
+ return;
1849
+ }
1850
+ if (input === "m") {
1851
+ handlers.onMessage();
1852
+ return;
1853
+ }
1854
+ if (input === "k") {
1855
+ handlers.onKill();
1856
+ return;
1857
+ }
1858
+ if (input === "w") {
1859
+ handlers.onGoToWindow();
1860
+ return;
1861
+ }
1862
+ if (input === "g") {
1863
+ handlers.onEditGoal();
1864
+ return;
1865
+ }
1866
+ if (input === "n") {
1867
+ handlers.onNewSession();
1868
+ return;
1869
+ }
1870
+ if (input === "c") {
1871
+ handlers.onClaude();
1872
+ return;
1873
+ }
1874
+ if (input === "p") {
1875
+ handlers.onOpenPlan();
1876
+ return;
1877
+ }
1878
+ if (input === "q") {
1879
+ handlers.onQuit();
1880
+ return;
1881
+ }
1882
+ if (input === "r") {
1883
+ handlers.onReRun();
1884
+ return;
1885
+ }
1886
+ if (input === "j") {
1887
+ handlers.onJumpToPane();
1888
+ return;
1889
+ }
1890
+ if (input === "R") {
1891
+ handlers.onResume();
1892
+ return;
1893
+ }
1894
+ if (input === "C") {
1895
+ handlers.onContinue();
1896
+ return;
1897
+ }
1898
+ if (input === "x") {
1899
+ handlers.onRestartAgent();
1900
+ return;
1901
+ }
1902
+ if (input === "b") {
1903
+ handlers.onRollback();
1904
+ return;
1905
+ }
1906
+ if (input === "l") {
1907
+ handlers.onToggleLogs();
1908
+ return;
1909
+ }
1910
+ },
1911
+ { isActive }
1912
+ );
1913
+ }
1914
+
1915
+ // src/tui/hooks/useLeaderKey.ts
1916
+ import { useInput as useInput4 } from "ink";
1917
+ function useLeaderKey(mode, onAction) {
1918
+ useInput4(
1919
+ (input, key) => {
1920
+ if (key.escape) {
1921
+ onAction({ type: "dismiss" });
1922
+ return;
1923
+ }
1924
+ if (input === "y") {
1925
+ onAction({ type: "enter-copy-menu" });
1926
+ return;
1927
+ }
1928
+ if (input === "d") {
1929
+ onAction({ type: "delete-session" });
1930
+ return;
1931
+ }
1932
+ if (input === "l") {
1933
+ onAction({ type: "open-logs" });
1934
+ return;
1935
+ }
1936
+ if (input === "o") {
1937
+ onAction({ type: "open-session-dir" });
1938
+ return;
1939
+ }
1940
+ if (input === "/") {
1941
+ onAction({ type: "search" });
1942
+ return;
1943
+ }
1944
+ if (input === "a") {
1945
+ onAction({ type: "spawn-agent" });
1946
+ return;
1947
+ }
1948
+ if (input === "m") {
1949
+ onAction({ type: "message-agent" });
1950
+ return;
1951
+ }
1952
+ if (input === "?") {
1953
+ onAction({ type: "help" });
1954
+ return;
1955
+ }
1956
+ if (input === "!") {
1957
+ onAction({ type: "shell-command" });
1958
+ return;
1959
+ }
1960
+ const digit = parseInt(input, 10);
1961
+ if (!isNaN(digit) && digit >= 1 && digit <= 9) {
1962
+ onAction({ type: "jump-to-session", index: digit });
1963
+ return;
1964
+ }
1965
+ onAction({ type: "dismiss" });
1966
+ },
1967
+ { isActive: mode === "leader" }
1968
+ );
1969
+ useInput4(
1970
+ (input, key) => {
1971
+ if (key.escape) {
1972
+ onAction({ type: "dismiss" });
1973
+ return;
1974
+ }
1975
+ if (input === "p") {
1976
+ onAction({ type: "copy-path" });
1977
+ return;
1978
+ }
1979
+ if (input === "C") {
1980
+ onAction({ type: "copy-context" });
1981
+ return;
1982
+ }
1983
+ if (input === "l") {
1984
+ onAction({ type: "copy-logs" });
1985
+ return;
1986
+ }
1987
+ if (input === "s") {
1988
+ onAction({ type: "copy-session-id" });
1989
+ return;
1990
+ }
1991
+ onAction({ type: "dismiss" });
1992
+ },
1993
+ { isActive: mode === "copy-menu" }
1994
+ );
1995
+ useInput4(
1996
+ (input, key) => {
1997
+ if (key.escape || input === "?") {
1998
+ onAction({ type: "dismiss" });
1999
+ return;
2000
+ }
2001
+ },
2002
+ { isActive: mode === "help" }
2003
+ );
2004
+ }
2005
+
2006
+ // src/tui/lib/reports.ts
2007
+ import { readFileSync as readFileSync3 } from "fs";
2008
+ function loadReportContent(report) {
2009
+ try {
2010
+ return readFileSync3(report.filePath, "utf-8");
2011
+ } catch {
2012
+ return report.summary;
2013
+ }
2014
+ }
2015
+ function resolveReports(reports) {
2016
+ return [...reports].reverse().map((r) => ({
2017
+ type: r.type,
2018
+ timestamp: r.timestamp,
2019
+ content: loadReportContent(r),
2020
+ summary: r.summary
2021
+ }));
2022
+ }
2023
+
2024
+ // src/tui/lib/clipboard.ts
2025
+ import { execSync as execSync2 } from "child_process";
2026
+ function copyToClipboard(text) {
2027
+ execSync2("pbcopy", { input: text });
2028
+ }
2029
+
2030
+ // src/tui/lib/context.ts
2031
+ import { readFileSync as readFileSync4 } from "fs";
2032
+ function readFileSafe(filePath) {
2033
+ try {
2034
+ return readFileSync4(filePath, "utf-8");
2035
+ } catch {
2036
+ return null;
2037
+ }
2038
+ }
2039
+ function escapeXml(s) {
2040
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
2041
+ }
2042
+ function buildSessionContext(session, cwd2) {
2043
+ const goal = readFileSafe(goalPath(cwd2, session.id));
2044
+ const roadmap = readFileSafe(roadmapPath(cwd2, session.id));
2045
+ const agentsXml = session.agents.map((agent) => {
2046
+ const reportBlocks = resolveReports(agent.reports);
2047
+ const reportsXml = [...reportBlocks].reverse().map((block) => {
2048
+ return ` <report type="${block.type}" time="${escapeXml(block.timestamp)}">${escapeXml(block.content)}</report>`;
2049
+ }).join("\n");
2050
+ return [
2051
+ ` <agent id="${escapeXml(agent.id)}" name="${escapeXml(agent.name)}" type="${escapeXml(agent.agentType)}" status="${escapeXml(agent.status)}">`,
2052
+ ` <instruction>${escapeXml(agent.instruction)}</instruction>`,
2053
+ ...reportsXml ? [reportsXml] : [],
2054
+ ` </agent>`
2055
+ ].join("\n");
2056
+ }).join("\n");
2057
+ const cyclesXml = session.orchestratorCycles.map((cycle) => {
2058
+ const agents = cycle.agentsSpawned.join(", ");
2059
+ const mode = cycle.mode ? ` mode="${escapeXml(cycle.mode)}"` : "";
2060
+ return ` <cycle number="${cycle.cycle}"${mode} agents="${escapeXml(agents)}" />`;
2061
+ }).join("\n");
2062
+ const lines = [
2063
+ "<context>",
2064
+ `<session id="${escapeXml(session.id)}" status="${escapeXml(session.status)}">`,
2065
+ ` <task>${escapeXml(session.task)}</task>`,
2066
+ ` <cwd>${escapeXml(session.cwd)}</cwd>`
2067
+ ];
2068
+ if (goal) lines.push(` <goal>${escapeXml(goal)}</goal>`);
2069
+ if (roadmap) lines.push(` <roadmap>${escapeXml(roadmap)}</roadmap>`);
2070
+ if (session.agents.length > 0) {
2071
+ lines.push(" <agents>");
2072
+ lines.push(agentsXml);
2073
+ lines.push(" </agents>");
2074
+ }
2075
+ if (session.orchestratorCycles.length > 0) {
2076
+ lines.push(" <cycles>");
2077
+ lines.push(cyclesXml);
2078
+ lines.push(" </cycles>");
2079
+ }
2080
+ if (session.completionReport) {
2081
+ lines.push(` <completion-report>${escapeXml(session.completionReport)}</completion-report>`);
2082
+ }
2083
+ lines.push("</session>");
2084
+ lines.push("</context>");
2085
+ return lines.join("\n");
2086
+ }
2087
+
2088
+ // src/tui/lib/tree.ts
2089
+ function sessionSortKey(s) {
2090
+ if (s.status === "completed") return 4;
2091
+ const open = s.tmuxWindowId ? windowExists(s.tmuxWindowId) : false;
2092
+ if (s.status === "active") return open ? 0 : 1;
2093
+ return open ? 2 : 3;
2094
+ }
2095
+ function buildTree(sessions, selectedSession, expanded) {
2096
+ const nodes = [];
2097
+ const sorted = [...sessions].sort((a, b) => {
2098
+ const keyDiff = sessionSortKey(a) - sessionSortKey(b);
2099
+ if (keyDiff !== 0) return keyDiff;
2100
+ return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
2101
+ });
2102
+ for (const s of sorted) {
2103
+ const sessionNodeId = `session:${s.id}`;
2104
+ const isSelected = selectedSession?.id === s.id;
2105
+ const isExpanded = expanded.has(sessionNodeId);
2106
+ nodes.push({
2107
+ id: sessionNodeId,
2108
+ type: "session",
2109
+ depth: 0,
2110
+ expandable: true,
2111
+ expanded: isExpanded && isSelected,
2112
+ sessionId: s.id,
2113
+ task: s.task,
2114
+ status: s.status,
2115
+ cycleCount: isSelected ? selectedSession?.orchestratorCycles.length ?? 0 : 0,
2116
+ agentCount: s.agentCount,
2117
+ createdAt: s.createdAt,
2118
+ completedAt: isSelected ? selectedSession?.completedAt : void 0
2119
+ });
2120
+ if (!isExpanded || !isSelected || !selectedSession) continue;
2121
+ const cycles = [...selectedSession.orchestratorCycles].reverse();
2122
+ const allSpawnedIds = new Set(
2123
+ selectedSession.orchestratorCycles.flatMap((c) => c.agentsSpawned)
2124
+ );
2125
+ for (const cycle of cycles) {
2126
+ const cycleNodeId = `cycle:${s.id}:${cycle.cycle}`;
2127
+ const cycleExpanded = expanded.has(cycleNodeId);
2128
+ const cycleAgents = selectedSession.agents.filter(
2129
+ (a) => cycle.agentsSpawned.includes(a.id)
2130
+ );
2131
+ const isLatest = cycle === cycles[0];
2132
+ const unassigned = isLatest ? selectedSession.agents.filter((a) => !allSpawnedIds.has(a.id)) : [];
2133
+ const allCycleAgents = [...cycleAgents, ...unassigned];
2134
+ nodes.push({
2135
+ id: cycleNodeId,
2136
+ type: "cycle",
2137
+ depth: 1,
2138
+ expandable: allCycleAgents.length > 0,
2139
+ expanded: cycleExpanded,
2140
+ sessionId: s.id,
2141
+ cycleNumber: cycle.cycle,
2142
+ timestamp: cycle.timestamp,
2143
+ completedAt: cycle.completedAt,
2144
+ agentCount: allCycleAgents.length,
2145
+ mode: cycle.mode
2146
+ });
2147
+ if (!cycleExpanded) continue;
2148
+ for (const agent of allCycleAgents) {
2149
+ const agentNodeId = `agent:${s.id}:${agent.id}`;
2150
+ const hasReports = agent.reports.length > 0;
2151
+ const agentExpanded = expanded.has(agentNodeId);
2152
+ nodes.push({
2153
+ id: agentNodeId,
2154
+ type: "agent",
2155
+ depth: 2,
2156
+ expandable: hasReports,
2157
+ expanded: agentExpanded && hasReports,
2158
+ sessionId: s.id,
2159
+ agentId: agent.id,
2160
+ name: agent.name,
2161
+ agentType: agent.agentType,
2162
+ status: agent.status,
2163
+ spawnedAt: agent.spawnedAt,
2164
+ completedAt: agent.completedAt,
2165
+ reportCount: agent.reports.length
2166
+ });
2167
+ if (!agentExpanded || !hasReports) continue;
2168
+ for (let ri = 0; ri < agent.reports.length; ri++) {
2169
+ const report = agent.reports[ri];
2170
+ nodes.push({
2171
+ id: `report:${s.id}:${agent.id}:${ri}`,
2172
+ type: "report",
2173
+ depth: 3,
2174
+ expandable: false,
2175
+ expanded: false,
2176
+ sessionId: s.id,
2177
+ reportIndex: ri,
2178
+ reportType: report.type,
2179
+ timestamp: report.timestamp,
2180
+ agentId: agent.id
2181
+ });
2182
+ }
2183
+ }
2184
+ }
2185
+ const messages = selectedSession.messages ?? [];
2186
+ if (messages.length > 0) {
2187
+ const msgsNodeId = `messages:${s.id}`;
2188
+ const msgsExpanded = expanded.has(msgsNodeId);
2189
+ nodes.push({
2190
+ id: msgsNodeId,
2191
+ type: "messages",
2192
+ depth: 1,
2193
+ expandable: true,
2194
+ expanded: msgsExpanded,
2195
+ sessionId: s.id,
2196
+ count: messages.length
2197
+ });
2198
+ if (msgsExpanded) {
2199
+ for (const msg of messages) {
2200
+ const sourceLabel2 = msg.source.type === "user" ? "You" : msg.source.type === "agent" ? msg.source.agentId : "system";
2201
+ nodes.push({
2202
+ id: `message:${s.id}:${msg.id}`,
2203
+ type: "message",
2204
+ depth: 2,
2205
+ expandable: false,
2206
+ expanded: false,
2207
+ sessionId: s.id,
2208
+ messageId: msg.id,
2209
+ source: sourceLabel2,
2210
+ summary: msg.summary || msg.content,
2211
+ timestamp: msg.timestamp
2212
+ });
2213
+ }
2214
+ }
2215
+ }
2216
+ }
2217
+ return nodes;
2218
+ }
2219
+ function findParentIndex(nodes, index) {
2220
+ const node = nodes[index];
2221
+ if (!node || node.depth === 0) return index;
2222
+ const targetDepth = node.depth - 1;
2223
+ for (let i = index - 1; i >= 0; i--) {
2224
+ if (nodes[i].depth === targetDepth) return i;
2225
+ if (nodes[i].depth < targetDepth) return i;
2226
+ }
2227
+ return 0;
2228
+ }
2229
+
2230
+ // src/tui/App.tsx
2231
+ import { Fragment as Fragment3, jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
2232
+ function resolveEditor(configEditor) {
2233
+ if (configEditor) return configEditor;
2234
+ if (process.env.EDITOR) return process.env.EDITOR;
2235
+ return "nvim";
2236
+ }
2237
+ function App({ cwd: cwd2 }) {
2238
+ const { exit } = useApp();
2239
+ const { stdout } = useStdout();
2240
+ const rows = stdout.rows || 24;
2241
+ const cols = stdout.columns || 80;
2242
+ const config = loadConfig(cwd2);
2243
+ const [cursorIndex, setCursorIndex] = useState4(0);
2244
+ const [expanded, setExpanded] = useState4(/* @__PURE__ */ new Set());
2245
+ const [mode, setMode] = useState4("navigate");
2246
+ const [notification, setNotification] = useState4(null);
2247
+ const [selectedSessionId, setSelectedSessionId] = useState4(null);
2248
+ const [detailScrollOffset, setDetailScrollOffset] = useState4(0);
2249
+ const [logsScrollOffset, setLogsScrollOffset] = useState4(0);
2250
+ const [showLogs, setShowLogs] = useState4(true);
2251
+ const [focusPane, setFocusPane] = useState4("tree");
2252
+ const [searchFilter, setSearchFilter] = useState4(null);
2253
+ const [targetAgentId, setTargetAgentId] = useState4(null);
2254
+ const cursorNodeIdRef = useRef3(null);
2255
+ const prevNodesRef = useRef3([]);
2256
+ const { sessions, selectedSession, planContent, goalContent, logsContent, logsCycles, paneAlive, error } = usePolling(cwd2, selectedSessionId);
2257
+ const filteredSessions = useMemo4(() => {
2258
+ if (!searchFilter) return sessions;
2259
+ const q = searchFilter.toLowerCase();
2260
+ return sessions.filter(
2261
+ (s) => s.task.toLowerCase().includes(q) || s.id.toLowerCase().includes(q)
2262
+ );
2263
+ }, [sessions, searchFilter]);
2264
+ const nodes = useMemo4(
2265
+ () => buildTree(filteredSessions, selectedSession, expanded),
2266
+ [filteredSessions, selectedSession, expanded]
2267
+ );
2268
+ if (nodes === prevNodesRef.current) {
2269
+ const node = nodes[cursorIndex];
2270
+ if (node) cursorNodeIdRef.current = node.id;
2271
+ } else if (cursorNodeIdRef.current === null && nodes.length > 0) {
2272
+ cursorNodeIdRef.current = nodes[0]?.id ?? null;
2273
+ }
2274
+ prevNodesRef.current = nodes;
2275
+ useEffect3(() => {
2276
+ const node = nodes[cursorIndex];
2277
+ const id = node?.sessionId ?? null;
2278
+ if (id !== selectedSessionId) {
2279
+ setSelectedSessionId(id);
2280
+ }
2281
+ }, [nodes, cursorIndex, selectedSessionId]);
2282
+ useEffect3(() => {
2283
+ setDetailScrollOffset(0);
2284
+ setLogsScrollOffset(0);
2285
+ }, [cursorIndex]);
2286
+ useEffect3(() => {
2287
+ if (nodes.length === 0) {
2288
+ setCursorIndex(0);
2289
+ return;
2290
+ }
2291
+ const targetId = cursorNodeIdRef.current;
2292
+ if (!targetId) {
2293
+ cursorNodeIdRef.current = nodes[0]?.id ?? null;
2294
+ return;
2295
+ }
2296
+ if (nodes[cursorIndex]?.id === targetId) return;
2297
+ const newIndex = nodes.findIndex((n) => n.id === targetId);
2298
+ if (newIndex !== -1) {
2299
+ setCursorIndex(newIndex);
2300
+ } else {
2301
+ const clamped = Math.min(cursorIndex, nodes.length - 1);
2302
+ setCursorIndex(clamped);
2303
+ cursorNodeIdRef.current = nodes[clamped]?.id ?? null;
2304
+ }
2305
+ }, [nodes]);
2306
+ const prevCycleCountRef = useRef3(0);
2307
+ useEffect3(() => {
2308
+ if (!selectedSession) return;
2309
+ const sessionNodeId = `session:${selectedSession.id}`;
2310
+ const cycles = selectedSession.orchestratorCycles;
2311
+ setExpanded((prev) => {
2312
+ if (!prev.has(sessionNodeId)) {
2313
+ prevCycleCountRef.current = cycles.length;
2314
+ return prev;
2315
+ }
2316
+ if (cycles.length === 0) {
2317
+ prevCycleCountRef.current = 0;
2318
+ return prev;
2319
+ }
2320
+ const latest = cycles[cycles.length - 1];
2321
+ const latestId = `cycle:${selectedSession.id}:${latest.cycle}`;
2322
+ let next = prev;
2323
+ if (cycles.length > prevCycleCountRef.current && prevCycleCountRef.current > 0) {
2324
+ const prevCycle = cycles[cycles.length - 2];
2325
+ if (prevCycle) {
2326
+ const prevId = `cycle:${selectedSession.id}:${prevCycle.cycle}`;
2327
+ next = new Set(prev);
2328
+ next.delete(prevId);
2329
+ next.add(latestId);
2330
+ }
2331
+ } else if (!prev.has(latestId)) {
2332
+ next = new Set(prev);
2333
+ next.add(latestId);
2334
+ }
2335
+ prevCycleCountRef.current = cycles.length;
2336
+ return next;
2337
+ });
2338
+ }, [selectedSession?.orchestratorCycles.length, selectedSession?.id]);
2339
+ useEffect3(() => {
2340
+ if (!notification) return;
2341
+ const t = setTimeout(() => setNotification(null), 3e3);
2342
+ return () => clearTimeout(t);
2343
+ }, [notification]);
2344
+ const notify = useCallback2((msg) => setNotification(msg), []);
2345
+ const session = selectedSession;
2346
+ const agents = session?.agents ?? [];
2347
+ const cursorNode = nodes[cursorIndex];
2348
+ const getAgentForNode = useCallback2(
2349
+ (node) => {
2350
+ if (!node || !session) return null;
2351
+ if (node.type === "agent") return agents.find((a) => a.id === node.agentId) ?? null;
2352
+ if (node.type === "report") return agents.find((a) => a.id === node.agentId) ?? null;
2353
+ return null;
2354
+ },
2355
+ [session, agents]
2356
+ );
2357
+ const sendAndNotify = useCallback2(
2358
+ async (request, successMsg) => {
2359
+ try {
2360
+ const res = await send(request);
2361
+ notify(res.ok ? successMsg : `Error: ${res.error}`);
2362
+ } catch (err) {
2363
+ notify(`Error: ${err.message}`);
2364
+ }
2365
+ },
2366
+ [notify]
2367
+ );
2368
+ const toggleExpand = useCallback2(
2369
+ (nodeId) => {
2370
+ setExpanded((prev) => {
2371
+ const next = new Set(prev);
2372
+ if (next.has(nodeId)) next.delete(nodeId);
2373
+ else next.add(nodeId);
2374
+ return next;
2375
+ });
2376
+ },
2377
+ []
2378
+ );
2379
+ const expandNode = useCallback2(
2380
+ (nodeId) => {
2381
+ setExpanded((prev) => {
2382
+ if (prev.has(nodeId)) return prev;
2383
+ const next = new Set(prev);
2384
+ next.add(nodeId);
2385
+ return next;
2386
+ });
2387
+ },
2388
+ []
2389
+ );
2390
+ const collapseNode = useCallback2(
2391
+ (nodeId) => {
2392
+ setExpanded((prev) => {
2393
+ if (!prev.has(nodeId)) return prev;
2394
+ const next = new Set(prev);
2395
+ next.delete(nodeId);
2396
+ return next;
2397
+ });
2398
+ },
2399
+ []
2400
+ );
2401
+ const treeWidth = 36;
2402
+ const remainingWidth = cols - treeWidth;
2403
+ const detailWidth = showLogs ? Math.floor(remainingWidth * 0.6) : remainingWidth;
2404
+ const logsWidth = showLogs ? remainingWidth - detailWidth : 0;
2405
+ const contentHeight = rows - 3;
2406
+ useKeybindings(
2407
+ {
2408
+ onMoveUp: () => {
2409
+ if (focusPane === "detail") {
2410
+ setDetailScrollOffset((o) => Math.max(0, o - 1));
2411
+ } else if (focusPane === "logs") {
2412
+ setLogsScrollOffset((o) => Math.max(0, o - 1));
2413
+ } else {
2414
+ setCursorIndex((i) => Math.max(0, i - 1));
2415
+ }
2416
+ },
2417
+ onMoveDown: () => {
2418
+ if (focusPane === "detail") {
2419
+ setDetailScrollOffset((o) => o + 1);
2420
+ } else if (focusPane === "logs") {
2421
+ setLogsScrollOffset((o) => o + 1);
2422
+ } else {
2423
+ setCursorIndex((i) => Math.min(nodes.length - 1, i + 1));
2424
+ }
2425
+ },
2426
+ onLeft: () => {
2427
+ if (focusPane === "logs") {
2428
+ setFocusPane("detail");
2429
+ return;
2430
+ }
2431
+ if (focusPane === "detail") {
2432
+ setFocusPane("tree");
2433
+ return;
2434
+ }
2435
+ const node = nodes[cursorIndex];
2436
+ if (!node) return;
2437
+ if (node.expanded) {
2438
+ collapseNode(node.id);
2439
+ } else {
2440
+ const parentIdx = findParentIndex(nodes, cursorIndex);
2441
+ if (parentIdx !== cursorIndex) setCursorIndex(parentIdx);
2442
+ }
2443
+ },
2444
+ onRight: () => {
2445
+ const node = nodes[cursorIndex];
2446
+ if (!node) return;
2447
+ if (node.expandable && !node.expanded) {
2448
+ expandNode(node.id);
2449
+ if (node.type === "session" && selectedSession?.id === node.sessionId) {
2450
+ const cycles = selectedSession.orchestratorCycles;
2451
+ if (cycles.length > 0) {
2452
+ const latest = cycles[cycles.length - 1];
2453
+ expandNode(`cycle:${node.sessionId}:${latest.cycle}`);
2454
+ }
2455
+ }
2456
+ } else if (node.expandable && node.expanded) {
2457
+ if (cursorIndex + 1 < nodes.length && nodes[cursorIndex + 1].depth > node.depth) {
2458
+ setCursorIndex(cursorIndex + 1);
2459
+ }
2460
+ }
2461
+ },
2462
+ onTab: () => {
2463
+ setFocusPane((f) => {
2464
+ if (f === "tree") return "detail";
2465
+ if (f === "detail") return showLogs ? "logs" : "tree";
2466
+ return "tree";
2467
+ });
2468
+ },
2469
+ onToggleLogs: () => {
2470
+ setShowLogs((prev) => {
2471
+ if (prev) {
2472
+ setFocusPane((f) => f === "logs" ? "detail" : f);
2473
+ setLogsScrollOffset(0);
2474
+ }
2475
+ return !prev;
2476
+ });
2477
+ },
2478
+ onSpace: () => {
2479
+ setMode("leader");
2480
+ },
2481
+ onEnter: () => {
2482
+ const node = nodes[cursorIndex];
2483
+ if (!node) return;
2484
+ if (node.expandable && !node.expanded) {
2485
+ expandNode(node.id);
2486
+ if (node.type === "session" && selectedSession?.id === node.sessionId) {
2487
+ const cycles = selectedSession.orchestratorCycles;
2488
+ if (cycles.length > 0) {
2489
+ const latest = cycles[cycles.length - 1];
2490
+ expandNode(`cycle:${node.sessionId}:${latest.cycle}`);
2491
+ }
2492
+ }
2493
+ } else if (node.type === "report") {
2494
+ setMode("report-detail");
2495
+ }
2496
+ },
2497
+ onMessage: () => {
2498
+ if (!selectedSessionId) {
2499
+ notify("No session selected");
2500
+ return;
2501
+ }
2502
+ const editor = resolveEditor(config.editor);
2503
+ try {
2504
+ const content = editInPopup(cwd2, editor);
2505
+ if (content) {
2506
+ void sendAndNotify(
2507
+ { type: "message", sessionId: selectedSessionId, content },
2508
+ "Message queued"
2509
+ );
2510
+ }
2511
+ } catch {
2512
+ notify("Failed to open editor");
2513
+ }
2514
+ },
2515
+ onKill: () => {
2516
+ if (!selectedSessionId) {
2517
+ notify("No session selected");
2518
+ return;
2519
+ }
2520
+ const node = nodes[cursorIndex];
2521
+ if (node && (node.type === "agent" || node.type === "report")) {
2522
+ const agentId = node.agentId;
2523
+ const agent = agents.find((a) => a.id === agentId);
2524
+ if (agent?.status !== "running") {
2525
+ notify(`Agent ${agentId} is not running`);
2526
+ return;
2527
+ }
2528
+ sendAndNotify({ type: "kill-agent", sessionId: selectedSessionId, agentId }, `Killed ${agentId}`);
2529
+ } else {
2530
+ sendAndNotify({ type: "kill", sessionId: selectedSessionId }, "Session killed");
2531
+ }
2532
+ },
2533
+ onGoToWindow: () => {
2534
+ if (!session?.tmuxWindowId) {
2535
+ notify("No tmux window");
2536
+ return;
2537
+ }
2538
+ if (session.tmuxSessionName) switchToSession(session.tmuxSessionName);
2539
+ selectWindow(session.tmuxWindowId);
2540
+ },
2541
+ onEditGoal: () => {
2542
+ if (!selectedSessionId) {
2543
+ notify("No session selected");
2544
+ return;
2545
+ }
2546
+ const gp = goalPath(cwd2, selectedSessionId);
2547
+ const editor = resolveEditor(config.editor);
2548
+ try {
2549
+ openEditorPopup(cwd2, editor, gp, { w: "80", h: "50%" });
2550
+ } catch {
2551
+ notify(`Failed to open goal in ${editor}`);
2552
+ }
2553
+ },
2554
+ onNewSession: () => {
2555
+ const editor = resolveEditor(config.editor);
2556
+ try {
2557
+ const content = editInPopup(cwd2, editor);
2558
+ if (content) {
2559
+ void sendAndNotify(
2560
+ { type: "start", task: content, cwd: cwd2 },
2561
+ "Session created"
2562
+ );
2563
+ }
2564
+ } catch {
2565
+ notify("Failed to open editor");
2566
+ }
2567
+ },
2568
+ onClaude: () => {
2569
+ try {
2570
+ openCompanionPopup(cwd2);
2571
+ } catch {
2572
+ notify("Failed to open companion popup");
2573
+ }
2574
+ },
2575
+ onOpenPlan: () => {
2576
+ if (!selectedSessionId) {
2577
+ notify("No session selected");
2578
+ return;
2579
+ }
2580
+ const pp = roadmapPath(cwd2, selectedSessionId);
2581
+ const editor = resolveEditor(config.editor);
2582
+ try {
2583
+ openEditorPopup(cwd2, editor, pp);
2584
+ } catch {
2585
+ notify(`Failed to open roadmap in ${editor}`);
2586
+ }
2587
+ },
2588
+ onQuit: () => exit(),
2589
+ onReRun: () => {
2590
+ const agent = getAgentForNode(cursorNode);
2591
+ if (!agent || !selectedSessionId) {
2592
+ notify("Select an agent to re-run");
2593
+ return;
2594
+ }
2595
+ sendAndNotify({
2596
+ type: "spawn",
2597
+ sessionId: selectedSessionId,
2598
+ agentType: agent.agentType,
2599
+ name: `${agent.name}-retry`,
2600
+ instruction: agent.instruction
2601
+ }, `Re-spawned ${agent.name}`);
2602
+ },
2603
+ onJumpToPane: () => {
2604
+ const agent = getAgentForNode(cursorNode);
2605
+ if (!agent?.paneId) {
2606
+ notify("Select an agent with an active pane");
2607
+ return;
2608
+ }
2609
+ if (session?.tmuxSessionName) switchToSession(session.tmuxSessionName);
2610
+ if (session?.tmuxWindowId) selectWindow(session.tmuxWindowId);
2611
+ selectPane(agent.paneId);
2612
+ },
2613
+ onResume: () => {
2614
+ if (!selectedSessionId) {
2615
+ notify("No session selected");
2616
+ return;
2617
+ }
2618
+ if (session?.status === "active" && paneAlive) {
2619
+ notify("Session already active");
2620
+ return;
2621
+ }
2622
+ setMode("resume");
2623
+ },
2624
+ onContinue: () => {
2625
+ if (!selectedSessionId) {
2626
+ notify("No session selected");
2627
+ return;
2628
+ }
2629
+ if (session?.status !== "completed") {
2630
+ notify("Session not completed");
2631
+ return;
2632
+ }
2633
+ setMode("continue");
2634
+ },
2635
+ onRestartAgent: () => {
2636
+ const agent = getAgentForNode(cursorNode);
2637
+ if (!agent || !selectedSessionId) {
2638
+ notify("Select an agent to restart");
2639
+ return;
2640
+ }
2641
+ sendAndNotify(
2642
+ { type: "restart-agent", sessionId: selectedSessionId, agentId: agent.id },
2643
+ `Restarted ${agent.id}`
2644
+ );
2645
+ },
2646
+ onRollback: () => {
2647
+ if (!selectedSessionId) {
2648
+ notify("No session selected");
2649
+ return;
2650
+ }
2651
+ setMode("rollback");
2652
+ }
2653
+ },
2654
+ mode === "navigate"
2655
+ );
2656
+ useLeaderKey(mode, (action) => {
2657
+ switch (action.type) {
2658
+ case "enter-copy-menu":
2659
+ setMode("copy-menu");
2660
+ break;
2661
+ case "copy-path": {
2662
+ if (!selectedSessionId) {
2663
+ notify("No session selected");
2664
+ setMode("navigate");
2665
+ break;
2666
+ }
2667
+ const path = sessionDir(cwd2, selectedSessionId);
2668
+ try {
2669
+ copyToClipboard(path);
2670
+ notify(`Copied path (${path})`);
2671
+ } catch {
2672
+ notify("Failed to copy to clipboard");
2673
+ }
2674
+ setMode("navigate");
2675
+ break;
2676
+ }
2677
+ case "copy-context": {
2678
+ if (!selectedSessionId || !session) {
2679
+ notify("No session selected");
2680
+ setMode("navigate");
2681
+ break;
2682
+ }
2683
+ try {
2684
+ const xml = buildSessionContext(session, cwd2);
2685
+ copyToClipboard(xml);
2686
+ notify(`Copied context (${xml.length} chars)`);
2687
+ } catch {
2688
+ notify("Failed to copy context");
2689
+ }
2690
+ setMode("navigate");
2691
+ break;
2692
+ }
2693
+ case "copy-logs": {
2694
+ if (!logsContent) {
2695
+ notify("No logs content");
2696
+ setMode("navigate");
2697
+ break;
2698
+ }
2699
+ try {
2700
+ copyToClipboard(logsContent);
2701
+ notify(`Copied logs (${logsContent.length} chars)`);
2702
+ } catch {
2703
+ notify("Failed to copy to clipboard");
2704
+ }
2705
+ setMode("navigate");
2706
+ break;
2707
+ }
2708
+ case "copy-session-id": {
2709
+ if (!selectedSessionId) {
2710
+ notify("No session selected");
2711
+ setMode("navigate");
2712
+ break;
2713
+ }
2714
+ try {
2715
+ copyToClipboard(selectedSessionId);
2716
+ notify(`Copied session ID (${selectedSessionId})`);
2717
+ } catch {
2718
+ notify("Failed to copy to clipboard");
2719
+ }
2720
+ setMode("navigate");
2721
+ break;
2722
+ }
2723
+ case "delete-session": {
2724
+ if (!selectedSessionId) {
2725
+ notify("No session selected");
2726
+ setMode("navigate");
2727
+ break;
2728
+ }
2729
+ setMode("delete-confirm");
2730
+ break;
2731
+ }
2732
+ case "open-logs": {
2733
+ try {
2734
+ openLogPopup();
2735
+ } catch {
2736
+ notify("Failed to open log popup");
2737
+ }
2738
+ setMode("navigate");
2739
+ break;
2740
+ }
2741
+ case "open-session-dir": {
2742
+ if (!selectedSessionId) {
2743
+ notify("No session selected");
2744
+ setMode("navigate");
2745
+ break;
2746
+ }
2747
+ try {
2748
+ openInFileManager(sessionDir(cwd2, selectedSessionId));
2749
+ } catch {
2750
+ notify("Failed to open session directory");
2751
+ }
2752
+ setMode("navigate");
2753
+ break;
2754
+ }
2755
+ case "search":
2756
+ setMode("search");
2757
+ break;
2758
+ case "jump-to-session": {
2759
+ let count = 0;
2760
+ for (let i = 0; i < nodes.length; i++) {
2761
+ if (nodes[i]?.type === "session") {
2762
+ count++;
2763
+ if (count === action.index) {
2764
+ setCursorIndex(i);
2765
+ break;
2766
+ }
2767
+ }
2768
+ }
2769
+ setMode("navigate");
2770
+ break;
2771
+ }
2772
+ case "spawn-agent": {
2773
+ if (!selectedSessionId) {
2774
+ notify("No session selected");
2775
+ setMode("navigate");
2776
+ break;
2777
+ }
2778
+ setMode("spawn-agent");
2779
+ break;
2780
+ }
2781
+ case "message-agent": {
2782
+ const agent = getAgentForNode(cursorNode);
2783
+ if (!agent) {
2784
+ notify("Cursor must be on an agent");
2785
+ setMode("navigate");
2786
+ break;
2787
+ }
2788
+ setTargetAgentId(agent.id);
2789
+ setMode("message-agent");
2790
+ break;
2791
+ }
2792
+ case "help":
2793
+ setMode("help");
2794
+ break;
2795
+ case "shell-command": {
2796
+ if (!selectedSessionId) {
2797
+ notify("No session selected");
2798
+ setMode("navigate");
2799
+ break;
2800
+ }
2801
+ setMode("shell-command");
2802
+ break;
2803
+ }
2804
+ case "dismiss":
2805
+ setMode("navigate");
2806
+ break;
2807
+ }
2808
+ });
2809
+ const handleSubmit = useCallback2(
2810
+ async (text) => {
2811
+ switch (mode) {
2812
+ case "resume": {
2813
+ if (!selectedSessionId) break;
2814
+ await sendAndNotify(
2815
+ { type: "resume", sessionId: selectedSessionId, cwd: cwd2, message: text || void 0 },
2816
+ "Session resumed"
2817
+ );
2818
+ break;
2819
+ }
2820
+ case "continue": {
2821
+ if (!selectedSessionId) break;
2822
+ try {
2823
+ const contRes = await send({ type: "continue", sessionId: selectedSessionId });
2824
+ if (!contRes.ok) {
2825
+ notify(`Error: ${contRes.error}`);
2826
+ break;
2827
+ }
2828
+ await sendAndNotify(
2829
+ { type: "resume", sessionId: selectedSessionId, cwd: cwd2, message: text || void 0 },
2830
+ "Session continued"
2831
+ );
2832
+ } catch (err) {
2833
+ notify(`Error: ${err.message}`);
2834
+ }
2835
+ break;
2836
+ }
2837
+ case "rollback": {
2838
+ if (!selectedSessionId) break;
2839
+ const toCycle = parseInt(text, 10);
2840
+ if (isNaN(toCycle) || toCycle < 1) {
2841
+ notify("Invalid cycle number");
2842
+ break;
2843
+ }
2844
+ await sendAndNotify(
2845
+ { type: "rollback", sessionId: selectedSessionId, cwd: cwd2, toCycle },
2846
+ `Rolled back to cycle ${toCycle} \u2014 use [R]esume to respawn`
2847
+ );
2848
+ break;
2849
+ }
2850
+ case "delete-confirm": {
2851
+ if (!selectedSessionId) break;
2852
+ if (text !== "yes") {
2853
+ notify('Delete cancelled (type "yes" to confirm)');
2854
+ break;
2855
+ }
2856
+ await sendAndNotify(
2857
+ { type: "delete", sessionId: selectedSessionId, cwd: cwd2 },
2858
+ "Session deleted"
2859
+ );
2860
+ break;
2861
+ }
2862
+ case "spawn-agent": {
2863
+ if (!selectedSessionId) break;
2864
+ if (!text.trim()) {
2865
+ notify("Instruction required");
2866
+ break;
2867
+ }
2868
+ await sendAndNotify(
2869
+ {
2870
+ type: "spawn",
2871
+ sessionId: selectedSessionId,
2872
+ agentType: "default",
2873
+ name: "agent",
2874
+ instruction: text
2875
+ },
2876
+ "Agent spawned"
2877
+ );
2878
+ break;
2879
+ }
2880
+ case "search": {
2881
+ setSearchFilter(text.trim() || null);
2882
+ break;
2883
+ }
2884
+ case "message-agent": {
2885
+ if (!selectedSessionId || !targetAgentId) break;
2886
+ await sendAndNotify(
2887
+ { type: "message", sessionId: selectedSessionId, content: text, source: { type: "agent", agentId: targetAgentId } },
2888
+ `Message sent to ${targetAgentId}`
2889
+ );
2890
+ setTargetAgentId(null);
2891
+ break;
2892
+ }
2893
+ case "shell-command": {
2894
+ if (!text.trim()) break;
2895
+ try {
2896
+ openShellPopup(cwd2, text);
2897
+ } catch {
2898
+ notify("Failed to run shell command");
2899
+ }
2900
+ break;
2901
+ }
2902
+ }
2903
+ setMode("navigate");
2904
+ },
2905
+ [mode, selectedSessionId, targetAgentId, cwd2, notify, sendAndNotify]
2906
+ );
2907
+ const handleCancel = useCallback2(() => {
2908
+ setMode("navigate");
2909
+ setFocusPane("tree");
2910
+ setTargetAgentId(null);
2911
+ }, []);
2912
+ const reportAgent = useMemo4(() => {
2913
+ if (mode === "report-detail") return getAgentForNode(cursorNode);
2914
+ return null;
2915
+ }, [mode, cursorNode, getAgentForNode]);
2916
+ const reportBlocks = useMemo4(
2917
+ () => reportAgent ? resolveReports(reportAgent.reports) : [],
2918
+ [reportAgent]
2919
+ );
2920
+ const detailAgent = useMemo4(() => {
2921
+ if (!cursorNode) return null;
2922
+ if (cursorNode.type === "agent" || cursorNode.type === "report") {
2923
+ return getAgentForNode(cursorNode);
2924
+ }
2925
+ return null;
2926
+ }, [cursorNode, getAgentForNode]);
2927
+ const detailReportBlocks = useMemo4(
2928
+ () => detailAgent ? resolveReports(detailAgent.reports) : [],
2929
+ [detailAgent]
2930
+ );
2931
+ const renderDetailPanel = useCallback2(
2932
+ (detailWidth2, contentHeight2) => {
2933
+ if (mode === "report-detail" && reportAgent) {
2934
+ return /* @__PURE__ */ jsx13(
2935
+ ReportView,
2936
+ {
2937
+ agent: reportAgent,
2938
+ reportBlocks,
2939
+ width: detailWidth2,
2940
+ height: contentHeight2,
2941
+ onClose: handleCancel
2942
+ }
2943
+ );
2944
+ }
2945
+ if (!cursorNode || !session) {
2946
+ return /* @__PURE__ */ jsx13(
2947
+ SessionDetail,
2948
+ {
2949
+ session: null,
2950
+ planContent: "",
2951
+ goalContent: "",
2952
+ logsContent: "",
2953
+ width: detailWidth2,
2954
+ height: contentHeight2,
2955
+ paneAlive: true
2956
+ }
2957
+ );
2958
+ }
2959
+ switch (cursorNode.type) {
2960
+ case "session":
2961
+ return /* @__PURE__ */ jsx13(
2962
+ SessionDetail,
2963
+ {
2964
+ session,
2965
+ planContent,
2966
+ goalContent,
2967
+ logsContent,
2968
+ width: detailWidth2,
2969
+ height: contentHeight2,
2970
+ paneAlive,
2971
+ scrollOffset: detailScrollOffset,
2972
+ focused: focusPane === "detail"
2973
+ }
2974
+ );
2975
+ case "cycle": {
2976
+ const cycle = session.orchestratorCycles.find(
2977
+ (c) => c.cycle === cursorNode.cycleNumber
2978
+ );
2979
+ if (!cycle) {
2980
+ return /* @__PURE__ */ jsx13(
2981
+ SessionDetail,
2982
+ {
2983
+ session,
2984
+ planContent,
2985
+ logsContent,
2986
+ width: detailWidth2,
2987
+ height: contentHeight2,
2988
+ paneAlive
2989
+ }
2990
+ );
2991
+ }
2992
+ return /* @__PURE__ */ jsx13(
2993
+ CycleDetail,
2994
+ {
2995
+ cycle,
2996
+ agents: session.agents,
2997
+ width: detailWidth2,
2998
+ height: contentHeight2
2999
+ }
3000
+ );
3001
+ }
3002
+ case "agent": {
3003
+ const agent = agents.find((a) => a.id === cursorNode.agentId);
3004
+ if (!agent) {
3005
+ return /* @__PURE__ */ jsx13(
3006
+ SessionDetail,
3007
+ {
3008
+ session,
3009
+ planContent,
3010
+ logsContent,
3011
+ width: detailWidth2,
3012
+ height: contentHeight2,
3013
+ paneAlive
3014
+ }
3015
+ );
3016
+ }
3017
+ return /* @__PURE__ */ jsx13(
3018
+ AgentDetail,
3019
+ {
3020
+ agent,
3021
+ reportBlocks: detailReportBlocks,
3022
+ width: detailWidth2,
3023
+ height: contentHeight2
3024
+ }
3025
+ );
3026
+ }
3027
+ case "report": {
3028
+ const agent = agents.find((a) => a.id === cursorNode.agentId);
3029
+ if (!agent) {
3030
+ return /* @__PURE__ */ jsx13(
3031
+ SessionDetail,
3032
+ {
3033
+ session,
3034
+ planContent,
3035
+ logsContent,
3036
+ width: detailWidth2,
3037
+ height: contentHeight2,
3038
+ paneAlive
3039
+ }
3040
+ );
3041
+ }
3042
+ const reportIdx = cursorNode.reportIndex;
3043
+ const specificBlock = detailReportBlocks.find((_b, i) => {
3044
+ const originalIdx = agent.reports.length - 1 - i;
3045
+ return originalIdx === reportIdx;
3046
+ });
3047
+ if (specificBlock) {
3048
+ const badge = specificBlock.type === "final" ? "FINAL" : "UPDATE";
3049
+ const badgeColor = specificBlock.type === "final" ? "cyan" : "yellow";
3050
+ const reportLines = wrapText(specificBlock.content.trim(), detailWidth2 - 8);
3051
+ const viewableLines = contentHeight2 - 7;
3052
+ return /* @__PURE__ */ jsxs12(
3053
+ Box13,
3054
+ {
3055
+ flexDirection: "column",
3056
+ width: detailWidth2,
3057
+ borderStyle: "round",
3058
+ borderColor: badgeColor,
3059
+ paddingX: 1,
3060
+ children: [
3061
+ /* @__PURE__ */ jsxs12(Text13, { bold: true, children: [
3062
+ " ",
3063
+ /* @__PURE__ */ jsx13(Text13, { color: badgeColor, children: badge }),
3064
+ " ",
3065
+ agent.id,
3066
+ " \xB7 ",
3067
+ agent.name !== agent.id ? agent.name : agent.agentType
3068
+ ] }),
3069
+ /* @__PURE__ */ jsxs12(Text13, { dimColor: true, children: [
3070
+ " ",
3071
+ formatTime(specificBlock.timestamp)
3072
+ ] }),
3073
+ /* @__PURE__ */ jsx13(Text13, { children: " " }),
3074
+ /* @__PURE__ */ jsxs12(Text13, { color: badgeColor, bold: true, children: [
3075
+ " ",
3076
+ "\u258E CONTENT"
3077
+ ] }),
3078
+ reportLines.slice(0, viewableLines).map((line, i) => /* @__PURE__ */ jsxs12(Text13, { children: [
3079
+ " ",
3080
+ line
3081
+ ] }, i)),
3082
+ reportLines.length > viewableLines && /* @__PURE__ */ jsxs12(Text13, { dimColor: true, children: [
3083
+ " ",
3084
+ "\u2026 ",
3085
+ reportLines.length - viewableLines,
3086
+ " more lines [enter] full view"
3087
+ ] })
3088
+ ]
3089
+ }
3090
+ );
3091
+ }
3092
+ return /* @__PURE__ */ jsx13(
3093
+ AgentDetail,
3094
+ {
3095
+ agent,
3096
+ reportBlocks: detailReportBlocks,
3097
+ width: detailWidth2,
3098
+ height: contentHeight2
3099
+ }
3100
+ );
3101
+ }
3102
+ case "messages":
3103
+ return /* @__PURE__ */ jsxs12(
3104
+ Box13,
3105
+ {
3106
+ flexDirection: "column",
3107
+ width: detailWidth2,
3108
+ borderStyle: "round",
3109
+ borderColor: "gray",
3110
+ paddingX: 1,
3111
+ children: [
3112
+ /* @__PURE__ */ jsxs12(Text13, { bold: true, children: [
3113
+ " Messages (",
3114
+ session.messages.length,
3115
+ ")"
3116
+ ] }),
3117
+ /* @__PURE__ */ jsx13(
3118
+ MessageLog,
3119
+ {
3120
+ messages: session.messages,
3121
+ maxMessages: contentHeight2 - 4,
3122
+ width: detailWidth2 - 4
3123
+ }
3124
+ )
3125
+ ]
3126
+ }
3127
+ );
3128
+ case "message": {
3129
+ const msg = session.messages.find((m) => m.id === cursorNode.messageId);
3130
+ return /* @__PURE__ */ jsxs12(
3131
+ Box13,
3132
+ {
3133
+ flexDirection: "column",
3134
+ width: detailWidth2,
3135
+ borderStyle: "round",
3136
+ borderColor: "gray",
3137
+ paddingX: 1,
3138
+ children: [
3139
+ /* @__PURE__ */ jsx13(Text13, { bold: true, children: " Message" }),
3140
+ msg ? /* @__PURE__ */ jsxs12(Fragment3, { children: [
3141
+ /* @__PURE__ */ jsxs12(Text13, { dimColor: true, children: [
3142
+ " ",
3143
+ cursorNode.source,
3144
+ " \xB7 ",
3145
+ cursorNode.timestamp
3146
+ ] }),
3147
+ /* @__PURE__ */ jsxs12(Text13, { children: [
3148
+ " ",
3149
+ msg.content
3150
+ ] })
3151
+ ] }) : /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: "Message not found" })
3152
+ ]
3153
+ }
3154
+ );
3155
+ }
3156
+ default:
3157
+ return /* @__PURE__ */ jsx13(
3158
+ SessionDetail,
3159
+ {
3160
+ session,
3161
+ planContent,
3162
+ goalContent,
3163
+ width: detailWidth2,
3164
+ height: contentHeight2,
3165
+ paneAlive
3166
+ }
3167
+ );
3168
+ }
3169
+ },
3170
+ [cursorNode, session, planContent, goalContent, logsContent, paneAlive, agents, mode, reportAgent, reportBlocks, detailReportBlocks, handleCancel, detailScrollOffset, focusPane]
3171
+ );
3172
+ return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", width: cols, height: rows, children: [
3173
+ /* @__PURE__ */ jsxs12(Box13, { flexDirection: "row", height: contentHeight, children: [
3174
+ /* @__PURE__ */ jsx13(
3175
+ SessionTree,
3176
+ {
3177
+ nodes,
3178
+ cursorIndex,
3179
+ width: treeWidth,
3180
+ height: contentHeight,
3181
+ focused: mode === "navigate" && focusPane === "tree"
3182
+ }
3183
+ ),
3184
+ renderDetailPanel(detailWidth, contentHeight),
3185
+ showLogs && /* @__PURE__ */ jsx13(
3186
+ LogsPanel,
3187
+ {
3188
+ cycleLogs: selectedSession ? logsCycles : [],
3189
+ width: logsWidth,
3190
+ height: contentHeight,
3191
+ scrollOffset: logsScrollOffset,
3192
+ focused: mode === "navigate" && focusPane === "logs"
3193
+ }
3194
+ )
3195
+ ] }),
3196
+ notification && /* @__PURE__ */ jsx13(Box13, { paddingX: 1, children: /* @__PURE__ */ jsx13(Text13, { color: "yellow", bold: true, children: /error|failed/i.test(notification) ? `\u2715 ${notification}` : /success|created|killed|sent|copied|deleted/i.test(notification) ? `\u2713 ${notification}` : `\u2139 ${notification}` }) }),
3197
+ error && !notification && /* @__PURE__ */ jsx13(Box13, { paddingX: 1, children: /* @__PURE__ */ jsxs12(Text13, { color: "red", children: [
3198
+ "\u26A0 ",
3199
+ error
3200
+ ] }) }),
3201
+ /* @__PURE__ */ jsx13(
3202
+ InputBar,
3203
+ {
3204
+ mode,
3205
+ defaultText: mode === "rollback" && cursorNode?.type === "cycle" ? String(cursorNode.cycleNumber) : void 0,
3206
+ onSubmit: handleSubmit,
3207
+ onCancel: handleCancel
3208
+ }
3209
+ ),
3210
+ /* @__PURE__ */ jsx13(
3211
+ StatusLine,
3212
+ {
3213
+ mode,
3214
+ detailFocused: focusPane === "detail",
3215
+ logsFocused: focusPane === "logs",
3216
+ showLogs
3217
+ }
3218
+ ),
3219
+ /* @__PURE__ */ jsx13(LeaderOverlay, { mode, rows, cols }),
3220
+ /* @__PURE__ */ jsx13(HelpOverlay, { mode, rows, cols })
3221
+ ] });
3222
+ }
3223
+
3224
+ // src/tui/index.tsx
3225
+ import { jsx as jsx14 } from "react/jsx-runtime";
3226
+ var args = process.argv.slice(2);
3227
+ function getArg(name) {
3228
+ const idx = args.indexOf(`--${name}`);
3229
+ if (idx !== -1 && idx + 1 < args.length) {
3230
+ return args[idx + 1];
3231
+ }
3232
+ return void 0;
3233
+ }
3234
+ var cwd = getArg("cwd") ?? process.cwd();
3235
+ render(/* @__PURE__ */ jsx14(App, { cwd }));
3236
+ //# sourceMappingURL=tui.js.map