sisyphi 1.1.9 → 1.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-REUQ4B45.js → chunk-GSXF3TCZ.js} +6 -2
- package/dist/{chunk-REUQ4B45.js.map → chunk-GSXF3TCZ.js.map} +1 -1
- package/dist/{chunk-M7LZ2ZHD.js → chunk-HQZOAX6D.js} +2 -2
- package/dist/{chunk-Z32YVDMY.js → chunk-ZSIYQB45.js} +2 -2
- package/dist/cli.js +7 -26
- package/dist/cli.js.map +1 -1
- package/dist/daemon.js +6 -3
- package/dist/daemon.js.map +1 -1
- package/dist/{paths-IJXOAN4E.js → paths-3EL2SCHI.js} +6 -4
- package/dist/templates/CLAUDE.md +2 -0
- package/dist/templates/orchestrator-base.md +15 -5
- package/dist/templates/orchestrator-completion.md +86 -0
- package/dist/templates/orchestrator-validation.md +8 -2
- package/dist/tui.js +1285 -265
- package/dist/tui.js.map +1 -1
- package/package.json +4 -1
- package/templates/CLAUDE.md +2 -0
- package/templates/orchestrator-base.md +15 -5
- package/templates/orchestrator-completion.md +86 -0
- package/templates/orchestrator-validation.md +8 -2
- /package/dist/{chunk-M7LZ2ZHD.js.map → chunk-HQZOAX6D.js.map} +0 -0
- /package/dist/{chunk-Z32YVDMY.js.map → chunk-ZSIYQB45.js.map} +0 -0
- /package/dist/{paths-IJXOAN4E.js.map → paths-3EL2SCHI.js.map} +0 -0
package/dist/tui.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
exec,
|
|
6
6
|
execSafe,
|
|
7
7
|
loadConfig
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-ZSIYQB45.js";
|
|
9
9
|
import {
|
|
10
10
|
buildSessionContext,
|
|
11
11
|
computeActiveTimeMs,
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
rawSend,
|
|
14
14
|
resolveReports,
|
|
15
15
|
statusColor
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-HQZOAX6D.js";
|
|
17
17
|
import {
|
|
18
18
|
shellQuote
|
|
19
19
|
} from "./chunk-6G226ZK7.js";
|
|
@@ -24,8 +24,9 @@ import {
|
|
|
24
24
|
logsDir,
|
|
25
25
|
roadmapPath,
|
|
26
26
|
sessionDir,
|
|
27
|
-
strategyPath
|
|
28
|
-
|
|
27
|
+
strategyPath,
|
|
28
|
+
tuiScratchDir
|
|
29
|
+
} from "./chunk-GSXF3TCZ.js";
|
|
29
30
|
|
|
30
31
|
// src/tui/terminal.ts
|
|
31
32
|
function emptyKey() {
|
|
@@ -172,10 +173,18 @@ function parseBuffer(buf) {
|
|
|
172
173
|
}
|
|
173
174
|
return { events, remaining: "" };
|
|
174
175
|
}
|
|
176
|
+
var rawBypassHandler = null;
|
|
177
|
+
function setRawBypass(handler) {
|
|
178
|
+
rawBypassHandler = handler;
|
|
179
|
+
}
|
|
175
180
|
function startKeypressListener(handler) {
|
|
176
181
|
let buffer = "";
|
|
177
182
|
let escTimer = null;
|
|
178
183
|
const onData = (data) => {
|
|
184
|
+
if (rawBypassHandler) {
|
|
185
|
+
const handled = rawBypassHandler(data);
|
|
186
|
+
if (handled) return;
|
|
187
|
+
}
|
|
179
188
|
if (escTimer !== null) {
|
|
180
189
|
clearTimeout(escTimer);
|
|
181
190
|
escTimer = null;
|
|
@@ -311,8 +320,7 @@ function createAppState(cwd2) {
|
|
|
311
320
|
targetAgentId: null,
|
|
312
321
|
notification: null,
|
|
313
322
|
notificationTimer: null,
|
|
314
|
-
|
|
315
|
-
showStrategy: false,
|
|
323
|
+
showCombinedView: false,
|
|
316
324
|
inputText: "",
|
|
317
325
|
inputCursorPos: 0,
|
|
318
326
|
detailScroll,
|
|
@@ -333,6 +341,17 @@ function createAppState(cwd2) {
|
|
|
333
341
|
cachedReportBlocks: /* @__PURE__ */ new Map(),
|
|
334
342
|
cachedTreeNodes: null,
|
|
335
343
|
treeCacheKey: "",
|
|
344
|
+
cachedDetailLines: null,
|
|
345
|
+
detailCacheKey: "",
|
|
346
|
+
detailRenderedCache: { lines: [], ansi: [] },
|
|
347
|
+
cachedLogsLines: null,
|
|
348
|
+
logsCacheKey: "",
|
|
349
|
+
logsRenderedCache: { lines: [], ansi: [] },
|
|
350
|
+
nvimBridge: null,
|
|
351
|
+
nvimEnabled: true,
|
|
352
|
+
prevNvimFile: null,
|
|
353
|
+
nvimEditable: false,
|
|
354
|
+
nvimOpenTabs: /* @__PURE__ */ new Map(),
|
|
336
355
|
cwd: cwd2
|
|
337
356
|
};
|
|
338
357
|
}
|
|
@@ -396,8 +415,8 @@ function autoExpandCycle(state2) {
|
|
|
396
415
|
}
|
|
397
416
|
|
|
398
417
|
// src/tui/app.ts
|
|
399
|
-
import { readFileSync as readFileSync2, existsSync as
|
|
400
|
-
import { join as
|
|
418
|
+
import { readFileSync as readFileSync2, existsSync as existsSync3, readdirSync, statSync } from "fs";
|
|
419
|
+
import { join as join5 } from "path";
|
|
401
420
|
|
|
402
421
|
// src/tui/lib/tree.ts
|
|
403
422
|
import { join } from "path";
|
|
@@ -785,6 +804,21 @@ function findParentIndex(nodes, index) {
|
|
|
785
804
|
}
|
|
786
805
|
|
|
787
806
|
// src/tui/input.ts
|
|
807
|
+
function activateNvimBypass(state2) {
|
|
808
|
+
setRawBypass((data) => {
|
|
809
|
+
if (data === " ") {
|
|
810
|
+
deactivateNvimBypass();
|
|
811
|
+
state2.focusPane = state2.showCombinedView ? "logs" : "tree";
|
|
812
|
+
requestRender();
|
|
813
|
+
return true;
|
|
814
|
+
}
|
|
815
|
+
state2.nvimBridge.write(data);
|
|
816
|
+
return true;
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
function deactivateNvimBypass() {
|
|
820
|
+
setRawBypass(null);
|
|
821
|
+
}
|
|
788
822
|
function handleCancel(state2) {
|
|
789
823
|
state2.mode = "navigate";
|
|
790
824
|
state2.targetAgentId = null;
|
|
@@ -1285,10 +1319,14 @@ function handleNavigateKey(input, key, state2, actions) {
|
|
|
1285
1319
|
if (key.leftArrow || input === "h") {
|
|
1286
1320
|
if (state2.focusPane === "logs") {
|
|
1287
1321
|
state2.focusPane = "detail";
|
|
1322
|
+
if (state2.nvimEnabled && state2.nvimBridge?.ready) {
|
|
1323
|
+
activateNvimBypass(state2);
|
|
1324
|
+
}
|
|
1288
1325
|
requestRender();
|
|
1289
1326
|
return;
|
|
1290
1327
|
}
|
|
1291
1328
|
if (state2.focusPane === "detail") {
|
|
1329
|
+
deactivateNvimBypass();
|
|
1292
1330
|
state2.focusPane = "tree";
|
|
1293
1331
|
requestRender();
|
|
1294
1332
|
return;
|
|
@@ -1327,8 +1365,12 @@ function handleNavigateKey(input, key, state2, actions) {
|
|
|
1327
1365
|
if (key.tab) {
|
|
1328
1366
|
if (state2.focusPane === "tree") {
|
|
1329
1367
|
state2.focusPane = "detail";
|
|
1368
|
+
if (state2.nvimEnabled && state2.nvimBridge?.ready) {
|
|
1369
|
+
activateNvimBypass(state2);
|
|
1370
|
+
}
|
|
1330
1371
|
} else if (state2.focusPane === "detail") {
|
|
1331
|
-
|
|
1372
|
+
deactivateNvimBypass();
|
|
1373
|
+
state2.focusPane = state2.showCombinedView ? "logs" : "tree";
|
|
1332
1374
|
} else {
|
|
1333
1375
|
state2.focusPane = "tree";
|
|
1334
1376
|
}
|
|
@@ -1571,15 +1613,6 @@ function handleNavigateKey(input, key, state2, actions) {
|
|
|
1571
1613
|
}
|
|
1572
1614
|
return;
|
|
1573
1615
|
}
|
|
1574
|
-
if (input === "s") {
|
|
1575
|
-
if (!state2.strategyContent) {
|
|
1576
|
-
notify(state2, "No strategy for this session");
|
|
1577
|
-
return;
|
|
1578
|
-
}
|
|
1579
|
-
state2.showStrategy = !state2.showStrategy;
|
|
1580
|
-
requestRender();
|
|
1581
|
-
return;
|
|
1582
|
-
}
|
|
1583
1616
|
if (input === "S") {
|
|
1584
1617
|
if (!state2.selectedSessionId) {
|
|
1585
1618
|
notify(state2, "No session selected");
|
|
@@ -1595,11 +1628,11 @@ function handleNavigateKey(input, key, state2, actions) {
|
|
|
1595
1628
|
return;
|
|
1596
1629
|
}
|
|
1597
1630
|
if (input === "t") {
|
|
1598
|
-
if (state2.
|
|
1631
|
+
if (state2.showCombinedView) {
|
|
1599
1632
|
if (state2.focusPane === "logs") state2.focusPane = "detail";
|
|
1600
1633
|
state2.logsScroll.reset();
|
|
1601
1634
|
}
|
|
1602
|
-
state2.
|
|
1635
|
+
state2.showCombinedView = !state2.showCombinedView;
|
|
1603
1636
|
requestRender();
|
|
1604
1637
|
return;
|
|
1605
1638
|
}
|
|
@@ -1651,15 +1684,23 @@ function renderLine(segs) {
|
|
|
1651
1684
|
}
|
|
1652
1685
|
return out;
|
|
1653
1686
|
}
|
|
1687
|
+
var cachedBlank = "";
|
|
1688
|
+
var cachedBlankWidth = 0;
|
|
1654
1689
|
function createFrameBuffer(width, height) {
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1690
|
+
if (width !== cachedBlankWidth) {
|
|
1691
|
+
cachedBlank = " ".repeat(width);
|
|
1692
|
+
cachedBlankWidth = width;
|
|
1693
|
+
}
|
|
1694
|
+
const lines = new Array(height);
|
|
1695
|
+
for (let i = 0; i < height; i++) lines[i] = cachedBlank;
|
|
1696
|
+
return { lines, width, height };
|
|
1697
|
+
}
|
|
1698
|
+
function copyRows(buf, src, startRow, count) {
|
|
1699
|
+
for (let i = 0; i < count && startRow + i < buf.height; i++) {
|
|
1700
|
+
buf.lines[startRow + i] = src[startRow + i];
|
|
1701
|
+
}
|
|
1661
1702
|
}
|
|
1662
|
-
function flushFrame(frame, prevFrame2) {
|
|
1703
|
+
function flushFrame(frame, prevFrame2, suffix) {
|
|
1663
1704
|
let out = "\x1B[?2026h";
|
|
1664
1705
|
for (let i = 0; i < frame.length; i++) {
|
|
1665
1706
|
if (frame[i] !== prevFrame2[i]) {
|
|
@@ -1668,10 +1709,76 @@ function flushFrame(frame, prevFrame2) {
|
|
|
1668
1709
|
out += frame[i];
|
|
1669
1710
|
}
|
|
1670
1711
|
}
|
|
1712
|
+
if (suffix) out += suffix;
|
|
1671
1713
|
out += "\x1B[?2026l";
|
|
1672
1714
|
return out;
|
|
1673
1715
|
}
|
|
1674
1716
|
var ANSI_RE = /\x1b\[[0-9;]*[a-zA-Z]/g;
|
|
1717
|
+
function clipAnsi(content, maxWidth) {
|
|
1718
|
+
let out = "";
|
|
1719
|
+
let displayWidth = 0;
|
|
1720
|
+
let i = 0;
|
|
1721
|
+
while (i < content.length) {
|
|
1722
|
+
if (content[i] === "\x1B" && content[i + 1] === "[") {
|
|
1723
|
+
const seqLen = ansiLen(content, i);
|
|
1724
|
+
if (seqLen > 0) {
|
|
1725
|
+
out += content.substring(i, i + seqLen);
|
|
1726
|
+
i += seqLen;
|
|
1727
|
+
continue;
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
const cp = content.codePointAt(i);
|
|
1731
|
+
const ch = String.fromCodePoint(cp);
|
|
1732
|
+
const chWidth = cp < 128 ? 1 : stringWidth2(ch);
|
|
1733
|
+
if (displayWidth + chWidth > maxWidth) break;
|
|
1734
|
+
out += ch;
|
|
1735
|
+
displayWidth += chWidth;
|
|
1736
|
+
i += ch.length;
|
|
1737
|
+
}
|
|
1738
|
+
if (out.includes("\x1B[") && !out.endsWith("\x1B[0m")) {
|
|
1739
|
+
out += "\x1B[0m";
|
|
1740
|
+
}
|
|
1741
|
+
const remaining = maxWidth - displayWidth;
|
|
1742
|
+
if (remaining > 0) out += " ".repeat(remaining);
|
|
1743
|
+
return out;
|
|
1744
|
+
}
|
|
1745
|
+
function displayWidthFast(s) {
|
|
1746
|
+
let w = 0;
|
|
1747
|
+
let i = 0;
|
|
1748
|
+
while (i < s.length) {
|
|
1749
|
+
if (s[i] === "\x1B" && s[i + 1] === "[") {
|
|
1750
|
+
const len = ansiLen(s, i);
|
|
1751
|
+
if (len > 0) {
|
|
1752
|
+
i += len;
|
|
1753
|
+
continue;
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
const cp = s.codePointAt(i);
|
|
1757
|
+
const ch = String.fromCodePoint(cp);
|
|
1758
|
+
w += cp < 128 ? 1 : stringWidth2(ch);
|
|
1759
|
+
i += ch.length;
|
|
1760
|
+
}
|
|
1761
|
+
return w;
|
|
1762
|
+
}
|
|
1763
|
+
function ansiLen(s, i) {
|
|
1764
|
+
let j = i + 2;
|
|
1765
|
+
const len = s.length;
|
|
1766
|
+
while (j < len) {
|
|
1767
|
+
const c = s.charCodeAt(j);
|
|
1768
|
+
if (c >= 48 && c <= 57 || c === 59) {
|
|
1769
|
+
j++;
|
|
1770
|
+
} else {
|
|
1771
|
+
break;
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
if (j < len) {
|
|
1775
|
+
const c = s.charCodeAt(j);
|
|
1776
|
+
if (c >= 65 && c <= 90 || c >= 97 && c <= 122) {
|
|
1777
|
+
return j + 1 - i;
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
return 0;
|
|
1781
|
+
}
|
|
1675
1782
|
function writeAt(buf, x, y, content) {
|
|
1676
1783
|
if (y < 0 || y >= buf.height) return;
|
|
1677
1784
|
if (x < 0 || x >= buf.width) return;
|
|
@@ -1691,16 +1798,16 @@ function writeClipped(buf, x, y, content, maxWidth) {
|
|
|
1691
1798
|
let i = 0;
|
|
1692
1799
|
while (i < content.length) {
|
|
1693
1800
|
if (content[i] === "\x1B" && content[i + 1] === "[") {
|
|
1694
|
-
const
|
|
1695
|
-
if (
|
|
1696
|
-
out +=
|
|
1697
|
-
i +=
|
|
1801
|
+
const seqLen = ansiLen(content, i);
|
|
1802
|
+
if (seqLen > 0) {
|
|
1803
|
+
out += content.substring(i, i + seqLen);
|
|
1804
|
+
i += seqLen;
|
|
1698
1805
|
continue;
|
|
1699
1806
|
}
|
|
1700
1807
|
}
|
|
1701
1808
|
const cp = content.codePointAt(i);
|
|
1702
1809
|
const ch = String.fromCodePoint(cp);
|
|
1703
|
-
const chWidth = stringWidth2(ch);
|
|
1810
|
+
const chWidth = cp < 128 ? 1 : stringWidth2(ch);
|
|
1704
1811
|
if (displayWidth + chWidth > maxWidth) break;
|
|
1705
1812
|
out += ch;
|
|
1706
1813
|
displayWidth += chWidth;
|
|
@@ -1713,7 +1820,12 @@ function writeClipped(buf, x, y, content, maxWidth) {
|
|
|
1713
1820
|
if (remaining > 0) {
|
|
1714
1821
|
out += " ".repeat(remaining);
|
|
1715
1822
|
}
|
|
1716
|
-
|
|
1823
|
+
const existing = buf.lines[y];
|
|
1824
|
+
const prefix = sliceDisplayCols(existing, 0, x);
|
|
1825
|
+
const suffix = sliceDisplayCols(existing, x + maxWidth, buf.width);
|
|
1826
|
+
const prefixDisplayW = displayWidthFast(prefix);
|
|
1827
|
+
const paddedPrefix = prefixDisplayW < x ? prefix + " ".repeat(x - prefixDisplayW) : prefix;
|
|
1828
|
+
buf.lines[y] = paddedPrefix + out + suffix;
|
|
1717
1829
|
}
|
|
1718
1830
|
function writeCenter(buf, row, content) {
|
|
1719
1831
|
const textWidth = stringWidth2(content.replace(ANSI_RE, ""));
|
|
@@ -1730,28 +1842,75 @@ function drawBorder(buf, x, y, w, h, color) {
|
|
|
1730
1842
|
writeAt(buf, x + w - 1, row, sgr + "\u2502" + reset);
|
|
1731
1843
|
}
|
|
1732
1844
|
}
|
|
1733
|
-
function
|
|
1734
|
-
const {
|
|
1735
|
-
|
|
1736
|
-
const
|
|
1845
|
+
function buildPanelRows(rect, lines, scrollOffset, focused, borderColor, renderedCache) {
|
|
1846
|
+
const { w, h } = rect;
|
|
1847
|
+
const rows = new Array(h);
|
|
1848
|
+
const color = focused ? "blue" : borderColor;
|
|
1849
|
+
const sgr = `\x1B[${colorToSGR(color)}m`;
|
|
1850
|
+
const reset = "\x1B[0m";
|
|
1737
1851
|
const innerW = w - 4;
|
|
1738
|
-
const innerY = y + 1;
|
|
1739
1852
|
const innerH = h - 2;
|
|
1740
|
-
|
|
1853
|
+
const blankInner = " ".repeat(innerW);
|
|
1854
|
+
rows[0] = sgr + "\u256D" + "\u2500".repeat(w - 2) + "\u256E" + reset;
|
|
1855
|
+
rows[h - 1] = sgr + "\u2570" + "\u2500".repeat(w - 2) + "\u256F" + reset;
|
|
1856
|
+
const borderL = sgr + "\u2502" + reset + " ";
|
|
1857
|
+
const borderR = " " + sgr + "\u2502" + reset;
|
|
1858
|
+
const emptyRow = borderL + blankInner + borderR;
|
|
1859
|
+
for (let i = 1; i < h - 1; i++) rows[i] = emptyRow;
|
|
1860
|
+
if (innerW <= 0 || innerH <= 0) return rows;
|
|
1861
|
+
let ansiLines;
|
|
1862
|
+
if (renderedCache && renderedCache.lines === lines) {
|
|
1863
|
+
ansiLines = renderedCache.ansi;
|
|
1864
|
+
} else {
|
|
1865
|
+
ansiLines = new Array(lines.length);
|
|
1866
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1867
|
+
ansiLines[i] = renderLine(lines[i]);
|
|
1868
|
+
}
|
|
1869
|
+
if (renderedCache) {
|
|
1870
|
+
renderedCache.lines = lines;
|
|
1871
|
+
renderedCache.ansi = ansiLines;
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1741
1874
|
const hasOverflow = lines.length > innerH;
|
|
1742
1875
|
const viewableH = hasOverflow ? innerH - 1 : innerH;
|
|
1743
1876
|
const maxScroll = Math.max(0, lines.length - viewableH);
|
|
1744
1877
|
const effectiveOffset = Math.min(scrollOffset, maxScroll);
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
writeClipped(buf, innerX, innerY + i, ansi, innerW);
|
|
1878
|
+
for (let i = 0; i < viewableH && effectiveOffset + i < ansiLines.length; i++) {
|
|
1879
|
+
const clipped = clipAnsi(ansiLines[effectiveOffset + i], innerW);
|
|
1880
|
+
rows[1 + i] = borderL + clipped + borderR;
|
|
1749
1881
|
}
|
|
1750
1882
|
if (hasOverflow) {
|
|
1751
1883
|
const scrollPct = maxScroll > 0 ? Math.round(effectiveOffset / maxScroll * 100) : 100;
|
|
1752
1884
|
const indicator = ` \u2195 ${scrollPct}% \xB7 ${lines.length} lines`;
|
|
1753
|
-
|
|
1885
|
+
const clipped = clipAnsi(`\x1B[2m${indicator}\x1B[0m`, innerW);
|
|
1886
|
+
rows[1 + viewableH] = borderL + clipped + borderR;
|
|
1754
1887
|
}
|
|
1888
|
+
return rows;
|
|
1889
|
+
}
|
|
1890
|
+
function buildEmptyPanelRows(rect, focused, borderColor, centerText) {
|
|
1891
|
+
const { w, h } = rect;
|
|
1892
|
+
const rows = new Array(h);
|
|
1893
|
+
const color = focused ? "blue" : borderColor;
|
|
1894
|
+
const sgr = `\x1B[${colorToSGR(color)}m`;
|
|
1895
|
+
const reset = "\x1B[0m";
|
|
1896
|
+
const innerW = w - 4;
|
|
1897
|
+
const borderL = sgr + "\u2502" + reset + " ";
|
|
1898
|
+
const borderR = " " + sgr + "\u2502" + reset;
|
|
1899
|
+
const emptyRow = borderL + " ".repeat(innerW) + borderR;
|
|
1900
|
+
rows[0] = sgr + "\u256D" + "\u2500".repeat(w - 2) + "\u256E" + reset;
|
|
1901
|
+
rows[h - 1] = sgr + "\u2570" + "\u2500".repeat(w - 2) + "\u256F" + reset;
|
|
1902
|
+
for (let i = 1; i < h - 1; i++) rows[i] = emptyRow;
|
|
1903
|
+
if (centerText) {
|
|
1904
|
+
const midRow = Math.floor(h / 2);
|
|
1905
|
+
if (midRow > 0 && midRow < h - 1) {
|
|
1906
|
+
const clipped = clipAnsi(centerText, innerW);
|
|
1907
|
+
const textW = displayWidthFast(centerText);
|
|
1908
|
+
const pad = Math.max(0, Math.floor((innerW - textW) / 2));
|
|
1909
|
+
const centered = " ".repeat(pad) + clipped;
|
|
1910
|
+
rows[midRow] = borderL + clipAnsi(centered, innerW) + borderR;
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
return rows;
|
|
1755
1914
|
}
|
|
1756
1915
|
function sliceDisplayCols(s, start, end) {
|
|
1757
1916
|
let out = "";
|
|
@@ -1761,19 +1920,20 @@ function sliceDisplayCols(s, start, end) {
|
|
|
1761
1920
|
let hasOpenSGR = false;
|
|
1762
1921
|
while (i < s.length && col < end) {
|
|
1763
1922
|
if (s[i] === "\x1B" && s[i + 1] === "[") {
|
|
1764
|
-
const
|
|
1765
|
-
if (
|
|
1923
|
+
const seqLen = ansiLen(s, i);
|
|
1924
|
+
if (seqLen > 0) {
|
|
1766
1925
|
if (col >= start) {
|
|
1767
|
-
|
|
1768
|
-
|
|
1926
|
+
const seq = s.substring(i, i + seqLen);
|
|
1927
|
+
out += seq;
|
|
1928
|
+
hasOpenSGR = seq !== "\x1B[0m" && seq !== "\x1B[m";
|
|
1769
1929
|
}
|
|
1770
|
-
i +=
|
|
1930
|
+
i += seqLen;
|
|
1771
1931
|
continue;
|
|
1772
1932
|
}
|
|
1773
1933
|
}
|
|
1774
1934
|
const cp = s.codePointAt(i);
|
|
1775
1935
|
const ch = String.fromCodePoint(cp);
|
|
1776
|
-
const chWidth = stringWidth2(ch);
|
|
1936
|
+
const chWidth = cp < 128 ? 1 : stringWidth2(ch);
|
|
1777
1937
|
if (col >= start) {
|
|
1778
1938
|
inSlice = true;
|
|
1779
1939
|
if (col + chWidth > end) break;
|
|
@@ -1788,6 +1948,76 @@ function sliceDisplayCols(s, start, end) {
|
|
|
1788
1948
|
return out;
|
|
1789
1949
|
}
|
|
1790
1950
|
|
|
1951
|
+
// src/tui/lib/tree-render.ts
|
|
1952
|
+
function renderTreePrefix(node, nodes, index) {
|
|
1953
|
+
if (node.depth === 0) {
|
|
1954
|
+
return node.expandable ? node.expanded ? "\u25BC " : "\u25B8 " : " ";
|
|
1955
|
+
}
|
|
1956
|
+
const parts = [];
|
|
1957
|
+
for (let d = 1; d < node.depth; d++) {
|
|
1958
|
+
parts.push(isAncestorLastSibling(nodes, index, d) ? " " : "\u2502 ");
|
|
1959
|
+
}
|
|
1960
|
+
parts.push(isLastSibling(nodes, index) ? "\u2514\u2500" : "\u251C\u2500");
|
|
1961
|
+
if (node.expandable) {
|
|
1962
|
+
parts.push(node.expanded ? "\u25BC " : "\u25B8 ");
|
|
1963
|
+
} else {
|
|
1964
|
+
parts.push(" ");
|
|
1965
|
+
}
|
|
1966
|
+
return parts.join("");
|
|
1967
|
+
}
|
|
1968
|
+
function isLastSibling(nodes, index) {
|
|
1969
|
+
const depth = nodes[index].depth;
|
|
1970
|
+
for (let i = index + 1; i < nodes.length; i++) {
|
|
1971
|
+
if (nodes[i].depth === depth) return false;
|
|
1972
|
+
if (nodes[i].depth < depth) return true;
|
|
1973
|
+
}
|
|
1974
|
+
return true;
|
|
1975
|
+
}
|
|
1976
|
+
function isAncestorLastSibling(nodes, index, depth) {
|
|
1977
|
+
for (let i = index - 1; i >= 0; i--) {
|
|
1978
|
+
if (nodes[i].depth === depth) {
|
|
1979
|
+
return isLastSibling(nodes, i);
|
|
1980
|
+
}
|
|
1981
|
+
if (nodes[i].depth < depth) return true;
|
|
1982
|
+
}
|
|
1983
|
+
return true;
|
|
1984
|
+
}
|
|
1985
|
+
function precomputePrefixes(nodes) {
|
|
1986
|
+
const len = nodes.length;
|
|
1987
|
+
if (len === 0) return;
|
|
1988
|
+
const isLast = new Array(len);
|
|
1989
|
+
const lastSeenAtDepth = /* @__PURE__ */ new Map();
|
|
1990
|
+
for (let i = len - 1; i >= 0; i--) {
|
|
1991
|
+
const depth = nodes[i].depth;
|
|
1992
|
+
isLast[i] = !lastSeenAtDepth.has(depth);
|
|
1993
|
+
lastSeenAtDepth.set(depth, i);
|
|
1994
|
+
for (const [d] of lastSeenAtDepth) {
|
|
1995
|
+
if (d > depth) lastSeenAtDepth.delete(d);
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
const ancestorIsLast = [];
|
|
1999
|
+
for (let i = 0; i < len; i++) {
|
|
2000
|
+
const node = nodes[i];
|
|
2001
|
+
if (node.depth === 0) {
|
|
2002
|
+
node.prefix = node.expandable ? node.expanded ? "\u25BC " : "\u25B8 " : " ";
|
|
2003
|
+
ancestorIsLast[0] = isLast[i];
|
|
2004
|
+
continue;
|
|
2005
|
+
}
|
|
2006
|
+
ancestorIsLast[node.depth] = isLast[i];
|
|
2007
|
+
const parts = [];
|
|
2008
|
+
for (let d = 1; d < node.depth; d++) {
|
|
2009
|
+
parts.push(ancestorIsLast[d] ? " " : "\u2502 ");
|
|
2010
|
+
}
|
|
2011
|
+
parts.push(isLast[i] ? "\u2514\u2500" : "\u251C\u2500");
|
|
2012
|
+
if (node.expandable) {
|
|
2013
|
+
parts.push(node.expanded ? "\u25BC " : "\u25B8 ");
|
|
2014
|
+
} else {
|
|
2015
|
+
parts.push(" ");
|
|
2016
|
+
}
|
|
2017
|
+
node.prefix = parts.join("");
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
|
|
1791
2021
|
// src/tui/lib/client.ts
|
|
1792
2022
|
function send(request) {
|
|
1793
2023
|
return rawSend(request, 5e3);
|
|
@@ -1804,8 +2034,13 @@ function selectWindow(windowId) {
|
|
|
1804
2034
|
function selectPane(paneId) {
|
|
1805
2035
|
execSafe(`tmux select-pane -t "${paneId}"`);
|
|
1806
2036
|
}
|
|
1807
|
-
function
|
|
1808
|
-
|
|
2037
|
+
function listAllWindowIds() {
|
|
2038
|
+
try {
|
|
2039
|
+
const output = execSync('tmux list-windows -a -F "#{window_id}"', { encoding: "utf-8", env: EXEC_ENV });
|
|
2040
|
+
return new Set(output.trim().split("\n").filter(Boolean));
|
|
2041
|
+
} catch {
|
|
2042
|
+
return /* @__PURE__ */ new Set();
|
|
2043
|
+
}
|
|
1809
2044
|
}
|
|
1810
2045
|
var companionPaneId = null;
|
|
1811
2046
|
function setupCompanionPlugin() {
|
|
@@ -1901,41 +2136,6 @@ function copyToClipboard(text) {
|
|
|
1901
2136
|
execSync2("pbcopy", { input: text });
|
|
1902
2137
|
}
|
|
1903
2138
|
|
|
1904
|
-
// src/tui/lib/tree-render.ts
|
|
1905
|
-
function renderTreePrefix(node, nodes, index) {
|
|
1906
|
-
if (node.depth === 0) {
|
|
1907
|
-
return node.expandable ? node.expanded ? "\u25BC " : "\u25B8 " : " ";
|
|
1908
|
-
}
|
|
1909
|
-
const parts = [];
|
|
1910
|
-
for (let d = 1; d < node.depth; d++) {
|
|
1911
|
-
parts.push(isAncestorLastSibling(nodes, index, d) ? " " : "\u2502 ");
|
|
1912
|
-
}
|
|
1913
|
-
parts.push(isLastSibling(nodes, index) ? "\u2514\u2500" : "\u251C\u2500");
|
|
1914
|
-
if (node.expandable) {
|
|
1915
|
-
parts.push(node.expanded ? "\u25BC " : "\u25B8 ");
|
|
1916
|
-
} else {
|
|
1917
|
-
parts.push(" ");
|
|
1918
|
-
}
|
|
1919
|
-
return parts.join("");
|
|
1920
|
-
}
|
|
1921
|
-
function isLastSibling(nodes, index) {
|
|
1922
|
-
const depth = nodes[index].depth;
|
|
1923
|
-
for (let i = index + 1; i < nodes.length; i++) {
|
|
1924
|
-
if (nodes[i].depth === depth) return false;
|
|
1925
|
-
if (nodes[i].depth < depth) return true;
|
|
1926
|
-
}
|
|
1927
|
-
return true;
|
|
1928
|
-
}
|
|
1929
|
-
function isAncestorLastSibling(nodes, index, depth) {
|
|
1930
|
-
for (let i = index - 1; i >= 0; i--) {
|
|
1931
|
-
if (nodes[i].depth === depth) {
|
|
1932
|
-
return isLastSibling(nodes, i);
|
|
1933
|
-
}
|
|
1934
|
-
if (nodes[i].depth < depth) return true;
|
|
1935
|
-
}
|
|
1936
|
-
return true;
|
|
1937
|
-
}
|
|
1938
|
-
|
|
1939
2139
|
// src/tui/panels/tree.ts
|
|
1940
2140
|
function renderNodeContent(node, maxWidth) {
|
|
1941
2141
|
switch (node.type) {
|
|
@@ -2075,7 +2275,7 @@ function renderTreePanel(buf, rect, nodes, cursorIndex, focused) {
|
|
|
2075
2275
|
const node = visible[i];
|
|
2076
2276
|
const realIdx = scrollOffset + i;
|
|
2077
2277
|
const isSelected = realIdx === cursorIndex;
|
|
2078
|
-
const prefix = renderTreePrefix(node, nodes, realIdx);
|
|
2278
|
+
const prefix = node.prefix ?? renderTreePrefix(node, nodes, realIdx);
|
|
2079
2279
|
const contentWidth = innerW;
|
|
2080
2280
|
const { icon, label, meta, color, dim, metaColor, suffix, suffixColor } = renderNodeContent(
|
|
2081
2281
|
node,
|
|
@@ -2187,7 +2387,7 @@ function buildPlanLines(content, maxLines, width) {
|
|
|
2187
2387
|
}
|
|
2188
2388
|
return lines;
|
|
2189
2389
|
}
|
|
2190
|
-
function buildSessionLines(session, planContent, goalContent, width, paneAlive, strategyContent = ""
|
|
2390
|
+
function buildSessionLines(session, planContent, goalContent, width, paneAlive, strategyContent = "") {
|
|
2191
2391
|
const lines = [];
|
|
2192
2392
|
const contentWidth = width - 4;
|
|
2193
2393
|
const agents = session.agents;
|
|
@@ -2228,12 +2428,8 @@ function buildSessionLines(session, planContent, goalContent, width, paneAlive,
|
|
|
2228
2428
|
]);
|
|
2229
2429
|
}
|
|
2230
2430
|
lines.push(singleLine(" "));
|
|
2231
|
-
if (
|
|
2232
|
-
|
|
2233
|
-
lines.push([
|
|
2234
|
-
seg(" \u258E \u25C8 STRATEGY", { color: "yellow", bold: true }),
|
|
2235
|
-
seg(stratHint, { dim: true })
|
|
2236
|
-
]);
|
|
2431
|
+
if (strategyContent) {
|
|
2432
|
+
lines.push([seg(" \u258E \u25C8 STRATEGY", { color: "yellow", bold: true })]);
|
|
2237
2433
|
const stratLines = buildPlanLines(strategyContent, 99999, width);
|
|
2238
2434
|
if (stratLines.length === 0) {
|
|
2239
2435
|
lines.push(singleLine(" (empty)", { dim: true, italic: true }));
|
|
@@ -2243,11 +2439,7 @@ function buildSessionLines(session, planContent, goalContent, width, paneAlive,
|
|
|
2243
2439
|
}
|
|
2244
2440
|
}
|
|
2245
2441
|
} else {
|
|
2246
|
-
|
|
2247
|
-
lines.push([
|
|
2248
|
-
seg(" \u258E \u25C8 PLAN", { color: "yellow", bold: true }),
|
|
2249
|
-
seg(toggleHint, { dim: true })
|
|
2250
|
-
]);
|
|
2442
|
+
lines.push([seg(" \u258E \u25C8 PLAN", { color: "yellow", bold: true })]);
|
|
2251
2443
|
const planLines = buildPlanLines(planContent, 99999, width);
|
|
2252
2444
|
if (planLines.length === 0) {
|
|
2253
2445
|
lines.push(singleLine(" orchestrator will create one", { dim: true, italic: true }));
|
|
@@ -2562,7 +2754,7 @@ function buildLogsLines(cycleLogs, width) {
|
|
|
2562
2754
|
}
|
|
2563
2755
|
return lines;
|
|
2564
2756
|
}
|
|
2565
|
-
function
|
|
2757
|
+
function renderDetailRows(rect, state2, detailCtx) {
|
|
2566
2758
|
const { session, agents, reportBlocks, detailReportBlocks, contextFileContent } = detailCtx;
|
|
2567
2759
|
const scrollOffset = state2.detailScroll.offset;
|
|
2568
2760
|
const focused = state2.focusPane === "detail";
|
|
@@ -2570,157 +2762,248 @@ function renderDetailContent(buf, rect, state2, detailCtx) {
|
|
|
2570
2762
|
const reportAgent = agents.find((a) => a.id === state2.targetAgentId);
|
|
2571
2763
|
if (reportAgent) {
|
|
2572
2764
|
const lines2 = buildReportViewLines(reportAgent, reportBlocks, rect.w);
|
|
2573
|
-
|
|
2574
|
-
return;
|
|
2765
|
+
return buildPanelRows(rect, lines2, scrollOffset, focused, "cyan");
|
|
2575
2766
|
}
|
|
2576
2767
|
}
|
|
2577
2768
|
const cursorNode = detailCtx.nodes[state2.cursorIndex];
|
|
2578
2769
|
if (!cursorNode || !session) {
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
return;
|
|
2583
|
-
}
|
|
2770
|
+
return buildEmptyPanelRows(rect, false, "gray", "\x1B[2mSelect a session to view details\x1B[0m");
|
|
2771
|
+
}
|
|
2772
|
+
if (cursorNode.sessionId !== session.id) {
|
|
2773
|
+
return buildEmptyPanelRows(rect, false, "gray");
|
|
2774
|
+
}
|
|
2775
|
+
const lastCycle = session.orchestratorCycles[session.orchestratorCycles.length - 1];
|
|
2776
|
+
const cacheKey = [
|
|
2777
|
+
cursorNode.id,
|
|
2778
|
+
cursorNode.type,
|
|
2779
|
+
state2.mode,
|
|
2780
|
+
state2.targetAgentId,
|
|
2781
|
+
rect.w,
|
|
2782
|
+
session.id,
|
|
2783
|
+
session.agents.length,
|
|
2784
|
+
session.orchestratorCycles.length,
|
|
2785
|
+
lastCycle?.completedAt ?? "",
|
|
2786
|
+
lastCycle?.agentsSpawned.length ?? 0,
|
|
2787
|
+
state2.planContent.length,
|
|
2788
|
+
state2.goalContent.length,
|
|
2789
|
+
state2.strategyContent.length,
|
|
2790
|
+
state2.paneAlive,
|
|
2791
|
+
detailReportBlocks.length,
|
|
2792
|
+
session.messages.length,
|
|
2793
|
+
state2.contextFiles.length,
|
|
2794
|
+
contextFileContent?.length ?? -1
|
|
2795
|
+
].join(":");
|
|
2584
2796
|
let lines;
|
|
2585
2797
|
let borderColor = "gray";
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
const cycle = session.orchestratorCycles.find((c) => c.cycle === cycleNode.cycleNumber);
|
|
2594
|
-
if (!cycle) {
|
|
2595
|
-
lines = buildSessionLines(session, state2.planContent, state2.goalContent, rect.w, state2.paneAlive, state2.strategyContent, state2.showStrategy);
|
|
2596
|
-
} else {
|
|
2597
|
-
lines = buildCycleLines(cycle, session.agents, rect.w);
|
|
2798
|
+
if (cacheKey === state2.detailCacheKey && state2.cachedDetailLines !== null) {
|
|
2799
|
+
lines = state2.cachedDetailLines;
|
|
2800
|
+
} else {
|
|
2801
|
+
switch (cursorNode.type) {
|
|
2802
|
+
case "session": {
|
|
2803
|
+
lines = buildSessionLines(session, state2.planContent, state2.goalContent, rect.w, state2.paneAlive, state2.strategyContent);
|
|
2804
|
+
break;
|
|
2598
2805
|
}
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2806
|
+
case "cycle": {
|
|
2807
|
+
const cycleNode = cursorNode;
|
|
2808
|
+
const cycle = session.orchestratorCycles.find((c) => c.cycle === cycleNode.cycleNumber);
|
|
2809
|
+
if (!cycle) {
|
|
2810
|
+
lines = buildSessionLines(session, state2.planContent, state2.goalContent, rect.w, state2.paneAlive, state2.strategyContent);
|
|
2811
|
+
} else {
|
|
2812
|
+
lines = buildCycleLines(cycle, session.agents, rect.w);
|
|
2813
|
+
}
|
|
2814
|
+
break;
|
|
2608
2815
|
}
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2816
|
+
case "agent": {
|
|
2817
|
+
const agentNode = cursorNode;
|
|
2818
|
+
const agent = agents.find((a) => a.id === agentNode.agentId);
|
|
2819
|
+
if (!agent) {
|
|
2820
|
+
lines = buildSessionLines(session, state2.planContent, state2.goalContent, rect.w, state2.paneAlive, state2.strategyContent);
|
|
2821
|
+
} else {
|
|
2822
|
+
lines = buildAgentLines(agent, detailReportBlocks, rect.w);
|
|
2823
|
+
}
|
|
2616
2824
|
break;
|
|
2617
2825
|
}
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
const
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2826
|
+
case "report": {
|
|
2827
|
+
const reportNode = cursorNode;
|
|
2828
|
+
const agent = agents.find((a) => a.id === reportNode.agentId);
|
|
2829
|
+
if (!agent) {
|
|
2830
|
+
lines = buildSessionLines(session, state2.planContent, state2.goalContent, rect.w, state2.paneAlive, state2.strategyContent);
|
|
2831
|
+
break;
|
|
2832
|
+
}
|
|
2833
|
+
const reportIdx = reportNode.reportIndex;
|
|
2834
|
+
const specificBlock = detailReportBlocks.find((_b, i) => {
|
|
2835
|
+
const originalIdx = agent.reports.length - 1 - i;
|
|
2836
|
+
return originalIdx === reportIdx;
|
|
2837
|
+
});
|
|
2838
|
+
if (specificBlock) {
|
|
2839
|
+
const { label: badge, color: badgeColor } = reportBadge(specificBlock.type);
|
|
2840
|
+
lines = [
|
|
2841
|
+
[seg(" "), seg(badge, { color: badgeColor }), seg(` ${agent.id} \xB7 ${agentDisplayName(agent)}`, { bold: true })],
|
|
2842
|
+
singleLine(` ${formatTime(specificBlock.timestamp)}`, { dim: true }),
|
|
2843
|
+
singleLine(" "),
|
|
2844
|
+
[seg(" \u258E CONTENT", { color: badgeColor, bold: true })],
|
|
2845
|
+
...wrapText(specificBlock.content.trim(), rect.w - 8).map((l) => singleLine(` ${l}`))
|
|
2846
|
+
];
|
|
2847
|
+
borderColor = badgeColor;
|
|
2848
|
+
} else {
|
|
2849
|
+
lines = buildAgentLines(agent, detailReportBlocks, rect.w);
|
|
2850
|
+
}
|
|
2851
|
+
break;
|
|
2635
2852
|
}
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
]);
|
|
2853
|
+
case "messages": {
|
|
2854
|
+
lines = [singleLine(` Messages (${session.messages.length})`, { bold: true })];
|
|
2855
|
+
if (session.messages.length === 0) {
|
|
2856
|
+
lines.push(singleLine(" No messages", { dim: true, italic: true }));
|
|
2857
|
+
} else {
|
|
2858
|
+
for (const msg of session.messages) {
|
|
2859
|
+
const time = formatTime(msg.timestamp);
|
|
2860
|
+
const agentId = msg.source.type === "agent" ? msg.source.agentId : void 0;
|
|
2861
|
+
const label = messageSourceLabel(msg.source.type, agentId);
|
|
2862
|
+
const labelColor = messageSourceColor(msg.source.type);
|
|
2863
|
+
const maxContent = Math.max(10, rect.w - label.length - 20);
|
|
2864
|
+
lines.push([
|
|
2865
|
+
seg(` [${time}] `, { dim: true }),
|
|
2866
|
+
seg(`${label}: `, { color: labelColor, bold: true }),
|
|
2867
|
+
seg(wrapText(msg.summary.length > 0 ? msg.summary : msg.content, maxContent)[0], {})
|
|
2868
|
+
]);
|
|
2869
|
+
}
|
|
2654
2870
|
}
|
|
2871
|
+
break;
|
|
2655
2872
|
}
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2873
|
+
case "message": {
|
|
2874
|
+
const msgNode = cursorNode;
|
|
2875
|
+
const msg = session.messages.find((m) => m.id === msgNode.messageId);
|
|
2876
|
+
lines = [singleLine(" Message", { bold: true })];
|
|
2877
|
+
if (msg) {
|
|
2878
|
+
lines.push(singleLine(` ${msgNode.source} \xB7 ${msgNode.timestamp}`, { dim: true }));
|
|
2879
|
+
for (const l of wrapText(msg.content, rect.w - 8)) {
|
|
2880
|
+
lines.push(singleLine(` ${l}`));
|
|
2881
|
+
}
|
|
2882
|
+
} else {
|
|
2883
|
+
lines.push(singleLine(" Message not found", { dim: true }));
|
|
2666
2884
|
}
|
|
2667
|
-
|
|
2668
|
-
lines.push(singleLine(" Message not found", { dim: true }));
|
|
2885
|
+
break;
|
|
2669
2886
|
}
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
lines.push(singleLine(` \xB7 ${f}`, { dim: true }));
|
|
2887
|
+
case "context": {
|
|
2888
|
+
lines = [
|
|
2889
|
+
[seg(" "), seg("\u229E", { color: "white" }), seg(` Context (${state2.contextFiles.length})`, { bold: true })]
|
|
2890
|
+
];
|
|
2891
|
+
if (state2.contextFiles.length === 0) {
|
|
2892
|
+
lines.push(singleLine(" No context files found.", { dim: true }));
|
|
2893
|
+
} else {
|
|
2894
|
+
for (const f of state2.contextFiles) {
|
|
2895
|
+
lines.push(singleLine(` \xB7 ${f}`, { dim: true }));
|
|
2896
|
+
}
|
|
2681
2897
|
}
|
|
2898
|
+
break;
|
|
2682
2899
|
}
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
if (contextFileContent == null) {
|
|
2692
|
-
lines.push(singleLine(" File not found or unreadable.", { dim: true }));
|
|
2693
|
-
} else {
|
|
2694
|
-
const wrapped = wrapText(stripFrontmatter(contextFileContent), rect.w - 8);
|
|
2695
|
-
if (wrapped.length === 0) {
|
|
2696
|
-
lines.push(singleLine(" (empty)", { dim: true }));
|
|
2900
|
+
case "context-file": {
|
|
2901
|
+
const ctxFileNode = cursorNode;
|
|
2902
|
+
lines = [
|
|
2903
|
+
[seg(" "), seg("\u229E", { color: "white" }), seg(` ${ctxFileNode.label}`, { bold: true })],
|
|
2904
|
+
singleLine(" ")
|
|
2905
|
+
];
|
|
2906
|
+
if (contextFileContent == null) {
|
|
2907
|
+
lines.push(singleLine(" File not found or unreadable.", { dim: true }));
|
|
2697
2908
|
} else {
|
|
2698
|
-
|
|
2699
|
-
|
|
2909
|
+
const wrapped = wrapText(stripFrontmatter(contextFileContent), rect.w - 8);
|
|
2910
|
+
if (wrapped.length === 0) {
|
|
2911
|
+
lines.push(singleLine(" (empty)", { dim: true }));
|
|
2912
|
+
} else {
|
|
2913
|
+
for (const l of wrapped) {
|
|
2914
|
+
lines.push(singleLine(` ${l}`));
|
|
2915
|
+
}
|
|
2700
2916
|
}
|
|
2701
2917
|
}
|
|
2918
|
+
borderColor = "white";
|
|
2919
|
+
break;
|
|
2920
|
+
}
|
|
2921
|
+
default: {
|
|
2922
|
+
lines = buildSessionLines(session, state2.planContent, state2.goalContent, rect.w, state2.paneAlive, state2.strategyContent);
|
|
2923
|
+
break;
|
|
2702
2924
|
}
|
|
2703
|
-
borderColor = "white";
|
|
2704
|
-
break;
|
|
2705
2925
|
}
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2926
|
+
state2.cachedDetailLines = lines;
|
|
2927
|
+
state2.detailCacheKey = cacheKey;
|
|
2928
|
+
}
|
|
2929
|
+
if (cursorNode.type === "context-file") {
|
|
2930
|
+
borderColor = "white";
|
|
2931
|
+
} else if (cursorNode.type === "report") {
|
|
2932
|
+
const reportNode = cursorNode;
|
|
2933
|
+
const agent = agents.find((a) => a.id === reportNode.agentId);
|
|
2934
|
+
if (agent) {
|
|
2935
|
+
const reportIdx = reportNode.reportIndex;
|
|
2936
|
+
const specificBlock = detailReportBlocks.find((_b, i) => {
|
|
2937
|
+
const originalIdx = agent.reports.length - 1 - i;
|
|
2938
|
+
return originalIdx === reportIdx;
|
|
2939
|
+
});
|
|
2940
|
+
if (specificBlock) borderColor = reportBadge(specificBlock.type).color;
|
|
2709
2941
|
}
|
|
2710
2942
|
}
|
|
2711
|
-
|
|
2943
|
+
return buildPanelRows(rect, lines, scrollOffset, focused, borderColor, state2.detailRenderedCache);
|
|
2712
2944
|
}
|
|
2713
|
-
function
|
|
2945
|
+
function renderLogsRows(rect, state2) {
|
|
2714
2946
|
const focused = state2.focusPane === "logs";
|
|
2715
2947
|
const scrollOffset = state2.logsScroll.offset;
|
|
2716
2948
|
if (state2.logsCycles.length === 0) {
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2949
|
+
return buildEmptyPanelRows(rect, focused, "gray", "\x1B[2mNo logs\x1B[0m");
|
|
2950
|
+
}
|
|
2951
|
+
const logsCacheKey = `${state2.logsCycles.length}:${rect.w}:${state2.logsCycles.map((c) => c.cycle).join(",")}`;
|
|
2952
|
+
let lines;
|
|
2953
|
+
if (logsCacheKey === state2.logsCacheKey && state2.cachedLogsLines !== null) {
|
|
2954
|
+
lines = state2.cachedLogsLines;
|
|
2955
|
+
} else {
|
|
2956
|
+
lines = buildLogsLines(state2.logsCycles, rect.w);
|
|
2957
|
+
state2.cachedLogsLines = lines;
|
|
2958
|
+
state2.logsCacheKey = logsCacheKey;
|
|
2721
2959
|
}
|
|
2722
|
-
|
|
2723
|
-
|
|
2960
|
+
return buildPanelRows(rect, lines, scrollOffset, focused, "gray", state2.logsRenderedCache);
|
|
2961
|
+
}
|
|
2962
|
+
|
|
2963
|
+
// src/tui/panels/nvim-detail.ts
|
|
2964
|
+
function renderNvimDetailRows(rect, bridge, focused, editable, statusRows) {
|
|
2965
|
+
const { w, h } = rect;
|
|
2966
|
+
const rows = new Array(h);
|
|
2967
|
+
const borderColor = focused ? "cyan" : "gray";
|
|
2968
|
+
const sgr = `\x1B[${colorToSGR(borderColor)}m`;
|
|
2969
|
+
const reset = "\x1B[0m";
|
|
2970
|
+
const innerW = w - 4;
|
|
2971
|
+
if (focused) {
|
|
2972
|
+
const badgeText = editable ? " EDIT " : " NVIM ";
|
|
2973
|
+
const badgeLen = badgeText.length;
|
|
2974
|
+
const dashesLeft = 2;
|
|
2975
|
+
const dashesRight = Math.max(0, w - 2 - dashesLeft - badgeLen);
|
|
2976
|
+
rows[0] = sgr + "\u256D" + "\u2500".repeat(dashesLeft) + reset + `\x1B[${colorToSGR("cyan")};1m` + badgeText + reset + sgr + "\u2500".repeat(dashesRight) + "\u256E" + reset;
|
|
2977
|
+
} else {
|
|
2978
|
+
rows[0] = sgr + "\u256D" + "\u2500".repeat(w - 2) + "\u256E" + reset;
|
|
2979
|
+
}
|
|
2980
|
+
rows[h - 1] = sgr + "\u2570" + "\u2500".repeat(w - 2) + "\u256F" + reset;
|
|
2981
|
+
const borderL = sgr + "\u2502" + reset + " ";
|
|
2982
|
+
const borderR = " " + sgr + "\u2502" + reset;
|
|
2983
|
+
const blankInner = " ".repeat(innerW);
|
|
2984
|
+
const emptyRow = borderL + blankInner + borderR;
|
|
2985
|
+
for (let i = 1; i < h - 1; i++) rows[i] = emptyRow;
|
|
2986
|
+
if (innerW <= 0 || h <= 2) return rows;
|
|
2987
|
+
const statusCount = statusRows.length;
|
|
2988
|
+
for (let i = 0; i < statusCount && i < h - 3; i++) {
|
|
2989
|
+
const clipped = clipAnsi(statusRows[i], innerW);
|
|
2990
|
+
rows[1 + i] = borderL + clipped + borderR;
|
|
2991
|
+
}
|
|
2992
|
+
const separatorRow = 1 + statusCount;
|
|
2993
|
+
if (separatorRow < h - 1) {
|
|
2994
|
+
rows[separatorRow] = sgr + "\u251C" + "\u2500".repeat(w - 2) + "\u2524" + reset;
|
|
2995
|
+
}
|
|
2996
|
+
const nvimStartRow = separatorRow + 1;
|
|
2997
|
+
const nvimRows = bridge.getRows();
|
|
2998
|
+
for (let i = nvimStartRow; i < h - 1; i++) {
|
|
2999
|
+
const nvimIdx = i - nvimStartRow;
|
|
3000
|
+
const nvimRow = nvimRows[nvimIdx];
|
|
3001
|
+
if (nvimRow !== void 0) {
|
|
3002
|
+
const clipped = clipAnsi(nvimRow, innerW);
|
|
3003
|
+
rows[i] = borderL + clipped + borderR;
|
|
3004
|
+
}
|
|
3005
|
+
}
|
|
3006
|
+
return rows;
|
|
2724
3007
|
}
|
|
2725
3008
|
|
|
2726
3009
|
// src/tui/panels/bottom.ts
|
|
@@ -2777,13 +3060,13 @@ function renderStatusLine(buf, y, state2, cursorNodeType) {
|
|
|
2777
3060
|
} else if (mode !== "navigate") {
|
|
2778
3061
|
content = D("[enter] send [esc] cancel");
|
|
2779
3062
|
} else if (focusPane === "logs" || focusPane === "detail") {
|
|
2780
|
-
content = B("[jk/\u2191\u2193]") + D(" scroll ") + B("[h/\u2190/tab]") + D(" back ") + B("[t]") + D("oggle
|
|
3063
|
+
content = B("[jk/\u2191\u2193]") + D(" scroll ") + B("[h/\u2190/tab]") + D(" back ") + B("[t]") + D("oggle view ") + SEP + B("[m]") + D("sg ") + B("[g]") + D("oal ") + B("[n]") + D("ew ") + B("[p]") + D("lan ") + B("[w]") + D("indow ") + B("[R]") + D("esume ") + B("[q]") + D("uit");
|
|
2781
3064
|
} else {
|
|
2782
3065
|
let contextFilePart = "";
|
|
2783
3066
|
if (cursorNodeType === "context-file") {
|
|
2784
3067
|
contextFilePart = B("[e]") + D("dit ") + B("[\u23CE]") + D(" open ");
|
|
2785
3068
|
}
|
|
2786
|
-
content = B("[hjkl]") + D(" navigate ") + SEP + contextFilePart + B("[space]") + D(" leader ") + B("[tab]") + D(" detail ") + B("[t]") + D("oggle
|
|
3069
|
+
content = B("[hjkl]") + D(" navigate ") + SEP + contextFilePart + B("[space]") + D(" leader ") + B("[tab]") + D(" detail ") + B("[t]") + D("oggle view ") + SEP + B("[m]") + D("sg ") + B("[n]") + D("ew ") + B("[R]") + D("esume ") + B("[q]") + D("uit");
|
|
2787
3070
|
}
|
|
2788
3071
|
writeClipped(buf, 1, y, content, buf.width - 2);
|
|
2789
3072
|
}
|
|
@@ -2884,13 +3167,662 @@ function renderHelpOverlay(buf, rows, cols) {
|
|
|
2884
3167
|
}
|
|
2885
3168
|
}
|
|
2886
3169
|
|
|
3170
|
+
// src/tui/lib/nvim-bridge.ts
|
|
3171
|
+
import { execSync as execSync3 } from "child_process";
|
|
3172
|
+
import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, unlinkSync } from "fs";
|
|
3173
|
+
import { join as join3 } from "path";
|
|
3174
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
3175
|
+
var NvimBridge = class {
|
|
3176
|
+
pty = null;
|
|
3177
|
+
xterm = null;
|
|
3178
|
+
_cols;
|
|
3179
|
+
_rows;
|
|
3180
|
+
onRender;
|
|
3181
|
+
renderTimer = null;
|
|
3182
|
+
currentFile = null;
|
|
3183
|
+
ready = false;
|
|
3184
|
+
dirty = true;
|
|
3185
|
+
available = false;
|
|
3186
|
+
/** DECSCUSR cursor style: 0=default, 1=blinking block, 2=steady block, 3=blinking underline, 4=steady underline, 5=blinking bar, 6=steady bar */
|
|
3187
|
+
cursorStyle = 0;
|
|
3188
|
+
cachedRows = null;
|
|
3189
|
+
nvimPath = "nvim";
|
|
3190
|
+
pendingFiles = null;
|
|
3191
|
+
fileDebounceTimer = null;
|
|
3192
|
+
cmdFile;
|
|
3193
|
+
constructor(cols, rows, onRender) {
|
|
3194
|
+
this._cols = cols;
|
|
3195
|
+
this._rows = rows;
|
|
3196
|
+
this.onRender = onRender;
|
|
3197
|
+
const cmdDir = join3(tmpdir2(), "sisyphus-nvim");
|
|
3198
|
+
mkdirSync2(cmdDir, { recursive: true });
|
|
3199
|
+
this.cmdFile = join3(cmdDir, `cmd-${process.pid}.lua`);
|
|
3200
|
+
try {
|
|
3201
|
+
this.nvimPath = execSync3("which nvim", { stdio: "pipe" }).toString().trim();
|
|
3202
|
+
this.available = true;
|
|
3203
|
+
} catch {
|
|
3204
|
+
this.available = false;
|
|
3205
|
+
return;
|
|
3206
|
+
}
|
|
3207
|
+
this.spawn().catch(() => {
|
|
3208
|
+
this.available = false;
|
|
3209
|
+
this.ready = false;
|
|
3210
|
+
});
|
|
3211
|
+
}
|
|
3212
|
+
async spawn() {
|
|
3213
|
+
const { spawn } = await import("node-pty");
|
|
3214
|
+
const xtermModule = await import("@xterm/headless");
|
|
3215
|
+
const { Terminal } = xtermModule.default;
|
|
3216
|
+
this.xterm = new Terminal({
|
|
3217
|
+
cols: this._cols,
|
|
3218
|
+
rows: this._rows,
|
|
3219
|
+
allowProposedApi: true
|
|
3220
|
+
});
|
|
3221
|
+
const nvimArgs = [
|
|
3222
|
+
// Pre-init: only settings needed before user config loads
|
|
3223
|
+
"--cmd",
|
|
3224
|
+
[
|
|
3225
|
+
"set noswapfile",
|
|
3226
|
+
"set nobackup",
|
|
3227
|
+
"set nowritebackup",
|
|
3228
|
+
"set hidden",
|
|
3229
|
+
"set autoread"
|
|
3230
|
+
].join(" | "),
|
|
3231
|
+
// Post-init: cosmetic overrides applied AFTER user config (LazyVim, etc.)
|
|
3232
|
+
"-c",
|
|
3233
|
+
[
|
|
3234
|
+
"set laststatus=0",
|
|
3235
|
+
"set showtabline=2",
|
|
3236
|
+
"set signcolumn=no",
|
|
3237
|
+
"set nonumber",
|
|
3238
|
+
"set noruler",
|
|
3239
|
+
"set noshowcmd",
|
|
3240
|
+
"set noshowmode",
|
|
3241
|
+
"set shortmess+=F",
|
|
3242
|
+
"set fillchars=eob:\\ ",
|
|
3243
|
+
"set scrolloff=3"
|
|
3244
|
+
].join(" | "),
|
|
3245
|
+
// Suppress LSP — prevent servers from ever starting (avoids exit warnings)
|
|
3246
|
+
"--cmd",
|
|
3247
|
+
"lua vim.lsp.start = function() end",
|
|
3248
|
+
// Poll-based command executor: reads lua from temp file — no command-line flash
|
|
3249
|
+
"-c",
|
|
3250
|
+
`lua local _t = vim.loop.new_timer(); _t:start(100, 50, vim.schedule_wrap(function() local f = io.open('${this.cmdFile.replace(/'/g, "\\'")}', 'r'); if not f then return end; local c = f:read('*a'); f:close(); os.remove('${this.cmdFile.replace(/'/g, "\\'")}'); if c and #c > 0 then local fn = loadstring(c); if fn then pcall(fn) end end end))`
|
|
3251
|
+
];
|
|
3252
|
+
this.pty = spawn(this.nvimPath, nvimArgs, {
|
|
3253
|
+
name: "xterm-256color",
|
|
3254
|
+
cols: this._cols,
|
|
3255
|
+
rows: this._rows,
|
|
3256
|
+
env: { ...process.env, TERM: "xterm-256color" }
|
|
3257
|
+
});
|
|
3258
|
+
this.pty.onData((data) => {
|
|
3259
|
+
const csMatch = data.match(/\x1b\[(\d+) q/);
|
|
3260
|
+
if (csMatch) this.cursorStyle = parseInt(csMatch[1], 10);
|
|
3261
|
+
this.xterm.write(data);
|
|
3262
|
+
this.dirty = true;
|
|
3263
|
+
this.cachedRows = null;
|
|
3264
|
+
this.debouncedRender();
|
|
3265
|
+
});
|
|
3266
|
+
this.pty.onExit(() => {
|
|
3267
|
+
this.ready = false;
|
|
3268
|
+
});
|
|
3269
|
+
setTimeout(() => {
|
|
3270
|
+
if (this.pty) {
|
|
3271
|
+
this.ready = true;
|
|
3272
|
+
this.dirty = true;
|
|
3273
|
+
this.cachedRows = null;
|
|
3274
|
+
this.onRender();
|
|
3275
|
+
}
|
|
3276
|
+
}, 500);
|
|
3277
|
+
}
|
|
3278
|
+
debouncedRender() {
|
|
3279
|
+
if (this.renderTimer !== null) return;
|
|
3280
|
+
this.renderTimer = setTimeout(() => {
|
|
3281
|
+
this.renderTimer = null;
|
|
3282
|
+
this.onRender();
|
|
3283
|
+
}, 16);
|
|
3284
|
+
}
|
|
3285
|
+
/**
|
|
3286
|
+
* Execute lua in nvim without flashing the command line.
|
|
3287
|
+
* Writes lua to a temp file — a libuv timer in nvim polls and executes it.
|
|
3288
|
+
*/
|
|
3289
|
+
execLua(lua) {
|
|
3290
|
+
writeFileSync2(this.cmdFile, lua);
|
|
3291
|
+
}
|
|
3292
|
+
openFile(path, readonly = true) {
|
|
3293
|
+
if (!this.pty || !this.ready) return;
|
|
3294
|
+
this.currentFile = path;
|
|
3295
|
+
const escapeLua = (s) => s.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
3296
|
+
const ro = readonly ? "vim.bo.readonly = true; vim.bo.modifiable = false" : "vim.bo.readonly = false; vim.bo.modifiable = true";
|
|
3297
|
+
this.execLua(`vim.cmd('edit! ${escapeLua(path)}'); ${ro}`);
|
|
3298
|
+
}
|
|
3299
|
+
openTabFiles(files) {
|
|
3300
|
+
if (!this.pty || !this.ready || files.length === 0) return;
|
|
3301
|
+
const key = files.map((f) => f.path).join("|");
|
|
3302
|
+
this.pendingFiles = { files, key };
|
|
3303
|
+
if (this.fileDebounceTimer !== null) clearTimeout(this.fileDebounceTimer);
|
|
3304
|
+
this.fileDebounceTimer = setTimeout(() => {
|
|
3305
|
+
this.fileDebounceTimer = null;
|
|
3306
|
+
if (this.pendingFiles) {
|
|
3307
|
+
this.executeOpenFiles(this.pendingFiles.files);
|
|
3308
|
+
this.currentFile = this.pendingFiles.key;
|
|
3309
|
+
this.pendingFiles = null;
|
|
3310
|
+
}
|
|
3311
|
+
}, 150);
|
|
3312
|
+
}
|
|
3313
|
+
executeOpenFiles(files) {
|
|
3314
|
+
if (!this.pty || !this.ready) return;
|
|
3315
|
+
const escapeLua = (s) => s.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
3316
|
+
const stmts = [
|
|
3317
|
+
"for _, b in ipairs(vim.api.nvim_list_bufs()) do pcall(vim.api.nvim_buf_delete, b, {force=true}) end"
|
|
3318
|
+
];
|
|
3319
|
+
for (let i = 0; i < files.length; i++) {
|
|
3320
|
+
const path = escapeLua(files[i].path);
|
|
3321
|
+
stmts.push(i === 0 ? `vim.cmd('edit! ${path}')` : `vim.cmd('edit ${path}')`);
|
|
3322
|
+
if (files[i].readonly) {
|
|
3323
|
+
stmts.push("vim.bo.readonly = true", "vim.bo.modifiable = false");
|
|
3324
|
+
} else {
|
|
3325
|
+
stmts.push("vim.bo.readonly = false", "vim.bo.modifiable = true");
|
|
3326
|
+
}
|
|
3327
|
+
}
|
|
3328
|
+
stmts.push("vim.cmd('bfirst')");
|
|
3329
|
+
this.execLua(`(function() ${stmts.join("; ")} end)()`);
|
|
3330
|
+
}
|
|
3331
|
+
openTabFile(path, readonly) {
|
|
3332
|
+
if (!this.pty || !this.ready) return;
|
|
3333
|
+
this.pty.write(`:tabedit ${path}\r`);
|
|
3334
|
+
if (readonly) {
|
|
3335
|
+
this.pty.write(":setlocal readonly nomodifiable\r");
|
|
3336
|
+
} else {
|
|
3337
|
+
this.pty.write(":setlocal noreadonly modifiable\r");
|
|
3338
|
+
}
|
|
3339
|
+
}
|
|
3340
|
+
closeAllTabs() {
|
|
3341
|
+
if (!this.pty || !this.ready) return;
|
|
3342
|
+
this.execLua('for _, b in ipairs(vim.api.nvim_list_bufs()) do pcall(vim.api.nvim_buf_delete, b, {force=true}) end; vim.cmd("enew!")');
|
|
3343
|
+
this.currentFile = null;
|
|
3344
|
+
}
|
|
3345
|
+
resize(cols, rows) {
|
|
3346
|
+
this._cols = cols;
|
|
3347
|
+
this._rows = rows;
|
|
3348
|
+
this.cachedRows = null;
|
|
3349
|
+
this.dirty = true;
|
|
3350
|
+
if (this.pty) this.pty.resize(cols, rows);
|
|
3351
|
+
if (this.xterm) this.xterm.resize(cols, rows);
|
|
3352
|
+
}
|
|
3353
|
+
write(data) {
|
|
3354
|
+
if (this.pty) this.pty.write(data);
|
|
3355
|
+
}
|
|
3356
|
+
getRows() {
|
|
3357
|
+
if (!this.dirty && this.cachedRows) return this.cachedRows;
|
|
3358
|
+
if (!this.xterm) return Array.from({ length: this._rows }, () => " ".repeat(this._cols));
|
|
3359
|
+
const rows = [];
|
|
3360
|
+
const buffer = this.xterm.buffer.active;
|
|
3361
|
+
const reusableCell = buffer.getNullCell();
|
|
3362
|
+
for (let y = 0; y < this._rows; y++) {
|
|
3363
|
+
const line = buffer.getLine(y);
|
|
3364
|
+
if (!line) {
|
|
3365
|
+
rows.push(" ".repeat(this._cols));
|
|
3366
|
+
continue;
|
|
3367
|
+
}
|
|
3368
|
+
let row = "";
|
|
3369
|
+
let prevFg = void 0;
|
|
3370
|
+
let prevBg = void 0;
|
|
3371
|
+
let prevFgMode = "default";
|
|
3372
|
+
let prevBgMode = "default";
|
|
3373
|
+
let prevBold = false;
|
|
3374
|
+
let prevDim = false;
|
|
3375
|
+
let prevItalic = false;
|
|
3376
|
+
let prevUnderline = false;
|
|
3377
|
+
let prevInverse = false;
|
|
3378
|
+
let hasOpenSGR = false;
|
|
3379
|
+
for (let x = 0; x < this._cols; x++) {
|
|
3380
|
+
const cell = line.getCell(x, reusableCell);
|
|
3381
|
+
if (!cell) {
|
|
3382
|
+
row += " ";
|
|
3383
|
+
continue;
|
|
3384
|
+
}
|
|
3385
|
+
const char = cell.getChars() || " ";
|
|
3386
|
+
const fgDefault = cell.isFgDefault();
|
|
3387
|
+
const fgPalette = cell.isFgPalette();
|
|
3388
|
+
const fgRGB = cell.isFgRGB();
|
|
3389
|
+
const fg = fgDefault ? void 0 : cell.getFgColor();
|
|
3390
|
+
let fgMode;
|
|
3391
|
+
if (fgDefault) fgMode = "default";
|
|
3392
|
+
else if (fgPalette) fgMode = "palette";
|
|
3393
|
+
else if (fgRGB) fgMode = "rgb";
|
|
3394
|
+
else throw new Error(`Unknown fg color mode at cell (${x}, ${y})`);
|
|
3395
|
+
const bgDefault = cell.isBgDefault();
|
|
3396
|
+
const bgPalette = cell.isBgPalette();
|
|
3397
|
+
const bgRGB = cell.isBgRGB();
|
|
3398
|
+
const bg = bgDefault ? void 0 : cell.getBgColor();
|
|
3399
|
+
let bgMode;
|
|
3400
|
+
if (bgDefault) bgMode = "default";
|
|
3401
|
+
else if (bgPalette) bgMode = "palette";
|
|
3402
|
+
else if (bgRGB) bgMode = "rgb";
|
|
3403
|
+
else throw new Error(`Unknown bg color mode at cell (${x}, ${y})`);
|
|
3404
|
+
const bold = cell.isBold() !== 0;
|
|
3405
|
+
const dim = cell.isDim() !== 0;
|
|
3406
|
+
const italic = cell.isItalic() !== 0;
|
|
3407
|
+
const underline = cell.isUnderline() !== 0;
|
|
3408
|
+
const inverse = cell.isInverse() !== 0;
|
|
3409
|
+
const attrChanged = fg !== prevFg || bg !== prevBg || fgMode !== prevFgMode || bgMode !== prevBgMode || bold !== prevBold || dim !== prevDim || italic !== prevItalic || underline !== prevUnderline || inverse !== prevInverse;
|
|
3410
|
+
if (attrChanged) {
|
|
3411
|
+
if (hasOpenSGR) {
|
|
3412
|
+
row += "\x1B[0m";
|
|
3413
|
+
hasOpenSGR = false;
|
|
3414
|
+
}
|
|
3415
|
+
const codes = [];
|
|
3416
|
+
if (bold) codes.push("1");
|
|
3417
|
+
if (dim) codes.push("2");
|
|
3418
|
+
if (italic) codes.push("3");
|
|
3419
|
+
if (underline) codes.push("4");
|
|
3420
|
+
if (inverse) codes.push("7");
|
|
3421
|
+
if (fg !== void 0) {
|
|
3422
|
+
if (fgMode === "palette") {
|
|
3423
|
+
codes.push(`38;5;${fg}`);
|
|
3424
|
+
} else if (fgMode === "rgb") {
|
|
3425
|
+
const r = fg >> 16 & 255;
|
|
3426
|
+
const g = fg >> 8 & 255;
|
|
3427
|
+
const b = fg & 255;
|
|
3428
|
+
codes.push(`38;2;${r};${g};${b}`);
|
|
3429
|
+
}
|
|
3430
|
+
}
|
|
3431
|
+
if (bg !== void 0) {
|
|
3432
|
+
if (bgMode === "palette") {
|
|
3433
|
+
codes.push(`48;5;${bg}`);
|
|
3434
|
+
} else if (bgMode === "rgb") {
|
|
3435
|
+
const r = bg >> 16 & 255;
|
|
3436
|
+
const g = bg >> 8 & 255;
|
|
3437
|
+
const b = bg & 255;
|
|
3438
|
+
codes.push(`48;2;${r};${g};${b}`);
|
|
3439
|
+
}
|
|
3440
|
+
}
|
|
3441
|
+
if (codes.length > 0) {
|
|
3442
|
+
row += `\x1B[${codes.join(";")}m`;
|
|
3443
|
+
hasOpenSGR = true;
|
|
3444
|
+
}
|
|
3445
|
+
prevFg = fg;
|
|
3446
|
+
prevBg = bg;
|
|
3447
|
+
prevFgMode = fgMode;
|
|
3448
|
+
prevBgMode = bgMode;
|
|
3449
|
+
prevBold = bold;
|
|
3450
|
+
prevDim = dim;
|
|
3451
|
+
prevItalic = italic;
|
|
3452
|
+
prevUnderline = underline;
|
|
3453
|
+
prevInverse = inverse;
|
|
3454
|
+
}
|
|
3455
|
+
row += char;
|
|
3456
|
+
}
|
|
3457
|
+
if (hasOpenSGR) {
|
|
3458
|
+
row += "\x1B[0m";
|
|
3459
|
+
}
|
|
3460
|
+
rows.push(row);
|
|
3461
|
+
}
|
|
3462
|
+
this.cachedRows = rows;
|
|
3463
|
+
this.dirty = false;
|
|
3464
|
+
return rows;
|
|
3465
|
+
}
|
|
3466
|
+
getCursorPos() {
|
|
3467
|
+
if (!this.xterm) return { x: 0, y: 0 };
|
|
3468
|
+
return {
|
|
3469
|
+
x: this.xterm.buffer.active.cursorX,
|
|
3470
|
+
y: this.xterm.buffer.active.cursorY
|
|
3471
|
+
};
|
|
3472
|
+
}
|
|
3473
|
+
checktime() {
|
|
3474
|
+
if (this.pty && this.ready) {
|
|
3475
|
+
this.execLua('vim.cmd("checktime")');
|
|
3476
|
+
}
|
|
3477
|
+
}
|
|
3478
|
+
destroy() {
|
|
3479
|
+
if (this.renderTimer !== null) {
|
|
3480
|
+
clearTimeout(this.renderTimer);
|
|
3481
|
+
this.renderTimer = null;
|
|
3482
|
+
}
|
|
3483
|
+
if (this.fileDebounceTimer !== null) {
|
|
3484
|
+
clearTimeout(this.fileDebounceTimer);
|
|
3485
|
+
this.fileDebounceTimer = null;
|
|
3486
|
+
}
|
|
3487
|
+
try {
|
|
3488
|
+
if (this.pty) {
|
|
3489
|
+
this.pty.kill();
|
|
3490
|
+
this.pty = null;
|
|
3491
|
+
}
|
|
3492
|
+
} catch {
|
|
3493
|
+
}
|
|
3494
|
+
if (this.xterm) {
|
|
3495
|
+
this.xterm.dispose();
|
|
3496
|
+
this.xterm = null;
|
|
3497
|
+
}
|
|
3498
|
+
this.ready = false;
|
|
3499
|
+
try {
|
|
3500
|
+
unlinkSync(this.cmdFile);
|
|
3501
|
+
} catch {
|
|
3502
|
+
}
|
|
3503
|
+
}
|
|
3504
|
+
};
|
|
3505
|
+
|
|
3506
|
+
// src/tui/lib/overview-writer.ts
|
|
3507
|
+
import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, renameSync, existsSync as existsSync2 } from "fs";
|
|
3508
|
+
import { join as join4 } from "path";
|
|
3509
|
+
function atomicWrite(filePath, content) {
|
|
3510
|
+
const tmp = filePath + ".tmp." + process.pid;
|
|
3511
|
+
writeFileSync3(tmp, content, "utf-8");
|
|
3512
|
+
renameSync(tmp, filePath);
|
|
3513
|
+
}
|
|
3514
|
+
function ensureTuiDir(cwd2, sessionId) {
|
|
3515
|
+
const dir = tuiScratchDir(cwd2, sessionId);
|
|
3516
|
+
mkdirSync3(dir, { recursive: true });
|
|
3517
|
+
return dir;
|
|
3518
|
+
}
|
|
3519
|
+
function formatTimestamp(iso) {
|
|
3520
|
+
try {
|
|
3521
|
+
const d = new Date(iso);
|
|
3522
|
+
return d.toLocaleString("en-US", {
|
|
3523
|
+
month: "short",
|
|
3524
|
+
day: "numeric",
|
|
3525
|
+
hour: "2-digit",
|
|
3526
|
+
minute: "2-digit",
|
|
3527
|
+
second: "2-digit",
|
|
3528
|
+
hour12: false
|
|
3529
|
+
});
|
|
3530
|
+
} catch {
|
|
3531
|
+
return iso;
|
|
3532
|
+
}
|
|
3533
|
+
}
|
|
3534
|
+
function formatDurationMs(ms) {
|
|
3535
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
3536
|
+
const s = Math.floor(ms / 1e3);
|
|
3537
|
+
if (s < 60) return `${s}s`;
|
|
3538
|
+
const m = Math.floor(s / 60);
|
|
3539
|
+
const rem = s % 60;
|
|
3540
|
+
if (m < 60) return rem > 0 ? `${m}m ${rem}s` : `${m}m`;
|
|
3541
|
+
const h = Math.floor(m / 60);
|
|
3542
|
+
const remM = m % 60;
|
|
3543
|
+
return remM > 0 ? `${h}h ${remM}m` : `${h}h`;
|
|
3544
|
+
}
|
|
3545
|
+
function sectionBreak() {
|
|
3546
|
+
return ["", "", "---", "", ""];
|
|
3547
|
+
}
|
|
3548
|
+
function composeCycleDetail(session, cycle) {
|
|
3549
|
+
const isRunning = !cycle.completedAt;
|
|
3550
|
+
const dur = isRunning ? "running" : formatDurationMs(cycle.activeMs);
|
|
3551
|
+
const cycleAgents = session.agents.filter((a) => cycle.agentsSpawned.includes(a.id));
|
|
3552
|
+
const lines = [];
|
|
3553
|
+
lines.push(`# Cycle ${cycle.cycle}`);
|
|
3554
|
+
lines.push("");
|
|
3555
|
+
lines.push(`**Status:** ${isRunning ? "running" : "completed"} | **Duration:** ${dur}`);
|
|
3556
|
+
lines.push(`**Started:** ${formatTimestamp(cycle.timestamp)}`);
|
|
3557
|
+
if (cycle.completedAt) {
|
|
3558
|
+
lines.push(`**Completed:** ${formatTimestamp(cycle.completedAt)}`);
|
|
3559
|
+
}
|
|
3560
|
+
if (cycle.mode) {
|
|
3561
|
+
lines.push(`**Mode:** ${cycle.mode}`);
|
|
3562
|
+
}
|
|
3563
|
+
if (cycle.claudeSessionId) {
|
|
3564
|
+
lines.push(`**Claude Session:** ${cycle.claudeSessionId}`);
|
|
3565
|
+
}
|
|
3566
|
+
lines.push(...sectionBreak());
|
|
3567
|
+
lines.push("## Agents");
|
|
3568
|
+
lines.push("");
|
|
3569
|
+
if (cycleAgents.length === 0) {
|
|
3570
|
+
lines.push("_No agents spawned yet._");
|
|
3571
|
+
} else {
|
|
3572
|
+
for (const agent of cycleAgents) {
|
|
3573
|
+
const agentDur = formatDurationMs(agent.activeMs);
|
|
3574
|
+
lines.push(`### ${agent.id} \u2014 ${agent.name || agent.agentType || agent.id}`);
|
|
3575
|
+
lines.push(`- **Status:** ${agent.status} | **Duration:** ${agentDur}`);
|
|
3576
|
+
lines.push(`- **Type:** ${agent.agentType || "\u2014"}`);
|
|
3577
|
+
if (agent.killedReason) {
|
|
3578
|
+
lines.push(`- **Killed reason:** ${agent.killedReason}`);
|
|
3579
|
+
}
|
|
3580
|
+
lines.push("");
|
|
3581
|
+
lines.push("**Instruction:**");
|
|
3582
|
+
lines.push("");
|
|
3583
|
+
lines.push(agent.instruction);
|
|
3584
|
+
const latestReport = agent.reports.length > 0 ? agent.reports[agent.reports.length - 1] : null;
|
|
3585
|
+
if (latestReport) {
|
|
3586
|
+
lines.push("");
|
|
3587
|
+
lines.push(`**Latest report** (${latestReport.type}, ${formatTimestamp(latestReport.timestamp)}):**`);
|
|
3588
|
+
lines.push("");
|
|
3589
|
+
lines.push(latestReport.summary);
|
|
3590
|
+
}
|
|
3591
|
+
lines.push("");
|
|
3592
|
+
}
|
|
3593
|
+
}
|
|
3594
|
+
if (cycle.nextPrompt) {
|
|
3595
|
+
lines.push(...sectionBreak());
|
|
3596
|
+
lines.push("## Next Prompt");
|
|
3597
|
+
lines.push("");
|
|
3598
|
+
lines.push(cycle.nextPrompt.trim());
|
|
3599
|
+
lines.push("");
|
|
3600
|
+
}
|
|
3601
|
+
return lines.join("\n") + "\n";
|
|
3602
|
+
}
|
|
3603
|
+
function composeAgentDetail(agent, reportBlocks) {
|
|
3604
|
+
const dur = formatDurationMs(agent.activeMs);
|
|
3605
|
+
const lines = [];
|
|
3606
|
+
lines.push(`# ${agent.id} \u2014 ${agent.name || agent.agentType || agent.id}`);
|
|
3607
|
+
lines.push("");
|
|
3608
|
+
lines.push(
|
|
3609
|
+
`**Status:** ${agent.status} | **Duration:** ${dur} | **Type:** ${agent.agentType || "\u2014"}`
|
|
3610
|
+
);
|
|
3611
|
+
lines.push(`**Spawned:** ${formatTimestamp(agent.spawnedAt)}`);
|
|
3612
|
+
if (agent.completedAt) {
|
|
3613
|
+
lines.push(`**Completed:** ${formatTimestamp(agent.completedAt)}`);
|
|
3614
|
+
}
|
|
3615
|
+
if (agent.killedReason) {
|
|
3616
|
+
lines.push(`**Killed reason:** ${agent.killedReason}`);
|
|
3617
|
+
}
|
|
3618
|
+
if (agent.claudeSessionId) {
|
|
3619
|
+
lines.push(`**Claude Session:** ${agent.claudeSessionId}`);
|
|
3620
|
+
}
|
|
3621
|
+
lines.push(...sectionBreak());
|
|
3622
|
+
lines.push("## Instruction");
|
|
3623
|
+
lines.push("");
|
|
3624
|
+
lines.push(agent.instruction.trim());
|
|
3625
|
+
if (reportBlocks.length > 0) {
|
|
3626
|
+
lines.push(...sectionBreak());
|
|
3627
|
+
lines.push(`## Reports (${reportBlocks.length})`);
|
|
3628
|
+
for (const block of reportBlocks) {
|
|
3629
|
+
lines.push("");
|
|
3630
|
+
const badge = block.type === "final" ? "FINAL" : "UPDATE";
|
|
3631
|
+
lines.push(`### ${badge} \u2014 ${formatTimestamp(block.timestamp)}`);
|
|
3632
|
+
lines.push("");
|
|
3633
|
+
lines.push(block.content.trim());
|
|
3634
|
+
}
|
|
3635
|
+
} else if (agent.reports.length > 0) {
|
|
3636
|
+
lines.push(...sectionBreak());
|
|
3637
|
+
lines.push(`## Reports (${agent.reports.length})`);
|
|
3638
|
+
for (const report of agent.reports) {
|
|
3639
|
+
const badge = report.type === "final" ? "FINAL" : "UPDATE";
|
|
3640
|
+
lines.push("");
|
|
3641
|
+
lines.push(`### ${badge} \u2014 ${formatTimestamp(report.timestamp)}`);
|
|
3642
|
+
lines.push("");
|
|
3643
|
+
lines.push(report.summary);
|
|
3644
|
+
}
|
|
3645
|
+
}
|
|
3646
|
+
return lines.join("\n") + "\n";
|
|
3647
|
+
}
|
|
3648
|
+
function composeMessages(session) {
|
|
3649
|
+
const lines = [];
|
|
3650
|
+
lines.push("# Messages");
|
|
3651
|
+
lines.push("");
|
|
3652
|
+
lines.push(`**Session:** ${session.name ?? session.task.slice(0, 60)}`);
|
|
3653
|
+
lines.push(`**Total messages:** ${session.messages.length}`);
|
|
3654
|
+
if (session.messages.length === 0) {
|
|
3655
|
+
lines.push("");
|
|
3656
|
+
lines.push("_No messages yet._");
|
|
3657
|
+
return lines.join("\n") + "\n";
|
|
3658
|
+
}
|
|
3659
|
+
const sorted = [...session.messages].sort(
|
|
3660
|
+
(a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
|
|
3661
|
+
);
|
|
3662
|
+
for (const msg of sorted) {
|
|
3663
|
+
lines.push("");
|
|
3664
|
+
lines.push("---");
|
|
3665
|
+
lines.push("");
|
|
3666
|
+
const src = msg.source;
|
|
3667
|
+
let sourceLabel;
|
|
3668
|
+
if (src.type === "agent") {
|
|
3669
|
+
sourceLabel = src.agentId;
|
|
3670
|
+
} else if (src.type === "user") {
|
|
3671
|
+
sourceLabel = "You";
|
|
3672
|
+
} else {
|
|
3673
|
+
sourceLabel = src.detail ? `system (${src.detail})` : "system";
|
|
3674
|
+
}
|
|
3675
|
+
lines.push(`**From:** ${sourceLabel} | **Time:** ${formatTimestamp(msg.timestamp)}`);
|
|
3676
|
+
if (msg.summary && msg.summary !== msg.content) {
|
|
3677
|
+
lines.push(`**Summary:** ${msg.summary}`);
|
|
3678
|
+
}
|
|
3679
|
+
lines.push("");
|
|
3680
|
+
lines.push(msg.content.trim());
|
|
3681
|
+
}
|
|
3682
|
+
return lines.join("\n") + "\n";
|
|
3683
|
+
}
|
|
3684
|
+
function resolveNvimFile(state2, cursorNode, detailCtx, cwd2) {
|
|
3685
|
+
if (!cursorNode) return null;
|
|
3686
|
+
const sessionId = cursorNode.sessionId;
|
|
3687
|
+
if (!sessionId) return null;
|
|
3688
|
+
const session = detailCtx.session;
|
|
3689
|
+
switch (cursorNode.type) {
|
|
3690
|
+
case "session": {
|
|
3691
|
+
if (!session) return null;
|
|
3692
|
+
const files = [];
|
|
3693
|
+
const gp = goalPath(cwd2, sessionId);
|
|
3694
|
+
if (existsSync2(gp)) files.push({ path: gp, readonly: false });
|
|
3695
|
+
const rp = roadmapPath(cwd2, sessionId);
|
|
3696
|
+
if (existsSync2(rp)) files.push({ path: rp, readonly: false });
|
|
3697
|
+
const sp = strategyPath(cwd2, sessionId);
|
|
3698
|
+
if (existsSync2(sp)) files.push({ path: sp, readonly: false });
|
|
3699
|
+
if (files.length === 0) return null;
|
|
3700
|
+
return { files };
|
|
3701
|
+
}
|
|
3702
|
+
case "cycle": {
|
|
3703
|
+
if (!session) return null;
|
|
3704
|
+
const cycle = session.orchestratorCycles.find(
|
|
3705
|
+
(c) => c.cycle === cursorNode.cycleNumber
|
|
3706
|
+
);
|
|
3707
|
+
if (!cycle) return null;
|
|
3708
|
+
const dir = ensureTuiDir(cwd2, sessionId);
|
|
3709
|
+
const filePath = join4(dir, `cycle-${cursorNode.cycleNumber}.md`);
|
|
3710
|
+
atomicWrite(filePath, composeCycleDetail(session, cycle));
|
|
3711
|
+
return { files: [{ path: filePath, readonly: true }] };
|
|
3712
|
+
}
|
|
3713
|
+
case "agent": {
|
|
3714
|
+
if (!session) return null;
|
|
3715
|
+
const agent = session.agents.find((a) => a.id === cursorNode.agentId);
|
|
3716
|
+
if (!agent) return null;
|
|
3717
|
+
const dir = ensureTuiDir(cwd2, sessionId);
|
|
3718
|
+
const filePath = join4(dir, `${agent.id}.md`);
|
|
3719
|
+
atomicWrite(filePath, composeAgentDetail(agent, state2.cachedReportBlocks.get(agent.id) ?? []));
|
|
3720
|
+
return { files: [{ path: filePath, readonly: true }] };
|
|
3721
|
+
}
|
|
3722
|
+
case "report": {
|
|
3723
|
+
const agent = session?.agents.find((a) => a.id === cursorNode.agentId);
|
|
3724
|
+
if (agent && agent.reports.length > 0) {
|
|
3725
|
+
const report = agent.reports[cursorNode.reportIndex];
|
|
3726
|
+
if (report?.filePath && existsSync2(report.filePath)) {
|
|
3727
|
+
return { files: [{ path: report.filePath, readonly: true }] };
|
|
3728
|
+
}
|
|
3729
|
+
}
|
|
3730
|
+
return null;
|
|
3731
|
+
}
|
|
3732
|
+
case "context-file": {
|
|
3733
|
+
if (cursorNode.filePath && existsSync2(cursorNode.filePath)) {
|
|
3734
|
+
return { files: [{ path: cursorNode.filePath, readonly: false }] };
|
|
3735
|
+
}
|
|
3736
|
+
return null;
|
|
3737
|
+
}
|
|
3738
|
+
case "messages":
|
|
3739
|
+
case "message": {
|
|
3740
|
+
if (!session || session.messages.length === 0) return null;
|
|
3741
|
+
const dir = ensureTuiDir(cwd2, sessionId);
|
|
3742
|
+
const filePath = join4(dir, "messages.md");
|
|
3743
|
+
atomicWrite(filePath, composeMessages(session));
|
|
3744
|
+
return { files: [{ path: filePath, readonly: true }] };
|
|
3745
|
+
}
|
|
3746
|
+
case "context": {
|
|
3747
|
+
return null;
|
|
3748
|
+
}
|
|
3749
|
+
default:
|
|
3750
|
+
return null;
|
|
3751
|
+
}
|
|
3752
|
+
}
|
|
3753
|
+
|
|
2887
3754
|
// src/tui/app.ts
|
|
2888
3755
|
var latestNodes = [];
|
|
2889
3756
|
var cachedContextFilePath = null;
|
|
2890
3757
|
var cachedContextFileContent = null;
|
|
2891
3758
|
var prevFrame = [];
|
|
3759
|
+
var prevTreeInputs = "";
|
|
3760
|
+
var prevBottomInputs = "";
|
|
3761
|
+
var prevOverlayMode = "";
|
|
3762
|
+
var cachedTreeRows = [];
|
|
2892
3763
|
var cachedLogSessionId = null;
|
|
2893
3764
|
var cachedLogFiles = /* @__PURE__ */ new Map();
|
|
3765
|
+
var STATUS_ROW_COUNT = 2;
|
|
3766
|
+
function buildStatusRows(cursorNode, session, state2) {
|
|
3767
|
+
if (!cursorNode || !session) {
|
|
3768
|
+
return [ansiDim(" No session selected"), ""];
|
|
3769
|
+
}
|
|
3770
|
+
const dur = formatDuration(session.createdAt, session.completedAt);
|
|
3771
|
+
const indicator = statusIndicator(session.status);
|
|
3772
|
+
const sColor = statusColor(session.status);
|
|
3773
|
+
const title = truncate(session.name ?? session.task, 40);
|
|
3774
|
+
switch (cursorNode.type) {
|
|
3775
|
+
case "session": {
|
|
3776
|
+
return [
|
|
3777
|
+
" " + ansiColor(indicator, sColor, true) + " " + ansiColor(title, "white", true),
|
|
3778
|
+
" " + ansiDim(`${session.status} \xB7 ${session.orchestratorCycles.length} cycles \xB7 ${session.agents.length} agents \xB7 ${dur}`)
|
|
3779
|
+
];
|
|
3780
|
+
}
|
|
3781
|
+
case "cycle": {
|
|
3782
|
+
const cycle = session.orchestratorCycles.find((c) => c.cycle === cursorNode.cycleNumber);
|
|
3783
|
+
if (!cycle) return [" " + ansiColor(title, "white", true), ""];
|
|
3784
|
+
const cDur = cycle.completedAt ? formatDuration(cycle.timestamp, cycle.completedAt) : "running";
|
|
3785
|
+
const cStatus = cycle.completedAt ? "completed" : "running";
|
|
3786
|
+
return [
|
|
3787
|
+
" " + ansiColor(indicator, sColor, true) + " " + ansiColor(title, "white", true) + ansiDim(` \xB7 Cycle ${cycle.cycle}`),
|
|
3788
|
+
" " + ansiDim(`${cStatus} \xB7 ${cDur} \xB7 ${cycle.agentsSpawned.length} agents`)
|
|
3789
|
+
];
|
|
3790
|
+
}
|
|
3791
|
+
case "agent":
|
|
3792
|
+
case "report": {
|
|
3793
|
+
const agentId = cursorNode.type === "agent" ? cursorNode.agentId : cursorNode.agentId;
|
|
3794
|
+
const agent = session.agents.find((a) => a.id === agentId);
|
|
3795
|
+
if (!agent) return [" " + ansiColor(title, "white", true), ""];
|
|
3796
|
+
const aIcon = agentStatusIcon(agent.status);
|
|
3797
|
+
const aDur = formatDuration(agent.spawnedAt, agent.completedAt);
|
|
3798
|
+
const aName = agentDisplayName(agent);
|
|
3799
|
+
return [
|
|
3800
|
+
" " + ansiColor(aIcon, statusColor(agent.status === "running" ? "active" : agent.status), true) + " " + ansiColor(`${agent.id} \xB7 ${aName}`, "white", true),
|
|
3801
|
+
" " + ansiDim(`${agent.status} \xB7 ${agent.agentType || "\u2014"} \xB7 ${aDur}`)
|
|
3802
|
+
];
|
|
3803
|
+
}
|
|
3804
|
+
case "context-file": {
|
|
3805
|
+
const name = cursorNode.filePath.split("/").pop() ?? cursorNode.filePath;
|
|
3806
|
+
return [
|
|
3807
|
+
" " + ansiColor("\u229E", "white") + " " + ansiColor(name, "white", true),
|
|
3808
|
+
" " + ansiDim(`context file \xB7 ${session.status}`)
|
|
3809
|
+
];
|
|
3810
|
+
}
|
|
3811
|
+
case "messages":
|
|
3812
|
+
case "message": {
|
|
3813
|
+
return [
|
|
3814
|
+
" " + ansiColor(indicator, sColor, true) + " " + ansiColor(title, "white", true),
|
|
3815
|
+
" " + ansiDim(`${session.messages.length} messages`)
|
|
3816
|
+
];
|
|
3817
|
+
}
|
|
3818
|
+
default: {
|
|
3819
|
+
return [
|
|
3820
|
+
" " + ansiColor(indicator, sColor, true) + " " + ansiColor(title, "white", true),
|
|
3821
|
+
" " + ansiDim(`${session.status} \xB7 ${dur}`)
|
|
3822
|
+
];
|
|
3823
|
+
}
|
|
3824
|
+
}
|
|
3825
|
+
}
|
|
2894
3826
|
function getAgentForNode(node, agents) {
|
|
2895
3827
|
if (!node) return null;
|
|
2896
3828
|
if (node.type === "agent" || node.type === "report") {
|
|
@@ -2900,7 +3832,18 @@ function getAgentForNode(node, agents) {
|
|
|
2900
3832
|
}
|
|
2901
3833
|
function startApp(state2, cleanup2) {
|
|
2902
3834
|
const config = loadConfig(state2.cwd);
|
|
3835
|
+
const treeWidth = 36;
|
|
3836
|
+
const initialDetailW = state2.cols - treeWidth - 4;
|
|
3837
|
+
const initialDetailH = state2.rows - 3 - 2 - STATUS_ROW_COUNT - 1;
|
|
3838
|
+
const bridge = new NvimBridge(
|
|
3839
|
+
Math.max(1, initialDetailW),
|
|
3840
|
+
Math.max(1, initialDetailH),
|
|
3841
|
+
requestRender
|
|
3842
|
+
);
|
|
3843
|
+
state2.nvimBridge = bridge.available ? bridge : null;
|
|
3844
|
+
state2.nvimEnabled = bridge.available;
|
|
2903
3845
|
let prevSelectedSessionId = void 0;
|
|
3846
|
+
let debouncedPollTimer = null;
|
|
2904
3847
|
async function poll() {
|
|
2905
3848
|
try {
|
|
2906
3849
|
let selectedSession = null;
|
|
@@ -2918,13 +3861,10 @@ function startApp(state2, cleanup2) {
|
|
|
2918
3861
|
statusPromise ?? Promise.resolve(null)
|
|
2919
3862
|
]);
|
|
2920
3863
|
const sessions = listRes.ok ? listRes.data?.sessions ?? [] : [];
|
|
3864
|
+
const aliveWindows = listAllWindowIds();
|
|
2921
3865
|
for (const s of sessions) {
|
|
2922
3866
|
if (s.status !== "completed" && s.tmuxWindowId) {
|
|
2923
|
-
|
|
2924
|
-
s.windowAlive = windowExists(s.tmuxWindowId);
|
|
2925
|
-
} catch {
|
|
2926
|
-
s.windowAlive = false;
|
|
2927
|
-
}
|
|
3867
|
+
s.windowAlive = aliveWindows.has(s.tmuxWindowId);
|
|
2928
3868
|
}
|
|
2929
3869
|
}
|
|
2930
3870
|
if (state2.selectedSessionId) {
|
|
@@ -2937,28 +3877,28 @@ function startApp(state2, cleanup2) {
|
|
|
2937
3877
|
}
|
|
2938
3878
|
try {
|
|
2939
3879
|
const pp = roadmapPath(state2.cwd, state2.selectedSessionId);
|
|
2940
|
-
if (
|
|
3880
|
+
if (existsSync3(pp)) {
|
|
2941
3881
|
planContent = readFileSync2(pp, "utf-8");
|
|
2942
3882
|
}
|
|
2943
3883
|
} catch {
|
|
2944
3884
|
}
|
|
2945
3885
|
try {
|
|
2946
3886
|
const gp = goalPath(state2.cwd, state2.selectedSessionId);
|
|
2947
|
-
if (
|
|
3887
|
+
if (existsSync3(gp)) {
|
|
2948
3888
|
goalContent = readFileSync2(gp, "utf-8");
|
|
2949
3889
|
}
|
|
2950
3890
|
} catch {
|
|
2951
3891
|
}
|
|
2952
3892
|
try {
|
|
2953
3893
|
const sp = strategyPath(state2.cwd, state2.selectedSessionId);
|
|
2954
|
-
if (
|
|
3894
|
+
if (existsSync3(sp)) {
|
|
2955
3895
|
strategyContent = readFileSync2(sp, "utf-8");
|
|
2956
3896
|
}
|
|
2957
3897
|
} catch {
|
|
2958
3898
|
}
|
|
2959
3899
|
try {
|
|
2960
3900
|
const ld = logsDir(state2.cwd, state2.selectedSessionId);
|
|
2961
|
-
if (
|
|
3901
|
+
if (existsSync3(ld)) {
|
|
2962
3902
|
if (state2.selectedSessionId !== cachedLogSessionId) {
|
|
2963
3903
|
cachedLogFiles = /* @__PURE__ */ new Map();
|
|
2964
3904
|
cachedLogSessionId = state2.selectedSessionId;
|
|
@@ -2969,7 +3909,7 @@ function startApp(state2, cleanup2) {
|
|
|
2969
3909
|
if (!fileSet.has(key)) cachedLogFiles.delete(key);
|
|
2970
3910
|
}
|
|
2971
3911
|
for (const f of files) {
|
|
2972
|
-
const filePath =
|
|
3912
|
+
const filePath = join5(ld, f);
|
|
2973
3913
|
const mtime = statSync(filePath).mtimeMs;
|
|
2974
3914
|
const cached = cachedLogFiles.get(f);
|
|
2975
3915
|
if (!cached || cached.mtime !== mtime) {
|
|
@@ -2989,7 +3929,7 @@ function startApp(state2, cleanup2) {
|
|
|
2989
3929
|
}
|
|
2990
3930
|
try {
|
|
2991
3931
|
const cd = contextDir(state2.cwd, state2.selectedSessionId);
|
|
2992
|
-
if (
|
|
3932
|
+
if (existsSync3(cd)) {
|
|
2993
3933
|
contextFiles = readdirSync(cd).filter((f) => !f.startsWith(".")).sort();
|
|
2994
3934
|
}
|
|
2995
3935
|
} catch {
|
|
@@ -3011,6 +3951,9 @@ function startApp(state2, cleanup2) {
|
|
|
3011
3951
|
state2.paneAlive = paneAlive;
|
|
3012
3952
|
state2.contextFiles = contextFiles;
|
|
3013
3953
|
state2.error = null;
|
|
3954
|
+
if (state2.nvimEnabled && state2.nvimBridge?.ready && state2.prevNvimFile) {
|
|
3955
|
+
state2.nvimBridge.checktime();
|
|
3956
|
+
}
|
|
3014
3957
|
requestRender();
|
|
3015
3958
|
} catch (err) {
|
|
3016
3959
|
state2.error = err.message;
|
|
@@ -3027,17 +3970,17 @@ function startApp(state2, cleanup2) {
|
|
|
3027
3970
|
writeCenter(buf, Math.floor(state2.rows / 2), "Terminal too small \u2014 resize to continue");
|
|
3028
3971
|
const out2 = flushFrame(buf.lines, prevFrame);
|
|
3029
3972
|
writeToStdout(out2);
|
|
3030
|
-
prevFrame =
|
|
3973
|
+
prevFrame = buf.lines;
|
|
3031
3974
|
return;
|
|
3032
3975
|
}
|
|
3033
|
-
const
|
|
3034
|
-
const remaining = state2.cols -
|
|
3035
|
-
const detailWidth = state2.
|
|
3036
|
-
const logsWidth = state2.
|
|
3976
|
+
const treeWidth2 = 36;
|
|
3977
|
+
const remaining = state2.cols - treeWidth2;
|
|
3978
|
+
const detailWidth = state2.showCombinedView ? Math.floor(remaining * 0.6) : remaining;
|
|
3979
|
+
const logsWidth = state2.showCombinedView ? remaining - detailWidth : 0;
|
|
3037
3980
|
const contentHeight = state2.rows - 3;
|
|
3038
|
-
const treeRect = { x: 0, y: 0, w:
|
|
3039
|
-
const detailRect = { x:
|
|
3040
|
-
const logsRect = state2.
|
|
3981
|
+
const treeRect = { x: 0, y: 0, w: treeWidth2, h: contentHeight };
|
|
3982
|
+
const detailRect = { x: treeWidth2, y: 0, w: detailWidth, h: contentHeight };
|
|
3983
|
+
const logsRect = state2.showCombinedView ? { x: treeWidth2 + detailWidth, y: 0, w: logsWidth, h: contentHeight } : null;
|
|
3041
3984
|
const bottomY = contentHeight;
|
|
3042
3985
|
const filteredSessions = state2.searchFilter ? state2.sessions.filter((s) => {
|
|
3043
3986
|
const q = state2.searchFilter.toLowerCase();
|
|
@@ -3055,6 +3998,7 @@ function startApp(state2, cleanup2) {
|
|
|
3055
3998
|
state2.cwd,
|
|
3056
3999
|
state2.contextFiles
|
|
3057
4000
|
);
|
|
4001
|
+
precomputePrefixes(nodes);
|
|
3058
4002
|
state2.cachedTreeNodes = nodes;
|
|
3059
4003
|
state2.treeCacheKey = cacheKey;
|
|
3060
4004
|
}
|
|
@@ -3067,11 +4011,20 @@ function startApp(state2, cleanup2) {
|
|
|
3067
4011
|
state2.selectedSessionId = newSessionId;
|
|
3068
4012
|
state2.detailScroll.reset();
|
|
3069
4013
|
state2.logsScroll.reset();
|
|
4014
|
+
state2.cachedDetailLines = null;
|
|
4015
|
+
state2.detailCacheKey = "";
|
|
4016
|
+
state2.prevNvimFile = null;
|
|
4017
|
+
state2.cachedLogsLines = null;
|
|
4018
|
+
state2.logsCacheKey = "";
|
|
3070
4019
|
}
|
|
3071
4020
|
if (state2.selectedSessionId !== prevSelectedSessionId) {
|
|
3072
4021
|
prevSelectedSessionId = state2.selectedSessionId;
|
|
4022
|
+
if (debouncedPollTimer !== null) clearTimeout(debouncedPollTimer);
|
|
3073
4023
|
if (state2.selectedSessionId !== null) {
|
|
3074
|
-
|
|
4024
|
+
debouncedPollTimer = setTimeout(() => {
|
|
4025
|
+
debouncedPollTimer = null;
|
|
4026
|
+
void poll();
|
|
4027
|
+
}, 80);
|
|
3075
4028
|
}
|
|
3076
4029
|
}
|
|
3077
4030
|
autoExpandCycle(state2);
|
|
@@ -3085,7 +4038,7 @@ function startApp(state2, cleanup2) {
|
|
|
3085
4038
|
if (cursorNode.filePath !== cachedContextFilePath) {
|
|
3086
4039
|
cachedContextFilePath = cursorNode.filePath;
|
|
3087
4040
|
try {
|
|
3088
|
-
if (
|
|
4041
|
+
if (existsSync3(cursorNode.filePath)) {
|
|
3089
4042
|
cachedContextFileContent = readFileSync2(cursorNode.filePath, "utf-8");
|
|
3090
4043
|
} else {
|
|
3091
4044
|
cachedContextFileContent = null;
|
|
@@ -3099,13 +4052,37 @@ function startApp(state2, cleanup2) {
|
|
|
3099
4052
|
cachedContextFilePath = null;
|
|
3100
4053
|
cachedContextFileContent = null;
|
|
3101
4054
|
}
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
4055
|
+
const treeFocused = state2.mode === "navigate" && state2.focusPane === "tree";
|
|
4056
|
+
const treeInputs = `${state2.treeCacheKey}:${state2.cursorIndex}:${treeFocused}`;
|
|
4057
|
+
const bottomInputs = `${state2.notification}:${state2.error}:${state2.mode}:${state2.inputText}:${state2.inputCursorPos}:${cursorNode?.type}`;
|
|
4058
|
+
const overlayMode = state2.mode === "leader" || state2.mode === "copy-menu" || state2.mode === "help" ? state2.mode : "";
|
|
4059
|
+
const hasPrev = prevFrame.length === buf.height;
|
|
4060
|
+
const treeDirty = !hasPrev || treeInputs !== prevTreeInputs;
|
|
4061
|
+
const bottomDirty = !hasPrev || bottomInputs !== prevBottomInputs;
|
|
4062
|
+
const overlayDirty = !hasPrev || overlayMode !== prevOverlayMode;
|
|
4063
|
+
prevTreeInputs = treeInputs;
|
|
4064
|
+
prevBottomInputs = bottomInputs;
|
|
4065
|
+
prevOverlayMode = overlayMode;
|
|
4066
|
+
let treeRows;
|
|
4067
|
+
if (treeDirty) {
|
|
4068
|
+
const treeBlank = " ".repeat(treeWidth2);
|
|
4069
|
+
const treeBuf = {
|
|
4070
|
+
lines: Array.from({ length: contentHeight }, () => treeBlank),
|
|
4071
|
+
width: treeWidth2,
|
|
4072
|
+
height: contentHeight
|
|
4073
|
+
};
|
|
4074
|
+
renderTreePanel(
|
|
4075
|
+
treeBuf,
|
|
4076
|
+
{ x: 0, y: 0, w: treeWidth2, h: contentHeight },
|
|
4077
|
+
nodes,
|
|
4078
|
+
state2.cursorIndex,
|
|
4079
|
+
treeFocused
|
|
4080
|
+
);
|
|
4081
|
+
cachedTreeRows = treeBuf.lines;
|
|
4082
|
+
treeRows = treeBuf.lines;
|
|
4083
|
+
} else {
|
|
4084
|
+
treeRows = cachedTreeRows;
|
|
4085
|
+
}
|
|
3109
4086
|
const detailCtx = {
|
|
3110
4087
|
nodes,
|
|
3111
4088
|
session: state2.selectedSession,
|
|
@@ -3114,19 +4091,55 @@ function startApp(state2, cleanup2) {
|
|
|
3114
4091
|
detailReportBlocks,
|
|
3115
4092
|
contextFileContent
|
|
3116
4093
|
};
|
|
3117
|
-
|
|
3118
|
-
if (
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
4094
|
+
let detailRows;
|
|
4095
|
+
if (state2.nvimEnabled && state2.nvimBridge?.ready) {
|
|
4096
|
+
const result = resolveNvimFile(state2, cursorNode, detailCtx, state2.cwd);
|
|
4097
|
+
const resultKey = result ? result.files.map((f) => f.path).join("|") : null;
|
|
4098
|
+
if (resultKey && resultKey !== state2.prevNvimFile) {
|
|
4099
|
+
state2.nvimBridge.openTabFiles(result.files);
|
|
4100
|
+
state2.prevNvimFile = resultKey;
|
|
4101
|
+
state2.nvimEditable = result.files.some((f) => !f.readonly);
|
|
4102
|
+
} else if (!resultKey) {
|
|
4103
|
+
state2.prevNvimFile = null;
|
|
4104
|
+
state2.nvimEditable = false;
|
|
4105
|
+
}
|
|
4106
|
+
const statusRows = buildStatusRows(cursorNode, state2.selectedSession, state2);
|
|
4107
|
+
detailRows = renderNvimDetailRows(detailRect, state2.nvimBridge, state2.focusPane === "detail", state2.nvimEditable, statusRows);
|
|
4108
|
+
} else {
|
|
4109
|
+
detailRows = renderDetailRows(detailRect, state2, detailCtx);
|
|
4110
|
+
}
|
|
4111
|
+
const logsRows = logsRect ? renderLogsRows(logsRect, state2) : null;
|
|
4112
|
+
for (let i = 0; i < contentHeight; i++) {
|
|
4113
|
+
if (logsRows) {
|
|
4114
|
+
buf.lines[i] = treeRows[i] + detailRows[i] + logsRows[i];
|
|
4115
|
+
} else {
|
|
4116
|
+
buf.lines[i] = treeRows[i] + detailRows[i];
|
|
4117
|
+
}
|
|
4118
|
+
}
|
|
4119
|
+
if (bottomDirty || overlayDirty) {
|
|
4120
|
+
renderNotificationRow(buf, bottomY, state2.notification, state2.error);
|
|
4121
|
+
renderInputBar(buf, bottomY + 1, state2);
|
|
4122
|
+
renderStatusLine(buf, bottomY + 2, state2, cursorNode?.type);
|
|
4123
|
+
} else {
|
|
4124
|
+
copyRows(buf, prevFrame, bottomY, 3);
|
|
4125
|
+
}
|
|
4126
|
+
if (overlayMode) {
|
|
4127
|
+
if (state2.mode === "leader") renderLeaderOverlay(buf, state2.rows, state2.cols);
|
|
4128
|
+
if (state2.mode === "copy-menu") renderCopyMenuOverlay(buf, state2.rows, state2.cols);
|
|
4129
|
+
if (state2.mode === "help") renderHelpOverlay(buf, state2.rows, state2.cols);
|
|
4130
|
+
}
|
|
4131
|
+
let cursorSuffix;
|
|
4132
|
+
if (state2.focusPane === "detail" && state2.nvimBridge?.ready) {
|
|
4133
|
+
const cursor = state2.nvimBridge.getCursorPos();
|
|
4134
|
+
const absX = detailRect.x + 2 + cursor.x;
|
|
4135
|
+
const absY = detailRect.y + 1 + STATUS_ROW_COUNT + 1 + cursor.y;
|
|
4136
|
+
cursorSuffix = `\x1B[${state2.nvimBridge.cursorStyle} q\x1B[?25h\x1B[${absY + 1};${absX + 1}H`;
|
|
4137
|
+
} else {
|
|
4138
|
+
cursorSuffix = "\x1B[0 q\x1B[?25l";
|
|
4139
|
+
}
|
|
4140
|
+
const out = flushFrame(buf.lines, prevFrame, cursorSuffix);
|
|
3128
4141
|
writeToStdout(out);
|
|
3129
|
-
prevFrame =
|
|
4142
|
+
prevFrame = buf.lines;
|
|
3130
4143
|
}
|
|
3131
4144
|
const inputActions = {
|
|
3132
4145
|
getNodes: () => latestNodes,
|
|
@@ -3180,6 +4193,11 @@ function startApp(state2, cleanup2) {
|
|
|
3180
4193
|
state2.rows = typeof stdoutRows === "number" && stdoutRows > 0 ? stdoutRows : 24;
|
|
3181
4194
|
state2.cols = typeof stdoutCols === "number" && stdoutCols > 0 ? stdoutCols : 80;
|
|
3182
4195
|
prevFrame = [];
|
|
4196
|
+
if (state2.nvimBridge) {
|
|
4197
|
+
const detailW = state2.cols - 36;
|
|
4198
|
+
const contentH = state2.rows - 3;
|
|
4199
|
+
state2.nvimBridge.resize(Math.max(1, detailW - 4), Math.max(1, contentH - 2 - STATUS_ROW_COUNT - 1));
|
|
4200
|
+
}
|
|
3183
4201
|
requestRender();
|
|
3184
4202
|
});
|
|
3185
4203
|
void poll();
|
|
@@ -3187,10 +4205,12 @@ function startApp(state2, cleanup2) {
|
|
|
3187
4205
|
const origCleanup = inputActions.cleanup;
|
|
3188
4206
|
inputActions.cleanup = () => {
|
|
3189
4207
|
clearInterval(pollInterval);
|
|
4208
|
+
if (debouncedPollTimer !== null) clearTimeout(debouncedPollTimer);
|
|
3190
4209
|
stopKeypress();
|
|
3191
4210
|
stopResize();
|
|
3192
4211
|
state2.detailScroll.destroy();
|
|
3193
4212
|
state2.logsScroll.destroy();
|
|
4213
|
+
state2.nvimBridge?.destroy();
|
|
3194
4214
|
origCleanup();
|
|
3195
4215
|
};
|
|
3196
4216
|
requestRender();
|