@wrongstack/tui 0.10.2 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +9 -0
- package/dist/index.js +429 -154
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Readable } from 'stream';
|
|
2
2
|
import { Box, Text, render, useApp, useStdout, measureElement, Static, useInput, useStdin } from 'ink';
|
|
3
|
-
import React4, { useState, useEffect, useReducer, useRef, useMemo, useLayoutEffect } from 'react';
|
|
3
|
+
import React4, { useState, useEffect, useReducer, useRef, useMemo, useCallback, useLayoutEffect } from 'react';
|
|
4
4
|
import * as fs2 from 'fs/promises';
|
|
5
5
|
import * as path2 from 'path';
|
|
6
6
|
import { InputBuilder, DefaultSessionRewinder, formatTodosList, buildGoalPreamble, buildChildEnv } from '@wrongstack/core';
|
|
@@ -35,6 +35,8 @@ var theme = Object.freeze({
|
|
|
35
35
|
diffAddBg: "greenBright",
|
|
36
36
|
diffDelBg: "redBright"
|
|
37
37
|
});
|
|
38
|
+
var COMPACT_THRESHOLD = 50;
|
|
39
|
+
var COMFORTABLE_THRESHOLD = 90;
|
|
38
40
|
function StatusBar({
|
|
39
41
|
model,
|
|
40
42
|
version,
|
|
@@ -58,6 +60,18 @@ function StatusBar({
|
|
|
58
60
|
eternalStage,
|
|
59
61
|
goalSummary
|
|
60
62
|
}) {
|
|
63
|
+
const { stdout } = useStdout();
|
|
64
|
+
const [termWidth, setTermWidth] = useState(stdout?.columns ?? 90);
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
const handleResize = () => setTermWidth(stdout?.columns ?? 90);
|
|
67
|
+
handleResize();
|
|
68
|
+
process.stdout.on("resize", handleResize);
|
|
69
|
+
return () => {
|
|
70
|
+
process.stdout.off("resize", handleResize);
|
|
71
|
+
};
|
|
72
|
+
}, [stdout]);
|
|
73
|
+
const isCompact = termWidth < COMPACT_THRESHOLD;
|
|
74
|
+
const isComfortable = termWidth >= COMFORTABLE_THRESHOLD;
|
|
61
75
|
const hiddenSet = new Set(hiddenItems);
|
|
62
76
|
const usage = tokenCounter?.total();
|
|
63
77
|
const cost = tokenCounter?.estimateCost();
|
|
@@ -77,74 +91,87 @@ function StatusBar({
|
|
|
77
91
|
borderLeft: false,
|
|
78
92
|
borderRight: false,
|
|
79
93
|
children: [
|
|
80
|
-
/* @__PURE__ */
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
94
|
+
/* @__PURE__ */ jsx(Box, { flexDirection: "row", gap: 2, children: isCompact ? (
|
|
95
|
+
// Ultra-compact: state · model
|
|
96
|
+
/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
97
|
+
/* @__PURE__ */ jsxs(Text, { color: stateColor, children: [
|
|
98
|
+
"\u25CF",
|
|
99
|
+
stateLabel
|
|
100
|
+
] }),
|
|
101
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
|
|
102
|
+
/* @__PURE__ */ jsx(Text, { color: "magenta", children: model })
|
|
103
|
+
] })
|
|
104
|
+
) : (
|
|
105
|
+
// Full mode: version · state · model · context · tokens · cost · queue · processes · hint
|
|
106
|
+
/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
107
|
+
version ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
108
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
109
|
+
/* @__PURE__ */ jsx(Text, { color: "blue", bold: true, children: "WS" }),
|
|
110
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
111
|
+
" v",
|
|
112
|
+
version
|
|
113
|
+
] })
|
|
114
|
+
] }),
|
|
115
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" })
|
|
116
|
+
] }) : null,
|
|
117
|
+
/* @__PURE__ */ jsxs(Text, { color: stateColor, children: [
|
|
118
|
+
"\u25CF ",
|
|
119
|
+
stateLabel
|
|
88
120
|
] }),
|
|
89
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" })
|
|
90
|
-
] }) : null,
|
|
91
|
-
/* @__PURE__ */ jsxs(Text, { color: stateColor, children: [
|
|
92
|
-
"\u25CF ",
|
|
93
|
-
stateLabel
|
|
94
|
-
] }),
|
|
95
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
|
|
96
|
-
/* @__PURE__ */ jsx(Text, { color: "magenta", children: model }),
|
|
97
|
-
context && context.max > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
98
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
|
|
99
|
-
/* @__PURE__ */ jsx(ContextChip, { ctx: context })
|
|
100
|
-
] }) : null,
|
|
101
|
-
usage ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
102
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
|
|
103
|
-
/* @__PURE__ */ jsxs(Text, { children: [
|
|
104
|
-
"\u2191",
|
|
105
|
-
" ",
|
|
106
|
-
/* @__PURE__ */ jsx(Text, { color: "cyan", children: fmtTok(usage.input + (usage.cacheRead ?? 0) + (usage.cacheWrite ?? 0)) }),
|
|
107
|
-
" ",
|
|
108
|
-
"\u2193 ",
|
|
109
|
-
/* @__PURE__ */ jsx(Text, { color: "cyan", children: fmtTok(usage.output) })
|
|
110
|
-
] })
|
|
111
|
-
] }) : null,
|
|
112
|
-
cache2 && cache2.hitRatio > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
113
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
|
|
114
|
-
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
115
|
-
"cache ",
|
|
116
|
-
(cache2.hitRatio * 100).toFixed(0),
|
|
117
|
-
"%"
|
|
118
|
-
] })
|
|
119
|
-
] }) : null,
|
|
120
|
-
cost && cost.total > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
121
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
|
|
122
|
-
/* @__PURE__ */ jsxs(Text, { color: "yellow", children: [
|
|
123
|
-
"$",
|
|
124
|
-
cost.total.toFixed(4)
|
|
125
|
-
] })
|
|
126
|
-
] }) : null,
|
|
127
|
-
queueCount > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
128
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
|
|
129
|
-
/* @__PURE__ */ jsxs(Text, { color: "cyan", children: [
|
|
130
|
-
"\u231B queued: ",
|
|
131
|
-
queueCount
|
|
132
|
-
] })
|
|
133
|
-
] }) : null,
|
|
134
|
-
typeof processCount === "number" && processCount > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
135
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
|
|
136
|
-
/* @__PURE__ */ jsxs(Text, { color: "red", children: [
|
|
137
|
-
"\u26A1 ",
|
|
138
|
-
processCount,
|
|
139
|
-
" process",
|
|
140
|
-
processCount === 1 ? "" : "es"
|
|
141
|
-
] })
|
|
142
|
-
] }) : null,
|
|
143
|
-
hint ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
144
121
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
|
|
145
|
-
/* @__PURE__ */ jsx(Text, {
|
|
146
|
-
|
|
147
|
-
|
|
122
|
+
/* @__PURE__ */ jsx(Text, { color: "magenta", children: model }),
|
|
123
|
+
context && context.max > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
124
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
|
|
125
|
+
/* @__PURE__ */ jsx(ContextChip, { ctx: context })
|
|
126
|
+
] }) : null,
|
|
127
|
+
usage && isComfortable ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
128
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
|
|
129
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
130
|
+
"\u2191",
|
|
131
|
+
" ",
|
|
132
|
+
/* @__PURE__ */ jsx(Text, { color: "cyan", children: fmtTok(usage.input + (usage.cacheRead ?? 0) + (usage.cacheWrite ?? 0)) }),
|
|
133
|
+
" ",
|
|
134
|
+
"\u2193 ",
|
|
135
|
+
/* @__PURE__ */ jsx(Text, { color: "cyan", children: fmtTok(usage.output) })
|
|
136
|
+
] })
|
|
137
|
+
] }) : null,
|
|
138
|
+
cache2 && cache2.hitRatio > 0 && isComfortable ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
139
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
|
|
140
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
141
|
+
"cache ",
|
|
142
|
+
(cache2.hitRatio * 100).toFixed(0),
|
|
143
|
+
"%"
|
|
144
|
+
] })
|
|
145
|
+
] }) : null,
|
|
146
|
+
cost && cost.total > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
147
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
|
|
148
|
+
/* @__PURE__ */ jsxs(Text, { color: "yellow", children: [
|
|
149
|
+
"$",
|
|
150
|
+
cost.total.toFixed(4)
|
|
151
|
+
] })
|
|
152
|
+
] }) : null,
|
|
153
|
+
queueCount > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
154
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
|
|
155
|
+
/* @__PURE__ */ jsxs(Text, { color: "cyan", children: [
|
|
156
|
+
"\u231B queued: ",
|
|
157
|
+
queueCount
|
|
158
|
+
] })
|
|
159
|
+
] }) : null,
|
|
160
|
+
typeof processCount === "number" && processCount > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
161
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
|
|
162
|
+
/* @__PURE__ */ jsxs(Text, { color: "red", children: [
|
|
163
|
+
"\u26A1 ",
|
|
164
|
+
processCount,
|
|
165
|
+
" process",
|
|
166
|
+
processCount === 1 ? "" : "es"
|
|
167
|
+
] })
|
|
168
|
+
] }) : null,
|
|
169
|
+
hint ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
170
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
|
|
171
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: hint })
|
|
172
|
+
] }) : null
|
|
173
|
+
] })
|
|
174
|
+
) }),
|
|
148
175
|
hasSecondLine ? /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 2, children: [
|
|
149
176
|
yolo ? /* @__PURE__ */ jsx(Text, { color: "red", bold: true, children: "\u26A0 YOLO" }) : null,
|
|
150
177
|
autonomy && autonomy !== "off" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
@@ -855,21 +882,58 @@ function AgentsMonitor({
|
|
|
855
882
|
] });
|
|
856
883
|
}
|
|
857
884
|
var AUTONOMY_OPTIONS = [
|
|
858
|
-
{
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
885
|
+
{
|
|
886
|
+
mode: "off",
|
|
887
|
+
label: "OFF",
|
|
888
|
+
description: "Agent stops after each turn (normal interactive mode)",
|
|
889
|
+
color: "green"
|
|
890
|
+
},
|
|
891
|
+
{
|
|
892
|
+
mode: "suggest",
|
|
893
|
+
label: "SUGGEST",
|
|
894
|
+
description: "Shows next-step suggestions after each turn",
|
|
895
|
+
color: "cyan"
|
|
896
|
+
},
|
|
897
|
+
{
|
|
898
|
+
mode: "auto",
|
|
899
|
+
label: "AUTO",
|
|
900
|
+
description: "Self-driving \u2014 agent picks next step and continues",
|
|
901
|
+
color: "yellow"
|
|
902
|
+
},
|
|
903
|
+
{
|
|
904
|
+
mode: "eternal",
|
|
905
|
+
label: "ETERNAL",
|
|
906
|
+
description: "Goal-driven loop \u2014 requires /goal set first",
|
|
907
|
+
color: "red"
|
|
908
|
+
},
|
|
909
|
+
{
|
|
910
|
+
mode: "eternal-parallel",
|
|
911
|
+
label: "PARALLEL",
|
|
912
|
+
description: "Fan-out 4\u20138 subagents per tick \u2014 requires /goal",
|
|
913
|
+
color: "magenta"
|
|
914
|
+
}
|
|
863
915
|
];
|
|
864
|
-
function AutonomyPicker({
|
|
916
|
+
function AutonomyPicker({
|
|
917
|
+
options,
|
|
918
|
+
selected,
|
|
919
|
+
hint
|
|
920
|
+
}) {
|
|
865
921
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
|
|
866
922
|
/* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "\u2501\u2501 Autonomy Mode \u2501\u2501" }),
|
|
867
923
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 navigate \xB7 Enter select \xB7 Esc cancel \xB7 Ctrl+C exit" }),
|
|
868
|
-
options.map((opt, i) => /* @__PURE__ */ jsxs(
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
924
|
+
options.map((opt, i) => /* @__PURE__ */ jsxs(
|
|
925
|
+
Text,
|
|
926
|
+
{
|
|
927
|
+
color: i === selected ? opt.color : void 0,
|
|
928
|
+
inverse: i === selected,
|
|
929
|
+
children: [
|
|
930
|
+
i === selected ? "\u203A " : " ",
|
|
931
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: opt.label.padEnd(12) }),
|
|
932
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: opt.description })
|
|
933
|
+
]
|
|
934
|
+
},
|
|
935
|
+
opt.mode
|
|
936
|
+
)),
|
|
873
937
|
hint ? /* @__PURE__ */ jsx(Text, { color: "yellow", children: hint }) : null
|
|
874
938
|
] });
|
|
875
939
|
}
|
|
@@ -907,7 +971,8 @@ function CheckpointTimeline({
|
|
|
907
971
|
new Date(cp.ts).toLocaleTimeString()
|
|
908
972
|
] }),
|
|
909
973
|
cp.fileCount > 0 && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
910
|
-
"
|
|
974
|
+
" ",
|
|
975
|
+
"\xB7 ",
|
|
911
976
|
cp.fileCount,
|
|
912
977
|
" file",
|
|
913
978
|
cp.fileCount !== 1 ? "s" : ""
|
|
@@ -1028,9 +1093,24 @@ function FilePicker({ query, matches, selected }) {
|
|
|
1028
1093
|
function highlight(path3, _query) {
|
|
1029
1094
|
return path3;
|
|
1030
1095
|
}
|
|
1031
|
-
function FleetPanel({
|
|
1096
|
+
function FleetPanel({
|
|
1097
|
+
entries,
|
|
1098
|
+
totalCost,
|
|
1099
|
+
collabSession
|
|
1100
|
+
}) {
|
|
1101
|
+
const { stdout } = useStdout();
|
|
1102
|
+
const [termWidth, setTermWidth] = useState(stdout?.columns ?? 90);
|
|
1103
|
+
useEffect(() => {
|
|
1104
|
+
const handleResize = () => setTermWidth(stdout?.columns ?? 90);
|
|
1105
|
+
handleResize();
|
|
1106
|
+
process.stdout.on("resize", handleResize);
|
|
1107
|
+
return () => {
|
|
1108
|
+
process.stdout.off("resize", handleResize);
|
|
1109
|
+
};
|
|
1110
|
+
}, [stdout]);
|
|
1032
1111
|
const list = Object.values(entries);
|
|
1033
1112
|
if (list.length === 0 && !collabSession) return null;
|
|
1113
|
+
const nameMaxLen = Math.max(6, Math.min(14, termWidth - 30));
|
|
1034
1114
|
const leader = list.find((e) => e.id === "leader");
|
|
1035
1115
|
const subagents = list.filter((e) => e.id !== "leader");
|
|
1036
1116
|
const running = subagents.filter((e) => e.status === "running");
|
|
@@ -1050,16 +1130,16 @@ function FleetPanel({ entries, totalCost, collabSession }) {
|
|
|
1050
1130
|
] }),
|
|
1051
1131
|
hasCollab && leader ? /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
1052
1132
|
/* @__PURE__ */ jsx(Text, { color: "yellow", children: "\u25CF" }),
|
|
1053
|
-
/* @__PURE__ */ jsx(Text, { children: leader.name.slice(0,
|
|
1133
|
+
/* @__PURE__ */ jsx(Text, { children: leader.name.slice(0, nameMaxLen) }),
|
|
1054
1134
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2192" }),
|
|
1055
1135
|
/* @__PURE__ */ jsx(Text, { color: "yellow", children: leaderTool })
|
|
1056
1136
|
] }) : null,
|
|
1057
1137
|
shown.map((entry) => {
|
|
1058
|
-
const name = entry.name && entry.name !== entry.id ? entry.name : entry.id.slice(0,
|
|
1138
|
+
const name = entry.name && entry.name !== entry.id ? entry.name : entry.id.slice(0, nameMaxLen);
|
|
1059
1139
|
const tool = entry.currentTool?.name ?? "\u2014";
|
|
1060
1140
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
1061
1141
|
/* @__PURE__ */ jsx(Text, { color: "green", children: "\u25CF" }),
|
|
1062
|
-
/* @__PURE__ */ jsx(Text, { children: name.slice(0,
|
|
1142
|
+
/* @__PURE__ */ jsx(Text, { children: name.slice(0, nameMaxLen) }),
|
|
1063
1143
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2192" }),
|
|
1064
1144
|
/* @__PURE__ */ jsx(Text, { color: "cyan", children: tool })
|
|
1065
1145
|
] }, entry.id);
|
|
@@ -1069,7 +1149,7 @@ function FleetPanel({ entries, totalCost, collabSession }) {
|
|
|
1069
1149
|
"+",
|
|
1070
1150
|
overflow,
|
|
1071
1151
|
": ",
|
|
1072
|
-
running[3]?.name?.slice(0,
|
|
1152
|
+
running[3]?.name?.slice(0, nameMaxLen - 2) ?? "agent",
|
|
1073
1153
|
"\u2026"
|
|
1074
1154
|
] }) : null
|
|
1075
1155
|
] });
|
|
@@ -1542,7 +1622,8 @@ function tokenizePython(line, carry) {
|
|
|
1542
1622
|
}
|
|
1543
1623
|
function tokenizeDiff(line) {
|
|
1544
1624
|
if (line.startsWith("@@")) return [{ text: line, color: C.diffMeta }];
|
|
1545
|
-
if (line.startsWith("+++") || line.startsWith("---"))
|
|
1625
|
+
if (line.startsWith("+++") || line.startsWith("---"))
|
|
1626
|
+
return [{ text: line, color: C.diffMeta, dim: true }];
|
|
1546
1627
|
if (line.startsWith("+")) return [{ text: line, color: C.diffAdd }];
|
|
1547
1628
|
if (line.startsWith("-")) return [{ text: line, color: C.diffDel }];
|
|
1548
1629
|
return [{ text: line, dim: true }];
|
|
@@ -1631,6 +1712,14 @@ function parseAlign(sep2) {
|
|
|
1631
1712
|
if (right) return "right";
|
|
1632
1713
|
return "left";
|
|
1633
1714
|
}
|
|
1715
|
+
function parseSeparatorWidths(sepCells) {
|
|
1716
|
+
return sepCells.map((cell) => {
|
|
1717
|
+
const trimmed = cell.trim();
|
|
1718
|
+
const dashes = trimmed.replace(/:/g, "");
|
|
1719
|
+
if (/^-+$/.test(dashes)) return dashes.length;
|
|
1720
|
+
return null;
|
|
1721
|
+
});
|
|
1722
|
+
}
|
|
1634
1723
|
function renderTable(tableLines, maxWidth) {
|
|
1635
1724
|
const header = parseCells(tableLines[0] ?? "");
|
|
1636
1725
|
const sepCells = parseCells(tableLines[1] ?? "");
|
|
@@ -1644,7 +1733,8 @@ function renderTable(tableLines, maxWidth) {
|
|
|
1644
1733
|
while (row.length < cols) row.push("");
|
|
1645
1734
|
row.length = cols;
|
|
1646
1735
|
}
|
|
1647
|
-
const
|
|
1736
|
+
const sepWidths = parseSeparatorWidths(sepCells);
|
|
1737
|
+
const widths = computeWidths([header, ...dataRows], cols, maxWidth, sepWidths);
|
|
1648
1738
|
const lines = [];
|
|
1649
1739
|
lines.push(border("\u250C", "\u252C", "\u2510", widths));
|
|
1650
1740
|
lines.push(...renderRow(header, widths, aligns));
|
|
@@ -1655,7 +1745,7 @@ function renderTable(tableLines, maxWidth) {
|
|
|
1655
1745
|
lines.push(border("\u2514", "\u2534", "\u2518", widths));
|
|
1656
1746
|
return lines.join("\n");
|
|
1657
1747
|
}
|
|
1658
|
-
function computeWidths(allRows, cols, maxWidth) {
|
|
1748
|
+
function computeWidths(allRows, cols, maxWidth, sepWidths) {
|
|
1659
1749
|
const overhead = 3 * cols + 1;
|
|
1660
1750
|
const avail = Math.max(cols * MIN_COL_WIDTH, maxWidth - overhead);
|
|
1661
1751
|
const natural = new Array(cols).fill(0);
|
|
@@ -1663,9 +1753,16 @@ function computeWidths(allRows, cols, maxWidth) {
|
|
|
1663
1753
|
for (let c = 0; c < cols; c++) {
|
|
1664
1754
|
const cell = row[c] ?? "";
|
|
1665
1755
|
const w = longestWord(cell);
|
|
1666
|
-
const total = cell
|
|
1667
|
-
natural[c] = Math.max(natural[c], total);
|
|
1668
|
-
|
|
1756
|
+
const total = strWidth(cell);
|
|
1757
|
+
natural[c] = Math.max(natural[c], w, total);
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
if (sepWidths) {
|
|
1761
|
+
for (let c = 0; c < cols && c < sepWidths.length; c++) {
|
|
1762
|
+
const sepW = sepWidths[c];
|
|
1763
|
+
if (sepW != null) {
|
|
1764
|
+
natural[c] = Math.max(natural[c], sepW);
|
|
1765
|
+
}
|
|
1669
1766
|
}
|
|
1670
1767
|
}
|
|
1671
1768
|
const sumNatural = natural.reduce((s2, n) => s2 + n, 0);
|
|
@@ -1689,9 +1786,51 @@ function computeWidths(allRows, cols, maxWidth) {
|
|
|
1689
1786
|
return widths;
|
|
1690
1787
|
}
|
|
1691
1788
|
var MIN_COL_WIDTH = 4;
|
|
1789
|
+
function strWidth(s2) {
|
|
1790
|
+
let width = 0;
|
|
1791
|
+
for (const cp of s2) {
|
|
1792
|
+
const code = cp.codePointAt(0);
|
|
1793
|
+
if (code < 32 || code >= 127 && code < 160) {
|
|
1794
|
+
continue;
|
|
1795
|
+
}
|
|
1796
|
+
if (code >= 126976 || // Supplementary Pictographs (U+1F000-U+1FFFF)
|
|
1797
|
+
code >= 9728 && code <= 10175 || // Miscellaneous Symbols, Dingbats
|
|
1798
|
+
code >= 8960 && code <= 9215 || // Miscellaneous Technical
|
|
1799
|
+
code >= 11088 && code <= 11093 || // Stars and similar
|
|
1800
|
+
code >= 10548 && code <= 10549 || // Arrow forms
|
|
1801
|
+
code >= 8592 && code <= 8703 || // Arrows
|
|
1802
|
+
code >= 9632 && code <= 9727 || // Geometric Shapes
|
|
1803
|
+
code >= 9664 && code <= 9726 || // More Geometric Shapes (includes ▶)
|
|
1804
|
+
code >= 9984 && code <= 10175) {
|
|
1805
|
+
width += 2;
|
|
1806
|
+
continue;
|
|
1807
|
+
}
|
|
1808
|
+
if (code >= 4352 && code <= 4447 || // Hangul Jamo
|
|
1809
|
+
code === 9001 || // LEFT-POINTING ANGLE BRACKET
|
|
1810
|
+
code === 9002 || // RIGHT-POINTING ANGLE BRACKET
|
|
1811
|
+
code >= 11904 && code <= 12350 || // CJK Radicals Supplement
|
|
1812
|
+
code >= 12352 && code <= 42191 || // Hiragana, Katakana, CJK
|
|
1813
|
+
code >= 44032 && code <= 55203 || // Hangul Syllables
|
|
1814
|
+
code >= 63744 && code <= 64249 || // CJK Compatibility Ideographs
|
|
1815
|
+
code >= 65040 && code <= 65055 || // Vertical forms
|
|
1816
|
+
code >= 65072 && code <= 65135 || // CJK Compatibility Forms
|
|
1817
|
+
code >= 65280 && code <= 65376 || // Fullwidth Forms
|
|
1818
|
+
code >= 65504 && code <= 65510 || // Halfwidth and Fullwidth Forms
|
|
1819
|
+
code >= 131072 && code <= 196605 || // CJK Extension B+
|
|
1820
|
+
code >= 196608 && code <= 262141) {
|
|
1821
|
+
width += 2;
|
|
1822
|
+
continue;
|
|
1823
|
+
}
|
|
1824
|
+
width += 1;
|
|
1825
|
+
}
|
|
1826
|
+
return width;
|
|
1827
|
+
}
|
|
1692
1828
|
function longestWord(s2) {
|
|
1693
1829
|
let max = 0;
|
|
1694
|
-
for (const w of s2.split(/\s+/))
|
|
1830
|
+
for (const w of s2.split(/\s+/)) {
|
|
1831
|
+
const visualWidth = strWidth(w);
|
|
1832
|
+
if (visualWidth > max) max = visualWidth;
|
|
1833
|
+
}
|
|
1695
1834
|
return max;
|
|
1696
1835
|
}
|
|
1697
1836
|
function border(left, mid, right, widths) {
|
|
@@ -1713,43 +1852,86 @@ function renderRow(cells, widths, aligns) {
|
|
|
1713
1852
|
return out;
|
|
1714
1853
|
}
|
|
1715
1854
|
function wrapCell(text, width) {
|
|
1716
|
-
if (text
|
|
1855
|
+
if (strWidth(text) <= width) return [text];
|
|
1717
1856
|
const out = [];
|
|
1718
1857
|
const words = text.split(/(\s+)/);
|
|
1719
1858
|
let cur = "";
|
|
1859
|
+
let curWidth = 0;
|
|
1720
1860
|
for (const word of words) {
|
|
1721
1861
|
if (!word) continue;
|
|
1722
|
-
|
|
1862
|
+
const wordWidth = strWidth(word);
|
|
1863
|
+
if (curWidth + wordWidth <= width) {
|
|
1723
1864
|
cur += word;
|
|
1865
|
+
curWidth += wordWidth;
|
|
1724
1866
|
continue;
|
|
1725
1867
|
}
|
|
1726
1868
|
if (cur) {
|
|
1727
|
-
out.push(cur
|
|
1869
|
+
out.push(padVisual(cur, width));
|
|
1728
1870
|
cur = "";
|
|
1871
|
+
curWidth = 0;
|
|
1729
1872
|
}
|
|
1730
|
-
if (
|
|
1873
|
+
if (wordWidth > width) {
|
|
1731
1874
|
let rest = word;
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1875
|
+
let restWidth = wordWidth;
|
|
1876
|
+
while (restWidth > width) {
|
|
1877
|
+
let collected = "";
|
|
1878
|
+
let collectedWidth = 0;
|
|
1879
|
+
for (const cp of rest) {
|
|
1880
|
+
const cpWidth = strWidth(cp);
|
|
1881
|
+
if (collectedWidth + cpWidth > width) break;
|
|
1882
|
+
collected += cp;
|
|
1883
|
+
collectedWidth += cpWidth;
|
|
1884
|
+
}
|
|
1885
|
+
out.push(padVisual(collected, width));
|
|
1886
|
+
rest = rest.slice([...collected].join("").length);
|
|
1887
|
+
restWidth = strWidth(rest);
|
|
1735
1888
|
}
|
|
1736
1889
|
cur = rest;
|
|
1890
|
+
curWidth = strWidth(rest);
|
|
1737
1891
|
} else if (!/^\s+$/.test(word)) {
|
|
1738
1892
|
cur = word;
|
|
1893
|
+
curWidth = wordWidth;
|
|
1739
1894
|
}
|
|
1740
1895
|
}
|
|
1741
|
-
if (cur) out.push(cur
|
|
1896
|
+
if (cur) out.push(padVisual(cur, width));
|
|
1742
1897
|
return out.length === 0 ? [""] : out;
|
|
1743
1898
|
}
|
|
1899
|
+
function padVisual(text, targetWidth) {
|
|
1900
|
+
const w = strWidth(text);
|
|
1901
|
+
if (w >= targetWidth) {
|
|
1902
|
+
let taken = 0;
|
|
1903
|
+
let endIdx = 0;
|
|
1904
|
+
for (const cp of text) {
|
|
1905
|
+
const cpw = strWidth(cp);
|
|
1906
|
+
if (taken + cpw > targetWidth) break;
|
|
1907
|
+
taken += cpw;
|
|
1908
|
+
endIdx += [...cp].join("").length;
|
|
1909
|
+
}
|
|
1910
|
+
return text.slice(0, endIdx);
|
|
1911
|
+
}
|
|
1912
|
+
return text + " ".repeat(targetWidth - w);
|
|
1913
|
+
}
|
|
1744
1914
|
function padCell(text, width, align) {
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
if (
|
|
1915
|
+
const visualLen = strWidth(text);
|
|
1916
|
+
let displayText = text;
|
|
1917
|
+
if (visualLen > width) {
|
|
1918
|
+
let takenWidth = 0;
|
|
1919
|
+
let endIdx = 0;
|
|
1920
|
+
for (const cp of text) {
|
|
1921
|
+
const cpWidth = strWidth(cp);
|
|
1922
|
+
if (takenWidth + cpWidth > width) break;
|
|
1923
|
+
takenWidth += cpWidth;
|
|
1924
|
+
endIdx += [...cp].join("").length;
|
|
1925
|
+
}
|
|
1926
|
+
displayText = text.slice(0, endIdx);
|
|
1927
|
+
}
|
|
1928
|
+
const pad = width - strWidth(displayText);
|
|
1929
|
+
if (align === "right") return " ".repeat(pad) + displayText;
|
|
1748
1930
|
if (align === "center") {
|
|
1749
1931
|
const l = Math.floor(pad / 2);
|
|
1750
|
-
return " ".repeat(l) +
|
|
1932
|
+
return " ".repeat(l) + displayText + " ".repeat(pad - l);
|
|
1751
1933
|
}
|
|
1752
|
-
return
|
|
1934
|
+
return displayText + " ".repeat(pad);
|
|
1753
1935
|
}
|
|
1754
1936
|
function parseInline(text) {
|
|
1755
1937
|
const tokens = [];
|
|
@@ -1857,7 +2039,7 @@ function MarkdownView({
|
|
|
1857
2039
|
if (quote && line.startsWith(">")) {
|
|
1858
2040
|
rows.push(
|
|
1859
2041
|
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
|
|
1860
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "
|
|
2042
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " " }),
|
|
1861
2043
|
/* @__PURE__ */ jsx(InlineLine, { tokens: parseInline(quote[1] ?? ""), dim: true })
|
|
1862
2044
|
] }, `q${key++}`)
|
|
1863
2045
|
);
|
|
@@ -1982,13 +2164,13 @@ function splitFencedBlocks(text) {
|
|
|
1982
2164
|
function CodeBlock({
|
|
1983
2165
|
code,
|
|
1984
2166
|
lang,
|
|
1985
|
-
|
|
2167
|
+
contentWidth
|
|
1986
2168
|
}) {
|
|
1987
2169
|
let lines = code.replace(/\n+$/, "").split("\n");
|
|
1988
2170
|
const hidden = Math.max(0, lines.length - MAX_CODE_LINES);
|
|
1989
2171
|
if (hidden > 0) lines = lines.slice(0, MAX_CODE_LINES);
|
|
1990
2172
|
const gutterW = String(lines.length).length;
|
|
1991
|
-
const maxW = Math.max(20, Math.min(
|
|
2173
|
+
const maxW = Math.max(20, Math.min(contentWidth - 6 - gutterW - 1, 120));
|
|
1992
2174
|
let carry = {};
|
|
1993
2175
|
const rows = lines.map((raw) => {
|
|
1994
2176
|
const display = raw.length > maxW ? `${raw.slice(0, maxW - 1)}\u2026` : raw;
|
|
@@ -1996,20 +2178,31 @@ function CodeBlock({
|
|
|
1996
2178
|
carry = r.carry;
|
|
1997
2179
|
return r.tokens;
|
|
1998
2180
|
});
|
|
1999
|
-
return /* @__PURE__ */ jsxs(
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2181
|
+
return /* @__PURE__ */ jsxs(
|
|
2182
|
+
Box,
|
|
2183
|
+
{
|
|
2184
|
+
flexDirection: "column",
|
|
2185
|
+
marginLeft: 2,
|
|
2186
|
+
marginY: 0,
|
|
2187
|
+
borderStyle: "round",
|
|
2188
|
+
borderColor: theme.borderDefault,
|
|
2189
|
+
paddingX: 1,
|
|
2190
|
+
children: [
|
|
2191
|
+
lang !== "plain" ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: lang }) : null,
|
|
2192
|
+
rows.map((tokens, i) => (
|
|
2193
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: code lines are positional
|
|
2194
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
2195
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: `${String(i + 1).padStart(gutterW, " ")} ` }),
|
|
2196
|
+
tokens.length === 0 ? " " : tokens.map((t, j) => (
|
|
2197
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: token order is stable per line
|
|
2198
|
+
/* @__PURE__ */ jsx(Text, { color: t.color, dimColor: t.dim, bold: t.bold, children: t.text }, j)
|
|
2199
|
+
))
|
|
2200
|
+
] }, i)
|
|
2201
|
+
)),
|
|
2202
|
+
hidden > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, italic: true, children: `\u2026 +${hidden} more line${hidden === 1 ? "" : "s"}` }) : null
|
|
2203
|
+
]
|
|
2204
|
+
}
|
|
2205
|
+
);
|
|
2013
2206
|
}
|
|
2014
2207
|
function AssistantBody({
|
|
2015
2208
|
text,
|
|
@@ -2021,7 +2214,7 @@ function AssistantBody({
|
|
|
2021
2214
|
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: segments.map(
|
|
2022
2215
|
(seg, i) => seg.type === "code" ? (
|
|
2023
2216
|
// biome-ignore lint/suspicious/noArrayIndexKey: segment order is stable
|
|
2024
|
-
/* @__PURE__ */ jsx(CodeBlock, { code: seg.text, lang: seg.lang ?? "plain",
|
|
2217
|
+
/* @__PURE__ */ jsx(CodeBlock, { code: seg.text, lang: seg.lang ?? "plain", contentWidth: inner }, i)
|
|
2025
2218
|
) : (
|
|
2026
2219
|
// biome-ignore lint/suspicious/noArrayIndexKey: segment order is stable
|
|
2027
2220
|
/* @__PURE__ */ jsx(MarkdownView, { text: seg.text, termWidth: inner }, i)
|
|
@@ -2150,14 +2343,7 @@ var Entry = React4.memo(function Entry2({
|
|
|
2150
2343
|
paddingLeft: 1,
|
|
2151
2344
|
children: [
|
|
2152
2345
|
/* @__PURE__ */ jsx(Box, { flexDirection: "row", children: /* @__PURE__ */ jsx(Text, { bold: true, color: theme.assistant, children: "ASSISTANT" }) }),
|
|
2153
|
-
/* @__PURE__ */ jsx(
|
|
2154
|
-
AssistantBody,
|
|
2155
|
-
{
|
|
2156
|
-
text: entry.text,
|
|
2157
|
-
termWidth,
|
|
2158
|
-
contentWidth
|
|
2159
|
-
}
|
|
2160
|
-
)
|
|
2346
|
+
/* @__PURE__ */ jsx(AssistantBody, { text: entry.text, termWidth, contentWidth })
|
|
2161
2347
|
]
|
|
2162
2348
|
}
|
|
2163
2349
|
);
|
|
@@ -3272,8 +3458,10 @@ function fmtRecentTool(tool) {
|
|
|
3272
3458
|
const name = tool.name.length > 18 ? `${tool.name.slice(0, 17)}...` : tool.name;
|
|
3273
3459
|
const parts = [status, name];
|
|
3274
3460
|
if (typeof tool.durationMs === "number") parts.push(fmtElapsed2(tool.durationMs));
|
|
3275
|
-
if (typeof tool.outputBytes === "number" && tool.outputBytes > 0)
|
|
3276
|
-
|
|
3461
|
+
if (typeof tool.outputBytes === "number" && tool.outputBytes > 0)
|
|
3462
|
+
parts.push(fmtBytes2(tool.outputBytes));
|
|
3463
|
+
if (typeof tool.outputLines === "number" && tool.outputLines > 0)
|
|
3464
|
+
parts.push(`${tool.outputLines}L`);
|
|
3277
3465
|
return parts.join(" ");
|
|
3278
3466
|
}
|
|
3279
3467
|
function fmtRecentMessage(message) {
|
|
@@ -3406,7 +3594,9 @@ function PhaseMonitor({
|
|
|
3406
3594
|
if (key.escape) onClose();
|
|
3407
3595
|
});
|
|
3408
3596
|
const phaseList = Object.values(phases);
|
|
3409
|
-
const running = phaseList.filter(
|
|
3597
|
+
const running = phaseList.filter(
|
|
3598
|
+
(p) => runningPhaseIds.includes(Object.keys(phases).find((k) => phases[k] === p) ?? "")
|
|
3599
|
+
);
|
|
3410
3600
|
const done = phaseList.filter((p) => p.status === "completed" || p.status === "skipped");
|
|
3411
3601
|
const failed = phaseList.filter((p) => p.status === "failed");
|
|
3412
3602
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [
|
|
@@ -3617,7 +3807,7 @@ function ScrollableHistory({
|
|
|
3617
3807
|
lastReported.current = height;
|
|
3618
3808
|
onMeasure(height);
|
|
3619
3809
|
}
|
|
3620
|
-
});
|
|
3810
|
+
}, [onMeasure]);
|
|
3621
3811
|
const vp = Math.max(1, viewportRows);
|
|
3622
3812
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
|
|
3623
3813
|
/* @__PURE__ */ jsx(
|
|
@@ -3853,7 +4043,9 @@ function WorktreePanel({
|
|
|
3853
4043
|
}) {
|
|
3854
4044
|
const list = Object.values(worktrees);
|
|
3855
4045
|
if (list.length === 0) return null;
|
|
3856
|
-
const active = list.filter(
|
|
4046
|
+
const active = list.filter(
|
|
4047
|
+
(w) => w.status === "active" || w.status === "committing" || w.status === "merging"
|
|
4048
|
+
).length;
|
|
3857
4049
|
const merged = list.filter((w) => w.status === "merged").length;
|
|
3858
4050
|
const failed = list.filter((w) => w.status === "failed" || w.status === "needs-review").length;
|
|
3859
4051
|
return /* @__PURE__ */ jsxs(
|
|
@@ -3897,7 +4089,8 @@ function WorktreePanel({
|
|
|
3897
4089
|
/* @__PURE__ */ jsx(Text, { children: w.branch.replace(/^wstack\/ap\//, "").slice(0, 18).padEnd(18) }),
|
|
3898
4090
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: w.ownerLabel.slice(0, 12) }),
|
|
3899
4091
|
conflict ? /* @__PURE__ */ jsx(Text, { color: "magenta", children: " CONFLICT" }) : /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
3900
|
-
"
|
|
4092
|
+
" ",
|
|
4093
|
+
"+",
|
|
3901
4094
|
w.insertions,
|
|
3902
4095
|
"/-",
|
|
3903
4096
|
w.deletions,
|
|
@@ -4061,13 +4254,18 @@ function createKillSlashCommand() {
|
|
|
4061
4254
|
if (sub === "all") {
|
|
4062
4255
|
const pids = getProcessRegistry().killAll();
|
|
4063
4256
|
if (pids.length === 0) return { message: "No processes to kill." };
|
|
4064
|
-
return {
|
|
4257
|
+
return {
|
|
4258
|
+
message: `Killed ${pids.length} process${pids.length === 1 ? "" : "es"}: ${pids.join(", ")}`
|
|
4259
|
+
};
|
|
4065
4260
|
}
|
|
4066
4261
|
if (sub === "force") {
|
|
4067
4262
|
getProcessRegistry().forceBreakerOpen();
|
|
4068
4263
|
const pids = getProcessRegistry().killAll({ force: true });
|
|
4069
|
-
if (pids.length === 0)
|
|
4070
|
-
|
|
4264
|
+
if (pids.length === 0)
|
|
4265
|
+
return { message: "Circuit breaker forced open. No processes to kill." };
|
|
4266
|
+
return {
|
|
4267
|
+
message: `Force-killed ${pids.length} process${pids.length === 1 ? "" : "es"}: ${pids.join(", ")}`
|
|
4268
|
+
};
|
|
4071
4269
|
}
|
|
4072
4270
|
if (sub === "reset") {
|
|
4073
4271
|
getProcessRegistry().forceBreakerReset();
|
|
@@ -5236,6 +5434,7 @@ function App({
|
|
|
5236
5434
|
switchProviderAndModel,
|
|
5237
5435
|
getSettings,
|
|
5238
5436
|
saveSettings,
|
|
5437
|
+
predictNext,
|
|
5239
5438
|
switchAutonomy,
|
|
5240
5439
|
effectiveMaxContext,
|
|
5241
5440
|
onExit,
|
|
@@ -5261,8 +5460,12 @@ function App({
|
|
|
5261
5460
|
const [hiddenItems, setHiddenItems] = useState(statuslineHiddenItems);
|
|
5262
5461
|
const { stdout } = useStdout();
|
|
5263
5462
|
const [termRows, setTermRows] = useState(stdout?.rows ?? 24);
|
|
5463
|
+
const [termCols, setTermCols] = useState(stdout?.columns ?? 80);
|
|
5264
5464
|
useEffect(() => {
|
|
5265
|
-
const onResize = () =>
|
|
5465
|
+
const onResize = () => {
|
|
5466
|
+
setTermRows(process.stdout.rows ?? 24);
|
|
5467
|
+
setTermCols(process.stdout.columns ?? 80);
|
|
5468
|
+
};
|
|
5266
5469
|
process.stdout.on("resize", onResize);
|
|
5267
5470
|
return () => {
|
|
5268
5471
|
process.stdout.off("resize", onResize);
|
|
@@ -5412,7 +5615,7 @@ function App({
|
|
|
5412
5615
|
if (vp !== s2.viewportRows) {
|
|
5413
5616
|
dispatch({ type: "setViewportRows", rows: vp });
|
|
5414
5617
|
}
|
|
5415
|
-
});
|
|
5618
|
+
}, [managedLive, termRows]);
|
|
5416
5619
|
const handleKeyRef = useRef(null);
|
|
5417
5620
|
const pendingClickConfirmRef = useRef(null);
|
|
5418
5621
|
const openModelPickerRef = useRef(null);
|
|
@@ -5460,7 +5663,7 @@ function App({
|
|
|
5460
5663
|
}
|
|
5461
5664
|
return;
|
|
5462
5665
|
}
|
|
5463
|
-
const cols =
|
|
5666
|
+
const cols = termCols || 80;
|
|
5464
5667
|
const onScrollbar = cols > 0 && ev.x >= cols - 2 && ev.y >= 1 && ev.y <= rows;
|
|
5465
5668
|
if (onScrollbar && s2.totalLines > rows) {
|
|
5466
5669
|
scrollbarDragRef.current = true;
|
|
@@ -5548,7 +5751,7 @@ function App({
|
|
|
5548
5751
|
if (!picker || picker.count === 0) {
|
|
5549
5752
|
const inputDisabled = s2.status === "aborting" && !s2.steeringPending;
|
|
5550
5753
|
if (!inputDisabled) {
|
|
5551
|
-
const cols =
|
|
5754
|
+
const cols = termCols || 80;
|
|
5552
5755
|
const inputTop = s2.viewportRows + affordance + liveStripRowsRef.current + 1;
|
|
5553
5756
|
const inputRows = layoutInputRows(INPUT_PROMPT, s2.buffer, s2.cursor, cols).length;
|
|
5554
5757
|
const rowIdx = ev.y - inputTop;
|
|
@@ -5591,7 +5794,8 @@ function App({
|
|
|
5591
5794
|
}
|
|
5592
5795
|
},
|
|
5593
5796
|
// dispatch is stable (useReducer); refs are mutable — no reactive deps.
|
|
5594
|
-
|
|
5797
|
+
// termCols is stable (useState + resize effect).
|
|
5798
|
+
[termCols]
|
|
5595
5799
|
);
|
|
5596
5800
|
useEffect(() => {
|
|
5597
5801
|
if (!subscribeMouse) return;
|
|
@@ -5760,6 +5964,12 @@ function App({
|
|
|
5760
5964
|
}, [agent.ctx.meta]);
|
|
5761
5965
|
const prevAnyOverlayOpen = useRef(false);
|
|
5762
5966
|
const prevEntriesCount = useRef(0);
|
|
5967
|
+
const eraseLiveRegion = useCallback(() => {
|
|
5968
|
+
try {
|
|
5969
|
+
process.stdout.write("\x1B[J");
|
|
5970
|
+
} catch {
|
|
5971
|
+
}
|
|
5972
|
+
}, []);
|
|
5763
5973
|
useEffect(() => {
|
|
5764
5974
|
const anyOpenNow = state.picker.open || state.slashPicker.open || state.modelPicker.open || state.autonomyPicker.open || state.settingsPicker.open || state.confirmQueue.length > 0;
|
|
5765
5975
|
const overlayClosed = prevAnyOverlayOpen.current && !anyOpenNow;
|
|
@@ -5767,10 +5977,7 @@ function App({
|
|
|
5767
5977
|
prevAnyOverlayOpen.current = anyOpenNow;
|
|
5768
5978
|
prevEntriesCount.current = state.entries.length;
|
|
5769
5979
|
if (overlayClosed || newEntryCommitted) {
|
|
5770
|
-
|
|
5771
|
-
process.stdout.write("\x1B[J");
|
|
5772
|
-
} catch {
|
|
5773
|
-
}
|
|
5980
|
+
eraseLiveRegion();
|
|
5774
5981
|
}
|
|
5775
5982
|
}, [
|
|
5776
5983
|
state.picker.open,
|
|
@@ -5779,8 +5986,16 @@ function App({
|
|
|
5779
5986
|
state.autonomyPicker.open,
|
|
5780
5987
|
state.settingsPicker.open,
|
|
5781
5988
|
state.confirmQueue.length,
|
|
5782
|
-
state.entries.length
|
|
5989
|
+
state.entries.length,
|
|
5990
|
+
eraseLiveRegion
|
|
5783
5991
|
]);
|
|
5992
|
+
useEffect(() => {
|
|
5993
|
+
const handleResize = () => eraseLiveRegion();
|
|
5994
|
+
process.stdout.on("resize", handleResize);
|
|
5995
|
+
return () => {
|
|
5996
|
+
process.stdout.off("resize", handleResize);
|
|
5997
|
+
};
|
|
5998
|
+
}, [eraseLiveRegion]);
|
|
5784
5999
|
useEffect(() => {
|
|
5785
6000
|
const detected = detectAtToken(state.buffer, state.cursor);
|
|
5786
6001
|
if (!detected) {
|
|
@@ -7754,6 +7969,22 @@ function App({
|
|
|
7754
7969
|
}
|
|
7755
7970
|
});
|
|
7756
7971
|
}
|
|
7972
|
+
if (result.status === "done" && predictNext) {
|
|
7973
|
+
try {
|
|
7974
|
+
const userRequest = blocks.filter((b) => b.type === "text").map((b) => b.text).join(" ").trim();
|
|
7975
|
+
const predictions = await predictNext({
|
|
7976
|
+
userRequest,
|
|
7977
|
+
assistantSummary: result.finalText ?? ""
|
|
7978
|
+
});
|
|
7979
|
+
if (predictions.length > 0) {
|
|
7980
|
+
const text = ["\u21B3 likely next:", ...predictions.map((p, i) => ` ${i + 1}. ${p}`)].join(
|
|
7981
|
+
"\n"
|
|
7982
|
+
);
|
|
7983
|
+
dispatch({ type: "addEntry", entry: { kind: "turn-summary", text } });
|
|
7984
|
+
}
|
|
7985
|
+
} catch {
|
|
7986
|
+
}
|
|
7987
|
+
}
|
|
7757
7988
|
} catch (err) {
|
|
7758
7989
|
dispatch({
|
|
7759
7990
|
type: "addEntry",
|
|
@@ -8397,8 +8628,48 @@ async function runTui(opts) {
|
|
|
8397
8628
|
let inkStdin = stdin;
|
|
8398
8629
|
let detachMouse = null;
|
|
8399
8630
|
if (useMouse) {
|
|
8400
|
-
|
|
8401
|
-
|
|
8631
|
+
class KeyboardReadable extends Readable {
|
|
8632
|
+
pendingChunks = [];
|
|
8633
|
+
// eslint-disable-next-line no-useless-constructor
|
|
8634
|
+
constructor() {
|
|
8635
|
+
super({ encoding: "utf8", highWaterMark: 64 * 1024 });
|
|
8636
|
+
}
|
|
8637
|
+
_read(_size) {
|
|
8638
|
+
this.flushPending();
|
|
8639
|
+
}
|
|
8640
|
+
flushPending() {
|
|
8641
|
+
while (this.pendingChunks.length > 0) {
|
|
8642
|
+
const chunk = this.pendingChunks[0];
|
|
8643
|
+
const ok = this.push(chunk);
|
|
8644
|
+
this.pendingChunks.shift();
|
|
8645
|
+
if (!ok) {
|
|
8646
|
+
break;
|
|
8647
|
+
}
|
|
8648
|
+
}
|
|
8649
|
+
}
|
|
8650
|
+
/** Called by the stdin data handler when keyboard bytes are available. */
|
|
8651
|
+
doPush(chunk) {
|
|
8652
|
+
if (chunk.length === 0) return;
|
|
8653
|
+
const ok = this.push(chunk);
|
|
8654
|
+
if (ok) {
|
|
8655
|
+
if (this.pendingChunks.length > 0) {
|
|
8656
|
+
this.flushPending();
|
|
8657
|
+
}
|
|
8658
|
+
} else {
|
|
8659
|
+
if (this.pendingChunks.length >= 100) {
|
|
8660
|
+
this.pendingChunks.shift();
|
|
8661
|
+
}
|
|
8662
|
+
this.pendingChunks.push(chunk);
|
|
8663
|
+
}
|
|
8664
|
+
}
|
|
8665
|
+
/** Called on shutdown so the stream closes cleanly. */
|
|
8666
|
+
doEnd() {
|
|
8667
|
+
this.pendingChunks = [];
|
|
8668
|
+
this.push(null);
|
|
8669
|
+
}
|
|
8670
|
+
}
|
|
8671
|
+
const keyboardStream = new KeyboardReadable();
|
|
8672
|
+
const p = keyboardStream;
|
|
8402
8673
|
p.isTTY = true;
|
|
8403
8674
|
p.setRawMode = (mode) => {
|
|
8404
8675
|
try {
|
|
@@ -8429,10 +8700,13 @@ async function runTui(opts) {
|
|
|
8429
8700
|
}
|
|
8430
8701
|
}
|
|
8431
8702
|
const rest = stripSgrMouse(chunk);
|
|
8432
|
-
|
|
8703
|
+
keyboardStream.doPush(rest);
|
|
8433
8704
|
};
|
|
8434
8705
|
stdin.on("data", onData);
|
|
8435
|
-
detachMouse = () =>
|
|
8706
|
+
detachMouse = () => {
|
|
8707
|
+
stdin.off("data", onData);
|
|
8708
|
+
keyboardStream.doEnd();
|
|
8709
|
+
};
|
|
8436
8710
|
inkStdin = p;
|
|
8437
8711
|
}
|
|
8438
8712
|
const subscribeMouse = useMouse ? (fn) => {
|
|
@@ -8551,6 +8825,7 @@ async function runTui(opts) {
|
|
|
8551
8825
|
projectRoot: opts.projectRoot,
|
|
8552
8826
|
getSettings: opts.getSettings,
|
|
8553
8827
|
saveSettings: opts.saveSettings,
|
|
8828
|
+
predictNext: opts.predictNext,
|
|
8554
8829
|
mouse: useMouse,
|
|
8555
8830
|
subscribeMouse,
|
|
8556
8831
|
// Managed viewport (in-app scroll + collapsibility) follows
|