sisyphi 1.1.16 → 1.1.18
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/README.md +209 -7
- package/dist/{chunk-6TIO23U3.js → chunk-22ZGZTGY.js} +3 -3
- package/dist/{chunk-6TIO23U3.js.map → chunk-22ZGZTGY.js.map} +1 -1
- package/dist/{chunk-UIVQXCWB.js → chunk-6PJVJEYQ.js} +2 -2
- package/dist/chunk-C2XKXERJ.js +1052 -0
- package/dist/chunk-C2XKXERJ.js.map +1 -0
- package/dist/{chunk-GSXF3TCZ.js → chunk-TMBAVPHH.js} +19 -3
- package/dist/chunk-TMBAVPHH.js.map +1 -0
- package/dist/chunk-V36NXMHP.js +299 -0
- package/dist/chunk-V36NXMHP.js.map +1 -0
- package/dist/cli.js +166 -8
- package/dist/cli.js.map +1 -1
- package/dist/daemon.js +1464 -129
- package/dist/daemon.js.map +1 -1
- package/dist/{paths-3EL2SCHI.js → paths-XRDEEJ5R.js} +10 -2
- package/dist/tui.js +1314 -984
- package/dist/tui.js.map +1 -1
- package/native/SisyphusNotify/AppIcon.icns +0 -0
- package/native/SisyphusNotify/Info.plist +26 -0
- package/native/SisyphusNotify/main.swift +126 -0
- package/native/SisyphusNotify/sisyphus-icon.jpg +0 -0
- package/native/build-notify.sh +58 -0
- package/package.json +3 -2
- package/dist/chunk-GSXF3TCZ.js.map +0 -1
- package/dist/chunk-HQZOAX6D.js +0 -240
- package/dist/chunk-HQZOAX6D.js.map +0 -1
- package/dist/chunk-IF55HPWX.js +0 -44
- package/dist/chunk-IF55HPWX.js.map +0 -1
- /package/dist/{chunk-UIVQXCWB.js.map → chunk-6PJVJEYQ.js.map} +0 -0
- /package/dist/{paths-3EL2SCHI.js.map → paths-XRDEEJ5R.js.map} +0 -0
package/dist/tui.js
CHANGED
|
@@ -4,20 +4,28 @@ import {
|
|
|
4
4
|
augmentedPath,
|
|
5
5
|
exec,
|
|
6
6
|
execSafe
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-22ZGZTGY.js";
|
|
8
8
|
import {
|
|
9
9
|
buildSessionContext,
|
|
10
10
|
computeActiveTimeMs,
|
|
11
|
+
createBadgeGallery,
|
|
11
12
|
formatDuration,
|
|
13
|
+
galleryNext,
|
|
14
|
+
galleryPrev,
|
|
12
15
|
rawSend,
|
|
16
|
+
renderBadgeCard,
|
|
13
17
|
resolveReports,
|
|
14
18
|
statusColor
|
|
15
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-C2XKXERJ.js";
|
|
16
20
|
import {
|
|
21
|
+
ACHIEVEMENTS,
|
|
22
|
+
getMoodFace,
|
|
17
23
|
loadConfig,
|
|
24
|
+
renderCompanion,
|
|
18
25
|
shellQuote
|
|
19
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-V36NXMHP.js";
|
|
20
27
|
import {
|
|
28
|
+
companionPath,
|
|
21
29
|
contextDir,
|
|
22
30
|
globalDir,
|
|
23
31
|
goalPath,
|
|
@@ -25,8 +33,9 @@ import {
|
|
|
25
33
|
roadmapPath,
|
|
26
34
|
sessionDir,
|
|
27
35
|
strategyPath,
|
|
36
|
+
tmuxSessionName,
|
|
28
37
|
tuiScratchDir
|
|
29
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-TMBAVPHH.js";
|
|
30
39
|
|
|
31
40
|
// src/tui/terminal.ts
|
|
32
41
|
function emptyKey() {
|
|
@@ -235,27 +244,6 @@ var COMPOSE_HEADERS = {
|
|
|
235
244
|
"spawn-agent": "Spawn Agent",
|
|
236
245
|
"message-agent": "Message Agent"
|
|
237
246
|
};
|
|
238
|
-
var INPUT_MODES = /* @__PURE__ */ new Set([
|
|
239
|
-
"resume",
|
|
240
|
-
"continue",
|
|
241
|
-
"rollback",
|
|
242
|
-
"delete-confirm",
|
|
243
|
-
"spawn-agent",
|
|
244
|
-
"search",
|
|
245
|
-
"message-agent",
|
|
246
|
-
"shell-command"
|
|
247
|
-
]);
|
|
248
|
-
var OPTIONAL_INPUT = /* @__PURE__ */ new Set(["resume", "continue", "search"]);
|
|
249
|
-
var PROMPTS = {
|
|
250
|
-
resume: "resume instructions (optional)",
|
|
251
|
-
continue: "new direction (optional)",
|
|
252
|
-
rollback: "cycle number",
|
|
253
|
-
"delete-confirm": "type 'yes' to confirm delete:",
|
|
254
|
-
"spawn-agent": "agent instruction:",
|
|
255
|
-
search: "filter:",
|
|
256
|
-
"message-agent": "message:",
|
|
257
|
-
"shell-command": "$ "
|
|
258
|
-
};
|
|
259
247
|
var renderScheduled = false;
|
|
260
248
|
var renderFn = null;
|
|
261
249
|
function setRenderFunction(fn) {
|
|
@@ -326,12 +314,11 @@ function createAppState(cwd2) {
|
|
|
326
314
|
focusPane: "tree",
|
|
327
315
|
selectedSessionId: null,
|
|
328
316
|
searchFilter: null,
|
|
317
|
+
searchText: "",
|
|
329
318
|
targetAgentId: null,
|
|
330
319
|
notification: null,
|
|
331
320
|
notificationTimer: null,
|
|
332
321
|
showCombinedView: false,
|
|
333
|
-
inputText: "",
|
|
334
|
-
inputCursorPos: 0,
|
|
335
322
|
detailScroll,
|
|
336
323
|
logsScroll,
|
|
337
324
|
sessions: [],
|
|
@@ -822,356 +809,911 @@ function findParentIndex(nodes, index) {
|
|
|
822
809
|
return 0;
|
|
823
810
|
}
|
|
824
811
|
|
|
825
|
-
// src/tui/
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
requestRender();
|
|
843
|
-
return true;
|
|
844
|
-
}
|
|
845
|
-
state2.nvimBridge.write(data);
|
|
846
|
-
return true;
|
|
847
|
-
});
|
|
848
|
-
}
|
|
849
|
-
function deactivateNvimBypass() {
|
|
850
|
-
setRawBypass(null);
|
|
812
|
+
// src/tui/render.ts
|
|
813
|
+
import stringWidth2 from "string-width";
|
|
814
|
+
var COLOR_SGR = {
|
|
815
|
+
black: 30,
|
|
816
|
+
red: 31,
|
|
817
|
+
green: 32,
|
|
818
|
+
yellow: 33,
|
|
819
|
+
blue: 34,
|
|
820
|
+
magenta: 35,
|
|
821
|
+
cyan: 36,
|
|
822
|
+
white: 37,
|
|
823
|
+
gray: 90
|
|
824
|
+
};
|
|
825
|
+
function colorToSGR(color) {
|
|
826
|
+
const code = COLOR_SGR[color];
|
|
827
|
+
if (code === void 0) throw new Error(`Unknown color: ${color}`);
|
|
828
|
+
return code;
|
|
851
829
|
}
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
state2.composePollTimer = setInterval(() => {
|
|
869
|
-
checkComposeSignal(state2, actions);
|
|
870
|
-
}, 100);
|
|
871
|
-
requestRender();
|
|
872
|
-
return true;
|
|
830
|
+
function renderLine(segs) {
|
|
831
|
+
let out = "";
|
|
832
|
+
for (const s of segs) {
|
|
833
|
+
const codes = [];
|
|
834
|
+
if (s.bold) codes.push(1);
|
|
835
|
+
if (s.dim) codes.push(2);
|
|
836
|
+
if (s.italic) codes.push(3);
|
|
837
|
+
if (s.inverse) codes.push(7);
|
|
838
|
+
if (s.color) codes.push(colorToSGR(s.color));
|
|
839
|
+
if (codes.length > 0) {
|
|
840
|
+
out += `\x1B[${codes.join(";")}m${s.text}\x1B[0m`;
|
|
841
|
+
} else {
|
|
842
|
+
out += s.text;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
return out;
|
|
873
846
|
}
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
847
|
+
var cachedBlank = "";
|
|
848
|
+
var cachedBlankWidth = 0;
|
|
849
|
+
function createFrameBuffer(width, height) {
|
|
850
|
+
if (width !== cachedBlankWidth) {
|
|
851
|
+
cachedBlank = " ".repeat(width);
|
|
852
|
+
cachedBlankWidth = width;
|
|
878
853
|
}
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
854
|
+
const lines = new Array(height);
|
|
855
|
+
for (let i = 0; i < height; i++) lines[i] = cachedBlank;
|
|
856
|
+
return { lines, width, height };
|
|
857
|
+
}
|
|
858
|
+
function copyRows(buf, src, startRow, count) {
|
|
859
|
+
for (let i = 0; i < count && startRow + i < buf.height; i++) {
|
|
860
|
+
buf.lines[startRow + i] = src[startRow + i];
|
|
884
861
|
}
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
862
|
+
}
|
|
863
|
+
function flushFrame(frame, prevFrame2, suffix) {
|
|
864
|
+
let out = "\x1B[?2026h";
|
|
865
|
+
for (let i = 0; i < frame.length; i++) {
|
|
866
|
+
if (frame[i] !== prevFrame2[i]) {
|
|
867
|
+
out += `\x1B[${i + 1};1H`;
|
|
868
|
+
out += "\x1B[2K";
|
|
869
|
+
out += frame[i];
|
|
889
870
|
}
|
|
890
871
|
}
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
state2.composeTempFile = null;
|
|
895
|
-
state2.composeSignalFile = null;
|
|
896
|
-
state2.mode = "navigate";
|
|
897
|
-
state2.focusPane = "tree";
|
|
898
|
-
deactivateNvimBypass();
|
|
899
|
-
requestRender();
|
|
872
|
+
if (suffix) out += suffix;
|
|
873
|
+
out += "\x1B[?2026l";
|
|
874
|
+
return out;
|
|
900
875
|
}
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
876
|
+
var ANSI_RE = /\x1b\[[0-9;]*[a-zA-Z]/g;
|
|
877
|
+
function clipAnsi(content, maxWidth) {
|
|
878
|
+
let out = "";
|
|
879
|
+
let displayWidth = 0;
|
|
880
|
+
let i = 0;
|
|
881
|
+
while (i < content.length) {
|
|
882
|
+
if (content[i] === "\x1B" && content[i + 1] === "[") {
|
|
883
|
+
const seqLen = ansiLen(content, i);
|
|
884
|
+
if (seqLen > 0) {
|
|
885
|
+
out += content.substring(i, i + seqLen);
|
|
886
|
+
i += seqLen;
|
|
887
|
+
continue;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
const cp = content.codePointAt(i);
|
|
891
|
+
const ch = String.fromCodePoint(cp);
|
|
892
|
+
const chWidth = cp < 128 ? 1 : stringWidth2(ch);
|
|
893
|
+
if (displayWidth + chWidth > maxWidth) break;
|
|
894
|
+
out += ch;
|
|
895
|
+
displayWidth += chWidth;
|
|
896
|
+
i += ch.length;
|
|
906
897
|
}
|
|
907
|
-
if (!
|
|
908
|
-
|
|
909
|
-
try {
|
|
910
|
-
signalContent = readFileSync(state2.composeSignalFile, "utf-8").trim();
|
|
911
|
-
} catch {
|
|
898
|
+
if (out.includes("\x1B[") && !out.endsWith("\x1B[0m")) {
|
|
899
|
+
out += "\x1B[0m";
|
|
912
900
|
}
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
901
|
+
const remaining = maxWidth - displayWidth;
|
|
902
|
+
if (remaining > 0) out += " ".repeat(remaining);
|
|
903
|
+
return out;
|
|
904
|
+
}
|
|
905
|
+
function displayWidthFast(s) {
|
|
906
|
+
let w = 0;
|
|
907
|
+
let i = 0;
|
|
908
|
+
while (i < s.length) {
|
|
909
|
+
if (s[i] === "\x1B" && s[i + 1] === "[") {
|
|
910
|
+
const len = ansiLen(s, i);
|
|
911
|
+
if (len > 0) {
|
|
912
|
+
i += len;
|
|
913
|
+
continue;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
const cp = s.codePointAt(i);
|
|
917
|
+
const ch = String.fromCodePoint(cp);
|
|
918
|
+
w += cp < 128 ? 1 : stringWidth2(ch);
|
|
919
|
+
i += ch.length;
|
|
916
920
|
}
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
921
|
+
return w;
|
|
922
|
+
}
|
|
923
|
+
function ansiLen(s, i) {
|
|
924
|
+
let j = i + 2;
|
|
925
|
+
const len = s.length;
|
|
926
|
+
while (j < len) {
|
|
927
|
+
const c = s.charCodeAt(j);
|
|
928
|
+
if (c >= 48 && c <= 57 || c === 59) {
|
|
929
|
+
j++;
|
|
930
|
+
} else {
|
|
931
|
+
break;
|
|
922
932
|
}
|
|
923
933
|
}
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
unlinkSync(state2.composeSignalFile);
|
|
929
|
-
} catch {
|
|
934
|
+
if (j < len) {
|
|
935
|
+
const c = s.charCodeAt(j);
|
|
936
|
+
if (c >= 65 && c <= 90 || c >= 97 && c <= 122) {
|
|
937
|
+
return j + 1 - i;
|
|
930
938
|
}
|
|
931
|
-
notify(state2, "Content required");
|
|
932
|
-
return;
|
|
933
939
|
}
|
|
934
|
-
|
|
935
|
-
cancelCompose(state2);
|
|
940
|
+
return 0;
|
|
936
941
|
}
|
|
937
|
-
function
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
942
|
+
function writeAt(buf, x, y, content) {
|
|
943
|
+
if (y < 0 || y >= buf.height) return;
|
|
944
|
+
if (x < 0 || x >= buf.width) return;
|
|
945
|
+
const existing = buf.lines[y];
|
|
946
|
+
const contentDisplayWidth = stringWidth2(content.replace(ANSI_RE, ""));
|
|
947
|
+
const prefix = sliceDisplayCols(existing, 0, x);
|
|
948
|
+
const suffix = sliceDisplayCols(existing, x + contentDisplayWidth, buf.width, true);
|
|
949
|
+
const prefixWidth = stringWidth2(prefix.replace(ANSI_RE, ""));
|
|
950
|
+
const paddedPrefix = prefix + " ".repeat(Math.max(0, x - prefixWidth));
|
|
951
|
+
buf.lines[y] = paddedPrefix + content + suffix;
|
|
952
|
+
}
|
|
953
|
+
function writeClipped(buf, x, y, content, maxWidth) {
|
|
954
|
+
if (y < 0 || y >= buf.height) return;
|
|
955
|
+
if (x < 0 || x >= buf.width) return;
|
|
956
|
+
let out = "";
|
|
957
|
+
let displayWidth = 0;
|
|
958
|
+
let i = 0;
|
|
959
|
+
while (i < content.length) {
|
|
960
|
+
if (content[i] === "\x1B" && content[i + 1] === "[") {
|
|
961
|
+
const seqLen = ansiLen(content, i);
|
|
962
|
+
if (seqLen > 0) {
|
|
963
|
+
out += content.substring(i, i + seqLen);
|
|
964
|
+
i += seqLen;
|
|
965
|
+
continue;
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
const cp = content.codePointAt(i);
|
|
969
|
+
const ch = String.fromCodePoint(cp);
|
|
970
|
+
const chWidth = cp < 128 ? 1 : stringWidth2(ch);
|
|
971
|
+
if (displayWidth + chWidth > maxWidth) break;
|
|
972
|
+
out += ch;
|
|
973
|
+
displayWidth += chWidth;
|
|
974
|
+
i += ch.length;
|
|
975
|
+
}
|
|
976
|
+
if (out.includes("\x1B[") && !out.endsWith("\x1B[0m")) {
|
|
977
|
+
out += "\x1B[0m";
|
|
978
|
+
}
|
|
979
|
+
const remaining = maxWidth - displayWidth;
|
|
980
|
+
if (remaining > 0) {
|
|
981
|
+
out += " ".repeat(remaining);
|
|
982
|
+
}
|
|
983
|
+
const existing = buf.lines[y];
|
|
984
|
+
const prefix = sliceDisplayCols(existing, 0, x);
|
|
985
|
+
const suffix = sliceDisplayCols(existing, x + maxWidth, buf.width, true);
|
|
986
|
+
const prefixDisplayW = displayWidthFast(prefix);
|
|
987
|
+
const paddedPrefix = prefixDisplayW < x ? prefix + " ".repeat(x - prefixDisplayW) : prefix;
|
|
988
|
+
buf.lines[y] = paddedPrefix + out + suffix;
|
|
989
|
+
}
|
|
990
|
+
function writeCenter(buf, row, content) {
|
|
991
|
+
const textWidth = stringWidth2(content.replace(ANSI_RE, ""));
|
|
992
|
+
const x = Math.max(0, Math.floor((buf.width - textWidth) / 2));
|
|
993
|
+
writeAt(buf, x, row, content);
|
|
994
|
+
}
|
|
995
|
+
function drawBorder(buf, x, y, w, h, color) {
|
|
996
|
+
const sgr = `\x1B[${colorToSGR(color)}m`;
|
|
997
|
+
const reset = "\x1B[0m";
|
|
998
|
+
writeAt(buf, x, y, sgr + "\u256D" + "\u2500".repeat(w - 2) + "\u256E" + reset);
|
|
999
|
+
writeAt(buf, x, y + h - 1, sgr + "\u2570" + "\u2500".repeat(w - 2) + "\u256F" + reset);
|
|
1000
|
+
for (let row = y + 1; row < y + h - 1; row++) {
|
|
1001
|
+
writeAt(buf, x, row, sgr + "\u2502" + reset);
|
|
1002
|
+
writeAt(buf, x + w - 1, row, sgr + "\u2502" + reset);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
function buildPanelRows(rect, lines, scrollOffset, focused, borderColor, renderedCache) {
|
|
1006
|
+
const { w, h } = rect;
|
|
1007
|
+
const rows = new Array(h);
|
|
1008
|
+
const color = focused ? "blue" : borderColor;
|
|
1009
|
+
const sgr = `\x1B[${colorToSGR(color)}m`;
|
|
1010
|
+
const reset = "\x1B[0m";
|
|
1011
|
+
const innerW = w - 4;
|
|
1012
|
+
const innerH = h - 2;
|
|
1013
|
+
const blankInner = " ".repeat(innerW);
|
|
1014
|
+
rows[0] = sgr + "\u256D" + "\u2500".repeat(w - 2) + "\u256E" + reset;
|
|
1015
|
+
rows[h - 1] = sgr + "\u2570" + "\u2500".repeat(w - 2) + "\u256F" + reset;
|
|
1016
|
+
const borderL = sgr + "\u2502" + reset + " ";
|
|
1017
|
+
const borderR = " " + sgr + "\u2502" + reset;
|
|
1018
|
+
const emptyRow = borderL + blankInner + borderR;
|
|
1019
|
+
for (let i = 1; i < h - 1; i++) rows[i] = emptyRow;
|
|
1020
|
+
if (innerW <= 0 || innerH <= 0) return rows;
|
|
1021
|
+
let ansiLines;
|
|
1022
|
+
if (renderedCache && renderedCache.lines === lines) {
|
|
1023
|
+
ansiLines = renderedCache.ansi;
|
|
1024
|
+
} else {
|
|
1025
|
+
ansiLines = new Array(lines.length);
|
|
1026
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1027
|
+
ansiLines[i] = renderLine(lines[i]);
|
|
1028
|
+
}
|
|
1029
|
+
if (renderedCache) {
|
|
1030
|
+
renderedCache.lines = lines;
|
|
1031
|
+
renderedCache.ansi = ansiLines;
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
const hasOverflow = lines.length > innerH;
|
|
1035
|
+
const viewableH = hasOverflow ? innerH - 1 : innerH;
|
|
1036
|
+
const maxScroll = Math.max(0, lines.length - viewableH);
|
|
1037
|
+
const effectiveOffset = Math.min(scrollOffset, maxScroll);
|
|
1038
|
+
for (let i = 0; i < viewableH && effectiveOffset + i < ansiLines.length; i++) {
|
|
1039
|
+
const clipped = clipAnsi(ansiLines[effectiveOffset + i], innerW);
|
|
1040
|
+
rows[1 + i] = borderL + clipped + borderR;
|
|
1041
|
+
}
|
|
1042
|
+
if (hasOverflow) {
|
|
1043
|
+
const scrollPct = maxScroll > 0 ? Math.round(effectiveOffset / maxScroll * 100) : 100;
|
|
1044
|
+
const indicator = ` \u2195 ${scrollPct}% \xB7 ${lines.length} lines`;
|
|
1045
|
+
const clipped = clipAnsi(`\x1B[2m${indicator}\x1B[0m`, innerW);
|
|
1046
|
+
rows[1 + viewableH] = borderL + clipped + borderR;
|
|
1047
|
+
}
|
|
1048
|
+
return rows;
|
|
1049
|
+
}
|
|
1050
|
+
function buildEmptyPanelRows(rect, focused, borderColor, centerText) {
|
|
1051
|
+
const { w, h } = rect;
|
|
1052
|
+
const rows = new Array(h);
|
|
1053
|
+
const color = focused ? "blue" : borderColor;
|
|
1054
|
+
const sgr = `\x1B[${colorToSGR(color)}m`;
|
|
1055
|
+
const reset = "\x1B[0m";
|
|
1056
|
+
const innerW = w - 4;
|
|
1057
|
+
const borderL = sgr + "\u2502" + reset + " ";
|
|
1058
|
+
const borderR = " " + sgr + "\u2502" + reset;
|
|
1059
|
+
const emptyRow = borderL + " ".repeat(innerW) + borderR;
|
|
1060
|
+
rows[0] = sgr + "\u256D" + "\u2500".repeat(w - 2) + "\u256E" + reset;
|
|
1061
|
+
rows[h - 1] = sgr + "\u2570" + "\u2500".repeat(w - 2) + "\u256F" + reset;
|
|
1062
|
+
for (let i = 1; i < h - 1; i++) rows[i] = emptyRow;
|
|
1063
|
+
if (centerText) {
|
|
1064
|
+
const midRow = Math.floor(h / 2);
|
|
1065
|
+
if (midRow > 0 && midRow < h - 1) {
|
|
1066
|
+
const clipped = clipAnsi(centerText, innerW);
|
|
1067
|
+
const textW = displayWidthFast(centerText);
|
|
1068
|
+
const pad = Math.max(0, Math.floor((innerW - textW) / 2));
|
|
1069
|
+
const centered = " ".repeat(pad) + clipped;
|
|
1070
|
+
rows[midRow] = borderL + clipAnsi(centered, innerW) + borderR;
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
return rows;
|
|
1074
|
+
}
|
|
1075
|
+
function sliceDisplayCols(s, start, end, restoreState = false) {
|
|
1076
|
+
let out = "";
|
|
1077
|
+
let col = 0;
|
|
1078
|
+
let i = 0;
|
|
1079
|
+
let inSlice = false;
|
|
1080
|
+
let hasOpenSGR = false;
|
|
1081
|
+
let pendingSGR = "";
|
|
1082
|
+
while (i < s.length && col < end) {
|
|
1083
|
+
if (s[i] === "\x1B" && s[i + 1] === "[") {
|
|
1084
|
+
const seqLen = ansiLen(s, i);
|
|
1085
|
+
if (seqLen > 0) {
|
|
1086
|
+
const seq = s.substring(i, i + seqLen);
|
|
1087
|
+
if (col >= start) {
|
|
1088
|
+
out += seq;
|
|
1089
|
+
hasOpenSGR = seq !== "\x1B[0m" && seq !== "\x1B[m";
|
|
1090
|
+
} else if (restoreState) {
|
|
1091
|
+
if (seq === "\x1B[0m" || seq === "\x1B[m") {
|
|
1092
|
+
pendingSGR = "";
|
|
1093
|
+
} else {
|
|
1094
|
+
pendingSGR += seq;
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
i += seqLen;
|
|
1098
|
+
continue;
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
const cp = s.codePointAt(i);
|
|
1102
|
+
const ch = String.fromCodePoint(cp);
|
|
1103
|
+
const chWidth = cp < 128 ? 1 : stringWidth2(ch);
|
|
1104
|
+
if (col >= start) {
|
|
1105
|
+
inSlice = true;
|
|
1106
|
+
if (col + chWidth > end) break;
|
|
1107
|
+
out += ch;
|
|
1108
|
+
}
|
|
1109
|
+
col += chWidth;
|
|
1110
|
+
i += ch.length;
|
|
1111
|
+
}
|
|
1112
|
+
if (inSlice && hasOpenSGR) {
|
|
1113
|
+
out += "\x1B[0m";
|
|
1114
|
+
}
|
|
1115
|
+
if (restoreState && pendingSGR) {
|
|
1116
|
+
out = pendingSGR + out;
|
|
1117
|
+
}
|
|
1118
|
+
return out;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
// src/tui/panels/overlays.ts
|
|
1122
|
+
var LEADER_WIDTH = 26;
|
|
1123
|
+
var LEADER_HEIGHT = 20;
|
|
1124
|
+
var COPY_HEIGHT = 9;
|
|
1125
|
+
var HELP_WIDTH = 62;
|
|
1126
|
+
var COMPANION_WIDTH = 52;
|
|
1127
|
+
var DEBUG_WIDTH = 50;
|
|
1128
|
+
function helpRow(left, right, innerWidth) {
|
|
1129
|
+
const col = Math.floor(innerWidth / 2);
|
|
1130
|
+
return (left.padEnd(col) + right).padEnd(innerWidth);
|
|
1131
|
+
}
|
|
1132
|
+
function renderLeaderOverlay(buf, rows, cols) {
|
|
1133
|
+
const x = cols - LEADER_WIDTH - 1;
|
|
1134
|
+
const y = rows - LEADER_HEIGHT - 2;
|
|
1135
|
+
const innerWidth = LEADER_WIDTH - 2;
|
|
1136
|
+
drawBorder(buf, x, y, LEADER_WIDTH, LEADER_HEIGHT, "magenta");
|
|
1137
|
+
const lines = [
|
|
1138
|
+
ansiColor(" LEADER".padEnd(innerWidth), "magenta", true),
|
|
1139
|
+
" ".padEnd(innerWidth),
|
|
1140
|
+
" y copy menu".padEnd(innerWidth),
|
|
1141
|
+
" d delete session".padEnd(innerWidth),
|
|
1142
|
+
" l daemon logs".padEnd(innerWidth),
|
|
1143
|
+
" o open session dir".padEnd(innerWidth),
|
|
1144
|
+
" a spawn agent".padEnd(innerWidth),
|
|
1145
|
+
" m message agent".padEnd(innerWidth),
|
|
1146
|
+
" / search".padEnd(innerWidth),
|
|
1147
|
+
" ! shell command".padEnd(innerWidth),
|
|
1148
|
+
" j jump to pane".padEnd(innerWidth),
|
|
1149
|
+
" k kill session/agent".padEnd(innerWidth),
|
|
1150
|
+
" c companion overlay".padEnd(innerWidth),
|
|
1151
|
+
" q quit".padEnd(innerWidth),
|
|
1152
|
+
" ? help".padEnd(innerWidth),
|
|
1153
|
+
" 1-9 jump to session".padEnd(innerWidth),
|
|
1154
|
+
" ".padEnd(innerWidth),
|
|
1155
|
+
ansiDim(" esc dismiss".padEnd(innerWidth))
|
|
1156
|
+
];
|
|
1157
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1158
|
+
writeClipped(buf, x + 1, y + 1 + i, lines[i], innerWidth);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
function renderCopyMenuOverlay(buf, rows, cols) {
|
|
1162
|
+
const x = cols - LEADER_WIDTH - 1;
|
|
1163
|
+
const y = rows - COPY_HEIGHT - 2;
|
|
1164
|
+
const innerWidth = LEADER_WIDTH - 2;
|
|
1165
|
+
drawBorder(buf, x, y, LEADER_WIDTH, COPY_HEIGHT, "cyan");
|
|
1166
|
+
const lines = [
|
|
1167
|
+
ansiColor(" COPY".padEnd(innerWidth), "cyan", true),
|
|
1168
|
+
" ".padEnd(innerWidth),
|
|
1169
|
+
" p session path".padEnd(innerWidth),
|
|
1170
|
+
" C LLM context".padEnd(innerWidth),
|
|
1171
|
+
" l logs content".padEnd(innerWidth),
|
|
1172
|
+
" s session ID".padEnd(innerWidth),
|
|
1173
|
+
ansiDim(" esc cancel".padEnd(innerWidth))
|
|
1174
|
+
];
|
|
1175
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1176
|
+
writeClipped(buf, x + 1, y + 1 + i, lines[i], innerWidth);
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
function renderHelpOverlay(buf, rows, cols) {
|
|
1180
|
+
const innerWidth = HELP_WIDTH - 2;
|
|
1181
|
+
const x = Math.max(0, Math.floor((cols - HELP_WIDTH) / 2));
|
|
1182
|
+
const contentLines = [
|
|
1183
|
+
helpRow(" hjkl/\u2191\u2193\u2190\u2192 navigate", " tab switch pane", innerWidth),
|
|
1184
|
+
helpRow(" enter expand/open", " t toggle logs", innerWidth),
|
|
1185
|
+
" ".padEnd(innerWidth),
|
|
1186
|
+
helpRow(" n new session", " m message orch.", innerWidth),
|
|
1187
|
+
helpRow(" R resume session", " C continue session", innerWidth),
|
|
1188
|
+
helpRow(" b rollback cycle", " x restart agent", innerWidth),
|
|
1189
|
+
helpRow(" r re-run agent", " g edit goal", innerWidth),
|
|
1190
|
+
helpRow(" p open roadmap", " s toggle strategy", innerWidth),
|
|
1191
|
+
helpRow(" S edit strategy", " w go to window", innerWidth),
|
|
1192
|
+
helpRow(" o resume claude session", " c claude companion", innerWidth),
|
|
1193
|
+
helpRow(" q quit", "", innerWidth),
|
|
1194
|
+
" ".padEnd(innerWidth),
|
|
1195
|
+
helpRow(" space \u2192 y copy submenu", " space \u2192 d delete session", innerWidth),
|
|
1196
|
+
helpRow(" space \u2192 j jump to pane", " space \u2192 k kill", innerWidth),
|
|
1197
|
+
helpRow(" space \u2192 q quit", " space \u2192 o open dir", innerWidth),
|
|
1198
|
+
helpRow(" space \u2192 l tail logs", " space \u2192 / search", innerWidth),
|
|
1199
|
+
helpRow(" space \u2192 a spawn agent", " space \u2192 m msg agent", innerWidth),
|
|
1200
|
+
helpRow(" space \u2192 ? help", " space \u2192 1-9 jump", innerWidth),
|
|
1201
|
+
" ".padEnd(innerWidth),
|
|
1202
|
+
helpRow(" y \u2192 p session path", " y \u2192 C LLM context", innerWidth),
|
|
1203
|
+
helpRow(" y \u2192 l logs content", " y \u2192 s session ID", innerWidth)
|
|
1204
|
+
];
|
|
1205
|
+
const height = Math.min(contentLines.length + 4, rows - 2);
|
|
1206
|
+
const y = Math.max(0, Math.floor((rows - height) / 2));
|
|
1207
|
+
drawBorder(buf, x, y, HELP_WIDTH, height, "yellow");
|
|
1208
|
+
writeClipped(buf, x + 1, y + 1, ansiColor(" KEYBINDINGS (esc or ? to close)".padEnd(innerWidth), "yellow", true), innerWidth);
|
|
1209
|
+
writeClipped(buf, x + 1, y + 2, " ".padEnd(innerWidth), innerWidth);
|
|
1210
|
+
const availableContentRows = height - 4;
|
|
1211
|
+
for (let i = 0; i < Math.min(contentLines.length, availableContentRows); i++) {
|
|
1212
|
+
writeClipped(buf, x + 1, y + 3 + i, contentLines[i], innerWidth);
|
|
1213
|
+
}
|
|
1214
|
+
const trailingBlankRow = y + 3 + Math.min(contentLines.length, availableContentRows);
|
|
1215
|
+
if (trailingBlankRow < y + height - 1) {
|
|
1216
|
+
writeClipped(buf, x + 1, trailingBlankRow, " ".padEnd(innerWidth), innerWidth);
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
var _page = "profile";
|
|
1220
|
+
var _prevPage = "profile";
|
|
1221
|
+
var _gallery = null;
|
|
1222
|
+
function companionOverlayNextPage() {
|
|
1223
|
+
if (_page === "help") {
|
|
1224
|
+
_page = _prevPage;
|
|
1225
|
+
return;
|
|
992
1226
|
}
|
|
1227
|
+
_page = _page === "profile" ? "badges" : "profile";
|
|
1228
|
+
}
|
|
1229
|
+
function companionOverlayShowHelp() {
|
|
1230
|
+
if (_page !== "help") _prevPage = _page;
|
|
1231
|
+
_page = "help";
|
|
1232
|
+
}
|
|
1233
|
+
function companionOverlayDismissHelp() {
|
|
1234
|
+
_page = _prevPage;
|
|
1235
|
+
}
|
|
1236
|
+
function badgeGalleryLeft() {
|
|
1237
|
+
if (_gallery) _gallery.currentIndex = galleryPrev(_gallery);
|
|
1238
|
+
}
|
|
1239
|
+
function badgeGalleryRight() {
|
|
1240
|
+
if (_gallery) _gallery.currentIndex = galleryNext(_gallery);
|
|
1241
|
+
}
|
|
1242
|
+
function closeBadgeGallery() {
|
|
1243
|
+
_page = "profile";
|
|
1244
|
+
_gallery = null;
|
|
1245
|
+
_badgeScroll = 0;
|
|
1246
|
+
}
|
|
1247
|
+
function getCompanionPage() {
|
|
1248
|
+
return _page;
|
|
1249
|
+
}
|
|
1250
|
+
var MOOD_ICONS = {
|
|
1251
|
+
happy: "\u25C9",
|
|
1252
|
+
grinding: "\u25C8",
|
|
1253
|
+
frustrated: "\u25C6",
|
|
1254
|
+
zen: "\u25CE",
|
|
1255
|
+
sleepy: "\u25CC",
|
|
1256
|
+
excited: "\u2726",
|
|
1257
|
+
existential: "\u25C9"
|
|
1258
|
+
};
|
|
1259
|
+
var MOOD_COLORS = {
|
|
1260
|
+
happy: "green",
|
|
1261
|
+
grinding: "yellow",
|
|
1262
|
+
frustrated: "red",
|
|
1263
|
+
zen: "cyan",
|
|
1264
|
+
sleepy: "gray",
|
|
1265
|
+
excited: "white",
|
|
1266
|
+
existential: "magenta"
|
|
1267
|
+
};
|
|
1268
|
+
function statBar(value, max, width, color) {
|
|
1269
|
+
const filled = max > 0 ? Math.min(width, Math.round(value / max * width)) : 0;
|
|
1270
|
+
const bar = ansiColor("\u2593".repeat(filled), color) + ansiDim("\u2591".repeat(width - filled));
|
|
1271
|
+
return bar;
|
|
1272
|
+
}
|
|
1273
|
+
function wrapText2(text, maxWidth) {
|
|
1274
|
+
const words = text.split(" ");
|
|
1275
|
+
const lines = [];
|
|
1276
|
+
let current = "";
|
|
1277
|
+
for (const word of words) {
|
|
1278
|
+
if (current.length + word.length + 1 > maxWidth && current.length > 0) {
|
|
1279
|
+
lines.push(current);
|
|
1280
|
+
current = word;
|
|
1281
|
+
} else {
|
|
1282
|
+
current = current.length > 0 ? `${current} ${word}` : word;
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
if (current.length > 0) lines.push(current);
|
|
1286
|
+
return lines;
|
|
1287
|
+
}
|
|
1288
|
+
function renderProfilePage(buf, rows, cols, companion) {
|
|
1289
|
+
const innerWidth = COMPANION_WIDTH - 2;
|
|
1290
|
+
const x = Math.max(0, Math.floor((cols - COMPANION_WIDTH) / 2));
|
|
1291
|
+
const unlockedCount = companion.achievements.length;
|
|
1292
|
+
const totalAchievements = ACHIEVEMENTS.length;
|
|
1293
|
+
const endH = Math.floor(companion.stats.endurance / 36e5);
|
|
1294
|
+
const face = getMoodFace(companion.mood);
|
|
1295
|
+
const moodColor = MOOD_COLORS[companion.mood];
|
|
1296
|
+
const moodIcon = MOOD_ICONS[companion.mood];
|
|
1297
|
+
const faceColored = ansiColor(`(${face})`, moodColor, true);
|
|
1298
|
+
const repoNicknames = Object.values(companion.repos).map((r) => r.nickname).filter((n) => n !== null);
|
|
1299
|
+
const repoDisplay = repoNicknames.length > 0 ? ansiDim(` "${repoNicknames[0]}"`) : "";
|
|
1300
|
+
const barW = 18;
|
|
1301
|
+
const xpInLevel = companion.xp % 50;
|
|
1302
|
+
const xpBar = statBar(xpInLevel, 50, 20, "cyan");
|
|
1303
|
+
const lastAchievement = companion.achievements.length > 0 ? companion.achievements[companion.achievements.length - 1] : null;
|
|
1304
|
+
const lastDef = lastAchievement ? ACHIEVEMENTS.find((a) => a.id === lastAchievement.id) : null;
|
|
1305
|
+
const contentLines = [];
|
|
1306
|
+
contentLines.push(` ${faceColored} .${repoDisplay}`.padEnd(innerWidth));
|
|
1307
|
+
contentLines.push(" ".padEnd(innerWidth));
|
|
1308
|
+
contentLines.push(` ${ansiColor(`Lv ${companion.level}`, "cyan", true)} ${ansiBold(companion.title)}`.padEnd(innerWidth));
|
|
1309
|
+
contentLines.push(` ${xpBar} ${ansiDim(`${companion.xp} xp`)}`.padEnd(innerWidth));
|
|
1310
|
+
contentLines.push(" ".padEnd(innerWidth));
|
|
1311
|
+
contentLines.push(` ${ansiColor(moodIcon, moodColor)} ${ansiColor(companion.mood, moodColor)}`.padEnd(innerWidth));
|
|
1312
|
+
contentLines.push(" ".padEnd(innerWidth));
|
|
1313
|
+
contentLines.push(` ${ansiColor("STR", "red")} ${String(companion.stats.strength).padStart(4)} ${statBar(companion.stats.strength, 100, barW, "red")}`.padEnd(innerWidth));
|
|
1314
|
+
contentLines.push(` ${ansiColor("END", "yellow")} ${String(endH + "h").padStart(4)} ${statBar(endH, 500, barW, "yellow")}`.padEnd(innerWidth));
|
|
1315
|
+
contentLines.push(` ${ansiColor("WIS", "blue")} ${String(companion.stats.wisdom).padStart(4)} ${statBar(companion.stats.wisdom, 50, barW, "blue")}`.padEnd(innerWidth));
|
|
1316
|
+
contentLines.push(` ${ansiColor("PAT", "magenta")} ${String(companion.stats.patience).padStart(4)} ${statBar(companion.stats.patience, 200, barW, "magenta")}`.padEnd(innerWidth));
|
|
1317
|
+
contentLines.push(" ".padEnd(innerWidth));
|
|
1318
|
+
if (lastDef) {
|
|
1319
|
+
contentLines.push(` ${ansiColor("\u2605", "yellow")} ${lastDef.name} ${ansiDim(`${unlockedCount}/${totalAchievements}`)}`.padEnd(innerWidth));
|
|
1320
|
+
} else {
|
|
1321
|
+
contentLines.push(` ${ansiDim(`\u25C7 ${unlockedCount}/${totalAchievements} achievements`)}`.padEnd(innerWidth));
|
|
1322
|
+
}
|
|
1323
|
+
contentLines.push(" ".padEnd(innerWidth));
|
|
1324
|
+
if (companion.lastCommentary) {
|
|
1325
|
+
const raw = companion.lastCommentary.text;
|
|
1326
|
+
const wrapped = wrapText2(raw, innerWidth - 6);
|
|
1327
|
+
contentLines.push(` ${ansiDim("\u250A")} ${ansiColor(wrapped[0] ?? "", "white")}`.padEnd(innerWidth));
|
|
1328
|
+
for (let i = 1; i < wrapped.length; i++) {
|
|
1329
|
+
contentLines.push(` ${ansiDim("\u250A")} ${ansiColor(wrapped[i] ?? "", "white")}`.padEnd(innerWidth));
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
contentLines.push(" ".padEnd(innerWidth));
|
|
1333
|
+
contentLines.push(` ${ansiDim("tab \u2192 badges ? \u2192 stat guide")}`.padEnd(innerWidth));
|
|
1334
|
+
const height = Math.min(contentLines.length + 4, rows - 2);
|
|
1335
|
+
const y = Math.max(0, Math.floor((rows - height) / 2));
|
|
1336
|
+
drawBorder(buf, x, y, COMPANION_WIDTH, height, "cyan");
|
|
1337
|
+
writeClipped(buf, x + 1, y + 1, ansiColor(" COMPANION (esc to close)".padEnd(innerWidth), "cyan", true), innerWidth);
|
|
1338
|
+
writeClipped(buf, x + 1, y + 2, " ".padEnd(innerWidth), innerWidth);
|
|
1339
|
+
const availableContentRows = height - 4;
|
|
1340
|
+
for (let i = 0; i < Math.min(contentLines.length, availableContentRows); i++) {
|
|
1341
|
+
writeClipped(buf, x + 1, y + 3 + i, contentLines[i], innerWidth);
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
var GALLERY_WIDTH = 50;
|
|
1345
|
+
var _badgeScroll = 0;
|
|
1346
|
+
function badgeListScrollUp() {
|
|
1347
|
+
_badgeScroll = Math.max(0, _badgeScroll - 1);
|
|
1348
|
+
}
|
|
1349
|
+
function badgeListScrollDown() {
|
|
1350
|
+
_badgeScroll++;
|
|
1351
|
+
}
|
|
1352
|
+
function renderBadgesPage(buf, rows, cols, companion) {
|
|
1353
|
+
if (!_gallery) _gallery = createBadgeGallery(companion.achievements);
|
|
1354
|
+
const gallery = _gallery;
|
|
1355
|
+
const innerWidth = GALLERY_WIDTH - 2;
|
|
1356
|
+
const x = Math.max(0, Math.floor((cols - GALLERY_WIDTH) / 2));
|
|
1357
|
+
const unlockedCount = companion.achievements.length;
|
|
1358
|
+
const totalAchievements = ACHIEVEMENTS.length;
|
|
1359
|
+
const currentDef = gallery.achievements[gallery.currentIndex];
|
|
1360
|
+
const currentUnlock = gallery.unlocked.get(currentDef.id) ?? null;
|
|
1361
|
+
const card = renderBadgeCard(currentDef, currentUnlock);
|
|
1362
|
+
const contentLines = [];
|
|
1363
|
+
for (const cardLine of card.lines) {
|
|
1364
|
+
const stripped = cardLine.replace(/\x1b\[[0-9;]*m/g, "");
|
|
1365
|
+
const pad = Math.max(0, Math.floor((innerWidth - stripped.length) / 2));
|
|
1366
|
+
const padded = " ".repeat(pad) + cardLine + " ".repeat(Math.max(0, innerWidth - stripped.length - pad));
|
|
1367
|
+
contentLines.push(padded);
|
|
1368
|
+
}
|
|
1369
|
+
contentLines.push(" ".padEnd(innerWidth));
|
|
1370
|
+
const navIdx = gallery.currentIndex + 1;
|
|
1371
|
+
const navTotal = gallery.total;
|
|
1372
|
+
const unlockLabel = currentUnlock !== null ? " \u2713 unlocked" : " \xB7 locked";
|
|
1373
|
+
const navLine = ` \u2190 ${navIdx}/${navTotal} \u2192 ${unlockedCount}/${totalAchievements} earned${unlockLabel}`;
|
|
1374
|
+
contentLines.push(navLine.padEnd(innerWidth));
|
|
1375
|
+
contentLines.push(" ".padEnd(innerWidth));
|
|
1376
|
+
const listStartIdx = contentLines.length;
|
|
1377
|
+
const maxListRows = Math.min(6, Math.max(4, rows - 2 - 4 - listStartIdx - 2));
|
|
1378
|
+
const maxScroll = Math.max(0, gallery.total - maxListRows + 1);
|
|
1379
|
+
if (_badgeScroll > maxScroll) _badgeScroll = maxScroll;
|
|
1380
|
+
if (gallery.currentIndex < _badgeScroll) _badgeScroll = gallery.currentIndex;
|
|
1381
|
+
for (let pass = 0; pass < 3; pass++) {
|
|
1382
|
+
const a = _badgeScroll > 0 ? 1 : 0;
|
|
1383
|
+
const b = _badgeScroll + maxListRows < gallery.total ? 1 : 0;
|
|
1384
|
+
const vis = maxListRows - a - b;
|
|
1385
|
+
if (gallery.currentIndex >= _badgeScroll + vis) {
|
|
1386
|
+
_badgeScroll = gallery.currentIndex - vis + 1;
|
|
1387
|
+
} else break;
|
|
1388
|
+
}
|
|
1389
|
+
if (_badgeScroll > maxScroll) _badgeScroll = maxScroll;
|
|
1390
|
+
const hasMoreAbove = _badgeScroll > 0;
|
|
1391
|
+
const hasMoreBelow = _badgeScroll + maxListRows < gallery.total;
|
|
1392
|
+
const itemRows = maxListRows - (hasMoreAbove ? 1 : 0) - (hasMoreBelow ? 1 : 0);
|
|
1393
|
+
if (hasMoreAbove) {
|
|
1394
|
+
contentLines.push(ansiDim(` \u2191 ${_badgeScroll} more`.padEnd(innerWidth)));
|
|
1395
|
+
}
|
|
1396
|
+
for (let i = 0; i < itemRows && _badgeScroll + i < gallery.total; i++) {
|
|
1397
|
+
const idx = _badgeScroll + i;
|
|
1398
|
+
const def = gallery.achievements[idx];
|
|
1399
|
+
const u = gallery.unlocked.has(def.id);
|
|
1400
|
+
const icon = u ? "\u2713" : "\xB7";
|
|
1401
|
+
const isCurrent = idx === gallery.currentIndex;
|
|
1402
|
+
let line = ` ${icon} ${def.name}`.padEnd(innerWidth);
|
|
1403
|
+
if (isCurrent) line = ansiColor(line, "cyan", false);
|
|
1404
|
+
else if (!u) line = ansiDim(line);
|
|
1405
|
+
contentLines.push(line);
|
|
1406
|
+
}
|
|
1407
|
+
if (hasMoreBelow) {
|
|
1408
|
+
const below = gallery.total - _badgeScroll - itemRows;
|
|
1409
|
+
contentLines.push(ansiDim(` \u2193 ${below} more`.padEnd(innerWidth)));
|
|
1410
|
+
}
|
|
1411
|
+
contentLines.push(" ".padEnd(innerWidth));
|
|
1412
|
+
contentLines.push(ansiDim(" tab \u2192 profile ? \u2192 stat guide".padEnd(innerWidth)));
|
|
1413
|
+
const height = Math.min(contentLines.length + 4, rows - 2);
|
|
1414
|
+
const y = Math.max(0, Math.floor((rows - height) / 2));
|
|
1415
|
+
drawBorder(buf, x, y, GALLERY_WIDTH, height, "cyan");
|
|
1416
|
+
writeClipped(buf, x + 1, y + 1, ansiColor(" BADGES (\u2191\u2193 navigate, esc close)".padEnd(innerWidth), "cyan", true), innerWidth);
|
|
1417
|
+
writeClipped(buf, x + 1, y + 2, " ".padEnd(innerWidth), innerWidth);
|
|
1418
|
+
const availableContentRows = height - 4;
|
|
1419
|
+
for (let i = 0; i < Math.min(contentLines.length, availableContentRows); i++) {
|
|
1420
|
+
writeClipped(buf, x + 1, y + 3 + i, contentLines[i], innerWidth);
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
function renderHelpPage(buf, rows, cols) {
|
|
1424
|
+
const innerWidth = COMPANION_WIDTH - 2;
|
|
1425
|
+
const x = Math.max(0, Math.floor((cols - COMPANION_WIDTH) / 2));
|
|
1426
|
+
const divider2 = (label, color) => {
|
|
1427
|
+
const rest = innerWidth - label.length - 5;
|
|
1428
|
+
return ` ${ansiColor(label, color, true)} ${ansiDim("\u2500".repeat(Math.max(0, rest)))}`;
|
|
1429
|
+
};
|
|
1430
|
+
const contentLines = [];
|
|
1431
|
+
contentLines.push(divider2("STR (Strength)", "red"));
|
|
1432
|
+
contentLines.push(" +1 per completed session".padEnd(innerWidth));
|
|
1433
|
+
contentLines.push(" ".padEnd(innerWidth));
|
|
1434
|
+
contentLines.push(divider2("END (Endurance)", "yellow"));
|
|
1435
|
+
contentLines.push(" Total active time across sessions".padEnd(innerWidth));
|
|
1436
|
+
contentLines.push(" (displayed in hours)".padEnd(innerWidth));
|
|
1437
|
+
contentLines.push(" ".padEnd(innerWidth));
|
|
1438
|
+
contentLines.push(divider2("WIS (Wisdom)", "blue"));
|
|
1439
|
+
contentLines.push(" +1 per efficient session".padEnd(innerWidth));
|
|
1440
|
+
contentLines.push(ansiDim(" agents have <30% stddev in active").padEnd(innerWidth));
|
|
1441
|
+
contentLines.push(ansiDim(" time, 2+ agents required").padEnd(innerWidth));
|
|
1442
|
+
contentLines.push(" ".padEnd(innerWidth));
|
|
1443
|
+
contentLines.push(divider2("PAT (Patience)", "magenta"));
|
|
1444
|
+
contentLines.push(" +cycles per completed session".padEnd(innerWidth));
|
|
1445
|
+
contentLines.push(ansiDim(" +3 if validation mode").padEnd(innerWidth));
|
|
1446
|
+
contentLines.push(ansiDim(" +2 if completion mode").padEnd(innerWidth));
|
|
1447
|
+
contentLines.push(" ".padEnd(innerWidth));
|
|
1448
|
+
contentLines.push(divider2("XP & Level", "cyan"));
|
|
1449
|
+
contentLines.push(" STR\xD780 + END/h\xD715 + WIS\xD740".padEnd(innerWidth));
|
|
1450
|
+
contentLines.push(" + PAT\xD75".padEnd(innerWidth));
|
|
1451
|
+
contentLines.push(ansiDim(" level: 150 base xp, \xD71.35/lvl").padEnd(innerWidth));
|
|
1452
|
+
contentLines.push(" ".padEnd(innerWidth));
|
|
1453
|
+
contentLines.push(divider2("Mood", "white"));
|
|
1454
|
+
contentLines.push(" Real-time scoring from signals:".padEnd(innerWidth));
|
|
1455
|
+
contentLines.push(ansiDim(" time of day, idle time, crashes,").padEnd(innerWidth));
|
|
1456
|
+
contentLines.push(ansiDim(" streaks, session length, agents").padEnd(innerWidth));
|
|
1457
|
+
contentLines.push(" ".padEnd(innerWidth));
|
|
1458
|
+
contentLines.push(divider2("Badges", "yellow"));
|
|
1459
|
+
contentLines.push(" Milestones, session feats, time".padEnd(innerWidth));
|
|
1460
|
+
contentLines.push(" patterns, and behavioral checks".padEnd(innerWidth));
|
|
1461
|
+
contentLines.push(" ".padEnd(innerWidth));
|
|
1462
|
+
contentLines.push(ansiDim(" tab \u2192 back ? \u2192 close".padEnd(innerWidth)));
|
|
1463
|
+
const height = Math.min(contentLines.length + 4, rows - 2);
|
|
1464
|
+
const y = Math.max(0, Math.floor((rows - height) / 2));
|
|
1465
|
+
drawBorder(buf, x, y, COMPANION_WIDTH, height, "cyan");
|
|
1466
|
+
writeClipped(buf, x + 1, y + 1, ansiColor(" STAT GUIDE (? or esc to close)".padEnd(innerWidth), "cyan", true), innerWidth);
|
|
1467
|
+
writeClipped(buf, x + 1, y + 2, " ".padEnd(innerWidth), innerWidth);
|
|
1468
|
+
const availableContentRows = height - 4;
|
|
1469
|
+
for (let i = 0; i < Math.min(contentLines.length, availableContentRows); i++) {
|
|
1470
|
+
writeClipped(buf, x + 1, y + 3 + i, contentLines[i], innerWidth);
|
|
1471
|
+
}
|
|
1472
|
+
const trailingBlankRow = y + 3 + Math.min(contentLines.length, availableContentRows);
|
|
1473
|
+
if (trailingBlankRow < y + height - 1) {
|
|
1474
|
+
writeClipped(buf, x + 1, trailingBlankRow, " ".padEnd(innerWidth), innerWidth);
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
function renderCompanionOverlay(buf, rows, cols, companion) {
|
|
1478
|
+
if (_page === "help") {
|
|
1479
|
+
renderHelpPage(buf, rows, cols);
|
|
1480
|
+
} else if (_page === "badges") {
|
|
1481
|
+
renderBadgesPage(buf, rows, cols, companion);
|
|
1482
|
+
} else {
|
|
1483
|
+
renderProfilePage(buf, rows, cols, companion);
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
function renderCompanionDebugOverlay(buf, rows, cols, companion) {
|
|
1487
|
+
const innerWidth = DEBUG_WIDTH - 2;
|
|
1488
|
+
const x = Math.max(0, Math.floor((cols - DEBUG_WIDTH) / 2));
|
|
1489
|
+
const face = getMoodFace(companion.mood);
|
|
1490
|
+
const debug = companion.debugMood;
|
|
1491
|
+
const contentLines = [
|
|
1492
|
+
` (${face}) mood: ${companion.mood}`.padEnd(innerWidth),
|
|
1493
|
+
" ".padEnd(innerWidth)
|
|
1494
|
+
];
|
|
1495
|
+
if (debug) {
|
|
1496
|
+
const { signals, scores } = debug;
|
|
1497
|
+
contentLines.push(ansiDim(" \u2500\u2500 Signals \u2500\u2500".padEnd(innerWidth)));
|
|
1498
|
+
contentLines.push(` hourOfDay: ${signals.hourOfDay}`.padEnd(innerWidth));
|
|
1499
|
+
contentLines.push(` sessionLengthMs: ${signals.sessionLengthMs} (${Math.round(signals.sessionLengthMs / 6e4)}min)`.padEnd(innerWidth));
|
|
1500
|
+
contentLines.push(` idleDurationMs: ${signals.idleDurationMs} (${Math.round(signals.idleDurationMs / 6e4)}min)`.padEnd(innerWidth));
|
|
1501
|
+
contentLines.push(` recentCrashes: ${signals.recentCrashes}`.padEnd(innerWidth));
|
|
1502
|
+
contentLines.push(` cleanStreak: ${signals.cleanStreak}`.padEnd(innerWidth));
|
|
1503
|
+
contentLines.push(` justCompleted: ${signals.justCompleted}`.padEnd(innerWidth));
|
|
1504
|
+
contentLines.push(` justCrashed: ${signals.justCrashed}`.padEnd(innerWidth));
|
|
1505
|
+
contentLines.push(` justLeveledUp: ${signals.justLeveledUp}`.padEnd(innerWidth));
|
|
1506
|
+
contentLines.push(" ".padEnd(innerWidth));
|
|
1507
|
+
contentLines.push(ansiDim(" \u2500\u2500 Scores \u2500\u2500".padEnd(innerWidth)));
|
|
1508
|
+
const moodOrder = ["happy", "grinding", "frustrated", "zen", "sleepy", "excited", "existential"];
|
|
1509
|
+
for (const mood of moodOrder) {
|
|
1510
|
+
const score = scores[mood] ?? 0;
|
|
1511
|
+
const bar = score > 0 ? ansiDim("\u2588".repeat(Math.min(Math.round(score / 5), 12))) : "";
|
|
1512
|
+
const marker = mood === debug.winner ? " \u25C0" : "";
|
|
1513
|
+
contentLines.push(` ${mood.padEnd(12)} ${String(score).padStart(3)} ${bar}${marker}`.padEnd(innerWidth));
|
|
1514
|
+
}
|
|
1515
|
+
} else {
|
|
1516
|
+
contentLines.push(ansiDim(" No mood signals yet".padEnd(innerWidth)));
|
|
1517
|
+
contentLines.push(ansiDim(" (mood is time-of-day only)".padEnd(innerWidth)));
|
|
1518
|
+
}
|
|
1519
|
+
contentLines.push(" ".padEnd(innerWidth));
|
|
1520
|
+
const height = Math.min(contentLines.length + 4, rows - 2);
|
|
1521
|
+
const y = Math.max(0, Math.floor((rows - height) / 2));
|
|
1522
|
+
drawBorder(buf, x, y, DEBUG_WIDTH, height, "yellow");
|
|
1523
|
+
writeClipped(buf, x + 1, y + 1, ansiColor(" COMPANION DEBUG (esc to close)".padEnd(innerWidth), "yellow", true), innerWidth);
|
|
1524
|
+
writeClipped(buf, x + 1, y + 2, " ".padEnd(innerWidth), innerWidth);
|
|
1525
|
+
const availableContentRows = height - 4;
|
|
1526
|
+
for (let i = 0; i < Math.min(contentLines.length, availableContentRows); i++) {
|
|
1527
|
+
writeClipped(buf, x + 1, y + 3 + i, contentLines[i], innerWidth);
|
|
1528
|
+
}
|
|
1529
|
+
const trailingRow = y + 3 + Math.min(contentLines.length, availableContentRows);
|
|
1530
|
+
if (trailingRow < y + height - 1) {
|
|
1531
|
+
writeClipped(buf, x + 1, trailingRow, " ".padEnd(innerWidth), innerWidth);
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
// src/tui/input.ts
|
|
1536
|
+
function activateNvimBypass(state2) {
|
|
1537
|
+
setRawBypass((data) => {
|
|
1538
|
+
if (!state2.nvimBridge?.ready) {
|
|
1539
|
+
deactivateNvimBypass();
|
|
1540
|
+
state2.focusPane = "tree";
|
|
1541
|
+
if (state2.mode === "compose") cancelCompose(state2);
|
|
1542
|
+
requestRender();
|
|
1543
|
+
return false;
|
|
1544
|
+
}
|
|
1545
|
+
if (data === " ") {
|
|
1546
|
+
if (state2.mode === "compose") {
|
|
1547
|
+
cancelCompose(state2);
|
|
1548
|
+
return true;
|
|
1549
|
+
}
|
|
1550
|
+
deactivateNvimBypass();
|
|
1551
|
+
state2.focusPane = state2.showCombinedView ? "logs" : "tree";
|
|
1552
|
+
requestRender();
|
|
1553
|
+
return true;
|
|
1554
|
+
}
|
|
1555
|
+
state2.nvimBridge.write(data);
|
|
1556
|
+
return true;
|
|
1557
|
+
});
|
|
1558
|
+
}
|
|
1559
|
+
function deactivateNvimBypass() {
|
|
1560
|
+
setRawBypass(null);
|
|
1561
|
+
}
|
|
1562
|
+
var COMPOSE_DIR = join2(tmpdir(), "sisyphus-nvim");
|
|
1563
|
+
function enterComposeMode(state2, action, actions) {
|
|
1564
|
+
if (!state2.nvimEnabled || !state2.nvimBridge?.ready) return false;
|
|
1565
|
+
mkdirSync(COMPOSE_DIR, { recursive: true });
|
|
1566
|
+
const id = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
1567
|
+
const tempFile = join2(COMPOSE_DIR, `compose-${id}.md`);
|
|
1568
|
+
const signalFile = join2(COMPOSE_DIR, `compose-signal-${id}`);
|
|
1569
|
+
writeFileSync(tempFile, "", "utf-8");
|
|
1570
|
+
state2.composePrevNvimFile = state2.prevNvimFile;
|
|
1571
|
+
state2.composeAction = action;
|
|
1572
|
+
state2.composeTempFile = tempFile;
|
|
1573
|
+
state2.composeSignalFile = signalFile;
|
|
1574
|
+
state2.mode = "compose";
|
|
1575
|
+
state2.focusPane = "detail";
|
|
1576
|
+
state2.nvimBridge.openComposeFile(tempFile, signalFile);
|
|
1577
|
+
activateNvimBypass(state2);
|
|
1578
|
+
state2.composePollTimer = setInterval(() => {
|
|
1579
|
+
checkComposeSignal(state2, actions);
|
|
1580
|
+
}, 100);
|
|
1581
|
+
requestRender();
|
|
1582
|
+
return true;
|
|
993
1583
|
}
|
|
994
|
-
function
|
|
1584
|
+
function cancelCompose(state2) {
|
|
1585
|
+
if (state2.composePollTimer !== null) {
|
|
1586
|
+
clearInterval(state2.composePollTimer);
|
|
1587
|
+
state2.composePollTimer = null;
|
|
1588
|
+
}
|
|
1589
|
+
if (state2.composeTempFile) {
|
|
1590
|
+
try {
|
|
1591
|
+
unlinkSync(state2.composeTempFile);
|
|
1592
|
+
} catch {
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
if (state2.composeSignalFile) {
|
|
1596
|
+
try {
|
|
1597
|
+
unlinkSync(state2.composeSignalFile);
|
|
1598
|
+
} catch {
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
state2.prevNvimFile = null;
|
|
1602
|
+
state2.composePrevNvimFile = null;
|
|
1603
|
+
state2.composeAction = null;
|
|
1604
|
+
state2.composeTempFile = null;
|
|
1605
|
+
state2.composeSignalFile = null;
|
|
995
1606
|
state2.mode = "navigate";
|
|
996
|
-
state2.
|
|
997
|
-
|
|
998
|
-
state2.inputCursorPos = 0;
|
|
1607
|
+
state2.focusPane = "tree";
|
|
1608
|
+
deactivateNvimBypass();
|
|
999
1609
|
requestRender();
|
|
1000
1610
|
}
|
|
1001
|
-
function
|
|
1002
|
-
if (
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1611
|
+
function checkComposeSignal(state2, actions) {
|
|
1612
|
+
if (!state2.composeSignalFile || !state2.composeAction) return;
|
|
1613
|
+
if (!state2.nvimBridge?.ready) {
|
|
1614
|
+
cancelCompose(state2);
|
|
1615
|
+
return;
|
|
1616
|
+
}
|
|
1617
|
+
if (!existsSync(state2.composeSignalFile)) return;
|
|
1618
|
+
let signalContent = "";
|
|
1619
|
+
try {
|
|
1620
|
+
signalContent = readFileSync(state2.composeSignalFile, "utf-8").trim();
|
|
1621
|
+
} catch {
|
|
1622
|
+
}
|
|
1623
|
+
if (signalContent === "cancel") {
|
|
1624
|
+
cancelCompose(state2);
|
|
1625
|
+
return;
|
|
1626
|
+
}
|
|
1627
|
+
let content = "";
|
|
1628
|
+
if (state2.composeTempFile) {
|
|
1629
|
+
try {
|
|
1630
|
+
content = readFileSync(state2.composeTempFile, "utf-8").trim();
|
|
1631
|
+
} catch {
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
const action = state2.composeAction;
|
|
1635
|
+
const required = !OPTIONAL_COMPOSE.has(action.kind);
|
|
1636
|
+
if (required && !content) {
|
|
1637
|
+
try {
|
|
1638
|
+
unlinkSync(state2.composeSignalFile);
|
|
1639
|
+
} catch {
|
|
1007
1640
|
}
|
|
1641
|
+
notify(state2, "Content required");
|
|
1642
|
+
return;
|
|
1008
1643
|
}
|
|
1644
|
+
dispatchComposeAction(action, content, state2, actions);
|
|
1645
|
+
cancelCompose(state2);
|
|
1009
1646
|
}
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
case "resume": {
|
|
1014
|
-
if (!selectedSessionId) break;
|
|
1647
|
+
function dispatchComposeAction(action, content, state2, actions) {
|
|
1648
|
+
switch (action.kind) {
|
|
1649
|
+
case "new-session":
|
|
1015
1650
|
actions.sendAndNotify(
|
|
1016
|
-
{ type: "
|
|
1017
|
-
"Session
|
|
1651
|
+
{ type: "start", task: content, cwd: state2.cwd },
|
|
1652
|
+
"Session created"
|
|
1018
1653
|
);
|
|
1019
1654
|
break;
|
|
1020
|
-
|
|
1021
|
-
case "continue": {
|
|
1022
|
-
if (!selectedSessionId) break;
|
|
1023
|
-
try {
|
|
1024
|
-
const contRes = await actions.send({ type: "continue", sessionId: selectedSessionId });
|
|
1025
|
-
if (!contRes.ok) {
|
|
1026
|
-
notify(state2, `Error: ${contRes.error}`);
|
|
1027
|
-
break;
|
|
1028
|
-
}
|
|
1029
|
-
actions.sendAndNotify(
|
|
1030
|
-
{ type: "resume", sessionId: selectedSessionId, cwd: state2.cwd, message: text || void 0 },
|
|
1031
|
-
"Session continued"
|
|
1032
|
-
);
|
|
1033
|
-
} catch (err) {
|
|
1034
|
-
notify(state2, `Error: ${err.message}`);
|
|
1035
|
-
}
|
|
1036
|
-
break;
|
|
1037
|
-
}
|
|
1038
|
-
case "rollback": {
|
|
1039
|
-
if (!selectedSessionId) break;
|
|
1040
|
-
const toCycle = parseInt(text, 10);
|
|
1041
|
-
if (isNaN(toCycle) || toCycle < 1) {
|
|
1042
|
-
notify(state2, "Invalid cycle number");
|
|
1043
|
-
break;
|
|
1044
|
-
}
|
|
1655
|
+
case "message-orchestrator":
|
|
1045
1656
|
actions.sendAndNotify(
|
|
1046
|
-
{ type: "
|
|
1047
|
-
|
|
1657
|
+
{ type: "message", sessionId: action.sessionId, content },
|
|
1658
|
+
"Message queued"
|
|
1048
1659
|
);
|
|
1049
1660
|
break;
|
|
1050
|
-
|
|
1051
|
-
case "delete-confirm": {
|
|
1052
|
-
if (!selectedSessionId) break;
|
|
1053
|
-
if (text !== "yes") {
|
|
1054
|
-
notify(state2, 'Delete cancelled (type "yes" to confirm)');
|
|
1055
|
-
break;
|
|
1056
|
-
}
|
|
1661
|
+
case "resume":
|
|
1057
1662
|
actions.sendAndNotify(
|
|
1058
|
-
{ type: "
|
|
1059
|
-
"Session
|
|
1663
|
+
{ type: "resume", sessionId: action.sessionId, cwd: state2.cwd, message: content || void 0 },
|
|
1664
|
+
"Session resumed"
|
|
1060
1665
|
);
|
|
1061
1666
|
break;
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1667
|
+
case "continue":
|
|
1668
|
+
void (async () => {
|
|
1669
|
+
try {
|
|
1670
|
+
const contRes = await actions.send({ type: "continue", sessionId: action.sessionId });
|
|
1671
|
+
if (!contRes.ok) {
|
|
1672
|
+
notify(state2, `Error: ${contRes.error}`);
|
|
1673
|
+
return;
|
|
1674
|
+
}
|
|
1675
|
+
actions.sendAndNotify(
|
|
1676
|
+
{ type: "resume", sessionId: action.sessionId, cwd: state2.cwd, message: content || void 0 },
|
|
1677
|
+
"Session continued"
|
|
1678
|
+
);
|
|
1679
|
+
} catch (err) {
|
|
1680
|
+
notify(state2, `Error: ${err.message}`);
|
|
1681
|
+
}
|
|
1682
|
+
})();
|
|
1683
|
+
break;
|
|
1684
|
+
case "spawn-agent":
|
|
1069
1685
|
actions.sendAndNotify(
|
|
1070
1686
|
{
|
|
1071
1687
|
type: "spawn",
|
|
1072
|
-
sessionId:
|
|
1688
|
+
sessionId: action.sessionId,
|
|
1073
1689
|
agentType: "default",
|
|
1074
1690
|
name: "agent",
|
|
1075
|
-
instruction:
|
|
1691
|
+
instruction: content
|
|
1076
1692
|
},
|
|
1077
1693
|
"Agent spawned"
|
|
1078
1694
|
);
|
|
1079
1695
|
break;
|
|
1080
|
-
|
|
1081
|
-
case "search": {
|
|
1082
|
-
state2.searchFilter = text.trim() || null;
|
|
1083
|
-
break;
|
|
1084
|
-
}
|
|
1085
|
-
case "message-agent": {
|
|
1086
|
-
if (!selectedSessionId || !state2.targetAgentId) break;
|
|
1696
|
+
case "message-agent":
|
|
1087
1697
|
actions.sendAndNotify(
|
|
1088
|
-
{ type: "message", sessionId:
|
|
1089
|
-
`Message sent to ${
|
|
1698
|
+
{ type: "message", sessionId: action.sessionId, content, source: { type: "agent", agentId: action.agentId } },
|
|
1699
|
+
`Message sent to ${action.agentId}`
|
|
1090
1700
|
);
|
|
1091
|
-
state2.targetAgentId = null;
|
|
1092
|
-
break;
|
|
1093
|
-
}
|
|
1094
|
-
case "shell-command": {
|
|
1095
|
-
if (!text.trim()) break;
|
|
1096
|
-
try {
|
|
1097
|
-
actions.openShellPopup(state2.cwd, text);
|
|
1098
|
-
} catch {
|
|
1099
|
-
notify(state2, "Failed to run shell command");
|
|
1100
|
-
}
|
|
1101
1701
|
break;
|
|
1102
|
-
}
|
|
1103
1702
|
}
|
|
1104
|
-
state2.mode = "navigate";
|
|
1105
|
-
state2.inputText = "";
|
|
1106
|
-
state2.inputCursorPos = 0;
|
|
1107
|
-
requestRender();
|
|
1108
1703
|
}
|
|
1109
|
-
function
|
|
1110
|
-
if (
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
}
|
|
1116
|
-
if (key.escape) {
|
|
1117
|
-
handleCancel(state2);
|
|
1118
|
-
return;
|
|
1119
|
-
}
|
|
1120
|
-
if (key.leftArrow) {
|
|
1121
|
-
state2.inputCursorPos = Math.max(0, state2.inputCursorPos - 1);
|
|
1122
|
-
requestRender();
|
|
1123
|
-
return;
|
|
1124
|
-
}
|
|
1125
|
-
if (key.rightArrow) {
|
|
1126
|
-
state2.inputCursorPos = Math.min(state2.inputText.length, state2.inputCursorPos + 1);
|
|
1127
|
-
requestRender();
|
|
1128
|
-
return;
|
|
1129
|
-
}
|
|
1130
|
-
if (key.ctrl && input === "a") {
|
|
1131
|
-
state2.inputCursorPos = 0;
|
|
1132
|
-
requestRender();
|
|
1133
|
-
return;
|
|
1134
|
-
}
|
|
1135
|
-
if (key.ctrl && input === "e") {
|
|
1136
|
-
state2.inputCursorPos = state2.inputText.length;
|
|
1137
|
-
requestRender();
|
|
1138
|
-
return;
|
|
1139
|
-
}
|
|
1140
|
-
if (key.ctrl && input === "k") {
|
|
1141
|
-
state2.inputText = state2.inputText.slice(0, state2.inputCursorPos);
|
|
1142
|
-
requestRender();
|
|
1143
|
-
return;
|
|
1144
|
-
}
|
|
1145
|
-
if (key.ctrl && input === "u") {
|
|
1146
|
-
state2.inputText = state2.inputText.slice(state2.inputCursorPos);
|
|
1147
|
-
state2.inputCursorPos = 0;
|
|
1148
|
-
requestRender();
|
|
1149
|
-
return;
|
|
1150
|
-
}
|
|
1151
|
-
if (key.backspace) {
|
|
1152
|
-
if (state2.inputCursorPos > 0) {
|
|
1153
|
-
state2.inputText = state2.inputText.slice(0, state2.inputCursorPos - 1) + state2.inputText.slice(state2.inputCursorPos);
|
|
1154
|
-
state2.inputCursorPos -= 1;
|
|
1155
|
-
requestRender();
|
|
1156
|
-
}
|
|
1157
|
-
return;
|
|
1158
|
-
}
|
|
1159
|
-
if (key.delete) {
|
|
1160
|
-
if (state2.inputCursorPos < state2.inputText.length) {
|
|
1161
|
-
state2.inputText = state2.inputText.slice(0, state2.inputCursorPos) + state2.inputText.slice(state2.inputCursorPos + 1);
|
|
1162
|
-
requestRender();
|
|
1704
|
+
function expandSessionLatestCycle(state2, node) {
|
|
1705
|
+
if (node.type === "session" && state2.selectedSession?.id === node.sessionId) {
|
|
1706
|
+
const cycles = state2.selectedSession.orchestratorCycles;
|
|
1707
|
+
if (cycles.length > 0) {
|
|
1708
|
+
const latest = cycles[cycles.length - 1];
|
|
1709
|
+
state2.expanded.add(`cycle:${node.sessionId}:${latest.cycle}`);
|
|
1163
1710
|
}
|
|
1164
|
-
return;
|
|
1165
|
-
}
|
|
1166
|
-
if (input && !key.ctrl && !key.meta) {
|
|
1167
|
-
state2.inputText = state2.inputText.slice(0, state2.inputCursorPos) + input + state2.inputText.slice(state2.inputCursorPos);
|
|
1168
|
-
state2.inputCursorPos += input.length;
|
|
1169
|
-
requestRender();
|
|
1170
1711
|
}
|
|
1171
1712
|
}
|
|
1172
|
-
function handleReportDetailKey(input, key, state2,
|
|
1713
|
+
function handleReportDetailKey(input, key, state2, _actions) {
|
|
1173
1714
|
if (key.escape || key.return) {
|
|
1174
|
-
|
|
1715
|
+
state2.mode = "navigate";
|
|
1716
|
+
requestRender();
|
|
1175
1717
|
return;
|
|
1176
1718
|
}
|
|
1177
1719
|
if (key.upArrow) {
|
|
@@ -1253,9 +1795,18 @@ function handleLeaderAction(action, state2, actions) {
|
|
|
1253
1795
|
notify(state2, "No session selected");
|
|
1254
1796
|
break;
|
|
1255
1797
|
}
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1798
|
+
const editor = actions.resolveEditor();
|
|
1799
|
+
try {
|
|
1800
|
+
const text = actions.editInPopup(state2.cwd, editor);
|
|
1801
|
+
if (text?.trim() === "yes") {
|
|
1802
|
+
actions.sendAndNotify({ type: "delete", sessionId: selectedSessionId, cwd: state2.cwd }, "Session deleted");
|
|
1803
|
+
} else {
|
|
1804
|
+
notify(state2, 'Delete cancelled (type "yes" to confirm)');
|
|
1805
|
+
}
|
|
1806
|
+
} catch {
|
|
1807
|
+
notify(state2, "Failed to open editor");
|
|
1808
|
+
}
|
|
1809
|
+
break;
|
|
1259
1810
|
}
|
|
1260
1811
|
case "open-logs": {
|
|
1261
1812
|
try {
|
|
@@ -1277,10 +1828,12 @@ function handleLeaderAction(action, state2, actions) {
|
|
|
1277
1828
|
}
|
|
1278
1829
|
break;
|
|
1279
1830
|
}
|
|
1280
|
-
case "search":
|
|
1831
|
+
case "search": {
|
|
1281
1832
|
state2.mode = "search";
|
|
1833
|
+
state2.searchText = "";
|
|
1282
1834
|
requestRender();
|
|
1283
1835
|
return;
|
|
1836
|
+
}
|
|
1284
1837
|
case "jump-to-session": {
|
|
1285
1838
|
let count = 0;
|
|
1286
1839
|
for (let i = 0; i < nodes.length; i++) {
|
|
@@ -1301,9 +1854,21 @@ function handleLeaderAction(action, state2, actions) {
|
|
|
1301
1854
|
break;
|
|
1302
1855
|
}
|
|
1303
1856
|
if (enterComposeMode(state2, { kind: "spawn-agent", sessionId: selectedSessionId }, actions)) return;
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1857
|
+
{
|
|
1858
|
+
const editor = actions.resolveEditor();
|
|
1859
|
+
try {
|
|
1860
|
+
const content = actions.editInPopup(state2.cwd, editor);
|
|
1861
|
+
if (content?.trim()) {
|
|
1862
|
+
actions.sendAndNotify(
|
|
1863
|
+
{ type: "spawn", sessionId: selectedSessionId, agentType: "default", name: "agent", instruction: content },
|
|
1864
|
+
"Agent spawned"
|
|
1865
|
+
);
|
|
1866
|
+
}
|
|
1867
|
+
} catch {
|
|
1868
|
+
notify(state2, "Failed to open editor");
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
break;
|
|
1307
1872
|
}
|
|
1308
1873
|
case "message-agent": {
|
|
1309
1874
|
const agent = actions.getAgentForNode(cursorNode);
|
|
@@ -1312,23 +1877,49 @@ function handleLeaderAction(action, state2, actions) {
|
|
|
1312
1877
|
break;
|
|
1313
1878
|
}
|
|
1314
1879
|
if (enterComposeMode(state2, { kind: "message-agent", sessionId: selectedSessionId, agentId: agent.id }, actions)) return;
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1880
|
+
{
|
|
1881
|
+
const editor = actions.resolveEditor();
|
|
1882
|
+
try {
|
|
1883
|
+
const content = actions.editInPopup(state2.cwd, editor);
|
|
1884
|
+
if (content?.trim()) {
|
|
1885
|
+
actions.sendAndNotify(
|
|
1886
|
+
{ type: "message", sessionId: selectedSessionId, content, source: { type: "agent", agentId: agent.id } },
|
|
1887
|
+
`Message sent to ${agent.id}`
|
|
1888
|
+
);
|
|
1889
|
+
}
|
|
1890
|
+
} catch {
|
|
1891
|
+
notify(state2, "Failed to open editor");
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
break;
|
|
1319
1895
|
}
|
|
1320
1896
|
case "help":
|
|
1321
1897
|
state2.mode = "help";
|
|
1322
1898
|
requestRender();
|
|
1323
1899
|
return;
|
|
1900
|
+
case "companion-overlay":
|
|
1901
|
+
state2.mode = "companion-overlay";
|
|
1902
|
+
requestRender();
|
|
1903
|
+
return;
|
|
1904
|
+
case "companion-debug":
|
|
1905
|
+
state2.mode = "companion-debug";
|
|
1906
|
+
requestRender();
|
|
1907
|
+
return;
|
|
1324
1908
|
case "shell-command": {
|
|
1325
1909
|
if (!selectedSessionId) {
|
|
1326
1910
|
notify(state2, "No session selected");
|
|
1327
1911
|
break;
|
|
1328
1912
|
}
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1913
|
+
const editor = actions.resolveEditor();
|
|
1914
|
+
try {
|
|
1915
|
+
const text = actions.editInPopup(state2.cwd, editor);
|
|
1916
|
+
if (text?.trim()) {
|
|
1917
|
+
actions.openShellPopup(state2.cwd, text.trim());
|
|
1918
|
+
}
|
|
1919
|
+
} catch {
|
|
1920
|
+
notify(state2, "Failed to open editor");
|
|
1921
|
+
}
|
|
1922
|
+
break;
|
|
1332
1923
|
}
|
|
1333
1924
|
case "jump-to-pane": {
|
|
1334
1925
|
const agent = actions.getAgentForNode(cursorNode);
|
|
@@ -1364,6 +1955,7 @@ function handleLeaderAction(action, state2, actions) {
|
|
|
1364
1955
|
actions.cleanup();
|
|
1365
1956
|
return;
|
|
1366
1957
|
case "dismiss":
|
|
1958
|
+
closeBadgeGallery();
|
|
1367
1959
|
break;
|
|
1368
1960
|
}
|
|
1369
1961
|
state2.mode = "navigate";
|
|
@@ -1407,6 +1999,14 @@ function handleLeaderKey(input, key, state2, actions) {
|
|
|
1407
1999
|
handleLeaderAction({ type: "help" }, state2, actions);
|
|
1408
2000
|
return;
|
|
1409
2001
|
}
|
|
2002
|
+
if (input === "c") {
|
|
2003
|
+
handleLeaderAction({ type: "companion-overlay" }, state2, actions);
|
|
2004
|
+
return;
|
|
2005
|
+
}
|
|
2006
|
+
if (input === "D") {
|
|
2007
|
+
handleLeaderAction({ type: "companion-debug" }, state2, actions);
|
|
2008
|
+
return;
|
|
2009
|
+
}
|
|
1410
2010
|
if (input === "!") {
|
|
1411
2011
|
handleLeaderAction({ type: "shell-command" }, state2, actions);
|
|
1412
2012
|
return;
|
|
@@ -1461,6 +2061,54 @@ function handleLeaderKey(input, key, state2, actions) {
|
|
|
1461
2061
|
return;
|
|
1462
2062
|
}
|
|
1463
2063
|
}
|
|
2064
|
+
if (state2.mode === "companion-overlay") {
|
|
2065
|
+
if (input === "?") {
|
|
2066
|
+
if (getCompanionPage() === "help") companionOverlayDismissHelp();
|
|
2067
|
+
else companionOverlayShowHelp();
|
|
2068
|
+
requestRender();
|
|
2069
|
+
return;
|
|
2070
|
+
}
|
|
2071
|
+
if (key.escape) {
|
|
2072
|
+
if (getCompanionPage() === "help") {
|
|
2073
|
+
companionOverlayDismissHelp();
|
|
2074
|
+
requestRender();
|
|
2075
|
+
return;
|
|
2076
|
+
}
|
|
2077
|
+
handleLeaderAction({ type: "dismiss" }, state2, actions);
|
|
2078
|
+
return;
|
|
2079
|
+
}
|
|
2080
|
+
if (key.tab) {
|
|
2081
|
+
companionOverlayNextPage();
|
|
2082
|
+
requestRender();
|
|
2083
|
+
return;
|
|
2084
|
+
}
|
|
2085
|
+
if (getCompanionPage() === "badges") {
|
|
2086
|
+
if (key.upArrow || input === "k") {
|
|
2087
|
+
badgeGalleryLeft();
|
|
2088
|
+
requestRender();
|
|
2089
|
+
return;
|
|
2090
|
+
}
|
|
2091
|
+
if (key.downArrow || input === "j") {
|
|
2092
|
+
badgeGalleryRight();
|
|
2093
|
+
requestRender();
|
|
2094
|
+
return;
|
|
2095
|
+
}
|
|
2096
|
+
if (key.leftArrow || input === "h") {
|
|
2097
|
+
badgeListScrollUp();
|
|
2098
|
+
requestRender();
|
|
2099
|
+
return;
|
|
2100
|
+
}
|
|
2101
|
+
if (key.rightArrow || input === "l") {
|
|
2102
|
+
badgeListScrollDown();
|
|
2103
|
+
requestRender();
|
|
2104
|
+
return;
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
return;
|
|
2108
|
+
}
|
|
2109
|
+
if (state2.mode === "companion-debug") {
|
|
2110
|
+
handleLeaderAction({ type: "dismiss" }, state2, actions);
|
|
2111
|
+
}
|
|
1464
2112
|
}
|
|
1465
2113
|
function handleNavigateKey(input, key, state2, actions) {
|
|
1466
2114
|
const nodes = actions.getNodes();
|
|
@@ -1602,41 +2250,24 @@ function handleNavigateKey(input, key, state2, actions) {
|
|
|
1602
2250
|
notify(state2, "No session selected");
|
|
1603
2251
|
return;
|
|
1604
2252
|
}
|
|
1605
|
-
if (session.status
|
|
1606
|
-
const lastCycle = session.orchestratorCycles[session.orchestratorCycles.length - 1];
|
|
1607
|
-
const claudeSessionId = lastCycle?.claudeSessionId;
|
|
1608
|
-
if (!claudeSessionId) {
|
|
1609
|
-
notify(state2, "No orchestrator Claude session ID available");
|
|
1610
|
-
return;
|
|
1611
|
-
}
|
|
1612
|
-
try {
|
|
1613
|
-
const label = session.name ?? state2.selectedSessionId.slice(0, 8);
|
|
1614
|
-
const sessionName = actions.openClaudeResumeSession(state2.cwd, claudeSessionId, label);
|
|
1615
|
-
actions.switchToSession(sessionName);
|
|
1616
|
-
} catch {
|
|
1617
|
-
notify(state2, "Failed to open Claude session");
|
|
1618
|
-
}
|
|
1619
|
-
return;
|
|
1620
|
-
}
|
|
1621
|
-
if (state2.paneAlive && session.tmuxWindowId) {
|
|
2253
|
+
if (session.status !== "completed" && state2.paneAlive && session.tmuxWindowId) {
|
|
1622
2254
|
if (session.tmuxSessionName) actions.switchToSession(session.tmuxSessionName);
|
|
1623
2255
|
actions.selectWindow(session.tmuxWindowId);
|
|
1624
2256
|
return;
|
|
1625
2257
|
}
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
})();
|
|
2258
|
+
const lastCycle = session.orchestratorCycles[session.orchestratorCycles.length - 1];
|
|
2259
|
+
const claudeSessionId = lastCycle?.claudeSessionId;
|
|
2260
|
+
if (!claudeSessionId) {
|
|
2261
|
+
notify(state2, "No orchestrator Claude session ID available");
|
|
2262
|
+
return;
|
|
2263
|
+
}
|
|
2264
|
+
try {
|
|
2265
|
+
const label = session.name ?? state2.selectedSessionId.slice(0, 8);
|
|
2266
|
+
const sessionName = actions.openClaudeResumeSession(state2.cwd, claudeSessionId, label, lastCycle.resumeEnv, lastCycle.resumeArgs);
|
|
2267
|
+
actions.switchToSession(sessionName);
|
|
2268
|
+
} catch {
|
|
2269
|
+
notify(state2, "Failed to open Claude session");
|
|
2270
|
+
}
|
|
1640
2271
|
return;
|
|
1641
2272
|
}
|
|
1642
2273
|
if (input === "o") {
|
|
@@ -1645,19 +2276,25 @@ function handleNavigateKey(input, key, state2, actions) {
|
|
|
1645
2276
|
return;
|
|
1646
2277
|
}
|
|
1647
2278
|
let claudeSessionId;
|
|
2279
|
+
let resumeEnv;
|
|
2280
|
+
let resumeArgs;
|
|
1648
2281
|
if (cursorNode.type === "agent" || cursorNode.type === "report") {
|
|
1649
2282
|
const agent = actions.getAgentForNode(cursorNode);
|
|
1650
2283
|
claudeSessionId = agent?.claudeSessionId ?? void 0;
|
|
2284
|
+
resumeEnv = agent?.resumeEnv;
|
|
2285
|
+
resumeArgs = agent?.resumeArgs;
|
|
1651
2286
|
} else if (cursorNode.type === "cycle" && session) {
|
|
1652
2287
|
const cycle = session.orchestratorCycles.find((c) => c.cycle === cursorNode.cycleNumber);
|
|
1653
2288
|
claudeSessionId = cycle?.claudeSessionId;
|
|
2289
|
+
resumeEnv = cycle?.resumeEnv;
|
|
2290
|
+
resumeArgs = cycle?.resumeArgs;
|
|
1654
2291
|
}
|
|
1655
2292
|
if (!claudeSessionId) {
|
|
1656
2293
|
notify(state2, "No Claude session ID available");
|
|
1657
2294
|
return;
|
|
1658
2295
|
}
|
|
1659
2296
|
try {
|
|
1660
|
-
actions.openClaudeResumePopup(state2.cwd, claudeSessionId);
|
|
2297
|
+
actions.openClaudeResumePopup(state2.cwd, claudeSessionId, resumeEnv, resumeArgs);
|
|
1661
2298
|
} catch {
|
|
1662
2299
|
notify(state2, "Failed to open Claude session");
|
|
1663
2300
|
}
|
|
@@ -1668,479 +2305,245 @@ function handleNavigateKey(input, key, state2, actions) {
|
|
|
1668
2305
|
notify(state2, "No session selected");
|
|
1669
2306
|
return;
|
|
1670
2307
|
}
|
|
1671
|
-
const gp = goalPath(state2.cwd, state2.selectedSessionId);
|
|
1672
|
-
const editor = actions.resolveEditor();
|
|
1673
|
-
try {
|
|
1674
|
-
actions.openEditorPopup(state2.cwd, editor, gp, { w: "80", h: "50%" });
|
|
1675
|
-
} catch {
|
|
1676
|
-
notify(state2, `Failed to open goal in ${editor}`);
|
|
1677
|
-
}
|
|
1678
|
-
return;
|
|
1679
|
-
}
|
|
1680
|
-
if (input === "n") {
|
|
1681
|
-
if (enterComposeMode(state2, { kind: "new-session" }, actions)) return;
|
|
1682
|
-
const editor = actions.resolveEditor();
|
|
1683
|
-
try {
|
|
1684
|
-
const content = actions.editInPopup(state2.cwd, editor);
|
|
1685
|
-
if (content) {
|
|
1686
|
-
actions.sendAndNotify(
|
|
1687
|
-
{ type: "start", task: content, cwd: state2.cwd },
|
|
1688
|
-
"Session created"
|
|
1689
|
-
);
|
|
1690
|
-
}
|
|
1691
|
-
} catch {
|
|
1692
|
-
notify(state2, "Failed to open editor");
|
|
1693
|
-
}
|
|
1694
|
-
return;
|
|
1695
|
-
}
|
|
1696
|
-
if (input === "c") {
|
|
1697
|
-
try {
|
|
1698
|
-
actions.openCompanionPane(state2.cwd);
|
|
1699
|
-
} catch {
|
|
1700
|
-
notify(state2, "Failed to open companion pane");
|
|
1701
|
-
}
|
|
1702
|
-
return;
|
|
1703
|
-
}
|
|
1704
|
-
if (input === "p") {
|
|
1705
|
-
if (!state2.selectedSessionId) {
|
|
1706
|
-
notify(state2, "No session selected");
|
|
1707
|
-
return;
|
|
1708
|
-
}
|
|
1709
|
-
const pp = roadmapPath(state2.cwd, state2.selectedSessionId);
|
|
1710
|
-
const editor = actions.resolveEditor();
|
|
1711
|
-
try {
|
|
1712
|
-
actions.openEditorPopup(state2.cwd, editor, pp);
|
|
1713
|
-
} catch {
|
|
1714
|
-
notify(state2, `Failed to open roadmap in ${editor}`);
|
|
1715
|
-
}
|
|
1716
|
-
return;
|
|
1717
|
-
}
|
|
1718
|
-
if (input === "q") {
|
|
1719
|
-
actions.cleanup();
|
|
1720
|
-
}
|
|
1721
|
-
if (input === "r") {
|
|
1722
|
-
const agent = actions.getAgentForNode(cursorNode);
|
|
1723
|
-
if (!agent || !state2.selectedSessionId) {
|
|
1724
|
-
notify(state2, "Select an agent to re-run");
|
|
1725
|
-
return;
|
|
1726
|
-
}
|
|
1727
|
-
actions.sendAndNotify(
|
|
1728
|
-
{
|
|
1729
|
-
type: "spawn",
|
|
1730
|
-
sessionId: state2.selectedSessionId,
|
|
1731
|
-
agentType: agent.agentType,
|
|
1732
|
-
name: `${agent.name}-retry`,
|
|
1733
|
-
instruction: agent.instruction
|
|
1734
|
-
},
|
|
1735
|
-
`Re-spawned ${agent.name}`
|
|
1736
|
-
);
|
|
1737
|
-
return;
|
|
1738
|
-
}
|
|
1739
|
-
if (input === "R") {
|
|
1740
|
-
if (!state2.selectedSessionId) {
|
|
1741
|
-
notify(state2, "No session selected");
|
|
1742
|
-
return;
|
|
1743
|
-
}
|
|
1744
|
-
if (session?.status === "active" && state2.paneAlive) {
|
|
1745
|
-
notify(state2, "Session already active");
|
|
1746
|
-
return;
|
|
1747
|
-
}
|
|
1748
|
-
if (enterComposeMode(state2, { kind: "resume", sessionId: state2.selectedSessionId }, actions)) return;
|
|
1749
|
-
state2.mode = "resume";
|
|
1750
|
-
state2.inputText = "";
|
|
1751
|
-
state2.inputCursorPos = 0;
|
|
1752
|
-
requestRender();
|
|
1753
|
-
return;
|
|
1754
|
-
}
|
|
1755
|
-
if (input === "C") {
|
|
1756
|
-
if (!state2.selectedSessionId) {
|
|
1757
|
-
notify(state2, "No session selected");
|
|
1758
|
-
return;
|
|
1759
|
-
}
|
|
1760
|
-
if (session?.status !== "completed") {
|
|
1761
|
-
notify(state2, "Session not completed");
|
|
1762
|
-
return;
|
|
1763
|
-
}
|
|
1764
|
-
if (enterComposeMode(state2, { kind: "continue", sessionId: state2.selectedSessionId }, actions)) return;
|
|
1765
|
-
state2.mode = "continue";
|
|
1766
|
-
state2.inputText = "";
|
|
1767
|
-
state2.inputCursorPos = 0;
|
|
1768
|
-
requestRender();
|
|
1769
|
-
return;
|
|
1770
|
-
}
|
|
1771
|
-
if (input === "x") {
|
|
1772
|
-
const agent = actions.getAgentForNode(cursorNode);
|
|
1773
|
-
if (!agent || !state2.selectedSessionId) {
|
|
1774
|
-
notify(state2, "Select an agent to restart");
|
|
1775
|
-
return;
|
|
1776
|
-
}
|
|
1777
|
-
actions.sendAndNotify(
|
|
1778
|
-
{ type: "restart-agent", sessionId: state2.selectedSessionId, agentId: agent.id },
|
|
1779
|
-
`Restarted ${agent.id}`
|
|
1780
|
-
);
|
|
1781
|
-
return;
|
|
1782
|
-
}
|
|
1783
|
-
if (input === "b") {
|
|
1784
|
-
if (!state2.selectedSessionId) {
|
|
1785
|
-
notify(state2, "No session selected");
|
|
1786
|
-
return;
|
|
1787
|
-
}
|
|
1788
|
-
const defaultText = cursorNode?.type === "cycle" ? String(cursorNode.cycleNumber) : void 0;
|
|
1789
|
-
state2.mode = "rollback";
|
|
1790
|
-
if (defaultText) {
|
|
1791
|
-
state2.inputText = defaultText;
|
|
1792
|
-
state2.inputCursorPos = defaultText.length;
|
|
1793
|
-
} else {
|
|
1794
|
-
state2.inputText = "";
|
|
1795
|
-
state2.inputCursorPos = 0;
|
|
1796
|
-
}
|
|
1797
|
-
requestRender();
|
|
1798
|
-
return;
|
|
1799
|
-
}
|
|
1800
|
-
if (input === "e") {
|
|
1801
|
-
if (!cursorNode || cursorNode.type !== "context-file") return;
|
|
1802
|
-
const editor = actions.resolveEditor();
|
|
1803
|
-
try {
|
|
1804
|
-
actions.openEditorPopup(state2.cwd, editor, cursorNode.filePath);
|
|
1805
|
-
} catch {
|
|
1806
|
-
notify(state2, "Failed to open file in editor");
|
|
1807
|
-
}
|
|
1808
|
-
return;
|
|
1809
|
-
}
|
|
1810
|
-
if (input === "S") {
|
|
1811
|
-
if (!state2.selectedSessionId) {
|
|
1812
|
-
notify(state2, "No session selected");
|
|
1813
|
-
return;
|
|
1814
|
-
}
|
|
1815
|
-
const sp = strategyPath(state2.cwd, state2.selectedSessionId);
|
|
1816
|
-
const editor = actions.resolveEditor();
|
|
1817
|
-
try {
|
|
1818
|
-
actions.openEditorPopup(state2.cwd, editor, sp);
|
|
1819
|
-
} catch {
|
|
1820
|
-
notify(state2, `Failed to open strategy in ${editor}`);
|
|
1821
|
-
}
|
|
1822
|
-
return;
|
|
1823
|
-
}
|
|
1824
|
-
if (input === "t") {
|
|
1825
|
-
if (state2.showCombinedView) {
|
|
1826
|
-
if (state2.focusPane === "logs") state2.focusPane = "detail";
|
|
1827
|
-
state2.logsScroll.reset();
|
|
1828
|
-
}
|
|
1829
|
-
state2.showCombinedView = !state2.showCombinedView;
|
|
1830
|
-
requestRender();
|
|
1831
|
-
return;
|
|
1832
|
-
}
|
|
1833
|
-
}
|
|
1834
|
-
function handleKeypress(input, key, state2, actions) {
|
|
1835
|
-
if (state2.mode === "compose") return;
|
|
1836
|
-
if (INPUT_MODES.has(state2.mode)) {
|
|
1837
|
-
handleInputBarKey(input, key, state2, actions);
|
|
1838
|
-
} else if (state2.mode === "leader" || state2.mode === "copy-menu" || state2.mode === "help") {
|
|
1839
|
-
handleLeaderKey(input, key, state2, actions);
|
|
1840
|
-
} else if (state2.mode === "report-detail") {
|
|
1841
|
-
handleReportDetailKey(input, key, state2, actions);
|
|
1842
|
-
} else {
|
|
1843
|
-
handleNavigateKey(input, key, state2, actions);
|
|
1844
|
-
}
|
|
1845
|
-
}
|
|
1846
|
-
|
|
1847
|
-
// src/tui/render.ts
|
|
1848
|
-
import stringWidth2 from "string-width";
|
|
1849
|
-
var COLOR_SGR = {
|
|
1850
|
-
black: 30,
|
|
1851
|
-
red: 31,
|
|
1852
|
-
green: 32,
|
|
1853
|
-
yellow: 33,
|
|
1854
|
-
blue: 34,
|
|
1855
|
-
magenta: 35,
|
|
1856
|
-
cyan: 36,
|
|
1857
|
-
white: 37,
|
|
1858
|
-
gray: 90
|
|
1859
|
-
};
|
|
1860
|
-
function colorToSGR(color) {
|
|
1861
|
-
const code = COLOR_SGR[color];
|
|
1862
|
-
if (code === void 0) throw new Error(`Unknown color: ${color}`);
|
|
1863
|
-
return code;
|
|
1864
|
-
}
|
|
1865
|
-
function renderLine(segs) {
|
|
1866
|
-
let out = "";
|
|
1867
|
-
for (const s of segs) {
|
|
1868
|
-
const codes = [];
|
|
1869
|
-
if (s.bold) codes.push(1);
|
|
1870
|
-
if (s.dim) codes.push(2);
|
|
1871
|
-
if (s.italic) codes.push(3);
|
|
1872
|
-
if (s.inverse) codes.push(7);
|
|
1873
|
-
if (s.color) codes.push(colorToSGR(s.color));
|
|
1874
|
-
if (codes.length > 0) {
|
|
1875
|
-
out += `\x1B[${codes.join(";")}m${s.text}\x1B[0m`;
|
|
1876
|
-
} else {
|
|
1877
|
-
out += s.text;
|
|
1878
|
-
}
|
|
1879
|
-
}
|
|
1880
|
-
return out;
|
|
1881
|
-
}
|
|
1882
|
-
var cachedBlank = "";
|
|
1883
|
-
var cachedBlankWidth = 0;
|
|
1884
|
-
function createFrameBuffer(width, height) {
|
|
1885
|
-
if (width !== cachedBlankWidth) {
|
|
1886
|
-
cachedBlank = " ".repeat(width);
|
|
1887
|
-
cachedBlankWidth = width;
|
|
1888
|
-
}
|
|
1889
|
-
const lines = new Array(height);
|
|
1890
|
-
for (let i = 0; i < height; i++) lines[i] = cachedBlank;
|
|
1891
|
-
return { lines, width, height };
|
|
1892
|
-
}
|
|
1893
|
-
function copyRows(buf, src, startRow, count) {
|
|
1894
|
-
for (let i = 0; i < count && startRow + i < buf.height; i++) {
|
|
1895
|
-
buf.lines[startRow + i] = src[startRow + i];
|
|
1896
|
-
}
|
|
1897
|
-
}
|
|
1898
|
-
function flushFrame(frame, prevFrame2, suffix) {
|
|
1899
|
-
let out = "\x1B[?2026h";
|
|
1900
|
-
for (let i = 0; i < frame.length; i++) {
|
|
1901
|
-
if (frame[i] !== prevFrame2[i]) {
|
|
1902
|
-
out += `\x1B[${i + 1};1H`;
|
|
1903
|
-
out += "\x1B[2K";
|
|
1904
|
-
out += frame[i];
|
|
1905
|
-
}
|
|
2308
|
+
const gp = goalPath(state2.cwd, state2.selectedSessionId);
|
|
2309
|
+
const editor = actions.resolveEditor();
|
|
2310
|
+
try {
|
|
2311
|
+
actions.openEditorPopup(state2.cwd, editor, gp, { w: "80", h: "50%" });
|
|
2312
|
+
} catch {
|
|
2313
|
+
notify(state2, `Failed to open goal in ${editor}`);
|
|
2314
|
+
}
|
|
2315
|
+
return;
|
|
1906
2316
|
}
|
|
1907
|
-
if (
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
if (content[i] === "\x1B" && content[i + 1] === "[") {
|
|
1918
|
-
const seqLen = ansiLen(content, i);
|
|
1919
|
-
if (seqLen > 0) {
|
|
1920
|
-
out += content.substring(i, i + seqLen);
|
|
1921
|
-
i += seqLen;
|
|
1922
|
-
continue;
|
|
2317
|
+
if (input === "n") {
|
|
2318
|
+
if (enterComposeMode(state2, { kind: "new-session" }, actions)) return;
|
|
2319
|
+
const editor = actions.resolveEditor();
|
|
2320
|
+
try {
|
|
2321
|
+
const content = actions.editInPopup(state2.cwd, editor);
|
|
2322
|
+
if (content) {
|
|
2323
|
+
actions.sendAndNotify(
|
|
2324
|
+
{ type: "start", task: content, cwd: state2.cwd },
|
|
2325
|
+
"Session created"
|
|
2326
|
+
);
|
|
1923
2327
|
}
|
|
2328
|
+
} catch {
|
|
2329
|
+
notify(state2, "Failed to open editor");
|
|
1924
2330
|
}
|
|
1925
|
-
|
|
1926
|
-
const ch = String.fromCodePoint(cp);
|
|
1927
|
-
const chWidth = cp < 128 ? 1 : stringWidth2(ch);
|
|
1928
|
-
if (displayWidth + chWidth > maxWidth) break;
|
|
1929
|
-
out += ch;
|
|
1930
|
-
displayWidth += chWidth;
|
|
1931
|
-
i += ch.length;
|
|
1932
|
-
}
|
|
1933
|
-
if (out.includes("\x1B[") && !out.endsWith("\x1B[0m")) {
|
|
1934
|
-
out += "\x1B[0m";
|
|
2331
|
+
return;
|
|
1935
2332
|
}
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
}
|
|
1940
|
-
|
|
1941
|
-
let w = 0;
|
|
1942
|
-
let i = 0;
|
|
1943
|
-
while (i < s.length) {
|
|
1944
|
-
if (s[i] === "\x1B" && s[i + 1] === "[") {
|
|
1945
|
-
const len = ansiLen(s, i);
|
|
1946
|
-
if (len > 0) {
|
|
1947
|
-
i += len;
|
|
1948
|
-
continue;
|
|
1949
|
-
}
|
|
2333
|
+
if (input === "c") {
|
|
2334
|
+
try {
|
|
2335
|
+
actions.openCompanionPane(state2.cwd);
|
|
2336
|
+
} catch {
|
|
2337
|
+
notify(state2, "Failed to open companion pane");
|
|
1950
2338
|
}
|
|
1951
|
-
|
|
1952
|
-
const ch = String.fromCodePoint(cp);
|
|
1953
|
-
w += cp < 128 ? 1 : stringWidth2(ch);
|
|
1954
|
-
i += ch.length;
|
|
2339
|
+
return;
|
|
1955
2340
|
}
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
const
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
}
|
|
1966
|
-
|
|
2341
|
+
if (input === "p") {
|
|
2342
|
+
if (!state2.selectedSessionId) {
|
|
2343
|
+
notify(state2, "No session selected");
|
|
2344
|
+
return;
|
|
2345
|
+
}
|
|
2346
|
+
const pp = roadmapPath(state2.cwd, state2.selectedSessionId);
|
|
2347
|
+
const editor = actions.resolveEditor();
|
|
2348
|
+
try {
|
|
2349
|
+
actions.openEditorPopup(state2.cwd, editor, pp);
|
|
2350
|
+
} catch {
|
|
2351
|
+
notify(state2, `Failed to open roadmap in ${editor}`);
|
|
1967
2352
|
}
|
|
2353
|
+
return;
|
|
1968
2354
|
}
|
|
1969
|
-
if (
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
2355
|
+
if (input === "q") {
|
|
2356
|
+
actions.cleanup();
|
|
2357
|
+
}
|
|
2358
|
+
if (input === "r") {
|
|
2359
|
+
const agent = actions.getAgentForNode(cursorNode);
|
|
2360
|
+
if (!agent || !state2.selectedSessionId) {
|
|
2361
|
+
notify(state2, "Select an agent to re-run");
|
|
2362
|
+
return;
|
|
1973
2363
|
}
|
|
2364
|
+
actions.sendAndNotify(
|
|
2365
|
+
{
|
|
2366
|
+
type: "spawn",
|
|
2367
|
+
sessionId: state2.selectedSessionId,
|
|
2368
|
+
agentType: agent.agentType,
|
|
2369
|
+
name: `${agent.name}-retry`,
|
|
2370
|
+
instruction: agent.instruction
|
|
2371
|
+
},
|
|
2372
|
+
`Re-spawned ${agent.name}`
|
|
2373
|
+
);
|
|
2374
|
+
return;
|
|
1974
2375
|
}
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
const seqLen = ansiLen(content, i);
|
|
1997
|
-
if (seqLen > 0) {
|
|
1998
|
-
out += content.substring(i, i + seqLen);
|
|
1999
|
-
i += seqLen;
|
|
2000
|
-
continue;
|
|
2376
|
+
if (input === "R") {
|
|
2377
|
+
if (!state2.selectedSessionId) {
|
|
2378
|
+
notify(state2, "No session selected");
|
|
2379
|
+
return;
|
|
2380
|
+
}
|
|
2381
|
+
if (session?.status === "active" && state2.paneAlive) {
|
|
2382
|
+
notify(state2, "Session already active");
|
|
2383
|
+
return;
|
|
2384
|
+
}
|
|
2385
|
+
if (enterComposeMode(state2, { kind: "resume", sessionId: state2.selectedSessionId }, actions)) return;
|
|
2386
|
+
{
|
|
2387
|
+
const sessionId = state2.selectedSessionId;
|
|
2388
|
+
const editor = actions.resolveEditor();
|
|
2389
|
+
try {
|
|
2390
|
+
const content = actions.editInPopup(state2.cwd, editor);
|
|
2391
|
+
actions.sendAndNotify(
|
|
2392
|
+
{ type: "resume", sessionId, cwd: state2.cwd, message: content?.trim() || void 0 },
|
|
2393
|
+
"Session resumed"
|
|
2394
|
+
);
|
|
2395
|
+
} catch {
|
|
2396
|
+
notify(state2, "Failed to open editor");
|
|
2001
2397
|
}
|
|
2002
2398
|
}
|
|
2003
|
-
|
|
2004
|
-
const ch = String.fromCodePoint(cp);
|
|
2005
|
-
const chWidth = cp < 128 ? 1 : stringWidth2(ch);
|
|
2006
|
-
if (displayWidth + chWidth > maxWidth) break;
|
|
2007
|
-
out += ch;
|
|
2008
|
-
displayWidth += chWidth;
|
|
2009
|
-
i += ch.length;
|
|
2010
|
-
}
|
|
2011
|
-
if (out.includes("\x1B[") && !out.endsWith("\x1B[0m")) {
|
|
2012
|
-
out += "\x1B[0m";
|
|
2399
|
+
return;
|
|
2013
2400
|
}
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2401
|
+
if (input === "C") {
|
|
2402
|
+
if (!state2.selectedSessionId) {
|
|
2403
|
+
notify(state2, "No session selected");
|
|
2404
|
+
return;
|
|
2405
|
+
}
|
|
2406
|
+
if (session?.status !== "completed") {
|
|
2407
|
+
notify(state2, "Session not completed");
|
|
2408
|
+
return;
|
|
2409
|
+
}
|
|
2410
|
+
if (enterComposeMode(state2, { kind: "continue", sessionId: state2.selectedSessionId }, actions)) return;
|
|
2411
|
+
{
|
|
2412
|
+
const sessionId = state2.selectedSessionId;
|
|
2413
|
+
const editor = actions.resolveEditor();
|
|
2414
|
+
void (async () => {
|
|
2415
|
+
try {
|
|
2416
|
+
const content = actions.editInPopup(state2.cwd, editor);
|
|
2417
|
+
const contRes = await actions.send({ type: "continue", sessionId });
|
|
2418
|
+
if (!contRes.ok) {
|
|
2419
|
+
notify(state2, `Error: ${contRes.error}`);
|
|
2420
|
+
return;
|
|
2421
|
+
}
|
|
2422
|
+
actions.sendAndNotify(
|
|
2423
|
+
{ type: "resume", sessionId, cwd: state2.cwd, message: content?.trim() || void 0 },
|
|
2424
|
+
"Session continued"
|
|
2425
|
+
);
|
|
2426
|
+
} catch (err) {
|
|
2427
|
+
notify(state2, `Error: ${err.message}`);
|
|
2428
|
+
}
|
|
2429
|
+
})();
|
|
2430
|
+
}
|
|
2431
|
+
return;
|
|
2017
2432
|
}
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
}
|
|
2030
|
-
function drawBorder(buf, x, y, w, h, color) {
|
|
2031
|
-
const sgr = `\x1B[${colorToSGR(color)}m`;
|
|
2032
|
-
const reset = "\x1B[0m";
|
|
2033
|
-
writeAt(buf, x, y, sgr + "\u256D" + "\u2500".repeat(w - 2) + "\u256E" + reset);
|
|
2034
|
-
writeAt(buf, x, y + h - 1, sgr + "\u2570" + "\u2500".repeat(w - 2) + "\u256F" + reset);
|
|
2035
|
-
for (let row = y + 1; row < y + h - 1; row++) {
|
|
2036
|
-
writeAt(buf, x, row, sgr + "\u2502" + reset);
|
|
2037
|
-
writeAt(buf, x + w - 1, row, sgr + "\u2502" + reset);
|
|
2433
|
+
if (input === "x") {
|
|
2434
|
+
const agent = actions.getAgentForNode(cursorNode);
|
|
2435
|
+
if (!agent || !state2.selectedSessionId) {
|
|
2436
|
+
notify(state2, "Select an agent to restart");
|
|
2437
|
+
return;
|
|
2438
|
+
}
|
|
2439
|
+
actions.sendAndNotify(
|
|
2440
|
+
{ type: "restart-agent", sessionId: state2.selectedSessionId, agentId: agent.id },
|
|
2441
|
+
`Restarted ${agent.id}`
|
|
2442
|
+
);
|
|
2443
|
+
return;
|
|
2038
2444
|
}
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2445
|
+
if (input === "b") {
|
|
2446
|
+
if (!state2.selectedSessionId) {
|
|
2447
|
+
notify(state2, "No session selected");
|
|
2448
|
+
return;
|
|
2449
|
+
}
|
|
2450
|
+
{
|
|
2451
|
+
const sessionId = state2.selectedSessionId;
|
|
2452
|
+
const editor = actions.resolveEditor();
|
|
2453
|
+
try {
|
|
2454
|
+
const text = actions.editInPopup(state2.cwd, editor);
|
|
2455
|
+
if (text?.trim()) {
|
|
2456
|
+
const toCycle = parseInt(text.trim(), 10);
|
|
2457
|
+
if (isNaN(toCycle) || toCycle < 1) {
|
|
2458
|
+
notify(state2, "Invalid cycle number");
|
|
2459
|
+
return;
|
|
2460
|
+
}
|
|
2461
|
+
actions.sendAndNotify(
|
|
2462
|
+
{ type: "rollback", sessionId, cwd: state2.cwd, toCycle },
|
|
2463
|
+
`Rolled back to cycle ${toCycle} \u2014 use [R]esume to respawn`
|
|
2464
|
+
);
|
|
2465
|
+
}
|
|
2466
|
+
} catch {
|
|
2467
|
+
notify(state2, "Failed to open editor");
|
|
2468
|
+
}
|
|
2063
2469
|
}
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2470
|
+
return;
|
|
2471
|
+
}
|
|
2472
|
+
if (input === "e") {
|
|
2473
|
+
if (!cursorNode || cursorNode.type !== "context-file") return;
|
|
2474
|
+
const editor = actions.resolveEditor();
|
|
2475
|
+
try {
|
|
2476
|
+
actions.openEditorPopup(state2.cwd, editor, cursorNode.filePath);
|
|
2477
|
+
} catch {
|
|
2478
|
+
notify(state2, "Failed to open file in editor");
|
|
2067
2479
|
}
|
|
2480
|
+
return;
|
|
2068
2481
|
}
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
const
|
|
2075
|
-
|
|
2482
|
+
if (input === "S") {
|
|
2483
|
+
if (!state2.selectedSessionId) {
|
|
2484
|
+
notify(state2, "No session selected");
|
|
2485
|
+
return;
|
|
2486
|
+
}
|
|
2487
|
+
const sp = strategyPath(state2.cwd, state2.selectedSessionId);
|
|
2488
|
+
const editor = actions.resolveEditor();
|
|
2489
|
+
try {
|
|
2490
|
+
actions.openEditorPopup(state2.cwd, editor, sp);
|
|
2491
|
+
} catch {
|
|
2492
|
+
notify(state2, `Failed to open strategy in ${editor}`);
|
|
2493
|
+
}
|
|
2494
|
+
return;
|
|
2076
2495
|
}
|
|
2077
|
-
if (
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2496
|
+
if (input === "/") {
|
|
2497
|
+
state2.mode = "search";
|
|
2498
|
+
state2.searchText = "";
|
|
2499
|
+
requestRender();
|
|
2500
|
+
return;
|
|
2082
2501
|
}
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
const rows = new Array(h);
|
|
2088
|
-
const color = focused ? "blue" : borderColor;
|
|
2089
|
-
const sgr = `\x1B[${colorToSGR(color)}m`;
|
|
2090
|
-
const reset = "\x1B[0m";
|
|
2091
|
-
const innerW = w - 4;
|
|
2092
|
-
const borderL = sgr + "\u2502" + reset + " ";
|
|
2093
|
-
const borderR = " " + sgr + "\u2502" + reset;
|
|
2094
|
-
const emptyRow = borderL + " ".repeat(innerW) + borderR;
|
|
2095
|
-
rows[0] = sgr + "\u256D" + "\u2500".repeat(w - 2) + "\u256E" + reset;
|
|
2096
|
-
rows[h - 1] = sgr + "\u2570" + "\u2500".repeat(w - 2) + "\u256F" + reset;
|
|
2097
|
-
for (let i = 1; i < h - 1; i++) rows[i] = emptyRow;
|
|
2098
|
-
if (centerText) {
|
|
2099
|
-
const midRow = Math.floor(h / 2);
|
|
2100
|
-
if (midRow > 0 && midRow < h - 1) {
|
|
2101
|
-
const clipped = clipAnsi(centerText, innerW);
|
|
2102
|
-
const textW = displayWidthFast(centerText);
|
|
2103
|
-
const pad = Math.max(0, Math.floor((innerW - textW) / 2));
|
|
2104
|
-
const centered = " ".repeat(pad) + clipped;
|
|
2105
|
-
rows[midRow] = borderL + clipAnsi(centered, innerW) + borderR;
|
|
2502
|
+
if (input === "t") {
|
|
2503
|
+
if (state2.showCombinedView) {
|
|
2504
|
+
if (state2.focusPane === "logs") state2.focusPane = "detail";
|
|
2505
|
+
state2.logsScroll.reset();
|
|
2106
2506
|
}
|
|
2507
|
+
state2.showCombinedView = !state2.showCombinedView;
|
|
2508
|
+
requestRender();
|
|
2509
|
+
return;
|
|
2107
2510
|
}
|
|
2108
|
-
return rows;
|
|
2109
2511
|
}
|
|
2110
|
-
function
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
let hasOpenSGR = false;
|
|
2116
|
-
while (i < s.length && col < end) {
|
|
2117
|
-
if (s[i] === "\x1B" && s[i + 1] === "[") {
|
|
2118
|
-
const seqLen = ansiLen(s, i);
|
|
2119
|
-
if (seqLen > 0) {
|
|
2120
|
-
if (col >= start) {
|
|
2121
|
-
const seq = s.substring(i, i + seqLen);
|
|
2122
|
-
out += seq;
|
|
2123
|
-
hasOpenSGR = seq !== "\x1B[0m" && seq !== "\x1B[m";
|
|
2124
|
-
}
|
|
2125
|
-
i += seqLen;
|
|
2126
|
-
continue;
|
|
2127
|
-
}
|
|
2128
|
-
}
|
|
2129
|
-
const cp = s.codePointAt(i);
|
|
2130
|
-
const ch = String.fromCodePoint(cp);
|
|
2131
|
-
const chWidth = cp < 128 ? 1 : stringWidth2(ch);
|
|
2132
|
-
if (col >= start) {
|
|
2133
|
-
inSlice = true;
|
|
2134
|
-
if (col + chWidth > end) break;
|
|
2135
|
-
out += ch;
|
|
2136
|
-
}
|
|
2137
|
-
col += chWidth;
|
|
2138
|
-
i += ch.length;
|
|
2512
|
+
function handleSearchKey(input, key, state2) {
|
|
2513
|
+
if (key.return) {
|
|
2514
|
+
state2.mode = "navigate";
|
|
2515
|
+
requestRender();
|
|
2516
|
+
return;
|
|
2139
2517
|
}
|
|
2140
|
-
if (
|
|
2141
|
-
|
|
2518
|
+
if (key.escape) {
|
|
2519
|
+
state2.searchFilter = null;
|
|
2520
|
+
state2.searchText = "";
|
|
2521
|
+
state2.mode = "navigate";
|
|
2522
|
+
requestRender();
|
|
2523
|
+
return;
|
|
2524
|
+
}
|
|
2525
|
+
if (key.backspace) {
|
|
2526
|
+
state2.searchText = state2.searchText.slice(0, -1);
|
|
2527
|
+
state2.searchFilter = state2.searchText || null;
|
|
2528
|
+
requestRender();
|
|
2529
|
+
return;
|
|
2530
|
+
}
|
|
2531
|
+
if (key.ctrl || key.meta || !input || input.length !== 1) return;
|
|
2532
|
+
state2.searchText += input;
|
|
2533
|
+
state2.searchFilter = state2.searchText;
|
|
2534
|
+
requestRender();
|
|
2535
|
+
}
|
|
2536
|
+
function handleKeypress(input, key, state2, actions) {
|
|
2537
|
+
if (state2.mode === "compose") return;
|
|
2538
|
+
if (state2.mode === "search") {
|
|
2539
|
+
handleSearchKey(input, key, state2);
|
|
2540
|
+
} else if (state2.mode === "leader" || state2.mode === "copy-menu" || state2.mode === "help" || state2.mode === "companion-overlay" || state2.mode === "companion-debug") {
|
|
2541
|
+
handleLeaderKey(input, key, state2, actions);
|
|
2542
|
+
} else if (state2.mode === "report-detail") {
|
|
2543
|
+
handleReportDetailKey(input, key, state2, actions);
|
|
2544
|
+
} else {
|
|
2545
|
+
handleNavigateKey(input, key, state2, actions);
|
|
2142
2546
|
}
|
|
2143
|
-
return out;
|
|
2144
2547
|
}
|
|
2145
2548
|
|
|
2146
2549
|
// src/tui/lib/tree-render.ts
|
|
@@ -2310,18 +2713,22 @@ function openShellPopup(cwd2, command) {
|
|
|
2310
2713
|
function openInFileManager(path) {
|
|
2311
2714
|
execSync(`open ${shellQuote(path)}`, { stdio: "inherit", env: EXEC_ENV });
|
|
2312
2715
|
}
|
|
2313
|
-
function openClaudeResumePopup(cwd2, claudeSessionId) {
|
|
2716
|
+
function openClaudeResumePopup(cwd2, claudeSessionId, resumeEnv, resumeArgs) {
|
|
2314
2717
|
const pathEnv = augmentedPath();
|
|
2315
|
-
const
|
|
2718
|
+
const envPrefix = resumeEnv ? `${resumeEnv} && ` : "";
|
|
2719
|
+
const args2 = resumeArgs ? `${resumeArgs} --resume ${shellQuote(claudeSessionId)}` : `--resume ${shellQuote(claudeSessionId)}`;
|
|
2720
|
+
const cmd = `${envPrefix}PATH=${shellQuote(pathEnv)} claude ${args2}`;
|
|
2316
2721
|
execSync(
|
|
2317
2722
|
`tmux display-popup -E -w 90% -h 80% -d ${shellQuote(cwd2)} ${shellQuote(cmd)}`,
|
|
2318
2723
|
{ stdio: "inherit", env: EXEC_ENV }
|
|
2319
2724
|
);
|
|
2320
2725
|
}
|
|
2321
|
-
function openClaudeResumeSession(cwd2, claudeSessionId, sessionLabel) {
|
|
2726
|
+
function openClaudeResumeSession(cwd2, claudeSessionId, sessionLabel, resumeEnv, resumeArgs) {
|
|
2322
2727
|
const pathEnv = augmentedPath();
|
|
2323
|
-
const
|
|
2324
|
-
const
|
|
2728
|
+
const envPrefix = resumeEnv ? `${resumeEnv} && ` : "";
|
|
2729
|
+
const args2 = resumeArgs ? `${resumeArgs} --resume ${shellQuote(claudeSessionId)}` : `--resume ${shellQuote(claudeSessionId)}`;
|
|
2730
|
+
const cmd = `${envPrefix}PATH=${shellQuote(pathEnv)} claude ${args2}`;
|
|
2731
|
+
const sessionName = tmuxSessionName(cwd2, `${sessionLabel}-resume`);
|
|
2325
2732
|
exec(`tmux new-session -d -s ${shellQuote(sessionName)} -c ${shellQuote(cwd2)} ${shellQuote(cmd)}`);
|
|
2326
2733
|
execSafe(`tmux set-option -t ${shellQuote(sessionName)} @sisyphus_cwd ${shellQuote(cwd2.replace(/\/+$/, ""))}`);
|
|
2327
2734
|
const paneTarget = `${sessionName}:`;
|
|
@@ -2449,7 +2856,7 @@ function renderNodeContent(node, maxWidth) {
|
|
|
2449
2856
|
}
|
|
2450
2857
|
}
|
|
2451
2858
|
}
|
|
2452
|
-
function renderTreePanel(buf, rect, nodes, cursorIndex, focused) {
|
|
2859
|
+
function renderTreePanel(buf, rect, nodes, cursorIndex, focused, companion) {
|
|
2453
2860
|
const { x, y, w, h } = rect;
|
|
2454
2861
|
drawBorder(buf, x, y, w, h, focused ? "yellow" : "gray");
|
|
2455
2862
|
const innerX = x + 2;
|
|
@@ -2464,7 +2871,26 @@ function renderTreePanel(buf, rect, nodes, cursorIndex, focused) {
|
|
|
2464
2871
|
writeClipped(buf, innerX, innerY + 3, "\x1B[2mPress [?] for all keybindings.\x1B[0m", innerW);
|
|
2465
2872
|
return;
|
|
2466
2873
|
}
|
|
2467
|
-
|
|
2874
|
+
let companionRows = 0;
|
|
2875
|
+
let _companionCommentaryLines = [];
|
|
2876
|
+
if (companion) {
|
|
2877
|
+
const commentaryText = companion.lastCommentary?.text;
|
|
2878
|
+
if (commentaryText) {
|
|
2879
|
+
const words = commentaryText.split(" ");
|
|
2880
|
+
let current = "";
|
|
2881
|
+
for (const word of words) {
|
|
2882
|
+
if (current.length + word.length + 1 > innerW && current.length > 0) {
|
|
2883
|
+
_companionCommentaryLines.push(current);
|
|
2884
|
+
current = word;
|
|
2885
|
+
} else {
|
|
2886
|
+
current = current.length > 0 ? `${current} ${word}` : word;
|
|
2887
|
+
}
|
|
2888
|
+
}
|
|
2889
|
+
if (current.length > 0) _companionCommentaryLines.push(current);
|
|
2890
|
+
}
|
|
2891
|
+
companionRows = 1 + 1 + _companionCommentaryLines.length;
|
|
2892
|
+
}
|
|
2893
|
+
const maxVisible = Math.max(1, innerH - companionRows);
|
|
2468
2894
|
const halfVisible = Math.floor(maxVisible / 2);
|
|
2469
2895
|
const scrollOffset = Math.max(
|
|
2470
2896
|
0,
|
|
@@ -2524,6 +2950,15 @@ function renderTreePanel(buf, rect, nodes, cursorIndex, focused) {
|
|
|
2524
2950
|
const bottomRow = rowStart + availRows;
|
|
2525
2951
|
writeClipped(buf, innerX, bottomRow, `\x1B[2m\u2193 ${bottomMore} more\x1B[0m`, innerW);
|
|
2526
2952
|
}
|
|
2953
|
+
if (companion) {
|
|
2954
|
+
const commentaryCount = _companionCommentaryLines.length;
|
|
2955
|
+
const faceRow = y + h - 2 - commentaryCount;
|
|
2956
|
+
const faceLine = renderCompanion(companion, ["face", "boulder"], { maxWidth: innerW, color: true });
|
|
2957
|
+
writeClipped(buf, innerX, faceRow, faceLine, innerW);
|
|
2958
|
+
for (let i = 0; i < commentaryCount; i++) {
|
|
2959
|
+
writeClipped(buf, innerX, faceRow + 1 + i, `\x1B[2m${_companionCommentaryLines[i]}\x1B[0m`, innerW);
|
|
2960
|
+
}
|
|
2961
|
+
}
|
|
2527
2962
|
}
|
|
2528
2963
|
|
|
2529
2964
|
// src/tui/panels/detail.ts
|
|
@@ -3221,66 +3656,28 @@ function renderNvimDetailRows(rect, bridge, focused, editable, statusRows, compo
|
|
|
3221
3656
|
}
|
|
3222
3657
|
|
|
3223
3658
|
// src/tui/panels/bottom.ts
|
|
3224
|
-
function renderNotificationRow(buf, y, notification, error) {
|
|
3225
|
-
if (notification !== null) {
|
|
3226
|
-
const icon = /error|failed/i.test(notification) ? "\u2715" : /success|created|killed|sent|copied|deleted/i.test(notification) ? "\u2713" : "\u2139";
|
|
3227
|
-
const content = `\x1B[1;33m${icon} ${notification}\x1B[0m`;
|
|
3228
|
-
writeClipped(buf, 1, y, content, buf.width - 2);
|
|
3229
|
-
} else if (error !== null) {
|
|
3230
|
-
const content = `\x1B[31m\u26A0 ${error}\x1B[0m`;
|
|
3231
|
-
writeClipped(buf, 1, y, content, buf.width - 2);
|
|
3232
|
-
}
|
|
3233
|
-
}
|
|
3234
|
-
function renderInputBar(buf, y, state2) {
|
|
3235
|
-
const { mode, inputText, inputCursorPos } = state2;
|
|
3236
|
-
if (mode === "compose") {
|
|
3237
|
-
const action = state2.composeAction;
|
|
3238
|
-
const label = action ? COMPOSE_HEADERS[action.kind] : "Compose";
|
|
3239
|
-
const content2 = `\x1B[1;33mCOMPOSE\x1B[0m\x1B[2m ${label} \xB7 :w to submit \xB7 Tab to cancel\x1B[0m`;
|
|
3240
|
-
writeClipped(buf, 1, y, content2, buf.width - 2);
|
|
3241
|
-
return;
|
|
3242
|
-
}
|
|
3243
|
-
if (mode === "navigate") {
|
|
3244
|
-
const content2 = `\x1B[2mPress [m] to message orchestrator, [n] for new session\x1B[0m`;
|
|
3245
|
-
writeClipped(buf, 1, y, content2, buf.width - 2);
|
|
3246
|
-
return;
|
|
3247
|
-
}
|
|
3248
|
-
if (mode === "report-detail" || mode === "leader" || mode === "copy-menu" || mode === "help" || !INPUT_MODES.has(mode)) {
|
|
3249
|
-
return;
|
|
3250
|
-
}
|
|
3251
|
-
const prompt = PROMPTS[mode] ?? mode;
|
|
3252
|
-
const cursorChar = inputCursorPos < inputText.length ? inputText[inputCursorPos] : " ";
|
|
3253
|
-
const before = inputText.slice(0, inputCursorPos);
|
|
3254
|
-
const after = inputText.slice(inputCursorPos + 1);
|
|
3255
|
-
const content = `\x1B[33m${prompt} > \x1B[0m` + before + `\x1B[7m${cursorChar}\x1B[0m` + after + `\x1B[2m (enter to send, esc to cancel)\x1B[0m`;
|
|
3256
|
-
writeClipped(buf, 1, y, content, buf.width - 2);
|
|
3257
|
-
}
|
|
3258
3659
|
var B = ansiBold;
|
|
3259
3660
|
var D = ansiDim;
|
|
3260
3661
|
var SEP = D("\u2502 ");
|
|
3261
3662
|
function renderStatusLine(buf, y, state2, cursorNodeType) {
|
|
3262
|
-
const { mode, focusPane } = state2;
|
|
3663
|
+
const { mode, focusPane, notification, error } = state2;
|
|
3263
3664
|
if (mode === "report-detail") return;
|
|
3264
3665
|
if (mode === "compose") return;
|
|
3265
3666
|
let content;
|
|
3266
|
-
if (
|
|
3667
|
+
if (notification !== null) {
|
|
3668
|
+
const icon = /error|failed/i.test(notification) ? "\u2715" : /success|created|killed|sent|copied|deleted/i.test(notification) ? "\u2713" : "\u2139";
|
|
3669
|
+
content = `\x1B[1;33m${icon} ${notification}\x1B[0m`;
|
|
3670
|
+
} else if (error !== null) {
|
|
3671
|
+
content = `\x1B[31m\u26A0 ${error}\x1B[0m`;
|
|
3672
|
+
} else if (mode === "search") {
|
|
3673
|
+
const cursor = `\x1B[7m \x1B[0m`;
|
|
3674
|
+
content = `\x1B[1;34m/\x1B[0m${state2.searchText}${cursor}` + D(" enter to apply \xB7 esc to clear");
|
|
3675
|
+
} else if (mode === "leader") {
|
|
3267
3676
|
content = `\x1B[1;35mLEADER\x1B[0m` + D(" press a command key or [esc] to cancel");
|
|
3268
3677
|
} else if (mode === "copy-menu") {
|
|
3269
3678
|
content = `\x1B[1;36mCOPY\x1B[0m` + D(" [p] path [C] context [l] logs [s] session ID [esc] cancel");
|
|
3270
3679
|
} else if (mode === "help") {
|
|
3271
3680
|
content = `\x1B[1;33mHELP\x1B[0m` + D(" [esc] or [?] to dismiss");
|
|
3272
|
-
} else if (mode === "delete-confirm") {
|
|
3273
|
-
content = `\x1B[1;31mDELETE\x1B[0m` + D(" type 'yes' to confirm, [esc] to cancel");
|
|
3274
|
-
} else if (mode === "spawn-agent") {
|
|
3275
|
-
content = `\x1B[1;32mSPAWN\x1B[0m` + D(" enter agent instruction, [esc] to cancel");
|
|
3276
|
-
} else if (mode === "search") {
|
|
3277
|
-
content = `\x1B[1;34mSEARCH\x1B[0m` + D(" type to filter, enter to apply, [esc] to cancel");
|
|
3278
|
-
} else if (mode === "message-agent") {
|
|
3279
|
-
content = `\x1B[1;36mMESSAGE\x1B[0m` + D(" enter message for agent, [esc] to cancel");
|
|
3280
|
-
} else if (mode === "shell-command") {
|
|
3281
|
-
content = `\x1B[1;35mSHELL\x1B[0m` + D(" enter command, [esc] to cancel");
|
|
3282
|
-
} else if (mode !== "navigate") {
|
|
3283
|
-
content = D("[enter] send [esc] cancel");
|
|
3284
3681
|
} else if (focusPane === "logs" || focusPane === "detail") {
|
|
3285
3682
|
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");
|
|
3286
3683
|
} else {
|
|
@@ -3293,102 +3690,6 @@ function renderStatusLine(buf, y, state2, cursorNodeType) {
|
|
|
3293
3690
|
writeClipped(buf, 1, y, content, buf.width - 2);
|
|
3294
3691
|
}
|
|
3295
3692
|
|
|
3296
|
-
// src/tui/panels/overlays.ts
|
|
3297
|
-
var LEADER_WIDTH = 26;
|
|
3298
|
-
var LEADER_HEIGHT = 19;
|
|
3299
|
-
var COPY_HEIGHT = 9;
|
|
3300
|
-
var HELP_WIDTH = 62;
|
|
3301
|
-
function helpRow(left, right, innerWidth) {
|
|
3302
|
-
const col = Math.floor(innerWidth / 2);
|
|
3303
|
-
return (left.padEnd(col) + right).padEnd(innerWidth);
|
|
3304
|
-
}
|
|
3305
|
-
function renderLeaderOverlay(buf, rows, cols) {
|
|
3306
|
-
const x = cols - LEADER_WIDTH - 1;
|
|
3307
|
-
const y = rows - LEADER_HEIGHT - 2;
|
|
3308
|
-
const innerWidth = LEADER_WIDTH - 2;
|
|
3309
|
-
drawBorder(buf, x, y, LEADER_WIDTH, LEADER_HEIGHT, "magenta");
|
|
3310
|
-
const lines = [
|
|
3311
|
-
ansiColor(" LEADER".padEnd(innerWidth), "magenta", true),
|
|
3312
|
-
" ".padEnd(innerWidth),
|
|
3313
|
-
" y copy menu".padEnd(innerWidth),
|
|
3314
|
-
" d delete session".padEnd(innerWidth),
|
|
3315
|
-
" l daemon logs".padEnd(innerWidth),
|
|
3316
|
-
" o open session dir".padEnd(innerWidth),
|
|
3317
|
-
" a spawn agent".padEnd(innerWidth),
|
|
3318
|
-
" m message agent".padEnd(innerWidth),
|
|
3319
|
-
" / search".padEnd(innerWidth),
|
|
3320
|
-
" ! shell command".padEnd(innerWidth),
|
|
3321
|
-
" j jump to pane".padEnd(innerWidth),
|
|
3322
|
-
" k kill session/agent".padEnd(innerWidth),
|
|
3323
|
-
" q quit".padEnd(innerWidth),
|
|
3324
|
-
" ? help".padEnd(innerWidth),
|
|
3325
|
-
" 1-9 jump to session".padEnd(innerWidth),
|
|
3326
|
-
" ".padEnd(innerWidth),
|
|
3327
|
-
ansiDim(" esc dismiss".padEnd(innerWidth))
|
|
3328
|
-
];
|
|
3329
|
-
for (let i = 0; i < lines.length; i++) {
|
|
3330
|
-
writeClipped(buf, x + 1, y + 1 + i, lines[i], innerWidth);
|
|
3331
|
-
}
|
|
3332
|
-
}
|
|
3333
|
-
function renderCopyMenuOverlay(buf, rows, cols) {
|
|
3334
|
-
const x = cols - LEADER_WIDTH - 1;
|
|
3335
|
-
const y = rows - COPY_HEIGHT - 2;
|
|
3336
|
-
const innerWidth = LEADER_WIDTH - 2;
|
|
3337
|
-
drawBorder(buf, x, y, LEADER_WIDTH, COPY_HEIGHT, "cyan");
|
|
3338
|
-
const lines = [
|
|
3339
|
-
ansiColor(" COPY".padEnd(innerWidth), "cyan", true),
|
|
3340
|
-
" ".padEnd(innerWidth),
|
|
3341
|
-
" p session path".padEnd(innerWidth),
|
|
3342
|
-
" C LLM context".padEnd(innerWidth),
|
|
3343
|
-
" l logs content".padEnd(innerWidth),
|
|
3344
|
-
" s session ID".padEnd(innerWidth),
|
|
3345
|
-
ansiDim(" esc cancel".padEnd(innerWidth))
|
|
3346
|
-
];
|
|
3347
|
-
for (let i = 0; i < lines.length; i++) {
|
|
3348
|
-
writeClipped(buf, x + 1, y + 1 + i, lines[i], innerWidth);
|
|
3349
|
-
}
|
|
3350
|
-
}
|
|
3351
|
-
function renderHelpOverlay(buf, rows, cols) {
|
|
3352
|
-
const innerWidth = HELP_WIDTH - 2;
|
|
3353
|
-
const x = Math.max(0, Math.floor((cols - HELP_WIDTH) / 2));
|
|
3354
|
-
const contentLines = [
|
|
3355
|
-
helpRow(" hjkl/\u2191\u2193\u2190\u2192 navigate", " tab switch pane", innerWidth),
|
|
3356
|
-
helpRow(" enter expand/open", " t toggle logs", innerWidth),
|
|
3357
|
-
" ".padEnd(innerWidth),
|
|
3358
|
-
helpRow(" n new session", " m message orch.", innerWidth),
|
|
3359
|
-
helpRow(" R resume session", " C continue session", innerWidth),
|
|
3360
|
-
helpRow(" b rollback cycle", " x restart agent", innerWidth),
|
|
3361
|
-
helpRow(" r re-run agent", " g edit goal", innerWidth),
|
|
3362
|
-
helpRow(" p open roadmap", " s toggle strategy", innerWidth),
|
|
3363
|
-
helpRow(" S edit strategy", " w go to window", innerWidth),
|
|
3364
|
-
helpRow(" o resume claude session", " c claude companion", innerWidth),
|
|
3365
|
-
helpRow(" q quit", "", innerWidth),
|
|
3366
|
-
" ".padEnd(innerWidth),
|
|
3367
|
-
helpRow(" space \u2192 y copy submenu", " space \u2192 d delete session", innerWidth),
|
|
3368
|
-
helpRow(" space \u2192 j jump to pane", " space \u2192 k kill", innerWidth),
|
|
3369
|
-
helpRow(" space \u2192 q quit", " space \u2192 o open dir", innerWidth),
|
|
3370
|
-
helpRow(" space \u2192 l tail logs", " space \u2192 / search", innerWidth),
|
|
3371
|
-
helpRow(" space \u2192 a spawn agent", " space \u2192 m msg agent", innerWidth),
|
|
3372
|
-
helpRow(" space \u2192 ? help", " space \u2192 1-9 jump", innerWidth),
|
|
3373
|
-
" ".padEnd(innerWidth),
|
|
3374
|
-
helpRow(" y \u2192 p session path", " y \u2192 C LLM context", innerWidth),
|
|
3375
|
-
helpRow(" y \u2192 l logs content", " y \u2192 s session ID", innerWidth)
|
|
3376
|
-
];
|
|
3377
|
-
const height = Math.min(contentLines.length + 4, rows - 2);
|
|
3378
|
-
const y = Math.max(0, Math.floor((rows - height) / 2));
|
|
3379
|
-
drawBorder(buf, x, y, HELP_WIDTH, height, "yellow");
|
|
3380
|
-
writeClipped(buf, x + 1, y + 1, ansiColor(" KEYBINDINGS (esc or ? to close)".padEnd(innerWidth), "yellow", true), innerWidth);
|
|
3381
|
-
writeClipped(buf, x + 1, y + 2, " ".padEnd(innerWidth), innerWidth);
|
|
3382
|
-
const availableContentRows = height - 4;
|
|
3383
|
-
for (let i = 0; i < Math.min(contentLines.length, availableContentRows); i++) {
|
|
3384
|
-
writeClipped(buf, x + 1, y + 3 + i, contentLines[i], innerWidth);
|
|
3385
|
-
}
|
|
3386
|
-
const trailingBlankRow = y + 3 + Math.min(contentLines.length, availableContentRows);
|
|
3387
|
-
if (trailingBlankRow < y + height - 1) {
|
|
3388
|
-
writeClipped(buf, x + 1, trailingBlankRow, " ".padEnd(innerWidth), innerWidth);
|
|
3389
|
-
}
|
|
3390
|
-
}
|
|
3391
|
-
|
|
3392
3693
|
// src/tui/lib/nvim-bridge.ts
|
|
3393
3694
|
import { execSync as execSync3 } from "child_process";
|
|
3394
3695
|
import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, unlinkSync as unlinkSync2, readFileSync as readFileSync3, statSync, existsSync as existsSync3 } from "fs";
|
|
@@ -4180,6 +4481,19 @@ function resolveNvimFile(state2, cursorNode, detailCtx, cwd2) {
|
|
|
4180
4481
|
}
|
|
4181
4482
|
|
|
4182
4483
|
// src/tui/app.ts
|
|
4484
|
+
var _cachedCompanion = null;
|
|
4485
|
+
var _companionMtime = 0;
|
|
4486
|
+
function getCompanion() {
|
|
4487
|
+
try {
|
|
4488
|
+
const { mtimeMs } = statSync2(companionPath());
|
|
4489
|
+
if (_cachedCompanion && mtimeMs === _companionMtime) return _cachedCompanion;
|
|
4490
|
+
_companionMtime = mtimeMs;
|
|
4491
|
+
_cachedCompanion = JSON.parse(readFileSync4(companionPath(), "utf-8"));
|
|
4492
|
+
return _cachedCompanion;
|
|
4493
|
+
} catch {
|
|
4494
|
+
return _cachedCompanion;
|
|
4495
|
+
}
|
|
4496
|
+
}
|
|
4183
4497
|
var latestNodes = [];
|
|
4184
4498
|
var cachedContextFilePath = null;
|
|
4185
4499
|
var cachedContextFileContent = null;
|
|
@@ -4262,7 +4576,7 @@ function startApp(state2, cleanup2) {
|
|
|
4262
4576
|
const config = loadConfig(state2.cwd);
|
|
4263
4577
|
const treeWidth = 36;
|
|
4264
4578
|
const initialDetailW = state2.cols - treeWidth - 4;
|
|
4265
|
-
const initialDetailH = state2.rows -
|
|
4579
|
+
const initialDetailH = state2.rows - 1 - 2 - STATUS_ROW_COUNT - 1;
|
|
4266
4580
|
const bridge = new NvimBridge(
|
|
4267
4581
|
Math.max(1, initialDetailW),
|
|
4268
4582
|
Math.max(1, initialDetailH),
|
|
@@ -4410,7 +4724,7 @@ function startApp(state2, cleanup2) {
|
|
|
4410
4724
|
const remaining = state2.cols - treeWidth2;
|
|
4411
4725
|
const detailWidth = state2.showCombinedView ? Math.floor(remaining * 0.6) : remaining;
|
|
4412
4726
|
const logsWidth = state2.showCombinedView ? remaining - detailWidth : 0;
|
|
4413
|
-
const contentHeight = state2.rows -
|
|
4727
|
+
const contentHeight = state2.rows - 1;
|
|
4414
4728
|
const treeRect = { x: 0, y: 0, w: treeWidth2, h: contentHeight };
|
|
4415
4729
|
const detailRect = { x: treeWidth2, y: 0, w: detailWidth, h: contentHeight };
|
|
4416
4730
|
const logsRect = state2.showCombinedView ? { x: treeWidth2 + detailWidth, y: 0, w: logsWidth, h: contentHeight } : null;
|
|
@@ -4487,15 +4801,24 @@ function startApp(state2, cleanup2) {
|
|
|
4487
4801
|
}
|
|
4488
4802
|
const treeFocused = state2.mode === "navigate" && state2.focusPane === "tree";
|
|
4489
4803
|
const treeInputs = `${state2.treeCacheKey}:${state2.cursorIndex}:${treeFocused}`;
|
|
4490
|
-
const bottomInputs = `${state2.notification}:${state2.error}:${state2.mode}:${state2.
|
|
4491
|
-
const overlayMode = state2.mode === "leader" || state2.mode === "copy-menu" || state2.mode === "help" ? state2.mode : "";
|
|
4804
|
+
const bottomInputs = `${state2.notification}:${state2.error}:${state2.mode}:${state2.searchText}:${cursorNode?.type}`;
|
|
4805
|
+
const overlayMode = state2.mode === "leader" || state2.mode === "copy-menu" || state2.mode === "help" || state2.mode === "companion-overlay" || state2.mode === "companion-debug" ? state2.mode : "";
|
|
4806
|
+
let companionFP = "";
|
|
4807
|
+
if (state2.mode === "companion-overlay" || state2.mode === "companion-debug") {
|
|
4808
|
+
const c = getCompanion();
|
|
4809
|
+
const ts = c && c.lastCommentary ? c.lastCommentary.timestamp : "";
|
|
4810
|
+
const xp = c ? c.xp : 0;
|
|
4811
|
+
const dm = c?.debugMood ? `${c.debugMood.winner}:${c.debugMood.scores[c.debugMood.winner]}` : "";
|
|
4812
|
+
companionFP = `${ts}:${xp}:${dm}`;
|
|
4813
|
+
}
|
|
4814
|
+
const overlayInputs = `${overlayMode}:${companionFP}`;
|
|
4492
4815
|
const hasPrev = prevFrame.length === buf.height;
|
|
4493
4816
|
const treeDirty = !hasPrev || treeInputs !== prevTreeInputs;
|
|
4494
4817
|
const bottomDirty = !hasPrev || bottomInputs !== prevBottomInputs;
|
|
4495
|
-
const overlayDirty = !hasPrev ||
|
|
4818
|
+
const overlayDirty = !hasPrev || overlayInputs !== prevOverlayMode;
|
|
4496
4819
|
prevTreeInputs = treeInputs;
|
|
4497
4820
|
prevBottomInputs = bottomInputs;
|
|
4498
|
-
prevOverlayMode =
|
|
4821
|
+
prevOverlayMode = overlayInputs;
|
|
4499
4822
|
let treeRows;
|
|
4500
4823
|
if (treeDirty) {
|
|
4501
4824
|
const treeBlank = " ".repeat(treeWidth2);
|
|
@@ -4509,7 +4832,8 @@ function startApp(state2, cleanup2) {
|
|
|
4509
4832
|
{ x: 0, y: 0, w: treeWidth2, h: contentHeight },
|
|
4510
4833
|
nodes,
|
|
4511
4834
|
state2.cursorIndex,
|
|
4512
|
-
treeFocused
|
|
4835
|
+
treeFocused,
|
|
4836
|
+
getCompanion()
|
|
4513
4837
|
);
|
|
4514
4838
|
cachedTreeRows = treeBuf.lines;
|
|
4515
4839
|
treeRows = treeBuf.lines;
|
|
@@ -4573,16 +4897,22 @@ function startApp(state2, cleanup2) {
|
|
|
4573
4897
|
}
|
|
4574
4898
|
}
|
|
4575
4899
|
if (bottomDirty || overlayDirty) {
|
|
4576
|
-
|
|
4577
|
-
renderInputBar(buf, bottomY + 1, state2);
|
|
4578
|
-
renderStatusLine(buf, bottomY + 2, state2, cursorNode?.type);
|
|
4900
|
+
renderStatusLine(buf, bottomY, state2, cursorNode?.type);
|
|
4579
4901
|
} else {
|
|
4580
|
-
copyRows(buf, prevFrame, bottomY,
|
|
4902
|
+
copyRows(buf, prevFrame, bottomY, 1);
|
|
4581
4903
|
}
|
|
4582
4904
|
if (overlayMode) {
|
|
4583
4905
|
if (state2.mode === "leader") renderLeaderOverlay(buf, state2.rows, state2.cols);
|
|
4584
4906
|
if (state2.mode === "copy-menu") renderCopyMenuOverlay(buf, state2.rows, state2.cols);
|
|
4585
4907
|
if (state2.mode === "help") renderHelpOverlay(buf, state2.rows, state2.cols);
|
|
4908
|
+
if (state2.mode === "companion-overlay") {
|
|
4909
|
+
const companion = getCompanion();
|
|
4910
|
+
if (companion) renderCompanionOverlay(buf, state2.rows, state2.cols, companion);
|
|
4911
|
+
}
|
|
4912
|
+
if (state2.mode === "companion-debug") {
|
|
4913
|
+
const companion = getCompanion();
|
|
4914
|
+
if (companion) renderCompanionDebugOverlay(buf, state2.rows, state2.cols, companion);
|
|
4915
|
+
}
|
|
4586
4916
|
}
|
|
4587
4917
|
let cursorSuffix;
|
|
4588
4918
|
if (state2.focusPane === "detail" && state2.nvimBridge?.ready) {
|
|
@@ -4652,7 +4982,7 @@ function startApp(state2, cleanup2) {
|
|
|
4652
4982
|
prevFrame = [];
|
|
4653
4983
|
if (state2.nvimBridge) {
|
|
4654
4984
|
const detailW = state2.cols - 36;
|
|
4655
|
-
const contentH = state2.rows -
|
|
4985
|
+
const contentH = state2.rows - 1;
|
|
4656
4986
|
state2.nvimBridge.resize(Math.max(1, detailW - 4), Math.max(1, contentH - 2 - STATUS_ROW_COUNT - 1));
|
|
4657
4987
|
}
|
|
4658
4988
|
requestRender();
|