@wrongstack/tui 0.10.3 → 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 +190 -64
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -234,6 +234,15 @@ interface RunTuiOptions {
|
|
|
234
234
|
mode: 'off' | 'suggest' | 'auto';
|
|
235
235
|
delayMs: number;
|
|
236
236
|
}) => string | null | Promise<string | null>;
|
|
237
|
+
/**
|
|
238
|
+
* Predict likely next steps after a completed turn. The CLI wires this from
|
|
239
|
+
* the session provider and the `/next` toggle; it returns [] when prediction
|
|
240
|
+
* is disabled or autonomy isn't 'off'. Display-only — never executed.
|
|
241
|
+
*/
|
|
242
|
+
predictNext?: (input: {
|
|
243
|
+
userRequest: string;
|
|
244
|
+
assistantSummary: string;
|
|
245
|
+
}) => Promise<string[]>;
|
|
237
246
|
}
|
|
238
247
|
declare function runTui(opts: RunTuiOptions): Promise<number>;
|
|
239
248
|
|
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';
|
|
@@ -882,21 +882,58 @@ function AgentsMonitor({
|
|
|
882
882
|
] });
|
|
883
883
|
}
|
|
884
884
|
var AUTONOMY_OPTIONS = [
|
|
885
|
-
{
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
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
|
+
}
|
|
890
915
|
];
|
|
891
|
-
function AutonomyPicker({
|
|
916
|
+
function AutonomyPicker({
|
|
917
|
+
options,
|
|
918
|
+
selected,
|
|
919
|
+
hint
|
|
920
|
+
}) {
|
|
892
921
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
|
|
893
922
|
/* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "\u2501\u2501 Autonomy Mode \u2501\u2501" }),
|
|
894
923
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 navigate \xB7 Enter select \xB7 Esc cancel \xB7 Ctrl+C exit" }),
|
|
895
|
-
options.map((opt, i) => /* @__PURE__ */ jsxs(
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
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
|
+
)),
|
|
900
937
|
hint ? /* @__PURE__ */ jsx(Text, { color: "yellow", children: hint }) : null
|
|
901
938
|
] });
|
|
902
939
|
}
|
|
@@ -934,7 +971,8 @@ function CheckpointTimeline({
|
|
|
934
971
|
new Date(cp.ts).toLocaleTimeString()
|
|
935
972
|
] }),
|
|
936
973
|
cp.fileCount > 0 && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
937
|
-
"
|
|
974
|
+
" ",
|
|
975
|
+
"\xB7 ",
|
|
938
976
|
cp.fileCount,
|
|
939
977
|
" file",
|
|
940
978
|
cp.fileCount !== 1 ? "s" : ""
|
|
@@ -1055,7 +1093,11 @@ function FilePicker({ query, matches, selected }) {
|
|
|
1055
1093
|
function highlight(path3, _query) {
|
|
1056
1094
|
return path3;
|
|
1057
1095
|
}
|
|
1058
|
-
function FleetPanel({
|
|
1096
|
+
function FleetPanel({
|
|
1097
|
+
entries,
|
|
1098
|
+
totalCost,
|
|
1099
|
+
collabSession
|
|
1100
|
+
}) {
|
|
1059
1101
|
const { stdout } = useStdout();
|
|
1060
1102
|
const [termWidth, setTermWidth] = useState(stdout?.columns ?? 90);
|
|
1061
1103
|
useEffect(() => {
|
|
@@ -1580,7 +1622,8 @@ function tokenizePython(line, carry) {
|
|
|
1580
1622
|
}
|
|
1581
1623
|
function tokenizeDiff(line) {
|
|
1582
1624
|
if (line.startsWith("@@")) return [{ text: line, color: C.diffMeta }];
|
|
1583
|
-
if (line.startsWith("+++") || line.startsWith("---"))
|
|
1625
|
+
if (line.startsWith("+++") || line.startsWith("---"))
|
|
1626
|
+
return [{ text: line, color: C.diffMeta, dim: true }];
|
|
1584
1627
|
if (line.startsWith("+")) return [{ text: line, color: C.diffAdd }];
|
|
1585
1628
|
if (line.startsWith("-")) return [{ text: line, color: C.diffDel }];
|
|
1586
1629
|
return [{ text: line, dim: true }];
|
|
@@ -1996,7 +2039,7 @@ function MarkdownView({
|
|
|
1996
2039
|
if (quote && line.startsWith(">")) {
|
|
1997
2040
|
rows.push(
|
|
1998
2041
|
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
|
|
1999
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "
|
|
2042
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " " }),
|
|
2000
2043
|
/* @__PURE__ */ jsx(InlineLine, { tokens: parseInline(quote[1] ?? ""), dim: true })
|
|
2001
2044
|
] }, `q${key++}`)
|
|
2002
2045
|
);
|
|
@@ -2121,13 +2164,13 @@ function splitFencedBlocks(text) {
|
|
|
2121
2164
|
function CodeBlock({
|
|
2122
2165
|
code,
|
|
2123
2166
|
lang,
|
|
2124
|
-
|
|
2167
|
+
contentWidth
|
|
2125
2168
|
}) {
|
|
2126
2169
|
let lines = code.replace(/\n+$/, "").split("\n");
|
|
2127
2170
|
const hidden = Math.max(0, lines.length - MAX_CODE_LINES);
|
|
2128
2171
|
if (hidden > 0) lines = lines.slice(0, MAX_CODE_LINES);
|
|
2129
2172
|
const gutterW = String(lines.length).length;
|
|
2130
|
-
const maxW = Math.max(20, Math.min(
|
|
2173
|
+
const maxW = Math.max(20, Math.min(contentWidth - 6 - gutterW - 1, 120));
|
|
2131
2174
|
let carry = {};
|
|
2132
2175
|
const rows = lines.map((raw) => {
|
|
2133
2176
|
const display = raw.length > maxW ? `${raw.slice(0, maxW - 1)}\u2026` : raw;
|
|
@@ -2135,20 +2178,31 @@ function CodeBlock({
|
|
|
2135
2178
|
carry = r.carry;
|
|
2136
2179
|
return r.tokens;
|
|
2137
2180
|
});
|
|
2138
|
-
return /* @__PURE__ */ jsxs(
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
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
|
+
);
|
|
2152
2206
|
}
|
|
2153
2207
|
function AssistantBody({
|
|
2154
2208
|
text,
|
|
@@ -2160,7 +2214,7 @@ function AssistantBody({
|
|
|
2160
2214
|
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: segments.map(
|
|
2161
2215
|
(seg, i) => seg.type === "code" ? (
|
|
2162
2216
|
// biome-ignore lint/suspicious/noArrayIndexKey: segment order is stable
|
|
2163
|
-
/* @__PURE__ */ jsx(CodeBlock, { code: seg.text, lang: seg.lang ?? "plain",
|
|
2217
|
+
/* @__PURE__ */ jsx(CodeBlock, { code: seg.text, lang: seg.lang ?? "plain", contentWidth: inner }, i)
|
|
2164
2218
|
) : (
|
|
2165
2219
|
// biome-ignore lint/suspicious/noArrayIndexKey: segment order is stable
|
|
2166
2220
|
/* @__PURE__ */ jsx(MarkdownView, { text: seg.text, termWidth: inner }, i)
|
|
@@ -2289,14 +2343,7 @@ var Entry = React4.memo(function Entry2({
|
|
|
2289
2343
|
paddingLeft: 1,
|
|
2290
2344
|
children: [
|
|
2291
2345
|
/* @__PURE__ */ jsx(Box, { flexDirection: "row", children: /* @__PURE__ */ jsx(Text, { bold: true, color: theme.assistant, children: "ASSISTANT" }) }),
|
|
2292
|
-
/* @__PURE__ */ jsx(
|
|
2293
|
-
AssistantBody,
|
|
2294
|
-
{
|
|
2295
|
-
text: entry.text,
|
|
2296
|
-
termWidth,
|
|
2297
|
-
contentWidth
|
|
2298
|
-
}
|
|
2299
|
-
)
|
|
2346
|
+
/* @__PURE__ */ jsx(AssistantBody, { text: entry.text, termWidth, contentWidth })
|
|
2300
2347
|
]
|
|
2301
2348
|
}
|
|
2302
2349
|
);
|
|
@@ -3411,8 +3458,10 @@ function fmtRecentTool(tool) {
|
|
|
3411
3458
|
const name = tool.name.length > 18 ? `${tool.name.slice(0, 17)}...` : tool.name;
|
|
3412
3459
|
const parts = [status, name];
|
|
3413
3460
|
if (typeof tool.durationMs === "number") parts.push(fmtElapsed2(tool.durationMs));
|
|
3414
|
-
if (typeof tool.outputBytes === "number" && tool.outputBytes > 0)
|
|
3415
|
-
|
|
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`);
|
|
3416
3465
|
return parts.join(" ");
|
|
3417
3466
|
}
|
|
3418
3467
|
function fmtRecentMessage(message) {
|
|
@@ -3545,7 +3594,9 @@ function PhaseMonitor({
|
|
|
3545
3594
|
if (key.escape) onClose();
|
|
3546
3595
|
});
|
|
3547
3596
|
const phaseList = Object.values(phases);
|
|
3548
|
-
const running = phaseList.filter(
|
|
3597
|
+
const running = phaseList.filter(
|
|
3598
|
+
(p) => runningPhaseIds.includes(Object.keys(phases).find((k) => phases[k] === p) ?? "")
|
|
3599
|
+
);
|
|
3549
3600
|
const done = phaseList.filter((p) => p.status === "completed" || p.status === "skipped");
|
|
3550
3601
|
const failed = phaseList.filter((p) => p.status === "failed");
|
|
3551
3602
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [
|
|
@@ -3756,7 +3807,7 @@ function ScrollableHistory({
|
|
|
3756
3807
|
lastReported.current = height;
|
|
3757
3808
|
onMeasure(height);
|
|
3758
3809
|
}
|
|
3759
|
-
});
|
|
3810
|
+
}, [onMeasure]);
|
|
3760
3811
|
const vp = Math.max(1, viewportRows);
|
|
3761
3812
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
|
|
3762
3813
|
/* @__PURE__ */ jsx(
|
|
@@ -3992,7 +4043,9 @@ function WorktreePanel({
|
|
|
3992
4043
|
}) {
|
|
3993
4044
|
const list = Object.values(worktrees);
|
|
3994
4045
|
if (list.length === 0) return null;
|
|
3995
|
-
const active = list.filter(
|
|
4046
|
+
const active = list.filter(
|
|
4047
|
+
(w) => w.status === "active" || w.status === "committing" || w.status === "merging"
|
|
4048
|
+
).length;
|
|
3996
4049
|
const merged = list.filter((w) => w.status === "merged").length;
|
|
3997
4050
|
const failed = list.filter((w) => w.status === "failed" || w.status === "needs-review").length;
|
|
3998
4051
|
return /* @__PURE__ */ jsxs(
|
|
@@ -4036,7 +4089,8 @@ function WorktreePanel({
|
|
|
4036
4089
|
/* @__PURE__ */ jsx(Text, { children: w.branch.replace(/^wstack\/ap\//, "").slice(0, 18).padEnd(18) }),
|
|
4037
4090
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: w.ownerLabel.slice(0, 12) }),
|
|
4038
4091
|
conflict ? /* @__PURE__ */ jsx(Text, { color: "magenta", children: " CONFLICT" }) : /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
4039
|
-
"
|
|
4092
|
+
" ",
|
|
4093
|
+
"+",
|
|
4040
4094
|
w.insertions,
|
|
4041
4095
|
"/-",
|
|
4042
4096
|
w.deletions,
|
|
@@ -4200,13 +4254,18 @@ function createKillSlashCommand() {
|
|
|
4200
4254
|
if (sub === "all") {
|
|
4201
4255
|
const pids = getProcessRegistry().killAll();
|
|
4202
4256
|
if (pids.length === 0) return { message: "No processes to kill." };
|
|
4203
|
-
return {
|
|
4257
|
+
return {
|
|
4258
|
+
message: `Killed ${pids.length} process${pids.length === 1 ? "" : "es"}: ${pids.join(", ")}`
|
|
4259
|
+
};
|
|
4204
4260
|
}
|
|
4205
4261
|
if (sub === "force") {
|
|
4206
4262
|
getProcessRegistry().forceBreakerOpen();
|
|
4207
4263
|
const pids = getProcessRegistry().killAll({ force: true });
|
|
4208
|
-
if (pids.length === 0)
|
|
4209
|
-
|
|
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
|
+
};
|
|
4210
4269
|
}
|
|
4211
4270
|
if (sub === "reset") {
|
|
4212
4271
|
getProcessRegistry().forceBreakerReset();
|
|
@@ -5375,6 +5434,7 @@ function App({
|
|
|
5375
5434
|
switchProviderAndModel,
|
|
5376
5435
|
getSettings,
|
|
5377
5436
|
saveSettings,
|
|
5437
|
+
predictNext,
|
|
5378
5438
|
switchAutonomy,
|
|
5379
5439
|
effectiveMaxContext,
|
|
5380
5440
|
onExit,
|
|
@@ -5400,8 +5460,12 @@ function App({
|
|
|
5400
5460
|
const [hiddenItems, setHiddenItems] = useState(statuslineHiddenItems);
|
|
5401
5461
|
const { stdout } = useStdout();
|
|
5402
5462
|
const [termRows, setTermRows] = useState(stdout?.rows ?? 24);
|
|
5463
|
+
const [termCols, setTermCols] = useState(stdout?.columns ?? 80);
|
|
5403
5464
|
useEffect(() => {
|
|
5404
|
-
const onResize = () =>
|
|
5465
|
+
const onResize = () => {
|
|
5466
|
+
setTermRows(process.stdout.rows ?? 24);
|
|
5467
|
+
setTermCols(process.stdout.columns ?? 80);
|
|
5468
|
+
};
|
|
5405
5469
|
process.stdout.on("resize", onResize);
|
|
5406
5470
|
return () => {
|
|
5407
5471
|
process.stdout.off("resize", onResize);
|
|
@@ -5551,7 +5615,7 @@ function App({
|
|
|
5551
5615
|
if (vp !== s2.viewportRows) {
|
|
5552
5616
|
dispatch({ type: "setViewportRows", rows: vp });
|
|
5553
5617
|
}
|
|
5554
|
-
});
|
|
5618
|
+
}, [managedLive, termRows]);
|
|
5555
5619
|
const handleKeyRef = useRef(null);
|
|
5556
5620
|
const pendingClickConfirmRef = useRef(null);
|
|
5557
5621
|
const openModelPickerRef = useRef(null);
|
|
@@ -5599,7 +5663,7 @@ function App({
|
|
|
5599
5663
|
}
|
|
5600
5664
|
return;
|
|
5601
5665
|
}
|
|
5602
|
-
const cols =
|
|
5666
|
+
const cols = termCols || 80;
|
|
5603
5667
|
const onScrollbar = cols > 0 && ev.x >= cols - 2 && ev.y >= 1 && ev.y <= rows;
|
|
5604
5668
|
if (onScrollbar && s2.totalLines > rows) {
|
|
5605
5669
|
scrollbarDragRef.current = true;
|
|
@@ -5687,7 +5751,7 @@ function App({
|
|
|
5687
5751
|
if (!picker || picker.count === 0) {
|
|
5688
5752
|
const inputDisabled = s2.status === "aborting" && !s2.steeringPending;
|
|
5689
5753
|
if (!inputDisabled) {
|
|
5690
|
-
const cols =
|
|
5754
|
+
const cols = termCols || 80;
|
|
5691
5755
|
const inputTop = s2.viewportRows + affordance + liveStripRowsRef.current + 1;
|
|
5692
5756
|
const inputRows = layoutInputRows(INPUT_PROMPT, s2.buffer, s2.cursor, cols).length;
|
|
5693
5757
|
const rowIdx = ev.y - inputTop;
|
|
@@ -5730,7 +5794,8 @@ function App({
|
|
|
5730
5794
|
}
|
|
5731
5795
|
},
|
|
5732
5796
|
// dispatch is stable (useReducer); refs are mutable — no reactive deps.
|
|
5733
|
-
|
|
5797
|
+
// termCols is stable (useState + resize effect).
|
|
5798
|
+
[termCols]
|
|
5734
5799
|
);
|
|
5735
5800
|
useEffect(() => {
|
|
5736
5801
|
if (!subscribeMouse) return;
|
|
@@ -5899,12 +5964,12 @@ function App({
|
|
|
5899
5964
|
}, [agent.ctx.meta]);
|
|
5900
5965
|
const prevAnyOverlayOpen = useRef(false);
|
|
5901
5966
|
const prevEntriesCount = useRef(0);
|
|
5902
|
-
const eraseLiveRegion = () => {
|
|
5967
|
+
const eraseLiveRegion = useCallback(() => {
|
|
5903
5968
|
try {
|
|
5904
5969
|
process.stdout.write("\x1B[J");
|
|
5905
5970
|
} catch {
|
|
5906
5971
|
}
|
|
5907
|
-
};
|
|
5972
|
+
}, []);
|
|
5908
5973
|
useEffect(() => {
|
|
5909
5974
|
const anyOpenNow = state.picker.open || state.slashPicker.open || state.modelPicker.open || state.autonomyPicker.open || state.settingsPicker.open || state.confirmQueue.length > 0;
|
|
5910
5975
|
const overlayClosed = prevAnyOverlayOpen.current && !anyOpenNow;
|
|
@@ -5921,7 +5986,8 @@ function App({
|
|
|
5921
5986
|
state.autonomyPicker.open,
|
|
5922
5987
|
state.settingsPicker.open,
|
|
5923
5988
|
state.confirmQueue.length,
|
|
5924
|
-
state.entries.length
|
|
5989
|
+
state.entries.length,
|
|
5990
|
+
eraseLiveRegion
|
|
5925
5991
|
]);
|
|
5926
5992
|
useEffect(() => {
|
|
5927
5993
|
const handleResize = () => eraseLiveRegion();
|
|
@@ -5929,7 +5995,7 @@ function App({
|
|
|
5929
5995
|
return () => {
|
|
5930
5996
|
process.stdout.off("resize", handleResize);
|
|
5931
5997
|
};
|
|
5932
|
-
}, []);
|
|
5998
|
+
}, [eraseLiveRegion]);
|
|
5933
5999
|
useEffect(() => {
|
|
5934
6000
|
const detected = detectAtToken(state.buffer, state.cursor);
|
|
5935
6001
|
if (!detected) {
|
|
@@ -7903,6 +7969,22 @@ function App({
|
|
|
7903
7969
|
}
|
|
7904
7970
|
});
|
|
7905
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
|
+
}
|
|
7906
7988
|
} catch (err) {
|
|
7907
7989
|
dispatch({
|
|
7908
7990
|
type: "addEntry",
|
|
@@ -8546,8 +8628,48 @@ async function runTui(opts) {
|
|
|
8546
8628
|
let inkStdin = stdin;
|
|
8547
8629
|
let detachMouse = null;
|
|
8548
8630
|
if (useMouse) {
|
|
8549
|
-
|
|
8550
|
-
|
|
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;
|
|
8551
8673
|
p.isTTY = true;
|
|
8552
8674
|
p.setRawMode = (mode) => {
|
|
8553
8675
|
try {
|
|
@@ -8578,10 +8700,13 @@ async function runTui(opts) {
|
|
|
8578
8700
|
}
|
|
8579
8701
|
}
|
|
8580
8702
|
const rest = stripSgrMouse(chunk);
|
|
8581
|
-
|
|
8703
|
+
keyboardStream.doPush(rest);
|
|
8582
8704
|
};
|
|
8583
8705
|
stdin.on("data", onData);
|
|
8584
|
-
detachMouse = () =>
|
|
8706
|
+
detachMouse = () => {
|
|
8707
|
+
stdin.off("data", onData);
|
|
8708
|
+
keyboardStream.doEnd();
|
|
8709
|
+
};
|
|
8585
8710
|
inkStdin = p;
|
|
8586
8711
|
}
|
|
8587
8712
|
const subscribeMouse = useMouse ? (fn) => {
|
|
@@ -8700,6 +8825,7 @@ async function runTui(opts) {
|
|
|
8700
8825
|
projectRoot: opts.projectRoot,
|
|
8701
8826
|
getSettings: opts.getSettings,
|
|
8702
8827
|
saveSettings: opts.saveSettings,
|
|
8828
|
+
predictNext: opts.predictNext,
|
|
8703
8829
|
mouse: useMouse,
|
|
8704
8830
|
subscribeMouse,
|
|
8705
8831
|
// Managed viewport (in-app scroll + collapsibility) follows
|