@wrongstack/tui 0.10.3 → 0.31.1
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 +230 -79
- 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';
|
|
@@ -554,11 +554,12 @@ function FleetMonitor({
|
|
|
554
554
|
events.push({ at: e.startedAt, icon: "\u25CF", color: "cyan", text: `${e.name} spawned` });
|
|
555
555
|
if (e.status !== "running" && e.status !== "idle") {
|
|
556
556
|
const s2 = STATUS[e.status];
|
|
557
|
+
const reason = e.failureReason ? ` [${e.failureReason}]` : "";
|
|
557
558
|
events.push({
|
|
558
559
|
at: e.lastEventAt,
|
|
559
560
|
icon: s2.icon,
|
|
560
561
|
color: s2.color,
|
|
561
|
-
text: `${e.name} ${e.status} (${e.toolCalls}t)`
|
|
562
|
+
text: `${e.name} ${e.status} (${e.toolCalls}t)${reason}`
|
|
562
563
|
});
|
|
563
564
|
}
|
|
564
565
|
if (e.budgetWarning) {
|
|
@@ -873,6 +874,10 @@ function AgentsMonitor({
|
|
|
873
874
|
e.budgetWarning.limit,
|
|
874
875
|
" \u2014 extending"
|
|
875
876
|
] }) }) : null,
|
|
877
|
+
e.failureReason && e.status !== "success" ? /* @__PURE__ */ jsx(Box, { paddingLeft: 2, children: /* @__PURE__ */ jsxs(Text, { color: "red", children: [
|
|
878
|
+
"\u2717 ",
|
|
879
|
+
e.failureReason
|
|
880
|
+
] }) }) : null,
|
|
876
881
|
e.ctxPct !== void 0 ? /* @__PURE__ */ jsxs(Box, { paddingLeft: 2, children: [
|
|
877
882
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "ctx " }),
|
|
878
883
|
/* @__PURE__ */ jsx(ContextBar, { pct: e.ctxPct, tokens: e.ctxTokens, maxTokens: e.ctxMaxTokens })
|
|
@@ -882,21 +887,58 @@ function AgentsMonitor({
|
|
|
882
887
|
] });
|
|
883
888
|
}
|
|
884
889
|
var AUTONOMY_OPTIONS = [
|
|
885
|
-
{
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
+
{
|
|
891
|
+
mode: "off",
|
|
892
|
+
label: "OFF",
|
|
893
|
+
description: "Agent stops after each turn (normal interactive mode)",
|
|
894
|
+
color: "green"
|
|
895
|
+
},
|
|
896
|
+
{
|
|
897
|
+
mode: "suggest",
|
|
898
|
+
label: "SUGGEST",
|
|
899
|
+
description: "Shows next-step suggestions after each turn",
|
|
900
|
+
color: "cyan"
|
|
901
|
+
},
|
|
902
|
+
{
|
|
903
|
+
mode: "auto",
|
|
904
|
+
label: "AUTO",
|
|
905
|
+
description: "Self-driving \u2014 agent picks next step and continues",
|
|
906
|
+
color: "yellow"
|
|
907
|
+
},
|
|
908
|
+
{
|
|
909
|
+
mode: "eternal",
|
|
910
|
+
label: "ETERNAL",
|
|
911
|
+
description: "Goal-driven loop \u2014 requires /goal set first",
|
|
912
|
+
color: "red"
|
|
913
|
+
},
|
|
914
|
+
{
|
|
915
|
+
mode: "eternal-parallel",
|
|
916
|
+
label: "PARALLEL",
|
|
917
|
+
description: "Fan-out 4\u20138 subagents per tick \u2014 requires /goal",
|
|
918
|
+
color: "magenta"
|
|
919
|
+
}
|
|
890
920
|
];
|
|
891
|
-
function AutonomyPicker({
|
|
921
|
+
function AutonomyPicker({
|
|
922
|
+
options,
|
|
923
|
+
selected,
|
|
924
|
+
hint
|
|
925
|
+
}) {
|
|
892
926
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
|
|
893
927
|
/* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "\u2501\u2501 Autonomy Mode \u2501\u2501" }),
|
|
894
928
|
/* @__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
|
-
|
|
929
|
+
options.map((opt, i) => /* @__PURE__ */ jsxs(
|
|
930
|
+
Text,
|
|
931
|
+
{
|
|
932
|
+
color: i === selected ? opt.color : void 0,
|
|
933
|
+
inverse: i === selected,
|
|
934
|
+
children: [
|
|
935
|
+
i === selected ? "\u203A " : " ",
|
|
936
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: opt.label.padEnd(12) }),
|
|
937
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: opt.description })
|
|
938
|
+
]
|
|
939
|
+
},
|
|
940
|
+
opt.mode
|
|
941
|
+
)),
|
|
900
942
|
hint ? /* @__PURE__ */ jsx(Text, { color: "yellow", children: hint }) : null
|
|
901
943
|
] });
|
|
902
944
|
}
|
|
@@ -934,7 +976,8 @@ function CheckpointTimeline({
|
|
|
934
976
|
new Date(cp.ts).toLocaleTimeString()
|
|
935
977
|
] }),
|
|
936
978
|
cp.fileCount > 0 && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
937
|
-
"
|
|
979
|
+
" ",
|
|
980
|
+
"\xB7 ",
|
|
938
981
|
cp.fileCount,
|
|
939
982
|
" file",
|
|
940
983
|
cp.fileCount !== 1 ? "s" : ""
|
|
@@ -1055,7 +1098,11 @@ function FilePicker({ query, matches, selected }) {
|
|
|
1055
1098
|
function highlight(path3, _query) {
|
|
1056
1099
|
return path3;
|
|
1057
1100
|
}
|
|
1058
|
-
function FleetPanel({
|
|
1101
|
+
function FleetPanel({
|
|
1102
|
+
entries,
|
|
1103
|
+
totalCost,
|
|
1104
|
+
collabSession
|
|
1105
|
+
}) {
|
|
1059
1106
|
const { stdout } = useStdout();
|
|
1060
1107
|
const [termWidth, setTermWidth] = useState(stdout?.columns ?? 90);
|
|
1061
1108
|
useEffect(() => {
|
|
@@ -1077,8 +1124,8 @@ function FleetPanel({ entries, totalCost, collabSession }) {
|
|
|
1077
1124
|
const costLabel = totalCost > 0 ? ` \xB7 ${totalCost.toFixed(3)}` : "";
|
|
1078
1125
|
const collabLabel = hasCollab && collabSession.sessionId ? ` \xB7 collab(${collabSession.bugCount}b/${collabSession.planCount}p/${collabSession.evalCount}e)` : "";
|
|
1079
1126
|
const summaryLine = runningCount > 0 ? `${runningCount} running${costLabel}${collabLabel}` : `idle${costLabel}${collabLabel}`;
|
|
1080
|
-
const shown = running.slice(0,
|
|
1081
|
-
const overflow = running.length >
|
|
1127
|
+
const shown = running.slice(0, 5);
|
|
1128
|
+
const overflow = running.length > 5 ? running.length - 5 : 0;
|
|
1082
1129
|
const leaderTool = hasCollab ? "waiting for agents" : leader?.currentTool?.name ?? (leader?.status === "running" ? "running" : "\u2014");
|
|
1083
1130
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
|
|
1084
1131
|
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
@@ -1102,13 +1149,17 @@ function FleetPanel({ entries, totalCost, collabSession }) {
|
|
|
1102
1149
|
/* @__PURE__ */ jsx(Text, { color: "cyan", children: tool })
|
|
1103
1150
|
] }, entry.id);
|
|
1104
1151
|
}),
|
|
1105
|
-
overflow > 0 ? /* @__PURE__ */ jsxs(
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1152
|
+
overflow > 0 ? /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
1153
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1154
|
+
"+",
|
|
1155
|
+
overflow,
|
|
1156
|
+
":"
|
|
1157
|
+
] }),
|
|
1158
|
+
running.slice(5, 7).map((e) => /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1159
|
+
e.name?.split(" ")[0]?.slice(0, nameMaxLen - 2) ?? "agent",
|
|
1160
|
+
","
|
|
1161
|
+
] }, e.id)),
|
|
1162
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2026" })
|
|
1112
1163
|
] }) : null
|
|
1113
1164
|
] });
|
|
1114
1165
|
}
|
|
@@ -1580,7 +1631,8 @@ function tokenizePython(line, carry) {
|
|
|
1580
1631
|
}
|
|
1581
1632
|
function tokenizeDiff(line) {
|
|
1582
1633
|
if (line.startsWith("@@")) return [{ text: line, color: C.diffMeta }];
|
|
1583
|
-
if (line.startsWith("+++") || line.startsWith("---"))
|
|
1634
|
+
if (line.startsWith("+++") || line.startsWith("---"))
|
|
1635
|
+
return [{ text: line, color: C.diffMeta, dim: true }];
|
|
1584
1636
|
if (line.startsWith("+")) return [{ text: line, color: C.diffAdd }];
|
|
1585
1637
|
if (line.startsWith("-")) return [{ text: line, color: C.diffDel }];
|
|
1586
1638
|
return [{ text: line, dim: true }];
|
|
@@ -1996,7 +2048,7 @@ function MarkdownView({
|
|
|
1996
2048
|
if (quote && line.startsWith(">")) {
|
|
1997
2049
|
rows.push(
|
|
1998
2050
|
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
|
|
1999
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "
|
|
2051
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " " }),
|
|
2000
2052
|
/* @__PURE__ */ jsx(InlineLine, { tokens: parseInline(quote[1] ?? ""), dim: true })
|
|
2001
2053
|
] }, `q${key++}`)
|
|
2002
2054
|
);
|
|
@@ -2121,13 +2173,13 @@ function splitFencedBlocks(text) {
|
|
|
2121
2173
|
function CodeBlock({
|
|
2122
2174
|
code,
|
|
2123
2175
|
lang,
|
|
2124
|
-
|
|
2176
|
+
contentWidth
|
|
2125
2177
|
}) {
|
|
2126
2178
|
let lines = code.replace(/\n+$/, "").split("\n");
|
|
2127
2179
|
const hidden = Math.max(0, lines.length - MAX_CODE_LINES);
|
|
2128
2180
|
if (hidden > 0) lines = lines.slice(0, MAX_CODE_LINES);
|
|
2129
2181
|
const gutterW = String(lines.length).length;
|
|
2130
|
-
const maxW = Math.max(20, Math.min(
|
|
2182
|
+
const maxW = Math.max(20, Math.min(contentWidth - 6 - gutterW - 1, 120));
|
|
2131
2183
|
let carry = {};
|
|
2132
2184
|
const rows = lines.map((raw) => {
|
|
2133
2185
|
const display = raw.length > maxW ? `${raw.slice(0, maxW - 1)}\u2026` : raw;
|
|
@@ -2135,20 +2187,31 @@ function CodeBlock({
|
|
|
2135
2187
|
carry = r.carry;
|
|
2136
2188
|
return r.tokens;
|
|
2137
2189
|
});
|
|
2138
|
-
return /* @__PURE__ */ jsxs(
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2190
|
+
return /* @__PURE__ */ jsxs(
|
|
2191
|
+
Box,
|
|
2192
|
+
{
|
|
2193
|
+
flexDirection: "column",
|
|
2194
|
+
marginLeft: 2,
|
|
2195
|
+
marginY: 0,
|
|
2196
|
+
borderStyle: "round",
|
|
2197
|
+
borderColor: theme.borderDefault,
|
|
2198
|
+
paddingX: 1,
|
|
2199
|
+
children: [
|
|
2200
|
+
lang !== "plain" ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: lang }) : null,
|
|
2201
|
+
rows.map((tokens, i) => (
|
|
2202
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: code lines are positional
|
|
2203
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
2204
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: `${String(i + 1).padStart(gutterW, " ")} ` }),
|
|
2205
|
+
tokens.length === 0 ? " " : tokens.map((t, j) => (
|
|
2206
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: token order is stable per line
|
|
2207
|
+
/* @__PURE__ */ jsx(Text, { color: t.color, dimColor: t.dim, bold: t.bold, children: t.text }, j)
|
|
2208
|
+
))
|
|
2209
|
+
] }, i)
|
|
2210
|
+
)),
|
|
2211
|
+
hidden > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, italic: true, children: `\u2026 +${hidden} more line${hidden === 1 ? "" : "s"}` }) : null
|
|
2212
|
+
]
|
|
2213
|
+
}
|
|
2214
|
+
);
|
|
2152
2215
|
}
|
|
2153
2216
|
function AssistantBody({
|
|
2154
2217
|
text,
|
|
@@ -2160,7 +2223,7 @@ function AssistantBody({
|
|
|
2160
2223
|
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: segments.map(
|
|
2161
2224
|
(seg, i) => seg.type === "code" ? (
|
|
2162
2225
|
// biome-ignore lint/suspicious/noArrayIndexKey: segment order is stable
|
|
2163
|
-
/* @__PURE__ */ jsx(CodeBlock, { code: seg.text, lang: seg.lang ?? "plain",
|
|
2226
|
+
/* @__PURE__ */ jsx(CodeBlock, { code: seg.text, lang: seg.lang ?? "plain", contentWidth: inner }, i)
|
|
2164
2227
|
) : (
|
|
2165
2228
|
// biome-ignore lint/suspicious/noArrayIndexKey: segment order is stable
|
|
2166
2229
|
/* @__PURE__ */ jsx(MarkdownView, { text: seg.text, termWidth: inner }, i)
|
|
@@ -2289,14 +2352,7 @@ var Entry = React4.memo(function Entry2({
|
|
|
2289
2352
|
paddingLeft: 1,
|
|
2290
2353
|
children: [
|
|
2291
2354
|
/* @__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
|
-
)
|
|
2355
|
+
/* @__PURE__ */ jsx(AssistantBody, { text: entry.text, termWidth, contentWidth })
|
|
2300
2356
|
]
|
|
2301
2357
|
}
|
|
2302
2358
|
);
|
|
@@ -3411,8 +3467,10 @@ function fmtRecentTool(tool) {
|
|
|
3411
3467
|
const name = tool.name.length > 18 ? `${tool.name.slice(0, 17)}...` : tool.name;
|
|
3412
3468
|
const parts = [status, name];
|
|
3413
3469
|
if (typeof tool.durationMs === "number") parts.push(fmtElapsed2(tool.durationMs));
|
|
3414
|
-
if (typeof tool.outputBytes === "number" && tool.outputBytes > 0)
|
|
3415
|
-
|
|
3470
|
+
if (typeof tool.outputBytes === "number" && tool.outputBytes > 0)
|
|
3471
|
+
parts.push(fmtBytes2(tool.outputBytes));
|
|
3472
|
+
if (typeof tool.outputLines === "number" && tool.outputLines > 0)
|
|
3473
|
+
parts.push(`${tool.outputLines}L`);
|
|
3416
3474
|
return parts.join(" ");
|
|
3417
3475
|
}
|
|
3418
3476
|
function fmtRecentMessage(message) {
|
|
@@ -3545,7 +3603,9 @@ function PhaseMonitor({
|
|
|
3545
3603
|
if (key.escape) onClose();
|
|
3546
3604
|
});
|
|
3547
3605
|
const phaseList = Object.values(phases);
|
|
3548
|
-
const running = phaseList.filter(
|
|
3606
|
+
const running = phaseList.filter(
|
|
3607
|
+
(p) => runningPhaseIds.includes(Object.keys(phases).find((k) => phases[k] === p) ?? "")
|
|
3608
|
+
);
|
|
3549
3609
|
const done = phaseList.filter((p) => p.status === "completed" || p.status === "skipped");
|
|
3550
3610
|
const failed = phaseList.filter((p) => p.status === "failed");
|
|
3551
3611
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [
|
|
@@ -3756,7 +3816,7 @@ function ScrollableHistory({
|
|
|
3756
3816
|
lastReported.current = height;
|
|
3757
3817
|
onMeasure(height);
|
|
3758
3818
|
}
|
|
3759
|
-
});
|
|
3819
|
+
}, [onMeasure]);
|
|
3760
3820
|
const vp = Math.max(1, viewportRows);
|
|
3761
3821
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
|
|
3762
3822
|
/* @__PURE__ */ jsx(
|
|
@@ -3992,7 +4052,9 @@ function WorktreePanel({
|
|
|
3992
4052
|
}) {
|
|
3993
4053
|
const list = Object.values(worktrees);
|
|
3994
4054
|
if (list.length === 0) return null;
|
|
3995
|
-
const active = list.filter(
|
|
4055
|
+
const active = list.filter(
|
|
4056
|
+
(w) => w.status === "active" || w.status === "committing" || w.status === "merging"
|
|
4057
|
+
).length;
|
|
3996
4058
|
const merged = list.filter((w) => w.status === "merged").length;
|
|
3997
4059
|
const failed = list.filter((w) => w.status === "failed" || w.status === "needs-review").length;
|
|
3998
4060
|
return /* @__PURE__ */ jsxs(
|
|
@@ -4036,7 +4098,8 @@ function WorktreePanel({
|
|
|
4036
4098
|
/* @__PURE__ */ jsx(Text, { children: w.branch.replace(/^wstack\/ap\//, "").slice(0, 18).padEnd(18) }),
|
|
4037
4099
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: w.ownerLabel.slice(0, 12) }),
|
|
4038
4100
|
conflict ? /* @__PURE__ */ jsx(Text, { color: "magenta", children: " CONFLICT" }) : /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
4039
|
-
"
|
|
4101
|
+
" ",
|
|
4102
|
+
"+",
|
|
4040
4103
|
w.insertions,
|
|
4041
4104
|
"/-",
|
|
4042
4105
|
w.deletions,
|
|
@@ -4200,13 +4263,18 @@ function createKillSlashCommand() {
|
|
|
4200
4263
|
if (sub === "all") {
|
|
4201
4264
|
const pids = getProcessRegistry().killAll();
|
|
4202
4265
|
if (pids.length === 0) return { message: "No processes to kill." };
|
|
4203
|
-
return {
|
|
4266
|
+
return {
|
|
4267
|
+
message: `Killed ${pids.length} process${pids.length === 1 ? "" : "es"}: ${pids.join(", ")}`
|
|
4268
|
+
};
|
|
4204
4269
|
}
|
|
4205
4270
|
if (sub === "force") {
|
|
4206
4271
|
getProcessRegistry().forceBreakerOpen();
|
|
4207
4272
|
const pids = getProcessRegistry().killAll({ force: true });
|
|
4208
|
-
if (pids.length === 0)
|
|
4209
|
-
|
|
4273
|
+
if (pids.length === 0)
|
|
4274
|
+
return { message: "Circuit breaker forced open. No processes to kill." };
|
|
4275
|
+
return {
|
|
4276
|
+
message: `Force-killed ${pids.length} process${pids.length === 1 ? "" : "es"}: ${pids.join(", ")}`
|
|
4277
|
+
};
|
|
4210
4278
|
}
|
|
4211
4279
|
if (sub === "reset") {
|
|
4212
4280
|
getProcessRegistry().forceBreakerReset();
|
|
@@ -4735,10 +4803,24 @@ function reducer(state, action) {
|
|
|
4735
4803
|
return { ...state, fleet: seeded, fleetCost: action.cost };
|
|
4736
4804
|
}
|
|
4737
4805
|
case "fleetSpawn": {
|
|
4738
|
-
|
|
4806
|
+
const existing = state.fleet[action.id];
|
|
4807
|
+
const incomingName = action.name ?? action.id.slice(0, 8);
|
|
4808
|
+
const isPlaceholderName = (name) => name === "adhoc" || name === "subagent" || name === "generic" || name.startsWith("slot-") || name === action.id.slice(0, 8);
|
|
4809
|
+
if (existing) {
|
|
4810
|
+
if (isPlaceholderName(existing.name) && !isPlaceholderName(incomingName) && incomingName !== existing.name) {
|
|
4811
|
+
return {
|
|
4812
|
+
...state,
|
|
4813
|
+
fleet: {
|
|
4814
|
+
...state.fleet,
|
|
4815
|
+
[action.id]: { ...existing, name: incomingName }
|
|
4816
|
+
}
|
|
4817
|
+
};
|
|
4818
|
+
}
|
|
4819
|
+
return state;
|
|
4820
|
+
}
|
|
4739
4821
|
const entry = {
|
|
4740
4822
|
id: action.id,
|
|
4741
|
-
name:
|
|
4823
|
+
name: incomingName,
|
|
4742
4824
|
provider: action.provider,
|
|
4743
4825
|
model: action.model,
|
|
4744
4826
|
status: "idle",
|
|
@@ -4876,7 +4958,8 @@ function reducer(state, action) {
|
|
|
4876
4958
|
currentTool: void 0,
|
|
4877
4959
|
budgetWarning: void 0,
|
|
4878
4960
|
// clear on done/restart
|
|
4879
|
-
lastEventAt: Date.now()
|
|
4961
|
+
lastEventAt: Date.now(),
|
|
4962
|
+
failureReason: action.failureReason
|
|
4880
4963
|
}
|
|
4881
4964
|
}
|
|
4882
4965
|
};
|
|
@@ -5375,6 +5458,7 @@ function App({
|
|
|
5375
5458
|
switchProviderAndModel,
|
|
5376
5459
|
getSettings,
|
|
5377
5460
|
saveSettings,
|
|
5461
|
+
predictNext,
|
|
5378
5462
|
switchAutonomy,
|
|
5379
5463
|
effectiveMaxContext,
|
|
5380
5464
|
onExit,
|
|
@@ -5400,8 +5484,12 @@ function App({
|
|
|
5400
5484
|
const [hiddenItems, setHiddenItems] = useState(statuslineHiddenItems);
|
|
5401
5485
|
const { stdout } = useStdout();
|
|
5402
5486
|
const [termRows, setTermRows] = useState(stdout?.rows ?? 24);
|
|
5487
|
+
const [termCols, setTermCols] = useState(stdout?.columns ?? 80);
|
|
5403
5488
|
useEffect(() => {
|
|
5404
|
-
const onResize = () =>
|
|
5489
|
+
const onResize = () => {
|
|
5490
|
+
setTermRows(process.stdout.rows ?? 24);
|
|
5491
|
+
setTermCols(process.stdout.columns ?? 80);
|
|
5492
|
+
};
|
|
5405
5493
|
process.stdout.on("resize", onResize);
|
|
5406
5494
|
return () => {
|
|
5407
5495
|
process.stdout.off("resize", onResize);
|
|
@@ -5551,7 +5639,7 @@ function App({
|
|
|
5551
5639
|
if (vp !== s2.viewportRows) {
|
|
5552
5640
|
dispatch({ type: "setViewportRows", rows: vp });
|
|
5553
5641
|
}
|
|
5554
|
-
});
|
|
5642
|
+
}, [managedLive, termRows]);
|
|
5555
5643
|
const handleKeyRef = useRef(null);
|
|
5556
5644
|
const pendingClickConfirmRef = useRef(null);
|
|
5557
5645
|
const openModelPickerRef = useRef(null);
|
|
@@ -5599,7 +5687,7 @@ function App({
|
|
|
5599
5687
|
}
|
|
5600
5688
|
return;
|
|
5601
5689
|
}
|
|
5602
|
-
const cols =
|
|
5690
|
+
const cols = termCols || 80;
|
|
5603
5691
|
const onScrollbar = cols > 0 && ev.x >= cols - 2 && ev.y >= 1 && ev.y <= rows;
|
|
5604
5692
|
if (onScrollbar && s2.totalLines > rows) {
|
|
5605
5693
|
scrollbarDragRef.current = true;
|
|
@@ -5687,7 +5775,7 @@ function App({
|
|
|
5687
5775
|
if (!picker || picker.count === 0) {
|
|
5688
5776
|
const inputDisabled = s2.status === "aborting" && !s2.steeringPending;
|
|
5689
5777
|
if (!inputDisabled) {
|
|
5690
|
-
const cols =
|
|
5778
|
+
const cols = termCols || 80;
|
|
5691
5779
|
const inputTop = s2.viewportRows + affordance + liveStripRowsRef.current + 1;
|
|
5692
5780
|
const inputRows = layoutInputRows(INPUT_PROMPT, s2.buffer, s2.cursor, cols).length;
|
|
5693
5781
|
const rowIdx = ev.y - inputTop;
|
|
@@ -5730,7 +5818,8 @@ function App({
|
|
|
5730
5818
|
}
|
|
5731
5819
|
},
|
|
5732
5820
|
// dispatch is stable (useReducer); refs are mutable — no reactive deps.
|
|
5733
|
-
|
|
5821
|
+
// termCols is stable (useState + resize effect).
|
|
5822
|
+
[termCols]
|
|
5734
5823
|
);
|
|
5735
5824
|
useEffect(() => {
|
|
5736
5825
|
if (!subscribeMouse) return;
|
|
@@ -5899,12 +5988,12 @@ function App({
|
|
|
5899
5988
|
}, [agent.ctx.meta]);
|
|
5900
5989
|
const prevAnyOverlayOpen = useRef(false);
|
|
5901
5990
|
const prevEntriesCount = useRef(0);
|
|
5902
|
-
const eraseLiveRegion = () => {
|
|
5991
|
+
const eraseLiveRegion = useCallback(() => {
|
|
5903
5992
|
try {
|
|
5904
5993
|
process.stdout.write("\x1B[J");
|
|
5905
5994
|
} catch {
|
|
5906
5995
|
}
|
|
5907
|
-
};
|
|
5996
|
+
}, []);
|
|
5908
5997
|
useEffect(() => {
|
|
5909
5998
|
const anyOpenNow = state.picker.open || state.slashPicker.open || state.modelPicker.open || state.autonomyPicker.open || state.settingsPicker.open || state.confirmQueue.length > 0;
|
|
5910
5999
|
const overlayClosed = prevAnyOverlayOpen.current && !anyOpenNow;
|
|
@@ -5921,7 +6010,8 @@ function App({
|
|
|
5921
6010
|
state.autonomyPicker.open,
|
|
5922
6011
|
state.settingsPicker.open,
|
|
5923
6012
|
state.confirmQueue.length,
|
|
5924
|
-
state.entries.length
|
|
6013
|
+
state.entries.length,
|
|
6014
|
+
eraseLiveRegion
|
|
5925
6015
|
]);
|
|
5926
6016
|
useEffect(() => {
|
|
5927
6017
|
const handleResize = () => eraseLiveRegion();
|
|
@@ -5929,7 +6019,7 @@ function App({
|
|
|
5929
6019
|
return () => {
|
|
5930
6020
|
process.stdout.off("resize", handleResize);
|
|
5931
6021
|
};
|
|
5932
|
-
}, []);
|
|
6022
|
+
}, [eraseLiveRegion]);
|
|
5933
6023
|
useEffect(() => {
|
|
5934
6024
|
const detected = detectAtToken(state.buffer, state.cursor);
|
|
5935
6025
|
if (!detected) {
|
|
@@ -6534,15 +6624,16 @@ function App({
|
|
|
6534
6624
|
});
|
|
6535
6625
|
const offCompleted = events.on("subagent.task_completed", (e) => {
|
|
6536
6626
|
const lbl = labelFor(e.subagentId);
|
|
6627
|
+
const errKind = e.error?.kind;
|
|
6537
6628
|
dispatch({
|
|
6538
6629
|
type: "fleetDone",
|
|
6539
6630
|
id: e.subagentId,
|
|
6540
6631
|
status: e.status,
|
|
6541
6632
|
iterations: e.iterations,
|
|
6542
|
-
toolCalls: e.toolCalls
|
|
6633
|
+
toolCalls: e.toolCalls,
|
|
6634
|
+
failureReason: errKind
|
|
6543
6635
|
});
|
|
6544
6636
|
const icon = e.status === "success" ? "\u2713" : e.status === "timeout" ? "\u23F1" : e.status === "stopped" ? "\u2298" : "\u2717";
|
|
6545
|
-
const errKind = e.error?.kind;
|
|
6546
6637
|
const errMsg = e.error?.message;
|
|
6547
6638
|
const errMsgTail = errMsg ? ` \u2014 ${errMsg.replace(/\s+/g, " ").slice(0, 100)}${errMsg.length > 100 ? "\u2026" : ""}` : "";
|
|
6548
6639
|
const errChip = errKind ? ` [${errKind}]` : "";
|
|
@@ -7903,6 +7994,22 @@ function App({
|
|
|
7903
7994
|
}
|
|
7904
7995
|
});
|
|
7905
7996
|
}
|
|
7997
|
+
if (result.status === "done" && predictNext) {
|
|
7998
|
+
try {
|
|
7999
|
+
const userRequest = blocks.filter((b) => b.type === "text").map((b) => b.text).join(" ").trim();
|
|
8000
|
+
const predictions = await predictNext({
|
|
8001
|
+
userRequest,
|
|
8002
|
+
assistantSummary: result.finalText ?? ""
|
|
8003
|
+
});
|
|
8004
|
+
if (predictions.length > 0) {
|
|
8005
|
+
const text = ["\u21B3 likely next:", ...predictions.map((p, i) => ` ${i + 1}. ${p}`)].join(
|
|
8006
|
+
"\n"
|
|
8007
|
+
);
|
|
8008
|
+
dispatch({ type: "addEntry", entry: { kind: "turn-summary", text } });
|
|
8009
|
+
}
|
|
8010
|
+
} catch {
|
|
8011
|
+
}
|
|
8012
|
+
}
|
|
7906
8013
|
} catch (err) {
|
|
7907
8014
|
dispatch({
|
|
7908
8015
|
type: "addEntry",
|
|
@@ -8546,8 +8653,48 @@ async function runTui(opts) {
|
|
|
8546
8653
|
let inkStdin = stdin;
|
|
8547
8654
|
let detachMouse = null;
|
|
8548
8655
|
if (useMouse) {
|
|
8549
|
-
|
|
8550
|
-
|
|
8656
|
+
class KeyboardReadable extends Readable {
|
|
8657
|
+
pendingChunks = [];
|
|
8658
|
+
// eslint-disable-next-line no-useless-constructor
|
|
8659
|
+
constructor() {
|
|
8660
|
+
super({ encoding: "utf8", highWaterMark: 64 * 1024 });
|
|
8661
|
+
}
|
|
8662
|
+
_read(_size) {
|
|
8663
|
+
this.flushPending();
|
|
8664
|
+
}
|
|
8665
|
+
flushPending() {
|
|
8666
|
+
while (this.pendingChunks.length > 0) {
|
|
8667
|
+
const chunk = this.pendingChunks[0];
|
|
8668
|
+
const ok = this.push(chunk);
|
|
8669
|
+
this.pendingChunks.shift();
|
|
8670
|
+
if (!ok) {
|
|
8671
|
+
break;
|
|
8672
|
+
}
|
|
8673
|
+
}
|
|
8674
|
+
}
|
|
8675
|
+
/** Called by the stdin data handler when keyboard bytes are available. */
|
|
8676
|
+
doPush(chunk) {
|
|
8677
|
+
if (chunk.length === 0) return;
|
|
8678
|
+
const ok = this.push(chunk);
|
|
8679
|
+
if (ok) {
|
|
8680
|
+
if (this.pendingChunks.length > 0) {
|
|
8681
|
+
this.flushPending();
|
|
8682
|
+
}
|
|
8683
|
+
} else {
|
|
8684
|
+
if (this.pendingChunks.length >= 100) {
|
|
8685
|
+
this.pendingChunks.shift();
|
|
8686
|
+
}
|
|
8687
|
+
this.pendingChunks.push(chunk);
|
|
8688
|
+
}
|
|
8689
|
+
}
|
|
8690
|
+
/** Called on shutdown so the stream closes cleanly. */
|
|
8691
|
+
doEnd() {
|
|
8692
|
+
this.pendingChunks = [];
|
|
8693
|
+
this.push(null);
|
|
8694
|
+
}
|
|
8695
|
+
}
|
|
8696
|
+
const keyboardStream = new KeyboardReadable();
|
|
8697
|
+
const p = keyboardStream;
|
|
8551
8698
|
p.isTTY = true;
|
|
8552
8699
|
p.setRawMode = (mode) => {
|
|
8553
8700
|
try {
|
|
@@ -8578,10 +8725,13 @@ async function runTui(opts) {
|
|
|
8578
8725
|
}
|
|
8579
8726
|
}
|
|
8580
8727
|
const rest = stripSgrMouse(chunk);
|
|
8581
|
-
|
|
8728
|
+
keyboardStream.doPush(rest);
|
|
8582
8729
|
};
|
|
8583
8730
|
stdin.on("data", onData);
|
|
8584
|
-
detachMouse = () =>
|
|
8731
|
+
detachMouse = () => {
|
|
8732
|
+
stdin.off("data", onData);
|
|
8733
|
+
keyboardStream.doEnd();
|
|
8734
|
+
};
|
|
8585
8735
|
inkStdin = p;
|
|
8586
8736
|
}
|
|
8587
8737
|
const subscribeMouse = useMouse ? (fn) => {
|
|
@@ -8700,6 +8850,7 @@ async function runTui(opts) {
|
|
|
8700
8850
|
projectRoot: opts.projectRoot,
|
|
8701
8851
|
getSettings: opts.getSettings,
|
|
8702
8852
|
saveSettings: opts.saveSettings,
|
|
8853
|
+
predictNext: opts.predictNext,
|
|
8703
8854
|
mouse: useMouse,
|
|
8704
8855
|
subscribeMouse,
|
|
8705
8856
|
// Managed viewport (in-app scroll + collapsibility) follows
|