@wrongstack/tui 0.3.3 → 0.3.4
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 +169 -134
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -256,81 +256,6 @@ function FleetPanel({ entries, totalCost, roster }) {
|
|
|
256
256
|
}
|
|
257
257
|
);
|
|
258
258
|
}
|
|
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
259
|
|
|
335
260
|
// src/markdown-table.ts
|
|
336
261
|
function renderMarkdownTables(text, maxWidth) {
|
|
@@ -641,7 +566,7 @@ function Entry({
|
|
|
641
566
|
parts.push(`${entry.outputLines} L`);
|
|
642
567
|
}
|
|
643
568
|
if (entry.outputBytes && entry.outputBytes > 0) {
|
|
644
|
-
parts.push(
|
|
569
|
+
parts.push(fmtBytes2(entry.outputBytes));
|
|
645
570
|
}
|
|
646
571
|
if (entry.outputTokens && entry.outputTokens > 0) {
|
|
647
572
|
parts.push(`\u2248${fmtTok(entry.outputTokens)} tok`);
|
|
@@ -925,7 +850,7 @@ function formatToolOutput(toolName, output, ok, _outputBytes, outputLines) {
|
|
|
925
850
|
const bytes = numOf(o["bytes_written"]) ?? numOf(o["bytes"]);
|
|
926
851
|
const created = o["created"] === true;
|
|
927
852
|
const tag = created ? "created" : "updated";
|
|
928
|
-
if (bytes !== void 0) return [`${tag} \xB7 ${
|
|
853
|
+
if (bytes !== void 0) return [`${tag} \xB7 ${fmtBytes2(bytes)}`];
|
|
929
854
|
return [tag];
|
|
930
855
|
}
|
|
931
856
|
}
|
|
@@ -990,17 +915,17 @@ function formatToolOutput(toolName, output, ok, _outputBytes, outputLines) {
|
|
|
990
915
|
if (json && typeof json === "object") {
|
|
991
916
|
const o = json;
|
|
992
917
|
const bytes = numOf(o["bytes"]);
|
|
993
|
-
if (bytes !== void 0) return [`${
|
|
918
|
+
if (bytes !== void 0) return [`${fmtBytes2(bytes)} read`];
|
|
994
919
|
}
|
|
995
920
|
const range = scanNumberedRange(text);
|
|
996
921
|
if (range.count > 0 && range.first !== void 0 && range.last !== void 0) {
|
|
997
922
|
if (range.first === range.last) {
|
|
998
|
-
return [`L${range.first} \xB7 ${
|
|
923
|
+
return [`L${range.first} \xB7 ${fmtBytes2(text.length)}`];
|
|
999
924
|
}
|
|
1000
925
|
const contiguous = range.count === range.last - range.first + 1;
|
|
1001
926
|
const head = `L${range.first}\u2013${range.last}`;
|
|
1002
927
|
const tail = contiguous ? `${range.count} line${range.count === 1 ? "" : "s"}` : `${range.count} lines (gaps)`;
|
|
1003
|
-
return [`${head} \xB7 ${tail} \xB7 ${
|
|
928
|
+
return [`${head} \xB7 ${tail} \xB7 ${fmtBytes2(text.length)}`];
|
|
1004
929
|
}
|
|
1005
930
|
}
|
|
1006
931
|
if (toolName === "grep" || toolName === "glob") {
|
|
@@ -1058,7 +983,7 @@ function formatToolOutput(toolName, output, ok, _outputBytes, outputLines) {
|
|
|
1058
983
|
const head = [];
|
|
1059
984
|
if (status !== void 0) head.push(`HTTP ${status}`);
|
|
1060
985
|
if (ct) head.push(ct.split(";")[0] ?? ct);
|
|
1061
|
-
if (content) head.push(
|
|
986
|
+
if (content) head.push(fmtBytes2(Buffer.byteLength(content, "utf8")));
|
|
1062
987
|
const lines = [];
|
|
1063
988
|
if (head.length > 0) lines.push(head.join(" \xB7 "));
|
|
1064
989
|
if (url && status !== void 0 && (status < 200 || status >= 400)) {
|
|
@@ -1434,7 +1359,7 @@ function countLines(text) {
|
|
|
1434
1359
|
if (!text) return 0;
|
|
1435
1360
|
return text.replace(/\n$/, "").split("\n").length;
|
|
1436
1361
|
}
|
|
1437
|
-
function
|
|
1362
|
+
function fmtBytes2(n) {
|
|
1438
1363
|
if (n < 1024) return `${n}B`;
|
|
1439
1364
|
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)}KB`;
|
|
1440
1365
|
return `${(n / (1024 * 1024)).toFixed(1)}MB`;
|
|
@@ -1515,6 +1440,81 @@ function Input({
|
|
|
1515
1440
|
hint ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: hint }) : null
|
|
1516
1441
|
] });
|
|
1517
1442
|
}
|
|
1443
|
+
function fmtElapsed(ms) {
|
|
1444
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
1445
|
+
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
|
|
1446
|
+
const m = Math.floor(ms / 6e4);
|
|
1447
|
+
const s = Math.floor(ms % 6e4 / 1e3);
|
|
1448
|
+
return `${m}m${s.toString().padStart(2, "0")}s`;
|
|
1449
|
+
}
|
|
1450
|
+
function fmtBytes3(n) {
|
|
1451
|
+
if (n < 1024) return `${n}B`;
|
|
1452
|
+
return `${(n / 1024).toFixed(1)}KB`;
|
|
1453
|
+
}
|
|
1454
|
+
function fmtRecentTool2(tool) {
|
|
1455
|
+
const status = tool.ok === false ? "fail" : "ok";
|
|
1456
|
+
const name = tool.name.length > 18 ? `${tool.name.slice(0, 17)}...` : tool.name;
|
|
1457
|
+
const parts = [status, name];
|
|
1458
|
+
if (typeof tool.durationMs === "number") parts.push(fmtElapsed(tool.durationMs));
|
|
1459
|
+
if (typeof tool.outputBytes === "number" && tool.outputBytes > 0) parts.push(fmtBytes3(tool.outputBytes));
|
|
1460
|
+
if (typeof tool.outputLines === "number" && tool.outputLines > 0) parts.push(`${tool.outputLines}L`);
|
|
1461
|
+
return parts.join(" ");
|
|
1462
|
+
}
|
|
1463
|
+
function fmtRecentMessage2(message) {
|
|
1464
|
+
const text = message.text.replace(/\s+/g, " ");
|
|
1465
|
+
return text.length > 48 ? `${text.slice(0, 47)}...` : text;
|
|
1466
|
+
}
|
|
1467
|
+
function LiveActivityStrip({
|
|
1468
|
+
entries,
|
|
1469
|
+
nowTick,
|
|
1470
|
+
maxRows = 4
|
|
1471
|
+
}) {
|
|
1472
|
+
const running = Object.values(entries).filter((e) => e.status === "running").sort((a, b) => a.startedAt - b.startedAt).slice(0, maxRows);
|
|
1473
|
+
if (running.length === 0) return null;
|
|
1474
|
+
const now = Date.now();
|
|
1475
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
|
|
1476
|
+
running.map((e) => {
|
|
1477
|
+
const toolElapsed = e.currentTool ? now - e.currentTool.startedAt : 0;
|
|
1478
|
+
const taskElapsed = now - e.startedAt;
|
|
1479
|
+
const toolSeg = e.currentTool ? `\u2192 ${e.currentTool.name} (${fmtElapsed(toolElapsed)})` : "idle between tools";
|
|
1480
|
+
const recentTools = (e.recentTools ?? []).slice(-2).map(fmtRecentTool2).join(" | ");
|
|
1481
|
+
const messageText = e.streamingText.trim() || (e.recentMessages ?? []).slice(-1).map(fmtRecentMessage2).join("");
|
|
1482
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
1483
|
+
/* @__PURE__ */ jsx(Text, { color: "cyan", children: "\u25CF" }),
|
|
1484
|
+
/* @__PURE__ */ jsx(Text, { children: e.name.slice(0, 14).padEnd(14) }),
|
|
1485
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
|
|
1486
|
+
/* @__PURE__ */ jsx(Text, { color: e.currentTool ? "green" : "yellow", children: toolSeg }),
|
|
1487
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
|
|
1488
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1489
|
+
e.iterations,
|
|
1490
|
+
"it ",
|
|
1491
|
+
e.toolCalls,
|
|
1492
|
+
"tc \xB7 ",
|
|
1493
|
+
fmtElapsed(taskElapsed)
|
|
1494
|
+
] }),
|
|
1495
|
+
recentTools ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1496
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "|" }),
|
|
1497
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1498
|
+
"last: ",
|
|
1499
|
+
recentTools
|
|
1500
|
+
] })
|
|
1501
|
+
] }) : null,
|
|
1502
|
+
messageText ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1503
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "|" }),
|
|
1504
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1505
|
+
"msg: ",
|
|
1506
|
+
fmtRecentMessage2({ text: messageText})
|
|
1507
|
+
] })
|
|
1508
|
+
] }) : null
|
|
1509
|
+
] }, e.id);
|
|
1510
|
+
}),
|
|
1511
|
+
Object.values(entries).filter((e) => e.status === "running").length > maxRows ? /* @__PURE__ */ jsx(Box, { paddingLeft: 2, children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1512
|
+
"\u2026+",
|
|
1513
|
+
Object.values(entries).filter((e) => e.status === "running").length - maxRows,
|
|
1514
|
+
" more"
|
|
1515
|
+
] }) }) : null
|
|
1516
|
+
] });
|
|
1517
|
+
}
|
|
1518
1518
|
function ModelPicker({
|
|
1519
1519
|
step,
|
|
1520
1520
|
providerOptions,
|
|
@@ -1526,7 +1526,7 @@ function ModelPicker({
|
|
|
1526
1526
|
if (step === "provider") {
|
|
1527
1527
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
|
|
1528
1528
|
/* @__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" }),
|
|
1529
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 navigate \xB7 Enter select \xB7 Esc cancel \xB7 Ctrl+C exit" }),
|
|
1530
1530
|
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
1531
|
i === selected ? "\u203A " : " ",
|
|
1532
1532
|
/* @__PURE__ */ jsx(Text, { bold: true, children: p.id.padEnd(28) }),
|
|
@@ -1551,7 +1551,7 @@ function ModelPicker({
|
|
|
1551
1551
|
pickedProviderId,
|
|
1552
1552
|
") \u2501\u2501"
|
|
1553
1553
|
] }),
|
|
1554
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 navigate \xB7 Enter select \xB7 Esc back \xB7 Ctrl
|
|
1554
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 navigate \xB7 Enter select \xB7 Esc back \xB7 Ctrl+C exit" }),
|
|
1555
1555
|
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
1556
|
i === selected ? "\u203A " : " ",
|
|
1557
1557
|
id
|
|
@@ -2050,6 +2050,11 @@ function oneLine(s, max) {
|
|
|
2050
2050
|
const collapsed = s.replace(/\s+/g, " ").trim();
|
|
2051
2051
|
return collapsed.length <= max ? collapsed : `${collapsed.slice(0, max - 1)}\u2026`;
|
|
2052
2052
|
}
|
|
2053
|
+
function selectedSlashCommandLine(picker) {
|
|
2054
|
+
if (!picker.open || picker.matches.length === 0) return null;
|
|
2055
|
+
const picked = picker.matches[picker.selected];
|
|
2056
|
+
return picked ? `/${picked.name}` : null;
|
|
2057
|
+
}
|
|
2053
2058
|
function reducer(state, action) {
|
|
2054
2059
|
switch (action.type) {
|
|
2055
2060
|
case "addEntry": {
|
|
@@ -2066,6 +2071,7 @@ function reducer(state, action) {
|
|
|
2066
2071
|
buffer: "",
|
|
2067
2072
|
cursor: 0,
|
|
2068
2073
|
placeholders: [],
|
|
2074
|
+
historyIndex: 0,
|
|
2069
2075
|
picker: { open: false, query: "", matches: [], selected: 0 },
|
|
2070
2076
|
slashPicker: { open: false, query: "", matches: [], selected: 0 }
|
|
2071
2077
|
};
|
|
@@ -2649,6 +2655,16 @@ function App({
|
|
|
2649
2655
|
const flushTimerRef = useRef(null);
|
|
2650
2656
|
const stateRef = useRef(state);
|
|
2651
2657
|
stateRef.current = state;
|
|
2658
|
+
const draftRef = useRef({ buffer: state.buffer, cursor: state.cursor });
|
|
2659
|
+
draftRef.current = { buffer: state.buffer, cursor: state.cursor };
|
|
2660
|
+
const setDraft = (buffer, cursor) => {
|
|
2661
|
+
draftRef.current = { buffer, cursor };
|
|
2662
|
+
dispatch({ type: "setBuffer", buffer, cursor });
|
|
2663
|
+
};
|
|
2664
|
+
const clearDraft = () => {
|
|
2665
|
+
draftRef.current = { buffer: "", cursor: 0 };
|
|
2666
|
+
dispatch({ type: "clearInput" });
|
|
2667
|
+
};
|
|
2652
2668
|
const startedAtRef = useRef(Date.now());
|
|
2653
2669
|
const [nowTick, setNowTick] = React.useState(Date.now());
|
|
2654
2670
|
useEffect(() => {
|
|
@@ -2838,7 +2854,7 @@ function App({
|
|
|
2838
2854
|
}).slice(0, 12).map(({ cmd, owner }) => ({
|
|
2839
2855
|
name: cmd.name,
|
|
2840
2856
|
description: cmd.description,
|
|
2841
|
-
argsHint:
|
|
2857
|
+
argsHint: cmd.argsHint,
|
|
2842
2858
|
isBuiltin: owner === "core"
|
|
2843
2859
|
}));
|
|
2844
2860
|
if (!state.slashPicker.open) {
|
|
@@ -2879,7 +2895,8 @@ function App({
|
|
|
2879
2895
|
if (!picked) return;
|
|
2880
2896
|
const builder = builderRef.current;
|
|
2881
2897
|
if (!builder) return;
|
|
2882
|
-
const
|
|
2898
|
+
const draft = draftRef.current;
|
|
2899
|
+
const tok = detectAtToken(draft.buffer, draft.cursor);
|
|
2883
2900
|
if (!tok) {
|
|
2884
2901
|
dispatch({ type: "pickerClose" });
|
|
2885
2902
|
return;
|
|
@@ -2892,14 +2909,10 @@ function App({
|
|
|
2892
2909
|
data,
|
|
2893
2910
|
meta: { filename: picked, label: picked }
|
|
2894
2911
|
});
|
|
2895
|
-
const before =
|
|
2896
|
-
const after =
|
|
2912
|
+
const before = draft.buffer.slice(0, tok.start);
|
|
2913
|
+
const after = draft.buffer.slice(tok.end);
|
|
2897
2914
|
const next = `${before}${placeholder}${after}`;
|
|
2898
|
-
|
|
2899
|
-
type: "setBuffer",
|
|
2900
|
-
buffer: next,
|
|
2901
|
-
cursor: tok.start + placeholder.length
|
|
2902
|
-
});
|
|
2915
|
+
setDraft(next, tok.start + placeholder.length);
|
|
2903
2916
|
dispatch({ type: "pickerClose" });
|
|
2904
2917
|
} catch (err) {
|
|
2905
2918
|
dispatch({
|
|
@@ -2918,7 +2931,7 @@ function App({
|
|
|
2918
2931
|
const picked = matches[selected];
|
|
2919
2932
|
if (!picked) return;
|
|
2920
2933
|
const cmd = picked.argsHint !== void 0 ? `/${picked.name} ` : `/${picked.name}`;
|
|
2921
|
-
|
|
2934
|
+
setDraft(cmd, cmd.length);
|
|
2922
2935
|
dispatch({ type: "slashPickerClose" });
|
|
2923
2936
|
};
|
|
2924
2937
|
useEffect(() => {
|
|
@@ -3461,8 +3474,9 @@ function App({
|
|
|
3461
3474
|
}, [director]);
|
|
3462
3475
|
useEffect(() => {
|
|
3463
3476
|
const onSigint = () => {
|
|
3464
|
-
|
|
3465
|
-
|
|
3477
|
+
const current = stateRef.current;
|
|
3478
|
+
if (current.interrupts >= 1) {
|
|
3479
|
+
if (current.interrupts >= 2) {
|
|
3466
3480
|
process.exit(130);
|
|
3467
3481
|
}
|
|
3468
3482
|
try {
|
|
@@ -3480,6 +3494,22 @@ function App({
|
|
|
3480
3494
|
return;
|
|
3481
3495
|
}
|
|
3482
3496
|
dispatch({ type: "interrupt" });
|
|
3497
|
+
if (current.modelPicker.open) {
|
|
3498
|
+
dispatch({ type: "modelPickerClose" });
|
|
3499
|
+
dispatch({
|
|
3500
|
+
type: "addEntry",
|
|
3501
|
+
entry: { kind: "warn", text: "Model picker cancelled." }
|
|
3502
|
+
});
|
|
3503
|
+
return;
|
|
3504
|
+
}
|
|
3505
|
+
if (current.slashPicker.open) {
|
|
3506
|
+
dispatch({ type: "slashPickerClose" });
|
|
3507
|
+
dispatch({
|
|
3508
|
+
type: "addEntry",
|
|
3509
|
+
entry: { kind: "warn", text: "Cancelled." }
|
|
3510
|
+
});
|
|
3511
|
+
return;
|
|
3512
|
+
}
|
|
3483
3513
|
if (activeCtrlRef.current) {
|
|
3484
3514
|
activeCtrlRef.current.abort();
|
|
3485
3515
|
dispatch({ type: "status", status: "aborting" });
|
|
@@ -3520,7 +3550,7 @@ function App({
|
|
|
3520
3550
|
return () => {
|
|
3521
3551
|
process.off("SIGINT", onSigint);
|
|
3522
3552
|
};
|
|
3523
|
-
}, [
|
|
3553
|
+
}, [exit, onExit, director]);
|
|
3524
3554
|
const handleKey = async (input, key) => {
|
|
3525
3555
|
if (state.status === "aborting") return;
|
|
3526
3556
|
if (inputGateRef.current) return;
|
|
@@ -3591,15 +3621,23 @@ function App({
|
|
|
3591
3621
|
return;
|
|
3592
3622
|
}
|
|
3593
3623
|
if (isEnter) {
|
|
3624
|
+
const now = Date.now();
|
|
3625
|
+
if (now - lastEnterAtRef.current < 50) return;
|
|
3626
|
+
lastEnterAtRef.current = now;
|
|
3594
3627
|
inputGateRef.current = true;
|
|
3595
|
-
|
|
3628
|
+
const line = selectedSlashCommandLine(state.slashPicker);
|
|
3629
|
+
if (line) {
|
|
3630
|
+
void submit(line);
|
|
3631
|
+
} else {
|
|
3632
|
+
acceptSlashPickerSelection();
|
|
3633
|
+
}
|
|
3596
3634
|
inputGateRef.current = false;
|
|
3597
3635
|
return;
|
|
3598
3636
|
}
|
|
3599
3637
|
if (key.tab && state.slashPicker.matches.length > 0) {
|
|
3600
3638
|
const sel = state.slashPicker.matches[state.slashPicker.selected];
|
|
3601
3639
|
if (sel) {
|
|
3602
|
-
|
|
3640
|
+
setDraft(`/${sel.name} `, sel.name.length + 2);
|
|
3603
3641
|
dispatch({ type: "slashPickerClose" });
|
|
3604
3642
|
}
|
|
3605
3643
|
return;
|
|
@@ -3675,64 +3713,60 @@ function App({
|
|
|
3675
3713
|
void submit();
|
|
3676
3714
|
return;
|
|
3677
3715
|
}
|
|
3716
|
+
const { buffer, cursor } = draftRef.current;
|
|
3678
3717
|
if (key.backspace || key.delete) {
|
|
3679
3718
|
if (key.ctrl) {
|
|
3680
|
-
const { cursor, buffer } = state;
|
|
3681
3719
|
if (key.backspace) {
|
|
3682
3720
|
if (cursor === 0) return;
|
|
3683
3721
|
const beforeCursor = buffer.slice(0, cursor);
|
|
3684
3722
|
const lastWordStart = beforeCursor.lastIndexOf(" ") + 1;
|
|
3685
3723
|
const next3 = buffer.slice(0, lastWordStart) + buffer.slice(cursor);
|
|
3686
|
-
|
|
3724
|
+
setDraft(next3, lastWordStart);
|
|
3687
3725
|
} else {
|
|
3688
3726
|
if (cursor >= buffer.length) return;
|
|
3689
3727
|
const afterCursor = buffer.slice(cursor);
|
|
3690
3728
|
const nextWordStart = afterCursor.indexOf(" ");
|
|
3691
3729
|
const end = nextWordStart === -1 ? buffer.length : cursor + nextWordStart + 1;
|
|
3692
3730
|
const next3 = buffer.slice(0, cursor) + buffer.slice(end);
|
|
3693
|
-
|
|
3731
|
+
setDraft(next3, cursor);
|
|
3694
3732
|
}
|
|
3695
3733
|
return;
|
|
3696
3734
|
}
|
|
3697
|
-
if (
|
|
3698
|
-
const next2 =
|
|
3699
|
-
|
|
3735
|
+
if (cursor === 0) return;
|
|
3736
|
+
const next2 = buffer.slice(0, cursor - 1) + buffer.slice(cursor);
|
|
3737
|
+
setDraft(next2, cursor - 1);
|
|
3700
3738
|
return;
|
|
3701
3739
|
}
|
|
3702
3740
|
if (key.leftArrow) {
|
|
3703
3741
|
if (key.ctrl) {
|
|
3704
|
-
const { cursor, buffer } = state;
|
|
3705
3742
|
if (cursor === 0) return;
|
|
3706
3743
|
const beforeCursor = buffer.slice(0, cursor);
|
|
3707
3744
|
const prevWordStart = beforeCursor.lastIndexOf(" ");
|
|
3708
3745
|
const target = prevWordStart === -1 ? 0 : prevWordStart + 1;
|
|
3709
|
-
|
|
3746
|
+
setDraft(buffer, target);
|
|
3710
3747
|
return;
|
|
3711
3748
|
}
|
|
3712
|
-
if (
|
|
3713
|
-
dispatch({ type: "setBuffer", buffer: state.buffer, cursor: state.cursor - 1 });
|
|
3749
|
+
if (cursor > 0) setDraft(buffer, cursor - 1);
|
|
3714
3750
|
return;
|
|
3715
3751
|
}
|
|
3716
3752
|
if (key.rightArrow) {
|
|
3717
3753
|
if (key.ctrl) {
|
|
3718
|
-
const { cursor, buffer } = state;
|
|
3719
3754
|
if (cursor >= buffer.length) return;
|
|
3720
3755
|
const afterCursor = buffer.slice(cursor);
|
|
3721
3756
|
const nextWordStart = afterCursor.indexOf(" ");
|
|
3722
3757
|
const target = nextWordStart === -1 ? buffer.length : cursor + nextWordStart + 1;
|
|
3723
|
-
|
|
3758
|
+
setDraft(buffer, target);
|
|
3724
3759
|
return;
|
|
3725
3760
|
}
|
|
3726
|
-
if (
|
|
3727
|
-
dispatch({ type: "setBuffer", buffer: state.buffer, cursor: state.cursor + 1 });
|
|
3761
|
+
if (cursor < buffer.length) setDraft(buffer, cursor + 1);
|
|
3728
3762
|
return;
|
|
3729
3763
|
}
|
|
3730
3764
|
if (key.home) {
|
|
3731
|
-
|
|
3765
|
+
setDraft(buffer, 0);
|
|
3732
3766
|
return;
|
|
3733
3767
|
}
|
|
3734
3768
|
if (key.end) {
|
|
3735
|
-
|
|
3769
|
+
setDraft(buffer, buffer.length);
|
|
3736
3770
|
return;
|
|
3737
3771
|
}
|
|
3738
3772
|
if (key.upArrow) {
|
|
@@ -3744,24 +3778,23 @@ function App({
|
|
|
3744
3778
|
return;
|
|
3745
3779
|
}
|
|
3746
3780
|
if (key.ctrl && input === "a") {
|
|
3747
|
-
|
|
3781
|
+
setDraft(buffer, 0);
|
|
3748
3782
|
return;
|
|
3749
3783
|
}
|
|
3750
3784
|
if (key.ctrl && input === "e") {
|
|
3751
|
-
|
|
3785
|
+
setDraft(buffer, buffer.length);
|
|
3752
3786
|
return;
|
|
3753
3787
|
}
|
|
3754
3788
|
if (key.ctrl && input === "u") {
|
|
3755
|
-
|
|
3789
|
+
setDraft("", 0);
|
|
3756
3790
|
return;
|
|
3757
3791
|
}
|
|
3758
3792
|
if (key.ctrl && input === "w") {
|
|
3759
|
-
const { cursor, buffer } = state;
|
|
3760
3793
|
if (cursor === 0) return;
|
|
3761
3794
|
const beforeCursor = buffer.slice(0, cursor);
|
|
3762
3795
|
const lastWordStart = beforeCursor.lastIndexOf(" ") + 1;
|
|
3763
3796
|
const next2 = buffer.slice(0, lastWordStart) + buffer.slice(cursor);
|
|
3764
|
-
|
|
3797
|
+
setDraft(next2, lastWordStart);
|
|
3765
3798
|
return;
|
|
3766
3799
|
}
|
|
3767
3800
|
if (key.meta && input === "v") {
|
|
@@ -3783,13 +3816,13 @@ function App({
|
|
|
3783
3816
|
const lineCount = cleanInput.split("\n").length;
|
|
3784
3817
|
dispatch({ type: "addPlaceholder", ph: `${ph} (${lineCount} lines)` });
|
|
3785
3818
|
} else {
|
|
3786
|
-
const next2 =
|
|
3787
|
-
|
|
3819
|
+
const next2 = buffer.slice(0, cursor) + cleanInput + buffer.slice(cursor);
|
|
3820
|
+
setDraft(next2, cursor + cleanInput.length);
|
|
3788
3821
|
}
|
|
3789
3822
|
return;
|
|
3790
3823
|
}
|
|
3791
|
-
const next =
|
|
3792
|
-
|
|
3824
|
+
const next = buffer.slice(0, cursor) + cleanInput + buffer.slice(cursor);
|
|
3825
|
+
setDraft(next, cursor + cleanInput.length);
|
|
3793
3826
|
};
|
|
3794
3827
|
const runBlocks = async (blocks) => {
|
|
3795
3828
|
const ctrl = new AbortController();
|
|
@@ -3871,21 +3904,24 @@ function App({
|
|
|
3871
3904
|
};
|
|
3872
3905
|
const runBlocksRef = useRef(runBlocks);
|
|
3873
3906
|
runBlocksRef.current = runBlocks;
|
|
3874
|
-
const submit = async () => {
|
|
3875
|
-
const raw =
|
|
3907
|
+
const submit = async (overrideRaw) => {
|
|
3908
|
+
const raw = overrideRaw ?? draftRef.current.buffer;
|
|
3876
3909
|
const trimmed = raw.trim();
|
|
3877
3910
|
if (!trimmed && state.placeholders.length === 0) return;
|
|
3878
3911
|
dispatch({ type: "resetInterrupts" });
|
|
3912
|
+
const pushSubmittedHistory = () => {
|
|
3913
|
+
if (trimmed) dispatch({ type: "historyPush", text: trimmed });
|
|
3914
|
+
};
|
|
3879
3915
|
if (trimmed === "/image" || trimmed === "/paste-image") {
|
|
3880
|
-
|
|
3916
|
+
pushSubmittedHistory();
|
|
3917
|
+
clearDraft();
|
|
3881
3918
|
await pasteClipboardImage();
|
|
3882
|
-
if (state.historyIndex > 0) dispatch({ type: "historyPush", text: trimmed });
|
|
3883
3919
|
return;
|
|
3884
3920
|
}
|
|
3885
3921
|
if (trimmed.startsWith("/")) {
|
|
3886
3922
|
dispatch({ type: "addEntry", entry: { kind: "user", text: trimmed } });
|
|
3887
|
-
|
|
3888
|
-
|
|
3923
|
+
pushSubmittedHistory();
|
|
3924
|
+
clearDraft();
|
|
3889
3925
|
try {
|
|
3890
3926
|
const res = await slashRegistry.dispatch(trimmed, agent.ctx);
|
|
3891
3927
|
if (res?.message) {
|
|
@@ -3931,20 +3967,19 @@ function App({
|
|
|
3931
3967
|
builder.appendText(toAppend);
|
|
3932
3968
|
}
|
|
3933
3969
|
if (steering) dispatch({ type: "steerConsume" });
|
|
3934
|
-
const blocks = await builder.submit();
|
|
3935
3970
|
const displayText = trimmed ? steering ? `\u21AF ${trimmed}` : trimmed : "(attachments only)";
|
|
3936
|
-
|
|
3971
|
+
pushSubmittedHistory();
|
|
3972
|
+
clearDraft();
|
|
3973
|
+
const blocks = await builder.submit();
|
|
3937
3974
|
if (state.status !== "idle") {
|
|
3938
3975
|
dispatch({
|
|
3939
3976
|
type: "addEntry",
|
|
3940
3977
|
entry: { kind: "user", text: displayText, queued: true }
|
|
3941
3978
|
});
|
|
3942
3979
|
dispatch({ type: "enqueue", item: { displayText, blocks } });
|
|
3943
|
-
if (state.historyIndex > 0) dispatch({ type: "historyPush", text: trimmed });
|
|
3944
3980
|
return;
|
|
3945
3981
|
}
|
|
3946
3982
|
dispatch({ type: "addEntry", entry: { kind: "user", text: displayText } });
|
|
3947
|
-
if (state.historyIndex > 0) dispatch({ type: "historyPush", text: trimmed });
|
|
3948
3983
|
await runBlocks(blocks);
|
|
3949
3984
|
};
|
|
3950
3985
|
const bootInjectedRef = useRef(false);
|