@wrongstack/tui 0.1.9 → 0.1.10
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 +1 -1
- package/dist/index.js +601 -464
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,13 +1,222 @@
|
|
|
1
|
-
import React, { useState, useReducer, useRef, useEffect, useMemo } from 'react';
|
|
2
1
|
import { render, useApp, Box, useStdout, Static, Text, useInput } from 'ink';
|
|
2
|
+
import React, { useState, useReducer, useRef, useEffect, useMemo } from 'react';
|
|
3
|
+
import * as fs from 'fs/promises';
|
|
3
4
|
import * as path3 from 'path';
|
|
4
|
-
import
|
|
5
|
-
import { InputBuilder } from '@wrongstack/core';
|
|
6
|
-
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
5
|
+
import { InputBuilder, formatTodosList } from '@wrongstack/core';
|
|
7
6
|
import { spawn } from 'child_process';
|
|
8
7
|
import * as os from 'os';
|
|
8
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
9
9
|
|
|
10
10
|
// src/run-tui.ts
|
|
11
|
+
var MAX_IMAGE_BYTES = 10 * 1024 * 1024;
|
|
12
|
+
async function readClipboardImage() {
|
|
13
|
+
const platform = process.platform;
|
|
14
|
+
if (platform === "win32") return readWindows();
|
|
15
|
+
if (platform === "darwin") return readDarwin();
|
|
16
|
+
if (platform === "linux") return readLinux();
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
async function readWindows() {
|
|
20
|
+
const tmp = path3.join(os.tmpdir(), `wstack-clip-${Date.now()}.png`);
|
|
21
|
+
const ps = [
|
|
22
|
+
"Add-Type -AssemblyName System.Windows.Forms",
|
|
23
|
+
"Add-Type -AssemblyName System.Drawing",
|
|
24
|
+
"$img = [System.Windows.Forms.Clipboard]::GetImage()",
|
|
25
|
+
'if ($img -eq $null) { Write-Output "NO_IMAGE"; exit 0 }',
|
|
26
|
+
`$img.Save('${tmp.replace(/\\/g, "\\\\")}', [System.Drawing.Imaging.ImageFormat]::Png)`,
|
|
27
|
+
'Write-Output "OK"'
|
|
28
|
+
].join("; ");
|
|
29
|
+
const out = await runCmd("powershell", ["-NoProfile", "-Command", ps]);
|
|
30
|
+
if (!out || out.trim() === "NO_IMAGE") return null;
|
|
31
|
+
if (!out.includes("OK")) return null;
|
|
32
|
+
return readPngFile(tmp);
|
|
33
|
+
}
|
|
34
|
+
async function readDarwin() {
|
|
35
|
+
const tmp = path3.join(os.tmpdir(), `wstack-clip-${Date.now()}.png`);
|
|
36
|
+
const script = [
|
|
37
|
+
"try",
|
|
38
|
+
` set the_file to (open for access POSIX file "${tmp}" with write permission)`,
|
|
39
|
+
" write (the clipboard as \xABclass PNGf\xBB) to the_file",
|
|
40
|
+
" close access the_file",
|
|
41
|
+
"on error",
|
|
42
|
+
" try",
|
|
43
|
+
' close access POSIX file "' + tmp + '"',
|
|
44
|
+
" end try",
|
|
45
|
+
' return "NO_IMAGE"',
|
|
46
|
+
"end try",
|
|
47
|
+
'return "OK"'
|
|
48
|
+
].join("\n");
|
|
49
|
+
const out = await runCmd("osascript", ["-e", script]);
|
|
50
|
+
if (!out || out.trim() !== "OK") return null;
|
|
51
|
+
return readPngFile(tmp);
|
|
52
|
+
}
|
|
53
|
+
async function readLinux() {
|
|
54
|
+
const tmp = path3.join(os.tmpdir(), `wstack-clip-${Date.now()}.png`);
|
|
55
|
+
const tries = [
|
|
56
|
+
["wl-paste", ["--type", "image/png"]],
|
|
57
|
+
["xclip", ["-selection", "clipboard", "-t", "image/png", "-o"]]
|
|
58
|
+
];
|
|
59
|
+
for (const [cmd, args] of tries) {
|
|
60
|
+
const ok = await runCmdToFile(cmd, args, tmp).catch(() => false);
|
|
61
|
+
if (ok) return readPngFile(tmp);
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
async function readPngFile(p) {
|
|
66
|
+
try {
|
|
67
|
+
const buf = await fs.readFile(p);
|
|
68
|
+
if (buf.length === 0) {
|
|
69
|
+
await fs.unlink(p).catch(() => void 0);
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
if (buf.length > MAX_IMAGE_BYTES) {
|
|
73
|
+
await fs.unlink(p).catch(() => void 0);
|
|
74
|
+
throw new Error(`Clipboard image exceeds ${MAX_IMAGE_BYTES / 1024 / 1024}MB limit`);
|
|
75
|
+
}
|
|
76
|
+
if (buf[0] !== 137 || buf[1] !== 80 || buf[2] !== 78 || buf[3] !== 71) {
|
|
77
|
+
await fs.unlink(p).catch(() => void 0);
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
await fs.unlink(p).catch(() => void 0);
|
|
81
|
+
return { base64: buf.toString("base64"), mediaType: "image/png", bytes: buf.length };
|
|
82
|
+
} catch (err) {
|
|
83
|
+
if (err.code === "ENOENT") return null;
|
|
84
|
+
throw err;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function runCmd(cmd, args) {
|
|
88
|
+
return new Promise((resolve) => {
|
|
89
|
+
const child = spawn(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
90
|
+
let out = "";
|
|
91
|
+
child.stdout.on("data", (c) => {
|
|
92
|
+
out += String(c);
|
|
93
|
+
});
|
|
94
|
+
child.on("error", () => resolve(null));
|
|
95
|
+
child.on("exit", (code) => resolve(code === 0 ? out : null));
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
function runCmdToFile(cmd, args, outPath) {
|
|
99
|
+
return new Promise((resolve) => {
|
|
100
|
+
const child = spawn(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
101
|
+
const chunks = [];
|
|
102
|
+
child.stdout.on("data", (c) => chunks.push(c));
|
|
103
|
+
child.on("error", () => resolve(false));
|
|
104
|
+
child.on("exit", async (code) => {
|
|
105
|
+
if (code !== 0 || chunks.length === 0) return resolve(false);
|
|
106
|
+
try {
|
|
107
|
+
await fs.writeFile(outPath, Buffer.concat(chunks));
|
|
108
|
+
resolve(true);
|
|
109
|
+
} catch {
|
|
110
|
+
resolve(false);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
function stringifyInput(input) {
|
|
116
|
+
if (!input || typeof input !== "object") return "";
|
|
117
|
+
const obj = input;
|
|
118
|
+
return Object.entries(obj).filter(([k]) => k !== "content" && k !== "new_string").map(([k, v]) => `${k}: ${truncate(JSON.stringify(v), 80)}`).join(" ");
|
|
119
|
+
}
|
|
120
|
+
function truncate(s, max) {
|
|
121
|
+
return s.length <= max ? s : `${s.slice(0, max - 1)}\u2026`;
|
|
122
|
+
}
|
|
123
|
+
function hasDiff(input) {
|
|
124
|
+
return Boolean(
|
|
125
|
+
input && typeof input === "object" && "diff" in input
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
function renderDiffLine(line) {
|
|
129
|
+
const prefix = line.startsWith("+") ? "green" : line.startsWith("-") ? "red" : line.startsWith("@@") ? "cyan" : void 0;
|
|
130
|
+
return /* @__PURE__ */ jsxs(Text, { color: prefix, children: [
|
|
131
|
+
line,
|
|
132
|
+
"\n"
|
|
133
|
+
] }, line);
|
|
134
|
+
}
|
|
135
|
+
function renderDiff(diff) {
|
|
136
|
+
const lines = diff.split("\n").filter((l) => l.length > 0).slice(0, 20);
|
|
137
|
+
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", paddingX: 2, children: lines.map((l) => renderDiffLine(l)) });
|
|
138
|
+
}
|
|
139
|
+
function ConfirmPrompt({
|
|
140
|
+
toolName,
|
|
141
|
+
input,
|
|
142
|
+
suggestedPattern,
|
|
143
|
+
onDecision
|
|
144
|
+
}) {
|
|
145
|
+
useInput((_, key) => {
|
|
146
|
+
if (key.return) {
|
|
147
|
+
onDecision("yes");
|
|
148
|
+
} else if (key.escape) {
|
|
149
|
+
onDecision("no");
|
|
150
|
+
} else if (key.ctrl && _.toLowerCase() === "a") {
|
|
151
|
+
onDecision("always");
|
|
152
|
+
} else if (key.ctrl && _.toLowerCase() === "d") {
|
|
153
|
+
onDecision("deny");
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
const inputSummary = stringifyInput(input);
|
|
157
|
+
const showDiff = hasDiff(input);
|
|
158
|
+
const inp = input;
|
|
159
|
+
const diff = typeof inp?.diff === "string" ? inp.diff : "";
|
|
160
|
+
return /* @__PURE__ */ jsxs(
|
|
161
|
+
Box,
|
|
162
|
+
{
|
|
163
|
+
flexDirection: "column",
|
|
164
|
+
borderStyle: "single",
|
|
165
|
+
borderTop: false,
|
|
166
|
+
borderLeft: false,
|
|
167
|
+
borderRight: false,
|
|
168
|
+
borderBottom: false,
|
|
169
|
+
paddingX: 1,
|
|
170
|
+
children: [
|
|
171
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
|
|
172
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "yellow", children: "\u26A0 Confirm" }),
|
|
173
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
174
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: toolName })
|
|
175
|
+
] }),
|
|
176
|
+
inputSummary ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: inputSummary }) : null,
|
|
177
|
+
showDiff && diff ? /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginY: 1, children: renderDiff(diff) }) : null,
|
|
178
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }),
|
|
179
|
+
/* @__PURE__ */ jsx(Box, { flexDirection: "row", children: /* @__PURE__ */ jsxs(Text, { children: [
|
|
180
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "green", children: "[\u21B5]" }),
|
|
181
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " yes " }),
|
|
182
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "red", children: "[Esc]" }),
|
|
183
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " no " }),
|
|
184
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "[Ctrl+A]" }),
|
|
185
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
186
|
+
" always (",
|
|
187
|
+
suggestedPattern,
|
|
188
|
+
") "
|
|
189
|
+
] }),
|
|
190
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "red", children: "[Ctrl+D]" }),
|
|
191
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " deny" })
|
|
192
|
+
] }) })
|
|
193
|
+
]
|
|
194
|
+
}
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
function FilePicker({ query, matches, selected }) {
|
|
198
|
+
if (matches.length === 0) {
|
|
199
|
+
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", paddingX: 1, children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
200
|
+
"@",
|
|
201
|
+
query,
|
|
202
|
+
" \u2014 no matches"
|
|
203
|
+
] }) });
|
|
204
|
+
}
|
|
205
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
|
|
206
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
207
|
+
"@",
|
|
208
|
+
query || "\u2026",
|
|
209
|
+
" \u2014 \u2191/\u2193 select, Enter attach, Esc cancel"
|
|
210
|
+
] }),
|
|
211
|
+
matches.map((m, i) => /* @__PURE__ */ jsxs(Text, { color: i === selected ? "cyan" : void 0, inverse: i === selected, children: [
|
|
212
|
+
i === selected ? "\u203A " : " ",
|
|
213
|
+
highlight(m)
|
|
214
|
+
] }, m))
|
|
215
|
+
] });
|
|
216
|
+
}
|
|
217
|
+
function highlight(path4, query) {
|
|
218
|
+
return path4;
|
|
219
|
+
}
|
|
11
220
|
|
|
12
221
|
// src/markdown-table.ts
|
|
13
222
|
function renderMarkdownTables(text, maxWidth) {
|
|
@@ -198,13 +407,53 @@ function History({ entries, streamingText, toolStream }) {
|
|
|
198
407
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "> " }),
|
|
199
408
|
/* @__PURE__ */ jsx(Text, { children: tail })
|
|
200
409
|
] }) : null,
|
|
201
|
-
toolTail ? /* @__PURE__ */
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
410
|
+
toolTail ? /* @__PURE__ */ jsx(
|
|
411
|
+
ToolStreamBox,
|
|
412
|
+
{
|
|
413
|
+
name: toolStream.name,
|
|
414
|
+
text: toolTail,
|
|
415
|
+
startedAt: toolStream.startedAt,
|
|
416
|
+
termWidth
|
|
417
|
+
}
|
|
418
|
+
) : null
|
|
205
419
|
] });
|
|
206
420
|
}
|
|
207
421
|
var MAX_STREAM_DISPLAY_CHARS = 480;
|
|
422
|
+
var MAX_STREAM_LINES = 8;
|
|
423
|
+
function ToolStreamBox({
|
|
424
|
+
name,
|
|
425
|
+
text,
|
|
426
|
+
startedAt,
|
|
427
|
+
termWidth
|
|
428
|
+
}) {
|
|
429
|
+
const [tick, setTick] = useState(0);
|
|
430
|
+
useEffect(() => {
|
|
431
|
+
const t = setInterval(() => setTick((n) => n + 1), 500);
|
|
432
|
+
return () => clearInterval(t);
|
|
433
|
+
}, []);
|
|
434
|
+
const elapsedMs = Date.now() - startedAt;
|
|
435
|
+
const lines = text.split("\n");
|
|
436
|
+
const totalLines = lines.length;
|
|
437
|
+
const hidden = Math.max(0, totalLines - MAX_STREAM_LINES);
|
|
438
|
+
const visible = hidden > 0 ? lines.slice(hidden) : lines;
|
|
439
|
+
const contentWidth = Math.max(20, Math.min(termWidth - 4, 100));
|
|
440
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 0, children: [
|
|
441
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
|
|
442
|
+
/* @__PURE__ */ jsx(Text, { color: "yellow", children: "\u25C6 " }),
|
|
443
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: name }),
|
|
444
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u23F1 ${fmtDuration(elapsedMs)}` }),
|
|
445
|
+
hidden > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` (${totalLines} lines, showing last ${MAX_STREAM_LINES})` }) : null
|
|
446
|
+
] }),
|
|
447
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [
|
|
448
|
+
hidden > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, italic: true, children: ` \u2026 ${hidden} more line${hidden === 1 ? "" : "s"} above` }) : null,
|
|
449
|
+
visible.map((line, i) => {
|
|
450
|
+
const key = i;
|
|
451
|
+
const trimmed = line.length > contentWidth ? `${line.slice(0, contentWidth - 1)}\u2026` : line;
|
|
452
|
+
return /* @__PURE__ */ jsx(Text, { dimColor: true, children: trimmed || " " }, key);
|
|
453
|
+
})
|
|
454
|
+
] })
|
|
455
|
+
] });
|
|
456
|
+
}
|
|
208
457
|
function tailForDisplay(text, maxChars) {
|
|
209
458
|
if (text.length <= maxChars) return text;
|
|
210
459
|
const cut = text.length - maxChars;
|
|
@@ -234,7 +483,10 @@ function DiffBlock({ rows, hidden }) {
|
|
|
234
483
|
hidden > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, italic: true, children: ` \u2026 ${hidden} more line${hidden === 1 ? "" : "s"}` }) : null
|
|
235
484
|
] });
|
|
236
485
|
}
|
|
237
|
-
function Entry({
|
|
486
|
+
function Entry({
|
|
487
|
+
entry,
|
|
488
|
+
termWidth
|
|
489
|
+
}) {
|
|
238
490
|
switch (entry.kind) {
|
|
239
491
|
case "user":
|
|
240
492
|
return /* @__PURE__ */ jsxs(Text, { children: [
|
|
@@ -247,8 +499,28 @@ function Entry({ entry, termWidth }) {
|
|
|
247
499
|
return /* @__PURE__ */ jsx(Text, { children: renderMarkdownTables(entry.text, termWidth) });
|
|
248
500
|
case "tool": {
|
|
249
501
|
const argSummary = formatToolArgs(entry.name, entry.input);
|
|
250
|
-
const outLines = formatToolOutput(
|
|
502
|
+
const outLines = formatToolOutput(
|
|
503
|
+
entry.name,
|
|
504
|
+
entry.output,
|
|
505
|
+
entry.ok,
|
|
506
|
+
entry.outputBytes,
|
|
507
|
+
entry.outputLines
|
|
508
|
+
);
|
|
251
509
|
const diff = entry.ok ? extractDiffPreview(entry.name, entry.output) : void 0;
|
|
510
|
+
const sizeChip = (() => {
|
|
511
|
+
if (!entry.ok) return "";
|
|
512
|
+
const parts = [];
|
|
513
|
+
if (entry.outputLines !== void 0 && entry.outputLines > 0) {
|
|
514
|
+
parts.push(`${entry.outputLines} L`);
|
|
515
|
+
}
|
|
516
|
+
if (entry.outputBytes && entry.outputBytes > 0) {
|
|
517
|
+
parts.push(fmtBytes(entry.outputBytes));
|
|
518
|
+
}
|
|
519
|
+
if (entry.outputTokens && entry.outputTokens > 0) {
|
|
520
|
+
parts.push(`\u2248${fmtTok(entry.outputTokens)} tok`);
|
|
521
|
+
}
|
|
522
|
+
return parts.join(" \xB7 ");
|
|
523
|
+
})();
|
|
252
524
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
253
525
|
/* @__PURE__ */ jsxs(Text, { children: [
|
|
254
526
|
/* @__PURE__ */ jsx(Text, { color: entry.ok ? "green" : "red", children: entry.ok ? "\u25CF" : "\u2717" }),
|
|
@@ -258,13 +530,21 @@ function Entry({ entry, termWidth }) {
|
|
|
258
530
|
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
259
531
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: argSummary })
|
|
260
532
|
] }) : null,
|
|
261
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \xB7 ${fmtDuration(entry.durationMs)}` })
|
|
533
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \xB7 ${fmtDuration(entry.durationMs)}` }),
|
|
534
|
+
sizeChip ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \xB7 ${sizeChip}` }) : null
|
|
262
535
|
] }),
|
|
263
536
|
outLines.map((line, i) => (
|
|
264
537
|
// biome-ignore lint/suspicious/noArrayIndexKey: tool output lines are static, index is stable
|
|
265
538
|
/* @__PURE__ */ jsxs(Text, { children: [
|
|
266
539
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: i === outLines.length - 1 && !diff ? " \u2514\u2500 " : " \u251C\u2500 " }),
|
|
267
|
-
/* @__PURE__ */ jsx(
|
|
540
|
+
/* @__PURE__ */ jsx(
|
|
541
|
+
Text,
|
|
542
|
+
{
|
|
543
|
+
color: !entry.ok || line.startsWith("!") ? "red" : void 0,
|
|
544
|
+
dimColor: entry.ok && !line.startsWith("!"),
|
|
545
|
+
children: line
|
|
546
|
+
}
|
|
547
|
+
)
|
|
268
548
|
] }, i)
|
|
269
549
|
)),
|
|
270
550
|
diff ? /* @__PURE__ */ jsx(DiffBlock, { rows: diff.rows, hidden: diff.hidden }) : null
|
|
@@ -279,29 +559,41 @@ function Entry({ entry, termWidth }) {
|
|
|
279
559
|
case "turn-summary":
|
|
280
560
|
return /* @__PURE__ */ jsx(Text, { dimColor: true, children: entry.text });
|
|
281
561
|
case "confirm":
|
|
282
|
-
return /* @__PURE__ */ jsxs(
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
entry.
|
|
299
|
-
")
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
562
|
+
return /* @__PURE__ */ jsxs(
|
|
563
|
+
Box,
|
|
564
|
+
{
|
|
565
|
+
flexDirection: "column",
|
|
566
|
+
borderStyle: "single",
|
|
567
|
+
borderTop: false,
|
|
568
|
+
borderLeft: false,
|
|
569
|
+
borderRight: false,
|
|
570
|
+
borderBottom: false,
|
|
571
|
+
paddingX: 1,
|
|
572
|
+
children: [
|
|
573
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
|
|
574
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "yellow", children: "\u26A0 Confirm" }),
|
|
575
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
576
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: entry.toolName })
|
|
577
|
+
] }),
|
|
578
|
+
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,
|
|
579
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }),
|
|
580
|
+
/* @__PURE__ */ jsx(Box, { flexDirection: "row", children: /* @__PURE__ */ jsxs(Text, { children: [
|
|
581
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "green", children: "[\u21B5]" }),
|
|
582
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " yes " }),
|
|
583
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "red", children: "[Esc]" }),
|
|
584
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " no " }),
|
|
585
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "[Ctrl+A]" }),
|
|
586
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
587
|
+
" always (",
|
|
588
|
+
entry.suggestedPattern,
|
|
589
|
+
") "
|
|
590
|
+
] }),
|
|
591
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "red", children: "[Ctrl+D]" }),
|
|
592
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " deny" })
|
|
593
|
+
] }) })
|
|
594
|
+
]
|
|
595
|
+
}
|
|
596
|
+
);
|
|
305
597
|
case "banner":
|
|
306
598
|
return /* @__PURE__ */ jsx(Banner, { entry });
|
|
307
599
|
}
|
|
@@ -310,55 +602,51 @@ function Banner({
|
|
|
310
602
|
entry
|
|
311
603
|
}) {
|
|
312
604
|
const cwdShort = shortenPath(entry.cwd, 48);
|
|
313
|
-
return /* @__PURE__ */ jsxs(
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
/* @__PURE__ */ jsx(Text, { color: "cyan", children: " cwd " }),
|
|
348
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: cwdShort })
|
|
349
|
-
] }),
|
|
350
|
-
/* @__PURE__ */ jsxs(Text, { children: [
|
|
351
|
-
/* @__PURE__ */ jsx(Text, { color: "cyan", children: " hints " }),
|
|
352
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "/help \xB7 /init \xB7 /memory \xB7 /queue \xB7 /exit" })
|
|
353
|
-
] })
|
|
354
|
-
]
|
|
355
|
-
}
|
|
356
|
-
);
|
|
605
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", paddingX: 2, paddingY: 0, children: [
|
|
606
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
607
|
+
/* @__PURE__ */ jsx(Text, { color: "magenta", bold: true, children: " \u259F\u259B " }),
|
|
608
|
+
/* @__PURE__ */ jsx(Text, { color: "magenta", bold: true, children: "WrongStack" }),
|
|
609
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " v" }),
|
|
610
|
+
/* @__PURE__ */ jsx(Text, { children: entry.version })
|
|
611
|
+
] }),
|
|
612
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, italic: true, children: " Built on the wrong stack. Shipped anyway." }),
|
|
613
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
614
|
+
/* @__PURE__ */ jsx(Text, { color: "cyan", children: " provider " }),
|
|
615
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
616
|
+
entry.provider,
|
|
617
|
+
"/",
|
|
618
|
+
entry.model
|
|
619
|
+
] })
|
|
620
|
+
] }),
|
|
621
|
+
entry.family ? /* @__PURE__ */ jsxs(Text, { children: [
|
|
622
|
+
/* @__PURE__ */ jsx(Text, { color: "cyan", children: " family " }),
|
|
623
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: entry.family })
|
|
624
|
+
] }) : null,
|
|
625
|
+
entry.keyTail ? /* @__PURE__ */ jsxs(Text, { children: [
|
|
626
|
+
/* @__PURE__ */ jsx(Text, { color: "cyan", children: " key " }),
|
|
627
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u25CF\u25CF\u25CF\u2026" }),
|
|
628
|
+
/* @__PURE__ */ jsx(Text, { children: entry.keyTail })
|
|
629
|
+
] }) : null,
|
|
630
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
631
|
+
/* @__PURE__ */ jsx(Text, { color: "cyan", children: " cwd " }),
|
|
632
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: cwdShort })
|
|
633
|
+
] }),
|
|
634
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
635
|
+
/* @__PURE__ */ jsx(Text, { color: "cyan", children: " hints " }),
|
|
636
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "/help \xB7 /init \xB7 /memory \xB7 /queue \xB7 /exit" })
|
|
637
|
+
] })
|
|
638
|
+
] });
|
|
357
639
|
}
|
|
358
640
|
function shortenPath(p, max) {
|
|
359
641
|
if (p.length <= max) return p;
|
|
360
642
|
return `\u2026${p.slice(p.length - (max - 1))}`;
|
|
361
643
|
}
|
|
644
|
+
function fmtTok(n) {
|
|
645
|
+
if (!Number.isFinite(n) || n <= 0) return "0";
|
|
646
|
+
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
647
|
+
if (n >= 1e3) return `${(n / 1e3).toFixed(n >= 1e4 ? 0 : 1)}k`;
|
|
648
|
+
return String(n);
|
|
649
|
+
}
|
|
362
650
|
function fmtDuration(ms) {
|
|
363
651
|
if (ms < 1e3) return `${ms}ms`;
|
|
364
652
|
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
|
|
@@ -476,7 +764,7 @@ function formatToolArgs(toolName, input) {
|
|
|
476
764
|
return "";
|
|
477
765
|
}
|
|
478
766
|
}
|
|
479
|
-
function formatToolOutput(toolName, output, ok) {
|
|
767
|
+
function formatToolOutput(toolName, output, ok, _outputBytes, outputLines) {
|
|
480
768
|
if (!output) return ok ? [] : ["failed"];
|
|
481
769
|
const text = output.trim();
|
|
482
770
|
if (!text) return ok ? [] : ["failed"];
|
|
@@ -541,12 +829,14 @@ function formatToolOutput(toolName, output, ok) {
|
|
|
541
829
|
if (!diff) return [files && files.length === 0 ? "no changes" : "empty diff"];
|
|
542
830
|
const head = [];
|
|
543
831
|
if (mode) head.push(mode);
|
|
544
|
-
if (files && files.length > 0)
|
|
832
|
+
if (files && files.length > 0)
|
|
833
|
+
head.push(`${files.length} file${files.length === 1 ? "" : "s"}`);
|
|
545
834
|
if (truncated) head.push("truncated");
|
|
546
835
|
return head.length > 0 ? [head.join(" \xB7 ")] : [];
|
|
547
836
|
}
|
|
548
837
|
}
|
|
549
838
|
if (toolName === "read") {
|
|
839
|
+
if (outputLines !== void 0) return [];
|
|
550
840
|
if (json && typeof json === "object") {
|
|
551
841
|
const o = json;
|
|
552
842
|
const bytes = numOf(o["bytes"]);
|
|
@@ -983,6 +1273,72 @@ function Input({
|
|
|
983
1273
|
hint ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: hint }) : null
|
|
984
1274
|
] });
|
|
985
1275
|
}
|
|
1276
|
+
function ModelPicker({
|
|
1277
|
+
step,
|
|
1278
|
+
providerOptions,
|
|
1279
|
+
modelOptions,
|
|
1280
|
+
selected,
|
|
1281
|
+
pickedProviderId,
|
|
1282
|
+
hint
|
|
1283
|
+
}) {
|
|
1284
|
+
if (step === "provider") {
|
|
1285
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
|
|
1286
|
+
/* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "\u2501\u2501 Switch model \u2014 Step 1/2: Pick provider \u2501\u2501" }),
|
|
1287
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 navigate \xB7 Enter select \xB7 Esc cancel" }),
|
|
1288
|
+
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: [
|
|
1289
|
+
i === selected ? "\u203A " : " ",
|
|
1290
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: p.id.padEnd(28) }),
|
|
1291
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1292
|
+
" [",
|
|
1293
|
+
p.family,
|
|
1294
|
+
"]"
|
|
1295
|
+
] }),
|
|
1296
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1297
|
+
" ",
|
|
1298
|
+
p.models.length,
|
|
1299
|
+
" model",
|
|
1300
|
+
p.models.length === 1 ? "" : "s"
|
|
1301
|
+
] })
|
|
1302
|
+
] }, p.id)),
|
|
1303
|
+
hint ? /* @__PURE__ */ jsx(Text, { color: "yellow", children: hint }) : null
|
|
1304
|
+
] });
|
|
1305
|
+
}
|
|
1306
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
|
|
1307
|
+
/* @__PURE__ */ jsxs(Text, { color: "cyan", bold: true, children: [
|
|
1308
|
+
"\u2501\u2501 Switch model \u2014 Step 2/2: Pick model (",
|
|
1309
|
+
pickedProviderId,
|
|
1310
|
+
") \u2501\u2501"
|
|
1311
|
+
] }),
|
|
1312
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 navigate \xB7 Enter select \xB7 Esc back \xB7 Ctrl-C cancel" }),
|
|
1313
|
+
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: [
|
|
1314
|
+
i === selected ? "\u203A " : " ",
|
|
1315
|
+
id
|
|
1316
|
+
] }, id)),
|
|
1317
|
+
hint ? /* @__PURE__ */ jsx(Text, { color: "yellow", children: hint }) : null
|
|
1318
|
+
] });
|
|
1319
|
+
}
|
|
1320
|
+
function SlashMenu({ query, matches, selected }) {
|
|
1321
|
+
const placeholder = query ? `/${query}` : "/";
|
|
1322
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
|
|
1323
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1324
|
+
placeholder || "/",
|
|
1325
|
+
" \u2014 \u2191/\u2193 select, Enter dispatch, Tab autocomplete, Esc close"
|
|
1326
|
+
] }),
|
|
1327
|
+
matches.map((m, i) => /* @__PURE__ */ jsxs(Text, { color: i === selected ? "cyan" : void 0, inverse: i === selected, children: [
|
|
1328
|
+
i === selected ? "\u203A " : " ",
|
|
1329
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: m.name }),
|
|
1330
|
+
m.argsHint ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1331
|
+
" ",
|
|
1332
|
+
m.argsHint
|
|
1333
|
+
] }) : null,
|
|
1334
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1335
|
+
" \u2014 ",
|
|
1336
|
+
m.description
|
|
1337
|
+
] })
|
|
1338
|
+
] }, m.name)),
|
|
1339
|
+
matches.length === 0 && /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No matching commands" })
|
|
1340
|
+
] });
|
|
1341
|
+
}
|
|
986
1342
|
function StatusBar({
|
|
987
1343
|
model,
|
|
988
1344
|
state,
|
|
@@ -1028,11 +1384,12 @@ function StatusBar({
|
|
|
1028
1384
|
usage ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1029
1385
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
|
|
1030
1386
|
/* @__PURE__ */ jsxs(Text, { children: [
|
|
1031
|
-
"\u2191
|
|
1032
|
-
/* @__PURE__ */ jsx(Text, { color: "cyan", children: fmtTok(usage.input) }),
|
|
1033
|
-
" \u2193",
|
|
1387
|
+
"\u2191",
|
|
1034
1388
|
" ",
|
|
1035
|
-
/* @__PURE__ */ jsx(Text, { color: "cyan", children:
|
|
1389
|
+
/* @__PURE__ */ jsx(Text, { color: "cyan", children: fmtTok2(usage.input + (usage.cacheRead ?? 0) + (usage.cacheWrite ?? 0)) }),
|
|
1390
|
+
" ",
|
|
1391
|
+
"\u2193 ",
|
|
1392
|
+
/* @__PURE__ */ jsx(Text, { color: "cyan", children: fmtTok2(usage.output) })
|
|
1036
1393
|
] })
|
|
1037
1394
|
] }) : null,
|
|
1038
1395
|
cache2 && cache2.hitRatio > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
@@ -1134,208 +1491,52 @@ function StatusBar({
|
|
|
1134
1491
|
}
|
|
1135
1492
|
function ContextChip({ ctx }) {
|
|
1136
1493
|
const ratio = Math.max(0, Math.min(1, ctx.used / ctx.max));
|
|
1137
|
-
const pct = Math.round(ratio * 100);
|
|
1138
|
-
const color = ratio >= 0.85 ? "red" : ratio >= 0.65 ? "yellow" : "cyan";
|
|
1139
|
-
return /* @__PURE__ */ jsxs(Text, { children: [
|
|
1140
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "ctx " }),
|
|
1141
|
-
/* @__PURE__ */ jsx(Text, { color, children: renderProgress(ratio, 10) }),
|
|
1142
|
-
/* @__PURE__ */ jsxs(Text, { color, children: [
|
|
1143
|
-
" ",
|
|
1144
|
-
pct,
|
|
1145
|
-
"%"
|
|
1146
|
-
] }),
|
|
1147
|
-
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1148
|
-
" ",
|
|
1149
|
-
"(",
|
|
1150
|
-
fmtTok(ctx.used),
|
|
1151
|
-
"/",
|
|
1152
|
-
fmtTok(ctx.max),
|
|
1153
|
-
")"
|
|
1154
|
-
] })
|
|
1155
|
-
] });
|
|
1156
|
-
}
|
|
1157
|
-
var FILLED = "\u2588";
|
|
1158
|
-
var EMPTY = "\u2591";
|
|
1159
|
-
function renderProgress(ratio, width) {
|
|
1160
|
-
const clamped = Math.max(0, Math.min(1, ratio));
|
|
1161
|
-
const filled = clamped === 0 ? 0 : Math.max(1, Math.round(clamped * width));
|
|
1162
|
-
const capped = Math.min(width, filled);
|
|
1163
|
-
return FILLED.repeat(capped) + EMPTY.repeat(width - capped);
|
|
1164
|
-
}
|
|
1165
|
-
function fmtTok(n) {
|
|
1166
|
-
if (n < 1e3) return String(n);
|
|
1167
|
-
if (n < 1e6) return `${(n / 1e3).toFixed(n < 1e4 ? 1 : 0)}k`;
|
|
1168
|
-
return `${(n / 1e6).toFixed(1)}M`;
|
|
1169
|
-
}
|
|
1170
|
-
function fmtElapsed(ms) {
|
|
1171
|
-
const totalSec = Math.floor(ms / 1e3);
|
|
1172
|
-
const h = Math.floor(totalSec / 3600);
|
|
1173
|
-
const m = Math.floor(totalSec % 3600 / 60);
|
|
1174
|
-
const s = totalSec % 60;
|
|
1175
|
-
if (h > 0) {
|
|
1176
|
-
return `${h}:${pad2(m)}:${pad2(s)}`;
|
|
1177
|
-
}
|
|
1178
|
-
return `${pad2(m)}:${pad2(s)}`;
|
|
1179
|
-
}
|
|
1180
|
-
function pad2(n) {
|
|
1181
|
-
return n < 10 ? `0${n}` : String(n);
|
|
1182
|
-
}
|
|
1183
|
-
function FilePicker({ query, matches, selected }) {
|
|
1184
|
-
if (matches.length === 0) {
|
|
1185
|
-
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", paddingX: 1, children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1186
|
-
"@",
|
|
1187
|
-
query,
|
|
1188
|
-
" \u2014 no matches"
|
|
1189
|
-
] }) });
|
|
1190
|
-
}
|
|
1191
|
-
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
|
|
1192
|
-
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1193
|
-
"@",
|
|
1194
|
-
query || "\u2026",
|
|
1195
|
-
" \u2014 \u2191/\u2193 select, Enter attach, Esc cancel"
|
|
1196
|
-
] }),
|
|
1197
|
-
matches.map((m, i) => /* @__PURE__ */ jsxs(Text, { color: i === selected ? "cyan" : void 0, inverse: i === selected, children: [
|
|
1198
|
-
i === selected ? "\u203A " : " ",
|
|
1199
|
-
highlight(m)
|
|
1200
|
-
] }, m))
|
|
1201
|
-
] });
|
|
1202
|
-
}
|
|
1203
|
-
function highlight(path4, query) {
|
|
1204
|
-
return path4;
|
|
1205
|
-
}
|
|
1206
|
-
function SlashMenu({ query, matches, selected }) {
|
|
1207
|
-
const placeholder = query ? `/${query}` : "/";
|
|
1208
|
-
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
|
|
1209
|
-
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1210
|
-
placeholder || "/",
|
|
1211
|
-
" \u2014 \u2191/\u2193 select, Enter dispatch, Tab autocomplete, Esc close"
|
|
1212
|
-
] }),
|
|
1213
|
-
matches.map((m, i) => /* @__PURE__ */ jsxs(Text, { color: i === selected ? "cyan" : void 0, inverse: i === selected, children: [
|
|
1214
|
-
i === selected ? "\u203A " : " ",
|
|
1215
|
-
/* @__PURE__ */ jsx(Text, { bold: true, children: m.name }),
|
|
1216
|
-
m.argsHint ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1217
|
-
" ",
|
|
1218
|
-
m.argsHint
|
|
1219
|
-
] }) : null,
|
|
1220
|
-
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1221
|
-
" \u2014 ",
|
|
1222
|
-
m.description
|
|
1223
|
-
] })
|
|
1224
|
-
] }, m.name)),
|
|
1225
|
-
matches.length === 0 && /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No matching commands" })
|
|
1226
|
-
] });
|
|
1227
|
-
}
|
|
1228
|
-
function ModelPicker({
|
|
1229
|
-
step,
|
|
1230
|
-
providerOptions,
|
|
1231
|
-
modelOptions,
|
|
1232
|
-
selected,
|
|
1233
|
-
pickedProviderId,
|
|
1234
|
-
hint
|
|
1235
|
-
}) {
|
|
1236
|
-
if (step === "provider") {
|
|
1237
|
-
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
|
|
1238
|
-
/* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "\u2501\u2501 Switch model \u2014 Step 1/2: Pick provider \u2501\u2501" }),
|
|
1239
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 navigate \xB7 Enter select \xB7 Esc cancel" }),
|
|
1240
|
-
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: [
|
|
1241
|
-
i === selected ? "\u203A " : " ",
|
|
1242
|
-
/* @__PURE__ */ jsx(Text, { bold: true, children: p.id.padEnd(28) }),
|
|
1243
|
-
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1244
|
-
" [",
|
|
1245
|
-
p.family,
|
|
1246
|
-
"]"
|
|
1247
|
-
] }),
|
|
1248
|
-
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1249
|
-
" ",
|
|
1250
|
-
p.models.length,
|
|
1251
|
-
" model",
|
|
1252
|
-
p.models.length === 1 ? "" : "s"
|
|
1253
|
-
] })
|
|
1254
|
-
] }, p.id)),
|
|
1255
|
-
hint ? /* @__PURE__ */ jsx(Text, { color: "yellow", children: hint }) : null
|
|
1256
|
-
] });
|
|
1257
|
-
}
|
|
1258
|
-
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
|
|
1259
|
-
/* @__PURE__ */ jsxs(Text, { color: "cyan", bold: true, children: [
|
|
1260
|
-
"\u2501\u2501 Switch model \u2014 Step 2/2: Pick model",
|
|
1261
|
-
" ",
|
|
1262
|
-
"(",
|
|
1263
|
-
pickedProviderId,
|
|
1264
|
-
") \u2501\u2501"
|
|
1265
|
-
] }),
|
|
1266
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 navigate \xB7 Enter select \xB7 Esc back \xB7 Ctrl-C cancel" }),
|
|
1267
|
-
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: [
|
|
1268
|
-
i === selected ? "\u203A " : " ",
|
|
1269
|
-
id
|
|
1270
|
-
] }, id)),
|
|
1271
|
-
hint ? /* @__PURE__ */ jsx(Text, { color: "yellow", children: hint }) : null
|
|
1272
|
-
] });
|
|
1273
|
-
}
|
|
1274
|
-
function stringifyInput(input) {
|
|
1275
|
-
if (!input || typeof input !== "object") return "";
|
|
1276
|
-
const obj = input;
|
|
1277
|
-
return Object.entries(obj).filter(([k]) => k !== "content" && k !== "new_string").map(([k, v]) => `${k}: ${truncate(JSON.stringify(v), 80)}`).join(" ");
|
|
1278
|
-
}
|
|
1279
|
-
function truncate(s, max) {
|
|
1280
|
-
return s.length <= max ? s : `${s.slice(0, max - 1)}\u2026`;
|
|
1281
|
-
}
|
|
1282
|
-
function hasDiff(input) {
|
|
1283
|
-
return Boolean(
|
|
1284
|
-
input && typeof input === "object" && "diff" in input
|
|
1285
|
-
);
|
|
1286
|
-
}
|
|
1287
|
-
function renderDiffLine(line) {
|
|
1288
|
-
const prefix = line.startsWith("+") ? "green" : line.startsWith("-") ? "red" : line.startsWith("@@") ? "cyan" : void 0;
|
|
1289
|
-
return /* @__PURE__ */ jsxs(Text, { color: prefix, children: [
|
|
1290
|
-
line,
|
|
1291
|
-
"\n"
|
|
1292
|
-
] }, line);
|
|
1293
|
-
}
|
|
1294
|
-
function renderDiff(diff) {
|
|
1295
|
-
const lines = diff.split("\n").filter((l) => l.length > 0).slice(0, 20);
|
|
1296
|
-
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", paddingX: 2, children: lines.map((l) => renderDiffLine(l)) });
|
|
1297
|
-
}
|
|
1298
|
-
function ConfirmPrompt({ toolName, input, suggestedPattern, onDecision }) {
|
|
1299
|
-
useInput((_, key) => {
|
|
1300
|
-
if (key.return) {
|
|
1301
|
-
onDecision("yes");
|
|
1302
|
-
} else if (key.escape) {
|
|
1303
|
-
onDecision("no");
|
|
1304
|
-
} else if (key.ctrl && _.toLowerCase() === "a") {
|
|
1305
|
-
onDecision("always");
|
|
1306
|
-
} else if (key.ctrl && _.toLowerCase() === "d") {
|
|
1307
|
-
onDecision("deny");
|
|
1308
|
-
}
|
|
1309
|
-
});
|
|
1310
|
-
const inputSummary = stringifyInput(input);
|
|
1311
|
-
const showDiff = hasDiff(input);
|
|
1312
|
-
const inp = input;
|
|
1313
|
-
const diff = typeof inp?.diff === "string" ? inp.diff : "";
|
|
1314
|
-
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "single", borderTop: false, borderLeft: false, borderRight: false, borderBottom: false, paddingX: 1, children: [
|
|
1315
|
-
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
|
|
1316
|
-
/* @__PURE__ */ jsx(Text, { bold: true, color: "yellow", children: "\u26A0 Confirm" }),
|
|
1317
|
-
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
1318
|
-
/* @__PURE__ */ jsx(Text, { bold: true, children: toolName })
|
|
1494
|
+
const pct = Math.round(ratio * 100);
|
|
1495
|
+
const color = ratio >= 0.85 ? "red" : ratio >= 0.65 ? "yellow" : "cyan";
|
|
1496
|
+
return /* @__PURE__ */ jsxs(Text, { children: [
|
|
1497
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "ctx " }),
|
|
1498
|
+
/* @__PURE__ */ jsx(Text, { color, children: renderProgress(ratio, 10) }),
|
|
1499
|
+
/* @__PURE__ */ jsxs(Text, { color, children: [
|
|
1500
|
+
" ",
|
|
1501
|
+
pct,
|
|
1502
|
+
"%"
|
|
1319
1503
|
] }),
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
/* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "[Ctrl+A]" }),
|
|
1329
|
-
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1330
|
-
" always (",
|
|
1331
|
-
suggestedPattern,
|
|
1332
|
-
") "
|
|
1333
|
-
] }),
|
|
1334
|
-
/* @__PURE__ */ jsx(Text, { bold: true, color: "red", children: "[Ctrl+D]" }),
|
|
1335
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " deny" })
|
|
1336
|
-
] }) })
|
|
1504
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1505
|
+
" ",
|
|
1506
|
+
"(",
|
|
1507
|
+
fmtTok2(ctx.used),
|
|
1508
|
+
"/",
|
|
1509
|
+
fmtTok2(ctx.max),
|
|
1510
|
+
")"
|
|
1511
|
+
] })
|
|
1337
1512
|
] });
|
|
1338
1513
|
}
|
|
1514
|
+
var FILLED = "\u2588";
|
|
1515
|
+
var EMPTY = "\u2591";
|
|
1516
|
+
function renderProgress(ratio, width) {
|
|
1517
|
+
const clamped = Math.max(0, Math.min(1, ratio));
|
|
1518
|
+
const filled = clamped === 0 ? 0 : Math.max(1, Math.round(clamped * width));
|
|
1519
|
+
const capped = Math.min(width, filled);
|
|
1520
|
+
return FILLED.repeat(capped) + EMPTY.repeat(width - capped);
|
|
1521
|
+
}
|
|
1522
|
+
function fmtTok2(n) {
|
|
1523
|
+
if (n < 1e3) return String(n);
|
|
1524
|
+
if (n < 1e6) return `${(n / 1e3).toFixed(n < 1e4 ? 1 : 0)}k`;
|
|
1525
|
+
return `${(n / 1e6).toFixed(1)}M`;
|
|
1526
|
+
}
|
|
1527
|
+
function fmtElapsed(ms) {
|
|
1528
|
+
const totalSec = Math.floor(ms / 1e3);
|
|
1529
|
+
const h = Math.floor(totalSec / 3600);
|
|
1530
|
+
const m = Math.floor(totalSec % 3600 / 60);
|
|
1531
|
+
const s = totalSec % 60;
|
|
1532
|
+
if (h > 0) {
|
|
1533
|
+
return `${h}:${pad2(m)}:${pad2(s)}`;
|
|
1534
|
+
}
|
|
1535
|
+
return `${pad2(m)}:${pad2(s)}`;
|
|
1536
|
+
}
|
|
1537
|
+
function pad2(n) {
|
|
1538
|
+
return n < 10 ? `0${n}` : String(n);
|
|
1539
|
+
}
|
|
1339
1540
|
var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
1340
1541
|
"node_modules",
|
|
1341
1542
|
".git",
|
|
@@ -1369,7 +1570,7 @@ async function walk(root, rel, depth, out) {
|
|
|
1369
1570
|
const dir = rel ? path3.join(root, rel) : root;
|
|
1370
1571
|
let entries;
|
|
1371
1572
|
try {
|
|
1372
|
-
entries = await
|
|
1573
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
1373
1574
|
} catch {
|
|
1374
1575
|
return;
|
|
1375
1576
|
}
|
|
@@ -1414,108 +1615,56 @@ async function searchFiles(root, query, limit = 8) {
|
|
|
1414
1615
|
scored.sort((a, b) => a.score - b.score);
|
|
1415
1616
|
return scored.slice(0, limit).map((x) => x.path);
|
|
1416
1617
|
}
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
return null;
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
'Write-Output "OK"'
|
|
1434
|
-
].join("; ");
|
|
1435
|
-
const out = await runCmd("powershell", ["-NoProfile", "-Command", ps]);
|
|
1436
|
-
if (!out || out.trim() === "NO_IMAGE") return null;
|
|
1437
|
-
if (!out.includes("OK")) return null;
|
|
1438
|
-
return readPngFile(tmp);
|
|
1439
|
-
}
|
|
1440
|
-
async function readDarwin() {
|
|
1441
|
-
const tmp = path3.join(os.tmpdir(), `wstack-clip-${Date.now()}.png`);
|
|
1442
|
-
const script = [
|
|
1443
|
-
"try",
|
|
1444
|
-
` set the_file to (open for access POSIX file "${tmp}" with write permission)`,
|
|
1445
|
-
" write (the clipboard as \xABclass PNGf\xBB) to the_file",
|
|
1446
|
-
" close access the_file",
|
|
1447
|
-
"on error",
|
|
1448
|
-
" try",
|
|
1449
|
-
' close access POSIX file "' + tmp + '"',
|
|
1450
|
-
" end try",
|
|
1451
|
-
' return "NO_IMAGE"',
|
|
1452
|
-
"end try",
|
|
1453
|
-
'return "OK"'
|
|
1454
|
-
].join("\n");
|
|
1455
|
-
const out = await runCmd("osascript", ["-e", script]);
|
|
1456
|
-
if (!out || out.trim() !== "OK") return null;
|
|
1457
|
-
return readPngFile(tmp);
|
|
1458
|
-
}
|
|
1459
|
-
async function readLinux() {
|
|
1460
|
-
const tmp = path3.join(os.tmpdir(), `wstack-clip-${Date.now()}.png`);
|
|
1461
|
-
const tries = [
|
|
1462
|
-
["wl-paste", ["--type", "image/png"]],
|
|
1463
|
-
["xclip", ["-selection", "clipboard", "-t", "image/png", "-o"]]
|
|
1464
|
-
];
|
|
1465
|
-
for (const [cmd, args] of tries) {
|
|
1466
|
-
const ok = await runCmdToFile(cmd, args, tmp).catch(() => false);
|
|
1467
|
-
if (ok) return readPngFile(tmp);
|
|
1618
|
+
async function readGitInfo(cwd) {
|
|
1619
|
+
const [branchRes, numstatRes, statusRes] = await Promise.all([
|
|
1620
|
+
runGit(cwd, ["branch", "--show-current"]),
|
|
1621
|
+
runGit(cwd, ["diff", "HEAD", "--numstat"]),
|
|
1622
|
+
runGit(cwd, ["status", "--porcelain"])
|
|
1623
|
+
]);
|
|
1624
|
+
if (!branchRes.ok || !numstatRes.ok || !statusRes.ok) return null;
|
|
1625
|
+
const branch = branchRes.stdout.trim();
|
|
1626
|
+
const branchLabel = branch || await detachedShortSha(cwd) || "detached";
|
|
1627
|
+
let added = 0;
|
|
1628
|
+
let deleted = 0;
|
|
1629
|
+
for (const line of numstatRes.stdout.split("\n")) {
|
|
1630
|
+
if (!line) continue;
|
|
1631
|
+
const [a, d] = line.split(" ");
|
|
1632
|
+
if (a && a !== "-") added += Number.parseInt(a, 10) || 0;
|
|
1633
|
+
if (d && d !== "-") deleted += Number.parseInt(d, 10) || 0;
|
|
1468
1634
|
}
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
try {
|
|
1473
|
-
const buf = await fs2.readFile(p);
|
|
1474
|
-
if (buf.length === 0) {
|
|
1475
|
-
await fs2.unlink(p).catch(() => void 0);
|
|
1476
|
-
return null;
|
|
1477
|
-
}
|
|
1478
|
-
if (buf.length > MAX_IMAGE_BYTES) {
|
|
1479
|
-
await fs2.unlink(p).catch(() => void 0);
|
|
1480
|
-
throw new Error(`Clipboard image exceeds ${MAX_IMAGE_BYTES / 1024 / 1024}MB limit`);
|
|
1481
|
-
}
|
|
1482
|
-
if (buf[0] !== 137 || buf[1] !== 80 || buf[2] !== 78 || buf[3] !== 71) {
|
|
1483
|
-
await fs2.unlink(p).catch(() => void 0);
|
|
1484
|
-
return null;
|
|
1485
|
-
}
|
|
1486
|
-
await fs2.unlink(p).catch(() => void 0);
|
|
1487
|
-
return { base64: buf.toString("base64"), mediaType: "image/png", bytes: buf.length };
|
|
1488
|
-
} catch (err) {
|
|
1489
|
-
if (err.code === "ENOENT") return null;
|
|
1490
|
-
throw err;
|
|
1635
|
+
let untracked = 0;
|
|
1636
|
+
for (const line of statusRes.stdout.split("\n")) {
|
|
1637
|
+
if (line.startsWith("?? ")) untracked++;
|
|
1491
1638
|
}
|
|
1639
|
+
return { branch: branchLabel, added, deleted, untracked };
|
|
1492
1640
|
}
|
|
1493
|
-
function
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
let out = "";
|
|
1497
|
-
child.stdout.on("data", (c) => {
|
|
1498
|
-
out += String(c);
|
|
1499
|
-
});
|
|
1500
|
-
child.on("error", () => resolve(null));
|
|
1501
|
-
child.on("exit", (code) => resolve(code === 0 ? out : null));
|
|
1502
|
-
});
|
|
1641
|
+
async function detachedShortSha(cwd) {
|
|
1642
|
+
const res = await runGit(cwd, ["rev-parse", "--short", "HEAD"]);
|
|
1643
|
+
return res.ok ? res.stdout.trim() : null;
|
|
1503
1644
|
}
|
|
1504
|
-
function
|
|
1645
|
+
function runGit(cwd, args) {
|
|
1505
1646
|
return new Promise((resolve) => {
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
}
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1647
|
+
let stdout = "";
|
|
1648
|
+
try {
|
|
1649
|
+
const child = spawn("git", args, {
|
|
1650
|
+
cwd,
|
|
1651
|
+
// Inherit stderr (silent) — we don't care about git's noise.
|
|
1652
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
1653
|
+
// Don't let a slow git hang the TUI.
|
|
1654
|
+
timeout: 3e3,
|
|
1655
|
+
windowsHide: true
|
|
1656
|
+
});
|
|
1657
|
+
child.stdout.setEncoding("utf8");
|
|
1658
|
+
child.stdout.on("data", (chunk) => {
|
|
1659
|
+
stdout += chunk;
|
|
1660
|
+
});
|
|
1661
|
+
child.on("error", () => resolve({ ok: false, stdout: "" }));
|
|
1662
|
+
child.on("close", (code) => {
|
|
1663
|
+
resolve({ ok: code === 0, stdout });
|
|
1664
|
+
});
|
|
1665
|
+
} catch {
|
|
1666
|
+
resolve({ ok: false, stdout: "" });
|
|
1667
|
+
}
|
|
1519
1668
|
});
|
|
1520
1669
|
}
|
|
1521
1670
|
|
|
@@ -1567,7 +1716,8 @@ function handleQueueCommand(args, deps) {
|
|
|
1567
1716
|
if (uniqueValid.length === 0) {
|
|
1568
1717
|
const parts2 = ["No valid positions to delete."];
|
|
1569
1718
|
if (invalid.length > 0) parts2.push(`Invalid: ${invalid.join(", ")}.`);
|
|
1570
|
-
if (outOfRange.length > 0)
|
|
1719
|
+
if (outOfRange.length > 0)
|
|
1720
|
+
parts2.push(`Out of range (queue has ${queue.length}): ${outOfRange.join(", ")}.`);
|
|
1571
1721
|
return parts2.join(" ");
|
|
1572
1722
|
}
|
|
1573
1723
|
deps.deleteAt(uniqueValid);
|
|
@@ -1596,65 +1746,10 @@ function oneLine(s, max) {
|
|
|
1596
1746
|
const collapsed = s.replace(/\s+/g, " ").trim();
|
|
1597
1747
|
return collapsed.length <= max ? collapsed : `${collapsed.slice(0, max - 1)}\u2026`;
|
|
1598
1748
|
}
|
|
1599
|
-
async function readGitInfo(cwd) {
|
|
1600
|
-
const [branchRes, numstatRes, statusRes] = await Promise.all([
|
|
1601
|
-
runGit(cwd, ["branch", "--show-current"]),
|
|
1602
|
-
runGit(cwd, ["diff", "HEAD", "--numstat"]),
|
|
1603
|
-
runGit(cwd, ["status", "--porcelain"])
|
|
1604
|
-
]);
|
|
1605
|
-
if (!branchRes.ok || !numstatRes.ok || !statusRes.ok) return null;
|
|
1606
|
-
const branch = branchRes.stdout.trim();
|
|
1607
|
-
const branchLabel = branch || await detachedShortSha(cwd) || "detached";
|
|
1608
|
-
let added = 0;
|
|
1609
|
-
let deleted = 0;
|
|
1610
|
-
for (const line of numstatRes.stdout.split("\n")) {
|
|
1611
|
-
if (!line) continue;
|
|
1612
|
-
const [a, d] = line.split(" ");
|
|
1613
|
-
if (a && a !== "-") added += Number.parseInt(a, 10) || 0;
|
|
1614
|
-
if (d && d !== "-") deleted += Number.parseInt(d, 10) || 0;
|
|
1615
|
-
}
|
|
1616
|
-
let untracked = 0;
|
|
1617
|
-
for (const line of statusRes.stdout.split("\n")) {
|
|
1618
|
-
if (line.startsWith("?? ")) untracked++;
|
|
1619
|
-
}
|
|
1620
|
-
return { branch: branchLabel, added, deleted, untracked };
|
|
1621
|
-
}
|
|
1622
|
-
async function detachedShortSha(cwd) {
|
|
1623
|
-
const res = await runGit(cwd, ["rev-parse", "--short", "HEAD"]);
|
|
1624
|
-
return res.ok ? res.stdout.trim() : null;
|
|
1625
|
-
}
|
|
1626
|
-
function runGit(cwd, args) {
|
|
1627
|
-
return new Promise((resolve) => {
|
|
1628
|
-
let stdout = "";
|
|
1629
|
-
try {
|
|
1630
|
-
const child = spawn("git", args, {
|
|
1631
|
-
cwd,
|
|
1632
|
-
// Inherit stderr (silent) — we don't care about git's noise.
|
|
1633
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
1634
|
-
// Don't let a slow git hang the TUI.
|
|
1635
|
-
timeout: 3e3,
|
|
1636
|
-
windowsHide: true
|
|
1637
|
-
});
|
|
1638
|
-
child.stdout.setEncoding("utf8");
|
|
1639
|
-
child.stdout.on("data", (chunk) => {
|
|
1640
|
-
stdout += chunk;
|
|
1641
|
-
});
|
|
1642
|
-
child.on("error", () => resolve({ ok: false, stdout: "" }));
|
|
1643
|
-
child.on("close", (code) => {
|
|
1644
|
-
resolve({ ok: code === 0, stdout });
|
|
1645
|
-
});
|
|
1646
|
-
} catch {
|
|
1647
|
-
resolve({ ok: false, stdout: "" });
|
|
1648
|
-
}
|
|
1649
|
-
});
|
|
1650
|
-
}
|
|
1651
1749
|
function reducer(state, action) {
|
|
1652
1750
|
switch (action.type) {
|
|
1653
1751
|
case "addEntry": {
|
|
1654
|
-
const appended = [
|
|
1655
|
-
...state.entries,
|
|
1656
|
-
{ ...action.entry, id: state.nextId }
|
|
1657
|
-
];
|
|
1752
|
+
const appended = [...state.entries, { ...action.entry, id: state.nextId }];
|
|
1658
1753
|
return { ...state, entries: appended, nextId: state.nextId + 1 };
|
|
1659
1754
|
}
|
|
1660
1755
|
case "setBuffer":
|
|
@@ -1748,14 +1843,20 @@ function reducer(state, action) {
|
|
|
1748
1843
|
}
|
|
1749
1844
|
return {
|
|
1750
1845
|
...state,
|
|
1751
|
-
toolStream: {
|
|
1846
|
+
toolStream: {
|
|
1847
|
+
toolUseId: action.toolUseId,
|
|
1848
|
+
name: action.name,
|
|
1849
|
+
text: action.text,
|
|
1850
|
+
startedAt: action.startedAt
|
|
1851
|
+
}
|
|
1752
1852
|
};
|
|
1753
1853
|
}
|
|
1754
1854
|
case "toolStreamClear": {
|
|
1755
1855
|
if (state.toolStream === null) return state;
|
|
1756
1856
|
const t = state.toolStream;
|
|
1757
1857
|
if (action.toolUseId !== void 0 && action.toolUseId !== t.toolUseId) return state;
|
|
1758
|
-
if (action.name !== void 0 && action.toolUseId === void 0 && action.name !== t.name)
|
|
1858
|
+
if (action.name !== void 0 && action.toolUseId === void 0 && action.name !== t.name)
|
|
1859
|
+
return state;
|
|
1759
1860
|
return { ...state, toolStream: null };
|
|
1760
1861
|
}
|
|
1761
1862
|
case "enqueue": {
|
|
@@ -1986,20 +2087,18 @@ function App({
|
|
|
1986
2087
|
const [lastInputTokens, setLastInputTokens] = React.useState(0);
|
|
1987
2088
|
useEffect(() => {
|
|
1988
2089
|
const off = events.on("provider.response", (e) => {
|
|
1989
|
-
|
|
2090
|
+
const total = (e.usage.input ?? 0) + (e.usage.cacheRead ?? 0) + (e.usage.cacheWrite ?? 0);
|
|
2091
|
+
setLastInputTokens(total);
|
|
1990
2092
|
});
|
|
1991
2093
|
return () => {
|
|
1992
2094
|
off();
|
|
1993
2095
|
};
|
|
1994
2096
|
}, [events]);
|
|
1995
2097
|
const maxContext = effectiveMaxContext ?? agent.ctx.provider.capabilities.maxContext;
|
|
1996
|
-
const contextWindow = useMemo(
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
},
|
|
2001
|
-
[lastInputTokens, maxContext, state.contextChipVersion]
|
|
2002
|
-
);
|
|
2098
|
+
const contextWindow = useMemo(() => {
|
|
2099
|
+
void state.contextChipVersion;
|
|
2100
|
+
return lastInputTokens > 0 && maxContext > 0 ? { used: lastInputTokens, max: maxContext } : void 0;
|
|
2101
|
+
}, [lastInputTokens, maxContext, state.contextChipVersion]);
|
|
2003
2102
|
const todos = useMemo(() => {
|
|
2004
2103
|
const counts = { pending: 0, inProgress: 0, completed: 0 };
|
|
2005
2104
|
for (const t of agent.ctx.todos) {
|
|
@@ -2095,7 +2194,7 @@ function App({
|
|
|
2095
2194
|
}
|
|
2096
2195
|
const absPath = path3.isAbsolute(picked) ? picked : path3.join(projectRoot, picked);
|
|
2097
2196
|
try {
|
|
2098
|
-
const data = await
|
|
2197
|
+
const data = await fs.readFile(absPath, "utf8");
|
|
2099
2198
|
const placeholder = await builder.appendFile({
|
|
2100
2199
|
kind: "file",
|
|
2101
2200
|
data,
|
|
@@ -2113,7 +2212,10 @@ function App({
|
|
|
2113
2212
|
} catch (err) {
|
|
2114
2213
|
dispatch({
|
|
2115
2214
|
type: "addEntry",
|
|
2116
|
-
entry: {
|
|
2215
|
+
entry: {
|
|
2216
|
+
kind: "error",
|
|
2217
|
+
text: `Attach failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2218
|
+
}
|
|
2117
2219
|
});
|
|
2118
2220
|
dispatch({ type: "pickerClose" });
|
|
2119
2221
|
}
|
|
@@ -2206,7 +2308,8 @@ function App({
|
|
|
2206
2308
|
type: "toolStreamAppend",
|
|
2207
2309
|
toolUseId: e.id,
|
|
2208
2310
|
name: e.name,
|
|
2209
|
-
text: e.event.text
|
|
2311
|
+
text: e.event.text,
|
|
2312
|
+
startedAt: Date.now()
|
|
2210
2313
|
});
|
|
2211
2314
|
});
|
|
2212
2315
|
const offTool = events.on("tool.executed", (e) => {
|
|
@@ -2218,11 +2321,23 @@ function App({
|
|
|
2218
2321
|
durationMs: e.durationMs,
|
|
2219
2322
|
ok: e.ok,
|
|
2220
2323
|
input: e.input,
|
|
2221
|
-
output: e.output
|
|
2324
|
+
output: e.output,
|
|
2325
|
+
// Real model-visible sizes — forwarded so the size chip beside
|
|
2326
|
+
// the tool header can show what the model paid for instead of
|
|
2327
|
+
// the misleading preview-byte count we used to surface.
|
|
2328
|
+
outputBytes: e.outputBytes,
|
|
2329
|
+
outputTokens: e.outputTokens,
|
|
2330
|
+
outputLines: e.outputLines
|
|
2222
2331
|
}
|
|
2223
2332
|
});
|
|
2224
2333
|
dispatch({ type: "toolEnded", name: e.name });
|
|
2225
2334
|
dispatch({ type: "toolStreamClear", name: e.name });
|
|
2335
|
+
if (e.ok && e.name === "todo") {
|
|
2336
|
+
dispatch({
|
|
2337
|
+
type: "addEntry",
|
|
2338
|
+
entry: { kind: "info", text: formatTodosList(agent.ctx.todos) }
|
|
2339
|
+
});
|
|
2340
|
+
}
|
|
2226
2341
|
});
|
|
2227
2342
|
const offRetry = events.on("provider.retry", (e) => {
|
|
2228
2343
|
const secs = (e.delayMs / 1e3).toFixed(e.delayMs >= 1e3 ? 1 : 2);
|
|
@@ -2237,6 +2352,19 @@ function App({
|
|
|
2237
2352
|
entry: { kind: "error", text: e.description }
|
|
2238
2353
|
});
|
|
2239
2354
|
});
|
|
2355
|
+
const offProvResp = events.on("provider.response", () => {
|
|
2356
|
+
const text = streamingTextRef.current;
|
|
2357
|
+
streamingTextRef.current = "";
|
|
2358
|
+
pendingDeltaRef.current = "";
|
|
2359
|
+
if (flushTimerRef.current) {
|
|
2360
|
+
clearTimeout(flushTimerRef.current);
|
|
2361
|
+
flushTimerRef.current = null;
|
|
2362
|
+
}
|
|
2363
|
+
dispatch({ type: "streamReset" });
|
|
2364
|
+
if (text.trim()) {
|
|
2365
|
+
dispatch({ type: "addEntry", entry: { kind: "assistant", text } });
|
|
2366
|
+
}
|
|
2367
|
+
});
|
|
2240
2368
|
const offConfirmNeeded = events.on("tool.confirm_needed", (e) => {
|
|
2241
2369
|
dispatch({
|
|
2242
2370
|
type: "addEntry",
|
|
@@ -2265,6 +2393,7 @@ function App({
|
|
|
2265
2393
|
offTool();
|
|
2266
2394
|
offRetry();
|
|
2267
2395
|
offProvErr();
|
|
2396
|
+
offProvResp();
|
|
2268
2397
|
offConfirmNeeded();
|
|
2269
2398
|
if (flushTimerRef.current) clearTimeout(flushTimerRef.current);
|
|
2270
2399
|
};
|
|
@@ -2439,7 +2568,8 @@ function App({
|
|
|
2439
2568
|
dispatch({ type: "setBuffer", buffer, cursor: target });
|
|
2440
2569
|
return;
|
|
2441
2570
|
}
|
|
2442
|
-
if (state.cursor > 0)
|
|
2571
|
+
if (state.cursor > 0)
|
|
2572
|
+
dispatch({ type: "setBuffer", buffer: state.buffer, cursor: state.cursor - 1 });
|
|
2443
2573
|
return;
|
|
2444
2574
|
}
|
|
2445
2575
|
if (key.rightArrow) {
|
|
@@ -2452,7 +2582,8 @@ function App({
|
|
|
2452
2582
|
dispatch({ type: "setBuffer", buffer, cursor: target });
|
|
2453
2583
|
return;
|
|
2454
2584
|
}
|
|
2455
|
-
if (state.cursor < state.buffer.length)
|
|
2585
|
+
if (state.cursor < state.buffer.length)
|
|
2586
|
+
dispatch({ type: "setBuffer", buffer: state.buffer, cursor: state.cursor + 1 });
|
|
2456
2587
|
return;
|
|
2457
2588
|
}
|
|
2458
2589
|
if (key.upArrow) {
|
|
@@ -2520,10 +2651,9 @@ function App({
|
|
|
2520
2651
|
const before = tokenCounter?.total();
|
|
2521
2652
|
const costBefore = tokenCounter?.estimateCost().total ?? 0;
|
|
2522
2653
|
const result = await agent.run(blocks, { signal: ctrl.signal });
|
|
2523
|
-
const
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
dispatch({ type: "addEntry", entry: { kind: "assistant", text } });
|
|
2654
|
+
const lingering = streamingTextRef.current;
|
|
2655
|
+
if (lingering.trim()) {
|
|
2656
|
+
dispatch({ type: "addEntry", entry: { kind: "assistant", text: lingering } });
|
|
2527
2657
|
}
|
|
2528
2658
|
streamingTextRef.current = "";
|
|
2529
2659
|
pendingDeltaRef.current = "";
|
|
@@ -2536,10 +2666,10 @@ function App({
|
|
|
2536
2666
|
dispatch({ type: "addEntry", entry: { kind: "warn", text: "Aborted." } });
|
|
2537
2667
|
} else if (result.status === "failed") {
|
|
2538
2668
|
const err = result.error;
|
|
2539
|
-
const
|
|
2669
|
+
const text = err ? `Failed [${err.severity}${err.recoverable ? ", recoverable" : ""}]: ${err.describe()}` : "Failed.";
|
|
2540
2670
|
dispatch({
|
|
2541
2671
|
type: "addEntry",
|
|
2542
|
-
entry: { kind: "error", text
|
|
2672
|
+
entry: { kind: "error", text }
|
|
2543
2673
|
});
|
|
2544
2674
|
} else if (result.status === "max_iterations") {
|
|
2545
2675
|
dispatch({
|
|
@@ -2554,7 +2684,7 @@ function App({
|
|
|
2554
2684
|
type: "addEntry",
|
|
2555
2685
|
entry: {
|
|
2556
2686
|
kind: "turn-summary",
|
|
2557
|
-
text: `[in: ${
|
|
2687
|
+
text: `[in: ${fmtTok3(after.input - before.input)} out: ${fmtTok3(after.output - before.output)} iters: ${result.iterations} cost: ${(costAfter - costBefore).toFixed(4)} ${((Date.now() - startedAt) / 1e3).toFixed(1)}s]`
|
|
2558
2688
|
}
|
|
2559
2689
|
});
|
|
2560
2690
|
}
|
|
@@ -2633,7 +2763,14 @@ function App({
|
|
|
2633
2763
|
return "";
|
|
2634
2764
|
}, [state.buffer, state.status, state.picker.open]);
|
|
2635
2765
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
2636
|
-
/* @__PURE__ */ jsx(
|
|
2766
|
+
/* @__PURE__ */ jsx(
|
|
2767
|
+
History,
|
|
2768
|
+
{
|
|
2769
|
+
entries: state.entries,
|
|
2770
|
+
streamingText: state.streamingText,
|
|
2771
|
+
toolStream: state.toolStream
|
|
2772
|
+
}
|
|
2773
|
+
),
|
|
2637
2774
|
/* @__PURE__ */ jsx(
|
|
2638
2775
|
Input,
|
|
2639
2776
|
{
|
|
@@ -2725,7 +2862,7 @@ function detectAtToken(buffer, cursor) {
|
|
|
2725
2862
|
}
|
|
2726
2863
|
return null;
|
|
2727
2864
|
}
|
|
2728
|
-
function
|
|
2865
|
+
function fmtTok3(n) {
|
|
2729
2866
|
if (n < 1e3) return String(n);
|
|
2730
2867
|
if (n < 1e6) return `${(n / 1e3).toFixed(n < 1e4 ? 1 : 0)}k`;
|
|
2731
2868
|
return `${(n / 1e6).toFixed(1)}M`;
|