@wrongstack/tui 0.66.13 → 0.73.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,8 +1,7 @@
1
- import { Readable } from 'stream';
2
1
  import { writeErr, InputBuilder, DefaultSessionRewinder, writeOut, formatTodosList, buildGoalPreamble, buildChildEnv } from '@wrongstack/core';
3
2
  export { buildGoalPreamble } from '@wrongstack/core';
4
3
  import { Box, Text, render, useApp, useStdout, measureElement, Static, useInput, useStdin } from 'ink';
5
- import React4, { useState, useEffect, useReducer, useRef, useMemo, useCallback, useLayoutEffect } from 'react';
4
+ import React5, { useState, useEffect, useReducer, useRef, useMemo, useCallback, useLayoutEffect } from 'react';
6
5
  import * as fs2 from 'fs/promises';
7
6
  import * as path2 from 'path';
8
7
  import { routeImagesForModel } from '@wrongstack/runtime/vision';
@@ -450,28 +449,6 @@ function ContextChip({ ctx }) {
450
449
  ] })
451
450
  ] });
452
451
  }
453
- var SB_GAP = 2;
454
- var SB_PADX = 1;
455
- function statusBarModelSpan(opts) {
456
- let col = SB_PADX;
457
- if (opts.version) {
458
- col += `WS v${opts.version}`.length + SB_GAP;
459
- col += 1 + SB_GAP;
460
- }
461
- const { label } = stateChip(opts.state, opts.fleetRunning ?? 0);
462
- col += 2 + label.length + SB_GAP;
463
- col += 1 + SB_GAP;
464
- return { start: col, len: opts.model.length };
465
- }
466
- function statusBarAutonomySpan(opts) {
467
- if (!opts.autonomy || opts.autonomy === "off") return null;
468
- let col = SB_PADX;
469
- if (opts.yolo) {
470
- col += "\u26A0 YOLO".length + SB_GAP;
471
- col += 1 + SB_GAP;
472
- }
473
- return { start: col, len: 2 + opts.autonomy.toUpperCase().length };
474
- }
475
452
  function stateChip(state, fleetRunning) {
476
453
  if (state === "idle" && fleetRunning > 0) {
477
454
  return { label: `agents \u25B6${fleetRunning}`, color: "magenta" };
@@ -1140,16 +1117,6 @@ function buttonLabels(suggestedPattern) {
1140
1117
  { decision: "deny", bracket: "[d]", rest: "eny" }
1141
1118
  ];
1142
1119
  }
1143
- function confirmButtonSegments(suggestedPattern) {
1144
- const out = [];
1145
- let col = 0;
1146
- for (const l of buttonLabels(suggestedPattern)) {
1147
- const len = l.bracket.length + l.rest.length;
1148
- out.push({ decision: l.decision, start: col, len });
1149
- col += len;
1150
- }
1151
- return out;
1152
- }
1153
1120
  function stringifyInput(input) {
1154
1121
  if (!input || typeof input !== "object") return "";
1155
1122
  const obj = input;
@@ -1180,7 +1147,7 @@ function ConfirmPrompt({
1180
1147
  suggestedPattern,
1181
1148
  onDecision
1182
1149
  }) {
1183
- React4.useEffect(() => {
1150
+ React5.useEffect(() => {
1184
1151
  writeOut("\x07");
1185
1152
  }, []);
1186
1153
  useInput((input2, _key) => {
@@ -1209,7 +1176,7 @@ function ConfirmPrompt({
1209
1176
  inputSummary ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: inputSummary }) : null,
1210
1177
  showDiff && diff ? /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginY: 1, children: renderDiff(diff) }) : null,
1211
1178
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }),
1212
- /* @__PURE__ */ jsx(Box, { flexDirection: "row", children: /* @__PURE__ */ jsx(Text, { children: buttonLabels(suggestedPattern).map((l) => /* @__PURE__ */ jsxs(React4.Fragment, { children: [
1179
+ /* @__PURE__ */ jsx(Box, { flexDirection: "row", children: /* @__PURE__ */ jsx(Text, { children: buttonLabels(suggestedPattern).map((l) => /* @__PURE__ */ jsxs(React5.Fragment, { children: [
1213
1180
  /* @__PURE__ */ jsx(Text, { bold: true, color: BUTTON_COLOR[l.decision], children: l.bracket }),
1214
1181
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: l.rest })
1215
1182
  ] }, l.decision)) }) })
@@ -1306,13 +1273,6 @@ function FleetPanel({
1306
1273
  function helpSections(opts) {
1307
1274
  const nav = [];
1308
1275
  if (opts.managed) nav.push({ keys: "PgUp/PgDn", desc: "scroll chat history" });
1309
- if (opts.mouse)
1310
- nav.push(
1311
- { keys: "wheel", desc: "scroll chat history" },
1312
- { keys: "drag scrollbar", desc: "scrub to any position" },
1313
- { keys: "click input", desc: "move the caret" },
1314
- { keys: "click", desc: "select / confirm" }
1315
- );
1316
1276
  nav.push(
1317
1277
  { keys: "\u2191/\u2193", desc: "previous / next input (empty prompt)" },
1318
1278
  { keys: "?", desc: "open this help (empty prompt)" }
@@ -1353,10 +1313,9 @@ function helpSections(opts) {
1353
1313
  ];
1354
1314
  }
1355
1315
  function HelpOverlay({
1356
- managed,
1357
- mouse
1316
+ managed
1358
1317
  }) {
1359
- const sections = helpSections({ managed, mouse });
1318
+ const sections = helpSections({ managed });
1360
1319
  const keyWidth = Math.max(...sections.flatMap((s2) => s2.entries.map((e) => e.keys.length)), 0);
1361
1320
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
1362
1321
  /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
@@ -2218,553 +2177,115 @@ function MarkdownView({
2218
2177
  }
2219
2178
  return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: rows });
2220
2179
  }
2221
- var MESSAGE_PANEL_CHROME_WIDTH = 2;
2222
- function assistantContentWidth(termWidth) {
2223
- return Math.max(20, termWidth - MESSAGE_PANEL_CHROME_WIDTH);
2180
+ function shortenPath(p, max) {
2181
+ if (p.length <= max) return p;
2182
+ return `\u2026${p.slice(p.length - (max - 1))}`;
2224
2183
  }
2225
- function History({ entries, streamingText, toolStream }) {
2226
- const { stdout } = useStdout();
2227
- const [termSize, setTermSize] = useState({
2228
- columns: stdout?.columns ?? 80,
2229
- rows: stdout?.rows ?? 24
2230
- });
2231
- useEffect(() => {
2232
- const handleResize = () => {
2233
- setTermSize({ columns: stdout?.columns ?? 80, rows: stdout?.rows ?? 24 });
2234
- };
2235
- process.stdout.on("resize", handleResize);
2236
- return () => {
2237
- process.stdout.off("resize", handleResize);
2238
- };
2239
- }, [stdout]);
2240
- const termWidth = termSize.columns;
2241
- const tail = streamingText ? tailForDisplay(streamingText, MAX_STREAM_DISPLAY_CHARS) : "";
2242
- const toolTail = toolStream?.text ? tailForDisplay(toolStream.text, MAX_STREAM_DISPLAY_CHARS) : "";
2243
- return /* @__PURE__ */ jsxs(Fragment, { children: [
2244
- /* @__PURE__ */ jsx(Static, { items: entries, children: (entry) => /* @__PURE__ */ jsx(Box, { marginBottom: entry.kind === "turn-summary" ? 1 : 0, children: /* @__PURE__ */ jsx(Entry, { entry, termWidth }) }, entry.id) }),
2245
- tail ? /* @__PURE__ */ jsx(AssistantTail, { text: tail }) : null,
2246
- toolTail ? /* @__PURE__ */ jsx(
2247
- ToolStreamBox,
2248
- {
2249
- name: toolStream.name,
2250
- text: toolTail,
2251
- startedAt: toolStream.startedAt,
2252
- termWidth
2253
- }
2254
- ) : null
2255
- ] });
2184
+ function fmtTok2(n) {
2185
+ if (!Number.isFinite(n) || n <= 0) return "0";
2186
+ if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
2187
+ if (n >= 1e3) return `${(n / 1e3).toFixed(n >= 1e4 ? 0 : 1)}k`;
2188
+ return String(n);
2256
2189
  }
2257
- function AssistantTail({ text }) {
2258
- return /* @__PURE__ */ jsxs(
2259
- Box,
2260
- {
2261
- flexDirection: "column",
2262
- marginY: 1,
2263
- borderStyle: "single",
2264
- borderTop: false,
2265
- borderRight: false,
2266
- borderBottom: false,
2267
- borderColor: theme.assistant,
2268
- paddingLeft: 1,
2269
- children: [
2270
- /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
2271
- /* @__PURE__ */ jsx(Text, { bold: true, color: theme.assistant, children: "ASSISTANT" }),
2272
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: " (streaming\u2026)" })
2273
- ] }),
2274
- /* @__PURE__ */ jsx(Text, { color: "white", children: text })
2275
- ]
2276
- }
2277
- );
2190
+ function fmtDuration(ms) {
2191
+ if (ms < 1e3) return `${ms}ms`;
2192
+ if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
2193
+ const totalSec = Math.floor(ms / 1e3);
2194
+ return `${Math.floor(totalSec / 60)}m${totalSec % 60}s`;
2278
2195
  }
2279
- var MAX_CODE_LINES = 80;
2280
- function splitFencedBlocks(text) {
2281
- const lines = text.split("\n");
2282
- const segs = [];
2283
- let prose = [];
2284
- let code = null;
2285
- let lang = "plain";
2286
- const flushProse = () => {
2287
- if (prose.length > 0) {
2288
- segs.push({ type: "prose", text: prose.join("\n") });
2289
- prose = [];
2290
- }
2291
- };
2292
- for (const line of lines) {
2293
- const fence = line.match(/^\s*```(.*)$/);
2294
- if (fence) {
2295
- if (code === null) {
2296
- flushProse();
2297
- code = [];
2298
- lang = detectLang(fence[1] ?? "");
2299
- } else {
2300
- segs.push({ type: "code", text: code.join("\n"), lang });
2301
- code = null;
2302
- lang = "plain";
2303
- }
2304
- continue;
2305
- }
2306
- if (code !== null) code.push(line);
2307
- else prose.push(line);
2308
- }
2309
- if (code !== null) segs.push({ type: "code", text: code.join("\n"), lang });
2310
- flushProse();
2311
- return segs;
2196
+ function fmtBytes(n) {
2197
+ if (n < 1024) return `${n}B`;
2198
+ if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)}KB`;
2199
+ return `${(n / (1024 * 1024)).toFixed(1)}MB`;
2312
2200
  }
2313
- function CodeBlock({
2314
- code,
2315
- lang,
2316
- contentWidth
2317
- }) {
2318
- let lines = code.replace(/\n+$/, "").split("\n");
2319
- const hidden = Math.max(0, lines.length - MAX_CODE_LINES);
2320
- if (hidden > 0) lines = lines.slice(0, MAX_CODE_LINES);
2321
- const gutterW = String(lines.length).length;
2322
- const maxW = Math.max(20, Math.min(contentWidth - 6 - gutterW - 1, 120));
2323
- let carry = {};
2324
- const rows = lines.map((raw) => {
2325
- const display = raw.length > maxW ? `${raw.slice(0, maxW - 1)}\u2026` : raw;
2326
- const r = highlightLine(display, lang, carry);
2327
- carry = r.carry;
2328
- return r.tokens;
2329
- });
2330
- return /* @__PURE__ */ jsxs(
2331
- Box,
2332
- {
2333
- flexDirection: "column",
2334
- marginLeft: 2,
2335
- marginY: 0,
2336
- borderStyle: "round",
2337
- borderColor: theme.borderDefault,
2338
- paddingX: 1,
2339
- children: [
2340
- lang !== "plain" ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: lang }) : null,
2341
- rows.map((tokens, i) => (
2342
- // biome-ignore lint/suspicious/noArrayIndexKey: code lines are positional
2343
- /* @__PURE__ */ jsxs(Text, { children: [
2344
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: `${String(i + 1).padStart(gutterW, " ")} ` }),
2345
- tokens.length === 0 ? " " : tokens.map((t, j) => (
2346
- // biome-ignore lint/suspicious/noArrayIndexKey: token order is stable per line
2347
- /* @__PURE__ */ jsx(Text, { color: t.color, dimColor: t.dim, bold: t.bold, children: t.text }, j)
2348
- ))
2349
- ] }, i)
2350
- )),
2351
- hidden > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, italic: true, children: `\u2026 +${hidden} more line${hidden === 1 ? "" : "s"}` }) : null
2352
- ]
2353
- }
2354
- );
2201
+ function truncMid(s2, max) {
2202
+ if (s2.length <= max) return s2;
2203
+ return `${s2.slice(0, max - 1)}\u2026`;
2355
2204
  }
2356
- function AssistantBody({
2357
- text,
2358
- termWidth,
2359
- contentWidth
2360
- }) {
2361
- const segments = splitFencedBlocks(text);
2362
- const inner = contentWidth ?? termWidth;
2363
- return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: segments.map(
2364
- (seg, i) => seg.type === "code" ? (
2365
- // biome-ignore lint/suspicious/noArrayIndexKey: segment order is stable
2366
- /* @__PURE__ */ jsx(CodeBlock, { code: seg.text, lang: seg.lang ?? "plain", contentWidth: inner }, i)
2367
- ) : (
2368
- // biome-ignore lint/suspicious/noArrayIndexKey: segment order is stable
2369
- /* @__PURE__ */ jsx(MarkdownView, { text: seg.text, termWidth: inner }, i)
2370
- )
2371
- ) });
2205
+ function stringOf(v) {
2206
+ return typeof v === "string" && v.length > 0 ? v : void 0;
2372
2207
  }
2373
- var MAX_STREAM_DISPLAY_CHARS = 480;
2374
- var MAX_STREAM_LINES = 8;
2375
- var ToolStreamBox = React4.memo(function ToolStreamBox2({
2376
- name,
2377
- text,
2378
- startedAt,
2379
- termWidth
2380
- }) {
2381
- const [tick, setTick] = useState(0);
2382
- useEffect(() => {
2383
- const t = setInterval(() => setTick((n) => n + 1), 500);
2384
- return () => clearInterval(t);
2385
- }, []);
2386
- const elapsedMs = Date.now() - startedAt;
2387
- const lines = text.split("\n");
2388
- const totalLines = lines.length;
2389
- const hidden = Math.max(0, totalLines - MAX_STREAM_LINES);
2390
- const visible = hidden > 0 ? lines.slice(hidden) : lines;
2391
- const contentWidth = Math.max(20, Math.min(termWidth - 4, 100));
2392
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 0, children: [
2393
- /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
2394
- /* @__PURE__ */ jsx(Text, { color: theme.warn, children: "\u25C6 " }),
2395
- /* @__PURE__ */ jsx(Text, { bold: true, color: theme.tool, children: name }),
2396
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u23F1 ${fmtDuration(elapsedMs)}` }),
2397
- hidden > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` (${totalLines} lines, showing last ${MAX_STREAM_LINES})` }) : null
2398
- ] }),
2399
- /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [
2400
- hidden > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, italic: true, children: ` \u2026 ${hidden} more line${hidden === 1 ? "" : "s"} above` }) : null,
2401
- visible.map((line, i) => {
2402
- const key = i;
2403
- const trimmed = line.length > contentWidth ? `${line.slice(0, contentWidth - 1)}\u2026` : line;
2404
- return /* @__PURE__ */ jsx(Text, { dimColor: true, children: trimmed || " " }, key);
2405
- })
2406
- ] })
2407
- ] });
2408
- });
2409
- function tailForDisplay(text, maxChars) {
2410
- if (text.length <= maxChars) return text;
2411
- const cut = text.length - maxChars;
2412
- const nl = text.indexOf("\n", cut);
2413
- if (nl !== -1 && nl < cut + 80) {
2414
- return `\u2026 ${text.slice(nl + 1)}`;
2208
+ function numOf(v) {
2209
+ return typeof v === "number" && Number.isFinite(v) ? v : void 0;
2210
+ }
2211
+ function tryParseJson(s2) {
2212
+ const t = s2.trimStart();
2213
+ if (!t.startsWith("{") && !t.startsWith("[")) return void 0;
2214
+ try {
2215
+ return JSON.parse(s2);
2216
+ } catch {
2217
+ return void 0;
2415
2218
  }
2416
- return `\u2026 ${text.slice(cut)}`;
2417
2219
  }
2418
- function DiffBlock({ rows, hidden }) {
2419
- let gutterWidth = 1;
2420
- for (const r of rows) {
2421
- const n = r.kind === "del" ? r.oldLine : r.newLine;
2422
- if (typeof n === "number") {
2423
- const w = String(n).length;
2424
- if (w > gutterWidth) gutterWidth = w;
2220
+ function scanNumberedRange(text) {
2221
+ let first;
2222
+ let last;
2223
+ let count = 0;
2224
+ for (const line of text.split("\n")) {
2225
+ const m = line.match(/^\s*(\d+)→/);
2226
+ if (m?.[1]) {
2227
+ const n = Number.parseInt(m[1], 10);
2228
+ if (Number.isFinite(n)) {
2229
+ if (first === void 0) first = n;
2230
+ last = n;
2231
+ count++;
2232
+ }
2425
2233
  }
2426
2234
  }
2427
- const blank = " ".repeat(gutterWidth);
2428
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginLeft: 4, marginTop: 0, children: [
2429
- rows.map((row, i) => {
2430
- const key = i;
2431
- if (row.kind === "hunk") {
2432
- return /* @__PURE__ */ jsx(Text, { color: "cyan", dimColor: true, children: row.text }, key);
2433
- }
2434
- if (row.kind === "meta") {
2435
- return /* @__PURE__ */ jsx(Text, { dimColor: true, children: `${blank} ${row.text}` }, key);
2436
- }
2437
- const lnNumber = row.kind === "del" ? row.oldLine : row.newLine;
2438
- const lnText = typeof lnNumber === "number" ? String(lnNumber).padStart(gutterWidth, " ") : blank;
2439
- if (row.kind === "ctx") {
2440
- return /* @__PURE__ */ jsx(Text, { dimColor: true, children: `${lnText} ${row.text}` }, key);
2441
- }
2442
- const bg = row.kind === "add" ? theme.diffAddBg : theme.diffDelBg;
2443
- return /* @__PURE__ */ jsxs(Text, { children: [
2444
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: `${lnText} ` }),
2445
- /* @__PURE__ */ jsx(Text, { backgroundColor: bg, color: "black", children: row.text })
2446
- ] }, key);
2447
- }),
2448
- hidden > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, italic: true, children: `${blank} \u2026 ${hidden} more line${hidden === 1 ? "" : "s"}` }) : null
2449
- ] });
2235
+ return { first, last, count };
2450
2236
  }
2451
- function brainStatusStyle(status) {
2452
- switch (status) {
2453
- case "thinking":
2454
- return { icon: "\u2026", color: "magenta" };
2455
- case "answered":
2456
- return { icon: "\u2696", color: "cyan" };
2457
- case "ask_human":
2458
- return { icon: "?", color: "yellow" };
2459
- case "denied":
2460
- return { icon: "\xD7", color: "red" };
2461
- }
2237
+ function countLines(text) {
2238
+ if (!text) return 0;
2239
+ return text.replace(/\n$/, "").split("\n").length;
2462
2240
  }
2463
- function brainRiskColor(risk) {
2464
- switch (risk) {
2465
- case "low":
2466
- return "green";
2467
- case "medium":
2468
- return "cyan";
2469
- case "high":
2470
- return "yellow";
2471
- case "critical":
2472
- return "red";
2241
+ function firstNonEmpty(text) {
2242
+ if (!text) return void 0;
2243
+ const line = text.split("\n").find((l) => l.trim());
2244
+ return line ? line.replace(/\s+/g, " ").trim() : void 0;
2245
+ }
2246
+ function formatMatchHit(hit) {
2247
+ if (typeof hit === "string") return truncMid(hit, 70);
2248
+ if (hit && typeof hit === "object") {
2249
+ const o = hit;
2250
+ const file = stringOf(o["file"]) ?? stringOf(o["path"]);
2251
+ const line = numOf(o["line"]) ?? numOf(o["lineNumber"]);
2252
+ const snippet2 = stringOf(o["text"]) ?? stringOf(o["match"]) ?? stringOf(o["preview"]);
2253
+ if (file) {
2254
+ const head = line !== void 0 ? `${shortenPath(file, 40)}:${line}` : shortenPath(file, 50);
2255
+ return snippet2 ? `${head} ${truncMid(snippet2.replace(/\s+/g, " "), 40)}` : head;
2256
+ }
2257
+ if (snippet2) return truncMid(snippet2, 70);
2473
2258
  }
2259
+ return void 0;
2474
2260
  }
2475
- var Entry = React4.memo(function Entry2({
2476
- entry,
2477
- termWidth
2478
- }) {
2479
- switch (entry.kind) {
2480
- case "user":
2481
- return /* @__PURE__ */ jsx(
2482
- Box,
2483
- {
2484
- borderStyle: "single",
2485
- borderTop: false,
2486
- borderRight: false,
2487
- borderBottom: false,
2488
- borderColor: theme.user,
2489
- paddingLeft: 1,
2490
- children: /* @__PURE__ */ jsxs(Text, { children: [
2491
- /* @__PURE__ */ jsx(Text, { bold: true, color: theme.user, children: "USER " }),
2492
- /* @__PURE__ */ jsx(Text, { color: "white", children: entry.text }),
2493
- entry.queued ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: " (queued)" }) : null,
2494
- entry.pasteContent ? /* @__PURE__ */ jsxs(Fragment, { children: [
2495
- entry.text ? "\n" : null,
2496
- /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
2497
- " \u21B3 ",
2498
- entry.pasteContent
2499
- ] })
2500
- ] }) : null
2501
- ] })
2502
- }
2503
- );
2504
- case "assistant": {
2505
- const contentWidth = assistantContentWidth(termWidth);
2506
- return /* @__PURE__ */ jsxs(
2507
- Box,
2508
- {
2509
- flexDirection: "column",
2510
- marginY: 1,
2511
- borderStyle: "single",
2512
- borderTop: false,
2513
- borderRight: false,
2514
- borderBottom: false,
2515
- borderColor: theme.assistant,
2516
- paddingLeft: 1,
2517
- children: [
2518
- /* @__PURE__ */ jsx(Box, { flexDirection: "row", children: /* @__PURE__ */ jsx(Text, { bold: true, color: theme.assistant, children: "ASSISTANT" }) }),
2519
- /* @__PURE__ */ jsx(AssistantBody, { text: entry.text, termWidth, contentWidth })
2520
- ]
2521
- }
2522
- );
2261
+ var ARG_BUDGET = 60;
2262
+ function formatToolArgs(toolName, input) {
2263
+ if (!input || typeof input !== "object") return "";
2264
+ const obj = input;
2265
+ switch (toolName) {
2266
+ case "read":
2267
+ case "write":
2268
+ case "edit":
2269
+ case "patch":
2270
+ case "document":
2271
+ case "list_dir":
2272
+ case "ls":
2273
+ case "tree": {
2274
+ const p = stringOf(obj["path"]) ?? stringOf(obj["file"]);
2275
+ return p ? shortenPath(p, ARG_BUDGET) : "";
2523
2276
  }
2524
- case "tool": {
2525
- const argSummary = formatToolArgs(entry.name, entry.input);
2526
- const outLines = formatToolOutput(
2527
- entry.name,
2528
- entry.output,
2529
- entry.ok,
2530
- entry.outputBytes,
2531
- entry.outputLines
2532
- );
2533
- const diff = entry.ok ? extractDiffPreview(entry.name, entry.output) : void 0;
2534
- const sizeChip = (() => {
2535
- if (!entry.ok) return "";
2536
- const parts = [];
2537
- if (entry.outputLines !== void 0 && entry.outputLines > 0) {
2538
- parts.push(`${entry.outputLines} L`);
2539
- }
2540
- if (entry.outputBytes && entry.outputBytes > 0) {
2541
- parts.push(fmtBytes(entry.outputBytes));
2542
- }
2543
- if (entry.outputTokens && entry.outputTokens > 0) {
2544
- parts.push(`\u2248${fmtTok2(entry.outputTokens)} tok`);
2545
- }
2546
- return parts.join(" \xB7 ");
2547
- })();
2548
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2549
- /* @__PURE__ */ jsxs(Text, { children: [
2550
- /* @__PURE__ */ jsx(Text, { color: entry.ok ? theme.success : theme.error, children: entry.ok ? "\u25CF" : "\u2717" }),
2551
- " ",
2552
- /* @__PURE__ */ jsx(Text, { bold: true, color: theme.tool, children: entry.name }),
2553
- argSummary ? /* @__PURE__ */ jsxs(Fragment, { children: [
2554
- /* @__PURE__ */ jsx(Text, { children: " " }),
2555
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: argSummary })
2556
- ] }) : null,
2557
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \xB7 ${fmtDuration(entry.durationMs)}` }),
2558
- sizeChip ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \xB7 ${sizeChip}` }) : null
2559
- ] }),
2560
- outLines.map((line, i) => (
2561
- // biome-ignore lint/suspicious/noArrayIndexKey: tool output lines are static, index is stable
2562
- /* @__PURE__ */ jsxs(Text, { children: [
2563
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: i === outLines.length - 1 && !diff ? " \u2514\u2500 " : " \u251C\u2500 " }),
2564
- /* @__PURE__ */ jsx(
2565
- Text,
2566
- {
2567
- color: !entry.ok || line.startsWith("!") ? "red" : void 0,
2568
- dimColor: entry.ok && !line.startsWith("!"),
2569
- children: line
2570
- }
2571
- )
2572
- ] }, i)
2573
- )),
2574
- diff ? /* @__PURE__ */ jsx(DiffBlock, { rows: diff.rows, hidden: diff.hidden }) : null
2575
- ] });
2277
+ case "grep":
2278
+ case "search":
2279
+ case "replace": {
2280
+ const pat = stringOf(obj["pattern"]) ?? stringOf(obj["query"]);
2281
+ const scope = stringOf(obj["path"]) ?? stringOf(obj["glob"]);
2282
+ const head = pat ? `"${truncMid(pat, 36)}"` : "";
2283
+ const tail = scope ? ` in ${shortenPath(scope, 28)}` : "";
2284
+ return `${head}${tail}` || (stringOf(obj["command"]) ?? "");
2576
2285
  }
2577
- case "info":
2578
- return /* @__PURE__ */ jsx(Text, { dimColor: true, children: entry.text });
2579
- case "warn":
2580
- return /* @__PURE__ */ jsx(
2581
- Box,
2582
- {
2583
- borderStyle: "single",
2584
- borderTop: false,
2585
- borderRight: false,
2586
- borderBottom: false,
2587
- borderColor: theme.warn,
2588
- paddingLeft: 1,
2589
- children: /* @__PURE__ */ jsx(Text, { color: theme.warn, children: entry.text })
2590
- }
2591
- );
2592
- case "error":
2593
- return /* @__PURE__ */ jsx(
2594
- Box,
2595
- {
2596
- borderStyle: "single",
2597
- borderTop: false,
2598
- borderRight: false,
2599
- borderBottom: false,
2600
- borderColor: theme.error,
2601
- paddingLeft: 1,
2602
- children: /* @__PURE__ */ jsx(Text, { color: theme.error, children: entry.text })
2603
- }
2604
- );
2605
- case "turn-summary":
2606
- return /* @__PURE__ */ jsx(Text, { dimColor: true, children: entry.text });
2607
- case "brain": {
2608
- const statusStyle = brainStatusStyle(entry.status);
2609
- const riskColor2 = brainRiskColor(entry.risk);
2610
- return /* @__PURE__ */ jsxs(
2611
- Box,
2612
- {
2613
- flexDirection: "column",
2614
- marginY: 1,
2615
- borderStyle: "single",
2616
- borderTop: false,
2617
- borderRight: false,
2618
- borderBottom: false,
2619
- borderColor: "magenta",
2620
- paddingLeft: 1,
2621
- children: [
2622
- /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
2623
- /* @__PURE__ */ jsx(Text, { bold: true, color: "magenta", children: "BRAIN" }),
2624
- /* @__PURE__ */ jsx(Text, { color: statusStyle.color, children: statusStyle.icon }),
2625
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: entry.source }),
2626
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
2627
- /* @__PURE__ */ jsx(Text, { color: riskColor2, children: entry.risk })
2628
- ] }),
2629
- /* @__PURE__ */ jsx(Text, { color: "white", children: entry.question }),
2630
- entry.decision ? /* @__PURE__ */ jsxs(Text, { children: [
2631
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Decision: " }),
2632
- /* @__PURE__ */ jsx(Text, { color: statusStyle.color, children: entry.decision })
2633
- ] }) : null,
2634
- entry.rationale ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: entry.rationale }) : null
2635
- ]
2636
- }
2637
- );
2638
- }
2639
- case "confirm":
2640
- return /* @__PURE__ */ jsxs(
2641
- Box,
2642
- {
2643
- flexDirection: "column",
2644
- borderStyle: "round",
2645
- borderColor: "yellow",
2646
- paddingX: 1,
2647
- marginY: 1,
2648
- children: [
2649
- /* @__PURE__ */ jsxs(Text, { bold: true, color: "yellow", children: [
2650
- "\u26A0 Confirm: ",
2651
- entry.toolName
2652
- ] }),
2653
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Waiting for y / n / a / d..." })
2654
- ]
2655
- }
2656
- );
2657
- case "banner":
2658
- return /* @__PURE__ */ jsx(Banner, { entry });
2659
- case "subagent": {
2660
- const lines = entry.text.split("\n");
2661
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2662
- /* @__PURE__ */ jsxs(Text, { children: [
2663
- /* @__PURE__ */ jsx(Text, { color: entry.agentColor, bold: true, children: `[${entry.agentLabel}]` }),
2664
- /* @__PURE__ */ jsx(Text, { children: " " }),
2665
- /* @__PURE__ */ jsx(Text, { color: entry.agentColor, children: entry.icon }),
2666
- /* @__PURE__ */ jsx(Text, { children: " " }),
2667
- /* @__PURE__ */ jsx(Text, { children: lines[0] ?? "" }),
2668
- entry.detail ? /* @__PURE__ */ jsxs(Fragment, { children: [
2669
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \xB7 " }),
2670
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: entry.detail })
2671
- ] }) : null
2672
- ] }),
2673
- lines.slice(1).map((line, i) => (
2674
- // biome-ignore lint/suspicious/noArrayIndexKey: stable line index
2675
- /* @__PURE__ */ jsxs(Text, { children: [
2676
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: " " }),
2677
- /* @__PURE__ */ jsx(Text, { children: line })
2678
- ] }, i)
2679
- ))
2680
- ] });
2681
- }
2682
- }
2683
- });
2684
- function Banner({
2685
- entry
2686
- }) {
2687
- const cwdShort = shortenPath(entry.cwd, 48);
2688
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", paddingX: 2, paddingY: 0, children: [
2689
- /* @__PURE__ */ jsxs(Text, { children: [
2690
- /* @__PURE__ */ jsx(Text, { color: "magenta", bold: true, children: " \u259F\u259B " }),
2691
- /* @__PURE__ */ jsx(Text, { color: "magenta", bold: true, children: "WrongStack" }),
2692
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: " v" }),
2693
- /* @__PURE__ */ jsx(Text, { children: entry.version })
2694
- ] }),
2695
- /* @__PURE__ */ jsx(Text, { dimColor: true, italic: true, children: " Built on the wrong stack. Shipped anyway." }),
2696
- /* @__PURE__ */ jsxs(Text, { children: [
2697
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: " provider " }),
2698
- /* @__PURE__ */ jsxs(Text, { children: [
2699
- entry.provider,
2700
- "/",
2701
- entry.model
2702
- ] })
2703
- ] }),
2704
- entry.family ? /* @__PURE__ */ jsxs(Text, { children: [
2705
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: " family " }),
2706
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: entry.family })
2707
- ] }) : null,
2708
- entry.keyTail ? /* @__PURE__ */ jsxs(Text, { children: [
2709
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: " key " }),
2710
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u25CF\u25CF\u25CF\u2026" }),
2711
- /* @__PURE__ */ jsx(Text, { children: entry.keyTail })
2712
- ] }) : null,
2713
- /* @__PURE__ */ jsxs(Text, { children: [
2714
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: " cwd " }),
2715
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: cwdShort })
2716
- ] }),
2717
- /* @__PURE__ */ jsxs(Text, { children: [
2718
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: " hints " }),
2719
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "/help \xB7 /init \xB7 /memory \xB7 /queue \xB7 /exit" })
2720
- ] })
2721
- ] });
2722
- }
2723
- function shortenPath(p, max) {
2724
- if (p.length <= max) return p;
2725
- return `\u2026${p.slice(p.length - (max - 1))}`;
2726
- }
2727
- function fmtTok2(n) {
2728
- if (!Number.isFinite(n) || n <= 0) return "0";
2729
- if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
2730
- if (n >= 1e3) return `${(n / 1e3).toFixed(n >= 1e4 ? 0 : 1)}k`;
2731
- return String(n);
2732
- }
2733
- function fmtDuration(ms) {
2734
- if (ms < 1e3) return `${ms}ms`;
2735
- if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
2736
- const totalSec = Math.floor(ms / 1e3);
2737
- return `${Math.floor(totalSec / 60)}m${totalSec % 60}s`;
2738
- }
2739
- var ARG_BUDGET = 60;
2740
- var OUT_BUDGET = 80;
2741
- function formatToolArgs(toolName, input) {
2742
- if (!input || typeof input !== "object") return "";
2743
- const obj = input;
2744
- switch (toolName) {
2745
- case "read":
2746
- case "write":
2747
- case "edit":
2748
- case "patch":
2749
- case "document":
2750
- case "list_dir":
2751
- case "ls":
2752
- case "tree": {
2753
- const p = stringOf(obj["path"]) ?? stringOf(obj["file"]);
2754
- return p ? shortenPath(p, ARG_BUDGET) : "";
2755
- }
2756
- case "grep":
2757
- case "search":
2758
- case "replace": {
2759
- const pat = stringOf(obj["pattern"]) ?? stringOf(obj["query"]);
2760
- const scope = stringOf(obj["path"]) ?? stringOf(obj["glob"]);
2761
- const head = pat ? `"${truncMid(pat, 36)}"` : "";
2762
- const tail = scope ? ` in ${shortenPath(scope, 28)}` : "";
2763
- return `${head}${tail}` || (stringOf(obj["command"]) ?? "");
2764
- }
2765
- case "glob": {
2766
- const pat = stringOf(obj["pattern"]) ?? stringOf(obj["glob"]);
2767
- return pat ? `"${truncMid(pat, ARG_BUDGET - 2)}"` : "";
2286
+ case "glob": {
2287
+ const pat = stringOf(obj["pattern"]) ?? stringOf(obj["glob"]);
2288
+ return pat ? `"${truncMid(pat, ARG_BUDGET - 2)}"` : "";
2768
2289
  }
2769
2290
  case "bash":
2770
2291
  case "shell":
@@ -2847,77 +2368,104 @@ function formatToolArgs(toolName, input) {
2847
2368
  return "";
2848
2369
  }
2849
2370
  }
2371
+ var OUT_BUDGET = 80;
2372
+ var GENERIC_BUDGET = 240;
2373
+ function summarizeJsonObject(obj) {
2374
+ const keys = Object.keys(obj);
2375
+ if (keys.length === 0) return null;
2376
+ const priority = [
2377
+ "ok",
2378
+ "status",
2379
+ "timedOut",
2380
+ "stopReason",
2381
+ "reason",
2382
+ "error",
2383
+ "message",
2384
+ "result",
2385
+ "summary",
2386
+ "iterations",
2387
+ "toolCalls",
2388
+ "durationMs",
2389
+ "subagentId",
2390
+ "taskId"
2391
+ ];
2392
+ const ordered = [
2393
+ ...priority.filter((k) => keys.includes(k)),
2394
+ ...keys.filter((k) => !priority.includes(k))
2395
+ ];
2396
+ const parts = [];
2397
+ let used = 0;
2398
+ for (const key of ordered) {
2399
+ const v = obj[key];
2400
+ if (v === void 0 || v === null) continue;
2401
+ 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}`;
2402
+ if (used + rendered.length > GENERIC_BUDGET) {
2403
+ parts.push("\u2026");
2404
+ break;
2405
+ }
2406
+ parts.push(rendered);
2407
+ used += rendered.length + 3;
2408
+ }
2409
+ return parts.length > 0 ? parts.join(" \xB7 ") : null;
2410
+ }
2850
2411
  function formatToolOutput(toolName, output, ok, _outputBytes, outputLines) {
2851
2412
  if (!output) return ok ? [] : ["failed"];
2852
2413
  const text = output.trim();
2853
2414
  if (!text) return ok ? [] : ["failed"];
2854
2415
  const json = tryParseJson(text);
2855
- if (toolName === "write") {
2856
- if (json && typeof json === "object") {
2857
- const o = json;
2858
- const bytes = numOf(o["bytes_written"]) ?? numOf(o["bytes"]);
2859
- const created = o["created"] === true;
2860
- const tag = created ? "created" : "updated";
2861
- if (bytes !== void 0) return [`${tag} \xB7 ${fmtBytes(bytes)}`];
2862
- return [tag];
2863
- }
2416
+ if (toolName === "write" && json && typeof json === "object") {
2417
+ const o = json;
2418
+ const bytes = numOf(o["bytes_written"]) ?? numOf(o["bytes"]);
2419
+ const created = o["created"] === true;
2420
+ const tag = created ? "created" : "updated";
2421
+ return bytes !== void 0 ? [`${tag} \xB7 ${fmtBytes(bytes)}`] : [tag];
2864
2422
  }
2865
- if (toolName === "edit") {
2866
- if (json && typeof json === "object") {
2867
- const o = json;
2868
- const reps = numOf(o["replacements"]);
2869
- if (reps !== void 0) return [`${reps} replacement${reps === 1 ? "" : "s"}`];
2870
- }
2423
+ if (toolName === "edit" && json && typeof json === "object") {
2424
+ const o = json;
2425
+ const reps = numOf(o["replacements"]);
2426
+ if (reps !== void 0) return [`${reps} replacement${reps === 1 ? "" : "s"}`];
2871
2427
  }
2872
- if (toolName === "patch") {
2873
- if (json && typeof json === "object") {
2874
- const o = json;
2875
- const applied = numOf(o["applied"]);
2876
- const rejected = numOf(o["rejected"]);
2877
- const files = Array.isArray(o["files"]) ? o["files"] : void 0;
2878
- const lines = [];
2879
- if (applied !== void 0 || rejected !== void 0) {
2880
- const parts = [];
2881
- if (applied !== void 0) parts.push(`${applied} applied`);
2882
- if (rejected !== void 0 && rejected > 0) parts.push(`${rejected} rejected`);
2883
- lines.push(parts.join(" \xB7 "));
2884
- }
2885
- if (files && files.length > 0) {
2886
- const first = stringOf(files[0]) ?? "";
2887
- const more = files.length > 1 ? ` (+${files.length - 1})` : "";
2888
- lines.push(`${shortenPath(first, 60)}${more}`);
2889
- }
2890
- if (lines.length > 0) return lines;
2428
+ if (toolName === "patch" && json && typeof json === "object") {
2429
+ const o = json;
2430
+ const applied = numOf(o["applied"]);
2431
+ const rejected = numOf(o["rejected"]);
2432
+ const files = Array.isArray(o["files"]) ? o["files"] : void 0;
2433
+ const lines = [];
2434
+ if (applied !== void 0 || rejected !== void 0) {
2435
+ const parts = [];
2436
+ if (applied !== void 0) parts.push(`${applied} applied`);
2437
+ if (rejected !== void 0 && rejected > 0) parts.push(`${rejected} rejected`);
2438
+ lines.push(parts.join(" \xB7 "));
2891
2439
  }
2892
- }
2893
- if (toolName === "replace") {
2894
- if (json && typeof json === "object") {
2895
- const o = json;
2896
- const files = numOf(o["files_modified"]);
2897
- const reps = numOf(o["total_replacements"]);
2898
- if (files !== void 0 && reps !== void 0) {
2899
- return [
2900
- `${reps} replacement${reps === 1 ? "" : "s"} in ${files} file${files === 1 ? "" : "s"}`
2901
- ];
2902
- }
2440
+ if (files && files.length > 0) {
2441
+ const first = stringOf(files[0]) ?? "";
2442
+ const more = files.length > 1 ? ` (+${files.length - 1})` : "";
2443
+ lines.push(`${shortenPath(first, 60)}${more}`);
2903
2444
  }
2445
+ if (lines.length > 0) return lines;
2904
2446
  }
2905
- if (toolName === "diff") {
2906
- if (json && typeof json === "object") {
2907
- const o = json;
2908
- const files = Array.isArray(o["files"]) ? o["files"] : void 0;
2909
- const truncated = o["truncated"] === true;
2910
- const mode = stringOf(o["mode"]);
2911
- const diff = stringOf(o["diff"]);
2912
- if (!diff) return [files && files.length === 0 ? "no changes" : "empty diff"];
2913
- const head = [];
2914
- if (mode) head.push(mode);
2915
- if (files && files.length > 0)
2916
- head.push(`${files.length} file${files.length === 1 ? "" : "s"}`);
2917
- if (truncated) head.push("truncated");
2918
- return head.length > 0 ? [head.join(" \xB7 ")] : [];
2447
+ if (toolName === "replace" && json && typeof json === "object") {
2448
+ const o = json;
2449
+ const files = numOf(o["files_modified"]);
2450
+ const reps = numOf(o["total_replacements"]);
2451
+ if (files !== void 0 && reps !== void 0) {
2452
+ return [`${reps} replacement${reps === 1 ? "" : "s"} in ${files} file${files === 1 ? "" : "s"}`];
2919
2453
  }
2920
2454
  }
2455
+ if (toolName === "diff" && json && typeof json === "object") {
2456
+ const o = json;
2457
+ const diffFiles = Array.isArray(o["files"]) ? o["files"] : void 0;
2458
+ const truncated = o["truncated"] === true;
2459
+ const mode = stringOf(o["mode"]);
2460
+ const diff = stringOf(o["diff"]);
2461
+ if (!diff) return [diffFiles && diffFiles.length === 0 ? "no changes" : "empty diff"];
2462
+ const head = [];
2463
+ if (mode) head.push(mode);
2464
+ if (diffFiles && diffFiles.length > 0)
2465
+ head.push(`${diffFiles.length} file${diffFiles.length === 1 ? "" : "s"}`);
2466
+ if (truncated) head.push("truncated");
2467
+ return head.length > 0 ? [head.join(" \xB7 ")] : [];
2468
+ }
2921
2469
  if (toolName === "read") {
2922
2470
  if (outputLines !== void 0) return [];
2923
2471
  if (json && typeof json === "object") {
@@ -2927,9 +2475,7 @@ function formatToolOutput(toolName, output, ok, _outputBytes, outputLines) {
2927
2475
  }
2928
2476
  const range = scanNumberedRange(text);
2929
2477
  if (range.count > 0 && range.first !== void 0 && range.last !== void 0) {
2930
- if (range.first === range.last) {
2931
- return [`L${range.first} \xB7 ${fmtBytes(text.length)}`];
2932
- }
2478
+ if (range.first === range.last) return [`L${range.first} \xB7 ${fmtBytes(text.length)}`];
2933
2479
  const contiguous = range.count === range.last - range.first + 1;
2934
2480
  const head = `L${range.first}\u2013${range.last}`;
2935
2481
  const tail = contiguous ? `${range.count} line${range.count === 1 ? "" : "s"}` : `${range.count} lines (gaps)`;
@@ -2978,9 +2524,7 @@ function formatToolOutput(toolName, output, ok, _outputBytes, outputLines) {
2978
2524
  if (lines.length > 0) return lines;
2979
2525
  }
2980
2526
  }
2981
- if (toolName === "todo") {
2982
- return ok ? [] : [text.split("\n")[0] ?? ""];
2983
- }
2527
+ if (toolName === "todo") return ok ? [] : [text.split("\n")[0] ?? ""];
2984
2528
  if (toolName === "fetch" || toolName === "webfetch" || toolName === "web_fetch") {
2985
2529
  if (json && typeof json === "object") {
2986
2530
  const o = json;
@@ -3000,200 +2544,174 @@ function formatToolOutput(toolName, output, ok, _outputBytes, outputLines) {
3000
2544
  if (lines.length > 0) return lines;
3001
2545
  }
3002
2546
  }
3003
- if (toolName === "git") {
3004
- if (json && typeof json === "object") {
3005
- const o = json;
3006
- const exit = numOf(o["exitCode"]) ?? numOf(o["exit_code"]);
3007
- const stdout = stringOf(o["stdout"]) ?? "";
3008
- const stderr = stringOf(o["stderr"]) ?? "";
3009
- const head = [];
3010
- if (exit !== void 0) head.push(`exit ${exit}`);
3011
- const stdoutLines = countLines(stdout);
3012
- const stderrLines = countLines(stderr);
3013
- const lparts = [];
3014
- if (stdoutLines > 0) lparts.push(`${stdoutLines} out`);
3015
- if (stderrLines > 0) lparts.push(`${stderrLines} err`);
3016
- if (lparts.length > 0) head.push(lparts.join(" \xB7 "));
3017
- const lines = [];
3018
- if (head.length > 0) lines.push(head.join(" \xB7 "));
3019
- const preview = firstNonEmpty(stdout) ?? firstNonEmpty(stderr);
3020
- if (preview) lines.push(`"${truncMid(preview, 70)}"`);
3021
- if (lines.length > 0) return lines;
3022
- }
2547
+ if (toolName === "git" && json && typeof json === "object") {
2548
+ const o = json;
2549
+ const exit = numOf(o["exitCode"]) ?? numOf(o["exit_code"]);
2550
+ const stdout = stringOf(o["stdout"]) ?? "";
2551
+ const stderr = stringOf(o["stderr"]) ?? "";
2552
+ const head = [];
2553
+ if (exit !== void 0) head.push(`exit ${exit}`);
2554
+ const stdoutLines = countLines(stdout);
2555
+ const stderrLines = countLines(stderr);
2556
+ const lparts = [];
2557
+ if (stdoutLines > 0) lparts.push(`${stdoutLines} out`);
2558
+ if (stderrLines > 0) lparts.push(`${stderrLines} err`);
2559
+ if (lparts.length > 0) head.push(lparts.join(" \xB7 "));
2560
+ const lines = [];
2561
+ if (head.length > 0) lines.push(head.join(" \xB7 "));
2562
+ const preview = firstNonEmpty(stdout) ?? firstNonEmpty(stderr);
2563
+ if (preview) lines.push(`"${truncMid(preview, 70)}"`);
2564
+ if (lines.length > 0) return lines;
3023
2565
  }
3024
- if (toolName === "lint") {
3025
- if (json && typeof json === "object") {
3026
- const o = json;
3027
- const linter = stringOf(o["linter"]);
3028
- const files = numOf(o["files_checked"]);
3029
- const errors = numOf(o["errors"]) ?? 0;
3030
- const warnings = numOf(o["warnings"]) ?? 0;
3031
- const fix = o["fix_applied"] === true;
3032
- const head = [];
3033
- if (linter && linter !== "none") head.push(linter);
3034
- head.push(`${errors} error${errors === 1 ? "" : "s"}`);
3035
- head.push(`${warnings} warning${warnings === 1 ? "" : "s"}`);
3036
- if (files !== void 0) head.push(`${files} file${files === 1 ? "" : "s"}`);
3037
- if (fix) head.push("fixed");
3038
- return [head.join(" \xB7 ")];
3039
- }
2566
+ if (toolName === "lint" && json && typeof json === "object") {
2567
+ const o = json;
2568
+ const linter = stringOf(o["linter"]);
2569
+ const files = numOf(o["files_checked"]);
2570
+ const errors = numOf(o["errors"]) ?? 0;
2571
+ const warnings = numOf(o["warnings"]) ?? 0;
2572
+ const fix = o["fix_applied"] === true;
2573
+ const head = [];
2574
+ if (linter && linter !== "none") head.push(linter);
2575
+ head.push(`${errors} error${errors === 1 ? "" : "s"}`);
2576
+ head.push(`${warnings} warning${warnings === 1 ? "" : "s"}`);
2577
+ if (files !== void 0) head.push(`${files} file${files === 1 ? "" : "s"}`);
2578
+ if (fix) head.push("fixed");
2579
+ return [head.join(" \xB7 ")];
3040
2580
  }
3041
- if (toolName === "format") {
3042
- if (json && typeof json === "object") {
3043
- const o = json;
3044
- const fixer = stringOf(o["fixer"]);
3045
- const checked = numOf(o["files_checked"]);
3046
- const changed = numOf(o["files_changed"]);
3047
- const head = [];
3048
- if (fixer && fixer !== "none") head.push(fixer);
3049
- if (changed !== void 0 && checked !== void 0) {
3050
- head.push(`${changed}/${checked} changed`);
3051
- } else if (changed !== void 0) {
3052
- head.push(`${changed} changed`);
3053
- }
3054
- return head.length > 0 ? [head.join(" \xB7 ")] : [];
3055
- }
2581
+ if (toolName === "format" && json && typeof json === "object") {
2582
+ const o = json;
2583
+ const fixer = stringOf(o["fixer"]);
2584
+ const checked = numOf(o["files_checked"]);
2585
+ const changed = numOf(o["files_changed"]);
2586
+ const head = [];
2587
+ if (fixer && fixer !== "none") head.push(fixer);
2588
+ if (changed !== void 0 && checked !== void 0) {
2589
+ head.push(`${changed}/${checked} changed`);
2590
+ } else if (changed !== void 0) {
2591
+ head.push(`${changed} changed`);
2592
+ }
2593
+ return head.length > 0 ? [head.join(" \xB7 ")] : [];
3056
2594
  }
3057
- if (toolName === "typecheck") {
3058
- if (json && typeof json === "object") {
3059
- const o = json;
3060
- const exit = numOf(o["exit_code"]) ?? numOf(o["exitCode"]);
3061
- const errors = numOf(o["errors"]);
3062
- const head = [];
3063
- if (errors !== void 0) head.push(`${errors} error${errors === 1 ? "" : "s"}`);
3064
- if (exit !== void 0) head.push(`exit ${exit}`);
3065
- const stdout = stringOf(o["output"]) ?? stringOf(o["stdout"]) ?? "";
3066
- const lines = [];
3067
- if (head.length > 0) lines.push(head.join(" \xB7 "));
3068
- const preview = firstNonEmpty(stdout);
3069
- if (preview && (!errors || errors > 0)) lines.push(`"${truncMid(preview, 70)}"`);
3070
- if (lines.length > 0) return lines;
3071
- }
2595
+ if (toolName === "typecheck" && json && typeof json === "object") {
2596
+ const o = json;
2597
+ const exit = numOf(o["exit_code"]) ?? numOf(o["exitCode"]);
2598
+ const errors = numOf(o["errors"]);
2599
+ const head = [];
2600
+ if (errors !== void 0) head.push(`${errors} error${errors === 1 ? "" : "s"}`);
2601
+ if (exit !== void 0) head.push(`exit ${exit}`);
2602
+ const stdout = stringOf(o["output"]) ?? stringOf(o["stdout"]) ?? "";
2603
+ const lines = [];
2604
+ if (head.length > 0) lines.push(head.join(" \xB7 "));
2605
+ const preview = firstNonEmpty(stdout);
2606
+ if (preview && (!errors || errors > 0)) lines.push(`"${truncMid(preview, 70)}"`);
2607
+ if (lines.length > 0) return lines;
3072
2608
  }
3073
- if (toolName === "test") {
3074
- if (json && typeof json === "object") {
3075
- const o = json;
3076
- const runner = stringOf(o["runner"]);
3077
- const total = numOf(o["tests_run"]) ?? 0;
3078
- const passed = numOf(o["passed"]) ?? 0;
3079
- const failed = numOf(o["failed"]) ?? 0;
3080
- const duration = numOf(o["duration_ms"]);
3081
- const head = [];
3082
- if (runner && runner !== "none") head.push(runner);
3083
- head.push(`${passed}/${total} passed`);
3084
- if (failed > 0) head.push(`${failed} failed`);
3085
- if (duration !== void 0) head.push(fmtDuration(duration));
3086
- return [head.join(" \xB7 ")];
3087
- }
2609
+ if (toolName === "test" && json && typeof json === "object") {
2610
+ const o = json;
2611
+ const runner = stringOf(o["runner"]);
2612
+ const total = numOf(o["tests_run"]) ?? 0;
2613
+ const passed = numOf(o["passed"]) ?? 0;
2614
+ const failed = numOf(o["failed"]) ?? 0;
2615
+ const duration = numOf(o["duration_ms"]);
2616
+ const head = [];
2617
+ if (runner && runner !== "none") head.push(runner);
2618
+ head.push(`${passed}/${total} passed`);
2619
+ if (failed > 0) head.push(`${failed} failed`);
2620
+ if (duration !== void 0) head.push(fmtDuration(duration));
2621
+ return [head.join(" \xB7 ")];
3088
2622
  }
3089
- if (toolName === "audit") {
3090
- if (json && typeof json === "object") {
3091
- const o = json;
3092
- const total = numOf(o["total"]) ?? 0;
3093
- const summary = stringOf(o["summary"]);
3094
- if (total === 0) return ["no vulnerabilities"];
3095
- const head = `${total} vulnerabilit${total === 1 ? "y" : "ies"}`;
3096
- return summary && summary.toLowerCase() !== head.toLowerCase() ? [head, truncMid(summary, OUT_BUDGET)] : [head];
3097
- }
2623
+ if (toolName === "audit" && json && typeof json === "object") {
2624
+ const o = json;
2625
+ const total = numOf(o["total"]) ?? 0;
2626
+ const summary = stringOf(o["summary"]);
2627
+ if (total === 0) return ["no vulnerabilities"];
2628
+ const head = `${total} vulnerabilit${total === 1 ? "y" : "ies"}`;
2629
+ return summary && summary.toLowerCase() !== head.toLowerCase() ? [head, truncMid(summary, OUT_BUDGET)] : [head];
3098
2630
  }
3099
- if (toolName === "outdated") {
3100
- if (json && typeof json === "object") {
3101
- const o = json;
3102
- const total = numOf(o["total"]) ?? 0;
3103
- const pkgs = Array.isArray(o["packages"]) ? o["packages"] : void 0;
3104
- if (total === 0) return ["all up to date"];
3105
- const lines = [`${total} outdated`];
3106
- if (pkgs && pkgs.length > 0) {
3107
- const first = pkgs[0];
3108
- if (first && typeof first === "object") {
3109
- const p = first;
3110
- const name = stringOf(p["name"]) ?? stringOf(p["package"]);
3111
- const cur = stringOf(p["current"]);
3112
- const wanted = stringOf(p["wanted"]) ?? stringOf(p["latest"]);
3113
- if (name && cur && wanted) lines.push(`${name}: ${cur} \u2192 ${wanted}`);
3114
- else if (name) lines.push(name);
3115
- }
3116
- }
3117
- return lines;
3118
- }
2631
+ if (toolName === "outdated" && json && typeof json === "object") {
2632
+ const o = json;
2633
+ const total = numOf(o["total"]) ?? 0;
2634
+ const pkgs = Array.isArray(o["packages"]) ? o["packages"] : void 0;
2635
+ if (total === 0) return ["all up to date"];
2636
+ const lines = [`${total} outdated`];
2637
+ if (pkgs && pkgs.length > 0) {
2638
+ const first = pkgs[0];
2639
+ if (first && typeof first === "object") {
2640
+ const p = first;
2641
+ const name = stringOf(p["name"]) ?? stringOf(p["package"]);
2642
+ const cur = stringOf(p["current"]);
2643
+ const wanted = stringOf(p["wanted"]) ?? stringOf(p["latest"]);
2644
+ if (name && cur && wanted) lines.push(`${name}: ${cur} \u2192 ${wanted}`);
2645
+ else if (name) lines.push(name);
2646
+ }
2647
+ }
2648
+ return lines;
3119
2649
  }
3120
- if (toolName === "tree") {
3121
- if (json && typeof json === "object") {
3122
- const o = json;
3123
- const files = numOf(o["total_files"]);
3124
- const dirs = numOf(o["total_dirs"]);
3125
- const truncated = o["truncated"] === true;
3126
- const parts = [];
3127
- if (files !== void 0) parts.push(`${files} file${files === 1 ? "" : "s"}`);
3128
- if (dirs !== void 0) parts.push(`${dirs} dir${dirs === 1 ? "" : "s"}`);
3129
- if (truncated) parts.push("truncated");
3130
- return parts.length > 0 ? [parts.join(" \xB7 ")] : [];
3131
- }
2650
+ if (toolName === "tree" && json && typeof json === "object") {
2651
+ const o = json;
2652
+ const files = numOf(o["total_files"]);
2653
+ const dirs = numOf(o["total_dirs"]);
2654
+ const truncated = o["truncated"] === true;
2655
+ const parts = [];
2656
+ if (files !== void 0) parts.push(`${files} file${files === 1 ? "" : "s"}`);
2657
+ if (dirs !== void 0) parts.push(`${dirs} dir${dirs === 1 ? "" : "s"}`);
2658
+ if (truncated) parts.push("truncated");
2659
+ return parts.length > 0 ? [parts.join(" \xB7 ")] : [];
3132
2660
  }
3133
- if (toolName === "json") {
3134
- if (json && typeof json === "object") {
3135
- const o = json;
3136
- const err = stringOf(o["error"]);
3137
- if (err) return [truncMid(err, OUT_BUDGET)];
3138
- const type = stringOf(o["type"]);
3139
- const keys = Array.isArray(o["keys"]) ? o["keys"] : void 0;
3140
- const parts = [];
3141
- if (type) parts.push(type);
3142
- if (keys) parts.push(`${keys.length} key${keys.length === 1 ? "" : "s"}`);
3143
- return parts.length > 0 ? [parts.join(" \xB7 ")] : [];
3144
- }
2661
+ if (toolName === "json" && json && typeof json === "object") {
2662
+ const o = json;
2663
+ const err = stringOf(o["error"]);
2664
+ if (err) return [truncMid(err, OUT_BUDGET)];
2665
+ const type = stringOf(o["type"]);
2666
+ const keys = Array.isArray(o["keys"]) ? o["keys"] : void 0;
2667
+ const parts = [];
2668
+ if (type) parts.push(type);
2669
+ if (keys) parts.push(`${keys.length} key${keys.length === 1 ? "" : "s"}`);
2670
+ return parts.length > 0 ? [parts.join(" \xB7 ")] : [];
3145
2671
  }
3146
- if (toolName === "install") {
3147
- if (json && typeof json === "object") {
3148
- const o = json;
3149
- const exit = numOf(o["exit_code"]) ?? numOf(o["exitCode"]);
3150
- const added = numOf(o["added"]);
3151
- const removed = numOf(o["removed"]);
3152
- const head = [];
3153
- if (exit !== void 0) head.push(`exit ${exit}`);
3154
- if (added !== void 0) head.push(`+${added}`);
3155
- if (removed !== void 0) head.push(`-${removed}`);
3156
- const stdout = stringOf(o["stdout"]) ?? stringOf(o["output"]) ?? "";
3157
- const lines = [];
3158
- if (head.length > 0) lines.push(head.join(" \xB7 "));
3159
- const preview = firstNonEmpty(stdout);
3160
- if (preview) lines.push(`"${truncMid(preview, 70)}"`);
3161
- if (lines.length > 0) return lines;
3162
- }
2672
+ if (toolName === "install" && json && typeof json === "object") {
2673
+ const o = json;
2674
+ const exit = numOf(o["exit_code"]) ?? numOf(o["exitCode"]);
2675
+ const added = numOf(o["added"]);
2676
+ const removed = numOf(o["removed"]);
2677
+ const head = [];
2678
+ if (exit !== void 0) head.push(`exit ${exit}`);
2679
+ if (added !== void 0) head.push(`+${added}`);
2680
+ if (removed !== void 0) head.push(`-${removed}`);
2681
+ const stdout = stringOf(o["stdout"]) ?? stringOf(o["output"]) ?? "";
2682
+ const lines = [];
2683
+ if (head.length > 0) lines.push(head.join(" \xB7 "));
2684
+ const preview = firstNonEmpty(stdout);
2685
+ if (preview) lines.push(`"${truncMid(preview, 70)}"`);
2686
+ if (lines.length > 0) return lines;
3163
2687
  }
3164
- if (toolName === "scaffold") {
3165
- if (json && typeof json === "object") {
3166
- const o = json;
3167
- const created = Array.isArray(o["created"]) ? o["created"] : void 0;
3168
- const skipped = Array.isArray(o["skipped"]) ? o["skipped"] : void 0;
3169
- const parts = [];
3170
- if (created !== void 0) parts.push(`${created.length} created`);
3171
- if (skipped !== void 0 && skipped.length > 0) parts.push(`${skipped.length} skipped`);
3172
- if (parts.length > 0) return [parts.join(" \xB7 ")];
3173
- }
2688
+ if (toolName === "scaffold" && json && typeof json === "object") {
2689
+ const o = json;
2690
+ const created = Array.isArray(o["created"]) ? o["created"] : void 0;
2691
+ const skipped = Array.isArray(o["skipped"]) ? o["skipped"] : void 0;
2692
+ const parts = [];
2693
+ if (created !== void 0) parts.push(`${created.length} created`);
2694
+ if (skipped !== void 0 && skipped.length > 0) parts.push(`${skipped.length} skipped`);
2695
+ if (parts.length > 0) return [parts.join(" \xB7 ")];
3174
2696
  }
3175
2697
  if (toolName === "remember" || toolName === "forget" || toolName === "memory") {
3176
2698
  return ok ? [toolName === "forget" ? "removed" : "saved"] : [text.split("\n")[0] ?? ""];
3177
2699
  }
3178
- if (toolName === "mode") {
3179
- if (json && typeof json === "object") {
3180
- const o = json;
3181
- const mode = stringOf(o["mode"]) ?? stringOf(o["active"]) ?? stringOf(o["name"]);
3182
- if (mode) return [`mode: ${mode}`];
3183
- }
3184
- }
3185
- if (toolName === "search") {
3186
- if (json && typeof json === "object") {
3187
- const o = json;
3188
- const matches = Array.isArray(o["matches"]) ? o["matches"] : Array.isArray(o["results"]) ? o["results"] : void 0;
3189
- const count = numOf(o["count"]) ?? matches?.length;
3190
- if (count !== void 0) {
3191
- if (count === 0) return ["no results"];
3192
- const lines = [`${count} result${count === 1 ? "" : "s"}`];
3193
- const firstHit = matches && matches.length > 0 ? formatMatchHit(matches[0]) : void 0;
3194
- if (firstHit) lines.push(firstHit);
3195
- return lines;
3196
- }
2700
+ if (toolName === "mode" && json && typeof json === "object") {
2701
+ const o = json;
2702
+ const mode = stringOf(o["mode"]) ?? stringOf(o["active"]) ?? stringOf(o["name"]);
2703
+ if (mode) return [`mode: ${mode}`];
2704
+ }
2705
+ if (toolName === "search" && json && typeof json === "object") {
2706
+ const o = json;
2707
+ const matches = Array.isArray(o["matches"]) ? o["matches"] : Array.isArray(o["results"]) ? o["results"] : void 0;
2708
+ const count = numOf(o["count"]) ?? matches?.length;
2709
+ if (count !== void 0) {
2710
+ if (count === 0) return ["no results"];
2711
+ const lines = [`${count} result${count === 1 ? "" : "s"}`];
2712
+ const firstHit = matches && matches.length > 0 ? formatMatchHit(matches[0]) : void 0;
2713
+ if (firstHit) lines.push(firstHit);
2714
+ return lines;
3197
2715
  }
3198
2716
  }
3199
2717
  if (toolName === "logs") {
@@ -3210,86 +2728,127 @@ function formatToolOutput(toolName, output, ok, _outputBytes, outputLines) {
3210
2728
  const collapsed = text.replace(/\s+/g, " ").trim();
3211
2729
  return [truncMid(collapsed, GENERIC_BUDGET)];
3212
2730
  }
3213
- var GENERIC_BUDGET = 240;
3214
- function summarizeJsonObject(obj) {
3215
- const keys = Object.keys(obj);
3216
- if (keys.length === 0) return null;
3217
- const priority = [
3218
- "ok",
3219
- "status",
3220
- "timedOut",
3221
- "stopReason",
3222
- "reason",
3223
- "error",
3224
- "message",
3225
- "result",
3226
- "summary",
3227
- "iterations",
3228
- "toolCalls",
3229
- "durationMs",
3230
- "subagentId",
3231
- "taskId"
3232
- ];
3233
- const ordered = [
3234
- ...priority.filter((k) => keys.includes(k)),
3235
- ...keys.filter((k) => !priority.includes(k))
3236
- ];
3237
- const parts = [];
3238
- let used = 0;
3239
- for (const key of ordered) {
3240
- const v = obj[key];
3241
- if (v === void 0 || v === null) continue;
3242
- 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}`;
3243
- if (used + rendered.length > GENERIC_BUDGET) {
3244
- parts.push("\u2026");
3245
- break;
3246
- }
3247
- parts.push(rendered);
3248
- used += rendered.length + 3;
3249
- }
3250
- return parts.length > 0 ? parts.join(" \xB7 ") : null;
3251
- }
3252
- function firstNonEmpty(text) {
3253
- if (!text) return void 0;
3254
- const line = text.split("\n").find((l) => l.trim());
3255
- return line ? line.replace(/\s+/g, " ").trim() : void 0;
3256
- }
3257
- function formatMatchHit(hit) {
3258
- if (typeof hit === "string") return truncMid(hit, 70);
3259
- if (hit && typeof hit === "object") {
3260
- const o = hit;
3261
- const file = stringOf(o["file"]) ?? stringOf(o["path"]);
3262
- const line = numOf(o["line"]) ?? numOf(o["lineNumber"]);
3263
- const snippet2 = stringOf(o["text"]) ?? stringOf(o["match"]) ?? stringOf(o["preview"]);
3264
- if (file) {
3265
- const head = line !== void 0 ? `${shortenPath(file, 40)}:${line}` : shortenPath(file, 50);
3266
- return snippet2 ? `${head} ${truncMid(snippet2.replace(/\s+/g, " "), 40)}` : head;
3267
- }
3268
- if (snippet2) return truncMid(snippet2, 70);
2731
+ var MAX_STREAM_DISPLAY_CHARS = 480;
2732
+ var MAX_STREAM_LINES = 8;
2733
+ var ToolStreamBox = React5.memo(function ToolStreamBox2({
2734
+ name,
2735
+ text,
2736
+ startedAt,
2737
+ termWidth
2738
+ }) {
2739
+ const [tick, setTick] = useState(0);
2740
+ useEffect(() => {
2741
+ const t = setInterval(() => setTick((n) => n + 1), 500);
2742
+ return () => clearInterval(t);
2743
+ }, []);
2744
+ const elapsedMs = Date.now() - startedAt;
2745
+ const lines = text.split("\n");
2746
+ const totalLines = lines.length;
2747
+ const hidden = Math.max(0, totalLines - MAX_STREAM_LINES);
2748
+ const visible = hidden > 0 ? lines.slice(hidden) : lines;
2749
+ const contentWidth = Math.max(20, Math.min(termWidth - 4, 100));
2750
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 0, children: [
2751
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
2752
+ /* @__PURE__ */ jsx(Text, { color: theme.warn, children: "\u25C6 " }),
2753
+ /* @__PURE__ */ jsx(Text, { bold: true, color: theme.tool, children: name }),
2754
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u23F1 ${fmtDuration(elapsedMs)}` }),
2755
+ hidden > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` (${totalLines} lines, showing last ${MAX_STREAM_LINES})` }) : null
2756
+ ] }),
2757
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [
2758
+ hidden > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, italic: true, children: ` \u2026 ${hidden} more line${hidden === 1 ? "" : "s"} above` }) : null,
2759
+ visible.map((line, i) => {
2760
+ const trimmed = line.length > contentWidth ? `${line.slice(0, contentWidth - 1)}\u2026` : line;
2761
+ return /* @__PURE__ */ jsx(Text, { dimColor: true, children: trimmed || " " }, i);
2762
+ })
2763
+ ] })
2764
+ ] });
2765
+ });
2766
+ function tailForDisplay(text, maxChars) {
2767
+ if (text.length <= maxChars) return text;
2768
+ const cut = text.length - maxChars;
2769
+ const nl = text.indexOf("\n", cut);
2770
+ if (nl !== -1 && nl < cut + 80) {
2771
+ return `\u2026 ${text.slice(nl + 1)}`;
3269
2772
  }
3270
- return void 0;
2773
+ return `\u2026 ${text.slice(cut)}`;
3271
2774
  }
2775
+ var MAX_CODE_LINES = 80;
3272
2776
  var DIFF_MAX_LINES = 8;
3273
- function extractDiffPreview(toolName, output) {
3274
- if (!output) return void 0;
3275
- const text = output.trim();
3276
- if (!text) return void 0;
3277
- let diff;
3278
- if (toolName === "edit" || toolName === "diff") {
3279
- const parsed = tryParseJson(text);
3280
- if (parsed && typeof parsed === "object") {
3281
- diff = stringOf(parsed["diff"]);
2777
+ function CodeBlock({
2778
+ code,
2779
+ lang,
2780
+ contentWidth
2781
+ }) {
2782
+ let lines = code.replace(/\n+$/, "").split("\n");
2783
+ const hidden = Math.max(0, lines.length - MAX_CODE_LINES);
2784
+ if (hidden > 0) lines = lines.slice(0, MAX_CODE_LINES);
2785
+ const gutterW = String(lines.length).length;
2786
+ const maxW = Math.max(20, Math.min(contentWidth - 6 - gutterW - 1, 120));
2787
+ let carry = {};
2788
+ const rows = lines.map((raw) => {
2789
+ const display = raw.length > maxW ? `${raw.slice(0, maxW - 1)}\u2026` : raw;
2790
+ const r = highlightLine(display, lang, carry);
2791
+ carry = r.carry;
2792
+ return r.tokens;
2793
+ });
2794
+ return /* @__PURE__ */ jsxs(
2795
+ Box,
2796
+ {
2797
+ flexDirection: "column",
2798
+ marginLeft: 2,
2799
+ marginY: 0,
2800
+ borderStyle: "round",
2801
+ borderColor: theme.borderDefault,
2802
+ paddingX: 1,
2803
+ children: [
2804
+ lang !== "plain" ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: lang }) : null,
2805
+ rows.map((tokens, i) => (
2806
+ // biome-ignore lint/suspicious/noArrayIndexKey: code lines are positional
2807
+ /* @__PURE__ */ jsxs(Text, { children: [
2808
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: `${String(i + 1).padStart(gutterW, " ")} ` }),
2809
+ tokens.length === 0 ? " " : tokens.map((t, j) => (
2810
+ // biome-ignore lint/suspicious/noArrayIndexKey: token order is stable per line
2811
+ /* @__PURE__ */ jsx(Text, { color: t.color, dimColor: t.dim, bold: t.bold, children: t.text }, j)
2812
+ ))
2813
+ ] }, i)
2814
+ )),
2815
+ hidden > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, italic: true, children: `\u2026 +${hidden} more line${hidden === 1 ? "" : "s"}` }) : null
2816
+ ]
3282
2817
  }
3283
- } else if (toolName === "patch") {
3284
- const parsed = tryParseJson(text);
3285
- if (parsed && typeof parsed === "object") {
3286
- diff = stringOf(parsed["diff"]) ?? stringOf(parsed["stdout"]);
3287
- } else if (text.includes("@@") || text.startsWith("---")) {
3288
- diff = text;
2818
+ );
2819
+ }
2820
+ function DiffBlock({ rows, hidden }) {
2821
+ let gutterWidth = 1;
2822
+ for (const r of rows) {
2823
+ const n = r.kind === "del" ? r.oldLine : r.newLine;
2824
+ if (typeof n === "number") {
2825
+ const w = String(n).length;
2826
+ if (w > gutterWidth) gutterWidth = w;
3289
2827
  }
3290
2828
  }
3291
- if (!diff || !diff.trim() || diff.startsWith("(no-op")) return void 0;
3292
- return parseUnifiedDiff(diff, DIFF_MAX_LINES);
2829
+ const blank = " ".repeat(gutterWidth);
2830
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginLeft: 4, marginTop: 0, children: [
2831
+ rows.map((row, i) => {
2832
+ const key = i;
2833
+ if (row.kind === "hunk") {
2834
+ return /* @__PURE__ */ jsx(Text, { color: "cyan", dimColor: true, children: row.text }, key);
2835
+ }
2836
+ if (row.kind === "meta") {
2837
+ return /* @__PURE__ */ jsx(Text, { dimColor: true, children: `${blank} ${row.text}` }, key);
2838
+ }
2839
+ const lnNumber = row.kind === "del" ? row.oldLine : row.newLine;
2840
+ const lnText = typeof lnNumber === "number" ? String(lnNumber).padStart(gutterWidth, " ") : blank;
2841
+ if (row.kind === "ctx") {
2842
+ return /* @__PURE__ */ jsx(Text, { dimColor: true, children: `${lnText} ${row.text}` }, key);
2843
+ }
2844
+ const bg = row.kind === "add" ? theme.diffAddBg : theme.diffDelBg;
2845
+ return /* @__PURE__ */ jsxs(Text, { children: [
2846
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: `${lnText} ` }),
2847
+ /* @__PURE__ */ jsx(Text, { backgroundColor: bg, color: "black", children: row.text })
2848
+ ] }, key);
2849
+ }),
2850
+ hidden > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, italic: true, children: `${blank} \u2026 ${hidden} more line${hidden === 1 ? "" : "s"}` }) : null
2851
+ ] });
3293
2852
  }
3294
2853
  function parseUnifiedDiff(diff, maxLines) {
3295
2854
  const all = [];
@@ -3331,50 +2890,406 @@ function parseUnifiedDiff(diff, maxLines) {
3331
2890
  if (all.length <= maxLines) return { rows: all, hidden: 0 };
3332
2891
  return { rows: all.slice(0, maxLines), hidden: all.length - maxLines };
3333
2892
  }
3334
- function stringOf(v) {
3335
- return typeof v === "string" && v.length > 0 ? v : void 0;
3336
- }
3337
- function numOf(v) {
3338
- return typeof v === "number" && Number.isFinite(v) ? v : void 0;
3339
- }
3340
- function tryParseJson(s2) {
3341
- const t = s2.trimStart();
3342
- if (!t.startsWith("{") && !t.startsWith("[")) return void 0;
3343
- try {
3344
- return JSON.parse(s2);
3345
- } catch {
3346
- return void 0;
3347
- }
3348
- }
3349
- function scanNumberedRange(text) {
3350
- let first;
3351
- let last;
3352
- let count = 0;
3353
- for (const line of text.split("\n")) {
3354
- const m = line.match(/^\s*(\d+)→/);
3355
- if (m?.[1]) {
3356
- const n = Number.parseInt(m[1], 10);
3357
- if (Number.isFinite(n)) {
3358
- if (first === void 0) first = n;
3359
- last = n;
3360
- count++;
3361
- }
2893
+ function extractDiffPreview(toolName, output) {
2894
+ if (!output) return void 0;
2895
+ const text = output.trim();
2896
+ if (!text) return void 0;
2897
+ let diff;
2898
+ if (toolName === "edit" || toolName === "diff") {
2899
+ const parsed = tryParseJson(text);
2900
+ if (parsed && typeof parsed === "object") {
2901
+ diff = stringOf(parsed["diff"]);
2902
+ }
2903
+ } else if (toolName === "patch") {
2904
+ const parsed = tryParseJson(text);
2905
+ if (parsed && typeof parsed === "object") {
2906
+ diff = stringOf(parsed["diff"]) ?? stringOf(parsed["stdout"]);
2907
+ } else if (text.includes("@@") || text.startsWith("---")) {
2908
+ diff = text;
3362
2909
  }
3363
2910
  }
3364
- return { first, last, count };
3365
- }
3366
- function countLines(text) {
3367
- if (!text) return 0;
3368
- return text.replace(/\n$/, "").split("\n").length;
3369
- }
3370
- function fmtBytes(n) {
3371
- if (n < 1024) return `${n}B`;
3372
- if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)}KB`;
3373
- return `${(n / (1024 * 1024)).toFixed(1)}MB`;
2911
+ if (!diff || !diff.trim() || diff.startsWith("(no-op")) return void 0;
2912
+ return parseUnifiedDiff(diff, DIFF_MAX_LINES);
3374
2913
  }
3375
- function truncMid(s2, max) {
3376
- if (s2.length <= max) return s2;
3377
- return `${s2.slice(0, max - 1)}\u2026`;
2914
+ var MESSAGE_PANEL_CHROME_WIDTH = 2;
2915
+ function splitFencedBlocks(text) {
2916
+ const lines = text.split("\n");
2917
+ const segs = [];
2918
+ let prose = [];
2919
+ let code = null;
2920
+ let lang = "plain";
2921
+ const flushProse = () => {
2922
+ if (prose.length > 0) {
2923
+ segs.push({ type: "prose", text: prose.join("\n") });
2924
+ prose = [];
2925
+ }
2926
+ };
2927
+ for (const line of lines) {
2928
+ const fence = line.match(/^\s*```(.*)$/);
2929
+ if (fence) {
2930
+ if (code === null) {
2931
+ flushProse();
2932
+ code = [];
2933
+ lang = detectLang(fence[1] ?? "");
2934
+ } else {
2935
+ segs.push({ type: "code", text: code.join("\n"), lang });
2936
+ code = null;
2937
+ lang = "plain";
2938
+ }
2939
+ continue;
2940
+ }
2941
+ if (code !== null) code.push(line);
2942
+ else prose.push(line);
2943
+ }
2944
+ if (code !== null) segs.push({ type: "code", text: code.join("\n"), lang });
2945
+ flushProse();
2946
+ return segs;
2947
+ }
2948
+ function AssistantBody({
2949
+ text,
2950
+ termWidth,
2951
+ contentWidth
2952
+ }) {
2953
+ const segments = splitFencedBlocks(text);
2954
+ const inner = contentWidth ?? termWidth;
2955
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: segments.map(
2956
+ (seg, i) => seg.type === "code" ? (
2957
+ // biome-ignore lint/suspicious/noArrayIndexKey: segment order is stable
2958
+ /* @__PURE__ */ jsx(CodeBlock, { code: seg.text, lang: seg.lang ?? "plain", contentWidth: inner }, i)
2959
+ ) : (
2960
+ // biome-ignore lint/suspicious/noArrayIndexKey: segment order is stable
2961
+ /* @__PURE__ */ jsx(MarkdownView, { text: seg.text, termWidth: inner }, i)
2962
+ )
2963
+ ) });
2964
+ }
2965
+ function AssistantTail({ text }) {
2966
+ return /* @__PURE__ */ jsxs(
2967
+ Box,
2968
+ {
2969
+ flexDirection: "column",
2970
+ marginY: 1,
2971
+ borderStyle: "single",
2972
+ borderTop: false,
2973
+ borderRight: false,
2974
+ borderBottom: false,
2975
+ borderColor: theme.assistant,
2976
+ paddingLeft: 1,
2977
+ children: [
2978
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
2979
+ /* @__PURE__ */ jsx(Text, { bold: true, color: theme.assistant, children: "ASSISTANT" }),
2980
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " (streaming\u2026)" })
2981
+ ] }),
2982
+ /* @__PURE__ */ jsx(Text, { color: "white", children: text })
2983
+ ]
2984
+ }
2985
+ );
2986
+ }
2987
+ function Banner({
2988
+ entry
2989
+ }) {
2990
+ const cwdShort = shortenPath(entry.cwd, 48);
2991
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", paddingX: 2, paddingY: 0, children: [
2992
+ /* @__PURE__ */ jsxs(Text, { children: [
2993
+ /* @__PURE__ */ jsx(Text, { color: "magenta", bold: true, children: " \u259F\u259B " }),
2994
+ /* @__PURE__ */ jsx(Text, { color: "magenta", bold: true, children: "WrongStack" }),
2995
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " v" }),
2996
+ /* @__PURE__ */ jsx(Text, { children: entry.version })
2997
+ ] }),
2998
+ /* @__PURE__ */ jsx(Text, { dimColor: true, italic: true, children: " Built on the wrong stack. Shipped anyway." }),
2999
+ /* @__PURE__ */ jsxs(Text, { children: [
3000
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: " provider " }),
3001
+ /* @__PURE__ */ jsxs(Text, { children: [
3002
+ entry.provider,
3003
+ "/",
3004
+ entry.model
3005
+ ] })
3006
+ ] }),
3007
+ entry.family ? /* @__PURE__ */ jsxs(Text, { children: [
3008
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: " family " }),
3009
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: entry.family })
3010
+ ] }) : null,
3011
+ entry.keyTail ? /* @__PURE__ */ jsxs(Text, { children: [
3012
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: " key " }),
3013
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u25CF\u25CF\u25CF\u2026" }),
3014
+ /* @__PURE__ */ jsx(Text, { children: entry.keyTail })
3015
+ ] }) : null,
3016
+ /* @__PURE__ */ jsxs(Text, { children: [
3017
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: " cwd " }),
3018
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: cwdShort })
3019
+ ] }),
3020
+ /* @__PURE__ */ jsxs(Text, { children: [
3021
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: " hints " }),
3022
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "/help \xB7 /init \xB7 /memory \xB7 /queue \xB7 /exit" })
3023
+ ] })
3024
+ ] });
3025
+ }
3026
+ function brainStatusStyle(status) {
3027
+ switch (status) {
3028
+ case "thinking":
3029
+ return { icon: "\u2026", color: "magenta" };
3030
+ case "answered":
3031
+ return { icon: "\u2696", color: "cyan" };
3032
+ case "ask_human":
3033
+ return { icon: "?", color: "yellow" };
3034
+ case "denied":
3035
+ return { icon: "\xD7", color: "red" };
3036
+ }
3037
+ }
3038
+ function brainRiskColor(risk) {
3039
+ switch (risk) {
3040
+ case "low":
3041
+ return "green";
3042
+ case "medium":
3043
+ return "cyan";
3044
+ case "high":
3045
+ return "yellow";
3046
+ case "critical":
3047
+ return "red";
3048
+ }
3049
+ }
3050
+ function assistantContentWidth(termWidth) {
3051
+ return Math.max(20, termWidth - MESSAGE_PANEL_CHROME_WIDTH);
3052
+ }
3053
+ var Entry = React5.memo(function Entry2({
3054
+ entry,
3055
+ termWidth
3056
+ }) {
3057
+ switch (entry.kind) {
3058
+ case "user":
3059
+ return /* @__PURE__ */ jsx(
3060
+ Box,
3061
+ {
3062
+ borderStyle: "single",
3063
+ borderTop: false,
3064
+ borderRight: false,
3065
+ borderBottom: false,
3066
+ borderColor: theme.user,
3067
+ paddingLeft: 1,
3068
+ children: /* @__PURE__ */ jsxs(Text, { children: [
3069
+ /* @__PURE__ */ jsx(Text, { bold: true, color: theme.user, children: "USER " }),
3070
+ /* @__PURE__ */ jsx(Text, { color: "white", children: entry.text }),
3071
+ entry.queued ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: " (queued)" }) : null,
3072
+ entry.pasteContent ? /* @__PURE__ */ jsxs(Fragment, { children: [
3073
+ entry.text ? "\n" : null,
3074
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
3075
+ " \u21B3 ",
3076
+ entry.pasteContent
3077
+ ] })
3078
+ ] }) : null
3079
+ ] })
3080
+ }
3081
+ );
3082
+ case "assistant": {
3083
+ const contentWidth = assistantContentWidth(termWidth);
3084
+ return /* @__PURE__ */ jsxs(
3085
+ Box,
3086
+ {
3087
+ flexDirection: "column",
3088
+ marginY: 1,
3089
+ borderStyle: "single",
3090
+ borderTop: false,
3091
+ borderRight: false,
3092
+ borderBottom: false,
3093
+ borderColor: theme.assistant,
3094
+ paddingLeft: 1,
3095
+ children: [
3096
+ /* @__PURE__ */ jsx(Box, { flexDirection: "row", children: /* @__PURE__ */ jsx(Text, { bold: true, color: theme.assistant, children: "ASSISTANT" }) }),
3097
+ /* @__PURE__ */ jsx(AssistantBody, { text: entry.text, termWidth, contentWidth })
3098
+ ]
3099
+ }
3100
+ );
3101
+ }
3102
+ case "tool": {
3103
+ const argSummary = formatToolArgs(entry.name, entry.input);
3104
+ const outLines = formatToolOutput(
3105
+ entry.name,
3106
+ entry.output,
3107
+ entry.ok,
3108
+ entry.outputBytes,
3109
+ entry.outputLines
3110
+ );
3111
+ const diff = entry.ok ? extractDiffPreview(entry.name, entry.output) : void 0;
3112
+ const sizeChip = (() => {
3113
+ if (!entry.ok) return "";
3114
+ const parts = [];
3115
+ if (entry.outputLines !== void 0 && entry.outputLines > 0) {
3116
+ parts.push(`${entry.outputLines} L`);
3117
+ }
3118
+ if (entry.outputBytes && entry.outputBytes > 0) {
3119
+ parts.push(fmtBytes(entry.outputBytes));
3120
+ }
3121
+ if (entry.outputTokens && entry.outputTokens > 0) {
3122
+ parts.push(`\u2248${fmtTok2(entry.outputTokens)} tok`);
3123
+ }
3124
+ return parts.join(" \xB7 ");
3125
+ })();
3126
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3127
+ /* @__PURE__ */ jsxs(Text, { children: [
3128
+ /* @__PURE__ */ jsx(Text, { color: entry.ok ? theme.success : theme.error, children: entry.ok ? "\u25CF" : "\u2717" }),
3129
+ " ",
3130
+ /* @__PURE__ */ jsx(Text, { bold: true, color: theme.tool, children: entry.name }),
3131
+ argSummary ? /* @__PURE__ */ jsxs(Fragment, { children: [
3132
+ /* @__PURE__ */ jsx(Text, { children: " " }),
3133
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: argSummary })
3134
+ ] }) : null,
3135
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \xB7 ${fmtDuration(entry.durationMs)}` }),
3136
+ sizeChip ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \xB7 ${sizeChip}` }) : null
3137
+ ] }),
3138
+ outLines.map((line, i) => (
3139
+ // biome-ignore lint/suspicious/noArrayIndexKey: tool output lines are static, index is stable
3140
+ /* @__PURE__ */ jsxs(Text, { children: [
3141
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: i === outLines.length - 1 && !diff ? " \u2514\u2500 " : " \u251C\u2500 " }),
3142
+ /* @__PURE__ */ jsx(
3143
+ Text,
3144
+ {
3145
+ color: !entry.ok || line.startsWith("!") ? "red" : void 0,
3146
+ dimColor: entry.ok && !line.startsWith("!"),
3147
+ children: line
3148
+ }
3149
+ )
3150
+ ] }, i)
3151
+ )),
3152
+ diff ? /* @__PURE__ */ jsx(DiffBlock, { rows: diff.rows, hidden: diff.hidden }) : null
3153
+ ] });
3154
+ }
3155
+ case "info":
3156
+ return /* @__PURE__ */ jsx(Text, { dimColor: true, children: entry.text });
3157
+ case "warn":
3158
+ return /* @__PURE__ */ jsx(
3159
+ Box,
3160
+ {
3161
+ borderStyle: "single",
3162
+ borderTop: false,
3163
+ borderRight: false,
3164
+ borderBottom: false,
3165
+ borderColor: theme.warn,
3166
+ paddingLeft: 1,
3167
+ children: /* @__PURE__ */ jsx(Text, { color: theme.warn, children: entry.text })
3168
+ }
3169
+ );
3170
+ case "error":
3171
+ return /* @__PURE__ */ jsx(
3172
+ Box,
3173
+ {
3174
+ borderStyle: "single",
3175
+ borderTop: false,
3176
+ borderRight: false,
3177
+ borderBottom: false,
3178
+ borderColor: theme.error,
3179
+ paddingLeft: 1,
3180
+ children: /* @__PURE__ */ jsx(Text, { color: theme.error, children: entry.text })
3181
+ }
3182
+ );
3183
+ case "turn-summary":
3184
+ return /* @__PURE__ */ jsx(Text, { dimColor: true, children: entry.text });
3185
+ case "brain": {
3186
+ const statusStyle = brainStatusStyle(entry.status);
3187
+ const riskColor2 = brainRiskColor(entry.risk);
3188
+ return /* @__PURE__ */ jsxs(
3189
+ Box,
3190
+ {
3191
+ flexDirection: "column",
3192
+ marginY: 1,
3193
+ borderStyle: "single",
3194
+ borderTop: false,
3195
+ borderRight: false,
3196
+ borderBottom: false,
3197
+ borderColor: "magenta",
3198
+ paddingLeft: 1,
3199
+ children: [
3200
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
3201
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "magenta", children: "BRAIN" }),
3202
+ /* @__PURE__ */ jsx(Text, { color: statusStyle.color, children: statusStyle.icon }),
3203
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: entry.source }),
3204
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
3205
+ /* @__PURE__ */ jsx(Text, { color: riskColor2, children: entry.risk })
3206
+ ] }),
3207
+ /* @__PURE__ */ jsx(Text, { color: "white", children: entry.question }),
3208
+ entry.decision ? /* @__PURE__ */ jsxs(Text, { children: [
3209
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Decision: " }),
3210
+ /* @__PURE__ */ jsx(Text, { color: statusStyle.color, children: entry.decision })
3211
+ ] }) : null,
3212
+ entry.rationale ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: entry.rationale }) : null
3213
+ ]
3214
+ }
3215
+ );
3216
+ }
3217
+ case "confirm":
3218
+ return /* @__PURE__ */ jsxs(
3219
+ Box,
3220
+ {
3221
+ flexDirection: "column",
3222
+ borderStyle: "round",
3223
+ borderColor: "yellow",
3224
+ paddingX: 1,
3225
+ marginY: 1,
3226
+ children: [
3227
+ /* @__PURE__ */ jsxs(Text, { bold: true, color: "yellow", children: [
3228
+ "\u26A0 Confirm: ",
3229
+ entry.toolName
3230
+ ] }),
3231
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Waiting for y / n / a / d..." })
3232
+ ]
3233
+ }
3234
+ );
3235
+ case "banner":
3236
+ return /* @__PURE__ */ jsx(Banner, { entry });
3237
+ case "subagent": {
3238
+ const lines = entry.text.split("\n");
3239
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3240
+ /* @__PURE__ */ jsxs(Text, { children: [
3241
+ /* @__PURE__ */ jsx(Text, { color: entry.agentColor, bold: true, children: `[${entry.agentLabel}]` }),
3242
+ /* @__PURE__ */ jsx(Text, { children: " " }),
3243
+ /* @__PURE__ */ jsx(Text, { color: entry.agentColor, children: entry.icon }),
3244
+ /* @__PURE__ */ jsx(Text, { children: " " }),
3245
+ /* @__PURE__ */ jsx(Text, { children: lines[0] ?? "" }),
3246
+ entry.detail ? /* @__PURE__ */ jsxs(Fragment, { children: [
3247
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \xB7 " }),
3248
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: entry.detail })
3249
+ ] }) : null
3250
+ ] }),
3251
+ lines.slice(1).map((line, i) => (
3252
+ // biome-ignore lint/suspicious/noArrayIndexKey: stable line index
3253
+ /* @__PURE__ */ jsxs(Text, { children: [
3254
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " " }),
3255
+ /* @__PURE__ */ jsx(Text, { children: line })
3256
+ ] }, i)
3257
+ ))
3258
+ ] });
3259
+ }
3260
+ }
3261
+ });
3262
+ function History({ entries, streamingText, toolStream }) {
3263
+ const { stdout } = useStdout();
3264
+ const [termSize, setTermSize] = useState({
3265
+ columns: stdout?.columns ?? 80,
3266
+ rows: stdout?.rows ?? 24
3267
+ });
3268
+ useEffect(() => {
3269
+ const handleResize = () => {
3270
+ setTermSize({ columns: stdout?.columns ?? 80, rows: stdout?.rows ?? 24 });
3271
+ };
3272
+ process.stdout.on("resize", handleResize);
3273
+ return () => {
3274
+ process.stdout.off("resize", handleResize);
3275
+ };
3276
+ }, [stdout]);
3277
+ const termWidth = termSize.columns;
3278
+ const tail = streamingText ? tailForDisplay(streamingText, MAX_STREAM_DISPLAY_CHARS) : "";
3279
+ const toolTail = toolStream?.text ? tailForDisplay(toolStream.text, MAX_STREAM_DISPLAY_CHARS) : "";
3280
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
3281
+ /* @__PURE__ */ jsx(Static, { items: entries, children: (entry) => /* @__PURE__ */ jsx(Box, { marginBottom: entry.kind === "turn-summary" ? 1 : 0, children: /* @__PURE__ */ jsx(Entry, { entry, termWidth }) }, entry.id) }),
3282
+ tail ? /* @__PURE__ */ jsx(AssistantTail, { text: tail }) : null,
3283
+ toolTail ? /* @__PURE__ */ jsx(
3284
+ ToolStreamBox,
3285
+ {
3286
+ name: toolStream.name,
3287
+ text: toolTail,
3288
+ startedAt: toolStream.startedAt,
3289
+ termWidth
3290
+ }
3291
+ ) : null
3292
+ ] });
3378
3293
  }
3379
3294
 
3380
3295
  // src/fn-keys.ts
@@ -3479,52 +3394,6 @@ function layoutInputRows(prompt, value, cursor, width) {
3479
3394
  if (row.length > 0 || rows.length === 0) rows.push(row);
3480
3395
  return rows;
3481
3396
  }
3482
- function inputIndexAtRowCol(prompt, value, width, row, col) {
3483
- const w = Math.max(1, Math.floor(width));
3484
- const flat = [];
3485
- for (let i = 0; i < prompt.length; i++) flat.push({ ch: prompt[i], buf: -1 });
3486
- for (let i = 0; i < value.length; i++) flat.push({ ch: value[i], buf: i });
3487
- const rows = [];
3488
- const starts = [];
3489
- let cur = [];
3490
- let curStart = 0;
3491
- for (const cell of flat) {
3492
- if (cell.ch === "\n") {
3493
- rows.push(cur);
3494
- starts.push(curStart);
3495
- cur = [];
3496
- curStart = cell.buf + 1;
3497
- continue;
3498
- }
3499
- if (cur.length === 0 && cell.buf >= 0) curStart = cell.buf;
3500
- cur.push({ buf: cell.buf });
3501
- if (cur.length >= w) {
3502
- rows.push(cur);
3503
- starts.push(curStart);
3504
- cur = [];
3505
- curStart = -1;
3506
- }
3507
- }
3508
- if (cur.length > 0 || rows.length === 0) {
3509
- rows.push(cur);
3510
- starts.push(curStart);
3511
- }
3512
- const clamp = (n) => Math.max(0, Math.min(value.length, n));
3513
- if (row < 0) return 0;
3514
- if (row >= rows.length) return value.length;
3515
- const r = rows[row];
3516
- const c = Math.max(0, col);
3517
- if (c < r.length) {
3518
- const b = r[c].buf;
3519
- return b < 0 ? 0 : clamp(b);
3520
- }
3521
- for (let k = r.length - 1; k >= 0; k--) {
3522
- const b = r[k].buf;
3523
- if (b >= 0) return clamp(b + 1);
3524
- }
3525
- const start = starts[row];
3526
- return clamp(start !== void 0 && start >= 0 ? start : value.length);
3527
- }
3528
3397
  function renderRow2(cells, rowKey, promptColor) {
3529
3398
  const out = [];
3530
3399
  let run = "";
@@ -3664,8 +3533,7 @@ function hintsFor(ctx) {
3664
3533
  return [
3665
3534
  { key: "\u2191\u2193", label: "move" },
3666
3535
  { key: "\u21B5", label: "select" },
3667
- { key: "Esc", label: "cancel" },
3668
- ...ctx.mouse ? [{ key: "click", label: "pick" }] : []
3536
+ { key: "Esc", label: "cancel" }
3669
3537
  ];
3670
3538
  }
3671
3539
  if (ctx.monitor) {
@@ -3678,7 +3546,6 @@ function hintsFor(ctx) {
3678
3546
  }
3679
3547
  const base = [{ key: "?", label: "help" }];
3680
3548
  if (ctx.managed) base.push({ key: "PgUp/PgDn", label: "scroll" });
3681
- if (ctx.mouse) base.push({ key: "wheel", label: "scroll" }, { key: "click", label: "select" });
3682
3549
  base.push({ key: "^G", label: "agents" }, { key: "^C", label: "stop" });
3683
3550
  return base;
3684
3551
  }
@@ -3718,7 +3585,7 @@ function fmtRecentMessage(message) {
3718
3585
  const text = message.text.replace(/\s+/g, " ");
3719
3586
  return text.length > 48 ? `${text.slice(0, 47)}...` : text;
3720
3587
  }
3721
- var LiveActivityStrip = React4.memo(function LiveActivityStrip2({
3588
+ var LiveActivityStrip = React5.memo(function LiveActivityStrip2({
3722
3589
  entries,
3723
3590
  nowTick,
3724
3591
  maxRows = 4
@@ -4044,13 +3911,6 @@ function scrollbarThumb(rows, offset, total) {
4044
3911
  const top = Math.max(0, Math.min(rawTop, rows - size));
4045
3912
  return { top, size, scrollable: true };
4046
3913
  }
4047
- function scrollOffsetForTrackRow(rows, total, cell) {
4048
- if (total <= rows) return 0;
4049
- const maxOffset = total - rows;
4050
- const clampedCell = Math.max(0, Math.min(rows - 1, cell));
4051
- const windowTop = Math.round(clampedCell / Math.max(1, rows - 1) * maxOffset);
4052
- return Math.max(0, Math.min(maxOffset, maxOffset - windowTop));
4053
- }
4054
3914
  function Scrollbar({
4055
3915
  rows,
4056
3916
  offset,
@@ -4535,6 +4395,132 @@ function runGit(cwd, args) {
4535
4395
  }
4536
4396
  });
4537
4397
  }
4398
+ var STREAM_COLORS = ["cyan", "magenta", "yellow", "green", "blue"];
4399
+ function labelFor(labelsRef, id, name) {
4400
+ const m = labelsRef.current;
4401
+ const existing = m.get(id);
4402
+ if (existing) return existing;
4403
+ const n = m.size + 1;
4404
+ const v = {
4405
+ label: name && name !== id ? name : `AGENT#${n}`,
4406
+ color: STREAM_COLORS[(n - 1) % STREAM_COLORS.length]
4407
+ };
4408
+ m.set(id, v);
4409
+ return v;
4410
+ }
4411
+ function useSubagentEvents(events, dispatch, setActiveMaxContext) {
4412
+ const labelsRef = useRef(/* @__PURE__ */ new Map());
4413
+ const lbl = useCallback(
4414
+ (id, name) => labelFor(labelsRef, id, name),
4415
+ []
4416
+ // labelsRef is a stable ref
4417
+ );
4418
+ useEffect(() => {
4419
+ const offSpawned = events.on("subagent.spawned", (e) => {
4420
+ const l = lbl(e.subagentId, e.name);
4421
+ dispatch({ type: "fleetSpawn", id: e.subagentId, name: e.name, provider: e.provider, model: e.model, transcriptPath: e.transcriptPath });
4422
+ const where = e.provider && e.model ? `${e.provider}/${e.model}` : "spawned";
4423
+ const desc = e.description ? ` \u2014 ${e.description.slice(0, 80)}` : "";
4424
+ dispatch({ type: "addEntry", entry: { kind: "subagent", agentLabel: l.label, agentColor: l.color, icon: "\u25B6", text: `${where}${desc}` } });
4425
+ });
4426
+ const offStarted = events.on("subagent.task_started", (e) => {
4427
+ const l = lbl(e.subagentId);
4428
+ dispatch({ type: "fleetStart", id: e.subagentId, taskId: e.taskId });
4429
+ const desc = e.description ? ` \u2014 ${e.description.slice(0, 80)}` : "";
4430
+ dispatch({ type: "addEntry", entry: { kind: "subagent", agentLabel: l.label, agentColor: l.color, icon: "\u25CF", text: `task started${desc}` } });
4431
+ });
4432
+ const offCompleted = events.on("subagent.task_completed", (e) => {
4433
+ const l = lbl(e.subagentId);
4434
+ const errKind = e.error?.kind;
4435
+ dispatch({ type: "fleetDone", id: e.subagentId, status: e.status, iterations: e.iterations, toolCalls: e.toolCalls, failureReason: errKind });
4436
+ const icon = e.status === "success" ? "\u2713" : e.status === "timeout" ? "\u23F1" : e.status === "stopped" ? "\u2298" : "\u2717";
4437
+ const errMsg = e.error?.message;
4438
+ const errMsgTail = errMsg ? ` \u2014 ${errMsg.replace(/\s+/g, " ").slice(0, 100)}${errMsg.length > 100 ? "\u2026" : ""}` : "";
4439
+ const errChip = errKind ? ` [${errKind}]` : "";
4440
+ const secs = (e.durationMs / 1e3).toFixed(e.durationMs < 1e4 ? 1 : 0);
4441
+ dispatch({ type: "addEntry", entry: { kind: "subagent", agentLabel: l.label, agentColor: l.color, icon, text: `${e.status} (${e.iterations} iter \xB7 ${e.toolCalls} tools \xB7 ${secs}s)${errChip}${errMsgTail}` } });
4442
+ });
4443
+ const offBudgetWarning = events.on("subagent.budget_warning", (e) => {
4444
+ const l = lbl(e.subagentId);
4445
+ dispatch({ type: "fleetBudgetWarning", id: e.subagentId, kind: e.kind, used: e.used, limit: e.limit });
4446
+ const timeoutSuffix = e.kind === "timeout" ? " (subagent continues running)" : " \u2014 extending";
4447
+ dispatch({ type: "addEntry", entry: { kind: "subagent", agentLabel: l.label, agentColor: l.color, icon: "\u26A1", text: `hitting ${e.kind} limit (${e.used}/${e.limit})${timeoutSuffix}` } });
4448
+ });
4449
+ const offBudgetExtended = events.on("subagent.budget_extended", (e) => {
4450
+ const l = lbl(e.subagentId);
4451
+ dispatch({ type: "fleetBudgetExtended", id: e.subagentId, totalExtensions: e.totalExtensions });
4452
+ dispatch({ type: "addEntry", entry: { kind: "subagent", agentLabel: l.label, agentColor: l.color, icon: "\u26A1", text: `extended ${e.kind} \u2192 ${e.newLimit} (\xD7${e.totalExtensions})` } });
4453
+ });
4454
+ const offIterationSummary = events.on("subagent.iteration_summary", (e) => {
4455
+ const l = lbl(e.subagentId);
4456
+ const costStr = e.costUsd > 0 ? ` \xB7 ${e.costUsd.toFixed(3)}` : "";
4457
+ const toolStr = e.currentTool ? ` \xB7 doing ${e.currentTool}` : "";
4458
+ const partial = e.partialText ? ` \xB7 "${e.partialText.slice(0, 60)}${e.partialText.length > 60 ? "\u2026" : ""}"` : "";
4459
+ dispatch({ type: "addEntry", entry: { kind: "subagent", agentLabel: l.label, agentColor: l.color, icon: "\u{1F4AC}", text: `L${e.iteration} \xB7 ${e.toolCalls} tools${costStr}${toolStr}${partial}` } });
4460
+ });
4461
+ const offCtxPct = events.on("subagent.ctx_pct", (e) => {
4462
+ dispatch({ type: "fleetCtxPct", id: e.subagentId, load: e.load, tokens: e.tokens, maxContext: e.maxContext });
4463
+ });
4464
+ const offConcurrencyChanged = events.on("concurrency.changed", (e) => {
4465
+ const { n } = e;
4466
+ if (typeof n === "number" && n > 0) {
4467
+ dispatch({ type: "fleetConcurrency", n });
4468
+ }
4469
+ });
4470
+ const offLeaderCtxPct = events.on("ctx.pct", (e) => {
4471
+ setActiveMaxContext(e.maxContext);
4472
+ dispatch({ type: "leaderCtxPct", load: e.load, tokens: e.tokens, maxContext: e.maxContext });
4473
+ });
4474
+ const offLeaderMaxContext = events.on("ctx.max_context", (e) => {
4475
+ if (e.maxContext > 0) setActiveMaxContext(e.maxContext);
4476
+ });
4477
+ const offTool = events.on("subagent.tool_executed", (e) => {
4478
+ dispatch({ type: "fleetTool", id: e.subagentId, name: e.name, ok: e.ok, durationMs: e.durationMs, outputBytes: e.outputBytes });
4479
+ dispatch({ type: "fleetToolEnd", id: e.subagentId });
4480
+ });
4481
+ return () => {
4482
+ offSpawned();
4483
+ offStarted();
4484
+ offCompleted();
4485
+ offBudgetWarning();
4486
+ offBudgetExtended();
4487
+ offIterationSummary();
4488
+ offCtxPct();
4489
+ offConcurrencyChanged();
4490
+ offLeaderCtxPct();
4491
+ offLeaderMaxContext();
4492
+ offTool();
4493
+ };
4494
+ }, [events, dispatch, setActiveMaxContext, lbl]);
4495
+ }
4496
+ function useBrainEvents(events, dispatch) {
4497
+ useEffect(() => {
4498
+ const requestSummary = (request) => `${request.source}: ${request.question}`.slice(0, 80);
4499
+ const addBrainEntry = (status, payload) => {
4500
+ const p = payload;
4501
+ const decision = p.decision.optionId ?? p.decision.text ?? p.decision.reason ?? p.decision.prompt ?? p.decision.type;
4502
+ dispatch({ type: "brainStatus", state: status, source: p.request.source, risk: p.request.risk, summary: decision });
4503
+ if (status === "ask_human") {
4504
+ dispatch({ type: "brainPromptSet", prompt: { requestId: p.request.id, source: p.request.source, risk: p.request.risk, question: p.request.question, context: p.request.context, options: p.request.options } });
4505
+ } else {
4506
+ dispatch({ type: "brainPromptClear" });
4507
+ }
4508
+ dispatch({ type: "addEntry", entry: { kind: "brain", status, source: p.request.source, risk: p.request.risk, question: p.request.question, decision, rationale: p.decision.rationale } });
4509
+ };
4510
+ const offRequested = events.on("brain.decision_requested", ({ request }) => {
4511
+ dispatch({ type: "brainStatus", state: "deciding", source: request.source, risk: request.risk, summary: requestSummary(request) });
4512
+ });
4513
+ const offAnswered = events.on("brain.decision_answered", (payload) => addBrainEntry("answered", payload));
4514
+ const offAskHuman = events.on("brain.decision_ask_human", (payload) => addBrainEntry("ask_human", payload));
4515
+ const offDenied = events.on("brain.decision_denied", (payload) => addBrainEntry("denied", payload));
4516
+ return () => {
4517
+ offRequested();
4518
+ offAnswered();
4519
+ offAskHuman();
4520
+ offDenied();
4521
+ };
4522
+ }, [events, dispatch]);
4523
+ }
4538
4524
  var USAGE = "Usage:\n /kill \u2014 list active processes + breaker state\n /kill list \u2014 same as /kill\n /kill all \u2014 kill all tracked processes (SIGTERM \u2192 SIGKILL)\n /kill force \u2014 kill all with SIGKILL immediately\n /kill reset \u2014 reset the circuit breaker to closed\n /kill <pid> \u2014 kill a specific process by PID";
4539
4525
  function createKillSlashCommand() {
4540
4526
  return {
@@ -4754,14 +4740,44 @@ function oneLine(s2, max) {
4754
4740
  const collapsed = s2.replace(/\s+/g, " ").trim();
4755
4741
  return collapsed.length <= max ? collapsed : `${collapsed.slice(0, max - 1)}\u2026`;
4756
4742
  }
4757
- var WHEEL_STEP = 3;
4758
- var MIN_VIEWPORT = 3;
4759
- var INPUT_PROMPT = "\u203A ";
4760
- function selectedSlashCommandLine(picker) {
4761
- if (!picker.open || picker.matches.length === 0) return null;
4762
- const picked = picker.matches[picker.selected];
4763
- return picked ? `/${picked.name}` : null;
4743
+
4744
+ // src/steering-preamble.ts
4745
+ function buildSteeringPreamble(snapshot, newDirection) {
4746
+ const lines = ["[STEERING \u2014 I pressed Esc to interrupt you mid-task on purpose.", ""];
4747
+ const ctx = [];
4748
+ if (snapshot?.runningTools && snapshot.runningTools.length > 0) {
4749
+ ctx.push(`- in-flight tools (now cancelled): ${snapshot.runningTools.join(", ")}`);
4750
+ }
4751
+ if (snapshot?.subagentsTerminated && snapshot.subagentsTerminated > 0) {
4752
+ const subDetails = snapshot.subagents.map((s2) => `${s2.label}${s2.tool ? ` (was running: ${s2.tool})` : ""}`).join(", ");
4753
+ ctx.push(
4754
+ `- subagents (${snapshot.subagentsTerminated} terminated by me, do NOT await them): ${subDetails}`
4755
+ );
4756
+ }
4757
+ if (snapshot?.partialAssistantText && snapshot.partialAssistantText.trim().length > 0) {
4758
+ const tail = snapshot.partialAssistantText.trim().slice(-300);
4759
+ ctx.push(`- your last partial output (truncated, for context only): "${tail}"`);
4760
+ }
4761
+ if (ctx.length > 0) {
4762
+ lines.push("What was happening when I cut you off:");
4763
+ lines.push(...ctx);
4764
+ lines.push("");
4765
+ }
4766
+ lines.push("You have authority to:");
4767
+ lines.push("- Abandon the prior plan entirely if the new direction makes it stale.");
4768
+ lines.push("- Re-spawn fresh subagents (with different roles or tasks) if needed.");
4769
+ lines.push('- Skip a polite "should I continue?" \u2014 just pivot.');
4770
+ lines.push("- Ask me to clarify if the new direction is genuinely ambiguous.");
4771
+ lines.push("");
4772
+ lines.push("New direction:");
4773
+ lines.push("---");
4774
+ lines.push(newDirection);
4775
+ lines.push("---");
4776
+ lines.push("]");
4777
+ return lines.join("\n");
4764
4778
  }
4779
+
4780
+ // src/app-reducer.ts
4765
4781
  function reducer(state, action) {
4766
4782
  switch (action.type) {
4767
4783
  case "addEntry": {
@@ -5355,6 +5371,9 @@ function reducer(state, action) {
5355
5371
  } : state.fleetTokens
5356
5372
  };
5357
5373
  }
5374
+ case "fleetConcurrency": {
5375
+ return { ...state, fleetConcurrency: action.n };
5376
+ }
5358
5377
  case "leaderIterStart": {
5359
5378
  return {
5360
5379
  ...state,
@@ -5543,7 +5562,7 @@ function reducer(state, action) {
5543
5562
  case "worktreeMonitorToggle": {
5544
5563
  return { ...state, worktreeMonitorOpen: !state.worktreeMonitorOpen };
5545
5564
  }
5546
- // --- In-app chat scroll (mouse mode) ---
5565
+ // --- In-app chat scroll ---
5547
5566
  case "scrollBy": {
5548
5567
  const maxOffset = Math.max(0, state.totalLines - state.viewportRows);
5549
5568
  const next = Math.max(0, Math.min(maxOffset, state.scrollOffset + action.delta));
@@ -5721,41 +5740,14 @@ function reducer(state, action) {
5721
5740
  }
5722
5741
  }
5723
5742
  }
5724
- var PASTE_THRESHOLD_CHARS = 200;
5725
- function buildSteeringPreamble(snapshot, newDirection) {
5726
- const lines = ["[STEERING \u2014 I pressed Esc to interrupt you mid-task on purpose.", ""];
5727
- const ctx = [];
5728
- if (snapshot?.runningTools && snapshot.runningTools.length > 0) {
5729
- ctx.push(`- in-flight tools (now cancelled): ${snapshot.runningTools.join(", ")}`);
5730
- }
5731
- if (snapshot?.subagentsTerminated && snapshot.subagentsTerminated > 0) {
5732
- const subDetails = snapshot.subagents.map((s2) => `${s2.label}${s2.tool ? ` (was running: ${s2.tool})` : ""}`).join(", ");
5733
- ctx.push(
5734
- `- subagents (${snapshot.subagentsTerminated} terminated by me, do NOT await them): ${subDetails}`
5735
- );
5736
- }
5737
- if (snapshot?.partialAssistantText && snapshot.partialAssistantText.trim().length > 0) {
5738
- const tail = snapshot.partialAssistantText.trim().slice(-300);
5739
- ctx.push(`- your last partial output (truncated, for context only): "${tail}"`);
5740
- }
5741
- if (ctx.length > 0) {
5742
- lines.push("What was happening when I cut you off:");
5743
- lines.push(...ctx);
5744
- lines.push("");
5745
- }
5746
- lines.push("You have authority to:");
5747
- lines.push("- Abandon the prior plan entirely if the new direction makes it stale.");
5748
- lines.push("- Re-spawn fresh subagents (with different roles or tasks) if needed.");
5749
- lines.push('- Skip a polite "should I continue?" \u2014 just pivot.');
5750
- lines.push("- Ask me to clarify if the new direction is genuinely ambiguous.");
5751
- lines.push("");
5752
- lines.push("New direction:");
5753
- lines.push("---");
5754
- lines.push(newDirection);
5755
- lines.push("---");
5756
- lines.push("]");
5757
- return lines.join("\n");
5743
+ var MIN_VIEWPORT = 3;
5744
+ var INPUT_PROMPT = "\u203A ";
5745
+ function selectedSlashCommandLine(picker) {
5746
+ if (!picker.open || picker.matches.length === 0) return null;
5747
+ const picked = picker.matches[picker.selected];
5748
+ return picked ? `/${picked.name}` : null;
5758
5749
  }
5750
+ var PASTE_THRESHOLD_CHARS = 200;
5759
5751
  function App({
5760
5752
  agent,
5761
5753
  slashRegistry,
@@ -5799,8 +5791,6 @@ function App({
5799
5791
  initialGoal,
5800
5792
  initialAsk,
5801
5793
  sessionsDir,
5802
- mouse = false,
5803
- subscribeMouse,
5804
5794
  managed = false
5805
5795
  }) {
5806
5796
  const { exit } = useApp();
@@ -5812,18 +5802,15 @@ function App({
5812
5802
  const [hiddenItems, setHiddenItems] = useState(statuslineHiddenItems);
5813
5803
  const { stdout } = useStdout();
5814
5804
  const [termRows, setTermRows] = useState(stdout?.rows ?? 24);
5815
- const [termCols, setTermCols] = useState(stdout?.columns ?? 80);
5816
5805
  useEffect(() => {
5817
5806
  const onResize = () => {
5818
5807
  setTermRows(process.stdout.rows ?? 24);
5819
- setTermCols(process.stdout.columns ?? 80);
5820
5808
  };
5821
5809
  process.stdout.on("resize", onResize);
5822
5810
  return () => {
5823
5811
  process.stdout.off("resize", onResize);
5824
5812
  };
5825
5813
  }, []);
5826
- const [mouseLive, setMouseLive] = useState(mouse);
5827
5814
  const [managedLive, setManagedLive] = useState(managed);
5828
5815
  useEffect(() => {
5829
5816
  setHiddenItems(statuslineHiddenItems);
@@ -5910,6 +5897,7 @@ function App({
5910
5897
  },
5911
5898
  fleetCost: 0,
5912
5899
  fleetTokens: { input: 0, output: 0 },
5900
+ fleetConcurrency: 4,
5913
5901
  streamFleet: true,
5914
5902
  monitorOpen: false,
5915
5903
  agentsMonitorOpen: false,
@@ -5936,251 +5924,34 @@ function App({
5936
5924
  const activeCtrlRef = useRef(null);
5937
5925
  const exitRequestedRef = useRef(false);
5938
5926
  const inputGateRef = useRef(false);
5939
- const lastEnterAtRef = useRef(0);
5940
- const tokenPreviewsRef = useRef(/* @__PURE__ */ new Map());
5941
- const projectName = React4.useMemo(() => {
5942
- const base = path2.basename(projectRoot);
5943
- return base && base !== path2.sep ? base : void 0;
5944
- }, [projectRoot]);
5945
- const streamingTextRef = useRef("");
5946
- const pendingDeltaRef = useRef("");
5947
- const flushTimerRef = useRef(null);
5948
- const stateRef = useRef(state);
5949
- stateRef.current = state;
5950
- const draftRef = useRef({ buffer: state.buffer, cursor: state.cursor });
5951
- draftRef.current = { buffer: state.buffer, cursor: state.cursor };
5952
- const bottomRef = useRef(null);
5953
- const prePickerRef = useRef(null);
5954
- const preRowsRef = useRef(0);
5955
- const liveStripRef = useRef(null);
5956
- const liveStripRowsRef = useRef(0);
5957
- React4.useLayoutEffect(() => {
5958
- if (!managedLive) return;
5959
- const node = bottomRef.current;
5960
- if (!node) return;
5961
- const { height } = measureElement(node);
5962
- if (prePickerRef.current) {
5963
- preRowsRef.current = measureElement(prePickerRef.current).height;
5964
- }
5965
- if (liveStripRef.current) {
5966
- liveStripRowsRef.current = measureElement(liveStripRef.current).height;
5967
- }
5968
- const s2 = stateRef.current;
5969
- const affordance = s2.scrollOffset > 0 && s2.pendingNewLines > 0 ? 1 : 0;
5970
- const vp = Math.max(MIN_VIEWPORT, termRows - height - affordance - 1);
5971
- if (vp !== s2.viewportRows) {
5972
- dispatch({ type: "setViewportRows", rows: vp });
5973
- }
5974
- }, [managedLive, termRows]);
5975
- const handleKeyRef = useRef(null);
5976
- const pendingClickConfirmRef = useRef(null);
5977
- const openModelPickerRef = useRef(null);
5978
- const openAutonomyPickerRef = useRef(null);
5979
- const handleRewindToRef = useRef(null);
5980
- const confirmRef = useRef(null);
5981
- const confirmDecisionRef = useRef(null);
5982
- const scrollbarDragRef = useRef(false);
5983
- const lastLeftClickRef = useRef(null);
5984
- const DOUBLE_CLICK_MS = 500;
5985
- const DOUBLE_CLICK_DIST = 5;
5986
- const statusChipRef = useRef({ version: appVersion, model, fleetRunning: 0, yolo, autonomy: "off" });
5987
- const handleMouse = React4.useCallback(
5988
- (ev) => {
5989
- const s2 = stateRef.current;
5990
- if (ev.type === "wheel") {
5991
- const up = ev.button === "wheelUp";
5992
- const step = up ? -1 : 1;
5993
- if (s2.slashPicker.open) return dispatch({ type: "slashPickerMove", delta: step });
5994
- if (s2.modelPicker.open) return dispatch({ type: "modelPickerMove", delta: step });
5995
- if (s2.autonomyPicker.open) return dispatch({ type: "autonomyPickerMove", delta: step });
5996
- if (s2.settingsPicker.open) return dispatch({ type: "settingsFieldMove", delta: step });
5997
- if (s2.picker.open) return dispatch({ type: "pickerMove", delta: step });
5998
- if (s2.rewindOverlay) return dispatch({ type: "rewindOverlayMove", delta: step });
5999
- return dispatch({ type: "scrollBy", delta: up ? WHEEL_STEP : -WHEEL_STEP });
6000
- }
6001
- if (ev.type === "release") {
6002
- scrollbarDragRef.current = false;
6003
- return;
6004
- }
6005
- if (ev.button !== "left") return;
6006
- const now = Date.now();
6007
- const lastClick = lastLeftClickRef.current;
6008
- const isMultiClick = lastClick !== null && now - lastClick.ts < DOUBLE_CLICK_MS && Math.abs(ev.x - lastClick.x) <= DOUBLE_CLICK_DIST && Math.abs(ev.y - lastClick.y) <= DOUBLE_CLICK_DIST;
6009
- const clickCount = isMultiClick ? lastClick.count + 1 : 1;
6010
- lastLeftClickRef.current = { x: ev.x, y: ev.y, ts: now, count: clickCount };
6011
- {
6012
- const rows = s2.viewportRows;
6013
- if (ev.drag) {
6014
- if (scrollbarDragRef.current && s2.totalLines > rows) {
6015
- dispatch({
6016
- type: "scrollTo",
6017
- offset: scrollOffsetForTrackRow(rows, s2.totalLines, ev.y - 1)
6018
- });
6019
- }
6020
- return;
6021
- }
6022
- const cols = termCols || 80;
6023
- const onScrollbar = cols > 0 && ev.x >= cols - 2 && ev.y >= 1 && ev.y <= rows;
6024
- if (onScrollbar && s2.totalLines > rows) {
6025
- scrollbarDragRef.current = true;
6026
- dispatch({
6027
- type: "scrollTo",
6028
- offset: scrollOffsetForTrackRow(rows, s2.totalLines, ev.y - 1)
6029
- });
6030
- return;
6031
- }
6032
- }
6033
- if (ev.y > s2.viewportRows) {
6034
- if (s2.helpOpen) return dispatch({ type: "toggleHelp" });
6035
- if (s2.agentsMonitorOpen) return dispatch({ type: "toggleAgentsMonitor" });
6036
- if (s2.monitorOpen) return dispatch({ type: "toggleMonitor" });
6037
- if (s2.worktreeMonitorOpen) return dispatch({ type: "worktreeMonitorToggle" });
6038
- if (s2.autoPhase?.monitorOpen) return dispatch({ type: "autoPhaseMonitorToggle" });
6039
- }
6040
- const affordance = s2.scrollOffset > 0 && s2.pendingNewLines > 0 ? 1 : 0;
6041
- if (affordance && ev.y === s2.viewportRows + 1) {
6042
- return dispatch({ type: "scrollToBottom" });
6043
- }
6044
- if (s2.confirmQueue.length > 0) {
6045
- const node = confirmRef.current;
6046
- const head = s2.confirmQueue[0];
6047
- if (node && head) {
6048
- const { height } = measureElement(node);
6049
- const top = s2.viewportRows + affordance + preRowsRef.current + 1;
6050
- const buttonsRow = top + height - 2;
6051
- if (ev.y === buttonsRow) {
6052
- const contentX = ev.x - 1 - 2;
6053
- for (const seg of confirmButtonSegments(head.suggestedPattern)) {
6054
- if (contentX >= seg.start && contentX < seg.start + seg.len) {
6055
- confirmDecisionRef.current?.(seg.decision);
6056
- return;
6057
- }
6058
- }
6059
- }
6060
- }
6061
- return;
6062
- }
6063
- if (s2.rewindOverlay) {
6064
- const cps = s2.rewindOverlay.checkpoints;
6065
- const firstItemRow2 = s2.viewportRows + affordance + preRowsRef.current + 3 + 1;
6066
- const index2 = ev.y - firstItemRow2;
6067
- if (index2 < 0 || index2 >= cps.length) return;
6068
- if (index2 === s2.rewindOverlay.selected) {
6069
- handleRewindToRef.current?.(cps[index2].promptIndex);
6070
- } else {
6071
- dispatch({ type: "rewindOverlayMove", delta: index2 - s2.rewindOverlay.selected });
6072
- }
6073
- return;
6074
- }
6075
- if (s2.settingsPicker.open) {
6076
- const firstRow = s2.viewportRows + affordance + preRowsRef.current + 2 + 1;
6077
- const field = ev.y - firstRow;
6078
- if (field < 0 || field > 1) return;
6079
- if (field === s2.settingsPicker.field) {
6080
- dispatch({ type: "settingsValueChange", delta: 1 });
6081
- } else {
6082
- dispatch({ type: "settingsFieldSet", field });
6083
- }
6084
- return;
6085
- }
6086
- const picker = s2.modelPicker.open ? {
6087
- header: 2,
6088
- count: s2.modelPicker.step === "provider" ? s2.modelPicker.providerOptions.length : s2.modelPicker.modelOptions.length,
6089
- selected: s2.modelPicker.selected,
6090
- move: (delta) => dispatch({ type: "modelPickerMove", delta })
6091
- } : s2.autonomyPicker.open ? {
6092
- header: 2,
6093
- count: s2.autonomyPicker.options.length,
6094
- selected: s2.autonomyPicker.selected,
6095
- move: (delta) => dispatch({ type: "autonomyPickerMove", delta })
6096
- } : s2.slashPicker.open ? {
6097
- header: 1,
6098
- count: s2.slashPicker.matches.length,
6099
- selected: s2.slashPicker.selected,
6100
- move: (delta) => dispatch({ type: "slashPickerMove", delta })
6101
- } : s2.picker.open ? {
6102
- header: 1,
6103
- count: s2.picker.matches.length,
6104
- selected: s2.picker.selected,
6105
- move: (delta) => dispatch({ type: "pickerMove", delta })
6106
- } : null;
6107
- if (!picker || picker.count === 0) {
6108
- const inputDisabled = s2.status === "aborting" && !s2.steeringPending;
6109
- if (!inputDisabled) {
6110
- const cols = termCols || 80;
6111
- const inputTop = s2.viewportRows + affordance + liveStripRowsRef.current + 1;
6112
- const inputRows = layoutInputRows(INPUT_PROMPT, s2.buffer, s2.cursor, cols).length;
6113
- const rowIdx = ev.y - inputTop;
6114
- if (rowIdx >= 0 && rowIdx < inputRows) {
6115
- const next = inputIndexAtRowCol(INPUT_PROMPT, s2.buffer, cols, rowIdx, ev.x - 1);
6116
- return dispatch({ type: "setBuffer", buffer: s2.buffer, cursor: next });
6117
- }
6118
- }
6119
- if (!s2.helpOpen && !s2.agentsMonitorOpen && !s2.monitorOpen && !s2.worktreeMonitorOpen && !s2.autoPhase?.monitorOpen) {
6120
- const chip = statusChipRef.current;
6121
- const statusTop = s2.viewportRows + affordance + preRowsRef.current + 1;
6122
- const contentX = ev.x - 1;
6123
- if (ev.y === statusTop + 1) {
6124
- const span = statusBarModelSpan({
6125
- version: chip.version,
6126
- state: s2.status,
6127
- fleetRunning: chip.fleetRunning,
6128
- model: chip.model
6129
- });
6130
- if (contentX >= span.start && contentX < span.start + span.len) {
6131
- openModelPickerRef.current?.();
6132
- }
6133
- } else if (ev.y === statusTop + 2) {
6134
- const span = statusBarAutonomySpan({ yolo: chip.yolo, autonomy: chip.autonomy });
6135
- if (span && contentX >= span.start && contentX < span.start + span.len) {
6136
- openAutonomyPickerRef.current?.();
6137
- }
6138
- }
6139
- }
6140
- return;
6141
- }
6142
- const firstItemRow = s2.viewportRows + affordance + preRowsRef.current + picker.header + 1;
6143
- const index = ev.y - firstItemRow;
6144
- if (index < 0 || index >= picker.count) return;
6145
- if (index === picker.selected) {
6146
- handleKeyRef.current?.("", { ...EMPTY_KEY, return: true });
6147
- } else {
6148
- pendingClickConfirmRef.current = index;
6149
- picker.move(index - picker.selected);
6150
- }
6151
- },
6152
- // dispatch is stable (useReducer); refs are mutable — no reactive deps.
6153
- // termCols is stable (useState + resize effect).
6154
- [termCols]
6155
- );
6156
- useEffect(() => {
6157
- if (!subscribeMouse) return;
6158
- return subscribeMouse(handleMouse);
6159
- }, [subscribeMouse, handleMouse]);
6160
- useEffect(() => {
6161
- const target = pendingClickConfirmRef.current;
6162
- if (target === null) return;
6163
- const open = state.slashPicker.open || state.modelPicker.open || state.autonomyPicker.open || state.picker.open;
6164
- if (!open) {
6165
- pendingClickConfirmRef.current = null;
6166
- return;
6167
- }
6168
- const sel = state.slashPicker.open ? state.slashPicker.selected : state.modelPicker.open ? state.modelPicker.selected : state.autonomyPicker.open ? state.autonomyPicker.selected : state.picker.selected;
6169
- if (sel === target) {
6170
- pendingClickConfirmRef.current = null;
6171
- handleKeyRef.current?.("", { ...EMPTY_KEY, return: true });
5927
+ const lastEnterAtRef = useRef(0);
5928
+ const tokenPreviewsRef = useRef(/* @__PURE__ */ new Map());
5929
+ const projectName = React5.useMemo(() => {
5930
+ const base = path2.basename(projectRoot);
5931
+ return base && base !== path2.sep ? base : void 0;
5932
+ }, [projectRoot]);
5933
+ const streamingTextRef = useRef("");
5934
+ const pendingDeltaRef = useRef("");
5935
+ const flushTimerRef = useRef(null);
5936
+ const stateRef = useRef(state);
5937
+ stateRef.current = state;
5938
+ const draftRef = useRef({ buffer: state.buffer, cursor: state.cursor });
5939
+ draftRef.current = { buffer: state.buffer, cursor: state.cursor };
5940
+ const bottomRef = useRef(null);
5941
+ React5.useLayoutEffect(() => {
5942
+ if (!managedLive) return;
5943
+ const node = bottomRef.current;
5944
+ if (!node) return;
5945
+ const { height } = measureElement(node);
5946
+ const s2 = stateRef.current;
5947
+ const affordance = s2.scrollOffset > 0 && s2.pendingNewLines > 0 ? 1 : 0;
5948
+ const vp = Math.max(MIN_VIEWPORT, termRows - height - affordance - 1);
5949
+ if (vp !== s2.viewportRows) {
5950
+ dispatch({ type: "setViewportRows", rows: vp });
6172
5951
  }
6173
- }, [
6174
- state.slashPicker.open,
6175
- state.slashPicker.selected,
6176
- state.modelPicker.open,
6177
- state.modelPicker.selected,
6178
- state.autonomyPicker.open,
6179
- state.autonomyPicker.selected,
6180
- state.picker.open,
6181
- state.picker.selected
6182
- ]);
6183
- const handleRewindTo = React4.useCallback(
5952
+ }, [managedLive, termRows]);
5953
+ const handleKeyRef = useRef(null);
5954
+ const handleRewindTo = React5.useCallback(
6184
5955
  async (checkpointIndex) => {
6185
5956
  const sessionId = agent.ctx.session.id;
6186
5957
  if (!sessionId) return;
@@ -6199,13 +5970,13 @@ function App({
6199
5970
  dispatch({ type: "clearInput" });
6200
5971
  };
6201
5972
  const startedAtRef = useRef(Date.now());
6202
- const [nowTick, setNowTick] = React4.useState(Date.now());
5973
+ const [nowTick, setNowTick] = React5.useState(Date.now());
6203
5974
  useEffect(() => {
6204
5975
  const t = setInterval(() => setNowTick(Date.now()), 1e3);
6205
5976
  return () => clearInterval(t);
6206
5977
  }, []);
6207
5978
  const elapsedMs = nowTick - startedAtRef.current;
6208
- const [gitInfo, setGitInfo] = React4.useState(null);
5979
+ const [gitInfo, setGitInfo] = React5.useState(null);
6209
5980
  useEffect(() => {
6210
5981
  let cancelled = false;
6211
5982
  const refresh = () => {
@@ -6270,16 +6041,16 @@ function App({
6270
6041
  };
6271
6042
  return { leader: leaderEntry, ...state.fleet };
6272
6043
  }, [state.fleet, state.leader, state.status, provider, model, effectiveMaxContext]);
6273
- const STREAM_COLORS = ["cyan", "magenta", "yellow", "green", "blue"];
6044
+ const STREAM_COLORS2 = ["cyan", "magenta", "yellow", "green", "blue"];
6274
6045
  const labelsRef = useRef(/* @__PURE__ */ new Map());
6275
- const labelFor = (id, name) => {
6046
+ const labelFor2 = (id, name) => {
6276
6047
  const m = labelsRef.current;
6277
6048
  const existing = m.get(id);
6278
6049
  if (existing) return existing;
6279
6050
  const n = m.size + 1;
6280
6051
  const v = {
6281
6052
  label: name && name !== id ? name : `AGENT#${n}`,
6282
- color: STREAM_COLORS[(n - 1) % STREAM_COLORS.length]
6053
+ color: STREAM_COLORS2[(n - 1) % STREAM_COLORS2.length]
6283
6054
  };
6284
6055
  m.set(id, v);
6285
6056
  return v;
@@ -6562,60 +6333,6 @@ function App({
6562
6333
  slashRegistry.unregister("altscreen");
6563
6334
  };
6564
6335
  }, [slashRegistry]);
6565
- const mouseLiveRef = useRef(mouseLive);
6566
- mouseLiveRef.current = mouseLive;
6567
- useEffect(() => {
6568
- const MOUSE_ON_SEQ = "\x1B[?1000h\x1B[?1006h";
6569
- const MOUSE_OFF_SEQ = "\x1B[?1006l\x1B[?1000l";
6570
- const ALT_ON = "\x1B[?1049h";
6571
- const ALT_OFF = "\x1B[?1049l";
6572
- const cmd = {
6573
- name: "mouse",
6574
- description: "Toggle mouse mode (clickable menus + wheel-scroll chat). Needs launch with --mouse to enable.",
6575
- async run(args) {
6576
- const arg = args.trim().toLowerCase();
6577
- if (arg !== "on" && arg !== "off") {
6578
- return {
6579
- message: `Mouse mode is ${mouseLiveRef.current ? "ON" : "OFF"}. Usage: /mouse on|off`
6580
- };
6581
- }
6582
- if (arg === "on") {
6583
- if (!mouse) {
6584
- return {
6585
- message: "Mouse mode needs the --mouse launch flag (it rewires stdin so mouse bytes never reach the input). Restart with `wstack --tui --mouse`."
6586
- };
6587
- }
6588
- try {
6589
- writeOut(ALT_ON);
6590
- writeOut("\x1B[H");
6591
- writeOut(MOUSE_ON_SEQ);
6592
- } catch {
6593
- return { message: "Failed to enable mouse mode." };
6594
- }
6595
- setMouseLive(true);
6596
- setManagedLive(true);
6597
- return {
6598
- message: "Mouse mode ON. Click menu items, wheel-scroll the chat (PgUp/PgDn too). Native terminal copy/scroll are suspended until `/mouse off`."
6599
- };
6600
- }
6601
- try {
6602
- writeOut(MOUSE_OFF_SEQ);
6603
- writeOut(ALT_OFF);
6604
- } catch {
6605
- return { message: "Failed to disable mouse mode." };
6606
- }
6607
- setMouseLive(false);
6608
- setManagedLive(false);
6609
- return {
6610
- message: "Mouse mode OFF. Native terminal scroll/copy restored; chat history flows into native scrollback again."
6611
- };
6612
- }
6613
- };
6614
- slashRegistry.register(cmd);
6615
- return () => {
6616
- slashRegistry.unregister("mouse");
6617
- };
6618
- }, [slashRegistry, mouse]);
6619
6336
  useEffect(() => {
6620
6337
  const cmd = {
6621
6338
  name: "steer",
@@ -6722,16 +6439,12 @@ function App({
6722
6439
  slashRegistry.unregister("agents");
6723
6440
  };
6724
6441
  }, [slashRegistry]);
6725
- const openModelPicker = React4.useCallback(async () => {
6442
+ const openModelPicker = React5.useCallback(async () => {
6726
6443
  if (!getPickableProviders) return;
6727
6444
  const providers = await getPickableProviders();
6728
6445
  dispatch({ type: "modelPickerOpen", providers });
6729
6446
  }, [getPickableProviders]);
6730
- const openAutonomyPicker = React4.useCallback(() => {
6731
- if (!switchAutonomy) return;
6732
- dispatch({ type: "autonomyPickerOpen", options: AUTONOMY_OPTIONS });
6733
- }, [switchAutonomy]);
6734
- const openSettings = React4.useCallback(() => {
6447
+ const openSettings = React5.useCallback(() => {
6735
6448
  if (!getSettings) return;
6736
6449
  const s2 = getSettings();
6737
6450
  dispatch({ type: "settingsOpen", mode: s2.mode, delayMs: s2.delayMs });
@@ -6915,172 +6628,7 @@ function App({
6915
6628
  useEffect(() => {
6916
6629
  streamFleetRef.current = state.streamFleet;
6917
6630
  }, [state.streamFleet]);
6918
- useEffect(() => {
6919
- const offSpawned = events.on("subagent.spawned", (e) => {
6920
- const lbl = labelFor(e.subagentId, e.name);
6921
- dispatch({
6922
- type: "fleetSpawn",
6923
- id: e.subagentId,
6924
- name: e.name,
6925
- provider: e.provider,
6926
- model: e.model,
6927
- transcriptPath: e.transcriptPath
6928
- });
6929
- const where = e.provider && e.model ? `${e.provider}/${e.model}` : "spawned";
6930
- const desc = e.description ? ` \u2014 ${e.description.slice(0, 80)}` : "";
6931
- dispatch({
6932
- type: "addEntry",
6933
- entry: {
6934
- kind: "subagent",
6935
- agentLabel: lbl.label,
6936
- agentColor: lbl.color,
6937
- icon: "\u25B6",
6938
- text: `${where}${desc}`
6939
- }
6940
- });
6941
- });
6942
- const offStarted = events.on("subagent.task_started", (e) => {
6943
- const lbl = labelFor(e.subagentId);
6944
- dispatch({ type: "fleetStart", id: e.subagentId, taskId: e.taskId });
6945
- const desc = e.description ? ` \u2014 ${e.description.slice(0, 80)}` : "";
6946
- dispatch({
6947
- type: "addEntry",
6948
- entry: {
6949
- kind: "subagent",
6950
- agentLabel: lbl.label,
6951
- agentColor: lbl.color,
6952
- icon: "\u25CF",
6953
- text: `task started${desc}`
6954
- }
6955
- });
6956
- });
6957
- const offCompleted = events.on("subagent.task_completed", (e) => {
6958
- const lbl = labelFor(e.subagentId);
6959
- const errKind = e.error?.kind;
6960
- dispatch({
6961
- type: "fleetDone",
6962
- id: e.subagentId,
6963
- status: e.status,
6964
- iterations: e.iterations,
6965
- toolCalls: e.toolCalls,
6966
- failureReason: errKind
6967
- });
6968
- const icon = e.status === "success" ? "\u2713" : e.status === "timeout" ? "\u23F1" : e.status === "stopped" ? "\u2298" : "\u2717";
6969
- const errMsg = e.error?.message;
6970
- const errMsgTail = errMsg ? ` \u2014 ${errMsg.replace(/\s+/g, " ").slice(0, 100)}${errMsg.length > 100 ? "\u2026" : ""}` : "";
6971
- const errChip = errKind ? ` [${errKind}]` : "";
6972
- const secs = (e.durationMs / 1e3).toFixed(e.durationMs < 1e4 ? 1 : 0);
6973
- dispatch({
6974
- type: "addEntry",
6975
- entry: {
6976
- kind: "subagent",
6977
- agentLabel: lbl.label,
6978
- agentColor: lbl.color,
6979
- icon,
6980
- text: `${e.status} (${e.iterations} iter \xB7 ${e.toolCalls} tools \xB7 ${secs}s)${errChip}${errMsgTail}`
6981
- }
6982
- });
6983
- });
6984
- const offBudgetWarning = events.on("subagent.budget_warning", (e) => {
6985
- const lbl = labelFor(e.subagentId);
6986
- dispatch({
6987
- type: "fleetBudgetWarning",
6988
- id: e.subagentId,
6989
- kind: e.kind,
6990
- used: e.used,
6991
- limit: e.limit
6992
- });
6993
- const timeoutSuffix = e.kind === "timeout" ? " (subagent continues running)" : " \u2014 extending";
6994
- dispatch({
6995
- type: "addEntry",
6996
- entry: {
6997
- kind: "subagent",
6998
- agentLabel: lbl.label,
6999
- agentColor: lbl.color,
7000
- icon: "\u26A1",
7001
- text: `hitting ${e.kind} limit (${e.used}/${e.limit})${timeoutSuffix}`
7002
- }
7003
- });
7004
- });
7005
- const offBudgetExtended = events.on("subagent.budget_extended", (e) => {
7006
- const lbl = labelFor(e.subagentId);
7007
- dispatch({
7008
- type: "fleetBudgetExtended",
7009
- id: e.subagentId,
7010
- totalExtensions: e.totalExtensions
7011
- });
7012
- dispatch({
7013
- type: "addEntry",
7014
- entry: {
7015
- kind: "subagent",
7016
- agentLabel: lbl.label,
7017
- agentColor: lbl.color,
7018
- icon: "\u26A1",
7019
- text: `extended ${e.kind} \u2192 ${e.newLimit} (\xD7${e.totalExtensions})`
7020
- }
7021
- });
7022
- });
7023
- const offIterationSummary = events.on("subagent.iteration_summary", (e) => {
7024
- const lbl = labelFor(e.subagentId);
7025
- const costStr = e.costUsd > 0 ? ` \xB7 ${e.costUsd.toFixed(3)}` : "";
7026
- const toolStr = e.currentTool ? ` \xB7 doing ${e.currentTool}` : "";
7027
- const partial = e.partialText ? ` \xB7 "${e.partialText.slice(0, 60)}${e.partialText.length > 60 ? "\u2026" : ""}"` : "";
7028
- dispatch({
7029
- type: "addEntry",
7030
- entry: {
7031
- kind: "subagent",
7032
- agentLabel: lbl.label,
7033
- agentColor: lbl.color,
7034
- icon: "\u{1F4AC}",
7035
- text: `L${e.iteration} \xB7 ${e.toolCalls} tools${costStr}${toolStr}${partial}`
7036
- }
7037
- });
7038
- });
7039
- const offCtxPct = events.on("subagent.ctx_pct", (e) => {
7040
- dispatch({
7041
- type: "fleetCtxPct",
7042
- id: e.subagentId,
7043
- load: e.load,
7044
- tokens: e.tokens,
7045
- maxContext: e.maxContext
7046
- });
7047
- });
7048
- const offLeaderCtxPct = events.on("ctx.pct", (e) => {
7049
- setActiveMaxContext(e.maxContext);
7050
- dispatch({
7051
- type: "leaderCtxPct",
7052
- load: e.load,
7053
- tokens: e.tokens,
7054
- maxContext: e.maxContext
7055
- });
7056
- });
7057
- const offLeaderMaxContext = events.on("ctx.max_context", (e) => {
7058
- if (e.maxContext > 0) setActiveMaxContext(e.maxContext);
7059
- });
7060
- const offTool = events.on("subagent.tool_executed", (e) => {
7061
- dispatch({
7062
- type: "fleetTool",
7063
- id: e.subagentId,
7064
- name: e.name,
7065
- ok: e.ok,
7066
- durationMs: e.durationMs,
7067
- outputBytes: e.outputBytes
7068
- });
7069
- dispatch({ type: "fleetToolEnd", id: e.subagentId });
7070
- });
7071
- return () => {
7072
- offSpawned();
7073
- offStarted();
7074
- offCompleted();
7075
- offBudgetWarning();
7076
- offBudgetExtended();
7077
- offIterationSummary();
7078
- offCtxPct();
7079
- offLeaderCtxPct();
7080
- offLeaderMaxContext();
7081
- offTool();
7082
- };
7083
- }, [events, director]);
6631
+ useSubagentEvents(events, dispatch, setActiveMaxContext);
7084
6632
  useEffect(() => {
7085
6633
  const offCheckpoint = events.on("checkpoint.written", (e) => {
7086
6634
  dispatch({
@@ -7105,71 +6653,7 @@ function App({
7105
6653
  offRewound();
7106
6654
  };
7107
6655
  }, [events, onClearHistory]);
7108
- useEffect(() => {
7109
- const requestSummary = (request) => `${request.source}: ${request.question}`.slice(0, 80);
7110
- const addBrainEntry = (status, payload) => {
7111
- const p = payload;
7112
- const decision = p.decision.optionId ?? p.decision.text ?? p.decision.reason ?? p.decision.prompt ?? p.decision.type;
7113
- dispatch({
7114
- type: "brainStatus",
7115
- state: status,
7116
- source: p.request.source,
7117
- risk: p.request.risk,
7118
- summary: decision
7119
- });
7120
- if (status === "ask_human") {
7121
- dispatch({
7122
- type: "brainPromptSet",
7123
- prompt: {
7124
- requestId: p.request.id,
7125
- source: p.request.source,
7126
- risk: p.request.risk,
7127
- question: p.request.question,
7128
- context: p.request.context,
7129
- options: p.request.options
7130
- }
7131
- });
7132
- } else {
7133
- dispatch({ type: "brainPromptClear" });
7134
- }
7135
- dispatch({
7136
- type: "addEntry",
7137
- entry: {
7138
- kind: "brain",
7139
- status,
7140
- source: p.request.source,
7141
- risk: p.request.risk,
7142
- question: p.request.question,
7143
- decision,
7144
- rationale: p.decision.rationale
7145
- }
7146
- });
7147
- };
7148
- const offRequested = events.on("brain.decision_requested", ({ request }) => {
7149
- dispatch({
7150
- type: "brainStatus",
7151
- state: "deciding",
7152
- source: request.source,
7153
- risk: request.risk,
7154
- summary: requestSummary(request)
7155
- });
7156
- });
7157
- const offAnswered = events.on("brain.decision_answered", (payload) => {
7158
- addBrainEntry("answered", payload);
7159
- });
7160
- const offAskHuman = events.on("brain.decision_ask_human", (payload) => {
7161
- addBrainEntry("ask_human", payload);
7162
- });
7163
- const offDenied = events.on("brain.decision_denied", (payload) => {
7164
- addBrainEntry("denied", payload);
7165
- });
7166
- return () => {
7167
- offRequested();
7168
- offAnswered();
7169
- offAskHuman();
7170
- offDenied();
7171
- };
7172
- }, [events]);
6656
+ useBrainEvents(events, dispatch);
7173
6657
  useEffect(() => {
7174
6658
  if (!subscribeAutoPhase) return;
7175
6659
  const handler = (event, payload) => {
@@ -7414,7 +6898,7 @@ function App({
7414
6898
  for (const [id, text] of streamBuf) {
7415
6899
  const trimmed = text.trim();
7416
6900
  if (!trimmed) continue;
7417
- const lbl = labelFor(id);
6901
+ const lbl = labelFor2(id);
7418
6902
  enq({ type: "fleetMessage", id, text: trimmed });
7419
6903
  if (streamFleetRef.current) {
7420
6904
  enq({
@@ -7442,7 +6926,7 @@ function App({
7442
6926
  provider: meta?.provider,
7443
6927
  model: meta?.model
7444
6928
  });
7445
- labelFor(s2.id, meta?.name ?? s2.name);
6929
+ labelFor2(s2.id, meta?.name ?? s2.name);
7446
6930
  }
7447
6931
  dispatch({
7448
6932
  type: "fleetCost",
@@ -7473,7 +6957,7 @@ function App({
7473
6957
  provider: meta?.provider,
7474
6958
  model: meta?.model
7475
6959
  });
7476
- const lbl = labelFor(e.subagentId, meta?.name);
6960
+ const lbl = labelFor2(e.subagentId, meta?.name);
7477
6961
  if (streamFleetRef.current) {
7478
6962
  const where = meta?.provider && meta?.model ? `${meta.provider}/${meta.model}` : "spawned";
7479
6963
  dispatch2({
@@ -7558,7 +7042,7 @@ function App({
7558
7042
  });
7559
7043
  dispatch2({ type: "fleetToolEnd", id: e.subagentId });
7560
7044
  if (streamFleetRef.current && p?.name) {
7561
- const lbl = labelFor(e.subagentId);
7045
+ const lbl = labelFor2(e.subagentId);
7562
7046
  dispatch2({
7563
7047
  type: "addEntry",
7564
7048
  entry: {
@@ -7951,6 +7435,9 @@ function App({
7951
7435
  return;
7952
7436
  }
7953
7437
  if (isEnter) {
7438
+ const now = Date.now();
7439
+ if (now - lastEnterAtRef.current < 50) return;
7440
+ lastEnterAtRef.current = now;
7954
7441
  inputGateRef.current = true;
7955
7442
  try {
7956
7443
  if (state.modelPicker.step === "provider") {
@@ -8000,6 +7487,9 @@ function App({
8000
7487
  return;
8001
7488
  }
8002
7489
  if (isEnter) {
7490
+ const now = Date.now();
7491
+ if (now - lastEnterAtRef.current < 50) return;
7492
+ lastEnterAtRef.current = now;
8003
7493
  const opt = state.autonomyPicker.options[state.autonomyPicker.selected];
8004
7494
  if (!opt) return;
8005
7495
  const err = switchAutonomy?.(opt.mode);
@@ -8034,6 +7524,9 @@ function App({
8034
7524
  return;
8035
7525
  }
8036
7526
  if (isEnter) {
7527
+ const now = Date.now();
7528
+ if (now - lastEnterAtRef.current < 50) return;
7529
+ lastEnterAtRef.current = now;
8037
7530
  const { mode, delayMs } = state.settingsPicker;
8038
7531
  const err = await saveSettings?.({ mode, delayMs });
8039
7532
  if (err) {
@@ -8095,6 +7588,9 @@ function App({
8095
7588
  return;
8096
7589
  }
8097
7590
  if (isEnter) {
7591
+ const now = Date.now();
7592
+ if (now - lastEnterAtRef.current < 50) return;
7593
+ lastEnterAtRef.current = now;
8098
7594
  inputGateRef.current = true;
8099
7595
  try {
8100
7596
  await acceptPickerSelection();
@@ -8691,20 +8187,6 @@ User message:
8691
8187
  })();
8692
8188
  }, [initialAsk, initialGoal]);
8693
8189
  handleKeyRef.current = handleKey;
8694
- openModelPickerRef.current = () => {
8695
- void openModelPicker();
8696
- };
8697
- openAutonomyPickerRef.current = openAutonomyPicker;
8698
- handleRewindToRef.current = (promptIndex) => {
8699
- void handleRewindTo(promptIndex);
8700
- };
8701
- statusChipRef.current = {
8702
- version: appVersion,
8703
- model: `${liveProvider}/${liveModel}`,
8704
- fleetRunning: fleetCounts?.running ?? 0,
8705
- yolo: yoloLive,
8706
- autonomy: autonomyLive
8707
- };
8708
8190
  const inputHint = useMemo(() => {
8709
8191
  if (state.status !== "idle") return "";
8710
8192
  if (state.buffer.startsWith("/")) return "slash command \u2014 Enter to dispatch";
@@ -8734,20 +8216,18 @@ User message:
8734
8216
  ),
8735
8217
  affordanceShown ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u2193 ${state.pendingNewLines} new line${state.pendingNewLines === 1 ? "" : "s"} \u2014 PgDn or click to jump to bottom` }) : null,
8736
8218
  /* @__PURE__ */ jsxs(Box, { ref: managedLive ? bottomRef : void 0, flexDirection: "column", flexShrink: 0, children: [
8737
- /* @__PURE__ */ jsxs(Box, { ref: managedLive ? prePickerRef : void 0, flexDirection: "column", flexShrink: 0, children: [
8738
- /* @__PURE__ */ jsx(Box, { ref: managedLive ? liveStripRef : void 0, flexDirection: "column", flexShrink: 0, children: /* @__PURE__ */ jsx(LiveActivityStrip, { entries: state.fleet, nowTick }) }),
8739
- /* @__PURE__ */ jsx(
8740
- Input,
8741
- {
8742
- prompt: INPUT_PROMPT,
8743
- value: state.buffer,
8744
- cursor: state.cursor,
8745
- disabled: state.status === "aborting" && !state.steeringPending || state.confirmQueue.length > 0,
8746
- hint: inputHint,
8747
- onKey: handleKey
8748
- }
8749
- )
8750
- ] }),
8219
+ /* @__PURE__ */ jsx(LiveActivityStrip, { entries: state.fleet, nowTick }),
8220
+ /* @__PURE__ */ jsx(
8221
+ Input,
8222
+ {
8223
+ prompt: INPUT_PROMPT,
8224
+ value: state.buffer,
8225
+ cursor: state.cursor,
8226
+ disabled: state.status === "aborting" && !state.steeringPending || state.confirmQueue.length > 0,
8227
+ hint: inputHint,
8228
+ onKey: handleKey
8229
+ }
8230
+ ),
8751
8231
  state.picker.open ? /* @__PURE__ */ jsx(
8752
8232
  FilePicker,
8753
8233
  {
@@ -8823,8 +8303,7 @@ User message:
8823
8303
  head.resolve(decision);
8824
8304
  dispatch({ type: "confirmClose" });
8825
8305
  };
8826
- confirmDecisionRef.current = onDecision;
8827
- return /* @__PURE__ */ jsx(Box, { ref: confirmRef, flexDirection: "column", marginY: 1, flexShrink: 0, children: /* @__PURE__ */ jsx(
8306
+ return /* @__PURE__ */ jsx(
8828
8307
  ConfirmPrompt,
8829
8308
  {
8830
8309
  toolName: head.toolName,
@@ -8832,7 +8311,7 @@ User message:
8832
8311
  suggestedPattern: head.suggestedPattern,
8833
8312
  onDecision
8834
8313
  }
8835
- ) });
8314
+ );
8836
8315
  })(),
8837
8316
  /* @__PURE__ */ jsx(
8838
8317
  StatusBar,
@@ -8867,12 +8346,11 @@ User message:
8867
8346
  confirm: state.confirmQueue.length > 0,
8868
8347
  picker: state.picker.open || state.slashPicker.open || state.modelPicker.open || state.autonomyPicker.open || state.settingsPicker.open || !!state.rewindOverlay,
8869
8348
  monitor: state.agentsMonitorOpen || state.monitorOpen || state.worktreeMonitorOpen || !!state.autoPhase?.monitorOpen,
8870
- managed: managedLive,
8871
- mouse: mouseLive
8349
+ managed: managedLive
8872
8350
  }
8873
8351
  }
8874
8352
  ) : null,
8875
- state.helpOpen ? /* @__PURE__ */ jsx(HelpOverlay, { managed: managedLive, mouse: mouseLive }) : null,
8353
+ state.helpOpen ? /* @__PURE__ */ jsx(HelpOverlay, { managed: managedLive }) : null,
8876
8354
  state.agentsMonitorOpen ? /* @__PURE__ */ jsx(
8877
8355
  AgentsMonitor,
8878
8356
  {
@@ -8904,6 +8382,7 @@ User message:
8904
8382
  entries: state.fleet,
8905
8383
  totalCost: state.fleetCost,
8906
8384
  totalTokens: state.fleetTokens,
8385
+ maxConcurrent: state.fleetConcurrency,
8907
8386
  nowTick,
8908
8387
  collabSession: state.collabSession
8909
8388
  }
@@ -8960,48 +8439,6 @@ function fmtTok3(n) {
8960
8439
  return `${(n / 1e6).toFixed(1)}M`;
8961
8440
  }
8962
8441
 
8963
- // src/mouse.ts
8964
- var SGR_MOUSE_RE = /\x1b?\[<(\d+);(\d+);(\d+)([Mm])/g;
8965
- function stripSgrMouse(s2) {
8966
- return s2.replace(SGR_MOUSE_RE, "");
8967
- }
8968
- function parseSgrMouse(s2) {
8969
- const events = [];
8970
- for (const m of s2.matchAll(SGR_MOUSE_RE)) {
8971
- const cb = Number.parseInt(m[1] ?? "", 10);
8972
- const x = Number.parseInt(m[2] ?? "", 10);
8973
- const y = Number.parseInt(m[3] ?? "", 10);
8974
- const final = m[4];
8975
- if (!Number.isFinite(cb) || !Number.isFinite(x) || !Number.isFinite(y)) continue;
8976
- const isWheel = (cb & 64) !== 0;
8977
- const drag = (cb & 32) !== 0;
8978
- const low = cb & 3;
8979
- let button;
8980
- let type;
8981
- if (isWheel) {
8982
- type = "wheel";
8983
- button = low === 0 ? "wheelUp" : low === 1 ? "wheelDown" : "other";
8984
- } else if (final === "m") {
8985
- type = "release";
8986
- button = low === 0 ? "left" : low === 1 ? "middle" : low === 2 ? "right" : "other";
8987
- } else {
8988
- type = "press";
8989
- button = low === 0 ? "left" : low === 1 ? "middle" : low === 2 ? "right" : "other";
8990
- }
8991
- events.push({
8992
- type,
8993
- button,
8994
- x,
8995
- y,
8996
- shift: (cb & 4) !== 0,
8997
- alt: (cb & 8) !== 0,
8998
- ctrl: (cb & 16) !== 0,
8999
- drag
9000
- });
9001
- }
9002
- return events;
9003
- }
9004
-
9005
8442
  // src/terminal-title.ts
9006
8443
  var SPINNER = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
9007
8444
  var setTitle = (s2) => `\x1B]0;${s2}\x07`;
@@ -9071,8 +8508,6 @@ var BRACKETED_PASTE_OFF = "\x1B[?2004l";
9071
8508
  var ALT_SCREEN_ON = "\x1B[?1049h";
9072
8509
  var ALT_SCREEN_OFF = "\x1B[?1049l";
9073
8510
  var CURSOR_HOME = "\x1B[H";
9074
- var MOUSE_ON = "\x1B[?1000h\x1B[?1002h\x1B[?1006h";
9075
- var MOUSE_OFF = "\x1B[?1006l\x1B[?1002l\x1B[?1000l";
9076
8511
  async function runTui(opts) {
9077
8512
  const stdout = process.stdout;
9078
8513
  const stdin = process.stdin;
@@ -9082,107 +8517,13 @@ async function runTui(opts) {
9082
8517
  );
9083
8518
  return 2;
9084
8519
  }
9085
- const useMouse = opts.mouse === true;
9086
- const useAltScreen = opts.altScreen === true || useMouse;
8520
+ const useAltScreen = opts.altScreen === true;
9087
8521
  if (useAltScreen) {
9088
8522
  stdout.write(ALT_SCREEN_ON);
9089
8523
  stdout.write(CURSOR_HOME);
9090
8524
  }
9091
8525
  stdout.write(BRACKETED_PASTE_ON);
9092
- if (useMouse) {
9093
- stdout.write(MOUSE_ON);
9094
- }
9095
- const mouseListeners = /* @__PURE__ */ new Set();
9096
- let inkStdin = stdin;
9097
- let detachMouse = null;
9098
- if (useMouse) {
9099
- class KeyboardReadable extends Readable {
9100
- pendingChunks = [];
9101
- // eslint-disable-next-line no-useless-constructor
9102
- constructor() {
9103
- super({ encoding: "utf8", highWaterMark: 64 * 1024 });
9104
- }
9105
- _read(_size) {
9106
- this.flushPending();
9107
- }
9108
- flushPending() {
9109
- while (this.pendingChunks.length > 0) {
9110
- const chunk = this.pendingChunks[0];
9111
- const ok = this.push(chunk);
9112
- this.pendingChunks.shift();
9113
- if (!ok) {
9114
- break;
9115
- }
9116
- }
9117
- }
9118
- /** Called by the stdin data handler when keyboard bytes are available. */
9119
- doPush(chunk) {
9120
- if (chunk.length === 0) return;
9121
- const ok = this.push(chunk);
9122
- if (ok) {
9123
- if (this.pendingChunks.length > 0) {
9124
- this.flushPending();
9125
- }
9126
- } else {
9127
- if (this.pendingChunks.length >= 100) {
9128
- this.pendingChunks.shift();
9129
- }
9130
- this.pendingChunks.push(chunk);
9131
- }
9132
- }
9133
- /** Called on shutdown so the stream closes cleanly. */
9134
- doEnd() {
9135
- this.pendingChunks = [];
9136
- this.push(null);
9137
- }
9138
- }
9139
- const keyboardStream = new KeyboardReadable();
9140
- const p = keyboardStream;
9141
- p.isTTY = true;
9142
- p.setRawMode = (mode) => {
9143
- try {
9144
- stdin.setRawMode?.(mode);
9145
- } catch {
9146
- }
9147
- return p;
9148
- };
9149
- const realRef = stdin.ref?.bind(stdin);
9150
- const realUnref = stdin.unref?.bind(stdin);
9151
- p.ref = () => {
9152
- realRef?.();
9153
- return p;
9154
- };
9155
- p.unref = () => {
9156
- realUnref?.();
9157
- return p;
9158
- };
9159
- stdin.setEncoding("utf8");
9160
- const onData = (chunk) => {
9161
- const evs = parseSgrMouse(chunk);
9162
- for (const ev of evs) {
9163
- for (const fn of mouseListeners) {
9164
- try {
9165
- fn(ev);
9166
- } catch {
9167
- }
9168
- }
9169
- }
9170
- const rest = stripSgrMouse(chunk);
9171
- keyboardStream.doPush(rest);
9172
- };
9173
- stdin.on("data", onData);
9174
- detachMouse = () => {
9175
- stdin.off("data", onData);
9176
- keyboardStream.doEnd();
9177
- };
9178
- inkStdin = p;
9179
- }
9180
- const subscribeMouse = useMouse ? (fn) => {
9181
- mouseListeners.add(fn);
9182
- return () => {
9183
- mouseListeners.delete(fn);
9184
- };
9185
- } : void 0;
8526
+ const inkStdin = stdin;
9186
8527
  const stopTitle = startTerminalTitle({ stdout, events: opts.events, model: opts.model });
9187
8528
  const swallowSignals = ["SIGTSTP", "SIGQUIT", "SIGTTIN", "SIGTTOU"];
9188
8529
  const swallow = () => {
@@ -9201,15 +8542,8 @@ async function runTui(opts) {
9201
8542
  stopTitle();
9202
8543
  } catch {
9203
8544
  }
9204
- try {
9205
- detachMouse?.();
9206
- } catch {
9207
- }
9208
8545
  try {
9209
8546
  stdout.write(BRACKETED_PASTE_OFF);
9210
- if (useMouse) {
9211
- stdout.write(MOUSE_OFF);
9212
- }
9213
8547
  if (useAltScreen) {
9214
8548
  stdout.write(ALT_SCREEN_OFF);
9215
8549
  }
@@ -9250,7 +8584,7 @@ async function runTui(opts) {
9250
8584
  let instance;
9251
8585
  try {
9252
8586
  instance = render(
9253
- React4.createElement(App, {
8587
+ React5.createElement(App, {
9254
8588
  agent: opts.agent,
9255
8589
  slashRegistry: opts.slashRegistry,
9256
8590
  attachments: opts.attachments,
@@ -9294,11 +8628,8 @@ async function runTui(opts) {
9294
8628
  getSettings: opts.getSettings,
9295
8629
  saveSettings: opts.saveSettings,
9296
8630
  predictNext: opts.predictNext,
9297
- mouse: useMouse,
9298
- subscribeMouse,
9299
8631
  // Managed viewport (in-app scroll + collapsibility) follows
9300
- // alt-screen: it owns the screen, so there's no native-scrollback
9301
- // leak. Decoupled from mouse so --alt-screen alone gets it.
8632
+ // alt-screen: it owns the screen, so there's no native-scrollback leak.
9302
8633
  managed: useAltScreen
9303
8634
  }),
9304
8635
  { exitOnCtrlC: false, stdin: inkStdin }