@wrongstack/tui 0.3.3 → 0.3.7
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.js +245 -229
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { render, useApp, Box, useStdout, Static, Text, useInput, useStdin } from 'ink';
|
|
2
|
-
import
|
|
2
|
+
import React4, { useState, useReducer, useRef, useEffect, useMemo } from 'react';
|
|
3
3
|
import * as fs2 from 'fs/promises';
|
|
4
4
|
import * as path2 from 'path';
|
|
5
5
|
import { InputBuilder, formatTodosList } from '@wrongstack/core';
|
|
@@ -39,14 +39,19 @@ function ConfirmPrompt({
|
|
|
39
39
|
suggestedPattern,
|
|
40
40
|
onDecision
|
|
41
41
|
}) {
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
React4.useEffect(() => {
|
|
43
|
+
process.stdout.write("\x07");
|
|
44
|
+
}, []);
|
|
45
|
+
useInput((input2, key) => {
|
|
46
|
+
if (!input2 || input2 === "\r" || input2 === "\n") return;
|
|
47
|
+
const ch = input2.toLowerCase();
|
|
48
|
+
if (ch === "y") {
|
|
44
49
|
onDecision("yes");
|
|
45
|
-
} else if (
|
|
50
|
+
} else if (ch === "n") {
|
|
46
51
|
onDecision("no");
|
|
47
|
-
} else if (
|
|
52
|
+
} else if (ch === "a") {
|
|
48
53
|
onDecision("always");
|
|
49
|
-
} else if (
|
|
54
|
+
} else if (ch === "d") {
|
|
50
55
|
onDecision("deny");
|
|
51
56
|
}
|
|
52
57
|
});
|
|
@@ -58,34 +63,32 @@ function ConfirmPrompt({
|
|
|
58
63
|
Box,
|
|
59
64
|
{
|
|
60
65
|
flexDirection: "column",
|
|
61
|
-
borderStyle: "
|
|
62
|
-
|
|
63
|
-
borderLeft: false,
|
|
64
|
-
borderRight: false,
|
|
65
|
-
borderBottom: false,
|
|
66
|
+
borderStyle: "round",
|
|
67
|
+
borderColor: "yellow",
|
|
66
68
|
paddingX: 1,
|
|
69
|
+
marginY: 1,
|
|
67
70
|
children: [
|
|
68
71
|
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
|
|
69
|
-
/* @__PURE__ */ jsx(Text, { bold: true, color: "yellow", children: "\u26A0
|
|
72
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "yellow", children: "\u26A0 APPROVAL REQUIRED" }),
|
|
70
73
|
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
71
|
-
/* @__PURE__ */ jsx(Text, { bold: true, children: toolName })
|
|
74
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "white", children: toolName })
|
|
72
75
|
] }),
|
|
73
76
|
inputSummary ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: inputSummary }) : null,
|
|
74
77
|
showDiff && diff ? /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginY: 1, children: renderDiff(diff) }) : null,
|
|
75
78
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }),
|
|
76
79
|
/* @__PURE__ */ jsx(Box, { flexDirection: "row", children: /* @__PURE__ */ jsxs(Text, { children: [
|
|
77
|
-
/* @__PURE__ */ jsx(Text, { bold: true, color: "green", children: "[
|
|
78
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "
|
|
79
|
-
/* @__PURE__ */ jsx(Text, { bold: true, color: "red", children: "[
|
|
80
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "
|
|
81
|
-
/* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "[
|
|
80
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "green", children: "[y]" }),
|
|
81
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "es " }),
|
|
82
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "red", children: "[n]" }),
|
|
83
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "o " }),
|
|
84
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "[a]" }),
|
|
82
85
|
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
83
|
-
"
|
|
86
|
+
"lways (",
|
|
84
87
|
suggestedPattern,
|
|
85
88
|
") "
|
|
86
89
|
] }),
|
|
87
|
-
/* @__PURE__ */ jsx(Text, { bold: true, color: "red", children: "[
|
|
88
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "
|
|
90
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "red", children: "[d]" }),
|
|
91
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "eny" })
|
|
89
92
|
] }) })
|
|
90
93
|
]
|
|
91
94
|
}
|
|
@@ -256,81 +259,6 @@ function FleetPanel({ entries, totalCost, roster }) {
|
|
|
256
259
|
}
|
|
257
260
|
);
|
|
258
261
|
}
|
|
259
|
-
function fmtElapsed(ms) {
|
|
260
|
-
if (ms < 1e3) return `${ms}ms`;
|
|
261
|
-
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
|
|
262
|
-
const m = Math.floor(ms / 6e4);
|
|
263
|
-
const s = Math.floor(ms % 6e4 / 1e3);
|
|
264
|
-
return `${m}m${s.toString().padStart(2, "0")}s`;
|
|
265
|
-
}
|
|
266
|
-
function fmtBytes2(n) {
|
|
267
|
-
if (n < 1024) return `${n}B`;
|
|
268
|
-
return `${(n / 1024).toFixed(1)}KB`;
|
|
269
|
-
}
|
|
270
|
-
function fmtRecentTool2(tool) {
|
|
271
|
-
const status = tool.ok === false ? "fail" : "ok";
|
|
272
|
-
const name = tool.name.length > 18 ? `${tool.name.slice(0, 17)}...` : tool.name;
|
|
273
|
-
const parts = [status, name];
|
|
274
|
-
if (typeof tool.durationMs === "number") parts.push(fmtElapsed(tool.durationMs));
|
|
275
|
-
if (typeof tool.outputBytes === "number" && tool.outputBytes > 0) parts.push(fmtBytes2(tool.outputBytes));
|
|
276
|
-
if (typeof tool.outputLines === "number" && tool.outputLines > 0) parts.push(`${tool.outputLines}L`);
|
|
277
|
-
return parts.join(" ");
|
|
278
|
-
}
|
|
279
|
-
function fmtRecentMessage2(message) {
|
|
280
|
-
const text = message.text.replace(/\s+/g, " ");
|
|
281
|
-
return text.length > 48 ? `${text.slice(0, 47)}...` : text;
|
|
282
|
-
}
|
|
283
|
-
function LiveActivityStrip({
|
|
284
|
-
entries,
|
|
285
|
-
nowTick,
|
|
286
|
-
maxRows = 4
|
|
287
|
-
}) {
|
|
288
|
-
const running = Object.values(entries).filter((e) => e.status === "running").sort((a, b) => a.startedAt - b.startedAt).slice(0, maxRows);
|
|
289
|
-
if (running.length === 0) return null;
|
|
290
|
-
const now = Date.now();
|
|
291
|
-
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
|
|
292
|
-
running.map((e) => {
|
|
293
|
-
const toolElapsed = e.currentTool ? now - e.currentTool.startedAt : 0;
|
|
294
|
-
const taskElapsed = now - e.startedAt;
|
|
295
|
-
const toolSeg = e.currentTool ? `\u2192 ${e.currentTool.name} (${fmtElapsed(toolElapsed)})` : "idle between tools";
|
|
296
|
-
const recentTools = (e.recentTools ?? []).slice(-2).map(fmtRecentTool2).join(" | ");
|
|
297
|
-
const messageText = e.streamingText.trim() || (e.recentMessages ?? []).slice(-1).map(fmtRecentMessage2).join("");
|
|
298
|
-
return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
299
|
-
/* @__PURE__ */ jsx(Text, { color: "cyan", children: "\u25CF" }),
|
|
300
|
-
/* @__PURE__ */ jsx(Text, { children: e.name.slice(0, 14).padEnd(14) }),
|
|
301
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
|
|
302
|
-
/* @__PURE__ */ jsx(Text, { color: e.currentTool ? "green" : "yellow", children: toolSeg }),
|
|
303
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
|
|
304
|
-
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
305
|
-
e.iterations,
|
|
306
|
-
"it ",
|
|
307
|
-
e.toolCalls,
|
|
308
|
-
"tc \xB7 ",
|
|
309
|
-
fmtElapsed(taskElapsed)
|
|
310
|
-
] }),
|
|
311
|
-
recentTools ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
312
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "|" }),
|
|
313
|
-
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
314
|
-
"last: ",
|
|
315
|
-
recentTools
|
|
316
|
-
] })
|
|
317
|
-
] }) : null,
|
|
318
|
-
messageText ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
319
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "|" }),
|
|
320
|
-
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
321
|
-
"msg: ",
|
|
322
|
-
fmtRecentMessage2({ text: messageText})
|
|
323
|
-
] })
|
|
324
|
-
] }) : null
|
|
325
|
-
] }, e.id);
|
|
326
|
-
}),
|
|
327
|
-
Object.values(entries).filter((e) => e.status === "running").length > maxRows ? /* @__PURE__ */ jsx(Box, { paddingLeft: 2, children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
328
|
-
"\u2026+",
|
|
329
|
-
Object.values(entries).filter((e) => e.status === "running").length - maxRows,
|
|
330
|
-
" more"
|
|
331
|
-
] }) }) : null
|
|
332
|
-
] });
|
|
333
|
-
}
|
|
334
262
|
|
|
335
263
|
// src/markdown-table.ts
|
|
336
264
|
function renderMarkdownTables(text, maxWidth) {
|
|
@@ -641,7 +569,7 @@ function Entry({
|
|
|
641
569
|
parts.push(`${entry.outputLines} L`);
|
|
642
570
|
}
|
|
643
571
|
if (entry.outputBytes && entry.outputBytes > 0) {
|
|
644
|
-
parts.push(
|
|
572
|
+
parts.push(fmtBytes2(entry.outputBytes));
|
|
645
573
|
}
|
|
646
574
|
if (entry.outputTokens && entry.outputTokens > 0) {
|
|
647
575
|
parts.push(`\u2248${fmtTok(entry.outputTokens)} tok`);
|
|
@@ -686,41 +614,13 @@ function Entry({
|
|
|
686
614
|
case "turn-summary":
|
|
687
615
|
return /* @__PURE__ */ jsx(Text, { dimColor: true, children: entry.text });
|
|
688
616
|
case "confirm":
|
|
689
|
-
return /* @__PURE__ */ jsxs(
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
borderRight: false,
|
|
697
|
-
borderBottom: false,
|
|
698
|
-
paddingX: 1,
|
|
699
|
-
children: [
|
|
700
|
-
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
|
|
701
|
-
/* @__PURE__ */ jsx(Text, { bold: true, color: "yellow", children: "\u26A0 Confirm" }),
|
|
702
|
-
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
703
|
-
/* @__PURE__ */ jsx(Text, { bold: true, children: entry.toolName })
|
|
704
|
-
] }),
|
|
705
|
-
entry.input && typeof entry.input === "object" ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: Object.entries(entry.input).filter(([k]) => k !== "content" && k !== "new_string").map(([k, v]) => `${k}: ${String(v).slice(0, 80)}`).join(" ") }) : null,
|
|
706
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }),
|
|
707
|
-
/* @__PURE__ */ jsx(Box, { flexDirection: "row", children: /* @__PURE__ */ jsxs(Text, { children: [
|
|
708
|
-
/* @__PURE__ */ jsx(Text, { bold: true, color: "green", children: "[\u21B5]" }),
|
|
709
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " yes " }),
|
|
710
|
-
/* @__PURE__ */ jsx(Text, { bold: true, color: "red", children: "[Esc]" }),
|
|
711
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " no " }),
|
|
712
|
-
/* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "[Ctrl+A]" }),
|
|
713
|
-
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
714
|
-
" always (",
|
|
715
|
-
entry.suggestedPattern,
|
|
716
|
-
") "
|
|
717
|
-
] }),
|
|
718
|
-
/* @__PURE__ */ jsx(Text, { bold: true, color: "red", children: "[Ctrl+D]" }),
|
|
719
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " deny" })
|
|
720
|
-
] }) })
|
|
721
|
-
]
|
|
722
|
-
}
|
|
723
|
-
);
|
|
617
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, marginY: 1, children: [
|
|
618
|
+
/* @__PURE__ */ jsxs(Text, { bold: true, color: "yellow", children: [
|
|
619
|
+
"\u26A0 Confirm: ",
|
|
620
|
+
entry.toolName
|
|
621
|
+
] }),
|
|
622
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Waiting for y / n / a / d..." })
|
|
623
|
+
] });
|
|
724
624
|
case "banner":
|
|
725
625
|
return /* @__PURE__ */ jsx(Banner, { entry });
|
|
726
626
|
case "subagent": {
|
|
@@ -925,7 +825,7 @@ function formatToolOutput(toolName, output, ok, _outputBytes, outputLines) {
|
|
|
925
825
|
const bytes = numOf(o["bytes_written"]) ?? numOf(o["bytes"]);
|
|
926
826
|
const created = o["created"] === true;
|
|
927
827
|
const tag = created ? "created" : "updated";
|
|
928
|
-
if (bytes !== void 0) return [`${tag} \xB7 ${
|
|
828
|
+
if (bytes !== void 0) return [`${tag} \xB7 ${fmtBytes2(bytes)}`];
|
|
929
829
|
return [tag];
|
|
930
830
|
}
|
|
931
831
|
}
|
|
@@ -990,17 +890,17 @@ function formatToolOutput(toolName, output, ok, _outputBytes, outputLines) {
|
|
|
990
890
|
if (json && typeof json === "object") {
|
|
991
891
|
const o = json;
|
|
992
892
|
const bytes = numOf(o["bytes"]);
|
|
993
|
-
if (bytes !== void 0) return [`${
|
|
893
|
+
if (bytes !== void 0) return [`${fmtBytes2(bytes)} read`];
|
|
994
894
|
}
|
|
995
895
|
const range = scanNumberedRange(text);
|
|
996
896
|
if (range.count > 0 && range.first !== void 0 && range.last !== void 0) {
|
|
997
897
|
if (range.first === range.last) {
|
|
998
|
-
return [`L${range.first} \xB7 ${
|
|
898
|
+
return [`L${range.first} \xB7 ${fmtBytes2(text.length)}`];
|
|
999
899
|
}
|
|
1000
900
|
const contiguous = range.count === range.last - range.first + 1;
|
|
1001
901
|
const head = `L${range.first}\u2013${range.last}`;
|
|
1002
902
|
const tail = contiguous ? `${range.count} line${range.count === 1 ? "" : "s"}` : `${range.count} lines (gaps)`;
|
|
1003
|
-
return [`${head} \xB7 ${tail} \xB7 ${
|
|
903
|
+
return [`${head} \xB7 ${tail} \xB7 ${fmtBytes2(text.length)}`];
|
|
1004
904
|
}
|
|
1005
905
|
}
|
|
1006
906
|
if (toolName === "grep" || toolName === "glob") {
|
|
@@ -1058,7 +958,7 @@ function formatToolOutput(toolName, output, ok, _outputBytes, outputLines) {
|
|
|
1058
958
|
const head = [];
|
|
1059
959
|
if (status !== void 0) head.push(`HTTP ${status}`);
|
|
1060
960
|
if (ct) head.push(ct.split(";")[0] ?? ct);
|
|
1061
|
-
if (content) head.push(
|
|
961
|
+
if (content) head.push(fmtBytes2(Buffer.byteLength(content, "utf8")));
|
|
1062
962
|
const lines = [];
|
|
1063
963
|
if (head.length > 0) lines.push(head.join(" \xB7 "));
|
|
1064
964
|
if (url && status !== void 0 && (status < 200 || status >= 400)) {
|
|
@@ -1434,7 +1334,7 @@ function countLines(text) {
|
|
|
1434
1334
|
if (!text) return 0;
|
|
1435
1335
|
return text.replace(/\n$/, "").split("\n").length;
|
|
1436
1336
|
}
|
|
1437
|
-
function
|
|
1337
|
+
function fmtBytes2(n) {
|
|
1438
1338
|
if (n < 1024) return `${n}B`;
|
|
1439
1339
|
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)}KB`;
|
|
1440
1340
|
return `${(n / (1024 * 1024)).toFixed(1)}MB`;
|
|
@@ -1515,6 +1415,81 @@ function Input({
|
|
|
1515
1415
|
hint ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: hint }) : null
|
|
1516
1416
|
] });
|
|
1517
1417
|
}
|
|
1418
|
+
function fmtElapsed(ms) {
|
|
1419
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
1420
|
+
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
|
|
1421
|
+
const m = Math.floor(ms / 6e4);
|
|
1422
|
+
const s = Math.floor(ms % 6e4 / 1e3);
|
|
1423
|
+
return `${m}m${s.toString().padStart(2, "0")}s`;
|
|
1424
|
+
}
|
|
1425
|
+
function fmtBytes3(n) {
|
|
1426
|
+
if (n < 1024) return `${n}B`;
|
|
1427
|
+
return `${(n / 1024).toFixed(1)}KB`;
|
|
1428
|
+
}
|
|
1429
|
+
function fmtRecentTool2(tool) {
|
|
1430
|
+
const status = tool.ok === false ? "fail" : "ok";
|
|
1431
|
+
const name = tool.name.length > 18 ? `${tool.name.slice(0, 17)}...` : tool.name;
|
|
1432
|
+
const parts = [status, name];
|
|
1433
|
+
if (typeof tool.durationMs === "number") parts.push(fmtElapsed(tool.durationMs));
|
|
1434
|
+
if (typeof tool.outputBytes === "number" && tool.outputBytes > 0) parts.push(fmtBytes3(tool.outputBytes));
|
|
1435
|
+
if (typeof tool.outputLines === "number" && tool.outputLines > 0) parts.push(`${tool.outputLines}L`);
|
|
1436
|
+
return parts.join(" ");
|
|
1437
|
+
}
|
|
1438
|
+
function fmtRecentMessage2(message) {
|
|
1439
|
+
const text = message.text.replace(/\s+/g, " ");
|
|
1440
|
+
return text.length > 48 ? `${text.slice(0, 47)}...` : text;
|
|
1441
|
+
}
|
|
1442
|
+
function LiveActivityStrip({
|
|
1443
|
+
entries,
|
|
1444
|
+
nowTick,
|
|
1445
|
+
maxRows = 4
|
|
1446
|
+
}) {
|
|
1447
|
+
const running = Object.values(entries).filter((e) => e.status === "running").sort((a, b) => a.startedAt - b.startedAt).slice(0, maxRows);
|
|
1448
|
+
if (running.length === 0) return null;
|
|
1449
|
+
const now = Date.now();
|
|
1450
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
|
|
1451
|
+
running.map((e) => {
|
|
1452
|
+
const toolElapsed = e.currentTool ? now - e.currentTool.startedAt : 0;
|
|
1453
|
+
const taskElapsed = now - e.startedAt;
|
|
1454
|
+
const toolSeg = e.currentTool ? `\u2192 ${e.currentTool.name} (${fmtElapsed(toolElapsed)})` : "idle between tools";
|
|
1455
|
+
const recentTools = (e.recentTools ?? []).slice(-2).map(fmtRecentTool2).join(" | ");
|
|
1456
|
+
const messageText = e.streamingText.trim() || (e.recentMessages ?? []).slice(-1).map(fmtRecentMessage2).join("");
|
|
1457
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
1458
|
+
/* @__PURE__ */ jsx(Text, { color: "cyan", children: "\u25CF" }),
|
|
1459
|
+
/* @__PURE__ */ jsx(Text, { children: e.name.slice(0, 14).padEnd(14) }),
|
|
1460
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
|
|
1461
|
+
/* @__PURE__ */ jsx(Text, { color: e.currentTool ? "green" : "yellow", children: toolSeg }),
|
|
1462
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
|
|
1463
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1464
|
+
e.iterations,
|
|
1465
|
+
"it ",
|
|
1466
|
+
e.toolCalls,
|
|
1467
|
+
"tc \xB7 ",
|
|
1468
|
+
fmtElapsed(taskElapsed)
|
|
1469
|
+
] }),
|
|
1470
|
+
recentTools ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1471
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "|" }),
|
|
1472
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1473
|
+
"last: ",
|
|
1474
|
+
recentTools
|
|
1475
|
+
] })
|
|
1476
|
+
] }) : null,
|
|
1477
|
+
messageText ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1478
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "|" }),
|
|
1479
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1480
|
+
"msg: ",
|
|
1481
|
+
fmtRecentMessage2({ text: messageText})
|
|
1482
|
+
] })
|
|
1483
|
+
] }) : null
|
|
1484
|
+
] }, e.id);
|
|
1485
|
+
}),
|
|
1486
|
+
Object.values(entries).filter((e) => e.status === "running").length > maxRows ? /* @__PURE__ */ jsx(Box, { paddingLeft: 2, children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1487
|
+
"\u2026+",
|
|
1488
|
+
Object.values(entries).filter((e) => e.status === "running").length - maxRows,
|
|
1489
|
+
" more"
|
|
1490
|
+
] }) }) : null
|
|
1491
|
+
] });
|
|
1492
|
+
}
|
|
1518
1493
|
function ModelPicker({
|
|
1519
1494
|
step,
|
|
1520
1495
|
providerOptions,
|
|
@@ -1526,7 +1501,7 @@ function ModelPicker({
|
|
|
1526
1501
|
if (step === "provider") {
|
|
1527
1502
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
|
|
1528
1503
|
/* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "\u2501\u2501 Switch model \u2014 Step 1/2: Pick provider \u2501\u2501" }),
|
|
1529
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 navigate \xB7 Enter select \xB7 Esc cancel" }),
|
|
1504
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 navigate \xB7 Enter select \xB7 Esc cancel \xB7 Ctrl+C exit" }),
|
|
1530
1505
|
providerOptions.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "(no providers with keys \u2014 add one via `wstack auth`)" }) : providerOptions.map((p, i) => /* @__PURE__ */ jsxs(Text, { color: i === selected ? "cyan" : void 0, inverse: i === selected, children: [
|
|
1531
1506
|
i === selected ? "\u203A " : " ",
|
|
1532
1507
|
/* @__PURE__ */ jsx(Text, { bold: true, children: p.id.padEnd(28) }),
|
|
@@ -1551,7 +1526,7 @@ function ModelPicker({
|
|
|
1551
1526
|
pickedProviderId,
|
|
1552
1527
|
") \u2501\u2501"
|
|
1553
1528
|
] }),
|
|
1554
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 navigate \xB7 Enter select \xB7 Esc back \xB7 Ctrl
|
|
1529
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 navigate \xB7 Enter select \xB7 Esc back \xB7 Ctrl+C exit" }),
|
|
1555
1530
|
modelOptions.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "(no models known for this provider)" }) : modelOptions.map((id, i) => /* @__PURE__ */ jsxs(Text, { color: i === selected ? "cyan" : void 0, inverse: i === selected, children: [
|
|
1556
1531
|
i === selected ? "\u203A " : " ",
|
|
1557
1532
|
id
|
|
@@ -2050,6 +2025,11 @@ function oneLine(s, max) {
|
|
|
2050
2025
|
const collapsed = s.replace(/\s+/g, " ").trim();
|
|
2051
2026
|
return collapsed.length <= max ? collapsed : `${collapsed.slice(0, max - 1)}\u2026`;
|
|
2052
2027
|
}
|
|
2028
|
+
function selectedSlashCommandLine(picker) {
|
|
2029
|
+
if (!picker.open || picker.matches.length === 0) return null;
|
|
2030
|
+
const picked = picker.matches[picker.selected];
|
|
2031
|
+
return picked ? `/${picked.name}` : null;
|
|
2032
|
+
}
|
|
2053
2033
|
function reducer(state, action) {
|
|
2054
2034
|
switch (action.type) {
|
|
2055
2035
|
case "addEntry": {
|
|
@@ -2066,6 +2046,7 @@ function reducer(state, action) {
|
|
|
2066
2046
|
buffer: "",
|
|
2067
2047
|
cursor: 0,
|
|
2068
2048
|
placeholders: [],
|
|
2049
|
+
historyIndex: 0,
|
|
2069
2050
|
picker: { open: false, query: "", matches: [], selected: 0 },
|
|
2070
2051
|
slashPicker: { open: false, query: "", matches: [], selected: 0 }
|
|
2071
2052
|
};
|
|
@@ -2285,9 +2266,9 @@ function reducer(state, action) {
|
|
|
2285
2266
|
modelPicker: { ...state.modelPicker, hint: action.text }
|
|
2286
2267
|
};
|
|
2287
2268
|
case "confirmOpen":
|
|
2288
|
-
return { ...state,
|
|
2269
|
+
return { ...state, confirmQueue: [...state.confirmQueue, action.info] };
|
|
2289
2270
|
case "confirmClose":
|
|
2290
|
-
return { ...state,
|
|
2271
|
+
return { ...state, confirmQueue: state.confirmQueue.slice(1) };
|
|
2291
2272
|
case "resetContextChip":
|
|
2292
2273
|
return { ...state, contextChipVersion: state.contextChipVersion + 1 };
|
|
2293
2274
|
// --- Fleet ---
|
|
@@ -2626,7 +2607,7 @@ function App({
|
|
|
2626
2607
|
modelOptions: [],
|
|
2627
2608
|
selected: 0
|
|
2628
2609
|
},
|
|
2629
|
-
|
|
2610
|
+
confirmQueue: [],
|
|
2630
2611
|
contextChipVersion: 0,
|
|
2631
2612
|
fleet: {},
|
|
2632
2613
|
fleetCost: 0,
|
|
@@ -2640,7 +2621,7 @@ function App({
|
|
|
2640
2621
|
const inputGateRef = useRef(false);
|
|
2641
2622
|
const lastEnterAtRef = useRef(0);
|
|
2642
2623
|
const projectRoot = agent.ctx.projectRoot;
|
|
2643
|
-
const projectName =
|
|
2624
|
+
const projectName = React4.useMemo(() => {
|
|
2644
2625
|
const base = path2.basename(projectRoot);
|
|
2645
2626
|
return base && base !== path2.sep ? base : void 0;
|
|
2646
2627
|
}, [projectRoot]);
|
|
@@ -2649,14 +2630,24 @@ function App({
|
|
|
2649
2630
|
const flushTimerRef = useRef(null);
|
|
2650
2631
|
const stateRef = useRef(state);
|
|
2651
2632
|
stateRef.current = state;
|
|
2633
|
+
const draftRef = useRef({ buffer: state.buffer, cursor: state.cursor });
|
|
2634
|
+
draftRef.current = { buffer: state.buffer, cursor: state.cursor };
|
|
2635
|
+
const setDraft = (buffer, cursor) => {
|
|
2636
|
+
draftRef.current = { buffer, cursor };
|
|
2637
|
+
dispatch({ type: "setBuffer", buffer, cursor });
|
|
2638
|
+
};
|
|
2639
|
+
const clearDraft = () => {
|
|
2640
|
+
draftRef.current = { buffer: "", cursor: 0 };
|
|
2641
|
+
dispatch({ type: "clearInput" });
|
|
2642
|
+
};
|
|
2652
2643
|
const startedAtRef = useRef(Date.now());
|
|
2653
|
-
const [nowTick, setNowTick] =
|
|
2644
|
+
const [nowTick, setNowTick] = React4.useState(Date.now());
|
|
2654
2645
|
useEffect(() => {
|
|
2655
2646
|
const t = setInterval(() => setNowTick(Date.now()), 1e3);
|
|
2656
2647
|
return () => clearInterval(t);
|
|
2657
2648
|
}, []);
|
|
2658
2649
|
const elapsedMs = nowTick - startedAtRef.current;
|
|
2659
|
-
const [gitInfo, setGitInfo] =
|
|
2650
|
+
const [gitInfo, setGitInfo] = React4.useState(null);
|
|
2660
2651
|
useEffect(() => {
|
|
2661
2652
|
let cancelled = false;
|
|
2662
2653
|
const refresh = () => {
|
|
@@ -2671,7 +2662,7 @@ function App({
|
|
|
2671
2662
|
clearInterval(t);
|
|
2672
2663
|
};
|
|
2673
2664
|
}, [agent.ctx.cwd]);
|
|
2674
|
-
const [lastInputTokens, setLastInputTokens] =
|
|
2665
|
+
const [lastInputTokens, setLastInputTokens] = React4.useState(0);
|
|
2675
2666
|
useEffect(() => {
|
|
2676
2667
|
const off = events.on("provider.response", (e) => {
|
|
2677
2668
|
const total = (e.usage.input ?? 0) + (e.usage.cacheRead ?? 0) + (e.usage.cacheWrite ?? 0);
|
|
@@ -2782,7 +2773,7 @@ function App({
|
|
|
2782
2773
|
const prevAnyOverlayOpen = useRef(false);
|
|
2783
2774
|
const prevEntriesCount = useRef(0);
|
|
2784
2775
|
useEffect(() => {
|
|
2785
|
-
const anyOpenNow = state.picker.open || state.slashPicker.open || state.modelPicker.open ||
|
|
2776
|
+
const anyOpenNow = state.picker.open || state.slashPicker.open || state.modelPicker.open || state.confirmQueue.length > 0;
|
|
2786
2777
|
const overlayClosed = prevAnyOverlayOpen.current && !anyOpenNow;
|
|
2787
2778
|
const newEntryCommitted = state.entries.length > prevEntriesCount.current;
|
|
2788
2779
|
prevAnyOverlayOpen.current = anyOpenNow;
|
|
@@ -2797,7 +2788,7 @@ function App({
|
|
|
2797
2788
|
state.picker.open,
|
|
2798
2789
|
state.slashPicker.open,
|
|
2799
2790
|
state.modelPicker.open,
|
|
2800
|
-
state.
|
|
2791
|
+
state.confirmQueue.length,
|
|
2801
2792
|
state.entries.length
|
|
2802
2793
|
]);
|
|
2803
2794
|
useEffect(() => {
|
|
@@ -2838,7 +2829,7 @@ function App({
|
|
|
2838
2829
|
}).slice(0, 12).map(({ cmd, owner }) => ({
|
|
2839
2830
|
name: cmd.name,
|
|
2840
2831
|
description: cmd.description,
|
|
2841
|
-
argsHint:
|
|
2832
|
+
argsHint: cmd.argsHint,
|
|
2842
2833
|
isBuiltin: owner === "core"
|
|
2843
2834
|
}));
|
|
2844
2835
|
if (!state.slashPicker.open) {
|
|
@@ -2879,7 +2870,8 @@ function App({
|
|
|
2879
2870
|
if (!picked) return;
|
|
2880
2871
|
const builder = builderRef.current;
|
|
2881
2872
|
if (!builder) return;
|
|
2882
|
-
const
|
|
2873
|
+
const draft = draftRef.current;
|
|
2874
|
+
const tok = detectAtToken(draft.buffer, draft.cursor);
|
|
2883
2875
|
if (!tok) {
|
|
2884
2876
|
dispatch({ type: "pickerClose" });
|
|
2885
2877
|
return;
|
|
@@ -2892,14 +2884,10 @@ function App({
|
|
|
2892
2884
|
data,
|
|
2893
2885
|
meta: { filename: picked, label: picked }
|
|
2894
2886
|
});
|
|
2895
|
-
const before =
|
|
2896
|
-
const after =
|
|
2887
|
+
const before = draft.buffer.slice(0, tok.start);
|
|
2888
|
+
const after = draft.buffer.slice(tok.end);
|
|
2897
2889
|
const next = `${before}${placeholder}${after}`;
|
|
2898
|
-
|
|
2899
|
-
type: "setBuffer",
|
|
2900
|
-
buffer: next,
|
|
2901
|
-
cursor: tok.start + placeholder.length
|
|
2902
|
-
});
|
|
2890
|
+
setDraft(next, tok.start + placeholder.length);
|
|
2903
2891
|
dispatch({ type: "pickerClose" });
|
|
2904
2892
|
} catch (err) {
|
|
2905
2893
|
dispatch({
|
|
@@ -2918,7 +2906,7 @@ function App({
|
|
|
2918
2906
|
const picked = matches[selected];
|
|
2919
2907
|
if (!picked) return;
|
|
2920
2908
|
const cmd = picked.argsHint !== void 0 ? `/${picked.name} ` : `/${picked.name}`;
|
|
2921
|
-
|
|
2909
|
+
setDraft(cmd, cmd.length);
|
|
2922
2910
|
dispatch({ type: "slashPickerClose" });
|
|
2923
2911
|
};
|
|
2924
2912
|
useEffect(() => {
|
|
@@ -3180,15 +3168,6 @@ function App({
|
|
|
3180
3168
|
}
|
|
3181
3169
|
});
|
|
3182
3170
|
const offConfirmNeeded = events.on("tool.confirm_needed", (e) => {
|
|
3183
|
-
dispatch({
|
|
3184
|
-
type: "addEntry",
|
|
3185
|
-
entry: {
|
|
3186
|
-
kind: "confirm",
|
|
3187
|
-
toolName: e.tool.name,
|
|
3188
|
-
input: e.input,
|
|
3189
|
-
suggestedPattern: e.suggestedPattern
|
|
3190
|
-
}
|
|
3191
|
-
});
|
|
3192
3171
|
dispatch({
|
|
3193
3172
|
type: "confirmOpen",
|
|
3194
3173
|
info: {
|
|
@@ -3200,6 +3179,17 @@ function App({
|
|
|
3200
3179
|
}
|
|
3201
3180
|
});
|
|
3202
3181
|
});
|
|
3182
|
+
const offTrustPersisted = events.on("trust.persisted", (e) => {
|
|
3183
|
+
const icon = e.decision === "always" ? "\u2713" : "\u2717";
|
|
3184
|
+
const label = e.decision === "always" ? "always allowed" : "denied";
|
|
3185
|
+
dispatch({
|
|
3186
|
+
type: "addEntry",
|
|
3187
|
+
entry: {
|
|
3188
|
+
kind: "info",
|
|
3189
|
+
text: `${icon} ${label}: ${e.tool}(${e.pattern})`
|
|
3190
|
+
}
|
|
3191
|
+
});
|
|
3192
|
+
});
|
|
3203
3193
|
return () => {
|
|
3204
3194
|
offDelta();
|
|
3205
3195
|
offToolStart();
|
|
@@ -3209,6 +3199,7 @@ function App({
|
|
|
3209
3199
|
offProvErr();
|
|
3210
3200
|
offProvResp();
|
|
3211
3201
|
offConfirmNeeded();
|
|
3202
|
+
offTrustPersisted();
|
|
3212
3203
|
if (flushTimerRef.current) clearTimeout(flushTimerRef.current);
|
|
3213
3204
|
};
|
|
3214
3205
|
}, [events, agent.ctx.todos]);
|
|
@@ -3461,25 +3452,35 @@ function App({
|
|
|
3461
3452
|
}, [director]);
|
|
3462
3453
|
useEffect(() => {
|
|
3463
3454
|
const onSigint = () => {
|
|
3464
|
-
|
|
3465
|
-
|
|
3455
|
+
const current = stateRef.current;
|
|
3456
|
+
if (current.interrupts >= 1) {
|
|
3457
|
+
if (current.interrupts >= 2) {
|
|
3466
3458
|
process.exit(130);
|
|
3467
3459
|
}
|
|
3468
3460
|
try {
|
|
3469
|
-
exit();
|
|
3470
|
-
onExit(130);
|
|
3461
|
+
process.exit(130);
|
|
3471
3462
|
} catch {
|
|
3472
3463
|
}
|
|
3473
|
-
setTimeout(() => {
|
|
3474
|
-
try {
|
|
3475
|
-
process.exit(130);
|
|
3476
|
-
} catch {
|
|
3477
|
-
}
|
|
3478
|
-
}, 500).unref?.();
|
|
3479
3464
|
dispatch({ type: "interrupt" });
|
|
3480
3465
|
return;
|
|
3481
3466
|
}
|
|
3482
3467
|
dispatch({ type: "interrupt" });
|
|
3468
|
+
if (current.modelPicker.open) {
|
|
3469
|
+
dispatch({ type: "modelPickerClose" });
|
|
3470
|
+
dispatch({
|
|
3471
|
+
type: "addEntry",
|
|
3472
|
+
entry: { kind: "warn", text: "Model picker cancelled." }
|
|
3473
|
+
});
|
|
3474
|
+
return;
|
|
3475
|
+
}
|
|
3476
|
+
if (current.slashPicker.open) {
|
|
3477
|
+
dispatch({ type: "slashPickerClose" });
|
|
3478
|
+
dispatch({
|
|
3479
|
+
type: "addEntry",
|
|
3480
|
+
entry: { kind: "warn", text: "Cancelled." }
|
|
3481
|
+
});
|
|
3482
|
+
return;
|
|
3483
|
+
}
|
|
3483
3484
|
if (activeCtrlRef.current) {
|
|
3484
3485
|
activeCtrlRef.current.abort();
|
|
3485
3486
|
dispatch({ type: "status", status: "aborting" });
|
|
@@ -3520,9 +3521,10 @@ function App({
|
|
|
3520
3521
|
return () => {
|
|
3521
3522
|
process.off("SIGINT", onSigint);
|
|
3522
3523
|
};
|
|
3523
|
-
}, [
|
|
3524
|
+
}, [exit, onExit, director]);
|
|
3524
3525
|
const handleKey = async (input, key) => {
|
|
3525
|
-
if (state.status === "aborting") return;
|
|
3526
|
+
if (state.status === "aborting" && state.interrupts === 0) return;
|
|
3527
|
+
if (state.confirmQueue.length > 0) return;
|
|
3526
3528
|
if (inputGateRef.current) return;
|
|
3527
3529
|
const isEnter = key.return || input === "\r" || input === "\n";
|
|
3528
3530
|
if (state.modelPicker.open) {
|
|
@@ -3591,15 +3593,23 @@ function App({
|
|
|
3591
3593
|
return;
|
|
3592
3594
|
}
|
|
3593
3595
|
if (isEnter) {
|
|
3596
|
+
const now = Date.now();
|
|
3597
|
+
if (now - lastEnterAtRef.current < 50) return;
|
|
3598
|
+
lastEnterAtRef.current = now;
|
|
3594
3599
|
inputGateRef.current = true;
|
|
3595
|
-
|
|
3600
|
+
const line = selectedSlashCommandLine(state.slashPicker);
|
|
3601
|
+
if (line) {
|
|
3602
|
+
void submit(line);
|
|
3603
|
+
} else {
|
|
3604
|
+
acceptSlashPickerSelection();
|
|
3605
|
+
}
|
|
3596
3606
|
inputGateRef.current = false;
|
|
3597
3607
|
return;
|
|
3598
3608
|
}
|
|
3599
3609
|
if (key.tab && state.slashPicker.matches.length > 0) {
|
|
3600
3610
|
const sel = state.slashPicker.matches[state.slashPicker.selected];
|
|
3601
3611
|
if (sel) {
|
|
3602
|
-
|
|
3612
|
+
setDraft(`/${sel.name} `, sel.name.length + 2);
|
|
3603
3613
|
dispatch({ type: "slashPickerClose" });
|
|
3604
3614
|
}
|
|
3605
3615
|
return;
|
|
@@ -3628,7 +3638,7 @@ function App({
|
|
|
3628
3638
|
return;
|
|
3629
3639
|
}
|
|
3630
3640
|
}
|
|
3631
|
-
if (key.escape && state.status !== "idle" &&
|
|
3641
|
+
if (key.escape && state.status !== "idle" && state.confirmQueue.length === 0) {
|
|
3632
3642
|
const runningTools = Array.from(state.runningTools.values()).map((t) => t.name);
|
|
3633
3643
|
const subagents = Object.values(state.fleet).filter((e) => e.status === "running").map((e) => ({
|
|
3634
3644
|
label: e.name,
|
|
@@ -3675,64 +3685,60 @@ function App({
|
|
|
3675
3685
|
void submit();
|
|
3676
3686
|
return;
|
|
3677
3687
|
}
|
|
3688
|
+
const { buffer, cursor } = draftRef.current;
|
|
3678
3689
|
if (key.backspace || key.delete) {
|
|
3679
3690
|
if (key.ctrl) {
|
|
3680
|
-
const { cursor, buffer } = state;
|
|
3681
3691
|
if (key.backspace) {
|
|
3682
3692
|
if (cursor === 0) return;
|
|
3683
3693
|
const beforeCursor = buffer.slice(0, cursor);
|
|
3684
3694
|
const lastWordStart = beforeCursor.lastIndexOf(" ") + 1;
|
|
3685
3695
|
const next3 = buffer.slice(0, lastWordStart) + buffer.slice(cursor);
|
|
3686
|
-
|
|
3696
|
+
setDraft(next3, lastWordStart);
|
|
3687
3697
|
} else {
|
|
3688
3698
|
if (cursor >= buffer.length) return;
|
|
3689
3699
|
const afterCursor = buffer.slice(cursor);
|
|
3690
3700
|
const nextWordStart = afterCursor.indexOf(" ");
|
|
3691
3701
|
const end = nextWordStart === -1 ? buffer.length : cursor + nextWordStart + 1;
|
|
3692
3702
|
const next3 = buffer.slice(0, cursor) + buffer.slice(end);
|
|
3693
|
-
|
|
3703
|
+
setDraft(next3, cursor);
|
|
3694
3704
|
}
|
|
3695
3705
|
return;
|
|
3696
3706
|
}
|
|
3697
|
-
if (
|
|
3698
|
-
const next2 =
|
|
3699
|
-
|
|
3707
|
+
if (cursor === 0) return;
|
|
3708
|
+
const next2 = buffer.slice(0, cursor - 1) + buffer.slice(cursor);
|
|
3709
|
+
setDraft(next2, cursor - 1);
|
|
3700
3710
|
return;
|
|
3701
3711
|
}
|
|
3702
3712
|
if (key.leftArrow) {
|
|
3703
3713
|
if (key.ctrl) {
|
|
3704
|
-
const { cursor, buffer } = state;
|
|
3705
3714
|
if (cursor === 0) return;
|
|
3706
3715
|
const beforeCursor = buffer.slice(0, cursor);
|
|
3707
3716
|
const prevWordStart = beforeCursor.lastIndexOf(" ");
|
|
3708
3717
|
const target = prevWordStart === -1 ? 0 : prevWordStart + 1;
|
|
3709
|
-
|
|
3718
|
+
setDraft(buffer, target);
|
|
3710
3719
|
return;
|
|
3711
3720
|
}
|
|
3712
|
-
if (
|
|
3713
|
-
dispatch({ type: "setBuffer", buffer: state.buffer, cursor: state.cursor - 1 });
|
|
3721
|
+
if (cursor > 0) setDraft(buffer, cursor - 1);
|
|
3714
3722
|
return;
|
|
3715
3723
|
}
|
|
3716
3724
|
if (key.rightArrow) {
|
|
3717
3725
|
if (key.ctrl) {
|
|
3718
|
-
const { cursor, buffer } = state;
|
|
3719
3726
|
if (cursor >= buffer.length) return;
|
|
3720
3727
|
const afterCursor = buffer.slice(cursor);
|
|
3721
3728
|
const nextWordStart = afterCursor.indexOf(" ");
|
|
3722
3729
|
const target = nextWordStart === -1 ? buffer.length : cursor + nextWordStart + 1;
|
|
3723
|
-
|
|
3730
|
+
setDraft(buffer, target);
|
|
3724
3731
|
return;
|
|
3725
3732
|
}
|
|
3726
|
-
if (
|
|
3727
|
-
dispatch({ type: "setBuffer", buffer: state.buffer, cursor: state.cursor + 1 });
|
|
3733
|
+
if (cursor < buffer.length) setDraft(buffer, cursor + 1);
|
|
3728
3734
|
return;
|
|
3729
3735
|
}
|
|
3730
3736
|
if (key.home) {
|
|
3731
|
-
|
|
3737
|
+
setDraft(buffer, 0);
|
|
3732
3738
|
return;
|
|
3733
3739
|
}
|
|
3734
3740
|
if (key.end) {
|
|
3735
|
-
|
|
3741
|
+
setDraft(buffer, buffer.length);
|
|
3736
3742
|
return;
|
|
3737
3743
|
}
|
|
3738
3744
|
if (key.upArrow) {
|
|
@@ -3744,24 +3750,23 @@ function App({
|
|
|
3744
3750
|
return;
|
|
3745
3751
|
}
|
|
3746
3752
|
if (key.ctrl && input === "a") {
|
|
3747
|
-
|
|
3753
|
+
setDraft(buffer, 0);
|
|
3748
3754
|
return;
|
|
3749
3755
|
}
|
|
3750
3756
|
if (key.ctrl && input === "e") {
|
|
3751
|
-
|
|
3757
|
+
setDraft(buffer, buffer.length);
|
|
3752
3758
|
return;
|
|
3753
3759
|
}
|
|
3754
3760
|
if (key.ctrl && input === "u") {
|
|
3755
|
-
|
|
3761
|
+
setDraft("", 0);
|
|
3756
3762
|
return;
|
|
3757
3763
|
}
|
|
3758
3764
|
if (key.ctrl && input === "w") {
|
|
3759
|
-
const { cursor, buffer } = state;
|
|
3760
3765
|
if (cursor === 0) return;
|
|
3761
3766
|
const beforeCursor = buffer.slice(0, cursor);
|
|
3762
3767
|
const lastWordStart = beforeCursor.lastIndexOf(" ") + 1;
|
|
3763
3768
|
const next2 = buffer.slice(0, lastWordStart) + buffer.slice(cursor);
|
|
3764
|
-
|
|
3769
|
+
setDraft(next2, lastWordStart);
|
|
3765
3770
|
return;
|
|
3766
3771
|
}
|
|
3767
3772
|
if (key.meta && input === "v") {
|
|
@@ -3783,13 +3788,13 @@ function App({
|
|
|
3783
3788
|
const lineCount = cleanInput.split("\n").length;
|
|
3784
3789
|
dispatch({ type: "addPlaceholder", ph: `${ph} (${lineCount} lines)` });
|
|
3785
3790
|
} else {
|
|
3786
|
-
const next2 =
|
|
3787
|
-
|
|
3791
|
+
const next2 = buffer.slice(0, cursor) + cleanInput + buffer.slice(cursor);
|
|
3792
|
+
setDraft(next2, cursor + cleanInput.length);
|
|
3788
3793
|
}
|
|
3789
3794
|
return;
|
|
3790
3795
|
}
|
|
3791
|
-
const next =
|
|
3792
|
-
|
|
3796
|
+
const next = buffer.slice(0, cursor) + cleanInput + buffer.slice(cursor);
|
|
3797
|
+
setDraft(next, cursor + cleanInput.length);
|
|
3793
3798
|
};
|
|
3794
3799
|
const runBlocks = async (blocks) => {
|
|
3795
3800
|
const ctrl = new AbortController();
|
|
@@ -3871,21 +3876,24 @@ function App({
|
|
|
3871
3876
|
};
|
|
3872
3877
|
const runBlocksRef = useRef(runBlocks);
|
|
3873
3878
|
runBlocksRef.current = runBlocks;
|
|
3874
|
-
const submit = async () => {
|
|
3875
|
-
const raw =
|
|
3879
|
+
const submit = async (overrideRaw) => {
|
|
3880
|
+
const raw = overrideRaw ?? draftRef.current.buffer;
|
|
3876
3881
|
const trimmed = raw.trim();
|
|
3877
3882
|
if (!trimmed && state.placeholders.length === 0) return;
|
|
3878
3883
|
dispatch({ type: "resetInterrupts" });
|
|
3884
|
+
const pushSubmittedHistory = () => {
|
|
3885
|
+
if (trimmed) dispatch({ type: "historyPush", text: trimmed });
|
|
3886
|
+
};
|
|
3879
3887
|
if (trimmed === "/image" || trimmed === "/paste-image") {
|
|
3880
|
-
|
|
3888
|
+
pushSubmittedHistory();
|
|
3889
|
+
clearDraft();
|
|
3881
3890
|
await pasteClipboardImage();
|
|
3882
|
-
if (state.historyIndex > 0) dispatch({ type: "historyPush", text: trimmed });
|
|
3883
3891
|
return;
|
|
3884
3892
|
}
|
|
3885
3893
|
if (trimmed.startsWith("/")) {
|
|
3886
3894
|
dispatch({ type: "addEntry", entry: { kind: "user", text: trimmed } });
|
|
3887
|
-
|
|
3888
|
-
|
|
3895
|
+
pushSubmittedHistory();
|
|
3896
|
+
clearDraft();
|
|
3889
3897
|
try {
|
|
3890
3898
|
const res = await slashRegistry.dispatch(trimmed, agent.ctx);
|
|
3891
3899
|
if (res?.message) {
|
|
@@ -3931,20 +3939,19 @@ function App({
|
|
|
3931
3939
|
builder.appendText(toAppend);
|
|
3932
3940
|
}
|
|
3933
3941
|
if (steering) dispatch({ type: "steerConsume" });
|
|
3934
|
-
const blocks = await builder.submit();
|
|
3935
3942
|
const displayText = trimmed ? steering ? `\u21AF ${trimmed}` : trimmed : "(attachments only)";
|
|
3936
|
-
|
|
3943
|
+
pushSubmittedHistory();
|
|
3944
|
+
clearDraft();
|
|
3945
|
+
const blocks = await builder.submit();
|
|
3937
3946
|
if (state.status !== "idle") {
|
|
3938
3947
|
dispatch({
|
|
3939
3948
|
type: "addEntry",
|
|
3940
3949
|
entry: { kind: "user", text: displayText, queued: true }
|
|
3941
3950
|
});
|
|
3942
3951
|
dispatch({ type: "enqueue", item: { displayText, blocks } });
|
|
3943
|
-
if (state.historyIndex > 0) dispatch({ type: "historyPush", text: trimmed });
|
|
3944
3952
|
return;
|
|
3945
3953
|
}
|
|
3946
3954
|
dispatch({ type: "addEntry", entry: { kind: "user", text: displayText } });
|
|
3947
|
-
if (state.historyIndex > 0) dispatch({ type: "historyPush", text: trimmed });
|
|
3948
3955
|
await runBlocks(blocks);
|
|
3949
3956
|
};
|
|
3950
3957
|
const bootInjectedRef = useRef(false);
|
|
@@ -3999,7 +4006,7 @@ function App({
|
|
|
3999
4006
|
value: state.buffer,
|
|
4000
4007
|
cursor: state.cursor,
|
|
4001
4008
|
placeholders: state.placeholders,
|
|
4002
|
-
disabled: state.status === "aborting",
|
|
4009
|
+
disabled: state.status === "aborting" || state.confirmQueue.length > 0,
|
|
4003
4010
|
hint: inputHint,
|
|
4004
4011
|
onKey: handleKey
|
|
4005
4012
|
}
|
|
@@ -4031,15 +4038,24 @@ function App({
|
|
|
4031
4038
|
hint: state.modelPicker.hint
|
|
4032
4039
|
}
|
|
4033
4040
|
) : null,
|
|
4034
|
-
state.
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4041
|
+
state.confirmQueue.length > 0 && (() => {
|
|
4042
|
+
const head = state.confirmQueue[0];
|
|
4043
|
+
let resolved = false;
|
|
4044
|
+
return /* @__PURE__ */ jsx(
|
|
4045
|
+
ConfirmPrompt,
|
|
4046
|
+
{
|
|
4047
|
+
toolName: head.toolName,
|
|
4048
|
+
input: head.input,
|
|
4049
|
+
suggestedPattern: head.suggestedPattern,
|
|
4050
|
+
onDecision: (decision) => {
|
|
4051
|
+
if (resolved) return;
|
|
4052
|
+
resolved = true;
|
|
4053
|
+
head.resolve(decision);
|
|
4054
|
+
dispatch({ type: "confirmClose" });
|
|
4055
|
+
}
|
|
4056
|
+
}
|
|
4057
|
+
);
|
|
4058
|
+
})(),
|
|
4043
4059
|
/* @__PURE__ */ jsx(
|
|
4044
4060
|
StatusBar,
|
|
4045
4061
|
{
|
|
@@ -4171,7 +4187,7 @@ async function runTui(opts) {
|
|
|
4171
4187
|
let instance;
|
|
4172
4188
|
try {
|
|
4173
4189
|
instance = render(
|
|
4174
|
-
|
|
4190
|
+
React4.createElement(App, {
|
|
4175
4191
|
agent: opts.agent,
|
|
4176
4192
|
slashRegistry: opts.slashRegistry,
|
|
4177
4193
|
attachments: opts.attachments,
|