@wrongstack/tui 0.66.13 → 0.68.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import { Readable } from 'stream';
2
2
  import { writeErr, InputBuilder, DefaultSessionRewinder, writeOut, formatTodosList, buildGoalPreamble, buildChildEnv } from '@wrongstack/core';
3
3
  export { buildGoalPreamble } from '@wrongstack/core';
4
4
  import { Box, Text, render, useApp, useStdout, measureElement, Static, useInput, useStdin } from 'ink';
5
- import React4, { useState, useEffect, useReducer, useRef, useMemo, useCallback, useLayoutEffect } from 'react';
5
+ import React6, { useState, useEffect, useReducer, useRef, useMemo, useCallback, useLayoutEffect } from 'react';
6
6
  import * as fs2 from 'fs/promises';
7
7
  import * as path2 from 'path';
8
8
  import { routeImagesForModel } from '@wrongstack/runtime/vision';
@@ -1180,7 +1180,7 @@ function ConfirmPrompt({
1180
1180
  suggestedPattern,
1181
1181
  onDecision
1182
1182
  }) {
1183
- React4.useEffect(() => {
1183
+ React6.useEffect(() => {
1184
1184
  writeOut("\x07");
1185
1185
  }, []);
1186
1186
  useInput((input2, _key) => {
@@ -1209,7 +1209,7 @@ function ConfirmPrompt({
1209
1209
  inputSummary ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: inputSummary }) : null,
1210
1210
  showDiff && diff ? /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginY: 1, children: renderDiff(diff) }) : null,
1211
1211
  /* @__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: [
1212
+ /* @__PURE__ */ jsx(Box, { flexDirection: "row", children: /* @__PURE__ */ jsx(Text, { children: buttonLabels(suggestedPattern).map((l) => /* @__PURE__ */ jsxs(React6.Fragment, { children: [
1213
1213
  /* @__PURE__ */ jsx(Text, { bold: true, color: BUTTON_COLOR[l.decision], children: l.bracket }),
1214
1214
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: l.rest })
1215
1215
  ] }, l.decision)) }) })
@@ -2218,705 +2218,294 @@ function MarkdownView({
2218
2218
  }
2219
2219
  return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: rows });
2220
2220
  }
2221
- var MESSAGE_PANEL_CHROME_WIDTH = 2;
2222
- function assistantContentWidth(termWidth) {
2223
- return Math.max(20, termWidth - MESSAGE_PANEL_CHROME_WIDTH);
2221
+ function shortenPath(p, max) {
2222
+ if (p.length <= max) return p;
2223
+ return `\u2026${p.slice(p.length - (max - 1))}`;
2224
2224
  }
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
- ] });
2225
+ function fmtTok2(n) {
2226
+ if (!Number.isFinite(n) || n <= 0) return "0";
2227
+ if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
2228
+ if (n >= 1e3) return `${(n / 1e3).toFixed(n >= 1e4 ? 0 : 1)}k`;
2229
+ return String(n);
2256
2230
  }
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
- );
2231
+ function fmtDuration(ms) {
2232
+ if (ms < 1e3) return `${ms}ms`;
2233
+ if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
2234
+ const totalSec = Math.floor(ms / 1e3);
2235
+ return `${Math.floor(totalSec / 60)}m${totalSec % 60}s`;
2278
2236
  }
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";
2237
+ function fmtBytes(n) {
2238
+ if (n < 1024) return `${n}B`;
2239
+ if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)}KB`;
2240
+ return `${(n / (1024 * 1024)).toFixed(1)}MB`;
2241
+ }
2242
+ function truncMid(s2, max) {
2243
+ if (s2.length <= max) return s2;
2244
+ return `${s2.slice(0, max - 1)}\u2026`;
2245
+ }
2246
+ function stringOf(v) {
2247
+ return typeof v === "string" && v.length > 0 ? v : void 0;
2248
+ }
2249
+ function numOf(v) {
2250
+ return typeof v === "number" && Number.isFinite(v) ? v : void 0;
2251
+ }
2252
+ function tryParseJson(s2) {
2253
+ const t = s2.trimStart();
2254
+ if (!t.startsWith("{") && !t.startsWith("[")) return void 0;
2255
+ try {
2256
+ return JSON.parse(s2);
2257
+ } catch {
2258
+ return void 0;
2259
+ }
2260
+ }
2261
+ function scanNumberedRange(text) {
2262
+ let first;
2263
+ let last;
2264
+ let count = 0;
2265
+ for (const line of text.split("\n")) {
2266
+ const m = line.match(/^\s*(\d+)→/);
2267
+ if (m?.[1]) {
2268
+ const n = Number.parseInt(m[1], 10);
2269
+ if (Number.isFinite(n)) {
2270
+ if (first === void 0) first = n;
2271
+ last = n;
2272
+ count++;
2303
2273
  }
2304
- continue;
2305
2274
  }
2306
- if (code !== null) code.push(line);
2307
- else prose.push(line);
2308
2275
  }
2309
- if (code !== null) segs.push({ type: "code", text: code.join("\n"), lang });
2310
- flushProse();
2311
- return segs;
2276
+ return { first, last, count };
2312
2277
  }
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
- );
2278
+ function countLines(text) {
2279
+ if (!text) return 0;
2280
+ return text.replace(/\n$/, "").split("\n").length;
2355
2281
  }
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
- ) });
2282
+ function firstNonEmpty(text) {
2283
+ if (!text) return void 0;
2284
+ const line = text.split("\n").find((l) => l.trim());
2285
+ return line ? line.replace(/\s+/g, " ").trim() : void 0;
2372
2286
  }
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)}`;
2287
+ function formatMatchHit(hit) {
2288
+ if (typeof hit === "string") return truncMid(hit, 70);
2289
+ if (hit && typeof hit === "object") {
2290
+ const o = hit;
2291
+ const file = stringOf(o["file"]) ?? stringOf(o["path"]);
2292
+ const line = numOf(o["line"]) ?? numOf(o["lineNumber"]);
2293
+ const snippet2 = stringOf(o["text"]) ?? stringOf(o["match"]) ?? stringOf(o["preview"]);
2294
+ if (file) {
2295
+ const head = line !== void 0 ? `${shortenPath(file, 40)}:${line}` : shortenPath(file, 50);
2296
+ return snippet2 ? `${head} ${truncMid(snippet2.replace(/\s+/g, " "), 40)}` : head;
2297
+ }
2298
+ if (snippet2) return truncMid(snippet2, 70);
2415
2299
  }
2416
- return `\u2026 ${text.slice(cut)}`;
2300
+ return void 0;
2417
2301
  }
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;
2302
+ var ARG_BUDGET = 60;
2303
+ function formatToolArgs(toolName, input) {
2304
+ if (!input || typeof input !== "object") return "";
2305
+ const obj = input;
2306
+ switch (toolName) {
2307
+ case "read":
2308
+ case "write":
2309
+ case "edit":
2310
+ case "patch":
2311
+ case "document":
2312
+ case "list_dir":
2313
+ case "ls":
2314
+ case "tree": {
2315
+ const p = stringOf(obj["path"]) ?? stringOf(obj["file"]);
2316
+ return p ? shortenPath(p, ARG_BUDGET) : "";
2425
2317
  }
2426
- }
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);
2318
+ case "grep":
2319
+ case "search":
2320
+ case "replace": {
2321
+ const pat = stringOf(obj["pattern"]) ?? stringOf(obj["query"]);
2322
+ const scope = stringOf(obj["path"]) ?? stringOf(obj["glob"]);
2323
+ const head = pat ? `"${truncMid(pat, 36)}"` : "";
2324
+ const tail = scope ? ` in ${shortenPath(scope, 28)}` : "";
2325
+ return `${head}${tail}` || (stringOf(obj["command"]) ?? "");
2326
+ }
2327
+ case "glob": {
2328
+ const pat = stringOf(obj["pattern"]) ?? stringOf(obj["glob"]);
2329
+ return pat ? `"${truncMid(pat, ARG_BUDGET - 2)}"` : "";
2330
+ }
2331
+ case "bash":
2332
+ case "shell":
2333
+ case "exec":
2334
+ case "install":
2335
+ case "git": {
2336
+ const cmd = stringOf(obj["command"]) ?? stringOf(obj["args"]);
2337
+ return cmd ? truncMid(cmd, ARG_BUDGET) : "";
2338
+ }
2339
+ case "diff": {
2340
+ const files = Array.isArray(obj["files"]) ? obj["files"] : void 0;
2341
+ if (files && files.length > 0) {
2342
+ const head = stringOf(files[0]) ?? "";
2343
+ const rest = files.length > 1 ? ` (+${files.length - 1})` : "";
2344
+ return head ? `${shortenPath(head, 50)}${rest}` : "";
2436
2345
  }
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);
2346
+ const mode = stringOf(obj["mode"]);
2347
+ return mode ? `mode: ${mode}` : "";
2348
+ }
2349
+ case "fetch":
2350
+ case "webfetch":
2351
+ case "web_fetch": {
2352
+ const u = stringOf(obj["url"]);
2353
+ return u ? truncMid(u, ARG_BUDGET) : "";
2354
+ }
2355
+ case "todo": {
2356
+ const list = obj["todos"];
2357
+ if (Array.isArray(list)) return `${list.length} item${list.length === 1 ? "" : "s"}`;
2358
+ return "";
2359
+ }
2360
+ case "lint":
2361
+ case "format":
2362
+ case "typecheck":
2363
+ case "test":
2364
+ case "audit":
2365
+ case "outdated": {
2366
+ const files = obj["files"];
2367
+ if (Array.isArray(files) && files.length > 0) {
2368
+ const first = stringOf(files[0]);
2369
+ const more = files.length > 1 ? ` (+${files.length - 1})` : "";
2370
+ return first ? `${shortenPath(first, 50)}${more}` : `${files.length} files`;
2441
2371
  }
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
- ] });
2450
- }
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" };
2372
+ const filter = stringOf(obj["filter"]) ?? stringOf(obj["pattern"]);
2373
+ return filter ? `"${truncMid(filter, ARG_BUDGET - 2)}"` : "";
2374
+ }
2375
+ case "json": {
2376
+ const file = stringOf(obj["file"]);
2377
+ const q = stringOf(obj["query"]);
2378
+ if (file) return q ? `${shortenPath(file, 40)} ${q}` : shortenPath(file, ARG_BUDGET);
2379
+ return q ? truncMid(q, ARG_BUDGET) : "";
2380
+ }
2381
+ case "scaffold": {
2382
+ const tmpl = stringOf(obj["template"]) ?? stringOf(obj["type"]);
2383
+ const name = stringOf(obj["name"]);
2384
+ if (tmpl && name) return `${tmpl} \u2192 ${truncMid(name, ARG_BUDGET - tmpl.length - 4)}`;
2385
+ return name ?? tmpl ?? "";
2386
+ }
2387
+ case "remember":
2388
+ case "forget":
2389
+ case "memory": {
2390
+ const key = stringOf(obj["key"]) ?? stringOf(obj["name"]);
2391
+ return key ? truncMid(key, ARG_BUDGET) : "";
2392
+ }
2393
+ case "mode": {
2394
+ const m = stringOf(obj["mode"]) ?? stringOf(obj["name"]);
2395
+ return m ? truncMid(m, ARG_BUDGET) : "";
2396
+ }
2397
+ case "logs": {
2398
+ const target = stringOf(obj["target"]) ?? stringOf(obj["service"]) ?? stringOf(obj["path"]);
2399
+ return target ? truncMid(target, ARG_BUDGET) : "";
2400
+ }
2401
+ }
2402
+ for (const key of ["path", "file", "url", "name", "query", "pattern", "command"]) {
2403
+ const v = stringOf(obj[key]);
2404
+ if (v) return truncMid(v, ARG_BUDGET);
2405
+ }
2406
+ try {
2407
+ return truncMid(JSON.stringify(obj), ARG_BUDGET);
2408
+ } catch {
2409
+ return "";
2461
2410
  }
2462
2411
  }
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";
2412
+ var OUT_BUDGET = 80;
2413
+ var GENERIC_BUDGET = 240;
2414
+ function summarizeJsonObject(obj) {
2415
+ const keys = Object.keys(obj);
2416
+ if (keys.length === 0) return null;
2417
+ const priority = [
2418
+ "ok",
2419
+ "status",
2420
+ "timedOut",
2421
+ "stopReason",
2422
+ "reason",
2423
+ "error",
2424
+ "message",
2425
+ "result",
2426
+ "summary",
2427
+ "iterations",
2428
+ "toolCalls",
2429
+ "durationMs",
2430
+ "subagentId",
2431
+ "taskId"
2432
+ ];
2433
+ const ordered = [
2434
+ ...priority.filter((k) => keys.includes(k)),
2435
+ ...keys.filter((k) => !priority.includes(k))
2436
+ ];
2437
+ const parts = [];
2438
+ let used = 0;
2439
+ for (const key of ordered) {
2440
+ const v = obj[key];
2441
+ if (v === void 0 || v === null) continue;
2442
+ 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}`;
2443
+ if (used + rendered.length > GENERIC_BUDGET) {
2444
+ parts.push("\u2026");
2445
+ break;
2446
+ }
2447
+ parts.push(rendered);
2448
+ used += rendered.length + 3;
2473
2449
  }
2450
+ return parts.length > 0 ? parts.join(" \xB7 ") : null;
2474
2451
  }
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
- );
2452
+ function formatToolOutput(toolName, output, ok, _outputBytes, outputLines) {
2453
+ if (!output) return ok ? [] : ["failed"];
2454
+ const text = output.trim();
2455
+ if (!text) return ok ? [] : ["failed"];
2456
+ const json = tryParseJson(text);
2457
+ if (toolName === "write" && json && typeof json === "object") {
2458
+ const o = json;
2459
+ const bytes = numOf(o["bytes_written"]) ?? numOf(o["bytes"]);
2460
+ const created = o["created"] === true;
2461
+ const tag = created ? "created" : "updated";
2462
+ return bytes !== void 0 ? [`${tag} \xB7 ${fmtBytes(bytes)}`] : [tag];
2463
+ }
2464
+ if (toolName === "edit" && json && typeof json === "object") {
2465
+ const o = json;
2466
+ const reps = numOf(o["replacements"]);
2467
+ if (reps !== void 0) return [`${reps} replacement${reps === 1 ? "" : "s"}`];
2468
+ }
2469
+ if (toolName === "patch" && json && typeof json === "object") {
2470
+ const o = json;
2471
+ const applied = numOf(o["applied"]);
2472
+ const rejected = numOf(o["rejected"]);
2473
+ const files = Array.isArray(o["files"]) ? o["files"] : void 0;
2474
+ const lines = [];
2475
+ if (applied !== void 0 || rejected !== void 0) {
2476
+ const parts = [];
2477
+ if (applied !== void 0) parts.push(`${applied} applied`);
2478
+ if (rejected !== void 0 && rejected > 0) parts.push(`${rejected} rejected`);
2479
+ lines.push(parts.join(" \xB7 "));
2523
2480
  }
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
- ] });
2576
- }
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)}"` : "";
2768
- }
2769
- case "bash":
2770
- case "shell":
2771
- case "exec":
2772
- case "install":
2773
- case "git": {
2774
- const cmd = stringOf(obj["command"]) ?? stringOf(obj["args"]);
2775
- return cmd ? truncMid(cmd, ARG_BUDGET) : "";
2776
- }
2777
- case "diff": {
2778
- const files = Array.isArray(obj["files"]) ? obj["files"] : void 0;
2779
- if (files && files.length > 0) {
2780
- const head = stringOf(files[0]) ?? "";
2781
- const rest = files.length > 1 ? ` (+${files.length - 1})` : "";
2782
- return head ? `${shortenPath(head, 50)}${rest}` : "";
2783
- }
2784
- const mode = stringOf(obj["mode"]);
2785
- return mode ? `mode: ${mode}` : "";
2786
- }
2787
- case "fetch":
2788
- case "webfetch":
2789
- case "web_fetch": {
2790
- const u = stringOf(obj["url"]);
2791
- return u ? truncMid(u, ARG_BUDGET) : "";
2792
- }
2793
- case "todo": {
2794
- const list = obj["todos"];
2795
- if (Array.isArray(list)) return `${list.length} item${list.length === 1 ? "" : "s"}`;
2796
- return "";
2797
- }
2798
- case "lint":
2799
- case "format":
2800
- case "typecheck":
2801
- case "test":
2802
- case "audit":
2803
- case "outdated": {
2804
- const files = obj["files"];
2805
- if (Array.isArray(files) && files.length > 0) {
2806
- const first = stringOf(files[0]);
2807
- const more = files.length > 1 ? ` (+${files.length - 1})` : "";
2808
- return first ? `${shortenPath(first, 50)}${more}` : `${files.length} files`;
2809
- }
2810
- const filter = stringOf(obj["filter"]) ?? stringOf(obj["pattern"]);
2811
- return filter ? `"${truncMid(filter, ARG_BUDGET - 2)}"` : "";
2812
- }
2813
- case "json": {
2814
- const file = stringOf(obj["file"]);
2815
- const q = stringOf(obj["query"]);
2816
- if (file) return q ? `${shortenPath(file, 40)} ${q}` : shortenPath(file, ARG_BUDGET);
2817
- return q ? truncMid(q, ARG_BUDGET) : "";
2818
- }
2819
- case "scaffold": {
2820
- const tmpl = stringOf(obj["template"]) ?? stringOf(obj["type"]);
2821
- const name = stringOf(obj["name"]);
2822
- if (tmpl && name) return `${tmpl} \u2192 ${truncMid(name, ARG_BUDGET - tmpl.length - 4)}`;
2823
- return name ?? tmpl ?? "";
2824
- }
2825
- case "remember":
2826
- case "forget":
2827
- case "memory": {
2828
- const key = stringOf(obj["key"]) ?? stringOf(obj["name"]);
2829
- return key ? truncMid(key, ARG_BUDGET) : "";
2830
- }
2831
- case "mode": {
2832
- const m = stringOf(obj["mode"]) ?? stringOf(obj["name"]);
2833
- return m ? truncMid(m, ARG_BUDGET) : "";
2834
- }
2835
- case "logs": {
2836
- const target = stringOf(obj["target"]) ?? stringOf(obj["service"]) ?? stringOf(obj["path"]);
2837
- return target ? truncMid(target, ARG_BUDGET) : "";
2838
- }
2839
- }
2840
- for (const key of ["path", "file", "url", "name", "query", "pattern", "command"]) {
2841
- const v = stringOf(obj[key]);
2842
- if (v) return truncMid(v, ARG_BUDGET);
2843
- }
2844
- try {
2845
- return truncMid(JSON.stringify(obj), ARG_BUDGET);
2846
- } catch {
2847
- return "";
2848
- }
2849
- }
2850
- function formatToolOutput(toolName, output, ok, _outputBytes, outputLines) {
2851
- if (!output) return ok ? [] : ["failed"];
2852
- const text = output.trim();
2853
- if (!text) return ok ? [] : ["failed"];
2854
- 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
- }
2864
- }
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
- }
2871
- }
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;
2481
+ if (files && files.length > 0) {
2482
+ const first = stringOf(files[0]) ?? "";
2483
+ const more = files.length > 1 ? ` (+${files.length - 1})` : "";
2484
+ lines.push(`${shortenPath(first, 60)}${more}`);
2891
2485
  }
2486
+ if (lines.length > 0) return lines;
2892
2487
  }
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
- }
2488
+ if (toolName === "replace" && json && typeof json === "object") {
2489
+ const o = json;
2490
+ const files = numOf(o["files_modified"]);
2491
+ const reps = numOf(o["total_replacements"]);
2492
+ if (files !== void 0 && reps !== void 0) {
2493
+ return [`${reps} replacement${reps === 1 ? "" : "s"} in ${files} file${files === 1 ? "" : "s"}`];
2903
2494
  }
2904
2495
  }
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 ")] : [];
2919
- }
2496
+ if (toolName === "diff" && json && typeof json === "object") {
2497
+ const o = json;
2498
+ const diffFiles = Array.isArray(o["files"]) ? o["files"] : void 0;
2499
+ const truncated = o["truncated"] === true;
2500
+ const mode = stringOf(o["mode"]);
2501
+ const diff = stringOf(o["diff"]);
2502
+ if (!diff) return [diffFiles && diffFiles.length === 0 ? "no changes" : "empty diff"];
2503
+ const head = [];
2504
+ if (mode) head.push(mode);
2505
+ if (diffFiles && diffFiles.length > 0)
2506
+ head.push(`${diffFiles.length} file${diffFiles.length === 1 ? "" : "s"}`);
2507
+ if (truncated) head.push("truncated");
2508
+ return head.length > 0 ? [head.join(" \xB7 ")] : [];
2920
2509
  }
2921
2510
  if (toolName === "read") {
2922
2511
  if (outputLines !== void 0) return [];
@@ -2927,9 +2516,7 @@ function formatToolOutput(toolName, output, ok, _outputBytes, outputLines) {
2927
2516
  }
2928
2517
  const range = scanNumberedRange(text);
2929
2518
  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
- }
2519
+ if (range.first === range.last) return [`L${range.first} \xB7 ${fmtBytes(text.length)}`];
2933
2520
  const contiguous = range.count === range.last - range.first + 1;
2934
2521
  const head = `L${range.first}\u2013${range.last}`;
2935
2522
  const tail = contiguous ? `${range.count} line${range.count === 1 ? "" : "s"}` : `${range.count} lines (gaps)`;
@@ -2978,9 +2565,7 @@ function formatToolOutput(toolName, output, ok, _outputBytes, outputLines) {
2978
2565
  if (lines.length > 0) return lines;
2979
2566
  }
2980
2567
  }
2981
- if (toolName === "todo") {
2982
- return ok ? [] : [text.split("\n")[0] ?? ""];
2983
- }
2568
+ if (toolName === "todo") return ok ? [] : [text.split("\n")[0] ?? ""];
2984
2569
  if (toolName === "fetch" || toolName === "webfetch" || toolName === "web_fetch") {
2985
2570
  if (json && typeof json === "object") {
2986
2571
  const o = json;
@@ -3000,200 +2585,174 @@ function formatToolOutput(toolName, output, ok, _outputBytes, outputLines) {
3000
2585
  if (lines.length > 0) return lines;
3001
2586
  }
3002
2587
  }
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
- }
2588
+ if (toolName === "git" && json && typeof json === "object") {
2589
+ const o = json;
2590
+ const exit = numOf(o["exitCode"]) ?? numOf(o["exit_code"]);
2591
+ const stdout = stringOf(o["stdout"]) ?? "";
2592
+ const stderr = stringOf(o["stderr"]) ?? "";
2593
+ const head = [];
2594
+ if (exit !== void 0) head.push(`exit ${exit}`);
2595
+ const stdoutLines = countLines(stdout);
2596
+ const stderrLines = countLines(stderr);
2597
+ const lparts = [];
2598
+ if (stdoutLines > 0) lparts.push(`${stdoutLines} out`);
2599
+ if (stderrLines > 0) lparts.push(`${stderrLines} err`);
2600
+ if (lparts.length > 0) head.push(lparts.join(" \xB7 "));
2601
+ const lines = [];
2602
+ if (head.length > 0) lines.push(head.join(" \xB7 "));
2603
+ const preview = firstNonEmpty(stdout) ?? firstNonEmpty(stderr);
2604
+ if (preview) lines.push(`"${truncMid(preview, 70)}"`);
2605
+ if (lines.length > 0) return lines;
3023
2606
  }
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
- }
2607
+ if (toolName === "lint" && json && typeof json === "object") {
2608
+ const o = json;
2609
+ const linter = stringOf(o["linter"]);
2610
+ const files = numOf(o["files_checked"]);
2611
+ const errors = numOf(o["errors"]) ?? 0;
2612
+ const warnings = numOf(o["warnings"]) ?? 0;
2613
+ const fix = o["fix_applied"] === true;
2614
+ const head = [];
2615
+ if (linter && linter !== "none") head.push(linter);
2616
+ head.push(`${errors} error${errors === 1 ? "" : "s"}`);
2617
+ head.push(`${warnings} warning${warnings === 1 ? "" : "s"}`);
2618
+ if (files !== void 0) head.push(`${files} file${files === 1 ? "" : "s"}`);
2619
+ if (fix) head.push("fixed");
2620
+ return [head.join(" \xB7 ")];
3040
2621
  }
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
- }
2622
+ if (toolName === "format" && json && typeof json === "object") {
2623
+ const o = json;
2624
+ const fixer = stringOf(o["fixer"]);
2625
+ const checked = numOf(o["files_checked"]);
2626
+ const changed = numOf(o["files_changed"]);
2627
+ const head = [];
2628
+ if (fixer && fixer !== "none") head.push(fixer);
2629
+ if (changed !== void 0 && checked !== void 0) {
2630
+ head.push(`${changed}/${checked} changed`);
2631
+ } else if (changed !== void 0) {
2632
+ head.push(`${changed} changed`);
2633
+ }
2634
+ return head.length > 0 ? [head.join(" \xB7 ")] : [];
3056
2635
  }
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
- }
2636
+ if (toolName === "typecheck" && json && typeof json === "object") {
2637
+ const o = json;
2638
+ const exit = numOf(o["exit_code"]) ?? numOf(o["exitCode"]);
2639
+ const errors = numOf(o["errors"]);
2640
+ const head = [];
2641
+ if (errors !== void 0) head.push(`${errors} error${errors === 1 ? "" : "s"}`);
2642
+ if (exit !== void 0) head.push(`exit ${exit}`);
2643
+ const stdout = stringOf(o["output"]) ?? stringOf(o["stdout"]) ?? "";
2644
+ const lines = [];
2645
+ if (head.length > 0) lines.push(head.join(" \xB7 "));
2646
+ const preview = firstNonEmpty(stdout);
2647
+ if (preview && (!errors || errors > 0)) lines.push(`"${truncMid(preview, 70)}"`);
2648
+ if (lines.length > 0) return lines;
3072
2649
  }
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
- }
2650
+ if (toolName === "test" && json && typeof json === "object") {
2651
+ const o = json;
2652
+ const runner = stringOf(o["runner"]);
2653
+ const total = numOf(o["tests_run"]) ?? 0;
2654
+ const passed = numOf(o["passed"]) ?? 0;
2655
+ const failed = numOf(o["failed"]) ?? 0;
2656
+ const duration = numOf(o["duration_ms"]);
2657
+ const head = [];
2658
+ if (runner && runner !== "none") head.push(runner);
2659
+ head.push(`${passed}/${total} passed`);
2660
+ if (failed > 0) head.push(`${failed} failed`);
2661
+ if (duration !== void 0) head.push(fmtDuration(duration));
2662
+ return [head.join(" \xB7 ")];
3088
2663
  }
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
- }
2664
+ if (toolName === "audit" && json && typeof json === "object") {
2665
+ const o = json;
2666
+ const total = numOf(o["total"]) ?? 0;
2667
+ const summary = stringOf(o["summary"]);
2668
+ if (total === 0) return ["no vulnerabilities"];
2669
+ const head = `${total} vulnerabilit${total === 1 ? "y" : "ies"}`;
2670
+ return summary && summary.toLowerCase() !== head.toLowerCase() ? [head, truncMid(summary, OUT_BUDGET)] : [head];
3098
2671
  }
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
- }
2672
+ if (toolName === "outdated" && json && typeof json === "object") {
2673
+ const o = json;
2674
+ const total = numOf(o["total"]) ?? 0;
2675
+ const pkgs = Array.isArray(o["packages"]) ? o["packages"] : void 0;
2676
+ if (total === 0) return ["all up to date"];
2677
+ const lines = [`${total} outdated`];
2678
+ if (pkgs && pkgs.length > 0) {
2679
+ const first = pkgs[0];
2680
+ if (first && typeof first === "object") {
2681
+ const p = first;
2682
+ const name = stringOf(p["name"]) ?? stringOf(p["package"]);
2683
+ const cur = stringOf(p["current"]);
2684
+ const wanted = stringOf(p["wanted"]) ?? stringOf(p["latest"]);
2685
+ if (name && cur && wanted) lines.push(`${name}: ${cur} \u2192 ${wanted}`);
2686
+ else if (name) lines.push(name);
2687
+ }
2688
+ }
2689
+ return lines;
3119
2690
  }
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
- }
2691
+ if (toolName === "tree" && json && typeof json === "object") {
2692
+ const o = json;
2693
+ const files = numOf(o["total_files"]);
2694
+ const dirs = numOf(o["total_dirs"]);
2695
+ const truncated = o["truncated"] === true;
2696
+ const parts = [];
2697
+ if (files !== void 0) parts.push(`${files} file${files === 1 ? "" : "s"}`);
2698
+ if (dirs !== void 0) parts.push(`${dirs} dir${dirs === 1 ? "" : "s"}`);
2699
+ if (truncated) parts.push("truncated");
2700
+ return parts.length > 0 ? [parts.join(" \xB7 ")] : [];
3132
2701
  }
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
- }
2702
+ if (toolName === "json" && json && typeof json === "object") {
2703
+ const o = json;
2704
+ const err = stringOf(o["error"]);
2705
+ if (err) return [truncMid(err, OUT_BUDGET)];
2706
+ const type = stringOf(o["type"]);
2707
+ const keys = Array.isArray(o["keys"]) ? o["keys"] : void 0;
2708
+ const parts = [];
2709
+ if (type) parts.push(type);
2710
+ if (keys) parts.push(`${keys.length} key${keys.length === 1 ? "" : "s"}`);
2711
+ return parts.length > 0 ? [parts.join(" \xB7 ")] : [];
3145
2712
  }
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
- }
2713
+ if (toolName === "install" && json && typeof json === "object") {
2714
+ const o = json;
2715
+ const exit = numOf(o["exit_code"]) ?? numOf(o["exitCode"]);
2716
+ const added = numOf(o["added"]);
2717
+ const removed = numOf(o["removed"]);
2718
+ const head = [];
2719
+ if (exit !== void 0) head.push(`exit ${exit}`);
2720
+ if (added !== void 0) head.push(`+${added}`);
2721
+ if (removed !== void 0) head.push(`-${removed}`);
2722
+ const stdout = stringOf(o["stdout"]) ?? stringOf(o["output"]) ?? "";
2723
+ const lines = [];
2724
+ if (head.length > 0) lines.push(head.join(" \xB7 "));
2725
+ const preview = firstNonEmpty(stdout);
2726
+ if (preview) lines.push(`"${truncMid(preview, 70)}"`);
2727
+ if (lines.length > 0) return lines;
3163
2728
  }
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
- }
2729
+ if (toolName === "scaffold" && json && typeof json === "object") {
2730
+ const o = json;
2731
+ const created = Array.isArray(o["created"]) ? o["created"] : void 0;
2732
+ const skipped = Array.isArray(o["skipped"]) ? o["skipped"] : void 0;
2733
+ const parts = [];
2734
+ if (created !== void 0) parts.push(`${created.length} created`);
2735
+ if (skipped !== void 0 && skipped.length > 0) parts.push(`${skipped.length} skipped`);
2736
+ if (parts.length > 0) return [parts.join(" \xB7 ")];
3174
2737
  }
3175
2738
  if (toolName === "remember" || toolName === "forget" || toolName === "memory") {
3176
2739
  return ok ? [toolName === "forget" ? "removed" : "saved"] : [text.split("\n")[0] ?? ""];
3177
2740
  }
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
- }
2741
+ if (toolName === "mode" && json && typeof json === "object") {
2742
+ const o = json;
2743
+ const mode = stringOf(o["mode"]) ?? stringOf(o["active"]) ?? stringOf(o["name"]);
2744
+ if (mode) return [`mode: ${mode}`];
3184
2745
  }
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
- }
2746
+ if (toolName === "search" && json && typeof json === "object") {
2747
+ const o = json;
2748
+ const matches = Array.isArray(o["matches"]) ? o["matches"] : Array.isArray(o["results"]) ? o["results"] : void 0;
2749
+ const count = numOf(o["count"]) ?? matches?.length;
2750
+ if (count !== void 0) {
2751
+ if (count === 0) return ["no results"];
2752
+ const lines = [`${count} result${count === 1 ? "" : "s"}`];
2753
+ const firstHit = matches && matches.length > 0 ? formatMatchHit(matches[0]) : void 0;
2754
+ if (firstHit) lines.push(firstHit);
2755
+ return lines;
3197
2756
  }
3198
2757
  }
3199
2758
  if (toolName === "logs") {
@@ -3210,66 +2769,168 @@ function formatToolOutput(toolName, output, ok, _outputBytes, outputLines) {
3210
2769
  const collapsed = text.replace(/\s+/g, " ").trim();
3211
2770
  return [truncMid(collapsed, GENERIC_BUDGET)];
3212
2771
  }
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;
2772
+ var MAX_STREAM_DISPLAY_CHARS = 480;
2773
+ var MAX_STREAM_LINES = 8;
2774
+ var ToolStreamBox = React6.memo(function ToolStreamBox2({
2775
+ name,
2776
+ text,
2777
+ startedAt,
2778
+ termWidth
2779
+ }) {
2780
+ const [tick, setTick] = useState(0);
2781
+ useEffect(() => {
2782
+ const t = setInterval(() => setTick((n) => n + 1), 500);
2783
+ return () => clearInterval(t);
2784
+ }, []);
2785
+ const elapsedMs = Date.now() - startedAt;
2786
+ const lines = text.split("\n");
2787
+ const totalLines = lines.length;
2788
+ const hidden = Math.max(0, totalLines - MAX_STREAM_LINES);
2789
+ const visible = hidden > 0 ? lines.slice(hidden) : lines;
2790
+ const contentWidth = Math.max(20, Math.min(termWidth - 4, 100));
2791
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 0, children: [
2792
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
2793
+ /* @__PURE__ */ jsx(Text, { color: theme.warn, children: "\u25C6 " }),
2794
+ /* @__PURE__ */ jsx(Text, { bold: true, color: theme.tool, children: name }),
2795
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u23F1 ${fmtDuration(elapsedMs)}` }),
2796
+ hidden > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` (${totalLines} lines, showing last ${MAX_STREAM_LINES})` }) : null
2797
+ ] }),
2798
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [
2799
+ hidden > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, italic: true, children: ` \u2026 ${hidden} more line${hidden === 1 ? "" : "s"} above` }) : null,
2800
+ visible.map((line, i) => {
2801
+ const trimmed = line.length > contentWidth ? `${line.slice(0, contentWidth - 1)}\u2026` : line;
2802
+ return /* @__PURE__ */ jsx(Text, { dimColor: true, children: trimmed || " " }, i);
2803
+ })
2804
+ ] })
2805
+ ] });
2806
+ });
2807
+ function tailForDisplay(text, maxChars) {
2808
+ if (text.length <= maxChars) return text;
2809
+ const cut = text.length - maxChars;
2810
+ const nl = text.indexOf("\n", cut);
2811
+ if (nl !== -1 && nl < cut + 80) {
2812
+ return `\u2026 ${text.slice(nl + 1)}`;
2813
+ }
2814
+ return `\u2026 ${text.slice(cut)}`;
2815
+ }
2816
+ var MAX_CODE_LINES = 80;
2817
+ var DIFF_MAX_LINES = 8;
2818
+ function CodeBlock({
2819
+ code,
2820
+ lang,
2821
+ contentWidth
2822
+ }) {
2823
+ let lines = code.replace(/\n+$/, "").split("\n");
2824
+ const hidden = Math.max(0, lines.length - MAX_CODE_LINES);
2825
+ if (hidden > 0) lines = lines.slice(0, MAX_CODE_LINES);
2826
+ const gutterW = String(lines.length).length;
2827
+ const maxW = Math.max(20, Math.min(contentWidth - 6 - gutterW - 1, 120));
2828
+ let carry = {};
2829
+ const rows = lines.map((raw) => {
2830
+ const display = raw.length > maxW ? `${raw.slice(0, maxW - 1)}\u2026` : raw;
2831
+ const r = highlightLine(display, lang, carry);
2832
+ carry = r.carry;
2833
+ return r.tokens;
2834
+ });
2835
+ return /* @__PURE__ */ jsxs(
2836
+ Box,
2837
+ {
2838
+ flexDirection: "column",
2839
+ marginLeft: 2,
2840
+ marginY: 0,
2841
+ borderStyle: "round",
2842
+ borderColor: theme.borderDefault,
2843
+ paddingX: 1,
2844
+ children: [
2845
+ lang !== "plain" ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: lang }) : null,
2846
+ rows.map((tokens, i) => (
2847
+ // biome-ignore lint/suspicious/noArrayIndexKey: code lines are positional
2848
+ /* @__PURE__ */ jsxs(Text, { children: [
2849
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: `${String(i + 1).padStart(gutterW, " ")} ` }),
2850
+ tokens.length === 0 ? " " : tokens.map((t, j) => (
2851
+ // biome-ignore lint/suspicious/noArrayIndexKey: token order is stable per line
2852
+ /* @__PURE__ */ jsx(Text, { color: t.color, dimColor: t.dim, bold: t.bold, children: t.text }, j)
2853
+ ))
2854
+ ] }, i)
2855
+ )),
2856
+ hidden > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, italic: true, children: `\u2026 +${hidden} more line${hidden === 1 ? "" : "s"}` }) : null
2857
+ ]
2858
+ }
2859
+ );
2860
+ }
2861
+ function DiffBlock({ rows, hidden }) {
2862
+ let gutterWidth = 1;
2863
+ for (const r of rows) {
2864
+ const n = r.kind === "del" ? r.oldLine : r.newLine;
2865
+ if (typeof n === "number") {
2866
+ const w = String(n).length;
2867
+ if (w > gutterWidth) gutterWidth = w;
3246
2868
  }
3247
- parts.push(rendered);
3248
- used += rendered.length + 3;
3249
2869
  }
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;
2870
+ const blank = " ".repeat(gutterWidth);
2871
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginLeft: 4, marginTop: 0, children: [
2872
+ rows.map((row, i) => {
2873
+ const key = i;
2874
+ if (row.kind === "hunk") {
2875
+ return /* @__PURE__ */ jsx(Text, { color: "cyan", dimColor: true, children: row.text }, key);
2876
+ }
2877
+ if (row.kind === "meta") {
2878
+ return /* @__PURE__ */ jsx(Text, { dimColor: true, children: `${blank} ${row.text}` }, key);
2879
+ }
2880
+ const lnNumber = row.kind === "del" ? row.oldLine : row.newLine;
2881
+ const lnText = typeof lnNumber === "number" ? String(lnNumber).padStart(gutterWidth, " ") : blank;
2882
+ if (row.kind === "ctx") {
2883
+ return /* @__PURE__ */ jsx(Text, { dimColor: true, children: `${lnText} ${row.text}` }, key);
2884
+ }
2885
+ const bg = row.kind === "add" ? theme.diffAddBg : theme.diffDelBg;
2886
+ return /* @__PURE__ */ jsxs(Text, { children: [
2887
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: `${lnText} ` }),
2888
+ /* @__PURE__ */ jsx(Text, { backgroundColor: bg, color: "black", children: row.text })
2889
+ ] }, key);
2890
+ }),
2891
+ hidden > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, italic: true, children: `${blank} \u2026 ${hidden} more line${hidden === 1 ? "" : "s"}` }) : null
2892
+ ] });
3256
2893
  }
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;
2894
+ function parseUnifiedDiff(diff, maxLines) {
2895
+ const all = [];
2896
+ let oldLn = 0;
2897
+ let newLn = 0;
2898
+ for (const raw of diff.split("\n")) {
2899
+ const line = raw.replace(/\r$/, "");
2900
+ if (line.startsWith("+++") || line.startsWith("---")) continue;
2901
+ if (line.startsWith("diff --git") || line.startsWith("index ")) continue;
2902
+ if (line.startsWith("@@")) {
2903
+ const m = line.match(/^@@\s+-(\d+)(?:,\d+)?\s+\+(\d+)(?:,\d+)?\s+@@/);
2904
+ if (m) {
2905
+ oldLn = Number.parseInt(m[1] ?? "0", 10) || 0;
2906
+ newLn = Number.parseInt(m[2] ?? "0", 10) || 0;
2907
+ }
2908
+ all.push({ kind: "hunk", text: truncMid(line, 60) });
2909
+ continue;
3267
2910
  }
3268
- if (snippet2) return truncMid(snippet2, 70);
2911
+ if (line.startsWith("+")) {
2912
+ all.push({ kind: "add", text: truncMid(line, 100), newLine: newLn });
2913
+ newLn++;
2914
+ continue;
2915
+ }
2916
+ if (line.startsWith("-")) {
2917
+ all.push({ kind: "del", text: truncMid(line, 100), oldLine: oldLn });
2918
+ oldLn++;
2919
+ continue;
2920
+ }
2921
+ if (line.startsWith("\\ No newline")) {
2922
+ all.push({ kind: "meta", text: line });
2923
+ continue;
2924
+ }
2925
+ if (line.length === 0) continue;
2926
+ all.push({ kind: "ctx", text: truncMid(line, 100), oldLine: oldLn, newLine: newLn });
2927
+ oldLn++;
2928
+ newLn++;
3269
2929
  }
3270
- return void 0;
2930
+ if (all.length === 0) return { rows: [], hidden: 0 };
2931
+ if (all.length <= maxLines) return { rows: all, hidden: 0 };
2932
+ return { rows: all.slice(0, maxLines), hidden: all.length - maxLines };
3271
2933
  }
3272
- var DIFF_MAX_LINES = 8;
3273
2934
  function extractDiffPreview(toolName, output) {
3274
2935
  if (!output) return void 0;
3275
2936
  const text = output.trim();
@@ -3291,90 +2952,385 @@ function extractDiffPreview(toolName, output) {
3291
2952
  if (!diff || !diff.trim() || diff.startsWith("(no-op")) return void 0;
3292
2953
  return parseUnifiedDiff(diff, DIFF_MAX_LINES);
3293
2954
  }
3294
- function parseUnifiedDiff(diff, maxLines) {
3295
- const all = [];
3296
- let oldLn = 0;
3297
- let newLn = 0;
3298
- for (const raw of diff.split("\n")) {
3299
- const line = raw.replace(/\r$/, "");
3300
- if (line.startsWith("+++") || line.startsWith("---")) continue;
3301
- if (line.startsWith("diff --git") || line.startsWith("index ")) continue;
3302
- if (line.startsWith("@@")) {
3303
- const m = line.match(/^@@\s+-(\d+)(?:,\d+)?\s+\+(\d+)(?:,\d+)?\s+@@/);
3304
- if (m) {
3305
- oldLn = Number.parseInt(m[1] ?? "0", 10) || 0;
3306
- newLn = Number.parseInt(m[2] ?? "0", 10) || 0;
2955
+ var MESSAGE_PANEL_CHROME_WIDTH = 2;
2956
+ function splitFencedBlocks(text) {
2957
+ const lines = text.split("\n");
2958
+ const segs = [];
2959
+ let prose = [];
2960
+ let code = null;
2961
+ let lang = "plain";
2962
+ const flushProse = () => {
2963
+ if (prose.length > 0) {
2964
+ segs.push({ type: "prose", text: prose.join("\n") });
2965
+ prose = [];
2966
+ }
2967
+ };
2968
+ for (const line of lines) {
2969
+ const fence = line.match(/^\s*```(.*)$/);
2970
+ if (fence) {
2971
+ if (code === null) {
2972
+ flushProse();
2973
+ code = [];
2974
+ lang = detectLang(fence[1] ?? "");
2975
+ } else {
2976
+ segs.push({ type: "code", text: code.join("\n"), lang });
2977
+ code = null;
2978
+ lang = "plain";
3307
2979
  }
3308
- all.push({ kind: "hunk", text: truncMid(line, 60) });
3309
2980
  continue;
3310
2981
  }
3311
- if (line.startsWith("+")) {
3312
- all.push({ kind: "add", text: truncMid(line, 100), newLine: newLn });
3313
- newLn++;
3314
- continue;
2982
+ if (code !== null) code.push(line);
2983
+ else prose.push(line);
2984
+ }
2985
+ if (code !== null) segs.push({ type: "code", text: code.join("\n"), lang });
2986
+ flushProse();
2987
+ return segs;
2988
+ }
2989
+ function AssistantBody({
2990
+ text,
2991
+ termWidth,
2992
+ contentWidth
2993
+ }) {
2994
+ const segments = splitFencedBlocks(text);
2995
+ const inner = contentWidth ?? termWidth;
2996
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: segments.map(
2997
+ (seg, i) => seg.type === "code" ? (
2998
+ // biome-ignore lint/suspicious/noArrayIndexKey: segment order is stable
2999
+ /* @__PURE__ */ jsx(CodeBlock, { code: seg.text, lang: seg.lang ?? "plain", contentWidth: inner }, i)
3000
+ ) : (
3001
+ // biome-ignore lint/suspicious/noArrayIndexKey: segment order is stable
3002
+ /* @__PURE__ */ jsx(MarkdownView, { text: seg.text, termWidth: inner }, i)
3003
+ )
3004
+ ) });
3005
+ }
3006
+ function AssistantTail({ text }) {
3007
+ return /* @__PURE__ */ jsxs(
3008
+ Box,
3009
+ {
3010
+ flexDirection: "column",
3011
+ marginY: 1,
3012
+ borderStyle: "single",
3013
+ borderTop: false,
3014
+ borderRight: false,
3015
+ borderBottom: false,
3016
+ borderColor: theme.assistant,
3017
+ paddingLeft: 1,
3018
+ children: [
3019
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
3020
+ /* @__PURE__ */ jsx(Text, { bold: true, color: theme.assistant, children: "ASSISTANT" }),
3021
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " (streaming\u2026)" })
3022
+ ] }),
3023
+ /* @__PURE__ */ jsx(Text, { color: "white", children: text })
3024
+ ]
3025
+ }
3026
+ );
3027
+ }
3028
+ function Banner({
3029
+ entry
3030
+ }) {
3031
+ const cwdShort = shortenPath(entry.cwd, 48);
3032
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", paddingX: 2, paddingY: 0, children: [
3033
+ /* @__PURE__ */ jsxs(Text, { children: [
3034
+ /* @__PURE__ */ jsx(Text, { color: "magenta", bold: true, children: " \u259F\u259B " }),
3035
+ /* @__PURE__ */ jsx(Text, { color: "magenta", bold: true, children: "WrongStack" }),
3036
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " v" }),
3037
+ /* @__PURE__ */ jsx(Text, { children: entry.version })
3038
+ ] }),
3039
+ /* @__PURE__ */ jsx(Text, { dimColor: true, italic: true, children: " Built on the wrong stack. Shipped anyway." }),
3040
+ /* @__PURE__ */ jsxs(Text, { children: [
3041
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: " provider " }),
3042
+ /* @__PURE__ */ jsxs(Text, { children: [
3043
+ entry.provider,
3044
+ "/",
3045
+ entry.model
3046
+ ] })
3047
+ ] }),
3048
+ entry.family ? /* @__PURE__ */ jsxs(Text, { children: [
3049
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: " family " }),
3050
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: entry.family })
3051
+ ] }) : null,
3052
+ entry.keyTail ? /* @__PURE__ */ jsxs(Text, { children: [
3053
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: " key " }),
3054
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u25CF\u25CF\u25CF\u2026" }),
3055
+ /* @__PURE__ */ jsx(Text, { children: entry.keyTail })
3056
+ ] }) : null,
3057
+ /* @__PURE__ */ jsxs(Text, { children: [
3058
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: " cwd " }),
3059
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: cwdShort })
3060
+ ] }),
3061
+ /* @__PURE__ */ jsxs(Text, { children: [
3062
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: " hints " }),
3063
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "/help \xB7 /init \xB7 /memory \xB7 /queue \xB7 /exit" })
3064
+ ] })
3065
+ ] });
3066
+ }
3067
+ function brainStatusStyle(status) {
3068
+ switch (status) {
3069
+ case "thinking":
3070
+ return { icon: "\u2026", color: "magenta" };
3071
+ case "answered":
3072
+ return { icon: "\u2696", color: "cyan" };
3073
+ case "ask_human":
3074
+ return { icon: "?", color: "yellow" };
3075
+ case "denied":
3076
+ return { icon: "\xD7", color: "red" };
3077
+ }
3078
+ }
3079
+ function brainRiskColor(risk) {
3080
+ switch (risk) {
3081
+ case "low":
3082
+ return "green";
3083
+ case "medium":
3084
+ return "cyan";
3085
+ case "high":
3086
+ return "yellow";
3087
+ case "critical":
3088
+ return "red";
3089
+ }
3090
+ }
3091
+ function assistantContentWidth(termWidth) {
3092
+ return Math.max(20, termWidth - MESSAGE_PANEL_CHROME_WIDTH);
3093
+ }
3094
+ var Entry = React6.memo(function Entry2({
3095
+ entry,
3096
+ termWidth
3097
+ }) {
3098
+ switch (entry.kind) {
3099
+ case "user":
3100
+ return /* @__PURE__ */ jsx(
3101
+ Box,
3102
+ {
3103
+ borderStyle: "single",
3104
+ borderTop: false,
3105
+ borderRight: false,
3106
+ borderBottom: false,
3107
+ borderColor: theme.user,
3108
+ paddingLeft: 1,
3109
+ children: /* @__PURE__ */ jsxs(Text, { children: [
3110
+ /* @__PURE__ */ jsx(Text, { bold: true, color: theme.user, children: "USER " }),
3111
+ /* @__PURE__ */ jsx(Text, { color: "white", children: entry.text }),
3112
+ entry.queued ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: " (queued)" }) : null,
3113
+ entry.pasteContent ? /* @__PURE__ */ jsxs(Fragment, { children: [
3114
+ entry.text ? "\n" : null,
3115
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
3116
+ " \u21B3 ",
3117
+ entry.pasteContent
3118
+ ] })
3119
+ ] }) : null
3120
+ ] })
3121
+ }
3122
+ );
3123
+ case "assistant": {
3124
+ const contentWidth = assistantContentWidth(termWidth);
3125
+ return /* @__PURE__ */ jsxs(
3126
+ Box,
3127
+ {
3128
+ flexDirection: "column",
3129
+ marginY: 1,
3130
+ borderStyle: "single",
3131
+ borderTop: false,
3132
+ borderRight: false,
3133
+ borderBottom: false,
3134
+ borderColor: theme.assistant,
3135
+ paddingLeft: 1,
3136
+ children: [
3137
+ /* @__PURE__ */ jsx(Box, { flexDirection: "row", children: /* @__PURE__ */ jsx(Text, { bold: true, color: theme.assistant, children: "ASSISTANT" }) }),
3138
+ /* @__PURE__ */ jsx(AssistantBody, { text: entry.text, termWidth, contentWidth })
3139
+ ]
3140
+ }
3141
+ );
3142
+ }
3143
+ case "tool": {
3144
+ const argSummary = formatToolArgs(entry.name, entry.input);
3145
+ const outLines = formatToolOutput(
3146
+ entry.name,
3147
+ entry.output,
3148
+ entry.ok,
3149
+ entry.outputBytes,
3150
+ entry.outputLines
3151
+ );
3152
+ const diff = entry.ok ? extractDiffPreview(entry.name, entry.output) : void 0;
3153
+ const sizeChip = (() => {
3154
+ if (!entry.ok) return "";
3155
+ const parts = [];
3156
+ if (entry.outputLines !== void 0 && entry.outputLines > 0) {
3157
+ parts.push(`${entry.outputLines} L`);
3158
+ }
3159
+ if (entry.outputBytes && entry.outputBytes > 0) {
3160
+ parts.push(fmtBytes(entry.outputBytes));
3161
+ }
3162
+ if (entry.outputTokens && entry.outputTokens > 0) {
3163
+ parts.push(`\u2248${fmtTok2(entry.outputTokens)} tok`);
3164
+ }
3165
+ return parts.join(" \xB7 ");
3166
+ })();
3167
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3168
+ /* @__PURE__ */ jsxs(Text, { children: [
3169
+ /* @__PURE__ */ jsx(Text, { color: entry.ok ? theme.success : theme.error, children: entry.ok ? "\u25CF" : "\u2717" }),
3170
+ " ",
3171
+ /* @__PURE__ */ jsx(Text, { bold: true, color: theme.tool, children: entry.name }),
3172
+ argSummary ? /* @__PURE__ */ jsxs(Fragment, { children: [
3173
+ /* @__PURE__ */ jsx(Text, { children: " " }),
3174
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: argSummary })
3175
+ ] }) : null,
3176
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \xB7 ${fmtDuration(entry.durationMs)}` }),
3177
+ sizeChip ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \xB7 ${sizeChip}` }) : null
3178
+ ] }),
3179
+ outLines.map((line, i) => (
3180
+ // biome-ignore lint/suspicious/noArrayIndexKey: tool output lines are static, index is stable
3181
+ /* @__PURE__ */ jsxs(Text, { children: [
3182
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: i === outLines.length - 1 && !diff ? " \u2514\u2500 " : " \u251C\u2500 " }),
3183
+ /* @__PURE__ */ jsx(
3184
+ Text,
3185
+ {
3186
+ color: !entry.ok || line.startsWith("!") ? "red" : void 0,
3187
+ dimColor: entry.ok && !line.startsWith("!"),
3188
+ children: line
3189
+ }
3190
+ )
3191
+ ] }, i)
3192
+ )),
3193
+ diff ? /* @__PURE__ */ jsx(DiffBlock, { rows: diff.rows, hidden: diff.hidden }) : null
3194
+ ] });
3315
3195
  }
3316
- if (line.startsWith("-")) {
3317
- all.push({ kind: "del", text: truncMid(line, 100), oldLine: oldLn });
3318
- oldLn++;
3319
- continue;
3196
+ case "info":
3197
+ return /* @__PURE__ */ jsx(Text, { dimColor: true, children: entry.text });
3198
+ case "warn":
3199
+ return /* @__PURE__ */ jsx(
3200
+ Box,
3201
+ {
3202
+ borderStyle: "single",
3203
+ borderTop: false,
3204
+ borderRight: false,
3205
+ borderBottom: false,
3206
+ borderColor: theme.warn,
3207
+ paddingLeft: 1,
3208
+ children: /* @__PURE__ */ jsx(Text, { color: theme.warn, children: entry.text })
3209
+ }
3210
+ );
3211
+ case "error":
3212
+ return /* @__PURE__ */ jsx(
3213
+ Box,
3214
+ {
3215
+ borderStyle: "single",
3216
+ borderTop: false,
3217
+ borderRight: false,
3218
+ borderBottom: false,
3219
+ borderColor: theme.error,
3220
+ paddingLeft: 1,
3221
+ children: /* @__PURE__ */ jsx(Text, { color: theme.error, children: entry.text })
3222
+ }
3223
+ );
3224
+ case "turn-summary":
3225
+ return /* @__PURE__ */ jsx(Text, { dimColor: true, children: entry.text });
3226
+ case "brain": {
3227
+ const statusStyle = brainStatusStyle(entry.status);
3228
+ const riskColor2 = brainRiskColor(entry.risk);
3229
+ return /* @__PURE__ */ jsxs(
3230
+ Box,
3231
+ {
3232
+ flexDirection: "column",
3233
+ marginY: 1,
3234
+ borderStyle: "single",
3235
+ borderTop: false,
3236
+ borderRight: false,
3237
+ borderBottom: false,
3238
+ borderColor: "magenta",
3239
+ paddingLeft: 1,
3240
+ children: [
3241
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
3242
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "magenta", children: "BRAIN" }),
3243
+ /* @__PURE__ */ jsx(Text, { color: statusStyle.color, children: statusStyle.icon }),
3244
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: entry.source }),
3245
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
3246
+ /* @__PURE__ */ jsx(Text, { color: riskColor2, children: entry.risk })
3247
+ ] }),
3248
+ /* @__PURE__ */ jsx(Text, { color: "white", children: entry.question }),
3249
+ entry.decision ? /* @__PURE__ */ jsxs(Text, { children: [
3250
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Decision: " }),
3251
+ /* @__PURE__ */ jsx(Text, { color: statusStyle.color, children: entry.decision })
3252
+ ] }) : null,
3253
+ entry.rationale ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: entry.rationale }) : null
3254
+ ]
3255
+ }
3256
+ );
3320
3257
  }
3321
- if (line.startsWith("\\ No newline")) {
3322
- all.push({ kind: "meta", text: line });
3323
- continue;
3258
+ case "confirm":
3259
+ return /* @__PURE__ */ jsxs(
3260
+ Box,
3261
+ {
3262
+ flexDirection: "column",
3263
+ borderStyle: "round",
3264
+ borderColor: "yellow",
3265
+ paddingX: 1,
3266
+ marginY: 1,
3267
+ children: [
3268
+ /* @__PURE__ */ jsxs(Text, { bold: true, color: "yellow", children: [
3269
+ "\u26A0 Confirm: ",
3270
+ entry.toolName
3271
+ ] }),
3272
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Waiting for y / n / a / d..." })
3273
+ ]
3274
+ }
3275
+ );
3276
+ case "banner":
3277
+ return /* @__PURE__ */ jsx(Banner, { entry });
3278
+ case "subagent": {
3279
+ const lines = entry.text.split("\n");
3280
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3281
+ /* @__PURE__ */ jsxs(Text, { children: [
3282
+ /* @__PURE__ */ jsx(Text, { color: entry.agentColor, bold: true, children: `[${entry.agentLabel}]` }),
3283
+ /* @__PURE__ */ jsx(Text, { children: " " }),
3284
+ /* @__PURE__ */ jsx(Text, { color: entry.agentColor, children: entry.icon }),
3285
+ /* @__PURE__ */ jsx(Text, { children: " " }),
3286
+ /* @__PURE__ */ jsx(Text, { children: lines[0] ?? "" }),
3287
+ entry.detail ? /* @__PURE__ */ jsxs(Fragment, { children: [
3288
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \xB7 " }),
3289
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: entry.detail })
3290
+ ] }) : null
3291
+ ] }),
3292
+ lines.slice(1).map((line, i) => (
3293
+ // biome-ignore lint/suspicious/noArrayIndexKey: stable line index
3294
+ /* @__PURE__ */ jsxs(Text, { children: [
3295
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " " }),
3296
+ /* @__PURE__ */ jsx(Text, { children: line })
3297
+ ] }, i)
3298
+ ))
3299
+ ] });
3324
3300
  }
3325
- if (line.length === 0) continue;
3326
- all.push({ kind: "ctx", text: truncMid(line, 100), oldLine: oldLn, newLine: newLn });
3327
- oldLn++;
3328
- newLn++;
3329
- }
3330
- if (all.length === 0) return { rows: [], hidden: 0 };
3331
- if (all.length <= maxLines) return { rows: all, hidden: 0 };
3332
- return { rows: all.slice(0, maxLines), hidden: all.length - maxLines };
3333
- }
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
3301
  }
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++;
3302
+ });
3303
+ function History({ entries, streamingText, toolStream }) {
3304
+ const { stdout } = useStdout();
3305
+ const [termSize, setTermSize] = useState({
3306
+ columns: stdout?.columns ?? 80,
3307
+ rows: stdout?.rows ?? 24
3308
+ });
3309
+ useEffect(() => {
3310
+ const handleResize = () => {
3311
+ setTermSize({ columns: stdout?.columns ?? 80, rows: stdout?.rows ?? 24 });
3312
+ };
3313
+ process.stdout.on("resize", handleResize);
3314
+ return () => {
3315
+ process.stdout.off("resize", handleResize);
3316
+ };
3317
+ }, [stdout]);
3318
+ const termWidth = termSize.columns;
3319
+ const tail = streamingText ? tailForDisplay(streamingText, MAX_STREAM_DISPLAY_CHARS) : "";
3320
+ const toolTail = toolStream?.text ? tailForDisplay(toolStream.text, MAX_STREAM_DISPLAY_CHARS) : "";
3321
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
3322
+ /* @__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) }),
3323
+ tail ? /* @__PURE__ */ jsx(AssistantTail, { text: tail }) : null,
3324
+ toolTail ? /* @__PURE__ */ jsx(
3325
+ ToolStreamBox,
3326
+ {
3327
+ name: toolStream.name,
3328
+ text: toolTail,
3329
+ startedAt: toolStream.startedAt,
3330
+ termWidth
3361
3331
  }
3362
- }
3363
- }
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`;
3374
- }
3375
- function truncMid(s2, max) {
3376
- if (s2.length <= max) return s2;
3377
- return `${s2.slice(0, max - 1)}\u2026`;
3332
+ ) : null
3333
+ ] });
3378
3334
  }
3379
3335
 
3380
3336
  // src/fn-keys.ts
@@ -3718,7 +3674,7 @@ function fmtRecentMessage(message) {
3718
3674
  const text = message.text.replace(/\s+/g, " ");
3719
3675
  return text.length > 48 ? `${text.slice(0, 47)}...` : text;
3720
3676
  }
3721
- var LiveActivityStrip = React4.memo(function LiveActivityStrip2({
3677
+ var LiveActivityStrip = React6.memo(function LiveActivityStrip2({
3722
3678
  entries,
3723
3679
  nowTick,
3724
3680
  maxRows = 4
@@ -4535,6 +4491,121 @@ function runGit(cwd, args) {
4535
4491
  }
4536
4492
  });
4537
4493
  }
4494
+ var STREAM_COLORS = ["cyan", "magenta", "yellow", "green", "blue"];
4495
+ function labelFor(labelsRef, id, name) {
4496
+ const m = labelsRef.current;
4497
+ const existing = m.get(id);
4498
+ if (existing) return existing;
4499
+ const n = m.size + 1;
4500
+ const v = {
4501
+ label: name && name !== id ? name : `AGENT#${n}`,
4502
+ color: STREAM_COLORS[(n - 1) % STREAM_COLORS.length]
4503
+ };
4504
+ m.set(id, v);
4505
+ return v;
4506
+ }
4507
+ function useSubagentEvents(events, dispatch, setActiveMaxContext) {
4508
+ const labelsRef = useRef(/* @__PURE__ */ new Map());
4509
+ const lbl = (id, name) => labelFor(labelsRef, id, name);
4510
+ useEffect(() => {
4511
+ const offSpawned = events.on("subagent.spawned", (e) => {
4512
+ const l = lbl(e.subagentId, e.name);
4513
+ dispatch({ type: "fleetSpawn", id: e.subagentId, name: e.name, provider: e.provider, model: e.model, transcriptPath: e.transcriptPath });
4514
+ const where = e.provider && e.model ? `${e.provider}/${e.model}` : "spawned";
4515
+ const desc = e.description ? ` \u2014 ${e.description.slice(0, 80)}` : "";
4516
+ dispatch({ type: "addEntry", entry: { kind: "subagent", agentLabel: l.label, agentColor: l.color, icon: "\u25B6", text: `${where}${desc}` } });
4517
+ });
4518
+ const offStarted = events.on("subagent.task_started", (e) => {
4519
+ const l = lbl(e.subagentId);
4520
+ dispatch({ type: "fleetStart", id: e.subagentId, taskId: e.taskId });
4521
+ const desc = e.description ? ` \u2014 ${e.description.slice(0, 80)}` : "";
4522
+ dispatch({ type: "addEntry", entry: { kind: "subagent", agentLabel: l.label, agentColor: l.color, icon: "\u25CF", text: `task started${desc}` } });
4523
+ });
4524
+ const offCompleted = events.on("subagent.task_completed", (e) => {
4525
+ const l = lbl(e.subagentId);
4526
+ const errKind = e.error?.kind;
4527
+ dispatch({ type: "fleetDone", id: e.subagentId, status: e.status, iterations: e.iterations, toolCalls: e.toolCalls, failureReason: errKind });
4528
+ const icon = e.status === "success" ? "\u2713" : e.status === "timeout" ? "\u23F1" : e.status === "stopped" ? "\u2298" : "\u2717";
4529
+ const errMsg = e.error?.message;
4530
+ const errMsgTail = errMsg ? ` \u2014 ${errMsg.replace(/\s+/g, " ").slice(0, 100)}${errMsg.length > 100 ? "\u2026" : ""}` : "";
4531
+ const errChip = errKind ? ` [${errKind}]` : "";
4532
+ const secs = (e.durationMs / 1e3).toFixed(e.durationMs < 1e4 ? 1 : 0);
4533
+ 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}` } });
4534
+ });
4535
+ const offBudgetWarning = events.on("subagent.budget_warning", (e) => {
4536
+ const l = lbl(e.subagentId);
4537
+ dispatch({ type: "fleetBudgetWarning", id: e.subagentId, kind: e.kind, used: e.used, limit: e.limit });
4538
+ const timeoutSuffix = e.kind === "timeout" ? " (subagent continues running)" : " \u2014 extending";
4539
+ dispatch({ type: "addEntry", entry: { kind: "subagent", agentLabel: l.label, agentColor: l.color, icon: "\u26A1", text: `hitting ${e.kind} limit (${e.used}/${e.limit})${timeoutSuffix}` } });
4540
+ });
4541
+ const offBudgetExtended = events.on("subagent.budget_extended", (e) => {
4542
+ const l = lbl(e.subagentId);
4543
+ dispatch({ type: "fleetBudgetExtended", id: e.subagentId, totalExtensions: e.totalExtensions });
4544
+ dispatch({ type: "addEntry", entry: { kind: "subagent", agentLabel: l.label, agentColor: l.color, icon: "\u26A1", text: `extended ${e.kind} \u2192 ${e.newLimit} (\xD7${e.totalExtensions})` } });
4545
+ });
4546
+ const offIterationSummary = events.on("subagent.iteration_summary", (e) => {
4547
+ const l = lbl(e.subagentId);
4548
+ const costStr = e.costUsd > 0 ? ` \xB7 ${e.costUsd.toFixed(3)}` : "";
4549
+ const toolStr = e.currentTool ? ` \xB7 doing ${e.currentTool}` : "";
4550
+ const partial = e.partialText ? ` \xB7 "${e.partialText.slice(0, 60)}${e.partialText.length > 60 ? "\u2026" : ""}"` : "";
4551
+ 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}` } });
4552
+ });
4553
+ const offCtxPct = events.on("subagent.ctx_pct", (e) => {
4554
+ dispatch({ type: "fleetCtxPct", id: e.subagentId, load: e.load, tokens: e.tokens, maxContext: e.maxContext });
4555
+ });
4556
+ const offLeaderCtxPct = events.on("ctx.pct", (e) => {
4557
+ setActiveMaxContext(e.maxContext);
4558
+ dispatch({ type: "leaderCtxPct", load: e.load, tokens: e.tokens, maxContext: e.maxContext });
4559
+ });
4560
+ const offLeaderMaxContext = events.on("ctx.max_context", (e) => {
4561
+ if (e.maxContext > 0) setActiveMaxContext(e.maxContext);
4562
+ });
4563
+ const offTool = events.on("subagent.tool_executed", (e) => {
4564
+ dispatch({ type: "fleetTool", id: e.subagentId, name: e.name, ok: e.ok, durationMs: e.durationMs, outputBytes: e.outputBytes });
4565
+ dispatch({ type: "fleetToolEnd", id: e.subagentId });
4566
+ });
4567
+ return () => {
4568
+ offSpawned();
4569
+ offStarted();
4570
+ offCompleted();
4571
+ offBudgetWarning();
4572
+ offBudgetExtended();
4573
+ offIterationSummary();
4574
+ offCtxPct();
4575
+ offLeaderCtxPct();
4576
+ offLeaderMaxContext();
4577
+ offTool();
4578
+ };
4579
+ }, [events, dispatch, setActiveMaxContext]);
4580
+ }
4581
+ function useBrainEvents(events, dispatch) {
4582
+ useEffect(() => {
4583
+ const requestSummary = (request) => `${request.source}: ${request.question}`.slice(0, 80);
4584
+ const addBrainEntry = (status, payload) => {
4585
+ const p = payload;
4586
+ const decision = p.decision.optionId ?? p.decision.text ?? p.decision.reason ?? p.decision.prompt ?? p.decision.type;
4587
+ dispatch({ type: "brainStatus", state: status, source: p.request.source, risk: p.request.risk, summary: decision });
4588
+ if (status === "ask_human") {
4589
+ 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 } });
4590
+ } else {
4591
+ dispatch({ type: "brainPromptClear" });
4592
+ }
4593
+ dispatch({ type: "addEntry", entry: { kind: "brain", status, source: p.request.source, risk: p.request.risk, question: p.request.question, decision, rationale: p.decision.rationale } });
4594
+ };
4595
+ const offRequested = events.on("brain.decision_requested", ({ request }) => {
4596
+ dispatch({ type: "brainStatus", state: "deciding", source: request.source, risk: request.risk, summary: requestSummary(request) });
4597
+ });
4598
+ const offAnswered = events.on("brain.decision_answered", (payload) => addBrainEntry("answered", payload));
4599
+ const offAskHuman = events.on("brain.decision_ask_human", (payload) => addBrainEntry("ask_human", payload));
4600
+ const offDenied = events.on("brain.decision_denied", (payload) => addBrainEntry("denied", payload));
4601
+ return () => {
4602
+ offRequested();
4603
+ offAnswered();
4604
+ offAskHuman();
4605
+ offDenied();
4606
+ };
4607
+ }, [events, dispatch]);
4608
+ }
4538
4609
  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
4610
  function createKillSlashCommand() {
4540
4611
  return {
@@ -4754,14 +4825,8 @@ function oneLine(s2, max) {
4754
4825
  const collapsed = s2.replace(/\s+/g, " ").trim();
4755
4826
  return collapsed.length <= max ? collapsed : `${collapsed.slice(0, max - 1)}\u2026`;
4756
4827
  }
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;
4764
- }
4828
+
4829
+ // src/app-reducer.ts
4765
4830
  function reducer(state, action) {
4766
4831
  switch (action.type) {
4767
4832
  case "addEntry": {
@@ -5721,6 +5786,14 @@ function reducer(state, action) {
5721
5786
  }
5722
5787
  }
5723
5788
  }
5789
+ var WHEEL_STEP = 3;
5790
+ var MIN_VIEWPORT = 3;
5791
+ var INPUT_PROMPT = "\u203A ";
5792
+ function selectedSlashCommandLine(picker) {
5793
+ if (!picker.open || picker.matches.length === 0) return null;
5794
+ const picked = picker.matches[picker.selected];
5795
+ return picked ? `/${picked.name}` : null;
5796
+ }
5724
5797
  var PASTE_THRESHOLD_CHARS = 200;
5725
5798
  function buildSteeringPreamble(snapshot, newDirection) {
5726
5799
  const lines = ["[STEERING \u2014 I pressed Esc to interrupt you mid-task on purpose.", ""];
@@ -5938,7 +6011,7 @@ function App({
5938
6011
  const inputGateRef = useRef(false);
5939
6012
  const lastEnterAtRef = useRef(0);
5940
6013
  const tokenPreviewsRef = useRef(/* @__PURE__ */ new Map());
5941
- const projectName = React4.useMemo(() => {
6014
+ const projectName = React6.useMemo(() => {
5942
6015
  const base = path2.basename(projectRoot);
5943
6016
  return base && base !== path2.sep ? base : void 0;
5944
6017
  }, [projectRoot]);
@@ -5954,7 +6027,7 @@ function App({
5954
6027
  const preRowsRef = useRef(0);
5955
6028
  const liveStripRef = useRef(null);
5956
6029
  const liveStripRowsRef = useRef(0);
5957
- React4.useLayoutEffect(() => {
6030
+ React6.useLayoutEffect(() => {
5958
6031
  if (!managedLive) return;
5959
6032
  const node = bottomRef.current;
5960
6033
  if (!node) return;
@@ -5984,7 +6057,7 @@ function App({
5984
6057
  const DOUBLE_CLICK_MS = 500;
5985
6058
  const DOUBLE_CLICK_DIST = 5;
5986
6059
  const statusChipRef = useRef({ version: appVersion, model, fleetRunning: 0, yolo, autonomy: "off" });
5987
- const handleMouse = React4.useCallback(
6060
+ const handleMouse = React6.useCallback(
5988
6061
  (ev) => {
5989
6062
  const s2 = stateRef.current;
5990
6063
  if (ev.type === "wheel") {
@@ -6180,7 +6253,7 @@ function App({
6180
6253
  state.picker.open,
6181
6254
  state.picker.selected
6182
6255
  ]);
6183
- const handleRewindTo = React4.useCallback(
6256
+ const handleRewindTo = React6.useCallback(
6184
6257
  async (checkpointIndex) => {
6185
6258
  const sessionId = agent.ctx.session.id;
6186
6259
  if (!sessionId) return;
@@ -6199,13 +6272,13 @@ function App({
6199
6272
  dispatch({ type: "clearInput" });
6200
6273
  };
6201
6274
  const startedAtRef = useRef(Date.now());
6202
- const [nowTick, setNowTick] = React4.useState(Date.now());
6275
+ const [nowTick, setNowTick] = React6.useState(Date.now());
6203
6276
  useEffect(() => {
6204
6277
  const t = setInterval(() => setNowTick(Date.now()), 1e3);
6205
6278
  return () => clearInterval(t);
6206
6279
  }, []);
6207
6280
  const elapsedMs = nowTick - startedAtRef.current;
6208
- const [gitInfo, setGitInfo] = React4.useState(null);
6281
+ const [gitInfo, setGitInfo] = React6.useState(null);
6209
6282
  useEffect(() => {
6210
6283
  let cancelled = false;
6211
6284
  const refresh = () => {
@@ -6270,16 +6343,16 @@ function App({
6270
6343
  };
6271
6344
  return { leader: leaderEntry, ...state.fleet };
6272
6345
  }, [state.fleet, state.leader, state.status, provider, model, effectiveMaxContext]);
6273
- const STREAM_COLORS = ["cyan", "magenta", "yellow", "green", "blue"];
6346
+ const STREAM_COLORS2 = ["cyan", "magenta", "yellow", "green", "blue"];
6274
6347
  const labelsRef = useRef(/* @__PURE__ */ new Map());
6275
- const labelFor = (id, name) => {
6348
+ const labelFor2 = (id, name) => {
6276
6349
  const m = labelsRef.current;
6277
6350
  const existing = m.get(id);
6278
6351
  if (existing) return existing;
6279
6352
  const n = m.size + 1;
6280
6353
  const v = {
6281
6354
  label: name && name !== id ? name : `AGENT#${n}`,
6282
- color: STREAM_COLORS[(n - 1) % STREAM_COLORS.length]
6355
+ color: STREAM_COLORS2[(n - 1) % STREAM_COLORS2.length]
6283
6356
  };
6284
6357
  m.set(id, v);
6285
6358
  return v;
@@ -6722,16 +6795,16 @@ function App({
6722
6795
  slashRegistry.unregister("agents");
6723
6796
  };
6724
6797
  }, [slashRegistry]);
6725
- const openModelPicker = React4.useCallback(async () => {
6798
+ const openModelPicker = React6.useCallback(async () => {
6726
6799
  if (!getPickableProviders) return;
6727
6800
  const providers = await getPickableProviders();
6728
6801
  dispatch({ type: "modelPickerOpen", providers });
6729
6802
  }, [getPickableProviders]);
6730
- const openAutonomyPicker = React4.useCallback(() => {
6803
+ const openAutonomyPicker = React6.useCallback(() => {
6731
6804
  if (!switchAutonomy) return;
6732
6805
  dispatch({ type: "autonomyPickerOpen", options: AUTONOMY_OPTIONS });
6733
6806
  }, [switchAutonomy]);
6734
- const openSettings = React4.useCallback(() => {
6807
+ const openSettings = React6.useCallback(() => {
6735
6808
  if (!getSettings) return;
6736
6809
  const s2 = getSettings();
6737
6810
  dispatch({ type: "settingsOpen", mode: s2.mode, delayMs: s2.delayMs });
@@ -6915,172 +6988,7 @@ function App({
6915
6988
  useEffect(() => {
6916
6989
  streamFleetRef.current = state.streamFleet;
6917
6990
  }, [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]);
6991
+ useSubagentEvents(events, dispatch, setActiveMaxContext);
7084
6992
  useEffect(() => {
7085
6993
  const offCheckpoint = events.on("checkpoint.written", (e) => {
7086
6994
  dispatch({
@@ -7105,71 +7013,7 @@ function App({
7105
7013
  offRewound();
7106
7014
  };
7107
7015
  }, [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]);
7016
+ useBrainEvents(events, dispatch);
7173
7017
  useEffect(() => {
7174
7018
  if (!subscribeAutoPhase) return;
7175
7019
  const handler = (event, payload) => {
@@ -7414,7 +7258,7 @@ function App({
7414
7258
  for (const [id, text] of streamBuf) {
7415
7259
  const trimmed = text.trim();
7416
7260
  if (!trimmed) continue;
7417
- const lbl = labelFor(id);
7261
+ const lbl = labelFor2(id);
7418
7262
  enq({ type: "fleetMessage", id, text: trimmed });
7419
7263
  if (streamFleetRef.current) {
7420
7264
  enq({
@@ -7442,7 +7286,7 @@ function App({
7442
7286
  provider: meta?.provider,
7443
7287
  model: meta?.model
7444
7288
  });
7445
- labelFor(s2.id, meta?.name ?? s2.name);
7289
+ labelFor2(s2.id, meta?.name ?? s2.name);
7446
7290
  }
7447
7291
  dispatch({
7448
7292
  type: "fleetCost",
@@ -7473,7 +7317,7 @@ function App({
7473
7317
  provider: meta?.provider,
7474
7318
  model: meta?.model
7475
7319
  });
7476
- const lbl = labelFor(e.subagentId, meta?.name);
7320
+ const lbl = labelFor2(e.subagentId, meta?.name);
7477
7321
  if (streamFleetRef.current) {
7478
7322
  const where = meta?.provider && meta?.model ? `${meta.provider}/${meta.model}` : "spawned";
7479
7323
  dispatch2({
@@ -7558,7 +7402,7 @@ function App({
7558
7402
  });
7559
7403
  dispatch2({ type: "fleetToolEnd", id: e.subagentId });
7560
7404
  if (streamFleetRef.current && p?.name) {
7561
- const lbl = labelFor(e.subagentId);
7405
+ const lbl = labelFor2(e.subagentId);
7562
7406
  dispatch2({
7563
7407
  type: "addEntry",
7564
7408
  entry: {
@@ -7951,6 +7795,9 @@ function App({
7951
7795
  return;
7952
7796
  }
7953
7797
  if (isEnter) {
7798
+ const now = Date.now();
7799
+ if (now - lastEnterAtRef.current < 50) return;
7800
+ lastEnterAtRef.current = now;
7954
7801
  inputGateRef.current = true;
7955
7802
  try {
7956
7803
  if (state.modelPicker.step === "provider") {
@@ -8000,6 +7847,9 @@ function App({
8000
7847
  return;
8001
7848
  }
8002
7849
  if (isEnter) {
7850
+ const now = Date.now();
7851
+ if (now - lastEnterAtRef.current < 50) return;
7852
+ lastEnterAtRef.current = now;
8003
7853
  const opt = state.autonomyPicker.options[state.autonomyPicker.selected];
8004
7854
  if (!opt) return;
8005
7855
  const err = switchAutonomy?.(opt.mode);
@@ -8034,6 +7884,9 @@ function App({
8034
7884
  return;
8035
7885
  }
8036
7886
  if (isEnter) {
7887
+ const now = Date.now();
7888
+ if (now - lastEnterAtRef.current < 50) return;
7889
+ lastEnterAtRef.current = now;
8037
7890
  const { mode, delayMs } = state.settingsPicker;
8038
7891
  const err = await saveSettings?.({ mode, delayMs });
8039
7892
  if (err) {
@@ -8095,6 +7948,9 @@ function App({
8095
7948
  return;
8096
7949
  }
8097
7950
  if (isEnter) {
7951
+ const now = Date.now();
7952
+ if (now - lastEnterAtRef.current < 50) return;
7953
+ lastEnterAtRef.current = now;
8098
7954
  inputGateRef.current = true;
8099
7955
  try {
8100
7956
  await acceptPickerSelection();
@@ -9250,7 +9106,7 @@ async function runTui(opts) {
9250
9106
  let instance;
9251
9107
  try {
9252
9108
  instance = render(
9253
- React4.createElement(App, {
9109
+ React6.createElement(App, {
9254
9110
  agent: opts.agent,
9255
9111
  slashRegistry: opts.slashRegistry,
9256
9112
  attachments: opts.attachments,