sisyphi 1.1.10 → 1.1.12
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 +2 -2
- 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 +11 -3
- package/dist/templates/orchestrator-completion.md +86 -0
- package/dist/templates/orchestrator-validation.md +8 -2
- package/dist/tui.js +815 -60
- package/dist/tui.js.map +1 -1
- package/package.json +4 -1
- package/templates/CLAUDE.md +2 -0
- package/templates/orchestrator-base.md +11 -3
- 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,
|
|
@@ -339,6 +347,11 @@ function createAppState(cwd2) {
|
|
|
339
347
|
cachedLogsLines: null,
|
|
340
348
|
logsCacheKey: "",
|
|
341
349
|
logsRenderedCache: { lines: [], ansi: [] },
|
|
350
|
+
nvimBridge: null,
|
|
351
|
+
nvimEnabled: true,
|
|
352
|
+
prevNvimFile: null,
|
|
353
|
+
nvimEditable: false,
|
|
354
|
+
nvimOpenTabs: /* @__PURE__ */ new Map(),
|
|
342
355
|
cwd: cwd2
|
|
343
356
|
};
|
|
344
357
|
}
|
|
@@ -402,8 +415,8 @@ function autoExpandCycle(state2) {
|
|
|
402
415
|
}
|
|
403
416
|
|
|
404
417
|
// src/tui/app.ts
|
|
405
|
-
import { readFileSync as readFileSync2, existsSync as
|
|
406
|
-
import { join as
|
|
418
|
+
import { readFileSync as readFileSync2, existsSync as existsSync3, readdirSync, statSync } from "fs";
|
|
419
|
+
import { join as join5 } from "path";
|
|
407
420
|
|
|
408
421
|
// src/tui/lib/tree.ts
|
|
409
422
|
import { join } from "path";
|
|
@@ -791,6 +804,21 @@ function findParentIndex(nodes, index) {
|
|
|
791
804
|
}
|
|
792
805
|
|
|
793
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
|
+
}
|
|
794
822
|
function handleCancel(state2) {
|
|
795
823
|
state2.mode = "navigate";
|
|
796
824
|
state2.targetAgentId = null;
|
|
@@ -1291,10 +1319,14 @@ function handleNavigateKey(input, key, state2, actions) {
|
|
|
1291
1319
|
if (key.leftArrow || input === "h") {
|
|
1292
1320
|
if (state2.focusPane === "logs") {
|
|
1293
1321
|
state2.focusPane = "detail";
|
|
1322
|
+
if (state2.nvimEnabled && state2.nvimBridge?.ready) {
|
|
1323
|
+
activateNvimBypass(state2);
|
|
1324
|
+
}
|
|
1294
1325
|
requestRender();
|
|
1295
1326
|
return;
|
|
1296
1327
|
}
|
|
1297
1328
|
if (state2.focusPane === "detail") {
|
|
1329
|
+
deactivateNvimBypass();
|
|
1298
1330
|
state2.focusPane = "tree";
|
|
1299
1331
|
requestRender();
|
|
1300
1332
|
return;
|
|
@@ -1333,8 +1365,12 @@ function handleNavigateKey(input, key, state2, actions) {
|
|
|
1333
1365
|
if (key.tab) {
|
|
1334
1366
|
if (state2.focusPane === "tree") {
|
|
1335
1367
|
state2.focusPane = "detail";
|
|
1368
|
+
if (state2.nvimEnabled && state2.nvimBridge?.ready) {
|
|
1369
|
+
activateNvimBypass(state2);
|
|
1370
|
+
}
|
|
1336
1371
|
} else if (state2.focusPane === "detail") {
|
|
1337
|
-
|
|
1372
|
+
deactivateNvimBypass();
|
|
1373
|
+
state2.focusPane = state2.showCombinedView ? "logs" : "tree";
|
|
1338
1374
|
} else {
|
|
1339
1375
|
state2.focusPane = "tree";
|
|
1340
1376
|
}
|
|
@@ -1577,15 +1613,6 @@ function handleNavigateKey(input, key, state2, actions) {
|
|
|
1577
1613
|
}
|
|
1578
1614
|
return;
|
|
1579
1615
|
}
|
|
1580
|
-
if (input === "s") {
|
|
1581
|
-
if (!state2.strategyContent) {
|
|
1582
|
-
notify(state2, "No strategy for this session");
|
|
1583
|
-
return;
|
|
1584
|
-
}
|
|
1585
|
-
state2.showStrategy = !state2.showStrategy;
|
|
1586
|
-
requestRender();
|
|
1587
|
-
return;
|
|
1588
|
-
}
|
|
1589
1616
|
if (input === "S") {
|
|
1590
1617
|
if (!state2.selectedSessionId) {
|
|
1591
1618
|
notify(state2, "No session selected");
|
|
@@ -1601,11 +1628,11 @@ function handleNavigateKey(input, key, state2, actions) {
|
|
|
1601
1628
|
return;
|
|
1602
1629
|
}
|
|
1603
1630
|
if (input === "t") {
|
|
1604
|
-
if (state2.
|
|
1631
|
+
if (state2.showCombinedView) {
|
|
1605
1632
|
if (state2.focusPane === "logs") state2.focusPane = "detail";
|
|
1606
1633
|
state2.logsScroll.reset();
|
|
1607
1634
|
}
|
|
1608
|
-
state2.
|
|
1635
|
+
state2.showCombinedView = !state2.showCombinedView;
|
|
1609
1636
|
requestRender();
|
|
1610
1637
|
return;
|
|
1611
1638
|
}
|
|
@@ -1673,7 +1700,7 @@ function copyRows(buf, src, startRow, count) {
|
|
|
1673
1700
|
buf.lines[startRow + i] = src[startRow + i];
|
|
1674
1701
|
}
|
|
1675
1702
|
}
|
|
1676
|
-
function flushFrame(frame, prevFrame2) {
|
|
1703
|
+
function flushFrame(frame, prevFrame2, suffix) {
|
|
1677
1704
|
let out = "\x1B[?2026h";
|
|
1678
1705
|
for (let i = 0; i < frame.length; i++) {
|
|
1679
1706
|
if (frame[i] !== prevFrame2[i]) {
|
|
@@ -1682,6 +1709,7 @@ function flushFrame(frame, prevFrame2) {
|
|
|
1682
1709
|
out += frame[i];
|
|
1683
1710
|
}
|
|
1684
1711
|
}
|
|
1712
|
+
if (suffix) out += suffix;
|
|
1685
1713
|
out += "\x1B[?2026l";
|
|
1686
1714
|
return out;
|
|
1687
1715
|
}
|
|
@@ -2359,7 +2387,7 @@ function buildPlanLines(content, maxLines, width) {
|
|
|
2359
2387
|
}
|
|
2360
2388
|
return lines;
|
|
2361
2389
|
}
|
|
2362
|
-
function buildSessionLines(session, planContent, goalContent, width, paneAlive, strategyContent = ""
|
|
2390
|
+
function buildSessionLines(session, planContent, goalContent, width, paneAlive, strategyContent = "") {
|
|
2363
2391
|
const lines = [];
|
|
2364
2392
|
const contentWidth = width - 4;
|
|
2365
2393
|
const agents = session.agents;
|
|
@@ -2400,12 +2428,8 @@ function buildSessionLines(session, planContent, goalContent, width, paneAlive,
|
|
|
2400
2428
|
]);
|
|
2401
2429
|
}
|
|
2402
2430
|
lines.push(singleLine(" "));
|
|
2403
|
-
if (
|
|
2404
|
-
|
|
2405
|
-
lines.push([
|
|
2406
|
-
seg(" \u258E \u25C8 STRATEGY", { color: "yellow", bold: true }),
|
|
2407
|
-
seg(stratHint, { dim: true })
|
|
2408
|
-
]);
|
|
2431
|
+
if (strategyContent) {
|
|
2432
|
+
lines.push([seg(" \u258E \u25C8 STRATEGY", { color: "yellow", bold: true })]);
|
|
2409
2433
|
const stratLines = buildPlanLines(strategyContent, 99999, width);
|
|
2410
2434
|
if (stratLines.length === 0) {
|
|
2411
2435
|
lines.push(singleLine(" (empty)", { dim: true, italic: true }));
|
|
@@ -2415,11 +2439,7 @@ function buildSessionLines(session, planContent, goalContent, width, paneAlive,
|
|
|
2415
2439
|
}
|
|
2416
2440
|
}
|
|
2417
2441
|
} else {
|
|
2418
|
-
|
|
2419
|
-
lines.push([
|
|
2420
|
-
seg(" \u258E \u25C8 PLAN", { color: "yellow", bold: true }),
|
|
2421
|
-
seg(toggleHint, { dim: true })
|
|
2422
|
-
]);
|
|
2442
|
+
lines.push([seg(" \u258E \u25C8 PLAN", { color: "yellow", bold: true })]);
|
|
2423
2443
|
const planLines = buildPlanLines(planContent, 99999, width);
|
|
2424
2444
|
if (planLines.length === 0) {
|
|
2425
2445
|
lines.push(singleLine(" orchestrator will create one", { dim: true, italic: true }));
|
|
@@ -2758,7 +2778,6 @@ function renderDetailRows(rect, state2, detailCtx) {
|
|
|
2758
2778
|
cursorNode.type,
|
|
2759
2779
|
state2.mode,
|
|
2760
2780
|
state2.targetAgentId,
|
|
2761
|
-
state2.showStrategy,
|
|
2762
2781
|
rect.w,
|
|
2763
2782
|
session.id,
|
|
2764
2783
|
session.agents.length,
|
|
@@ -2781,14 +2800,14 @@ function renderDetailRows(rect, state2, detailCtx) {
|
|
|
2781
2800
|
} else {
|
|
2782
2801
|
switch (cursorNode.type) {
|
|
2783
2802
|
case "session": {
|
|
2784
|
-
lines = buildSessionLines(session, state2.planContent, state2.goalContent, rect.w, state2.paneAlive, state2.strategyContent
|
|
2803
|
+
lines = buildSessionLines(session, state2.planContent, state2.goalContent, rect.w, state2.paneAlive, state2.strategyContent);
|
|
2785
2804
|
break;
|
|
2786
2805
|
}
|
|
2787
2806
|
case "cycle": {
|
|
2788
2807
|
const cycleNode = cursorNode;
|
|
2789
2808
|
const cycle = session.orchestratorCycles.find((c) => c.cycle === cycleNode.cycleNumber);
|
|
2790
2809
|
if (!cycle) {
|
|
2791
|
-
lines = buildSessionLines(session, state2.planContent, state2.goalContent, rect.w, state2.paneAlive, state2.strategyContent
|
|
2810
|
+
lines = buildSessionLines(session, state2.planContent, state2.goalContent, rect.w, state2.paneAlive, state2.strategyContent);
|
|
2792
2811
|
} else {
|
|
2793
2812
|
lines = buildCycleLines(cycle, session.agents, rect.w);
|
|
2794
2813
|
}
|
|
@@ -2798,7 +2817,7 @@ function renderDetailRows(rect, state2, detailCtx) {
|
|
|
2798
2817
|
const agentNode = cursorNode;
|
|
2799
2818
|
const agent = agents.find((a) => a.id === agentNode.agentId);
|
|
2800
2819
|
if (!agent) {
|
|
2801
|
-
lines = buildSessionLines(session, state2.planContent, state2.goalContent, rect.w, state2.paneAlive, state2.strategyContent
|
|
2820
|
+
lines = buildSessionLines(session, state2.planContent, state2.goalContent, rect.w, state2.paneAlive, state2.strategyContent);
|
|
2802
2821
|
} else {
|
|
2803
2822
|
lines = buildAgentLines(agent, detailReportBlocks, rect.w);
|
|
2804
2823
|
}
|
|
@@ -2808,7 +2827,7 @@ function renderDetailRows(rect, state2, detailCtx) {
|
|
|
2808
2827
|
const reportNode = cursorNode;
|
|
2809
2828
|
const agent = agents.find((a) => a.id === reportNode.agentId);
|
|
2810
2829
|
if (!agent) {
|
|
2811
|
-
lines = buildSessionLines(session, state2.planContent, state2.goalContent, rect.w, state2.paneAlive, state2.strategyContent
|
|
2830
|
+
lines = buildSessionLines(session, state2.planContent, state2.goalContent, rect.w, state2.paneAlive, state2.strategyContent);
|
|
2812
2831
|
break;
|
|
2813
2832
|
}
|
|
2814
2833
|
const reportIdx = reportNode.reportIndex;
|
|
@@ -2900,7 +2919,7 @@ function renderDetailRows(rect, state2, detailCtx) {
|
|
|
2900
2919
|
break;
|
|
2901
2920
|
}
|
|
2902
2921
|
default: {
|
|
2903
|
-
lines = buildSessionLines(session, state2.planContent, state2.goalContent, rect.w, state2.paneAlive, state2.strategyContent
|
|
2922
|
+
lines = buildSessionLines(session, state2.planContent, state2.goalContent, rect.w, state2.paneAlive, state2.strategyContent);
|
|
2904
2923
|
break;
|
|
2905
2924
|
}
|
|
2906
2925
|
}
|
|
@@ -2941,6 +2960,52 @@ function renderLogsRows(rect, state2) {
|
|
|
2941
2960
|
return buildPanelRows(rect, lines, scrollOffset, focused, "gray", state2.logsRenderedCache);
|
|
2942
2961
|
}
|
|
2943
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;
|
|
3007
|
+
}
|
|
3008
|
+
|
|
2944
3009
|
// src/tui/panels/bottom.ts
|
|
2945
3010
|
function renderNotificationRow(buf, y, notification, error) {
|
|
2946
3011
|
if (notification !== null) {
|
|
@@ -2995,13 +3060,13 @@ function renderStatusLine(buf, y, state2, cursorNodeType) {
|
|
|
2995
3060
|
} else if (mode !== "navigate") {
|
|
2996
3061
|
content = D("[enter] send [esc] cancel");
|
|
2997
3062
|
} else if (focusPane === "logs" || focusPane === "detail") {
|
|
2998
|
-
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");
|
|
2999
3064
|
} else {
|
|
3000
3065
|
let contextFilePart = "";
|
|
3001
3066
|
if (cursorNodeType === "context-file") {
|
|
3002
3067
|
contextFilePart = B("[e]") + D("dit ") + B("[\u23CE]") + D(" open ");
|
|
3003
3068
|
}
|
|
3004
|
-
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");
|
|
3005
3070
|
}
|
|
3006
3071
|
writeClipped(buf, 1, y, content, buf.width - 2);
|
|
3007
3072
|
}
|
|
@@ -3102,6 +3167,590 @@ function renderHelpOverlay(buf, rows, cols) {
|
|
|
3102
3167
|
}
|
|
3103
3168
|
}
|
|
3104
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
|
+
|
|
3105
3754
|
// src/tui/app.ts
|
|
3106
3755
|
var latestNodes = [];
|
|
3107
3756
|
var cachedContextFilePath = null;
|
|
@@ -3113,6 +3762,67 @@ var prevOverlayMode = "";
|
|
|
3113
3762
|
var cachedTreeRows = [];
|
|
3114
3763
|
var cachedLogSessionId = null;
|
|
3115
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
|
+
}
|
|
3116
3826
|
function getAgentForNode(node, agents) {
|
|
3117
3827
|
if (!node) return null;
|
|
3118
3828
|
if (node.type === "agent" || node.type === "report") {
|
|
@@ -3122,6 +3832,16 @@ function getAgentForNode(node, agents) {
|
|
|
3122
3832
|
}
|
|
3123
3833
|
function startApp(state2, cleanup2) {
|
|
3124
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;
|
|
3125
3845
|
let prevSelectedSessionId = void 0;
|
|
3126
3846
|
let debouncedPollTimer = null;
|
|
3127
3847
|
async function poll() {
|
|
@@ -3157,28 +3877,28 @@ function startApp(state2, cleanup2) {
|
|
|
3157
3877
|
}
|
|
3158
3878
|
try {
|
|
3159
3879
|
const pp = roadmapPath(state2.cwd, state2.selectedSessionId);
|
|
3160
|
-
if (
|
|
3880
|
+
if (existsSync3(pp)) {
|
|
3161
3881
|
planContent = readFileSync2(pp, "utf-8");
|
|
3162
3882
|
}
|
|
3163
3883
|
} catch {
|
|
3164
3884
|
}
|
|
3165
3885
|
try {
|
|
3166
3886
|
const gp = goalPath(state2.cwd, state2.selectedSessionId);
|
|
3167
|
-
if (
|
|
3887
|
+
if (existsSync3(gp)) {
|
|
3168
3888
|
goalContent = readFileSync2(gp, "utf-8");
|
|
3169
3889
|
}
|
|
3170
3890
|
} catch {
|
|
3171
3891
|
}
|
|
3172
3892
|
try {
|
|
3173
3893
|
const sp = strategyPath(state2.cwd, state2.selectedSessionId);
|
|
3174
|
-
if (
|
|
3894
|
+
if (existsSync3(sp)) {
|
|
3175
3895
|
strategyContent = readFileSync2(sp, "utf-8");
|
|
3176
3896
|
}
|
|
3177
3897
|
} catch {
|
|
3178
3898
|
}
|
|
3179
3899
|
try {
|
|
3180
3900
|
const ld = logsDir(state2.cwd, state2.selectedSessionId);
|
|
3181
|
-
if (
|
|
3901
|
+
if (existsSync3(ld)) {
|
|
3182
3902
|
if (state2.selectedSessionId !== cachedLogSessionId) {
|
|
3183
3903
|
cachedLogFiles = /* @__PURE__ */ new Map();
|
|
3184
3904
|
cachedLogSessionId = state2.selectedSessionId;
|
|
@@ -3189,7 +3909,7 @@ function startApp(state2, cleanup2) {
|
|
|
3189
3909
|
if (!fileSet.has(key)) cachedLogFiles.delete(key);
|
|
3190
3910
|
}
|
|
3191
3911
|
for (const f of files) {
|
|
3192
|
-
const filePath =
|
|
3912
|
+
const filePath = join5(ld, f);
|
|
3193
3913
|
const mtime = statSync(filePath).mtimeMs;
|
|
3194
3914
|
const cached = cachedLogFiles.get(f);
|
|
3195
3915
|
if (!cached || cached.mtime !== mtime) {
|
|
@@ -3209,7 +3929,7 @@ function startApp(state2, cleanup2) {
|
|
|
3209
3929
|
}
|
|
3210
3930
|
try {
|
|
3211
3931
|
const cd = contextDir(state2.cwd, state2.selectedSessionId);
|
|
3212
|
-
if (
|
|
3932
|
+
if (existsSync3(cd)) {
|
|
3213
3933
|
contextFiles = readdirSync(cd).filter((f) => !f.startsWith(".")).sort();
|
|
3214
3934
|
}
|
|
3215
3935
|
} catch {
|
|
@@ -3231,6 +3951,9 @@ function startApp(state2, cleanup2) {
|
|
|
3231
3951
|
state2.paneAlive = paneAlive;
|
|
3232
3952
|
state2.contextFiles = contextFiles;
|
|
3233
3953
|
state2.error = null;
|
|
3954
|
+
if (state2.nvimEnabled && state2.nvimBridge?.ready && state2.prevNvimFile) {
|
|
3955
|
+
state2.nvimBridge.checktime();
|
|
3956
|
+
}
|
|
3234
3957
|
requestRender();
|
|
3235
3958
|
} catch (err) {
|
|
3236
3959
|
state2.error = err.message;
|
|
@@ -3250,14 +3973,14 @@ function startApp(state2, cleanup2) {
|
|
|
3250
3973
|
prevFrame = buf.lines;
|
|
3251
3974
|
return;
|
|
3252
3975
|
}
|
|
3253
|
-
const
|
|
3254
|
-
const remaining = state2.cols -
|
|
3255
|
-
const detailWidth = state2.
|
|
3256
|
-
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;
|
|
3257
3980
|
const contentHeight = state2.rows - 3;
|
|
3258
|
-
const treeRect = { x: 0, y: 0, w:
|
|
3259
|
-
const detailRect = { x:
|
|
3260
|
-
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;
|
|
3261
3984
|
const bottomY = contentHeight;
|
|
3262
3985
|
const filteredSessions = state2.searchFilter ? state2.sessions.filter((s) => {
|
|
3263
3986
|
const q = state2.searchFilter.toLowerCase();
|
|
@@ -3290,6 +4013,7 @@ function startApp(state2, cleanup2) {
|
|
|
3290
4013
|
state2.logsScroll.reset();
|
|
3291
4014
|
state2.cachedDetailLines = null;
|
|
3292
4015
|
state2.detailCacheKey = "";
|
|
4016
|
+
state2.prevNvimFile = null;
|
|
3293
4017
|
state2.cachedLogsLines = null;
|
|
3294
4018
|
state2.logsCacheKey = "";
|
|
3295
4019
|
}
|
|
@@ -3314,7 +4038,7 @@ function startApp(state2, cleanup2) {
|
|
|
3314
4038
|
if (cursorNode.filePath !== cachedContextFilePath) {
|
|
3315
4039
|
cachedContextFilePath = cursorNode.filePath;
|
|
3316
4040
|
try {
|
|
3317
|
-
if (
|
|
4041
|
+
if (existsSync3(cursorNode.filePath)) {
|
|
3318
4042
|
cachedContextFileContent = readFileSync2(cursorNode.filePath, "utf-8");
|
|
3319
4043
|
} else {
|
|
3320
4044
|
cachedContextFileContent = null;
|
|
@@ -3341,15 +4065,15 @@ function startApp(state2, cleanup2) {
|
|
|
3341
4065
|
prevOverlayMode = overlayMode;
|
|
3342
4066
|
let treeRows;
|
|
3343
4067
|
if (treeDirty) {
|
|
3344
|
-
const treeBlank = " ".repeat(
|
|
4068
|
+
const treeBlank = " ".repeat(treeWidth2);
|
|
3345
4069
|
const treeBuf = {
|
|
3346
4070
|
lines: Array.from({ length: contentHeight }, () => treeBlank),
|
|
3347
|
-
width:
|
|
4071
|
+
width: treeWidth2,
|
|
3348
4072
|
height: contentHeight
|
|
3349
4073
|
};
|
|
3350
4074
|
renderTreePanel(
|
|
3351
4075
|
treeBuf,
|
|
3352
|
-
{ x: 0, y: 0, w:
|
|
4076
|
+
{ x: 0, y: 0, w: treeWidth2, h: contentHeight },
|
|
3353
4077
|
nodes,
|
|
3354
4078
|
state2.cursorIndex,
|
|
3355
4079
|
treeFocused
|
|
@@ -3367,7 +4091,23 @@ function startApp(state2, cleanup2) {
|
|
|
3367
4091
|
detailReportBlocks,
|
|
3368
4092
|
contextFileContent
|
|
3369
4093
|
};
|
|
3370
|
-
|
|
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
|
+
}
|
|
3371
4111
|
const logsRows = logsRect ? renderLogsRows(logsRect, state2) : null;
|
|
3372
4112
|
for (let i = 0; i < contentHeight; i++) {
|
|
3373
4113
|
if (logsRows) {
|
|
@@ -3388,7 +4128,16 @@ function startApp(state2, cleanup2) {
|
|
|
3388
4128
|
if (state2.mode === "copy-menu") renderCopyMenuOverlay(buf, state2.rows, state2.cols);
|
|
3389
4129
|
if (state2.mode === "help") renderHelpOverlay(buf, state2.rows, state2.cols);
|
|
3390
4130
|
}
|
|
3391
|
-
|
|
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);
|
|
3392
4141
|
writeToStdout(out);
|
|
3393
4142
|
prevFrame = buf.lines;
|
|
3394
4143
|
}
|
|
@@ -3444,6 +4193,11 @@ function startApp(state2, cleanup2) {
|
|
|
3444
4193
|
state2.rows = typeof stdoutRows === "number" && stdoutRows > 0 ? stdoutRows : 24;
|
|
3445
4194
|
state2.cols = typeof stdoutCols === "number" && stdoutCols > 0 ? stdoutCols : 80;
|
|
3446
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
|
+
}
|
|
3447
4201
|
requestRender();
|
|
3448
4202
|
});
|
|
3449
4203
|
void poll();
|
|
@@ -3456,6 +4210,7 @@ function startApp(state2, cleanup2) {
|
|
|
3456
4210
|
stopResize();
|
|
3457
4211
|
state2.detailScroll.destroy();
|
|
3458
4212
|
state2.logsScroll.destroy();
|
|
4213
|
+
state2.nvimBridge?.destroy();
|
|
3459
4214
|
origCleanup();
|
|
3460
4215
|
};
|
|
3461
4216
|
requestRender();
|