@wrongstack/tui 0.1.10 → 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 +1291 -66
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { render, useApp, Box, useStdout, Static, Text, useInput } from 'ink';
|
|
1
|
+
import { render, useApp, Box, useStdout, Static, Text, useInput, useStdin } from 'ink';
|
|
2
2
|
import React, { useState, useReducer, useRef, useEffect, useMemo } from 'react';
|
|
3
3
|
import * as fs from 'fs/promises';
|
|
4
4
|
import * as path3 from 'path';
|
|
@@ -217,6 +217,159 @@ function FilePicker({ query, matches, selected }) {
|
|
|
217
217
|
function highlight(path4, query) {
|
|
218
218
|
return path4;
|
|
219
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
|
+
}
|
|
220
373
|
|
|
221
374
|
// src/markdown-table.ts
|
|
222
375
|
function renderMarkdownTables(text, maxWidth) {
|
|
@@ -596,6 +749,29 @@ function Entry({
|
|
|
596
749
|
);
|
|
597
750
|
case "banner":
|
|
598
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
|
+
}
|
|
599
775
|
}
|
|
600
776
|
}
|
|
601
777
|
function Banner({
|
|
@@ -1120,8 +1296,51 @@ function formatToolOutput(toolName, output, ok, _outputBytes, outputLines) {
|
|
|
1120
1296
|
const lastLine = lines[lines.length - 1];
|
|
1121
1297
|
return lastLine ? [head, `"${truncMid(lastLine.trim(), 70)}"`] : [head];
|
|
1122
1298
|
}
|
|
1123
|
-
|
|
1124
|
-
|
|
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;
|
|
1125
1344
|
}
|
|
1126
1345
|
function firstNonEmpty(text) {
|
|
1127
1346
|
if (!text) return void 0;
|
|
@@ -1239,6 +1458,31 @@ function truncMid(s, max) {
|
|
|
1239
1458
|
if (s.length <= max) return s;
|
|
1240
1459
|
return `${s.slice(0, max - 1)}\u2026`;
|
|
1241
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
|
+
};
|
|
1242
1486
|
function Input({
|
|
1243
1487
|
prompt = "\u203A ",
|
|
1244
1488
|
value,
|
|
@@ -1252,6 +1496,19 @@ function Input({
|
|
|
1252
1496
|
if (disabled) return;
|
|
1253
1497
|
onKey(input, key);
|
|
1254
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]);
|
|
1255
1512
|
const before = value.slice(0, cursor);
|
|
1256
1513
|
const at = value.slice(cursor, cursor + 1) || " ";
|
|
1257
1514
|
const after = value.slice(cursor + 1);
|
|
@@ -1348,6 +1605,9 @@ function StatusBar({
|
|
|
1348
1605
|
yolo = false,
|
|
1349
1606
|
elapsedMs,
|
|
1350
1607
|
todos,
|
|
1608
|
+
plan,
|
|
1609
|
+
fleet,
|
|
1610
|
+
fleetAgents,
|
|
1351
1611
|
git,
|
|
1352
1612
|
subagentCount = 0,
|
|
1353
1613
|
context,
|
|
@@ -1358,7 +1618,9 @@ function StatusBar({
|
|
|
1358
1618
|
const cache2 = tokenCounter?.cacheStats();
|
|
1359
1619
|
const stateColor = state === "idle" ? "cyan" : state === "aborting" ? "yellow" : "green";
|
|
1360
1620
|
const stateLabel = state === "idle" ? "idle" : state === "aborting" ? "aborting\u2026" : "thinking\u2026";
|
|
1361
|
-
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;
|
|
1362
1624
|
return /* @__PURE__ */ jsxs(
|
|
1363
1625
|
Box,
|
|
1364
1626
|
{
|
|
@@ -1425,7 +1687,7 @@ function StatusBar({
|
|
|
1425
1687
|
yolo ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }) : null,
|
|
1426
1688
|
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1427
1689
|
"\u23F1 ",
|
|
1428
|
-
|
|
1690
|
+
fmtElapsed2(elapsedMs)
|
|
1429
1691
|
] })
|
|
1430
1692
|
] }) : null,
|
|
1431
1693
|
projectName ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
@@ -1455,36 +1717,93 @@ function StatusBar({
|
|
|
1455
1717
|
git.untracked
|
|
1456
1718
|
] }) : null
|
|
1457
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
|
|
1458
1739
|
] }) : null,
|
|
1459
|
-
|
|
1460
|
-
/* @__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,
|
|
1461
1742
|
/* @__PURE__ */ jsxs(Text, { children: [
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1743
|
+
/* @__PURE__ */ jsx(Text, { color: "cyan", children: "\u{1F4CB} " }),
|
|
1744
|
+
plan.inProgress > 0 ? /* @__PURE__ */ jsxs(Text, { color: "yellow", children: [
|
|
1745
|
+
"\u231B",
|
|
1746
|
+
plan.inProgress
|
|
1465
1747
|
] }) : null,
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
"\u2610
|
|
1469
|
-
|
|
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
|
|
1470
1752
|
] }) : null,
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
"\u2713
|
|
1474
|
-
|
|
1753
|
+
plan.open > 0 && plan.done > 0 ? " " : "",
|
|
1754
|
+
plan.done > 0 ? /* @__PURE__ */ jsxs(Text, { color: "green", children: [
|
|
1755
|
+
"\u2713",
|
|
1756
|
+
plan.done
|
|
1475
1757
|
] }) : null
|
|
1476
1758
|
] })
|
|
1477
1759
|
] }) : null,
|
|
1478
|
-
|
|
1479
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
|
|
1480
|
-
/* @__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: [
|
|
1481
1785
|
"\u{1F310} ",
|
|
1482
1786
|
subagentCount,
|
|
1483
1787
|
" agent",
|
|
1484
1788
|
subagentCount === 1 ? "" : "s"
|
|
1485
1789
|
] })
|
|
1486
1790
|
] }) : null
|
|
1487
|
-
] }) : 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
|
|
1488
1807
|
]
|
|
1489
1808
|
}
|
|
1490
1809
|
);
|
|
@@ -1524,7 +1843,7 @@ function fmtTok2(n) {
|
|
|
1524
1843
|
if (n < 1e6) return `${(n / 1e3).toFixed(n < 1e4 ? 1 : 0)}k`;
|
|
1525
1844
|
return `${(n / 1e6).toFixed(1)}M`;
|
|
1526
1845
|
}
|
|
1527
|
-
function
|
|
1846
|
+
function fmtElapsed2(ms) {
|
|
1528
1847
|
const totalSec = Math.floor(ms / 1e3);
|
|
1529
1848
|
const h = Math.floor(totalSec / 3600);
|
|
1530
1849
|
const m = Math.floor(totalSec % 3600 / 60);
|
|
@@ -1782,6 +2101,10 @@ function reducer(state, action) {
|
|
|
1782
2101
|
return { ...state, status: action.status };
|
|
1783
2102
|
case "interrupt":
|
|
1784
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 };
|
|
1785
2108
|
case "resetInterrupts":
|
|
1786
2109
|
return { ...state, interrupts: 0 };
|
|
1787
2110
|
case "hint":
|
|
@@ -1982,9 +2305,234 @@ function reducer(state, action) {
|
|
|
1982
2305
|
return { ...state, confirm: null };
|
|
1983
2306
|
case "resetContextChip":
|
|
1984
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
|
+
}
|
|
1985
2430
|
}
|
|
1986
2431
|
}
|
|
1987
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
|
+
}
|
|
1988
2536
|
function App({
|
|
1989
2537
|
agent,
|
|
1990
2538
|
slashRegistry,
|
|
@@ -2003,7 +2551,12 @@ function App({
|
|
|
2003
2551
|
switchProviderAndModel,
|
|
2004
2552
|
effectiveMaxContext,
|
|
2005
2553
|
onExit,
|
|
2006
|
-
|
|
2554
|
+
director,
|
|
2555
|
+
fleetRoster,
|
|
2556
|
+
onClearHistory,
|
|
2557
|
+
fleetStreamController,
|
|
2558
|
+
initialGoal,
|
|
2559
|
+
initialAsk
|
|
2007
2560
|
}) {
|
|
2008
2561
|
const { exit } = useApp();
|
|
2009
2562
|
const [liveModel, setLiveModel] = useState(model);
|
|
@@ -2028,6 +2581,8 @@ function App({
|
|
|
2028
2581
|
toolStream: null,
|
|
2029
2582
|
status: "idle",
|
|
2030
2583
|
interrupts: 0,
|
|
2584
|
+
steeringPending: false,
|
|
2585
|
+
steerSnapshot: null,
|
|
2031
2586
|
hint: "",
|
|
2032
2587
|
nextId: 1,
|
|
2033
2588
|
picker: { open: false, query: "", matches: [], selected: 0 },
|
|
@@ -2045,13 +2600,18 @@ function App({
|
|
|
2045
2600
|
selected: 0
|
|
2046
2601
|
},
|
|
2047
2602
|
confirm: null,
|
|
2048
|
-
contextChipVersion: 0
|
|
2603
|
+
contextChipVersion: 0,
|
|
2604
|
+
fleet: {},
|
|
2605
|
+
fleetCost: 0,
|
|
2606
|
+
streamFleet: true
|
|
2049
2607
|
});
|
|
2050
2608
|
const builderRef = useRef(null);
|
|
2051
2609
|
if (builderRef.current === null) {
|
|
2052
2610
|
builderRef.current = new InputBuilder({ store: attachments });
|
|
2053
2611
|
}
|
|
2054
2612
|
const activeCtrlRef = useRef(null);
|
|
2613
|
+
const inputGateRef = useRef(false);
|
|
2614
|
+
const lastEnterAtRef = useRef(0);
|
|
2055
2615
|
const projectRoot = agent.ctx.projectRoot;
|
|
2056
2616
|
const projectName = React.useMemo(() => {
|
|
2057
2617
|
const base = path3.basename(projectRoot);
|
|
@@ -2108,6 +2668,111 @@ function App({
|
|
|
2108
2668
|
}
|
|
2109
2669
|
return counts;
|
|
2110
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
|
+
]);
|
|
2111
2776
|
useEffect(() => {
|
|
2112
2777
|
const detected = detectAtToken(state.buffer, state.cursor);
|
|
2113
2778
|
if (!detected) {
|
|
@@ -2267,6 +2932,128 @@ function App({
|
|
|
2267
2932
|
slashRegistry.unregister("queue");
|
|
2268
2933
|
};
|
|
2269
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]);
|
|
2270
3057
|
useEffect(() => {
|
|
2271
3058
|
if (!getPickableProviders || !switchProviderAndModel) return;
|
|
2272
3059
|
const cmd = {
|
|
@@ -2397,18 +3184,298 @@ function App({
|
|
|
2397
3184
|
offConfirmNeeded();
|
|
2398
3185
|
if (flushTimerRef.current) clearTimeout(flushTimerRef.current);
|
|
2399
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
|
+
};
|
|
2400
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]);
|
|
2401
3448
|
useEffect(() => {
|
|
2402
3449
|
const onSigint = () => {
|
|
2403
|
-
if (state.interrupts >= 1
|
|
2404
|
-
|
|
2405
|
-
|
|
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" });
|
|
2406
3466
|
return;
|
|
2407
3467
|
}
|
|
2408
3468
|
dispatch({ type: "interrupt" });
|
|
2409
3469
|
if (activeCtrlRef.current) {
|
|
2410
3470
|
activeCtrlRef.current.abort();
|
|
2411
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
|
+
}
|
|
2412
3479
|
const droppedCount = stateRef.current.queue.length;
|
|
2413
3480
|
if (droppedCount > 0) {
|
|
2414
3481
|
dispatch({ type: "queueClear" });
|
|
@@ -2416,13 +3483,16 @@ function App({
|
|
|
2416
3483
|
type: "addEntry",
|
|
2417
3484
|
entry: {
|
|
2418
3485
|
kind: "warn",
|
|
2419
|
-
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.`
|
|
2420
3487
|
}
|
|
2421
3488
|
});
|
|
2422
3489
|
} else {
|
|
2423
3490
|
dispatch({
|
|
2424
3491
|
type: "addEntry",
|
|
2425
|
-
entry: {
|
|
3492
|
+
entry: {
|
|
3493
|
+
kind: "warn",
|
|
3494
|
+
text: `Iteration cancelled${director ? " + fleet terminated" : ""}. Press Ctrl+C again to exit.`
|
|
3495
|
+
}
|
|
2426
3496
|
});
|
|
2427
3497
|
}
|
|
2428
3498
|
} else {
|
|
@@ -2436,9 +3506,11 @@ function App({
|
|
|
2436
3506
|
return () => {
|
|
2437
3507
|
process.off("SIGINT", onSigint);
|
|
2438
3508
|
};
|
|
2439
|
-
}, [state.interrupts,
|
|
3509
|
+
}, [state.interrupts, exit, onExit, director]);
|
|
2440
3510
|
const handleKey = async (input, key) => {
|
|
2441
3511
|
if (state.status === "aborting") return;
|
|
3512
|
+
if (inputGateRef.current) return;
|
|
3513
|
+
const isEnter = key.return || input === "\r" || input === "\n";
|
|
2442
3514
|
if (state.modelPicker.open) {
|
|
2443
3515
|
if (key.escape) {
|
|
2444
3516
|
if (state.modelPicker.step === "model") {
|
|
@@ -2456,33 +3528,38 @@ function App({
|
|
|
2456
3528
|
dispatch({ type: "modelPickerMove", delta: 1 });
|
|
2457
3529
|
return;
|
|
2458
3530
|
}
|
|
2459
|
-
if (
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
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);
|
|
2463
3554
|
dispatch({
|
|
2464
|
-
type: "
|
|
2465
|
-
|
|
2466
|
-
models: opt.models
|
|
3555
|
+
type: "addEntry",
|
|
3556
|
+
entry: { kind: "info", text: `Switched to ${providerId} / ${modelId}.` }
|
|
2467
3557
|
});
|
|
3558
|
+
dispatch({ type: "modelPickerClose" });
|
|
2468
3559
|
return;
|
|
3560
|
+
} finally {
|
|
3561
|
+
inputGateRef.current = false;
|
|
2469
3562
|
}
|
|
2470
|
-
const providerId = state.modelPicker.pickedProviderId;
|
|
2471
|
-
const modelId = state.modelPicker.modelOptions[state.modelPicker.selected];
|
|
2472
|
-
if (!providerId || !modelId) return;
|
|
2473
|
-
const err = switchProviderAndModel?.(providerId, modelId);
|
|
2474
|
-
if (err) {
|
|
2475
|
-
dispatch({ type: "modelPickerHint", text: err });
|
|
2476
|
-
return;
|
|
2477
|
-
}
|
|
2478
|
-
setLiveProvider(providerId);
|
|
2479
|
-
setLiveModel(modelId);
|
|
2480
|
-
dispatch({
|
|
2481
|
-
type: "addEntry",
|
|
2482
|
-
entry: { kind: "info", text: `Switched to ${providerId} / ${modelId}.` }
|
|
2483
|
-
});
|
|
2484
|
-
dispatch({ type: "modelPickerClose" });
|
|
2485
|
-
return;
|
|
2486
3563
|
}
|
|
2487
3564
|
return;
|
|
2488
3565
|
}
|
|
@@ -2499,8 +3576,10 @@ function App({
|
|
|
2499
3576
|
dispatch({ type: "slashPickerMove", delta: 1 });
|
|
2500
3577
|
return;
|
|
2501
3578
|
}
|
|
2502
|
-
if (
|
|
2503
|
-
|
|
3579
|
+
if (isEnter) {
|
|
3580
|
+
inputGateRef.current = true;
|
|
3581
|
+
acceptSlashPickerSelection();
|
|
3582
|
+
inputGateRef.current = false;
|
|
2504
3583
|
return;
|
|
2505
3584
|
}
|
|
2506
3585
|
if (key.tab && state.slashPicker.matches.length > 0) {
|
|
@@ -2525,13 +3604,61 @@ function App({
|
|
|
2525
3604
|
dispatch({ type: "pickerMove", delta: 1 });
|
|
2526
3605
|
return;
|
|
2527
3606
|
}
|
|
2528
|
-
if (
|
|
2529
|
-
|
|
3607
|
+
if (isEnter) {
|
|
3608
|
+
inputGateRef.current = true;
|
|
3609
|
+
try {
|
|
3610
|
+
await acceptPickerSelection();
|
|
3611
|
+
} finally {
|
|
3612
|
+
inputGateRef.current = false;
|
|
3613
|
+
}
|
|
2530
3614
|
return;
|
|
2531
3615
|
}
|
|
2532
3616
|
}
|
|
2533
|
-
if (key.
|
|
2534
|
-
|
|
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();
|
|
2535
3662
|
return;
|
|
2536
3663
|
}
|
|
2537
3664
|
if (key.backspace || key.delete) {
|
|
@@ -2586,6 +3713,14 @@ function App({
|
|
|
2586
3713
|
dispatch({ type: "setBuffer", buffer: state.buffer, cursor: state.cursor + 1 });
|
|
2587
3714
|
return;
|
|
2588
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 });
|
|
3722
|
+
return;
|
|
3723
|
+
}
|
|
2589
3724
|
if (key.upArrow) {
|
|
2590
3725
|
if (state.inputHistory.length > 0) dispatch({ type: "historyUp" });
|
|
2591
3726
|
return;
|
|
@@ -2725,6 +3860,18 @@ function App({
|
|
|
2725
3860
|
exit();
|
|
2726
3861
|
onExit(0);
|
|
2727
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
|
+
}
|
|
2728
3875
|
const cmd = trimmed.slice(1).split(/\s+/, 1)[0];
|
|
2729
3876
|
if (cmd === "clear") {
|
|
2730
3877
|
onClearHistory?.(dispatch);
|
|
@@ -2739,9 +3886,14 @@ function App({
|
|
|
2739
3886
|
}
|
|
2740
3887
|
const builder = builderRef.current;
|
|
2741
3888
|
if (!builder) return;
|
|
2742
|
-
|
|
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" });
|
|
2743
3895
|
const blocks = await builder.submit();
|
|
2744
|
-
const displayText = trimmed
|
|
3896
|
+
const displayText = trimmed ? steering ? `\u21AF ${trimmed}` : trimmed : "(attachments only)";
|
|
2745
3897
|
dispatch({ type: "clearInput" });
|
|
2746
3898
|
if (state.status !== "idle") {
|
|
2747
3899
|
dispatch({
|
|
@@ -2756,6 +3908,36 @@ function App({
|
|
|
2756
3908
|
if (state.historyIndex > 0) dispatch({ type: "historyPush", text: trimmed });
|
|
2757
3909
|
await runBlocks(blocks);
|
|
2758
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
|
+
}, []);
|
|
2759
3941
|
const inputHint = useMemo(() => {
|
|
2760
3942
|
if (state.status !== "idle") return "";
|
|
2761
3943
|
if (state.buffer.startsWith("/")) return "slash command \u2014 Enter to dispatch";
|
|
@@ -2771,6 +3953,7 @@ function App({
|
|
|
2771
3953
|
toolStream: state.toolStream
|
|
2772
3954
|
}
|
|
2773
3955
|
),
|
|
3956
|
+
/* @__PURE__ */ jsx(LiveActivityStrip, { entries: state.fleet, nowTick }),
|
|
2774
3957
|
/* @__PURE__ */ jsx(
|
|
2775
3958
|
Input,
|
|
2776
3959
|
{
|
|
@@ -2829,11 +4012,16 @@ function App({
|
|
|
2829
4012
|
yolo,
|
|
2830
4013
|
elapsedMs,
|
|
2831
4014
|
todos,
|
|
4015
|
+
plan: planCounts ?? void 0,
|
|
4016
|
+
fleet: fleetCounts,
|
|
4017
|
+
fleetAgents,
|
|
2832
4018
|
git: gitInfo,
|
|
2833
4019
|
context: contextWindow,
|
|
2834
|
-
projectName
|
|
4020
|
+
projectName,
|
|
4021
|
+
subagentCount: Object.keys(state.fleet).length
|
|
2835
4022
|
}
|
|
2836
|
-
)
|
|
4023
|
+
),
|
|
4024
|
+
director ? /* @__PURE__ */ jsx(FleetPanel, { entries: state.fleet, totalCost: state.fleetCost, roster: fleetRoster }) : null
|
|
2837
4025
|
] });
|
|
2838
4026
|
}
|
|
2839
4027
|
function renderRunningTools(running) {
|
|
@@ -2889,6 +4077,15 @@ async function runTui(opts) {
|
|
|
2889
4077
|
stdout.write(CURSOR_HOME);
|
|
2890
4078
|
}
|
|
2891
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
|
+
}
|
|
2892
4089
|
let cleaned = false;
|
|
2893
4090
|
const cleanup = () => {
|
|
2894
4091
|
if (cleaned) return;
|
|
@@ -2908,6 +4105,12 @@ async function runTui(opts) {
|
|
|
2908
4105
|
process.on("exit", exitHandler);
|
|
2909
4106
|
const detachListeners = () => {
|
|
2910
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
|
+
}
|
|
2911
4114
|
process.off("exit", exitHandler);
|
|
2912
4115
|
};
|
|
2913
4116
|
return new Promise((resolve) => {
|
|
@@ -2947,7 +4150,12 @@ async function runTui(opts) {
|
|
|
2947
4150
|
switchProviderAndModel: opts.switchProviderAndModel,
|
|
2948
4151
|
effectiveMaxContext: opts.effectiveMaxContext,
|
|
2949
4152
|
onExit,
|
|
2950
|
-
|
|
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
|
|
2951
4159
|
}),
|
|
2952
4160
|
{ exitOnCtrlC: false }
|
|
2953
4161
|
);
|
|
@@ -2959,7 +4167,24 @@ async function runTui(opts) {
|
|
|
2959
4167
|
settle(1);
|
|
2960
4168
|
return;
|
|
2961
4169
|
}
|
|
2962
|
-
|
|
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
|
+
});
|
|
2963
4188
|
});
|
|
2964
4189
|
}
|
|
2965
4190
|
|