@wrongstack/tui 0.1.9 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/index.d.ts +48 -7
- package/dist/index.js +1861 -499
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,13 +1,375 @@
|
|
|
1
|
+
import { render, useApp, Box, useStdout, Static, Text, useInput, useStdin } from 'ink';
|
|
1
2
|
import React, { useState, useReducer, useRef, useEffect, useMemo } from 'react';
|
|
2
|
-
import
|
|
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
|
+
}
|
|
220
|
+
var STATUS_ICON = {
|
|
221
|
+
idle: { icon: "\u25CB", color: "gray" },
|
|
222
|
+
running: { icon: "\u25CF", color: "green" },
|
|
223
|
+
success: { icon: "\u2713", color: "green" },
|
|
224
|
+
failed: { icon: "\u2717", color: "red" },
|
|
225
|
+
timeout: { icon: "\u23F1", color: "yellow" },
|
|
226
|
+
stopped: { icon: "\u2298", color: "yellow" }
|
|
227
|
+
};
|
|
228
|
+
function fmtCost(n) {
|
|
229
|
+
if (n === 0) return "\u2014";
|
|
230
|
+
return `$${n.toFixed(3)}`;
|
|
231
|
+
}
|
|
232
|
+
function fmtCount(n) {
|
|
233
|
+
if (n === 0) return "\u2014";
|
|
234
|
+
return String(n);
|
|
235
|
+
}
|
|
236
|
+
function fmtModel(provider, model) {
|
|
237
|
+
if (!provider && !model) return "";
|
|
238
|
+
const p = provider ?? "";
|
|
239
|
+
const m = model ?? "";
|
|
240
|
+
return p && m ? `${p}/${m}` : p || m;
|
|
241
|
+
}
|
|
242
|
+
function resolveName(entry, roster) {
|
|
243
|
+
const rosterEntry = roster?.[entry.id];
|
|
244
|
+
if (rosterEntry) return rosterEntry.name;
|
|
245
|
+
return entry.name;
|
|
246
|
+
}
|
|
247
|
+
function FleetPanel({ entries, totalCost, roster }) {
|
|
248
|
+
const list = Object.values(entries);
|
|
249
|
+
if (list.length === 0) return null;
|
|
250
|
+
const sorted = [...list].sort((a, b) => {
|
|
251
|
+
const order = { running: 0, success: 1, failed: 2, timeout: 3, stopped: 4, idle: 5 };
|
|
252
|
+
const ao = order[a.status] ?? 9;
|
|
253
|
+
const bo = order[b.status] ?? 9;
|
|
254
|
+
if (ao !== bo) return ao - bo;
|
|
255
|
+
return b.lastEventAt - a.lastEventAt;
|
|
256
|
+
});
|
|
257
|
+
const runningCount = list.filter((e) => e.status === "running").length;
|
|
258
|
+
const totalLabel = totalCost > 0 ? `$${totalCost.toFixed(3)} \xB7 ${runningCount} active` : `${runningCount} active`;
|
|
259
|
+
return /* @__PURE__ */ jsxs(
|
|
260
|
+
Box,
|
|
261
|
+
{
|
|
262
|
+
flexDirection: "column",
|
|
263
|
+
paddingX: 1,
|
|
264
|
+
borderStyle: "single",
|
|
265
|
+
borderTop: false,
|
|
266
|
+
borderBottom: false,
|
|
267
|
+
borderLeft: false,
|
|
268
|
+
borderRight: false,
|
|
269
|
+
children: [
|
|
270
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 2, children: [
|
|
271
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Fleet" }),
|
|
272
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
|
|
273
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
274
|
+
list.length,
|
|
275
|
+
" agent",
|
|
276
|
+
list.length === 1 ? "" : "s"
|
|
277
|
+
] }),
|
|
278
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
|
|
279
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: totalLabel })
|
|
280
|
+
] }),
|
|
281
|
+
sorted.map((entry) => {
|
|
282
|
+
const si = STATUS_ICON[entry.status];
|
|
283
|
+
const modelTag = fmtModel(entry.provider, entry.model);
|
|
284
|
+
const name = resolveName(entry, roster);
|
|
285
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
286
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
287
|
+
/* @__PURE__ */ jsx(Text, { color: si.color, children: si.icon }),
|
|
288
|
+
/* @__PURE__ */ jsx(Text, { children: name.slice(0, 16).padEnd(16) }),
|
|
289
|
+
modelTag ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
290
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
|
|
291
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: modelTag })
|
|
292
|
+
] }) : null,
|
|
293
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
|
|
294
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
295
|
+
fmtCount(entry.iterations).padStart(3),
|
|
296
|
+
"it"
|
|
297
|
+
] }),
|
|
298
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
299
|
+
fmtCount(entry.toolCalls).padStart(3),
|
|
300
|
+
"tc"
|
|
301
|
+
] }),
|
|
302
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
|
|
303
|
+
/* @__PURE__ */ jsx(Text, { color: "yellow", children: fmtCost(entry.cost) })
|
|
304
|
+
] }),
|
|
305
|
+
entry.status === "running" && entry.currentTool ? /* @__PURE__ */ jsxs(Box, { paddingLeft: 2, children: [
|
|
306
|
+
/* @__PURE__ */ jsxs(Text, { color: "cyan", children: [
|
|
307
|
+
"\u2192 ",
|
|
308
|
+
entry.currentTool.name
|
|
309
|
+
] }),
|
|
310
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
311
|
+
" (",
|
|
312
|
+
Math.max(0, Date.now() - entry.currentTool.startedAt),
|
|
313
|
+
"ms)"
|
|
314
|
+
] })
|
|
315
|
+
] }) : null,
|
|
316
|
+
entry.status === "running" && entry.streamingText ? /* @__PURE__ */ jsx(Box, { paddingLeft: 2, children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
317
|
+
">",
|
|
318
|
+
" ",
|
|
319
|
+
entry.streamingText.slice(-80)
|
|
320
|
+
] }) }) : null,
|
|
321
|
+
entry.transcriptPath ? /* @__PURE__ */ jsx(Box, { paddingLeft: 2, children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
322
|
+
"log: ",
|
|
323
|
+
entry.transcriptPath
|
|
324
|
+
] }) }) : null
|
|
325
|
+
] }, entry.id);
|
|
326
|
+
})
|
|
327
|
+
]
|
|
328
|
+
}
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
function fmtElapsed(ms) {
|
|
332
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
333
|
+
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
|
|
334
|
+
const m = Math.floor(ms / 6e4);
|
|
335
|
+
const s = Math.floor(ms % 6e4 / 1e3);
|
|
336
|
+
return `${m}m${s.toString().padStart(2, "0")}s`;
|
|
337
|
+
}
|
|
338
|
+
function LiveActivityStrip({
|
|
339
|
+
entries,
|
|
340
|
+
nowTick,
|
|
341
|
+
maxRows = 4
|
|
342
|
+
}) {
|
|
343
|
+
const running = Object.values(entries).filter((e) => e.status === "running").sort((a, b) => a.startedAt - b.startedAt).slice(0, maxRows);
|
|
344
|
+
if (running.length === 0) return null;
|
|
345
|
+
const now = Date.now();
|
|
346
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
|
|
347
|
+
running.map((e) => {
|
|
348
|
+
const toolElapsed = e.currentTool ? now - e.currentTool.startedAt : 0;
|
|
349
|
+
const taskElapsed = now - e.startedAt;
|
|
350
|
+
const toolSeg = e.currentTool ? `\u2192 ${e.currentTool.name} (${fmtElapsed(toolElapsed)})` : "idle between tools";
|
|
351
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
352
|
+
/* @__PURE__ */ jsx(Text, { color: "cyan", children: "\u25CF" }),
|
|
353
|
+
/* @__PURE__ */ jsx(Text, { children: e.name.slice(0, 14).padEnd(14) }),
|
|
354
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
|
|
355
|
+
/* @__PURE__ */ jsx(Text, { color: e.currentTool ? "green" : "yellow", children: toolSeg }),
|
|
356
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
|
|
357
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
358
|
+
e.iterations,
|
|
359
|
+
"it ",
|
|
360
|
+
e.toolCalls,
|
|
361
|
+
"tc \xB7 ",
|
|
362
|
+
fmtElapsed(taskElapsed)
|
|
363
|
+
] })
|
|
364
|
+
] }, e.id);
|
|
365
|
+
}),
|
|
366
|
+
Object.values(entries).filter((e) => e.status === "running").length > maxRows ? /* @__PURE__ */ jsx(Box, { paddingLeft: 2, children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
367
|
+
"\u2026+",
|
|
368
|
+
Object.values(entries).filter((e) => e.status === "running").length - maxRows,
|
|
369
|
+
" more"
|
|
370
|
+
] }) }) : null
|
|
371
|
+
] });
|
|
372
|
+
}
|
|
11
373
|
|
|
12
374
|
// src/markdown-table.ts
|
|
13
375
|
function renderMarkdownTables(text, maxWidth) {
|
|
@@ -198,13 +560,53 @@ function History({ entries, streamingText, toolStream }) {
|
|
|
198
560
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "> " }),
|
|
199
561
|
/* @__PURE__ */ jsx(Text, { children: tail })
|
|
200
562
|
] }) : null,
|
|
201
|
-
toolTail ? /* @__PURE__ */
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
563
|
+
toolTail ? /* @__PURE__ */ jsx(
|
|
564
|
+
ToolStreamBox,
|
|
565
|
+
{
|
|
566
|
+
name: toolStream.name,
|
|
567
|
+
text: toolTail,
|
|
568
|
+
startedAt: toolStream.startedAt,
|
|
569
|
+
termWidth
|
|
570
|
+
}
|
|
571
|
+
) : null
|
|
205
572
|
] });
|
|
206
573
|
}
|
|
207
574
|
var MAX_STREAM_DISPLAY_CHARS = 480;
|
|
575
|
+
var MAX_STREAM_LINES = 8;
|
|
576
|
+
function ToolStreamBox({
|
|
577
|
+
name,
|
|
578
|
+
text,
|
|
579
|
+
startedAt,
|
|
580
|
+
termWidth
|
|
581
|
+
}) {
|
|
582
|
+
const [tick, setTick] = useState(0);
|
|
583
|
+
useEffect(() => {
|
|
584
|
+
const t = setInterval(() => setTick((n) => n + 1), 500);
|
|
585
|
+
return () => clearInterval(t);
|
|
586
|
+
}, []);
|
|
587
|
+
const elapsedMs = Date.now() - startedAt;
|
|
588
|
+
const lines = text.split("\n");
|
|
589
|
+
const totalLines = lines.length;
|
|
590
|
+
const hidden = Math.max(0, totalLines - MAX_STREAM_LINES);
|
|
591
|
+
const visible = hidden > 0 ? lines.slice(hidden) : lines;
|
|
592
|
+
const contentWidth = Math.max(20, Math.min(termWidth - 4, 100));
|
|
593
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 0, children: [
|
|
594
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
|
|
595
|
+
/* @__PURE__ */ jsx(Text, { color: "yellow", children: "\u25C6 " }),
|
|
596
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: name }),
|
|
597
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u23F1 ${fmtDuration(elapsedMs)}` }),
|
|
598
|
+
hidden > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` (${totalLines} lines, showing last ${MAX_STREAM_LINES})` }) : null
|
|
599
|
+
] }),
|
|
600
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [
|
|
601
|
+
hidden > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, italic: true, children: ` \u2026 ${hidden} more line${hidden === 1 ? "" : "s"} above` }) : null,
|
|
602
|
+
visible.map((line, i) => {
|
|
603
|
+
const key = i;
|
|
604
|
+
const trimmed = line.length > contentWidth ? `${line.slice(0, contentWidth - 1)}\u2026` : line;
|
|
605
|
+
return /* @__PURE__ */ jsx(Text, { dimColor: true, children: trimmed || " " }, key);
|
|
606
|
+
})
|
|
607
|
+
] })
|
|
608
|
+
] });
|
|
609
|
+
}
|
|
208
610
|
function tailForDisplay(text, maxChars) {
|
|
209
611
|
if (text.length <= maxChars) return text;
|
|
210
612
|
const cut = text.length - maxChars;
|
|
@@ -234,7 +636,10 @@ function DiffBlock({ rows, hidden }) {
|
|
|
234
636
|
hidden > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, italic: true, children: ` \u2026 ${hidden} more line${hidden === 1 ? "" : "s"}` }) : null
|
|
235
637
|
] });
|
|
236
638
|
}
|
|
237
|
-
function Entry({
|
|
639
|
+
function Entry({
|
|
640
|
+
entry,
|
|
641
|
+
termWidth
|
|
642
|
+
}) {
|
|
238
643
|
switch (entry.kind) {
|
|
239
644
|
case "user":
|
|
240
645
|
return /* @__PURE__ */ jsxs(Text, { children: [
|
|
@@ -247,8 +652,28 @@ function Entry({ entry, termWidth }) {
|
|
|
247
652
|
return /* @__PURE__ */ jsx(Text, { children: renderMarkdownTables(entry.text, termWidth) });
|
|
248
653
|
case "tool": {
|
|
249
654
|
const argSummary = formatToolArgs(entry.name, entry.input);
|
|
250
|
-
const outLines = formatToolOutput(
|
|
655
|
+
const outLines = formatToolOutput(
|
|
656
|
+
entry.name,
|
|
657
|
+
entry.output,
|
|
658
|
+
entry.ok,
|
|
659
|
+
entry.outputBytes,
|
|
660
|
+
entry.outputLines
|
|
661
|
+
);
|
|
251
662
|
const diff = entry.ok ? extractDiffPreview(entry.name, entry.output) : void 0;
|
|
663
|
+
const sizeChip = (() => {
|
|
664
|
+
if (!entry.ok) return "";
|
|
665
|
+
const parts = [];
|
|
666
|
+
if (entry.outputLines !== void 0 && entry.outputLines > 0) {
|
|
667
|
+
parts.push(`${entry.outputLines} L`);
|
|
668
|
+
}
|
|
669
|
+
if (entry.outputBytes && entry.outputBytes > 0) {
|
|
670
|
+
parts.push(fmtBytes(entry.outputBytes));
|
|
671
|
+
}
|
|
672
|
+
if (entry.outputTokens && entry.outputTokens > 0) {
|
|
673
|
+
parts.push(`\u2248${fmtTok(entry.outputTokens)} tok`);
|
|
674
|
+
}
|
|
675
|
+
return parts.join(" \xB7 ");
|
|
676
|
+
})();
|
|
252
677
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
253
678
|
/* @__PURE__ */ jsxs(Text, { children: [
|
|
254
679
|
/* @__PURE__ */ jsx(Text, { color: entry.ok ? "green" : "red", children: entry.ok ? "\u25CF" : "\u2717" }),
|
|
@@ -258,13 +683,21 @@ function Entry({ entry, termWidth }) {
|
|
|
258
683
|
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
259
684
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: argSummary })
|
|
260
685
|
] }) : null,
|
|
261
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \xB7 ${fmtDuration(entry.durationMs)}` })
|
|
686
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \xB7 ${fmtDuration(entry.durationMs)}` }),
|
|
687
|
+
sizeChip ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \xB7 ${sizeChip}` }) : null
|
|
262
688
|
] }),
|
|
263
689
|
outLines.map((line, i) => (
|
|
264
690
|
// biome-ignore lint/suspicious/noArrayIndexKey: tool output lines are static, index is stable
|
|
265
691
|
/* @__PURE__ */ jsxs(Text, { children: [
|
|
266
692
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: i === outLines.length - 1 && !diff ? " \u2514\u2500 " : " \u251C\u2500 " }),
|
|
267
|
-
/* @__PURE__ */ jsx(
|
|
693
|
+
/* @__PURE__ */ jsx(
|
|
694
|
+
Text,
|
|
695
|
+
{
|
|
696
|
+
color: !entry.ok || line.startsWith("!") ? "red" : void 0,
|
|
697
|
+
dimColor: entry.ok && !line.startsWith("!"),
|
|
698
|
+
children: line
|
|
699
|
+
}
|
|
700
|
+
)
|
|
268
701
|
] }, i)
|
|
269
702
|
)),
|
|
270
703
|
diff ? /* @__PURE__ */ jsx(DiffBlock, { rows: diff.rows, hidden: diff.hidden }) : null
|
|
@@ -279,86 +712,117 @@ function Entry({ entry, termWidth }) {
|
|
|
279
712
|
case "turn-summary":
|
|
280
713
|
return /* @__PURE__ */ jsx(Text, { dimColor: true, children: entry.text });
|
|
281
714
|
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
|
-
|
|
715
|
+
return /* @__PURE__ */ jsxs(
|
|
716
|
+
Box,
|
|
717
|
+
{
|
|
718
|
+
flexDirection: "column",
|
|
719
|
+
borderStyle: "single",
|
|
720
|
+
borderTop: false,
|
|
721
|
+
borderLeft: false,
|
|
722
|
+
borderRight: false,
|
|
723
|
+
borderBottom: false,
|
|
724
|
+
paddingX: 1,
|
|
725
|
+
children: [
|
|
726
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
|
|
727
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "yellow", children: "\u26A0 Confirm" }),
|
|
728
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
729
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: entry.toolName })
|
|
730
|
+
] }),
|
|
731
|
+
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,
|
|
732
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }),
|
|
733
|
+
/* @__PURE__ */ jsx(Box, { flexDirection: "row", children: /* @__PURE__ */ jsxs(Text, { children: [
|
|
734
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "green", children: "[\u21B5]" }),
|
|
735
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " yes " }),
|
|
736
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "red", children: "[Esc]" }),
|
|
737
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " no " }),
|
|
738
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "[Ctrl+A]" }),
|
|
739
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
740
|
+
" always (",
|
|
741
|
+
entry.suggestedPattern,
|
|
742
|
+
") "
|
|
743
|
+
] }),
|
|
744
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "red", children: "[Ctrl+D]" }),
|
|
745
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " deny" })
|
|
746
|
+
] }) })
|
|
747
|
+
]
|
|
748
|
+
}
|
|
749
|
+
);
|
|
305
750
|
case "banner":
|
|
306
751
|
return /* @__PURE__ */ jsx(Banner, { entry });
|
|
752
|
+
case "subagent": {
|
|
753
|
+
const lines = entry.text.split("\n");
|
|
754
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
755
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
756
|
+
/* @__PURE__ */ jsx(Text, { color: entry.agentColor, bold: true, children: `[${entry.agentLabel}]` }),
|
|
757
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
758
|
+
/* @__PURE__ */ jsx(Text, { color: entry.agentColor, children: entry.icon }),
|
|
759
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
760
|
+
/* @__PURE__ */ jsx(Text, { children: lines[0] ?? "" }),
|
|
761
|
+
entry.detail ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
762
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " \xB7 " }),
|
|
763
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: entry.detail })
|
|
764
|
+
] }) : null
|
|
765
|
+
] }),
|
|
766
|
+
lines.slice(1).map((line, i) => (
|
|
767
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: stable line index
|
|
768
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
769
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " " }),
|
|
770
|
+
/* @__PURE__ */ jsx(Text, { children: line })
|
|
771
|
+
] }, i)
|
|
772
|
+
))
|
|
773
|
+
] });
|
|
774
|
+
}
|
|
307
775
|
}
|
|
308
776
|
}
|
|
309
777
|
function Banner({
|
|
310
778
|
entry
|
|
311
779
|
}) {
|
|
312
780
|
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
|
-
);
|
|
781
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", paddingX: 2, paddingY: 0, children: [
|
|
782
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
783
|
+
/* @__PURE__ */ jsx(Text, { color: "magenta", bold: true, children: " \u259F\u259B " }),
|
|
784
|
+
/* @__PURE__ */ jsx(Text, { color: "magenta", bold: true, children: "WrongStack" }),
|
|
785
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " v" }),
|
|
786
|
+
/* @__PURE__ */ jsx(Text, { children: entry.version })
|
|
787
|
+
] }),
|
|
788
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, italic: true, children: " Built on the wrong stack. Shipped anyway." }),
|
|
789
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
790
|
+
/* @__PURE__ */ jsx(Text, { color: "cyan", children: " provider " }),
|
|
791
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
792
|
+
entry.provider,
|
|
793
|
+
"/",
|
|
794
|
+
entry.model
|
|
795
|
+
] })
|
|
796
|
+
] }),
|
|
797
|
+
entry.family ? /* @__PURE__ */ jsxs(Text, { children: [
|
|
798
|
+
/* @__PURE__ */ jsx(Text, { color: "cyan", children: " family " }),
|
|
799
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: entry.family })
|
|
800
|
+
] }) : null,
|
|
801
|
+
entry.keyTail ? /* @__PURE__ */ jsxs(Text, { children: [
|
|
802
|
+
/* @__PURE__ */ jsx(Text, { color: "cyan", children: " key " }),
|
|
803
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u25CF\u25CF\u25CF\u2026" }),
|
|
804
|
+
/* @__PURE__ */ jsx(Text, { children: entry.keyTail })
|
|
805
|
+
] }) : null,
|
|
806
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
807
|
+
/* @__PURE__ */ jsx(Text, { color: "cyan", children: " cwd " }),
|
|
808
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: cwdShort })
|
|
809
|
+
] }),
|
|
810
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
811
|
+
/* @__PURE__ */ jsx(Text, { color: "cyan", children: " hints " }),
|
|
812
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "/help \xB7 /init \xB7 /memory \xB7 /queue \xB7 /exit" })
|
|
813
|
+
] })
|
|
814
|
+
] });
|
|
357
815
|
}
|
|
358
816
|
function shortenPath(p, max) {
|
|
359
817
|
if (p.length <= max) return p;
|
|
360
818
|
return `\u2026${p.slice(p.length - (max - 1))}`;
|
|
361
819
|
}
|
|
820
|
+
function fmtTok(n) {
|
|
821
|
+
if (!Number.isFinite(n) || n <= 0) return "0";
|
|
822
|
+
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
823
|
+
if (n >= 1e3) return `${(n / 1e3).toFixed(n >= 1e4 ? 0 : 1)}k`;
|
|
824
|
+
return String(n);
|
|
825
|
+
}
|
|
362
826
|
function fmtDuration(ms) {
|
|
363
827
|
if (ms < 1e3) return `${ms}ms`;
|
|
364
828
|
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
|
|
@@ -476,7 +940,7 @@ function formatToolArgs(toolName, input) {
|
|
|
476
940
|
return "";
|
|
477
941
|
}
|
|
478
942
|
}
|
|
479
|
-
function formatToolOutput(toolName, output, ok) {
|
|
943
|
+
function formatToolOutput(toolName, output, ok, _outputBytes, outputLines) {
|
|
480
944
|
if (!output) return ok ? [] : ["failed"];
|
|
481
945
|
const text = output.trim();
|
|
482
946
|
if (!text) return ok ? [] : ["failed"];
|
|
@@ -541,12 +1005,14 @@ function formatToolOutput(toolName, output, ok) {
|
|
|
541
1005
|
if (!diff) return [files && files.length === 0 ? "no changes" : "empty diff"];
|
|
542
1006
|
const head = [];
|
|
543
1007
|
if (mode) head.push(mode);
|
|
544
|
-
if (files && files.length > 0)
|
|
1008
|
+
if (files && files.length > 0)
|
|
1009
|
+
head.push(`${files.length} file${files.length === 1 ? "" : "s"}`);
|
|
545
1010
|
if (truncated) head.push("truncated");
|
|
546
1011
|
return head.length > 0 ? [head.join(" \xB7 ")] : [];
|
|
547
1012
|
}
|
|
548
1013
|
}
|
|
549
1014
|
if (toolName === "read") {
|
|
1015
|
+
if (outputLines !== void 0) return [];
|
|
550
1016
|
if (json && typeof json === "object") {
|
|
551
1017
|
const o = json;
|
|
552
1018
|
const bytes = numOf(o["bytes"]);
|
|
@@ -830,8 +1296,51 @@ function formatToolOutput(toolName, output, ok) {
|
|
|
830
1296
|
const lastLine = lines[lines.length - 1];
|
|
831
1297
|
return lastLine ? [head, `"${truncMid(lastLine.trim(), 70)}"`] : [head];
|
|
832
1298
|
}
|
|
833
|
-
|
|
834
|
-
|
|
1299
|
+
if (json && typeof json === "object" && !Array.isArray(json)) {
|
|
1300
|
+
const summary = summarizeJsonObject(json);
|
|
1301
|
+
if (summary) return [summary];
|
|
1302
|
+
}
|
|
1303
|
+
const collapsed = text.replace(/\s+/g, " ").trim();
|
|
1304
|
+
return [truncMid(collapsed, GENERIC_BUDGET)];
|
|
1305
|
+
}
|
|
1306
|
+
var GENERIC_BUDGET = 240;
|
|
1307
|
+
function summarizeJsonObject(obj) {
|
|
1308
|
+
const keys = Object.keys(obj);
|
|
1309
|
+
if (keys.length === 0) return null;
|
|
1310
|
+
const priority = [
|
|
1311
|
+
"ok",
|
|
1312
|
+
"status",
|
|
1313
|
+
"timedOut",
|
|
1314
|
+
"stopReason",
|
|
1315
|
+
"reason",
|
|
1316
|
+
"error",
|
|
1317
|
+
"message",
|
|
1318
|
+
"result",
|
|
1319
|
+
"summary",
|
|
1320
|
+
"iterations",
|
|
1321
|
+
"toolCalls",
|
|
1322
|
+
"durationMs",
|
|
1323
|
+
"subagentId",
|
|
1324
|
+
"taskId"
|
|
1325
|
+
];
|
|
1326
|
+
const ordered = [
|
|
1327
|
+
...priority.filter((k) => keys.includes(k)),
|
|
1328
|
+
...keys.filter((k) => !priority.includes(k))
|
|
1329
|
+
];
|
|
1330
|
+
const parts = [];
|
|
1331
|
+
let used = 0;
|
|
1332
|
+
for (const key of ordered) {
|
|
1333
|
+
const v = obj[key];
|
|
1334
|
+
if (v === void 0 || v === null) continue;
|
|
1335
|
+
const rendered = typeof v === "string" ? `${key}="${truncMid(v.replace(/\s+/g, " "), 80)}"` : typeof v === "number" || typeof v === "boolean" ? `${key}=${v}` : Array.isArray(v) ? `${key}=[${v.length}]` : `${key}={\u2026}`;
|
|
1336
|
+
if (used + rendered.length > GENERIC_BUDGET) {
|
|
1337
|
+
parts.push("\u2026");
|
|
1338
|
+
break;
|
|
1339
|
+
}
|
|
1340
|
+
parts.push(rendered);
|
|
1341
|
+
used += rendered.length + 3;
|
|
1342
|
+
}
|
|
1343
|
+
return parts.length > 0 ? parts.join(" \xB7 ") : null;
|
|
835
1344
|
}
|
|
836
1345
|
function firstNonEmpty(text) {
|
|
837
1346
|
if (!text) return void 0;
|
|
@@ -949,6 +1458,31 @@ function truncMid(s, max) {
|
|
|
949
1458
|
if (s.length <= max) return s;
|
|
950
1459
|
return `${s.slice(0, max - 1)}\u2026`;
|
|
951
1460
|
}
|
|
1461
|
+
function isHomeEnd(data) {
|
|
1462
|
+
if (data === "\x1B[H" || data === "\x1B[1~" || data === "\x1BOH" || data === "\x1B[7~")
|
|
1463
|
+
return "home";
|
|
1464
|
+
if (data === "\x1B[F" || data === "\x1B[4~" || data === "\x1BOF" || data === "\x1B[8~")
|
|
1465
|
+
return "end";
|
|
1466
|
+
return null;
|
|
1467
|
+
}
|
|
1468
|
+
var EMPTY_KEY = {
|
|
1469
|
+
upArrow: false,
|
|
1470
|
+
downArrow: false,
|
|
1471
|
+
leftArrow: false,
|
|
1472
|
+
rightArrow: false,
|
|
1473
|
+
return: false,
|
|
1474
|
+
escape: false,
|
|
1475
|
+
ctrl: false,
|
|
1476
|
+
meta: false,
|
|
1477
|
+
shift: false,
|
|
1478
|
+
tab: false,
|
|
1479
|
+
backspace: false,
|
|
1480
|
+
delete: false,
|
|
1481
|
+
pageUp: false,
|
|
1482
|
+
pageDown: false,
|
|
1483
|
+
home: false,
|
|
1484
|
+
end: false
|
|
1485
|
+
};
|
|
952
1486
|
function Input({
|
|
953
1487
|
prompt = "\u203A ",
|
|
954
1488
|
value,
|
|
@@ -962,6 +1496,19 @@ function Input({
|
|
|
962
1496
|
if (disabled) return;
|
|
963
1497
|
onKey(input, key);
|
|
964
1498
|
});
|
|
1499
|
+
const { stdin } = useStdin();
|
|
1500
|
+
useEffect(() => {
|
|
1501
|
+
if (!stdin || disabled) return;
|
|
1502
|
+
const handleData = (data) => {
|
|
1503
|
+
const kind = isHomeEnd(data.toString());
|
|
1504
|
+
if (kind === "home") onKey("", { ...EMPTY_KEY, home: true });
|
|
1505
|
+
else if (kind === "end") onKey("", { ...EMPTY_KEY, end: true });
|
|
1506
|
+
};
|
|
1507
|
+
stdin.on("data", handleData);
|
|
1508
|
+
return () => {
|
|
1509
|
+
stdin.off("data", handleData);
|
|
1510
|
+
};
|
|
1511
|
+
}, [stdin, disabled, onKey]);
|
|
965
1512
|
const before = value.slice(0, cursor);
|
|
966
1513
|
const at = value.slice(cursor, cursor + 1) || " ";
|
|
967
1514
|
const after = value.slice(cursor + 1);
|
|
@@ -983,6 +1530,72 @@ function Input({
|
|
|
983
1530
|
hint ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: hint }) : null
|
|
984
1531
|
] });
|
|
985
1532
|
}
|
|
1533
|
+
function ModelPicker({
|
|
1534
|
+
step,
|
|
1535
|
+
providerOptions,
|
|
1536
|
+
modelOptions,
|
|
1537
|
+
selected,
|
|
1538
|
+
pickedProviderId,
|
|
1539
|
+
hint
|
|
1540
|
+
}) {
|
|
1541
|
+
if (step === "provider") {
|
|
1542
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
|
|
1543
|
+
/* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "\u2501\u2501 Switch model \u2014 Step 1/2: Pick provider \u2501\u2501" }),
|
|
1544
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 navigate \xB7 Enter select \xB7 Esc cancel" }),
|
|
1545
|
+
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: [
|
|
1546
|
+
i === selected ? "\u203A " : " ",
|
|
1547
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: p.id.padEnd(28) }),
|
|
1548
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1549
|
+
" [",
|
|
1550
|
+
p.family,
|
|
1551
|
+
"]"
|
|
1552
|
+
] }),
|
|
1553
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1554
|
+
" ",
|
|
1555
|
+
p.models.length,
|
|
1556
|
+
" model",
|
|
1557
|
+
p.models.length === 1 ? "" : "s"
|
|
1558
|
+
] })
|
|
1559
|
+
] }, p.id)),
|
|
1560
|
+
hint ? /* @__PURE__ */ jsx(Text, { color: "yellow", children: hint }) : null
|
|
1561
|
+
] });
|
|
1562
|
+
}
|
|
1563
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
|
|
1564
|
+
/* @__PURE__ */ jsxs(Text, { color: "cyan", bold: true, children: [
|
|
1565
|
+
"\u2501\u2501 Switch model \u2014 Step 2/2: Pick model (",
|
|
1566
|
+
pickedProviderId,
|
|
1567
|
+
") \u2501\u2501"
|
|
1568
|
+
] }),
|
|
1569
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 navigate \xB7 Enter select \xB7 Esc back \xB7 Ctrl-C cancel" }),
|
|
1570
|
+
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: [
|
|
1571
|
+
i === selected ? "\u203A " : " ",
|
|
1572
|
+
id
|
|
1573
|
+
] }, id)),
|
|
1574
|
+
hint ? /* @__PURE__ */ jsx(Text, { color: "yellow", children: hint }) : null
|
|
1575
|
+
] });
|
|
1576
|
+
}
|
|
1577
|
+
function SlashMenu({ query, matches, selected }) {
|
|
1578
|
+
const placeholder = query ? `/${query}` : "/";
|
|
1579
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
|
|
1580
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1581
|
+
placeholder || "/",
|
|
1582
|
+
" \u2014 \u2191/\u2193 select, Enter dispatch, Tab autocomplete, Esc close"
|
|
1583
|
+
] }),
|
|
1584
|
+
matches.map((m, i) => /* @__PURE__ */ jsxs(Text, { color: i === selected ? "cyan" : void 0, inverse: i === selected, children: [
|
|
1585
|
+
i === selected ? "\u203A " : " ",
|
|
1586
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: m.name }),
|
|
1587
|
+
m.argsHint ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1588
|
+
" ",
|
|
1589
|
+
m.argsHint
|
|
1590
|
+
] }) : null,
|
|
1591
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1592
|
+
" \u2014 ",
|
|
1593
|
+
m.description
|
|
1594
|
+
] })
|
|
1595
|
+
] }, m.name)),
|
|
1596
|
+
matches.length === 0 && /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No matching commands" })
|
|
1597
|
+
] });
|
|
1598
|
+
}
|
|
986
1599
|
function StatusBar({
|
|
987
1600
|
model,
|
|
988
1601
|
state,
|
|
@@ -992,6 +1605,9 @@ function StatusBar({
|
|
|
992
1605
|
yolo = false,
|
|
993
1606
|
elapsedMs,
|
|
994
1607
|
todos,
|
|
1608
|
+
plan,
|
|
1609
|
+
fleet,
|
|
1610
|
+
fleetAgents,
|
|
995
1611
|
git,
|
|
996
1612
|
subagentCount = 0,
|
|
997
1613
|
context,
|
|
@@ -1002,7 +1618,9 @@ function StatusBar({
|
|
|
1002
1618
|
const cache2 = tokenCounter?.cacheStats();
|
|
1003
1619
|
const stateColor = state === "idle" ? "cyan" : state === "aborting" ? "yellow" : "green";
|
|
1004
1620
|
const stateLabel = state === "idle" ? "idle" : state === "aborting" ? "aborting\u2026" : "thinking\u2026";
|
|
1005
|
-
const hasSecondLine = yolo || elapsedMs !== void 0 ||
|
|
1621
|
+
const hasSecondLine = yolo || elapsedMs !== void 0 || git !== null && git !== void 0 || projectName !== void 0 && projectName.length > 0;
|
|
1622
|
+
const fleetHasActivity = fleet && (fleet.running > 0 || fleet.idle > 0 || fleet.pending > 0 || fleet.completed > 0) || subagentCount > 0;
|
|
1623
|
+
const hasThirdLine = todos && (todos.pending > 0 || todos.inProgress > 0 || todos.completed > 0) || plan && (plan.open > 0 || plan.inProgress > 0 || plan.done > 0) || fleetHasActivity;
|
|
1006
1624
|
return /* @__PURE__ */ jsxs(
|
|
1007
1625
|
Box,
|
|
1008
1626
|
{
|
|
@@ -1028,11 +1646,12 @@ function StatusBar({
|
|
|
1028
1646
|
usage ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1029
1647
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
|
|
1030
1648
|
/* @__PURE__ */ jsxs(Text, { children: [
|
|
1031
|
-
"\u2191
|
|
1032
|
-
/* @__PURE__ */ jsx(Text, { color: "cyan", children: fmtTok(usage.input) }),
|
|
1033
|
-
" \u2193",
|
|
1649
|
+
"\u2191",
|
|
1034
1650
|
" ",
|
|
1035
|
-
/* @__PURE__ */ jsx(Text, { color: "cyan", children:
|
|
1651
|
+
/* @__PURE__ */ jsx(Text, { color: "cyan", children: fmtTok2(usage.input + (usage.cacheRead ?? 0) + (usage.cacheWrite ?? 0)) }),
|
|
1652
|
+
" ",
|
|
1653
|
+
"\u2193 ",
|
|
1654
|
+
/* @__PURE__ */ jsx(Text, { color: "cyan", children: fmtTok2(usage.output) })
|
|
1036
1655
|
] })
|
|
1037
1656
|
] }) : null,
|
|
1038
1657
|
cache2 && cache2.hitRatio > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
@@ -1068,7 +1687,7 @@ function StatusBar({
|
|
|
1068
1687
|
yolo ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }) : null,
|
|
1069
1688
|
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1070
1689
|
"\u23F1 ",
|
|
1071
|
-
|
|
1690
|
+
fmtElapsed2(elapsedMs)
|
|
1072
1691
|
] })
|
|
1073
1692
|
] }) : null,
|
|
1074
1693
|
projectName ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
@@ -1098,36 +1717,93 @@ function StatusBar({
|
|
|
1098
1717
|
git.untracked
|
|
1099
1718
|
] }) : null
|
|
1100
1719
|
] })
|
|
1720
|
+
] }) : null
|
|
1721
|
+
] }) : null,
|
|
1722
|
+
hasThirdLine ? /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 2, children: [
|
|
1723
|
+
todos && (todos.pending > 0 || todos.inProgress > 0 || todos.completed > 0) ? /* @__PURE__ */ jsxs(Text, { children: [
|
|
1724
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "todos " }),
|
|
1725
|
+
todos.inProgress > 0 ? /* @__PURE__ */ jsxs(Text, { color: "yellow", children: [
|
|
1726
|
+
"\u231B",
|
|
1727
|
+
todos.inProgress
|
|
1728
|
+
] }) : null,
|
|
1729
|
+
todos.inProgress > 0 && (todos.pending > 0 || todos.completed > 0) ? " " : "",
|
|
1730
|
+
todos.pending > 0 ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1731
|
+
"\u2610",
|
|
1732
|
+
todos.pending
|
|
1733
|
+
] }) : null,
|
|
1734
|
+
todos.pending > 0 && todos.completed > 0 ? " " : "",
|
|
1735
|
+
todos.completed > 0 ? /* @__PURE__ */ jsxs(Text, { color: "green", children: [
|
|
1736
|
+
"\u2713",
|
|
1737
|
+
todos.completed
|
|
1738
|
+
] }) : null
|
|
1101
1739
|
] }) : null,
|
|
1102
|
-
|
|
1103
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
|
|
1740
|
+
plan && (plan.open > 0 || plan.inProgress > 0 || plan.done > 0) ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1741
|
+
todos && (todos.pending > 0 || todos.inProgress > 0 || todos.completed > 0) ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }) : null,
|
|
1104
1742
|
/* @__PURE__ */ jsxs(Text, { children: [
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1743
|
+
/* @__PURE__ */ jsx(Text, { color: "cyan", children: "\u{1F4CB} " }),
|
|
1744
|
+
plan.inProgress > 0 ? /* @__PURE__ */ jsxs(Text, { color: "yellow", children: [
|
|
1745
|
+
"\u231B",
|
|
1746
|
+
plan.inProgress
|
|
1108
1747
|
] }) : null,
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
"\u2610
|
|
1112
|
-
|
|
1748
|
+
plan.inProgress > 0 && (plan.open > 0 || plan.done > 0) ? " " : "",
|
|
1749
|
+
plan.open > 0 ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1750
|
+
"\u2610",
|
|
1751
|
+
plan.open
|
|
1113
1752
|
] }) : null,
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
"\u2713
|
|
1117
|
-
|
|
1753
|
+
plan.open > 0 && plan.done > 0 ? " " : "",
|
|
1754
|
+
plan.done > 0 ? /* @__PURE__ */ jsxs(Text, { color: "green", children: [
|
|
1755
|
+
"\u2713",
|
|
1756
|
+
plan.done
|
|
1118
1757
|
] }) : null
|
|
1119
1758
|
] })
|
|
1120
1759
|
] }) : null,
|
|
1121
|
-
|
|
1122
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
|
|
1123
|
-
/* @__PURE__ */ jsxs(Text, {
|
|
1760
|
+
fleetHasActivity ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1761
|
+
todos && (todos.pending > 0 || todos.inProgress > 0 || todos.completed > 0) || plan && (plan.open > 0 || plan.inProgress > 0 || plan.done > 0) ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }) : null,
|
|
1762
|
+
fleet ? /* @__PURE__ */ jsxs(Text, { children: [
|
|
1763
|
+
/* @__PURE__ */ jsx(Text, { color: "blue", children: "\u{1F310} " }),
|
|
1764
|
+
fleet.running > 0 ? /* @__PURE__ */ jsxs(Text, { color: "yellow", children: [
|
|
1765
|
+
"\u25B6",
|
|
1766
|
+
fleet.running
|
|
1767
|
+
] }) : null,
|
|
1768
|
+
fleet.running > 0 && (fleet.pending > 0 || fleet.idle > 0 || fleet.completed > 0) ? " " : "",
|
|
1769
|
+
fleet.pending > 0 ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1770
|
+
"\u2610",
|
|
1771
|
+
fleet.pending
|
|
1772
|
+
] }) : null,
|
|
1773
|
+
fleet.pending > 0 && (fleet.idle > 0 || fleet.completed > 0) ? " " : "",
|
|
1774
|
+
fleet.idle > 0 ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1775
|
+
"\xB7",
|
|
1776
|
+
fleet.idle,
|
|
1777
|
+
"idle"
|
|
1778
|
+
] }) : null,
|
|
1779
|
+
fleet.idle > 0 && fleet.completed > 0 ? " " : "",
|
|
1780
|
+
fleet.completed > 0 ? /* @__PURE__ */ jsxs(Text, { color: "green", children: [
|
|
1781
|
+
"\u2713",
|
|
1782
|
+
fleet.completed
|
|
1783
|
+
] }) : null
|
|
1784
|
+
] }) : /* @__PURE__ */ jsxs(Text, { color: "blue", children: [
|
|
1124
1785
|
"\u{1F310} ",
|
|
1125
1786
|
subagentCount,
|
|
1126
1787
|
" agent",
|
|
1127
1788
|
subagentCount === 1 ? "" : "s"
|
|
1128
1789
|
] })
|
|
1129
1790
|
] }) : null
|
|
1130
|
-
] }) : null
|
|
1791
|
+
] }) : null,
|
|
1792
|
+
fleetAgents && fleetAgents.length > 0 ? /* @__PURE__ */ jsx(Box, { flexDirection: "row", gap: 2, children: fleetAgents.map((a, i) => (
|
|
1793
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: agent list is stable per render
|
|
1794
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
1795
|
+
/* @__PURE__ */ jsx(Text, { color: a.color, bold: true, children: a.label }),
|
|
1796
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " " }),
|
|
1797
|
+
/* @__PURE__ */ jsx(Text, { color: a.running ? "yellow" : void 0, dimColor: !a.running, children: a.running ? "\u25B6" : "\xB7" }),
|
|
1798
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " " }),
|
|
1799
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: fmtElapsed2(a.elapsedMs) }),
|
|
1800
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " \xB7 " }),
|
|
1801
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1802
|
+
a.toolCalls,
|
|
1803
|
+
"t"
|
|
1804
|
+
] })
|
|
1805
|
+
] }, i)
|
|
1806
|
+
)) }) : null
|
|
1131
1807
|
]
|
|
1132
1808
|
}
|
|
1133
1809
|
);
|
|
@@ -1147,9 +1823,9 @@ function ContextChip({ ctx }) {
|
|
|
1147
1823
|
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1148
1824
|
" ",
|
|
1149
1825
|
"(",
|
|
1150
|
-
|
|
1826
|
+
fmtTok2(ctx.used),
|
|
1151
1827
|
"/",
|
|
1152
|
-
|
|
1828
|
+
fmtTok2(ctx.max),
|
|
1153
1829
|
")"
|
|
1154
1830
|
] })
|
|
1155
1831
|
] });
|
|
@@ -1162,179 +1838,23 @@ function renderProgress(ratio, width) {
|
|
|
1162
1838
|
const capped = Math.min(width, filled);
|
|
1163
1839
|
return FILLED.repeat(capped) + EMPTY.repeat(width - capped);
|
|
1164
1840
|
}
|
|
1165
|
-
function
|
|
1841
|
+
function fmtTok2(n) {
|
|
1166
1842
|
if (n < 1e3) return String(n);
|
|
1167
1843
|
if (n < 1e6) return `${(n / 1e3).toFixed(n < 1e4 ? 1 : 0)}k`;
|
|
1168
1844
|
return `${(n / 1e6).toFixed(1)}M`;
|
|
1169
1845
|
}
|
|
1170
|
-
function
|
|
1846
|
+
function fmtElapsed2(ms) {
|
|
1171
1847
|
const totalSec = Math.floor(ms / 1e3);
|
|
1172
1848
|
const h = Math.floor(totalSec / 3600);
|
|
1173
1849
|
const m = Math.floor(totalSec % 3600 / 60);
|
|
1174
1850
|
const s = totalSec % 60;
|
|
1175
1851
|
if (h > 0) {
|
|
1176
1852
|
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)) });
|
|
1853
|
+
}
|
|
1854
|
+
return `${pad2(m)}:${pad2(s)}`;
|
|
1297
1855
|
}
|
|
1298
|
-
function
|
|
1299
|
-
|
|
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 })
|
|
1319
|
-
] }),
|
|
1320
|
-
inputSummary ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: inputSummary }) : null,
|
|
1321
|
-
showDiff && diff ? /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginY: 1, children: renderDiff(diff) }) : null,
|
|
1322
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }),
|
|
1323
|
-
/* @__PURE__ */ jsx(Box, { flexDirection: "row", children: /* @__PURE__ */ jsxs(Text, { children: [
|
|
1324
|
-
/* @__PURE__ */ jsx(Text, { bold: true, color: "green", children: "[\u21B5]" }),
|
|
1325
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " yes " }),
|
|
1326
|
-
/* @__PURE__ */ jsx(Text, { bold: true, color: "red", children: "[Esc]" }),
|
|
1327
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " no " }),
|
|
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
|
-
] }) })
|
|
1337
|
-
] });
|
|
1856
|
+
function pad2(n) {
|
|
1857
|
+
return n < 10 ? `0${n}` : String(n);
|
|
1338
1858
|
}
|
|
1339
1859
|
var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
1340
1860
|
"node_modules",
|
|
@@ -1369,7 +1889,7 @@ async function walk(root, rel, depth, out) {
|
|
|
1369
1889
|
const dir = rel ? path3.join(root, rel) : root;
|
|
1370
1890
|
let entries;
|
|
1371
1891
|
try {
|
|
1372
|
-
entries = await
|
|
1892
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
1373
1893
|
} catch {
|
|
1374
1894
|
return;
|
|
1375
1895
|
}
|
|
@@ -1414,108 +1934,56 @@ async function searchFiles(root, query, limit = 8) {
|
|
|
1414
1934
|
scored.sort((a, b) => a.score - b.score);
|
|
1415
1935
|
return scored.slice(0, limit).map((x) => x.path);
|
|
1416
1936
|
}
|
|
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);
|
|
1937
|
+
async function readGitInfo(cwd) {
|
|
1938
|
+
const [branchRes, numstatRes, statusRes] = await Promise.all([
|
|
1939
|
+
runGit(cwd, ["branch", "--show-current"]),
|
|
1940
|
+
runGit(cwd, ["diff", "HEAD", "--numstat"]),
|
|
1941
|
+
runGit(cwd, ["status", "--porcelain"])
|
|
1942
|
+
]);
|
|
1943
|
+
if (!branchRes.ok || !numstatRes.ok || !statusRes.ok) return null;
|
|
1944
|
+
const branch = branchRes.stdout.trim();
|
|
1945
|
+
const branchLabel = branch || await detachedShortSha(cwd) || "detached";
|
|
1946
|
+
let added = 0;
|
|
1947
|
+
let deleted = 0;
|
|
1948
|
+
for (const line of numstatRes.stdout.split("\n")) {
|
|
1949
|
+
if (!line) continue;
|
|
1950
|
+
const [a, d] = line.split(" ");
|
|
1951
|
+
if (a && a !== "-") added += Number.parseInt(a, 10) || 0;
|
|
1952
|
+
if (d && d !== "-") deleted += Number.parseInt(d, 10) || 0;
|
|
1468
1953
|
}
|
|
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;
|
|
1954
|
+
let untracked = 0;
|
|
1955
|
+
for (const line of statusRes.stdout.split("\n")) {
|
|
1956
|
+
if (line.startsWith("?? ")) untracked++;
|
|
1491
1957
|
}
|
|
1958
|
+
return { branch: branchLabel, added, deleted, untracked };
|
|
1492
1959
|
}
|
|
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
|
-
});
|
|
1960
|
+
async function detachedShortSha(cwd) {
|
|
1961
|
+
const res = await runGit(cwd, ["rev-parse", "--short", "HEAD"]);
|
|
1962
|
+
return res.ok ? res.stdout.trim() : null;
|
|
1503
1963
|
}
|
|
1504
|
-
function
|
|
1964
|
+
function runGit(cwd, args) {
|
|
1505
1965
|
return new Promise((resolve) => {
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
}
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1966
|
+
let stdout = "";
|
|
1967
|
+
try {
|
|
1968
|
+
const child = spawn("git", args, {
|
|
1969
|
+
cwd,
|
|
1970
|
+
// Inherit stderr (silent) — we don't care about git's noise.
|
|
1971
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
1972
|
+
// Don't let a slow git hang the TUI.
|
|
1973
|
+
timeout: 3e3,
|
|
1974
|
+
windowsHide: true
|
|
1975
|
+
});
|
|
1976
|
+
child.stdout.setEncoding("utf8");
|
|
1977
|
+
child.stdout.on("data", (chunk) => {
|
|
1978
|
+
stdout += chunk;
|
|
1979
|
+
});
|
|
1980
|
+
child.on("error", () => resolve({ ok: false, stdout: "" }));
|
|
1981
|
+
child.on("close", (code) => {
|
|
1982
|
+
resolve({ ok: code === 0, stdout });
|
|
1983
|
+
});
|
|
1984
|
+
} catch {
|
|
1985
|
+
resolve({ ok: false, stdout: "" });
|
|
1986
|
+
}
|
|
1519
1987
|
});
|
|
1520
1988
|
}
|
|
1521
1989
|
|
|
@@ -1567,7 +2035,8 @@ function handleQueueCommand(args, deps) {
|
|
|
1567
2035
|
if (uniqueValid.length === 0) {
|
|
1568
2036
|
const parts2 = ["No valid positions to delete."];
|
|
1569
2037
|
if (invalid.length > 0) parts2.push(`Invalid: ${invalid.join(", ")}.`);
|
|
1570
|
-
if (outOfRange.length > 0)
|
|
2038
|
+
if (outOfRange.length > 0)
|
|
2039
|
+
parts2.push(`Out of range (queue has ${queue.length}): ${outOfRange.join(", ")}.`);
|
|
1571
2040
|
return parts2.join(" ");
|
|
1572
2041
|
}
|
|
1573
2042
|
deps.deleteAt(uniqueValid);
|
|
@@ -1596,65 +2065,10 @@ function oneLine(s, max) {
|
|
|
1596
2065
|
const collapsed = s.replace(/\s+/g, " ").trim();
|
|
1597
2066
|
return collapsed.length <= max ? collapsed : `${collapsed.slice(0, max - 1)}\u2026`;
|
|
1598
2067
|
}
|
|
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
2068
|
function reducer(state, action) {
|
|
1652
2069
|
switch (action.type) {
|
|
1653
2070
|
case "addEntry": {
|
|
1654
|
-
const appended = [
|
|
1655
|
-
...state.entries,
|
|
1656
|
-
{ ...action.entry, id: state.nextId }
|
|
1657
|
-
];
|
|
2071
|
+
const appended = [...state.entries, { ...action.entry, id: state.nextId }];
|
|
1658
2072
|
return { ...state, entries: appended, nextId: state.nextId + 1 };
|
|
1659
2073
|
}
|
|
1660
2074
|
case "setBuffer":
|
|
@@ -1687,6 +2101,10 @@ function reducer(state, action) {
|
|
|
1687
2101
|
return { ...state, status: action.status };
|
|
1688
2102
|
case "interrupt":
|
|
1689
2103
|
return { ...state, interrupts: state.interrupts + 1 };
|
|
2104
|
+
case "steerStart":
|
|
2105
|
+
return { ...state, steeringPending: true, steerSnapshot: action.snapshot };
|
|
2106
|
+
case "steerConsume":
|
|
2107
|
+
return { ...state, steeringPending: false, steerSnapshot: null };
|
|
1690
2108
|
case "resetInterrupts":
|
|
1691
2109
|
return { ...state, interrupts: 0 };
|
|
1692
2110
|
case "hint":
|
|
@@ -1748,14 +2166,20 @@ function reducer(state, action) {
|
|
|
1748
2166
|
}
|
|
1749
2167
|
return {
|
|
1750
2168
|
...state,
|
|
1751
|
-
toolStream: {
|
|
2169
|
+
toolStream: {
|
|
2170
|
+
toolUseId: action.toolUseId,
|
|
2171
|
+
name: action.name,
|
|
2172
|
+
text: action.text,
|
|
2173
|
+
startedAt: action.startedAt
|
|
2174
|
+
}
|
|
1752
2175
|
};
|
|
1753
2176
|
}
|
|
1754
2177
|
case "toolStreamClear": {
|
|
1755
2178
|
if (state.toolStream === null) return state;
|
|
1756
2179
|
const t = state.toolStream;
|
|
1757
2180
|
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)
|
|
2181
|
+
if (action.name !== void 0 && action.toolUseId === void 0 && action.name !== t.name)
|
|
2182
|
+
return state;
|
|
1759
2183
|
return { ...state, toolStream: null };
|
|
1760
2184
|
}
|
|
1761
2185
|
case "enqueue": {
|
|
@@ -1875,15 +2299,240 @@ function reducer(state, action) {
|
|
|
1875
2299
|
...state,
|
|
1876
2300
|
modelPicker: { ...state.modelPicker, hint: action.text }
|
|
1877
2301
|
};
|
|
1878
|
-
case "confirmOpen":
|
|
1879
|
-
return { ...state, confirm: action.info };
|
|
1880
|
-
case "confirmClose":
|
|
1881
|
-
return { ...state, confirm: null };
|
|
1882
|
-
case "resetContextChip":
|
|
1883
|
-
return { ...state, contextChipVersion: state.contextChipVersion + 1 };
|
|
2302
|
+
case "confirmOpen":
|
|
2303
|
+
return { ...state, confirm: action.info };
|
|
2304
|
+
case "confirmClose":
|
|
2305
|
+
return { ...state, confirm: null };
|
|
2306
|
+
case "resetContextChip":
|
|
2307
|
+
return { ...state, contextChipVersion: state.contextChipVersion + 1 };
|
|
2308
|
+
// --- Fleet ---
|
|
2309
|
+
case "fleetSeed": {
|
|
2310
|
+
const seeded = {};
|
|
2311
|
+
for (const e of action.entries) seeded[e.id] = e;
|
|
2312
|
+
return { ...state, fleet: seeded, fleetCost: action.cost };
|
|
2313
|
+
}
|
|
2314
|
+
case "fleetSpawn": {
|
|
2315
|
+
if (state.fleet[action.id]) return state;
|
|
2316
|
+
const entry = {
|
|
2317
|
+
id: action.id,
|
|
2318
|
+
name: action.name ?? action.id.slice(0, 8),
|
|
2319
|
+
provider: action.provider,
|
|
2320
|
+
model: action.model,
|
|
2321
|
+
status: "idle",
|
|
2322
|
+
streamingText: "",
|
|
2323
|
+
iterations: 0,
|
|
2324
|
+
toolCalls: 0,
|
|
2325
|
+
cost: 0,
|
|
2326
|
+
startedAt: Date.now(),
|
|
2327
|
+
lastEventAt: Date.now(),
|
|
2328
|
+
transcriptPath: action.transcriptPath
|
|
2329
|
+
};
|
|
2330
|
+
return { ...state, fleet: { ...state.fleet, [action.id]: entry } };
|
|
2331
|
+
}
|
|
2332
|
+
case "fleetToolStart": {
|
|
2333
|
+
const cur = state.fleet[action.id];
|
|
2334
|
+
if (!cur) return state;
|
|
2335
|
+
return {
|
|
2336
|
+
...state,
|
|
2337
|
+
fleet: {
|
|
2338
|
+
...state.fleet,
|
|
2339
|
+
[action.id]: {
|
|
2340
|
+
...cur,
|
|
2341
|
+
currentTool: { name: action.name, startedAt: Date.now() },
|
|
2342
|
+
lastEventAt: Date.now()
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
};
|
|
2346
|
+
}
|
|
2347
|
+
case "fleetToolEnd": {
|
|
2348
|
+
const cur = state.fleet[action.id];
|
|
2349
|
+
if (!cur) return state;
|
|
2350
|
+
return {
|
|
2351
|
+
...state,
|
|
2352
|
+
fleet: {
|
|
2353
|
+
...state.fleet,
|
|
2354
|
+
[action.id]: { ...cur, currentTool: void 0, lastEventAt: Date.now() }
|
|
2355
|
+
}
|
|
2356
|
+
};
|
|
2357
|
+
}
|
|
2358
|
+
case "fleetStart": {
|
|
2359
|
+
const cur = state.fleet[action.id];
|
|
2360
|
+
if (!cur) return state;
|
|
2361
|
+
return {
|
|
2362
|
+
...state,
|
|
2363
|
+
fleet: {
|
|
2364
|
+
...state.fleet,
|
|
2365
|
+
[action.id]: {
|
|
2366
|
+
...cur,
|
|
2367
|
+
status: "running",
|
|
2368
|
+
streamingText: "",
|
|
2369
|
+
startedAt: Date.now()
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2372
|
+
};
|
|
2373
|
+
}
|
|
2374
|
+
case "fleetDelta": {
|
|
2375
|
+
const cur = state.fleet[action.id];
|
|
2376
|
+
if (!cur) return state;
|
|
2377
|
+
const appended = (cur.streamingText + action.text).slice(-200);
|
|
2378
|
+
return {
|
|
2379
|
+
...state,
|
|
2380
|
+
fleet: {
|
|
2381
|
+
...state.fleet,
|
|
2382
|
+
[action.id]: { ...cur, streamingText: appended, lastEventAt: Date.now() }
|
|
2383
|
+
}
|
|
2384
|
+
};
|
|
2385
|
+
}
|
|
2386
|
+
case "fleetTool": {
|
|
2387
|
+
const cur = state.fleet[action.id];
|
|
2388
|
+
if (!cur) return state;
|
|
2389
|
+
return {
|
|
2390
|
+
...state,
|
|
2391
|
+
fleet: {
|
|
2392
|
+
...state.fleet,
|
|
2393
|
+
[action.id]: { ...cur, toolCalls: cur.toolCalls + 1, lastEventAt: Date.now() }
|
|
2394
|
+
}
|
|
2395
|
+
};
|
|
2396
|
+
}
|
|
2397
|
+
case "fleetUsage": {
|
|
2398
|
+
const cur = state.fleet[action.id];
|
|
2399
|
+
if (!cur) return state;
|
|
2400
|
+
const cost = cur.cost;
|
|
2401
|
+
return {
|
|
2402
|
+
...state,
|
|
2403
|
+
fleet: { ...state.fleet, [action.id]: { ...cur, cost, lastEventAt: Date.now() } }
|
|
2404
|
+
};
|
|
2405
|
+
}
|
|
2406
|
+
case "fleetDone": {
|
|
2407
|
+
const cur = state.fleet[action.id];
|
|
2408
|
+
if (!cur) return state;
|
|
2409
|
+
return {
|
|
2410
|
+
...state,
|
|
2411
|
+
fleet: {
|
|
2412
|
+
...state.fleet,
|
|
2413
|
+
[action.id]: {
|
|
2414
|
+
...cur,
|
|
2415
|
+
status: action.status,
|
|
2416
|
+
iterations: action.iterations,
|
|
2417
|
+
toolCalls: action.toolCalls,
|
|
2418
|
+
streamingText: "",
|
|
2419
|
+
lastEventAt: Date.now()
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
};
|
|
2423
|
+
}
|
|
2424
|
+
case "fleetCost": {
|
|
2425
|
+
return { ...state, fleetCost: action.cost };
|
|
2426
|
+
}
|
|
2427
|
+
case "setStreamFleet": {
|
|
2428
|
+
return { ...state, streamFleet: action.enabled };
|
|
2429
|
+
}
|
|
1884
2430
|
}
|
|
1885
2431
|
}
|
|
1886
2432
|
var PASTE_THRESHOLD_CHARS = 200;
|
|
2433
|
+
function buildSteeringPreamble(snapshot, newDirection) {
|
|
2434
|
+
const lines = ["[STEERING \u2014 I pressed Esc to interrupt you mid-task on purpose.", ""];
|
|
2435
|
+
const ctx = [];
|
|
2436
|
+
if (snapshot?.runningTools && snapshot.runningTools.length > 0) {
|
|
2437
|
+
ctx.push(`- in-flight tools (now cancelled): ${snapshot.runningTools.join(", ")}`);
|
|
2438
|
+
}
|
|
2439
|
+
if (snapshot?.subagentsTerminated && snapshot.subagentsTerminated > 0) {
|
|
2440
|
+
const subDetails = snapshot.subagents.map((s) => `${s.label}${s.tool ? ` (was running: ${s.tool})` : ""}`).join(", ");
|
|
2441
|
+
ctx.push(
|
|
2442
|
+
`- subagents (${snapshot.subagentsTerminated} terminated by me, do NOT await them): ${subDetails}`
|
|
2443
|
+
);
|
|
2444
|
+
}
|
|
2445
|
+
if (snapshot?.partialAssistantText && snapshot.partialAssistantText.trim().length > 0) {
|
|
2446
|
+
const tail = snapshot.partialAssistantText.trim().slice(-300);
|
|
2447
|
+
ctx.push(`- your last partial output (truncated, for context only): "${tail}"`);
|
|
2448
|
+
}
|
|
2449
|
+
if (ctx.length > 0) {
|
|
2450
|
+
lines.push("What was happening when I cut you off:");
|
|
2451
|
+
lines.push(...ctx);
|
|
2452
|
+
lines.push("");
|
|
2453
|
+
}
|
|
2454
|
+
lines.push("You have authority to:");
|
|
2455
|
+
lines.push("- Abandon the prior plan entirely if the new direction makes it stale.");
|
|
2456
|
+
lines.push("- Re-spawn fresh subagents (with different roles or tasks) if needed.");
|
|
2457
|
+
lines.push('- Skip a polite "should I continue?" \u2014 just pivot.');
|
|
2458
|
+
lines.push("- Ask me to clarify if the new direction is genuinely ambiguous.");
|
|
2459
|
+
lines.push("");
|
|
2460
|
+
lines.push("New direction:");
|
|
2461
|
+
lines.push("---");
|
|
2462
|
+
lines.push(newDirection);
|
|
2463
|
+
lines.push("---");
|
|
2464
|
+
lines.push("]");
|
|
2465
|
+
return lines.join("\n");
|
|
2466
|
+
}
|
|
2467
|
+
function buildGoalPreamble(goal) {
|
|
2468
|
+
return [
|
|
2469
|
+
"[GOAL \u2014 LOCKED IN. You will work on this until it is verifiably done.",
|
|
2470
|
+
"The user granted you full autonomy. Read these constraints once, then act.",
|
|
2471
|
+
"",
|
|
2472
|
+
"YOUR GOAL:",
|
|
2473
|
+
"---",
|
|
2474
|
+
goal,
|
|
2475
|
+
"---",
|
|
2476
|
+
"",
|
|
2477
|
+
"AUTHORITY YOU HAVE:",
|
|
2478
|
+
"- Spawn as many subagents as the work needs (delegate / spawn_subagent).",
|
|
2479
|
+
" Parallel + recursive fan-out are both fine. There is no spawn budget.",
|
|
2480
|
+
"- Use any provider/model per subagent \u2014 pick the right tool for each",
|
|
2481
|
+
" piece of work. Heavy reasoning model for planning, fast model for",
|
|
2482
|
+
" batch work, specialist model for domain code.",
|
|
2483
|
+
"- Run unlimited tool calls and iterations. There is NO hidden budget.",
|
|
2484
|
+
" The Agent loop auto-extends every 100 iterations forever.",
|
|
2485
|
+
"- Retry failed tools with different inputs, alternative paths, fresh",
|
|
2486
|
+
" subagents. Switch providers mid-run if one is rate-limited.",
|
|
2487
|
+
"- Re-plan freely when an approach hits a dead end. You are not obliged",
|
|
2488
|
+
" to stick with the first plan you proposed.",
|
|
2489
|
+
"",
|
|
2490
|
+
'WHAT "DONE" MEANS \u2014 non-negotiable:',
|
|
2491
|
+
"- You can name a concrete artifact (a passing test, a written file at",
|
|
2492
|
+
" a specific path, a fixed bug verified by re-running the failing case,",
|
|
2493
|
+
" a clean grep that previously had matches).",
|
|
2494
|
+
"- You can tell the user HOW to verify it themselves in 10 seconds.",
|
|
2495
|
+
'- You have NOT hedged. None of: "looks like it should work", "I',
|
|
2496
|
+
' believe this fixes it", "the changes appear correct".',
|
|
2497
|
+
"",
|
|
2498
|
+
"WHAT IS NOT DONE \u2014 never report any of these as completion:",
|
|
2499
|
+
"- An error message you didn't recover from.",
|
|
2500
|
+
'- An empty result, a 0-line file, a "no matches found" you accepted',
|
|
2501
|
+
" without questioning the search.",
|
|
2502
|
+
'- "Should I continue?" / "Want me to also...?" / "Let me know if you',
|
|
2503
|
+
' want X." Those are hedges. The user already told you to finish the',
|
|
2504
|
+
" goal \u2014 just do it.",
|
|
2505
|
+
"- Partial progress dressed up as success. Fixed 3 of 5 bugs = 60%",
|
|
2506
|
+
" done, not done.",
|
|
2507
|
+
"- A subagent's failed/timeout/stopped TaskResult that you didn't",
|
|
2508
|
+
" respond to with a fresh attempt (different role, different model,",
|
|
2509
|
+
" tighter prompt).",
|
|
2510
|
+
"",
|
|
2511
|
+
"PERSISTENCE PROTOCOL:",
|
|
2512
|
+
"- If blocked, try at least 3 different angles before reporting the",
|
|
2513
|
+
" problem to the user. Different tool inputs, different subagent",
|
|
2514
|
+
" roles, different providers, different decomposition of the task.",
|
|
2515
|
+
"- If a tool fails, read its error, alter the input, try again. Do",
|
|
2516
|
+
" not just report the failure back.",
|
|
2517
|
+
"- If a subagent returns useless output, respawn with a tighter prompt",
|
|
2518
|
+
' or a different role. Do not accept "I could not determine\u2026" as the',
|
|
2519
|
+
" final answer.",
|
|
2520
|
+
"- Use `ask_subagent` for one-shot questions when you don't need a",
|
|
2521
|
+
" full delegated task.",
|
|
2522
|
+
"",
|
|
2523
|
+
"REPORTING:",
|
|
2524
|
+
"- Stream short progress notes between major actions so the user can",
|
|
2525
|
+
" monitor. Do not go silent for 50 tool calls then dump a wall of",
|
|
2526
|
+
" text \u2014 but also do not narrate every tool call.",
|
|
2527
|
+
"- Use the shared scratchpad (if available) to leave breadcrumbs",
|
|
2528
|
+
" subagents can read.",
|
|
2529
|
+
"- Final response must include: (a) what was accomplished, (b) how",
|
|
2530
|
+
" to verify, (c) any caveats (residual TODOs, things the user",
|
|
2531
|
+
" should know about).",
|
|
2532
|
+
"",
|
|
2533
|
+
"BEGIN.]"
|
|
2534
|
+
].join("\n");
|
|
2535
|
+
}
|
|
1887
2536
|
function App({
|
|
1888
2537
|
agent,
|
|
1889
2538
|
slashRegistry,
|
|
@@ -1902,7 +2551,12 @@ function App({
|
|
|
1902
2551
|
switchProviderAndModel,
|
|
1903
2552
|
effectiveMaxContext,
|
|
1904
2553
|
onExit,
|
|
1905
|
-
|
|
2554
|
+
director,
|
|
2555
|
+
fleetRoster,
|
|
2556
|
+
onClearHistory,
|
|
2557
|
+
fleetStreamController,
|
|
2558
|
+
initialGoal,
|
|
2559
|
+
initialAsk
|
|
1906
2560
|
}) {
|
|
1907
2561
|
const { exit } = useApp();
|
|
1908
2562
|
const [liveModel, setLiveModel] = useState(model);
|
|
@@ -1927,6 +2581,8 @@ function App({
|
|
|
1927
2581
|
toolStream: null,
|
|
1928
2582
|
status: "idle",
|
|
1929
2583
|
interrupts: 0,
|
|
2584
|
+
steeringPending: false,
|
|
2585
|
+
steerSnapshot: null,
|
|
1930
2586
|
hint: "",
|
|
1931
2587
|
nextId: 1,
|
|
1932
2588
|
picker: { open: false, query: "", matches: [], selected: 0 },
|
|
@@ -1944,13 +2600,18 @@ function App({
|
|
|
1944
2600
|
selected: 0
|
|
1945
2601
|
},
|
|
1946
2602
|
confirm: null,
|
|
1947
|
-
contextChipVersion: 0
|
|
2603
|
+
contextChipVersion: 0,
|
|
2604
|
+
fleet: {},
|
|
2605
|
+
fleetCost: 0,
|
|
2606
|
+
streamFleet: true
|
|
1948
2607
|
});
|
|
1949
2608
|
const builderRef = useRef(null);
|
|
1950
2609
|
if (builderRef.current === null) {
|
|
1951
2610
|
builderRef.current = new InputBuilder({ store: attachments });
|
|
1952
2611
|
}
|
|
1953
2612
|
const activeCtrlRef = useRef(null);
|
|
2613
|
+
const inputGateRef = useRef(false);
|
|
2614
|
+
const lastEnterAtRef = useRef(0);
|
|
1954
2615
|
const projectRoot = agent.ctx.projectRoot;
|
|
1955
2616
|
const projectName = React.useMemo(() => {
|
|
1956
2617
|
const base = path3.basename(projectRoot);
|
|
@@ -1986,20 +2647,18 @@ function App({
|
|
|
1986
2647
|
const [lastInputTokens, setLastInputTokens] = React.useState(0);
|
|
1987
2648
|
useEffect(() => {
|
|
1988
2649
|
const off = events.on("provider.response", (e) => {
|
|
1989
|
-
|
|
2650
|
+
const total = (e.usage.input ?? 0) + (e.usage.cacheRead ?? 0) + (e.usage.cacheWrite ?? 0);
|
|
2651
|
+
setLastInputTokens(total);
|
|
1990
2652
|
});
|
|
1991
2653
|
return () => {
|
|
1992
2654
|
off();
|
|
1993
2655
|
};
|
|
1994
2656
|
}, [events]);
|
|
1995
2657
|
const maxContext = effectiveMaxContext ?? agent.ctx.provider.capabilities.maxContext;
|
|
1996
|
-
const contextWindow = useMemo(
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
},
|
|
2001
|
-
[lastInputTokens, maxContext, state.contextChipVersion]
|
|
2002
|
-
);
|
|
2658
|
+
const contextWindow = useMemo(() => {
|
|
2659
|
+
void state.contextChipVersion;
|
|
2660
|
+
return lastInputTokens > 0 && maxContext > 0 ? { used: lastInputTokens, max: maxContext } : void 0;
|
|
2661
|
+
}, [lastInputTokens, maxContext, state.contextChipVersion]);
|
|
2003
2662
|
const todos = useMemo(() => {
|
|
2004
2663
|
const counts = { pending: 0, inProgress: 0, completed: 0 };
|
|
2005
2664
|
for (const t of agent.ctx.todos) {
|
|
@@ -2009,6 +2668,111 @@ function App({
|
|
|
2009
2668
|
}
|
|
2010
2669
|
return counts;
|
|
2011
2670
|
}, [nowTick, agent.ctx.todos]);
|
|
2671
|
+
const fleetCounts = useMemo(() => {
|
|
2672
|
+
const entries = Object.values(state.fleet);
|
|
2673
|
+
if (entries.length === 0) return void 0;
|
|
2674
|
+
let running = 0;
|
|
2675
|
+
let idle = 0;
|
|
2676
|
+
let completed = 0;
|
|
2677
|
+
for (const e of entries) {
|
|
2678
|
+
if (e.status === "running") running += 1;
|
|
2679
|
+
else if (e.status === "idle") idle += 1;
|
|
2680
|
+
else completed += 1;
|
|
2681
|
+
}
|
|
2682
|
+
return { running, idle, pending: 0, completed };
|
|
2683
|
+
}, [state.fleet]);
|
|
2684
|
+
const STREAM_COLORS = ["cyan", "magenta", "yellow", "green", "blue"];
|
|
2685
|
+
const labelsRef = useRef(/* @__PURE__ */ new Map());
|
|
2686
|
+
const labelFor = (id, name) => {
|
|
2687
|
+
const m = labelsRef.current;
|
|
2688
|
+
const existing = m.get(id);
|
|
2689
|
+
if (existing) return existing;
|
|
2690
|
+
const n = m.size + 1;
|
|
2691
|
+
const suffix = name && name !== id ? ` ${name}` : "";
|
|
2692
|
+
const v = {
|
|
2693
|
+
label: `AGENT#${n}${suffix}`,
|
|
2694
|
+
color: STREAM_COLORS[(n - 1) % STREAM_COLORS.length]
|
|
2695
|
+
};
|
|
2696
|
+
m.set(id, v);
|
|
2697
|
+
return v;
|
|
2698
|
+
};
|
|
2699
|
+
const fleetAgents = useMemo(() => {
|
|
2700
|
+
const entries = Object.entries(state.fleet);
|
|
2701
|
+
if (entries.length === 0) return void 0;
|
|
2702
|
+
const active = entries.filter(([_id, e]) => e.status === "running" || e.status === "idle");
|
|
2703
|
+
if (active.length === 0) return void 0;
|
|
2704
|
+
active.sort((a, b) => {
|
|
2705
|
+
const sa = a[1].status === "running" ? 0 : 1;
|
|
2706
|
+
const sb = b[1].status === "running" ? 0 : 1;
|
|
2707
|
+
if (sa !== sb) return sa - sb;
|
|
2708
|
+
return a[1].startedAt - b[1].startedAt;
|
|
2709
|
+
});
|
|
2710
|
+
return active.slice(0, 4).map(([id, e]) => {
|
|
2711
|
+
const lbl = labelFor(id, e.name);
|
|
2712
|
+
return {
|
|
2713
|
+
label: lbl.label,
|
|
2714
|
+
color: lbl.color,
|
|
2715
|
+
elapsedMs: Math.max(0, nowTick - e.startedAt),
|
|
2716
|
+
toolCalls: e.toolCalls,
|
|
2717
|
+
running: e.status === "running"
|
|
2718
|
+
};
|
|
2719
|
+
});
|
|
2720
|
+
}, [state.fleet, nowTick]);
|
|
2721
|
+
const [planCounts, setPlanCounts] = useState(null);
|
|
2722
|
+
useEffect(() => {
|
|
2723
|
+
const planPath = agent.ctx.meta["plan.path"];
|
|
2724
|
+
if (typeof planPath !== "string" || !planPath) return;
|
|
2725
|
+
let cancelled = false;
|
|
2726
|
+
const poll = async () => {
|
|
2727
|
+
try {
|
|
2728
|
+
const data = await fs.readFile(planPath, "utf8");
|
|
2729
|
+
const parsed = JSON.parse(data);
|
|
2730
|
+
if (cancelled) return;
|
|
2731
|
+
if (!Array.isArray(parsed.items)) {
|
|
2732
|
+
setPlanCounts(null);
|
|
2733
|
+
return;
|
|
2734
|
+
}
|
|
2735
|
+
let open = 0;
|
|
2736
|
+
let inProgress = 0;
|
|
2737
|
+
let done = 0;
|
|
2738
|
+
for (const it of parsed.items) {
|
|
2739
|
+
if (it?.status === "done") done++;
|
|
2740
|
+
else if (it?.status === "in_progress") inProgress++;
|
|
2741
|
+
else open++;
|
|
2742
|
+
}
|
|
2743
|
+
setPlanCounts(open + inProgress + done > 0 ? { open, inProgress, done } : null);
|
|
2744
|
+
} catch {
|
|
2745
|
+
if (!cancelled) setPlanCounts(null);
|
|
2746
|
+
}
|
|
2747
|
+
};
|
|
2748
|
+
void poll();
|
|
2749
|
+
const id = setInterval(poll, 3e3);
|
|
2750
|
+
return () => {
|
|
2751
|
+
cancelled = true;
|
|
2752
|
+
clearInterval(id);
|
|
2753
|
+
};
|
|
2754
|
+
}, [agent.ctx.meta]);
|
|
2755
|
+
const prevAnyOverlayOpen = useRef(false);
|
|
2756
|
+
const prevEntriesCount = useRef(0);
|
|
2757
|
+
useEffect(() => {
|
|
2758
|
+
const anyOpenNow = state.picker.open || state.slashPicker.open || state.modelPicker.open || !!state.confirm;
|
|
2759
|
+
const overlayClosed = prevAnyOverlayOpen.current && !anyOpenNow;
|
|
2760
|
+
const newEntryCommitted = state.entries.length > prevEntriesCount.current;
|
|
2761
|
+
prevAnyOverlayOpen.current = anyOpenNow;
|
|
2762
|
+
prevEntriesCount.current = state.entries.length;
|
|
2763
|
+
if (overlayClosed || newEntryCommitted) {
|
|
2764
|
+
try {
|
|
2765
|
+
process.stdout.write("\x1B[J");
|
|
2766
|
+
} catch {
|
|
2767
|
+
}
|
|
2768
|
+
}
|
|
2769
|
+
}, [
|
|
2770
|
+
state.picker.open,
|
|
2771
|
+
state.slashPicker.open,
|
|
2772
|
+
state.modelPicker.open,
|
|
2773
|
+
state.confirm,
|
|
2774
|
+
state.entries.length
|
|
2775
|
+
]);
|
|
2012
2776
|
useEffect(() => {
|
|
2013
2777
|
const detected = detectAtToken(state.buffer, state.cursor);
|
|
2014
2778
|
if (!detected) {
|
|
@@ -2095,7 +2859,7 @@ function App({
|
|
|
2095
2859
|
}
|
|
2096
2860
|
const absPath = path3.isAbsolute(picked) ? picked : path3.join(projectRoot, picked);
|
|
2097
2861
|
try {
|
|
2098
|
-
const data = await
|
|
2862
|
+
const data = await fs.readFile(absPath, "utf8");
|
|
2099
2863
|
const placeholder = await builder.appendFile({
|
|
2100
2864
|
kind: "file",
|
|
2101
2865
|
data,
|
|
@@ -2113,7 +2877,10 @@ function App({
|
|
|
2113
2877
|
} catch (err) {
|
|
2114
2878
|
dispatch({
|
|
2115
2879
|
type: "addEntry",
|
|
2116
|
-
entry: {
|
|
2880
|
+
entry: {
|
|
2881
|
+
kind: "error",
|
|
2882
|
+
text: `Attach failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2883
|
+
}
|
|
2117
2884
|
});
|
|
2118
2885
|
dispatch({ type: "pickerClose" });
|
|
2119
2886
|
}
|
|
@@ -2165,6 +2932,128 @@ function App({
|
|
|
2165
2932
|
slashRegistry.unregister("queue");
|
|
2166
2933
|
};
|
|
2167
2934
|
}, [slashRegistry]);
|
|
2935
|
+
useEffect(() => {
|
|
2936
|
+
const ALT_OFF = "\x1B[?1049l";
|
|
2937
|
+
const ALT_ON = "\x1B[?1049h";
|
|
2938
|
+
const cmd = {
|
|
2939
|
+
name: "altscreen",
|
|
2940
|
+
description: "Toggle the alt-screen buffer. Default is OFF (native scroll); /altscreen on for full-screen mode.",
|
|
2941
|
+
async run(args) {
|
|
2942
|
+
const arg = args.trim().toLowerCase();
|
|
2943
|
+
if (arg === "off") {
|
|
2944
|
+
try {
|
|
2945
|
+
process.stdout.write(ALT_OFF);
|
|
2946
|
+
} catch {
|
|
2947
|
+
return { message: "Failed to exit alt-screen." };
|
|
2948
|
+
}
|
|
2949
|
+
return {
|
|
2950
|
+
message: "Alt-screen disabled. New entries will land in normal scrollback (mouse wheel / Shift+PgUp work). On-screen history rendered before this command is no longer reachable via terminal scroll. Resize may now leak the live region \u2014 `/altscreen on` to re-enable."
|
|
2951
|
+
};
|
|
2952
|
+
}
|
|
2953
|
+
if (arg === "on") {
|
|
2954
|
+
try {
|
|
2955
|
+
process.stdout.write(ALT_ON);
|
|
2956
|
+
} catch {
|
|
2957
|
+
return { message: "Failed to re-enter alt-screen." };
|
|
2958
|
+
}
|
|
2959
|
+
return { message: "Alt-screen re-enabled. Native scroll is now disabled." };
|
|
2960
|
+
}
|
|
2961
|
+
return { message: "Usage: /altscreen on|off" };
|
|
2962
|
+
}
|
|
2963
|
+
};
|
|
2964
|
+
slashRegistry.register(cmd);
|
|
2965
|
+
return () => {
|
|
2966
|
+
slashRegistry.unregister("altscreen");
|
|
2967
|
+
};
|
|
2968
|
+
}, [slashRegistry]);
|
|
2969
|
+
useEffect(() => {
|
|
2970
|
+
const cmd = {
|
|
2971
|
+
name: "steer",
|
|
2972
|
+
description: "Interrupt the running agent (incl. fleet) and redirect: /steer <new direction>",
|
|
2973
|
+
help: [
|
|
2974
|
+
"Usage: /steer <new direction>",
|
|
2975
|
+
"",
|
|
2976
|
+
"Aborts the active iteration, terminates any running subagents,",
|
|
2977
|
+
"drops queued messages, and sends your text to the model with a",
|
|
2978
|
+
"STEERING preamble explaining what was in flight and what the",
|
|
2979
|
+
"model is authorised to do (pivot hard, respawn subagents, ask",
|
|
2980
|
+
"for clarification). Equivalent to pressing Esc then typing."
|
|
2981
|
+
].join("\n"),
|
|
2982
|
+
async run(args) {
|
|
2983
|
+
const text = args.trim();
|
|
2984
|
+
if (!text) {
|
|
2985
|
+
return { message: "Usage: /steer <new direction>" };
|
|
2986
|
+
}
|
|
2987
|
+
const s = stateRef.current;
|
|
2988
|
+
const runningTools = Array.from(s.runningTools.values()).map((t) => t.name);
|
|
2989
|
+
const subagents = Object.values(s.fleet).filter((e) => e.status === "running").map((e) => ({ label: e.name, status: e.status, tool: e.currentTool?.name }));
|
|
2990
|
+
const subagentsTerminated = subagents.length;
|
|
2991
|
+
const partialAssistantText = streamingTextRef.current.slice(-1500);
|
|
2992
|
+
activeCtrlRef.current?.abort();
|
|
2993
|
+
dispatch({
|
|
2994
|
+
type: "steerStart",
|
|
2995
|
+
snapshot: { runningTools, subagents, subagentsTerminated, partialAssistantText }
|
|
2996
|
+
});
|
|
2997
|
+
const droppedCount = s.queue.length;
|
|
2998
|
+
if (droppedCount > 0) dispatch({ type: "queueClear" });
|
|
2999
|
+
if (director && subagentsTerminated > 0) {
|
|
3000
|
+
const cap = new Promise((resolve) => {
|
|
3001
|
+
const t = setTimeout(resolve, 1500);
|
|
3002
|
+
t.unref?.();
|
|
3003
|
+
});
|
|
3004
|
+
void Promise.race([director.terminateAll().catch(() => void 0), cap]);
|
|
3005
|
+
}
|
|
3006
|
+
const preamble = buildSteeringPreamble(
|
|
3007
|
+
{ runningTools, subagents, subagentsTerminated, partialAssistantText },
|
|
3008
|
+
text
|
|
3009
|
+
);
|
|
3010
|
+
dispatch({ type: "steerConsume" });
|
|
3011
|
+
const droppedTag = droppedCount > 0 ? ` \xB7 dropped ${droppedCount} queued` : "";
|
|
3012
|
+
const fleetTag = subagentsTerminated > 0 ? ` \xB7 stopped ${subagentsTerminated} subagent${subagentsTerminated === 1 ? "" : "s"}` : "";
|
|
3013
|
+
return {
|
|
3014
|
+
message: `\u21AF Steering${droppedTag}${fleetTag}.`,
|
|
3015
|
+
runText: preamble
|
|
3016
|
+
};
|
|
3017
|
+
}
|
|
3018
|
+
};
|
|
3019
|
+
slashRegistry.register(cmd);
|
|
3020
|
+
return () => {
|
|
3021
|
+
slashRegistry.unregister("steer");
|
|
3022
|
+
};
|
|
3023
|
+
}, [slashRegistry, director]);
|
|
3024
|
+
useEffect(() => {
|
|
3025
|
+
const cmd = {
|
|
3026
|
+
name: "goal",
|
|
3027
|
+
description: "Lock in a goal \u2014 no budgets, no hedging, no premature done. /goal <description>",
|
|
3028
|
+
help: [
|
|
3029
|
+
"Usage: /goal <description>",
|
|
3030
|
+
"",
|
|
3031
|
+
"Hands the agent a task it must drive to a verifiable finish.",
|
|
3032
|
+
"Adds a preamble to the next turn that grants full autonomy",
|
|
3033
|
+
"(unlimited subagents, any provider/model, retry-until-it-works),",
|
|
3034
|
+
'spells out what "done" actually means, and forbids hedge-style',
|
|
3035
|
+
'completions ("I believe this works", "should I continue?").',
|
|
3036
|
+
"",
|
|
3037
|
+
"Combine with /steer to redirect mid-goal, or Ctrl+C / /fleet kill",
|
|
3038
|
+
"to bail out \u2014 only the user can stop a /goal."
|
|
3039
|
+
].join("\n"),
|
|
3040
|
+
async run(args) {
|
|
3041
|
+
const goal = args.trim();
|
|
3042
|
+
if (!goal) return { message: "Usage: /goal <description>" };
|
|
3043
|
+
const preamble = buildGoalPreamble(goal);
|
|
3044
|
+
const shortGoal = goal.length > 80 ? `${goal.slice(0, 80)}\u2026` : goal;
|
|
3045
|
+
return {
|
|
3046
|
+
message: `\u{1F3AF} Goal locked: ${shortGoal}
|
|
3047
|
+
Agent will work until verifiably complete. Esc / /steer to redirect, Ctrl+C to stop.`,
|
|
3048
|
+
runText: preamble
|
|
3049
|
+
};
|
|
3050
|
+
}
|
|
3051
|
+
};
|
|
3052
|
+
slashRegistry.register(cmd);
|
|
3053
|
+
return () => {
|
|
3054
|
+
slashRegistry.unregister("goal");
|
|
3055
|
+
};
|
|
3056
|
+
}, [slashRegistry]);
|
|
2168
3057
|
useEffect(() => {
|
|
2169
3058
|
if (!getPickableProviders || !switchProviderAndModel) return;
|
|
2170
3059
|
const cmd = {
|
|
@@ -2206,7 +3095,8 @@ function App({
|
|
|
2206
3095
|
type: "toolStreamAppend",
|
|
2207
3096
|
toolUseId: e.id,
|
|
2208
3097
|
name: e.name,
|
|
2209
|
-
text: e.event.text
|
|
3098
|
+
text: e.event.text,
|
|
3099
|
+
startedAt: Date.now()
|
|
2210
3100
|
});
|
|
2211
3101
|
});
|
|
2212
3102
|
const offTool = events.on("tool.executed", (e) => {
|
|
@@ -2218,11 +3108,23 @@ function App({
|
|
|
2218
3108
|
durationMs: e.durationMs,
|
|
2219
3109
|
ok: e.ok,
|
|
2220
3110
|
input: e.input,
|
|
2221
|
-
output: e.output
|
|
3111
|
+
output: e.output,
|
|
3112
|
+
// Real model-visible sizes — forwarded so the size chip beside
|
|
3113
|
+
// the tool header can show what the model paid for instead of
|
|
3114
|
+
// the misleading preview-byte count we used to surface.
|
|
3115
|
+
outputBytes: e.outputBytes,
|
|
3116
|
+
outputTokens: e.outputTokens,
|
|
3117
|
+
outputLines: e.outputLines
|
|
2222
3118
|
}
|
|
2223
3119
|
});
|
|
2224
3120
|
dispatch({ type: "toolEnded", name: e.name });
|
|
2225
3121
|
dispatch({ type: "toolStreamClear", name: e.name });
|
|
3122
|
+
if (e.ok && e.name === "todo") {
|
|
3123
|
+
dispatch({
|
|
3124
|
+
type: "addEntry",
|
|
3125
|
+
entry: { kind: "info", text: formatTodosList(agent.ctx.todos) }
|
|
3126
|
+
});
|
|
3127
|
+
}
|
|
2226
3128
|
});
|
|
2227
3129
|
const offRetry = events.on("provider.retry", (e) => {
|
|
2228
3130
|
const secs = (e.delayMs / 1e3).toFixed(e.delayMs >= 1e3 ? 1 : 2);
|
|
@@ -2237,6 +3139,19 @@ function App({
|
|
|
2237
3139
|
entry: { kind: "error", text: e.description }
|
|
2238
3140
|
});
|
|
2239
3141
|
});
|
|
3142
|
+
const offProvResp = events.on("provider.response", () => {
|
|
3143
|
+
const text = streamingTextRef.current;
|
|
3144
|
+
streamingTextRef.current = "";
|
|
3145
|
+
pendingDeltaRef.current = "";
|
|
3146
|
+
if (flushTimerRef.current) {
|
|
3147
|
+
clearTimeout(flushTimerRef.current);
|
|
3148
|
+
flushTimerRef.current = null;
|
|
3149
|
+
}
|
|
3150
|
+
dispatch({ type: "streamReset" });
|
|
3151
|
+
if (text.trim()) {
|
|
3152
|
+
dispatch({ type: "addEntry", entry: { kind: "assistant", text } });
|
|
3153
|
+
}
|
|
3154
|
+
});
|
|
2240
3155
|
const offConfirmNeeded = events.on("tool.confirm_needed", (e) => {
|
|
2241
3156
|
dispatch({
|
|
2242
3157
|
type: "addEntry",
|
|
@@ -2265,21 +3180,302 @@ function App({
|
|
|
2265
3180
|
offTool();
|
|
2266
3181
|
offRetry();
|
|
2267
3182
|
offProvErr();
|
|
3183
|
+
offProvResp();
|
|
2268
3184
|
offConfirmNeeded();
|
|
2269
3185
|
if (flushTimerRef.current) clearTimeout(flushTimerRef.current);
|
|
2270
3186
|
};
|
|
3187
|
+
}, [events, agent.ctx.todos]);
|
|
3188
|
+
const streamFleetRef = useRef(state.streamFleet);
|
|
3189
|
+
useEffect(() => {
|
|
3190
|
+
streamFleetRef.current = state.streamFleet;
|
|
3191
|
+
}, [state.streamFleet]);
|
|
3192
|
+
useEffect(() => {
|
|
3193
|
+
const offSpawned = events.on("subagent.spawned", (e) => {
|
|
3194
|
+
const lbl = labelFor(e.subagentId, e.name);
|
|
3195
|
+
dispatch({
|
|
3196
|
+
type: "fleetSpawn",
|
|
3197
|
+
id: e.subagentId,
|
|
3198
|
+
name: e.name,
|
|
3199
|
+
provider: e.provider,
|
|
3200
|
+
model: e.model,
|
|
3201
|
+
transcriptPath: e.transcriptPath
|
|
3202
|
+
});
|
|
3203
|
+
const where = e.provider && e.model ? `${e.provider}/${e.model}` : "spawned";
|
|
3204
|
+
const desc = e.description ? ` \u2014 ${e.description.slice(0, 80)}` : "";
|
|
3205
|
+
dispatch({
|
|
3206
|
+
type: "addEntry",
|
|
3207
|
+
entry: {
|
|
3208
|
+
kind: "subagent",
|
|
3209
|
+
agentLabel: lbl.label,
|
|
3210
|
+
agentColor: lbl.color,
|
|
3211
|
+
icon: "\u25B6",
|
|
3212
|
+
text: `${where}${desc}`
|
|
3213
|
+
}
|
|
3214
|
+
});
|
|
3215
|
+
});
|
|
3216
|
+
const offStarted = events.on("subagent.task_started", (e) => {
|
|
3217
|
+
const lbl = labelFor(e.subagentId);
|
|
3218
|
+
dispatch({ type: "fleetStart", id: e.subagentId, taskId: e.taskId });
|
|
3219
|
+
const desc = e.description ? ` \u2014 ${e.description.slice(0, 80)}` : "";
|
|
3220
|
+
dispatch({
|
|
3221
|
+
type: "addEntry",
|
|
3222
|
+
entry: {
|
|
3223
|
+
kind: "subagent",
|
|
3224
|
+
agentLabel: lbl.label,
|
|
3225
|
+
agentColor: lbl.color,
|
|
3226
|
+
icon: "\u25CF",
|
|
3227
|
+
text: `task started${desc}`
|
|
3228
|
+
}
|
|
3229
|
+
});
|
|
3230
|
+
});
|
|
3231
|
+
const offCompleted = events.on("subagent.task_completed", (e) => {
|
|
3232
|
+
const lbl = labelFor(e.subagentId);
|
|
3233
|
+
dispatch({
|
|
3234
|
+
type: "fleetDone",
|
|
3235
|
+
id: e.subagentId,
|
|
3236
|
+
status: e.status,
|
|
3237
|
+
iterations: e.iterations,
|
|
3238
|
+
toolCalls: e.toolCalls
|
|
3239
|
+
});
|
|
3240
|
+
const icon = e.status === "success" ? "\u2713" : e.status === "timeout" ? "\u23F1" : e.status === "stopped" ? "\u2298" : "\u2717";
|
|
3241
|
+
const errKind = e.error?.kind;
|
|
3242
|
+
const errMsg = e.error?.message;
|
|
3243
|
+
const errMsgTail = errMsg ? ` \u2014 ${errMsg.replace(/\s+/g, " ").slice(0, 100)}${errMsg.length > 100 ? "\u2026" : ""}` : "";
|
|
3244
|
+
const errChip = errKind ? ` [${errKind}]` : "";
|
|
3245
|
+
const secs = (e.durationMs / 1e3).toFixed(e.durationMs < 1e4 ? 1 : 0);
|
|
3246
|
+
dispatch({
|
|
3247
|
+
type: "addEntry",
|
|
3248
|
+
entry: {
|
|
3249
|
+
kind: "subagent",
|
|
3250
|
+
agentLabel: lbl.label,
|
|
3251
|
+
agentColor: lbl.color,
|
|
3252
|
+
icon,
|
|
3253
|
+
text: `${e.status} (${e.iterations} iter \xB7 ${e.toolCalls} tools \xB7 ${secs}s)${errChip}${errMsgTail}`
|
|
3254
|
+
}
|
|
3255
|
+
});
|
|
3256
|
+
});
|
|
3257
|
+
const offTool = events.on("subagent.tool_executed", (e) => {
|
|
3258
|
+
const lbl = labelFor(e.subagentId);
|
|
3259
|
+
dispatch({ type: "fleetTool", id: e.subagentId });
|
|
3260
|
+
dispatch({ type: "fleetToolEnd", id: e.subagentId });
|
|
3261
|
+
const bytesTag = typeof e.outputBytes === "number" && e.outputBytes > 0 ? ` \xB7 ${e.outputBytes < 1024 ? `${e.outputBytes}B` : `${(e.outputBytes / 1024).toFixed(1)}KB`}` : "";
|
|
3262
|
+
dispatch({
|
|
3263
|
+
type: "addEntry",
|
|
3264
|
+
entry: {
|
|
3265
|
+
kind: "subagent",
|
|
3266
|
+
agentLabel: lbl.label,
|
|
3267
|
+
agentColor: lbl.color,
|
|
3268
|
+
icon: e.ok === false ? "\u2717" : "\u25CF",
|
|
3269
|
+
text: e.name,
|
|
3270
|
+
detail: `${e.durationMs}ms${bytesTag}`
|
|
3271
|
+
}
|
|
3272
|
+
});
|
|
3273
|
+
});
|
|
3274
|
+
return () => {
|
|
3275
|
+
offSpawned();
|
|
3276
|
+
offStarted();
|
|
3277
|
+
offCompleted();
|
|
3278
|
+
offTool();
|
|
3279
|
+
};
|
|
2271
3280
|
}, [events]);
|
|
3281
|
+
useEffect(() => {
|
|
3282
|
+
if (!fleetStreamController) return;
|
|
3283
|
+
fleetStreamController.enabled = state.streamFleet;
|
|
3284
|
+
fleetStreamController.setEnabled = (enabled) => {
|
|
3285
|
+
dispatch({ type: "setStreamFleet", enabled });
|
|
3286
|
+
};
|
|
3287
|
+
return () => {
|
|
3288
|
+
fleetStreamController.setEnabled = (enabled) => {
|
|
3289
|
+
fleetStreamController.enabled = enabled;
|
|
3290
|
+
};
|
|
3291
|
+
};
|
|
3292
|
+
}, [fleetStreamController, state.streamFleet]);
|
|
3293
|
+
useEffect(() => {
|
|
3294
|
+
if (fleetStreamController) fleetStreamController.enabled = state.streamFleet;
|
|
3295
|
+
}, [state.streamFleet, fleetStreamController]);
|
|
3296
|
+
useEffect(() => {
|
|
3297
|
+
const d = director;
|
|
3298
|
+
if (!d) return;
|
|
3299
|
+
const FLUSH_MS = 150;
|
|
3300
|
+
const streamBuf = /* @__PURE__ */ new Map();
|
|
3301
|
+
let streamFlushTimer = null;
|
|
3302
|
+
const flushStreamBufs = () => {
|
|
3303
|
+
for (const [id, text] of streamBuf) {
|
|
3304
|
+
if (!text.trim()) continue;
|
|
3305
|
+
const lbl = labelFor(id);
|
|
3306
|
+
dispatch({
|
|
3307
|
+
type: "addEntry",
|
|
3308
|
+
entry: {
|
|
3309
|
+
kind: "subagent",
|
|
3310
|
+
agentLabel: lbl.label,
|
|
3311
|
+
agentColor: lbl.color,
|
|
3312
|
+
icon: "\u{1F4AC}",
|
|
3313
|
+
text: text.trim()
|
|
3314
|
+
}
|
|
3315
|
+
});
|
|
3316
|
+
}
|
|
3317
|
+
streamBuf.clear();
|
|
3318
|
+
streamFlushTimer = null;
|
|
3319
|
+
};
|
|
3320
|
+
const status = d.status();
|
|
3321
|
+
for (const s of status.subagents) {
|
|
3322
|
+
const meta = d.getSubagentMeta(s.id);
|
|
3323
|
+
dispatch({
|
|
3324
|
+
type: "fleetSpawn",
|
|
3325
|
+
id: s.id,
|
|
3326
|
+
name: meta?.name ?? s.name,
|
|
3327
|
+
provider: meta?.provider,
|
|
3328
|
+
model: meta?.model
|
|
3329
|
+
});
|
|
3330
|
+
labelFor(s.id, meta?.name ?? s.name);
|
|
3331
|
+
}
|
|
3332
|
+
dispatch({ type: "fleetCost", cost: d.snapshot().total.cost });
|
|
3333
|
+
const seen = new Set(Object.keys(status.subagents));
|
|
3334
|
+
const pending = /* @__PURE__ */ new Map();
|
|
3335
|
+
let flushTimer = null;
|
|
3336
|
+
const doFlush = () => {
|
|
3337
|
+
for (const [id, text] of pending) {
|
|
3338
|
+
if (text) dispatch({ type: "fleetDelta", id, text });
|
|
3339
|
+
}
|
|
3340
|
+
pending.clear();
|
|
3341
|
+
flushTimer = null;
|
|
3342
|
+
};
|
|
3343
|
+
const offFleet = d.fleet.onAny((e) => {
|
|
3344
|
+
const fresh = !seen.has(e.subagentId);
|
|
3345
|
+
if (fresh) {
|
|
3346
|
+
seen.add(e.subagentId);
|
|
3347
|
+
const meta = d.getSubagentMeta(e.subagentId);
|
|
3348
|
+
dispatch({
|
|
3349
|
+
type: "fleetSpawn",
|
|
3350
|
+
id: e.subagentId,
|
|
3351
|
+
name: meta?.name,
|
|
3352
|
+
provider: meta?.provider,
|
|
3353
|
+
model: meta?.model
|
|
3354
|
+
});
|
|
3355
|
+
const lbl = labelFor(e.subagentId, meta?.name);
|
|
3356
|
+
if (streamFleetRef.current) {
|
|
3357
|
+
const where = meta?.provider && meta?.model ? `${meta.provider}/${meta.model}` : "spawned";
|
|
3358
|
+
dispatch({
|
|
3359
|
+
type: "addEntry",
|
|
3360
|
+
entry: {
|
|
3361
|
+
kind: "subagent",
|
|
3362
|
+
agentLabel: lbl.label,
|
|
3363
|
+
agentColor: lbl.color,
|
|
3364
|
+
icon: "\u25B6",
|
|
3365
|
+
text: where
|
|
3366
|
+
}
|
|
3367
|
+
});
|
|
3368
|
+
}
|
|
3369
|
+
}
|
|
3370
|
+
switch (e.type) {
|
|
3371
|
+
case "iteration.started":
|
|
3372
|
+
dispatch({ type: "fleetStart", id: e.subagentId });
|
|
3373
|
+
break;
|
|
3374
|
+
case "provider.text_delta": {
|
|
3375
|
+
const p = e.payload;
|
|
3376
|
+
if (p?.text) {
|
|
3377
|
+
const cur = pending.get(e.subagentId) ?? "";
|
|
3378
|
+
pending.set(e.subagentId, cur + p.text);
|
|
3379
|
+
if (!flushTimer) flushTimer = setTimeout(doFlush, FLUSH_MS);
|
|
3380
|
+
if (streamFleetRef.current) {
|
|
3381
|
+
streamBuf.set(e.subagentId, (streamBuf.get(e.subagentId) ?? "") + p.text);
|
|
3382
|
+
if (!streamFlushTimer) streamFlushTimer = setTimeout(flushStreamBufs, FLUSH_MS * 4);
|
|
3383
|
+
}
|
|
3384
|
+
}
|
|
3385
|
+
break;
|
|
3386
|
+
}
|
|
3387
|
+
case "tool.started": {
|
|
3388
|
+
const p = e.payload;
|
|
3389
|
+
if (p?.name) {
|
|
3390
|
+
dispatch({ type: "fleetToolStart", id: e.subagentId, name: p.name });
|
|
3391
|
+
}
|
|
3392
|
+
break;
|
|
3393
|
+
}
|
|
3394
|
+
case "tool.executed": {
|
|
3395
|
+
dispatch({ type: "fleetTool", id: e.subagentId });
|
|
3396
|
+
dispatch({ type: "fleetToolEnd", id: e.subagentId });
|
|
3397
|
+
if (streamFleetRef.current) {
|
|
3398
|
+
if (streamFlushTimer) {
|
|
3399
|
+
clearTimeout(streamFlushTimer);
|
|
3400
|
+
flushStreamBufs();
|
|
3401
|
+
}
|
|
3402
|
+
const p = e.payload;
|
|
3403
|
+
const args = p?.input ? formatToolArgs(p.name ?? "", p.input) : "";
|
|
3404
|
+
const lbl = labelFor(e.subagentId);
|
|
3405
|
+
dispatch({
|
|
3406
|
+
type: "addEntry",
|
|
3407
|
+
entry: {
|
|
3408
|
+
kind: "subagent",
|
|
3409
|
+
agentLabel: lbl.label,
|
|
3410
|
+
agentColor: lbl.color,
|
|
3411
|
+
icon: p?.ok === false ? "\u2717" : "\u25CF",
|
|
3412
|
+
text: args ? `${p?.name ?? "tool"} ${args}` : p?.name ?? "tool",
|
|
3413
|
+
detail: typeof p?.durationMs === "number" ? `${p.durationMs}ms` : void 0
|
|
3414
|
+
}
|
|
3415
|
+
});
|
|
3416
|
+
}
|
|
3417
|
+
break;
|
|
3418
|
+
}
|
|
3419
|
+
case "provider.response": {
|
|
3420
|
+
dispatch({ type: "fleetCost", cost: d.snapshot().total.cost });
|
|
3421
|
+
break;
|
|
3422
|
+
}
|
|
3423
|
+
}
|
|
3424
|
+
});
|
|
3425
|
+
const offDone = d.on("task.completed", (payload) => {
|
|
3426
|
+
dispatch({
|
|
3427
|
+
type: "fleetDone",
|
|
3428
|
+
id: payload.result.subagentId,
|
|
3429
|
+
status: payload.result.status,
|
|
3430
|
+
iterations: payload.result.iterations,
|
|
3431
|
+
toolCalls: payload.result.toolCalls
|
|
3432
|
+
});
|
|
3433
|
+
dispatch({ type: "fleetCost", cost: d.snapshot().total.cost });
|
|
3434
|
+
if (streamFleetRef.current && streamFlushTimer) {
|
|
3435
|
+
clearTimeout(streamFlushTimer);
|
|
3436
|
+
flushStreamBufs();
|
|
3437
|
+
}
|
|
3438
|
+
});
|
|
3439
|
+
return () => {
|
|
3440
|
+
offFleet();
|
|
3441
|
+
offDone();
|
|
3442
|
+
if (flushTimer) clearTimeout(flushTimer);
|
|
3443
|
+
doFlush();
|
|
3444
|
+
if (streamFlushTimer) clearTimeout(streamFlushTimer);
|
|
3445
|
+
flushStreamBufs();
|
|
3446
|
+
};
|
|
3447
|
+
}, [director]);
|
|
2272
3448
|
useEffect(() => {
|
|
2273
3449
|
const onSigint = () => {
|
|
2274
|
-
if (state.interrupts >= 1
|
|
2275
|
-
|
|
2276
|
-
|
|
3450
|
+
if (state.interrupts >= 1) {
|
|
3451
|
+
if (state.interrupts >= 2) {
|
|
3452
|
+
process.exit(130);
|
|
3453
|
+
}
|
|
3454
|
+
try {
|
|
3455
|
+
exit();
|
|
3456
|
+
onExit(130);
|
|
3457
|
+
} catch {
|
|
3458
|
+
}
|
|
3459
|
+
setTimeout(() => {
|
|
3460
|
+
try {
|
|
3461
|
+
process.exit(130);
|
|
3462
|
+
} catch {
|
|
3463
|
+
}
|
|
3464
|
+
}, 500).unref?.();
|
|
3465
|
+
dispatch({ type: "interrupt" });
|
|
2277
3466
|
return;
|
|
2278
3467
|
}
|
|
2279
3468
|
dispatch({ type: "interrupt" });
|
|
2280
3469
|
if (activeCtrlRef.current) {
|
|
2281
3470
|
activeCtrlRef.current.abort();
|
|
2282
3471
|
dispatch({ type: "status", status: "aborting" });
|
|
3472
|
+
if (director) {
|
|
3473
|
+
const cap = new Promise((resolve) => {
|
|
3474
|
+
const t = setTimeout(resolve, 1500);
|
|
3475
|
+
t.unref?.();
|
|
3476
|
+
});
|
|
3477
|
+
void Promise.race([director.terminateAll().catch(() => void 0), cap]);
|
|
3478
|
+
}
|
|
2283
3479
|
const droppedCount = stateRef.current.queue.length;
|
|
2284
3480
|
if (droppedCount > 0) {
|
|
2285
3481
|
dispatch({ type: "queueClear" });
|
|
@@ -2287,13 +3483,16 @@ function App({
|
|
|
2287
3483
|
type: "addEntry",
|
|
2288
3484
|
entry: {
|
|
2289
3485
|
kind: "warn",
|
|
2290
|
-
text: `Iteration cancelled. Dropped ${droppedCount} queued message${droppedCount === 1 ? "" : "s"}. Press Ctrl+C again to exit.`
|
|
3486
|
+
text: `Iteration cancelled${director ? " + fleet terminated" : ""}. Dropped ${droppedCount} queued message${droppedCount === 1 ? "" : "s"}. Press Ctrl+C again to exit.`
|
|
2291
3487
|
}
|
|
2292
3488
|
});
|
|
2293
3489
|
} else {
|
|
2294
3490
|
dispatch({
|
|
2295
3491
|
type: "addEntry",
|
|
2296
|
-
entry: {
|
|
3492
|
+
entry: {
|
|
3493
|
+
kind: "warn",
|
|
3494
|
+
text: `Iteration cancelled${director ? " + fleet terminated" : ""}. Press Ctrl+C again to exit.`
|
|
3495
|
+
}
|
|
2297
3496
|
});
|
|
2298
3497
|
}
|
|
2299
3498
|
} else {
|
|
@@ -2307,9 +3506,11 @@ function App({
|
|
|
2307
3506
|
return () => {
|
|
2308
3507
|
process.off("SIGINT", onSigint);
|
|
2309
3508
|
};
|
|
2310
|
-
}, [state.interrupts,
|
|
3509
|
+
}, [state.interrupts, exit, onExit, director]);
|
|
2311
3510
|
const handleKey = async (input, key) => {
|
|
2312
3511
|
if (state.status === "aborting") return;
|
|
3512
|
+
if (inputGateRef.current) return;
|
|
3513
|
+
const isEnter = key.return || input === "\r" || input === "\n";
|
|
2313
3514
|
if (state.modelPicker.open) {
|
|
2314
3515
|
if (key.escape) {
|
|
2315
3516
|
if (state.modelPicker.step === "model") {
|
|
@@ -2327,33 +3528,38 @@ function App({
|
|
|
2327
3528
|
dispatch({ type: "modelPickerMove", delta: 1 });
|
|
2328
3529
|
return;
|
|
2329
3530
|
}
|
|
2330
|
-
if (
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
if (
|
|
3531
|
+
if (isEnter) {
|
|
3532
|
+
inputGateRef.current = true;
|
|
3533
|
+
try {
|
|
3534
|
+
if (state.modelPicker.step === "provider") {
|
|
3535
|
+
const opt = state.modelPicker.providerOptions[state.modelPicker.selected];
|
|
3536
|
+
if (!opt) return;
|
|
3537
|
+
dispatch({
|
|
3538
|
+
type: "modelPickerPickProvider",
|
|
3539
|
+
providerId: opt.id,
|
|
3540
|
+
models: opt.models
|
|
3541
|
+
});
|
|
3542
|
+
return;
|
|
3543
|
+
}
|
|
3544
|
+
const providerId = state.modelPicker.pickedProviderId;
|
|
3545
|
+
const modelId = state.modelPicker.modelOptions[state.modelPicker.selected];
|
|
3546
|
+
if (!providerId || !modelId) return;
|
|
3547
|
+
const err = switchProviderAndModel?.(providerId, modelId);
|
|
3548
|
+
if (err) {
|
|
3549
|
+
dispatch({ type: "modelPickerHint", text: err });
|
|
3550
|
+
return;
|
|
3551
|
+
}
|
|
3552
|
+
setLiveProvider(providerId);
|
|
3553
|
+
setLiveModel(modelId);
|
|
2334
3554
|
dispatch({
|
|
2335
|
-
type: "
|
|
2336
|
-
|
|
2337
|
-
models: opt.models
|
|
3555
|
+
type: "addEntry",
|
|
3556
|
+
entry: { kind: "info", text: `Switched to ${providerId} / ${modelId}.` }
|
|
2338
3557
|
});
|
|
3558
|
+
dispatch({ type: "modelPickerClose" });
|
|
2339
3559
|
return;
|
|
3560
|
+
} finally {
|
|
3561
|
+
inputGateRef.current = false;
|
|
2340
3562
|
}
|
|
2341
|
-
const providerId = state.modelPicker.pickedProviderId;
|
|
2342
|
-
const modelId = state.modelPicker.modelOptions[state.modelPicker.selected];
|
|
2343
|
-
if (!providerId || !modelId) return;
|
|
2344
|
-
const err = switchProviderAndModel?.(providerId, modelId);
|
|
2345
|
-
if (err) {
|
|
2346
|
-
dispatch({ type: "modelPickerHint", text: err });
|
|
2347
|
-
return;
|
|
2348
|
-
}
|
|
2349
|
-
setLiveProvider(providerId);
|
|
2350
|
-
setLiveModel(modelId);
|
|
2351
|
-
dispatch({
|
|
2352
|
-
type: "addEntry",
|
|
2353
|
-
entry: { kind: "info", text: `Switched to ${providerId} / ${modelId}.` }
|
|
2354
|
-
});
|
|
2355
|
-
dispatch({ type: "modelPickerClose" });
|
|
2356
|
-
return;
|
|
2357
3563
|
}
|
|
2358
3564
|
return;
|
|
2359
3565
|
}
|
|
@@ -2370,8 +3576,10 @@ function App({
|
|
|
2370
3576
|
dispatch({ type: "slashPickerMove", delta: 1 });
|
|
2371
3577
|
return;
|
|
2372
3578
|
}
|
|
2373
|
-
if (
|
|
2374
|
-
|
|
3579
|
+
if (isEnter) {
|
|
3580
|
+
inputGateRef.current = true;
|
|
3581
|
+
acceptSlashPickerSelection();
|
|
3582
|
+
inputGateRef.current = false;
|
|
2375
3583
|
return;
|
|
2376
3584
|
}
|
|
2377
3585
|
if (key.tab && state.slashPicker.matches.length > 0) {
|
|
@@ -2396,13 +3604,61 @@ function App({
|
|
|
2396
3604
|
dispatch({ type: "pickerMove", delta: 1 });
|
|
2397
3605
|
return;
|
|
2398
3606
|
}
|
|
2399
|
-
if (
|
|
2400
|
-
|
|
3607
|
+
if (isEnter) {
|
|
3608
|
+
inputGateRef.current = true;
|
|
3609
|
+
try {
|
|
3610
|
+
await acceptPickerSelection();
|
|
3611
|
+
} finally {
|
|
3612
|
+
inputGateRef.current = false;
|
|
3613
|
+
}
|
|
2401
3614
|
return;
|
|
2402
3615
|
}
|
|
2403
3616
|
}
|
|
2404
|
-
if (key.
|
|
2405
|
-
|
|
3617
|
+
if (key.escape && state.status !== "idle" && !state.confirm) {
|
|
3618
|
+
const runningTools = Array.from(state.runningTools.values()).map((t) => t.name);
|
|
3619
|
+
const subagents = Object.values(state.fleet).filter((e) => e.status === "running").map((e) => ({
|
|
3620
|
+
label: e.name,
|
|
3621
|
+
status: e.status,
|
|
3622
|
+
tool: e.currentTool?.name
|
|
3623
|
+
}));
|
|
3624
|
+
const subagentsTerminated = subagents.length;
|
|
3625
|
+
const partialAssistantText = streamingTextRef.current.slice(-1500);
|
|
3626
|
+
activeCtrlRef.current?.abort();
|
|
3627
|
+
dispatch({ type: "status", status: "aborting" });
|
|
3628
|
+
dispatch({
|
|
3629
|
+
type: "steerStart",
|
|
3630
|
+
snapshot: {
|
|
3631
|
+
runningTools,
|
|
3632
|
+
subagents,
|
|
3633
|
+
subagentsTerminated,
|
|
3634
|
+
partialAssistantText
|
|
3635
|
+
}
|
|
3636
|
+
});
|
|
3637
|
+
if (director && subagentsTerminated > 0) {
|
|
3638
|
+
const cap = new Promise((resolve) => {
|
|
3639
|
+
const t = setTimeout(resolve, 1500);
|
|
3640
|
+
t.unref?.();
|
|
3641
|
+
});
|
|
3642
|
+
void Promise.race([director.terminateAll().catch(() => void 0), cap]);
|
|
3643
|
+
}
|
|
3644
|
+
const droppedCount = state.queue.length;
|
|
3645
|
+
if (droppedCount > 0) dispatch({ type: "queueClear" });
|
|
3646
|
+
const droppedTag = droppedCount > 0 ? ` \xB7 dropped ${droppedCount} queued` : "";
|
|
3647
|
+
const fleetTag = subagentsTerminated > 0 ? ` \xB7 stopped ${subagentsTerminated} subagent${subagentsTerminated === 1 ? "" : "s"}` : "";
|
|
3648
|
+
dispatch({
|
|
3649
|
+
type: "addEntry",
|
|
3650
|
+
entry: {
|
|
3651
|
+
kind: "warn",
|
|
3652
|
+
text: `\u21AF Interrupted${droppedTag}${fleetTag}. Type your new direction.`
|
|
3653
|
+
}
|
|
3654
|
+
});
|
|
3655
|
+
return;
|
|
3656
|
+
}
|
|
3657
|
+
if (isEnter) {
|
|
3658
|
+
const now = Date.now();
|
|
3659
|
+
if (now - lastEnterAtRef.current < 50) return;
|
|
3660
|
+
lastEnterAtRef.current = now;
|
|
3661
|
+
void submit();
|
|
2406
3662
|
return;
|
|
2407
3663
|
}
|
|
2408
3664
|
if (key.backspace || key.delete) {
|
|
@@ -2439,7 +3695,8 @@ function App({
|
|
|
2439
3695
|
dispatch({ type: "setBuffer", buffer, cursor: target });
|
|
2440
3696
|
return;
|
|
2441
3697
|
}
|
|
2442
|
-
if (state.cursor > 0)
|
|
3698
|
+
if (state.cursor > 0)
|
|
3699
|
+
dispatch({ type: "setBuffer", buffer: state.buffer, cursor: state.cursor - 1 });
|
|
2443
3700
|
return;
|
|
2444
3701
|
}
|
|
2445
3702
|
if (key.rightArrow) {
|
|
@@ -2452,7 +3709,16 @@ function App({
|
|
|
2452
3709
|
dispatch({ type: "setBuffer", buffer, cursor: target });
|
|
2453
3710
|
return;
|
|
2454
3711
|
}
|
|
2455
|
-
if (state.cursor < state.buffer.length)
|
|
3712
|
+
if (state.cursor < state.buffer.length)
|
|
3713
|
+
dispatch({ type: "setBuffer", buffer: state.buffer, cursor: state.cursor + 1 });
|
|
3714
|
+
return;
|
|
3715
|
+
}
|
|
3716
|
+
if (key.home) {
|
|
3717
|
+
dispatch({ type: "setBuffer", buffer: state.buffer, cursor: 0 });
|
|
3718
|
+
return;
|
|
3719
|
+
}
|
|
3720
|
+
if (key.end) {
|
|
3721
|
+
dispatch({ type: "setBuffer", buffer: state.buffer, cursor: state.buffer.length });
|
|
2456
3722
|
return;
|
|
2457
3723
|
}
|
|
2458
3724
|
if (key.upArrow) {
|
|
@@ -2520,10 +3786,9 @@ function App({
|
|
|
2520
3786
|
const before = tokenCounter?.total();
|
|
2521
3787
|
const costBefore = tokenCounter?.estimateCost().total ?? 0;
|
|
2522
3788
|
const result = await agent.run(blocks, { signal: ctrl.signal });
|
|
2523
|
-
const
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
dispatch({ type: "addEntry", entry: { kind: "assistant", text } });
|
|
3789
|
+
const lingering = streamingTextRef.current;
|
|
3790
|
+
if (lingering.trim()) {
|
|
3791
|
+
dispatch({ type: "addEntry", entry: { kind: "assistant", text: lingering } });
|
|
2527
3792
|
}
|
|
2528
3793
|
streamingTextRef.current = "";
|
|
2529
3794
|
pendingDeltaRef.current = "";
|
|
@@ -2536,10 +3801,10 @@ function App({
|
|
|
2536
3801
|
dispatch({ type: "addEntry", entry: { kind: "warn", text: "Aborted." } });
|
|
2537
3802
|
} else if (result.status === "failed") {
|
|
2538
3803
|
const err = result.error;
|
|
2539
|
-
const
|
|
3804
|
+
const text = err ? `Failed [${err.severity}${err.recoverable ? ", recoverable" : ""}]: ${err.describe()}` : "Failed.";
|
|
2540
3805
|
dispatch({
|
|
2541
3806
|
type: "addEntry",
|
|
2542
|
-
entry: { kind: "error", text
|
|
3807
|
+
entry: { kind: "error", text }
|
|
2543
3808
|
});
|
|
2544
3809
|
} else if (result.status === "max_iterations") {
|
|
2545
3810
|
dispatch({
|
|
@@ -2554,7 +3819,7 @@ function App({
|
|
|
2554
3819
|
type: "addEntry",
|
|
2555
3820
|
entry: {
|
|
2556
3821
|
kind: "turn-summary",
|
|
2557
|
-
text: `[in: ${
|
|
3822
|
+
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
3823
|
}
|
|
2559
3824
|
});
|
|
2560
3825
|
}
|
|
@@ -2595,6 +3860,18 @@ function App({
|
|
|
2595
3860
|
exit();
|
|
2596
3861
|
onExit(0);
|
|
2597
3862
|
}
|
|
3863
|
+
if (res?.runText) {
|
|
3864
|
+
const b = builderRef.current;
|
|
3865
|
+
if (b) {
|
|
3866
|
+
b.appendText(res.runText);
|
|
3867
|
+
const blocks2 = await b.submit();
|
|
3868
|
+
const start = Date.now();
|
|
3869
|
+
while (stateRef.current.status !== "idle" && Date.now() - start < 1500) {
|
|
3870
|
+
await new Promise((r) => setTimeout(r, 25));
|
|
3871
|
+
}
|
|
3872
|
+
await runBlocks(blocks2);
|
|
3873
|
+
}
|
|
3874
|
+
}
|
|
2598
3875
|
const cmd = trimmed.slice(1).split(/\s+/, 1)[0];
|
|
2599
3876
|
if (cmd === "clear") {
|
|
2600
3877
|
onClearHistory?.(dispatch);
|
|
@@ -2609,9 +3886,14 @@ function App({
|
|
|
2609
3886
|
}
|
|
2610
3887
|
const builder = builderRef.current;
|
|
2611
3888
|
if (!builder) return;
|
|
2612
|
-
|
|
3889
|
+
const steering = state.steeringPending;
|
|
3890
|
+
if (trimmed) {
|
|
3891
|
+
const toAppend = steering ? buildSteeringPreamble(state.steerSnapshot, trimmed) : trimmed;
|
|
3892
|
+
builder.appendText(toAppend);
|
|
3893
|
+
}
|
|
3894
|
+
if (steering) dispatch({ type: "steerConsume" });
|
|
2613
3895
|
const blocks = await builder.submit();
|
|
2614
|
-
const displayText = trimmed
|
|
3896
|
+
const displayText = trimmed ? steering ? `\u21AF ${trimmed}` : trimmed : "(attachments only)";
|
|
2615
3897
|
dispatch({ type: "clearInput" });
|
|
2616
3898
|
if (state.status !== "idle") {
|
|
2617
3899
|
dispatch({
|
|
@@ -2626,6 +3908,36 @@ function App({
|
|
|
2626
3908
|
if (state.historyIndex > 0) dispatch({ type: "historyPush", text: trimmed });
|
|
2627
3909
|
await runBlocks(blocks);
|
|
2628
3910
|
};
|
|
3911
|
+
const bootInjectedRef = useRef(false);
|
|
3912
|
+
useEffect(() => {
|
|
3913
|
+
if (bootInjectedRef.current) return;
|
|
3914
|
+
bootInjectedRef.current = true;
|
|
3915
|
+
const goal = initialGoal?.trim();
|
|
3916
|
+
const ask = initialAsk?.trim();
|
|
3917
|
+
if (!goal && !ask) return;
|
|
3918
|
+
void (async () => {
|
|
3919
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
3920
|
+
const b = builderRef.current;
|
|
3921
|
+
if (!b) return;
|
|
3922
|
+
if (goal) {
|
|
3923
|
+
const shortGoal = goal.length > 80 ? `${goal.slice(0, 80)}\u2026` : goal;
|
|
3924
|
+
dispatch({
|
|
3925
|
+
type: "addEntry",
|
|
3926
|
+
entry: {
|
|
3927
|
+
kind: "info",
|
|
3928
|
+
text: `\u{1F3AF} Goal locked: ${shortGoal}
|
|
3929
|
+
Agent will work until verifiably complete. Esc / /steer to redirect, Ctrl+C to stop.`
|
|
3930
|
+
}
|
|
3931
|
+
});
|
|
3932
|
+
b.appendText(buildGoalPreamble(goal));
|
|
3933
|
+
} else if (ask) {
|
|
3934
|
+
dispatch({ type: "addEntry", entry: { kind: "user", text: ask } });
|
|
3935
|
+
b.appendText(ask);
|
|
3936
|
+
}
|
|
3937
|
+
const blocks = await b.submit();
|
|
3938
|
+
await runBlocks(blocks);
|
|
3939
|
+
})();
|
|
3940
|
+
}, []);
|
|
2629
3941
|
const inputHint = useMemo(() => {
|
|
2630
3942
|
if (state.status !== "idle") return "";
|
|
2631
3943
|
if (state.buffer.startsWith("/")) return "slash command \u2014 Enter to dispatch";
|
|
@@ -2633,7 +3945,15 @@ function App({
|
|
|
2633
3945
|
return "";
|
|
2634
3946
|
}, [state.buffer, state.status, state.picker.open]);
|
|
2635
3947
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
2636
|
-
/* @__PURE__ */ jsx(
|
|
3948
|
+
/* @__PURE__ */ jsx(
|
|
3949
|
+
History,
|
|
3950
|
+
{
|
|
3951
|
+
entries: state.entries,
|
|
3952
|
+
streamingText: state.streamingText,
|
|
3953
|
+
toolStream: state.toolStream
|
|
3954
|
+
}
|
|
3955
|
+
),
|
|
3956
|
+
/* @__PURE__ */ jsx(LiveActivityStrip, { entries: state.fleet, nowTick }),
|
|
2637
3957
|
/* @__PURE__ */ jsx(
|
|
2638
3958
|
Input,
|
|
2639
3959
|
{
|
|
@@ -2692,11 +4012,16 @@ function App({
|
|
|
2692
4012
|
yolo,
|
|
2693
4013
|
elapsedMs,
|
|
2694
4014
|
todos,
|
|
4015
|
+
plan: planCounts ?? void 0,
|
|
4016
|
+
fleet: fleetCounts,
|
|
4017
|
+
fleetAgents,
|
|
2695
4018
|
git: gitInfo,
|
|
2696
4019
|
context: contextWindow,
|
|
2697
|
-
projectName
|
|
4020
|
+
projectName,
|
|
4021
|
+
subagentCount: Object.keys(state.fleet).length
|
|
2698
4022
|
}
|
|
2699
|
-
)
|
|
4023
|
+
),
|
|
4024
|
+
director ? /* @__PURE__ */ jsx(FleetPanel, { entries: state.fleet, totalCost: state.fleetCost, roster: fleetRoster }) : null
|
|
2700
4025
|
] });
|
|
2701
4026
|
}
|
|
2702
4027
|
function renderRunningTools(running) {
|
|
@@ -2725,7 +4050,7 @@ function detectAtToken(buffer, cursor) {
|
|
|
2725
4050
|
}
|
|
2726
4051
|
return null;
|
|
2727
4052
|
}
|
|
2728
|
-
function
|
|
4053
|
+
function fmtTok3(n) {
|
|
2729
4054
|
if (n < 1e3) return String(n);
|
|
2730
4055
|
if (n < 1e6) return `${(n / 1e3).toFixed(n < 1e4 ? 1 : 0)}k`;
|
|
2731
4056
|
return `${(n / 1e6).toFixed(1)}M`;
|
|
@@ -2752,6 +4077,15 @@ async function runTui(opts) {
|
|
|
2752
4077
|
stdout.write(CURSOR_HOME);
|
|
2753
4078
|
}
|
|
2754
4079
|
stdout.write(BRACKETED_PASTE_ON);
|
|
4080
|
+
const swallowSignals = ["SIGTSTP", "SIGQUIT", "SIGTTIN", "SIGTTOU"];
|
|
4081
|
+
const swallow = () => {
|
|
4082
|
+
};
|
|
4083
|
+
for (const s of swallowSignals) {
|
|
4084
|
+
try {
|
|
4085
|
+
process.on(s, swallow);
|
|
4086
|
+
} catch {
|
|
4087
|
+
}
|
|
4088
|
+
}
|
|
2755
4089
|
let cleaned = false;
|
|
2756
4090
|
const cleanup = () => {
|
|
2757
4091
|
if (cleaned) return;
|
|
@@ -2771,6 +4105,12 @@ async function runTui(opts) {
|
|
|
2771
4105
|
process.on("exit", exitHandler);
|
|
2772
4106
|
const detachListeners = () => {
|
|
2773
4107
|
for (const s of signals) process.off(s, signalHandler);
|
|
4108
|
+
for (const s of swallowSignals) {
|
|
4109
|
+
try {
|
|
4110
|
+
process.off(s, swallow);
|
|
4111
|
+
} catch {
|
|
4112
|
+
}
|
|
4113
|
+
}
|
|
2774
4114
|
process.off("exit", exitHandler);
|
|
2775
4115
|
};
|
|
2776
4116
|
return new Promise((resolve) => {
|
|
@@ -2810,7 +4150,12 @@ async function runTui(opts) {
|
|
|
2810
4150
|
switchProviderAndModel: opts.switchProviderAndModel,
|
|
2811
4151
|
effectiveMaxContext: opts.effectiveMaxContext,
|
|
2812
4152
|
onExit,
|
|
2813
|
-
|
|
4153
|
+
director: opts.director ?? null,
|
|
4154
|
+
fleetRoster: opts.fleetRoster,
|
|
4155
|
+
onClearHistory: opts.onClearHistory ? (dispatch) => opts.onClearHistory(dispatch) : void 0,
|
|
4156
|
+
fleetStreamController: opts.fleetStreamController,
|
|
4157
|
+
initialGoal: opts.initialGoal,
|
|
4158
|
+
initialAsk: opts.initialAsk
|
|
2814
4159
|
}),
|
|
2815
4160
|
{ exitOnCtrlC: false }
|
|
2816
4161
|
);
|
|
@@ -2822,7 +4167,24 @@ async function runTui(opts) {
|
|
|
2822
4167
|
settle(1);
|
|
2823
4168
|
return;
|
|
2824
4169
|
}
|
|
2825
|
-
|
|
4170
|
+
let detachResize = null;
|
|
4171
|
+
if (!useAltScreen) {
|
|
4172
|
+
const onResize = () => {
|
|
4173
|
+
try {
|
|
4174
|
+
stdout.write("\x1B[J");
|
|
4175
|
+
} catch {
|
|
4176
|
+
}
|
|
4177
|
+
};
|
|
4178
|
+
stdout.on("resize", onResize);
|
|
4179
|
+
detachResize = () => stdout.off("resize", onResize);
|
|
4180
|
+
}
|
|
4181
|
+
instance.waitUntilExit().then(() => {
|
|
4182
|
+
detachResize?.();
|
|
4183
|
+
settle(exitCode);
|
|
4184
|
+
}).catch(() => {
|
|
4185
|
+
detachResize?.();
|
|
4186
|
+
settle(1);
|
|
4187
|
+
});
|
|
2826
4188
|
});
|
|
2827
4189
|
}
|
|
2828
4190
|
|