@wrongstack/tui 0.77.0 → 0.82.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { writeErr, InputBuilder, DefaultSessionRewinder, writeOut, formatTodosList, buildGoalPreamble, shouldEnhance, enhanceUserPrompt, recentTextTurns, normalizedEqual, buildChildEnv } from '@wrongstack/core';
2
2
  export { buildGoalPreamble } from '@wrongstack/core';
3
- import { Box, Text, render, useApp, useStdout, measureElement, Static, useInput, useStdin } from 'ink';
4
- import React6, { useState, useEffect, useReducer, useRef, useMemo, useCallback, useLayoutEffect } from 'react';
3
+ import { Box, Text, render, useApp, useStdout, Static, useInput, useStdin } from 'ink';
4
+ import React6, { useState, useEffect, useReducer, useRef, useMemo, useCallback } from 'react';
5
5
  import * as fs2 from 'fs/promises';
6
6
  import * as path2 from 'path';
7
7
  import { routeImagesForModel } from '@wrongstack/runtime/vision';
@@ -34,6 +34,12 @@ var theme = Object.freeze({
34
34
  diffAddBg: "greenBright",
35
35
  diffDelBg: "redBright"
36
36
  });
37
+ function expectDefined(value) {
38
+ if (value === null || value === void 0) {
39
+ throw new Error("Expected value to be defined");
40
+ }
41
+ return value;
42
+ }
37
43
  var MODE_ICONS = {
38
44
  teach: "\u{1F9D1}\u200D\u{1F3EB}",
39
45
  brief: "\u26A1",
@@ -55,7 +61,7 @@ function modeIcon(label) {
55
61
  var COMPACT_THRESHOLD = 50;
56
62
  var COMFORTABLE_THRESHOLD = 90;
57
63
  var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
58
- var SPINNER_INTERVAL_MS = 130;
64
+ var SPINNER_INTERVAL_MS = 250;
59
65
  function StatusBar({
60
66
  model,
61
67
  version,
@@ -65,7 +71,7 @@ function StatusBar({
65
71
  queueCount = 0,
66
72
  yolo = false,
67
73
  autonomy,
68
- elapsedMs,
74
+ startedAt,
69
75
  todos,
70
76
  plan,
71
77
  fleet,
@@ -98,6 +104,12 @@ function StatusBar({
98
104
  const usage = tokenCounter?.total();
99
105
  const cost = tokenCounter?.estimateCost();
100
106
  const cache2 = tokenCounter?.cacheStats();
107
+ const [elapsedMs, setElapsedMs] = useState(startedAt ? Date.now() - startedAt : 0);
108
+ useEffect(() => {
109
+ if (startedAt == null) return;
110
+ const t = setInterval(() => setElapsedMs(Date.now() - startedAt), 1e3);
111
+ return () => clearInterval(t);
112
+ }, [startedAt]);
101
113
  const [spinnerIdx, setSpinnerIdx] = useState(0);
102
114
  useEffect(() => {
103
115
  if (state === "idle" || state === "aborting") return;
@@ -107,10 +119,11 @@ function StatusBar({
107
119
  );
108
120
  return () => clearInterval(t);
109
121
  }, [state]);
110
- const spinner = SPINNER_FRAMES[spinnerIdx];
122
+ const spinner = expectDefined(SPINNER_FRAMES[spinnerIdx]);
111
123
  const { label: stateLabel, color: stateColor } = stateChip(state, fleet?.running ?? 0);
112
124
  const statePrefix = state === "idle" || state === "aborting" ? "\u25CF" : spinner;
113
- const hasSecondLine = yolo || autonomy && autonomy !== "off" || elapsedMs !== void 0 || git !== null && git !== void 0 || projectName !== void 0 && projectName.length > 0 || goalSummary !== null && goalSummary !== void 0 || !!modeLabel;
125
+ const thinking = state === "running" || state === "streaming";
126
+ const hasSecondLine = yolo || autonomy && autonomy !== "off" || startedAt != null || git !== null && git !== void 0 || projectName !== void 0 && projectName.length > 0 || goalSummary !== null && goalSummary !== void 0 || !!modeLabel;
114
127
  const fleetHasActivity = fleet && (fleet.running > 0 || fleet.idle > 0 || fleet.pending > 0 || fleet.completed > 0) || subagentCount > 0;
115
128
  const hasBrainActivity = !!brain && brain.state !== "idle";
116
129
  const hasThirdLine = todos && (todos.pending > 0 || todos.inProgress > 0 || todos.completed > 0) || plan && (plan.open > 0 || plan.inProgress > 0 || plan.done > 0) || fleetHasActivity || hasBrainActivity;
@@ -128,7 +141,7 @@ function StatusBar({
128
141
  /* @__PURE__ */ jsx(Box, { flexDirection: "row", gap: 2, children: isCompact ? (
129
142
  // Ultra-compact: state · model
130
143
  /* @__PURE__ */ jsxs(Fragment, { children: [
131
- /* @__PURE__ */ jsxs(Text, { color: stateColor, children: [
144
+ thinking ? /* @__PURE__ */ jsx(WaveText, { text: `${statePrefix}${stateLabel}`, phase: spinnerIdx }) : /* @__PURE__ */ jsxs(Text, { color: stateColor, children: [
132
145
  statePrefix,
133
146
  stateLabel
134
147
  ] }),
@@ -148,7 +161,7 @@ function StatusBar({
148
161
  ] }),
149
162
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" })
150
163
  ] }) : null,
151
- /* @__PURE__ */ jsxs(Text, { color: stateColor, children: [
164
+ thinking ? /* @__PURE__ */ jsx(WaveText, { text: `${statePrefix} ${stateLabel}`, phase: spinnerIdx }) : /* @__PURE__ */ jsxs(Text, { color: stateColor, children: [
152
165
  statePrefix,
153
166
  " ",
154
167
  stateLabel
@@ -205,7 +218,7 @@ function StatusBar({
205
218
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
206
219
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: hint })
207
220
  ] }) : null,
208
- indexState && indexState.indexing ? /* @__PURE__ */ jsxs(Fragment, { children: [
221
+ indexState?.indexing ? /* @__PURE__ */ jsxs(Fragment, { children: [
209
222
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
210
223
  /* @__PURE__ */ jsxs(Text, { color: "yellow", children: [
211
224
  "\u2699 indexing ",
@@ -244,14 +257,14 @@ function StatusBar({
244
257
  ] })
245
258
  ] }) : null,
246
259
  projectName ? /* @__PURE__ */ jsxs(Fragment, { children: [
247
- yolo || elapsedMs !== void 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }) : null,
260
+ yolo || startedAt != null ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }) : null,
248
261
  /* @__PURE__ */ jsxs(Text, { color: "blue", children: [
249
262
  "\u{1F4C1} ",
250
263
  projectName
251
264
  ] })
252
265
  ] }) : null,
253
266
  goalSummary ? /* @__PURE__ */ jsxs(Fragment, { children: [
254
- yolo || elapsedMs !== void 0 || projectName ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }) : null,
267
+ yolo || startedAt != null || projectName ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }) : null,
255
268
  /* @__PURE__ */ jsxs(
256
269
  Text,
257
270
  {
@@ -271,11 +284,11 @@ function StatusBar({
271
284
  )
272
285
  ] }) : null,
273
286
  modeLabel ? /* @__PURE__ */ jsxs(Fragment, { children: [
274
- yolo || autonomy && autonomy !== "off" || eternalStage || elapsedMs !== void 0 || projectName || goalSummary ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }) : null,
287
+ yolo || autonomy && autonomy !== "off" || eternalStage || startedAt != null || projectName || goalSummary ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }) : null,
275
288
  /* @__PURE__ */ jsx(Text, { color: "cyan", children: modeIcon(modeLabel) })
276
289
  ] }) : null,
277
290
  git ? /* @__PURE__ */ jsxs(Fragment, { children: [
278
- yolo || elapsedMs !== void 0 || projectName ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }) : null,
291
+ yolo || startedAt != null || projectName ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }) : null,
279
292
  /* @__PURE__ */ jsxs(Text, { children: [
280
293
  /* @__PURE__ */ jsxs(Text, { color: "magenta", children: [
281
294
  "\u2387 ",
@@ -375,7 +388,7 @@ function StatusBar({
375
388
  /* @__PURE__ */ jsxs(Text, { children: [
376
389
  /* @__PURE__ */ jsx(Text, { color: a.color, bold: true, children: a.label }),
377
390
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: " " }),
378
- /* @__PURE__ */ jsx(Text, { color: a.running ? "yellow" : void 0, dimColor: !a.running, children: a.running ? "\u25B6" : "\xB7" }),
391
+ /* @__PURE__ */ jsx(Text, { dimColor: !a.running, ...a.running ? { color: "yellow" } : {}, children: a.running ? "\u25B6" : "\xB7" }),
379
392
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: " " }),
380
393
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: fmtElapsed(a.elapsedMs) }),
381
394
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \xB7 " }),
@@ -504,6 +517,26 @@ function stateChip(state, fleetRunning) {
504
517
  if (state === "aborting") return { label: "aborting\u2026", color: "yellow" };
505
518
  return { label: "thinking\u2026", color: "green" };
506
519
  }
520
+ var WAVE_COLORS = [
521
+ "#ff5f5f",
522
+ "#ff8f3f",
523
+ "#ffd23f",
524
+ "#bce84a",
525
+ "#6bcb77",
526
+ "#3dd9c0",
527
+ "#3fb6ff",
528
+ "#5f8bff",
529
+ "#845ef7",
530
+ "#b15bff",
531
+ "#f06595",
532
+ "#ff5fa2"
533
+ ];
534
+ function WaveText({ text, phase }) {
535
+ return /* @__PURE__ */ jsx(Text, { bold: true, children: Array.from(text).map((ch, i) => (
536
+ // biome-ignore lint/suspicious/noArrayIndexKey: glyph order is positional and re-rendered each tick
537
+ /* @__PURE__ */ jsx(Text, { color: WAVE_COLORS[(i + phase) % WAVE_COLORS.length], children: ch }, i)
538
+ )) });
539
+ }
507
540
  var FILLED = "\u2588";
508
541
  var EMPTY = "\u2591";
509
542
  function renderProgress(ratio, width) {
@@ -565,7 +598,7 @@ function bucketActivity(recentTools, now, bins = 12, binMs = 2e3) {
565
598
  let idx = Math.floor((t.at - windowStart) / binMs);
566
599
  if (idx < 0) idx = 0;
567
600
  if (idx >= bins) idx = bins - 1;
568
- out[idx]++;
601
+ out[idx] = (out[idx] ?? 0) + 1;
569
602
  }
570
603
  return out;
571
604
  }
@@ -694,7 +727,7 @@ function FleetMonitor({
694
727
  // biome-ignore lint/suspicious/noArrayIndexKey: timeline is rebuilt per render
695
728
  /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
696
729
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: `${fmtElapsed(Math.max(0, nowTick - ev.at))} ago`.padEnd(10) }),
697
- /* @__PURE__ */ jsx(Text, { color: ev.color, children: ev.icon }),
730
+ /* @__PURE__ */ jsx(Text, { ...ev.color ? { color: ev.color } : {}, children: ev.icon }),
698
731
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: ev.text })
699
732
  ] }, i)
700
733
  )) }) : null
@@ -1021,8 +1054,8 @@ function AutonomyPicker({
1021
1054
  options.map((opt, i) => /* @__PURE__ */ jsxs(
1022
1055
  Text,
1023
1056
  {
1024
- color: i === selected ? opt.color : void 0,
1025
1057
  inverse: i === selected,
1058
+ ...i === selected ? { color: opt.color } : {},
1026
1059
  children: [
1027
1060
  i === selected ? "\u203A " : " ",
1028
1061
  /* @__PURE__ */ jsx(Text, { bold: true, children: opt.label.padEnd(12) }),
@@ -1147,8 +1180,8 @@ function CheckpointTimeline({
1147
1180
  const isSelected = i === selected;
1148
1181
  const label = `[${cp.promptIndex}] ${cp.promptPreview}`;
1149
1182
  return /* @__PURE__ */ jsxs(Box, { children: [
1150
- /* @__PURE__ */ jsx(Text, { color: isSelected ? "cyan" : void 0, bold: isSelected, children: isSelected ? "\u25B8 " : " " }),
1151
- /* @__PURE__ */ jsx(Text, { color: isSelected ? "cyan" : void 0, bold: isSelected, children: label }),
1183
+ /* @__PURE__ */ jsx(Text, { bold: isSelected, ...isSelected ? { color: "cyan" } : {}, children: isSelected ? "\u25B8 " : " " }),
1184
+ /* @__PURE__ */ jsx(Text, { bold: isSelected, ...isSelected ? { color: "cyan" } : {}, children: label }),
1152
1185
  /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1153
1186
  " ",
1154
1187
  new Date(cp.ts).toLocaleTimeString()
@@ -1193,7 +1226,7 @@ function hasDiff(input) {
1193
1226
  }
1194
1227
  function renderDiffLine(line) {
1195
1228
  const prefix = line.startsWith("+") ? "green" : line.startsWith("-") ? "red" : line.startsWith("@@") ? "cyan" : void 0;
1196
- return /* @__PURE__ */ jsxs(Text, { color: prefix, children: [
1229
+ return /* @__PURE__ */ jsxs(Text, { ...prefix ? { color: prefix } : {}, children: [
1197
1230
  line,
1198
1231
  "\n"
1199
1232
  ] }, line);
@@ -1318,7 +1351,7 @@ function FilePicker({ query, matches, selected }) {
1318
1351
  query || "\u2026",
1319
1352
  " \u2014 \u2191/\u2193 select, Enter attach, Esc cancel"
1320
1353
  ] }),
1321
- matches.map((m, i) => /* @__PURE__ */ jsxs(Text, { color: i === selected ? "cyan" : void 0, inverse: i === selected, children: [
1354
+ matches.map((m, i) => /* @__PURE__ */ jsxs(Text, { inverse: i === selected, ...i === selected ? { color: "cyan" } : {}, children: [
1322
1355
  i === selected ? "\u203A " : " ",
1323
1356
  highlight(m)
1324
1357
  ] }, m))
@@ -1392,9 +1425,8 @@ function FleetPanel({
1392
1425
  ] }) : null
1393
1426
  ] });
1394
1427
  }
1395
- function helpSections(opts) {
1428
+ function helpSections() {
1396
1429
  const nav = [];
1397
- if (opts.managed) nav.push({ keys: "PgUp/PgDn", desc: "scroll chat history" });
1398
1430
  nav.push(
1399
1431
  { keys: "\u2191/\u2193", desc: "previous / next input (empty prompt)" },
1400
1432
  { keys: "?", desc: "open this help (empty prompt)" }
@@ -1437,10 +1469,8 @@ function helpSections(opts) {
1437
1469
  }
1438
1470
  ];
1439
1471
  }
1440
- function HelpOverlay({
1441
- managed
1442
- }) {
1443
- const sections = helpSections({ managed });
1472
+ function HelpOverlay() {
1473
+ const sections = helpSections();
1444
1474
  const keyWidth = Math.max(...sections.flatMap((s2) => s2.entries.map((e) => e.keys.length)), 0);
1445
1475
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
1446
1476
  /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
@@ -1905,6 +1935,12 @@ function detectLang(fenceInfo) {
1905
1935
  }
1906
1936
 
1907
1937
  // src/markdown-table.ts
1938
+ function expectDefined2(value) {
1939
+ if (value === null || value === void 0) {
1940
+ throw new Error("Expected value to be defined");
1941
+ }
1942
+ return value;
1943
+ }
1908
1944
  var ROW_RE = /^\s*\|.*\|\s*$/;
1909
1945
  var SEP_RE = /^\s*\|[\s\-:|]+\|\s*$/;
1910
1946
  function detectTable(lines, start) {
@@ -1988,14 +2024,14 @@ function computeWidths(allRows, cols, maxWidth, sepWidths) {
1988
2024
  const stripped = stripInlineMarkers(cell);
1989
2025
  const w = longestWord(stripped);
1990
2026
  const total = strWidth(stripped);
1991
- natural[c] = Math.max(natural[c], w, total);
2027
+ natural[c] = Math.max(expectDefined2(natural[c]), w, total);
1992
2028
  }
1993
2029
  }
1994
2030
  if (sepWidths) {
1995
2031
  for (let c = 0; c < cols && c < sepWidths.length; c++) {
1996
2032
  const sepW = sepWidths[c];
1997
2033
  if (sepW != null) {
1998
- natural[c] = Math.max(natural[c], sepW);
2034
+ natural[c] = Math.max(expectDefined2(natural[c]), sepW);
1999
2035
  }
2000
2036
  }
2001
2037
  }
@@ -2007,14 +2043,14 @@ function computeWidths(allRows, cols, maxWidth, sepWidths) {
2007
2043
  let maxIdx = -1;
2008
2044
  let maxVal = MIN_COL_WIDTH;
2009
2045
  for (let i = 0; i < cols; i++) {
2010
- const w = widths[i];
2046
+ const w = expectDefined2(widths[i]);
2011
2047
  if (w > maxVal) {
2012
2048
  maxVal = w;
2013
2049
  maxIdx = i;
2014
2050
  }
2015
2051
  }
2016
2052
  if (maxIdx < 0) break;
2017
- widths[maxIdx]--;
2053
+ widths[maxIdx] = (widths[maxIdx] ?? 0) - 1;
2018
2054
  sum--;
2019
2055
  }
2020
2056
  return widths;
@@ -2067,7 +2103,7 @@ function strWidth(s2) {
2067
2103
  if (i < len) i++;
2068
2104
  continue;
2069
2105
  }
2070
- const code = s2.codePointAt(i);
2106
+ const code = expectDefined2(s2.codePointAt(i));
2071
2107
  const cpLen = code > 65535 ? 2 : 1;
2072
2108
  if (code === 8205 || // ZWJ — Zero Width Joiner (emoji sequences)
2073
2109
  code === 8203 || // ZWSP — Zero Width Space
@@ -2310,10 +2346,10 @@ function InlineLine({ tokens, dim }) {
2310
2346
  Text,
2311
2347
  {
2312
2348
  color: t.code ? theme.accent : "white",
2313
- bold: t.bold,
2314
- italic: t.italic,
2315
- strikethrough: t.strike,
2316
- dimColor: dim,
2349
+ bold: Boolean(t.bold),
2350
+ italic: Boolean(t.italic),
2351
+ strikethrough: Boolean(t.strike),
2352
+ dimColor: Boolean(dim),
2317
2353
  children: t.text
2318
2354
  },
2319
2355
  j
@@ -2938,7 +2974,22 @@ function formatToolOutput(toolName, output, ok, _outputBytes, outputLines) {
2938
2974
  }
2939
2975
  var MAX_STREAM_DISPLAY_CHARS = 480;
2940
2976
  var MAX_STREAM_LINES = 8;
2941
- var ToolStreamBox = React6.memo(function ToolStreamBox2({
2977
+ function streamBoxRows(text, maxLines, contentWidth) {
2978
+ const trunc = (line) => line.length > contentWidth ? `${line.slice(0, contentWidth - 1)}\u2026` : line;
2979
+ const lines = text.split("\n");
2980
+ const totalLines = lines.length;
2981
+ const hidden = Math.max(0, totalLines - maxLines);
2982
+ const rows = [];
2983
+ if (hidden > 0) {
2984
+ rows.push({ text: ` \u2026 ${hidden} more line${hidden === 1 ? "" : "s"} above`, italic: true });
2985
+ for (const line of lines.slice(totalLines - (maxLines - 1))) rows.push({ text: trunc(line) });
2986
+ } else {
2987
+ for (let i = 0; i < maxLines - totalLines; i++) rows.push({ text: "" });
2988
+ for (const line of lines) rows.push({ text: trunc(line) });
2989
+ }
2990
+ return rows;
2991
+ }
2992
+ React6.memo(function ToolStreamBox2({
2942
2993
  name,
2943
2994
  text,
2944
2995
  startedAt,
@@ -2950,11 +3001,10 @@ var ToolStreamBox = React6.memo(function ToolStreamBox2({
2950
3001
  return () => clearInterval(t);
2951
3002
  }, []);
2952
3003
  const elapsedMs = Date.now() - startedAt;
2953
- const lines = text.split("\n");
2954
- const totalLines = lines.length;
3004
+ const totalLines = text.split("\n").length;
2955
3005
  const hidden = Math.max(0, totalLines - MAX_STREAM_LINES);
2956
- const visible = hidden > 0 ? lines.slice(hidden) : lines;
2957
3006
  const contentWidth = Math.max(20, Math.min(termWidth - 4, 100));
3007
+ const rows = streamBoxRows(text, MAX_STREAM_LINES, contentWidth);
2958
3008
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 0, children: [
2959
3009
  /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
2960
3010
  /* @__PURE__ */ jsx(Text, { color: theme.warn, children: "\u25C6 " }),
@@ -2962,13 +3012,10 @@ var ToolStreamBox = React6.memo(function ToolStreamBox2({
2962
3012
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u23F1 ${fmtDuration(elapsedMs)}` }),
2963
3013
  hidden > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` (${totalLines} lines, showing last ${MAX_STREAM_LINES})` }) : null
2964
3014
  ] }),
2965
- /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [
2966
- hidden > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, italic: true, children: ` \u2026 ${hidden} more line${hidden === 1 ? "" : "s"} above` }) : null,
2967
- visible.map((line, i) => {
2968
- const trimmed = line.length > contentWidth ? `${line.slice(0, contentWidth - 1)}\u2026` : line;
2969
- return /* @__PURE__ */ jsx(Text, { dimColor: true, children: trimmed || " " }, i);
2970
- })
2971
- ] })
3015
+ /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginLeft: 2, children: rows.map((r, i) => (
3016
+ // biome-ignore lint/suspicious/noArrayIndexKey: fixed-height block, index is the row
3017
+ /* @__PURE__ */ jsx(Text, { dimColor: true, italic: Boolean(r.italic), children: r.text || " " }, i)
3018
+ )) })
2972
3019
  ] });
2973
3020
  });
2974
3021
  function tailForDisplay(text, maxChars) {
@@ -3014,9 +3061,15 @@ function CodeBlock({
3014
3061
  // biome-ignore lint/suspicious/noArrayIndexKey: code lines are positional
3015
3062
  /* @__PURE__ */ jsxs(Text, { children: [
3016
3063
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: `${String(i + 1).padStart(gutterW, " ")} ` }),
3017
- tokens.length === 0 ? " " : tokens.map((t, j) => (
3018
- // biome-ignore lint/suspicious/noArrayIndexKey: token order is stable per line
3019
- /* @__PURE__ */ jsx(Text, { color: t.color, dimColor: t.dim, bold: t.bold, children: t.text }, j)
3064
+ tokens.length === 0 ? " " : tokens.map((t, j) => /* @__PURE__ */ jsx(
3065
+ Text,
3066
+ {
3067
+ dimColor: Boolean(t.dim),
3068
+ bold: Boolean(t.bold),
3069
+ ...t.color ? { color: t.color } : {},
3070
+ children: t.text
3071
+ },
3072
+ j
3020
3073
  ))
3021
3074
  ] }, i)
3022
3075
  )),
@@ -3170,7 +3223,22 @@ function AssistantBody({
3170
3223
  )
3171
3224
  ) });
3172
3225
  }
3173
- function AssistantTail({ text }) {
3226
+ var ASSISTANT_TAIL_LINES = 8;
3227
+ function assistantTailRows(text, tailLines, contentWidth) {
3228
+ const tail = text.split("\n").slice(-tailLines);
3229
+ const rows = [];
3230
+ for (let i = 0; i < tailLines - tail.length; i++) rows.push("");
3231
+ for (const line of tail) {
3232
+ rows.push(line.length > contentWidth ? `${line.slice(0, contentWidth - 1)}\u2026` : line);
3233
+ }
3234
+ return rows;
3235
+ }
3236
+ function AssistantTail({
3237
+ text,
3238
+ termWidth
3239
+ }) {
3240
+ const contentWidth = Math.max(20, termWidth - 3);
3241
+ const rows = assistantTailRows(text, ASSISTANT_TAIL_LINES, contentWidth);
3174
3242
  return /* @__PURE__ */ jsxs(
3175
3243
  Box,
3176
3244
  {
@@ -3187,7 +3255,10 @@ function AssistantTail({ text }) {
3187
3255
  /* @__PURE__ */ jsx(Text, { bold: true, color: theme.assistant, children: "ASSISTANT" }),
3188
3256
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: " (streaming\u2026)" })
3189
3257
  ] }),
3190
- /* @__PURE__ */ jsx(Text, { color: "white", children: text })
3258
+ /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: rows.map((r, i) => (
3259
+ // biome-ignore lint/suspicious/noArrayIndexKey: fixed-height block, index is the row
3260
+ /* @__PURE__ */ jsx(Text, { color: "white", children: r || " " }, i)
3261
+ )) })
3191
3262
  ]
3192
3263
  }
3193
3264
  );
@@ -3350,8 +3421,8 @@ var Entry = React6.memo(function Entry2({
3350
3421
  /* @__PURE__ */ jsx(
3351
3422
  Text,
3352
3423
  {
3353
- color: !entry.ok || line.startsWith("!") ? "red" : void 0,
3354
3424
  dimColor: entry.ok && !line.startsWith("!"),
3425
+ ...!entry.ok || line.startsWith("!") ? { color: "red" } : {},
3355
3426
  children: line
3356
3427
  }
3357
3428
  )
@@ -3484,19 +3555,9 @@ function History({ entries, streamingText, toolStream }) {
3484
3555
  }, [stdout]);
3485
3556
  const termWidth = termSize.columns;
3486
3557
  const tail = streamingText ? tailForDisplay(streamingText, MAX_STREAM_DISPLAY_CHARS) : "";
3487
- const toolTail = toolStream?.text ? tailForDisplay(toolStream.text, MAX_STREAM_DISPLAY_CHARS) : "";
3488
3558
  return /* @__PURE__ */ jsxs(Fragment, { children: [
3489
3559
  /* @__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) }),
3490
- tail ? /* @__PURE__ */ jsx(AssistantTail, { text: tail }) : null,
3491
- toolTail ? /* @__PURE__ */ jsx(
3492
- ToolStreamBox,
3493
- {
3494
- name: toolStream.name,
3495
- text: toolTail,
3496
- startedAt: toolStream.startedAt,
3497
- termWidth
3498
- }
3499
- ) : null
3560
+ tail ? /* @__PURE__ */ jsx(AssistantTail, { text: tail, termWidth }) : null
3500
3561
  ] });
3501
3562
  }
3502
3563
 
@@ -3602,6 +3663,12 @@ function layoutInputRows(prompt, value, cursor, width) {
3602
3663
  if (row.length > 0 || rows.length === 0) rows.push(row);
3603
3664
  return rows;
3604
3665
  }
3666
+ function expectDefined3(value) {
3667
+ if (value === null || value === void 0) {
3668
+ throw new Error("Expected value to be defined");
3669
+ }
3670
+ return value;
3671
+ }
3605
3672
  function renderRow2(cells, rowKey, promptColor) {
3606
3673
  const out = [];
3607
3674
  let run = "";
@@ -3655,6 +3722,14 @@ function isBackspaceOrDelete(data) {
3655
3722
  if (data === "\x1B[3~") return "delete";
3656
3723
  return null;
3657
3724
  }
3725
+ function parseMouseWheel(data) {
3726
+ const m = data.match(new RegExp(`^${String.fromCharCode(27)}\\[<(\\d+);(\\d+);(\\d+)([Mm])$`, "u"));
3727
+ if (!m) return null;
3728
+ const cb = Number.parseInt(expectDefined3(m[1]), 10);
3729
+ if (cb === 64) return 1;
3730
+ if (cb === 65) return -1;
3731
+ return null;
3732
+ }
3658
3733
  var EMPTY_KEY = {
3659
3734
  upArrow: false,
3660
3735
  downArrow: false,
@@ -3708,6 +3783,11 @@ function Input({
3708
3783
  onKey("", { ...EMPTY_KEY, delete: true });
3709
3784
  return;
3710
3785
  }
3786
+ const wheelDelta = parseMouseWheel(s2);
3787
+ if (wheelDelta !== null) {
3788
+ onKey("", { ...EMPTY_KEY, wheelDeltaY: wheelDelta });
3789
+ return;
3790
+ }
3711
3791
  const fn = fnKey(s2);
3712
3792
  if (fn !== null) onKey("", { ...EMPTY_KEY, fn });
3713
3793
  };
@@ -3742,46 +3822,6 @@ function Input({
3742
3822
  hint ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: hint }) : null
3743
3823
  ] });
3744
3824
  }
3745
- function hintsFor(ctx) {
3746
- if (ctx.confirm) {
3747
- return [
3748
- { key: "y", label: "yes" },
3749
- { key: "n", label: "no" },
3750
- { key: "a", label: "always" },
3751
- { key: "d", label: "deny" }
3752
- ];
3753
- }
3754
- if (ctx.picker) {
3755
- return [
3756
- { key: "\u2191\u2193", label: "move" },
3757
- { key: "\u21B5", label: "select" },
3758
- { key: "Esc", label: "cancel" }
3759
- ];
3760
- }
3761
- if (ctx.monitor) {
3762
- return [
3763
- { key: "Esc", label: "close" },
3764
- { key: "^F", label: "fleet" },
3765
- { key: "^G", label: "agents" },
3766
- { key: "^T", label: "worktrees" },
3767
- { key: "F6", label: "todos" }
3768
- ];
3769
- }
3770
- const base = [{ key: "?", label: "help" }];
3771
- if (ctx.managed) base.push({ key: "PgUp/PgDn", label: "scroll" }, { key: "F5", label: "Settings" });
3772
- base.push({ key: "^G", label: "agents" }, { key: "^C", label: "stop" });
3773
- return base;
3774
- }
3775
- function KeyHintBar({ context }) {
3776
- const hints = hintsFor(context);
3777
- return /* @__PURE__ */ jsx(Box, { flexDirection: "row", paddingX: 1, children: hints.map((h, i) => (
3778
- // biome-ignore lint/suspicious/noArrayIndexKey: hints are positional + stable
3779
- /* @__PURE__ */ jsxs(Box, { flexDirection: "row", marginRight: 2, children: [
3780
- /* @__PURE__ */ jsx(Text, { color: theme.accent, children: h.key }),
3781
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` ${h.label}` })
3782
- ] }, i)
3783
- )) });
3784
- }
3785
3825
  function fmtElapsed2(ms) {
3786
3826
  if (ms < 1e3) return `${ms}ms`;
3787
3827
  if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
@@ -3816,7 +3856,7 @@ var LiveActivityStrip = React6.memo(function LiveActivityStrip2({
3816
3856
  const running = Object.values(entries).filter((e) => e.status === "running").sort((a, b) => a.startedAt - b.startedAt).slice(0, maxRows);
3817
3857
  if (running.length === 0) {
3818
3858
  if (Object.keys(entries).length === 0) return null;
3819
- return /* @__PURE__ */ jsx(Box, { flexDirection: "column", paddingX: 1, children: Array.from({ length: maxRows }, (_, i) => /* @__PURE__ */ jsx(Box, { height: 1, children: /* @__PURE__ */ jsx(Text, { children: " " }) }, `empty-${i}`)) });
3859
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", paddingX: 1, children: Array.from({ length: maxRows }, (_, slot) => slot).map((slot) => /* @__PURE__ */ jsx(Box, { height: 1, children: /* @__PURE__ */ jsx(Text, { children: " " }) }, `empty-${slot}`)) });
3820
3860
  }
3821
3861
  const now = Date.now();
3822
3862
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
@@ -3890,7 +3930,7 @@ function ModelPicker({
3890
3930
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
3891
3931
  /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "\u2501\u2501 Switch model \u2014 Step 1/2: Pick provider \u2501\u2501" }),
3892
3932
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 navigate \xB7 Enter select \xB7 Esc cancel \xB7 Ctrl+C exit" }),
3893
- providerOptions.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "(no providers with keys \u2014 add one via `wstack auth`)" }) : providerOptions.map((p, i) => /* @__PURE__ */ jsxs(Text, { color: i === selected ? "cyan" : void 0, inverse: i === selected, children: [
3933
+ providerOptions.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "(no providers with keys \u2014 add one via `wstack auth`)" }) : providerOptions.map((p, i) => /* @__PURE__ */ jsxs(Text, { inverse: i === selected, ...i === selected ? { color: "cyan" } : {}, children: [
3894
3934
  i === selected ? "\u203A " : " ",
3895
3935
  /* @__PURE__ */ jsx(Text, { bold: true, children: p.id.padEnd(28) }),
3896
3936
  /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
@@ -3933,8 +3973,8 @@ function ModelPicker({
3933
3973
  return /* @__PURE__ */ jsxs(
3934
3974
  Text,
3935
3975
  {
3936
- color: absoluteIndex === selected ? "cyan" : void 0,
3937
3976
  inverse: absoluteIndex === selected,
3977
+ ...absoluteIndex === selected ? { color: "cyan" } : {},
3938
3978
  children: [
3939
3979
  absoluteIndex === selected ? "\u203A " : " ",
3940
3980
  id
@@ -4173,102 +4213,6 @@ function QueuePanel({ items }) {
4173
4213
  ] })
4174
4214
  ] });
4175
4215
  }
4176
- var MAX_MOUNTED = 500;
4177
- function scrollbarThumb(rows, offset, total) {
4178
- const scrollable = total > rows;
4179
- if (!scrollable) return { top: 0, size: rows, scrollable: false };
4180
- const windowTop = Math.max(0, total - rows - offset);
4181
- const size = Math.max(1, Math.round(rows / total * rows));
4182
- const rawTop = Math.round(windowTop / total * rows);
4183
- const top = Math.max(0, Math.min(rawTop, rows - size));
4184
- return { top, size, scrollable: true };
4185
- }
4186
- function Scrollbar({
4187
- rows,
4188
- offset,
4189
- total
4190
- }) {
4191
- const { top: thumbTop, size: thumbSize, scrollable } = scrollbarThumb(rows, offset, total);
4192
- const cells = [];
4193
- for (let i = 0; i < rows; i++) {
4194
- cells.push(i >= thumbTop && i < thumbTop + thumbSize ? "\u2588" : "\u2502");
4195
- }
4196
- return /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginLeft: 1, flexShrink: 0, children: cells.map((c, i) => /* @__PURE__ */ jsx(
4197
- Text,
4198
- {
4199
- color: scrollable ? theme.accent : void 0,
4200
- dimColor: !scrollable || c === "\u2502",
4201
- children: c
4202
- },
4203
- i
4204
- )) });
4205
- }
4206
- function ScrollableHistory({
4207
- entries,
4208
- streamingText,
4209
- toolStream,
4210
- scrollOffset,
4211
- viewportRows,
4212
- totalLines,
4213
- onMeasure,
4214
- maxWidth
4215
- }) {
4216
- const { stdout } = useStdout();
4217
- const rawWidth = stdout?.columns ?? 80;
4218
- const termWidth = maxWidth ? Math.min(rawWidth, maxWidth) : rawWidth;
4219
- const tail = streamingText ? tailForDisplay(streamingText, MAX_STREAM_DISPLAY_CHARS) : "";
4220
- const toolTail = toolStream?.text ? tailForDisplay(toolStream.text, MAX_STREAM_DISPLAY_CHARS) : "";
4221
- const hiddenCount = Math.max(0, entries.length - MAX_MOUNTED);
4222
- const shown = hiddenCount > 0 ? entries.slice(-MAX_MOUNTED) : entries;
4223
- const contentRef = useRef(null);
4224
- const lastReported = useRef(-1);
4225
- useLayoutEffect(() => {
4226
- const node = contentRef.current;
4227
- if (!node) return;
4228
- const { height } = measureElement(node);
4229
- if (height !== lastReported.current) {
4230
- lastReported.current = height;
4231
- onMeasure(height);
4232
- }
4233
- }, [onMeasure]);
4234
- const vp = Math.max(1, viewportRows);
4235
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
4236
- /* @__PURE__ */ jsx(
4237
- Box,
4238
- {
4239
- flexDirection: "column",
4240
- flexGrow: 1,
4241
- height: vp,
4242
- overflowY: "hidden",
4243
- justifyContent: "flex-end",
4244
- children: /* @__PURE__ */ jsxs(
4245
- Box,
4246
- {
4247
- ref: contentRef,
4248
- flexDirection: "column",
4249
- marginBottom: Math.max(0, scrollOffset),
4250
- flexShrink: 0,
4251
- children: [
4252
- hiddenCount > 0 ? /* @__PURE__ */ jsx(Box, { flexShrink: 0, children: /* @__PURE__ */ jsx(Text, { dimColor: true, italic: true, children: ` \u2191 ${hiddenCount} earlier ${hiddenCount === 1 ? "entry" : "entries"} (scroll lives in this session; full log on disk)` }) }) : null,
4253
- shown.map((entry) => /* @__PURE__ */ jsx(Box, { marginBottom: entry.kind === "turn-summary" ? 1 : 0, flexShrink: 0, children: /* @__PURE__ */ jsx(Entry, { entry, termWidth }) }, entry.id)),
4254
- tail ? /* @__PURE__ */ jsx(AssistantTail, { text: tail }) : null,
4255
- toolTail ? /* @__PURE__ */ jsx(
4256
- ToolStreamBox,
4257
- {
4258
- name: toolStream.name,
4259
- text: toolTail,
4260
- startedAt: toolStream.startedAt,
4261
- termWidth
4262
- }
4263
- ) : null
4264
- ]
4265
- }
4266
- )
4267
- }
4268
- ),
4269
- /* @__PURE__ */ jsx(Scrollbar, { rows: vp, offset: Math.max(0, scrollOffset), total: totalLines })
4270
- ] });
4271
- }
4272
4216
  var DELAY_PRESETS_MS = [0, 15e3, 3e4, 45e3, 6e4, 12e4];
4273
4217
  var SETTINGS_MODES = ["off", "suggest", "auto"];
4274
4218
  var LOG_LEVELS = ["error", "warn", "info", "debug", "trace"];
@@ -4425,7 +4369,7 @@ function SettingsPicker({
4425
4369
  ];
4426
4370
  const fieldRowIndex = [];
4427
4371
  for (let i = 0; i < rows.length; i++) {
4428
- if (!rows[i].section) fieldRowIndex.push(i);
4372
+ if (!rows[i]?.section) fieldRowIndex.push(i);
4429
4373
  }
4430
4374
  const VISIBLE_FIELDS = 8;
4431
4375
  const totalFields = fieldRowIndex.length;
@@ -4436,7 +4380,7 @@ function SettingsPicker({
4436
4380
  const sectionFields = [];
4437
4381
  let curHeader = -1;
4438
4382
  for (let i = 0; i < rows.length; i++) {
4439
- if (rows[i].section) curHeader = i;
4383
+ if (rows[i]?.section) curHeader = i;
4440
4384
  else if (curHeader >= 0) {
4441
4385
  const fieldIdx = fieldRowIndex.indexOf(i);
4442
4386
  if (fieldIdx === -1) continue;
@@ -4465,18 +4409,18 @@ function SettingsPicker({
4465
4409
  "\u2500\u2500 ",
4466
4410
  row.section,
4467
4411
  " \u2500\u2500"
4468
- ] }, `section-${i}`);
4412
+ ] }, `section-${row.section ?? i}`);
4469
4413
  }
4470
4414
  return null;
4471
4415
  }
4472
4416
  if (fieldAtRow < windowStart || fieldAtRow >= windowEnd) return null;
4473
4417
  const selected = fieldAtRow === field;
4474
- return /* @__PURE__ */ jsxs(Text, { color: selected ? "yellow" : void 0, inverse: selected, children: [
4418
+ return /* @__PURE__ */ jsxs(Text, { inverse: selected, ...selected ? { color: "yellow" } : {}, children: [
4475
4419
  selected ? "\u203A " : " ",
4476
4420
  /* @__PURE__ */ jsx(Text, { bold: true, children: (row.label ?? "").padEnd(26) }),
4477
4421
  /* @__PURE__ */ jsx(Text, { color: "cyan", children: String(row.value ?? "").padEnd(12) }),
4478
4422
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: row.detail ?? "" })
4479
- ] }, `row-${i}`);
4423
+ ] }, `row-${row.label ?? fieldAtRow}`);
4480
4424
  }),
4481
4425
  hasBelow ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u2193 ${totalFields - windowEnd} field${totalFields - windowEnd === 1 ? "" : "s"} below` }) : null,
4482
4426
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Persisted to ~/.wrongstack/config.json" }),
@@ -4485,31 +4429,47 @@ function SettingsPicker({
4485
4429
  }
4486
4430
  function SlashMenu({ query, matches, selected }) {
4487
4431
  const placeholder = query ? `/${query}` : "/";
4432
+ const rows = [];
4433
+ let lastCategory = "";
4434
+ for (let i = 0; i < matches.length; i++) {
4435
+ const m = matches[i];
4436
+ if (m.category !== lastCategory) {
4437
+ lastCategory = m.category;
4438
+ rows.push({ type: "header", category: m.category });
4439
+ }
4440
+ rows.push({ type: "item", match: m, index: i });
4441
+ }
4488
4442
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [
4489
4443
  /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
4490
4444
  placeholder || "/",
4491
4445
  " \u2014 \u2191/\u2193 select, Enter dispatch, Tab autocomplete, Esc close"
4492
4446
  ] }),
4493
- matches.map((m, i) => /* @__PURE__ */ jsxs(Text, { color: i === selected ? "cyan" : void 0, inverse: i === selected, children: [
4494
- i === selected ? "\u203A " : " ",
4495
- /* @__PURE__ */ jsx(Text, { bold: true, children: m.name }),
4496
- m.argsHint ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
4497
- " ",
4498
- m.argsHint
4499
- ] }) : null,
4500
- /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
4501
- " \u2014 ",
4502
- m.description
4503
- ] })
4504
- ] }, m.name)),
4447
+ rows.map((row) => {
4448
+ if (row.type === "header") {
4449
+ return /* @__PURE__ */ jsxs(Text, { bold: true, color: "yellow", dimColor: true, children: [
4450
+ " ",
4451
+ row.category
4452
+ ] }, `cat-${row.category}`);
4453
+ }
4454
+ const { match: m, index: i } = row;
4455
+ return /* @__PURE__ */ jsxs(Text, { inverse: i === selected, ...i === selected ? { color: "cyan" } : {}, children: [
4456
+ i === selected ? "\u203A " : " ",
4457
+ /* @__PURE__ */ jsx(Text, { bold: true, children: m.name }),
4458
+ m.argsHint ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
4459
+ " ",
4460
+ m.argsHint
4461
+ ] }) : null,
4462
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
4463
+ " \u2014 ",
4464
+ m.description
4465
+ ] })
4466
+ ] }, m.name);
4467
+ }),
4505
4468
  matches.length === 0 && /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No matching commands" })
4506
4469
  ] });
4507
4470
  }
4508
4471
  function TodosMonitor({ todos }) {
4509
4472
  const { stdout } = useStdout();
4510
- useInput((_input, key) => {
4511
- if (key.escape) ;
4512
- });
4513
4473
  const done = todos.filter((t) => t.status === "completed").length;
4514
4474
  const inProgress = todos.filter((t) => t.status === "in_progress").length;
4515
4475
  const pending = todos.filter((t) => t.status === "pending").length;
@@ -4949,7 +4909,15 @@ function useBrainEvents(events, dispatch) {
4949
4909
  const decision = p.decision.optionId ?? p.decision.text ?? p.decision.reason ?? p.decision.prompt ?? p.decision.type;
4950
4910
  dispatch({ type: "brainStatus", state: status, source: p.request.source, risk: p.request.risk, summary: decision });
4951
4911
  if (status === "ask_human") {
4952
- 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 } });
4912
+ const prompt = {
4913
+ requestId: p.request.id,
4914
+ source: p.request.source,
4915
+ risk: p.request.risk,
4916
+ question: p.request.question
4917
+ };
4918
+ if (p.request.context !== void 0) prompt.context = p.request.context;
4919
+ if (p.request.options !== void 0) prompt.options = p.request.options;
4920
+ dispatch({ type: "brainPromptSet", prompt });
4953
4921
  } else {
4954
4922
  dispatch({ type: "brainPromptClear" });
4955
4923
  }
@@ -4969,6 +4937,12 @@ function useBrainEvents(events, dispatch) {
4969
4937
  };
4970
4938
  }, [events, dispatch]);
4971
4939
  }
4940
+ function expectDefined4(value) {
4941
+ if (value === null || value === void 0) {
4942
+ throw new Error("Expected value to be defined");
4943
+ }
4944
+ return value;
4945
+ }
4972
4946
  var STREAM_COLORS = ["cyan", "magenta", "yellow", "green", "blue"];
4973
4947
  function labelFor(labelsRef, id, name) {
4974
4948
  const m = labelsRef.current;
@@ -4977,7 +4951,7 @@ function labelFor(labelsRef, id, name) {
4977
4951
  const n = m.size + 1;
4978
4952
  const v = {
4979
4953
  label: name && name !== id ? name : `AGENT#${n}`,
4980
- color: STREAM_COLORS[(n - 1) % STREAM_COLORS.length]
4954
+ color: expectDefined4(STREAM_COLORS[(n - 1) % STREAM_COLORS.length])
4981
4955
  };
4982
4956
  m.set(id, v);
4983
4957
  return v;
@@ -5324,6 +5298,12 @@ function buildSteeringPreamble(snapshot, newDirection) {
5324
5298
  }
5325
5299
 
5326
5300
  // src/app-reducer.ts
5301
+ function expectDefined5(value) {
5302
+ if (value === null || value === void 0) {
5303
+ throw new Error("Expected value to be defined");
5304
+ }
5305
+ return value;
5306
+ }
5327
5307
  function reducer(state, action) {
5328
5308
  switch (action.type) {
5329
5309
  case "addEntry": {
@@ -5688,13 +5668,13 @@ function reducer(state, action) {
5688
5668
  const i = SETTINGS_MODES.indexOf(sp.mode);
5689
5669
  const base = i < 0 ? 0 : i;
5690
5670
  const next = (base + action.delta + SETTINGS_MODES.length) % SETTINGS_MODES.length;
5691
- return { ...state, settingsPicker: { ...sp, mode: SETTINGS_MODES[next], hint: void 0 } };
5671
+ return { ...state, settingsPicker: { ...sp, mode: expectDefined5(SETTINGS_MODES[next]), hint: void 0 } };
5692
5672
  }
5693
5673
  if (f === 1) {
5694
5674
  const j = DELAY_PRESETS_MS.indexOf(sp.delayMs);
5695
5675
  const base = j < 0 ? 0 : j;
5696
5676
  const next = (base + action.delta + DELAY_PRESETS_MS.length) % DELAY_PRESETS_MS.length;
5697
- return { ...state, settingsPicker: { ...sp, delayMs: DELAY_PRESETS_MS[next], hint: void 0 } };
5677
+ return { ...state, settingsPicker: { ...sp, delayMs: expectDefined5(DELAY_PRESETS_MS[next]), hint: void 0 } };
5698
5678
  }
5699
5679
  if (f === 2) return { ...state, settingsPicker: { ...sp, titleAnimation: !sp.titleAnimation, hint: void 0 } };
5700
5680
  if (f === 3) return { ...state, settingsPicker: { ...sp, yolo: !sp.yolo, hint: void 0 } };
@@ -5712,26 +5692,26 @@ function reducer(state, action) {
5712
5692
  const i = COMPACTOR_STRATEGIES.indexOf(sp.contextStrategy);
5713
5693
  const base = i < 0 ? 0 : i;
5714
5694
  const next = (base + action.delta + COMPACTOR_STRATEGIES.length) % COMPACTOR_STRATEGIES.length;
5715
- return { ...state, settingsPicker: { ...sp, contextStrategy: COMPACTOR_STRATEGIES[next], hint: void 0 } };
5695
+ return { ...state, settingsPicker: { ...sp, contextStrategy: expectDefined5(COMPACTOR_STRATEGIES[next]), hint: void 0 } };
5716
5696
  }
5717
5697
  if (f === 15) {
5718
5698
  const i = LOG_LEVELS.indexOf(sp.logLevel);
5719
5699
  const base = i < 0 ? 0 : i;
5720
5700
  const next = (base + action.delta + LOG_LEVELS.length) % LOG_LEVELS.length;
5721
- return { ...state, settingsPicker: { ...sp, logLevel: LOG_LEVELS[next], hint: void 0 } };
5701
+ return { ...state, settingsPicker: { ...sp, logLevel: expectDefined5(LOG_LEVELS[next]), hint: void 0 } };
5722
5702
  }
5723
5703
  if (f === 16) {
5724
5704
  const i = AUDIT_LEVELS.indexOf(sp.auditLevel);
5725
5705
  const base = i < 0 ? 0 : i;
5726
5706
  const next = (base + action.delta + AUDIT_LEVELS.length) % AUDIT_LEVELS.length;
5727
- return { ...state, settingsPicker: { ...sp, auditLevel: AUDIT_LEVELS[next], hint: void 0 } };
5707
+ return { ...state, settingsPicker: { ...sp, auditLevel: expectDefined5(AUDIT_LEVELS[next]), hint: void 0 } };
5728
5708
  }
5729
5709
  if (f === 17) return { ...state, settingsPicker: { ...sp, indexOnStart: !sp.indexOnStart, hint: void 0 } };
5730
5710
  {
5731
5711
  const j = MAX_ITERATIONS_PRESETS.indexOf(sp.maxIterations);
5732
5712
  const base = j < 0 ? 0 : j;
5733
5713
  const next = (base + action.delta + MAX_ITERATIONS_PRESETS.length) % MAX_ITERATIONS_PRESETS.length;
5734
- return { ...state, settingsPicker: { ...sp, maxIterations: MAX_ITERATIONS_PRESETS[next], hint: void 0 } };
5714
+ return { ...state, settingsPicker: { ...sp, maxIterations: expectDefined5(MAX_ITERATIONS_PRESETS[next]), hint: void 0 } };
5735
5715
  }
5736
5716
  }
5737
5717
  case "settingsHint":
@@ -6379,7 +6359,12 @@ function reducer(state, action) {
6379
6359
  }
6380
6360
  }
6381
6361
  }
6382
- var MIN_VIEWPORT = 3;
6362
+ function expectDefined6(value) {
6363
+ if (value === null || value === void 0) {
6364
+ throw new Error("Expected value to be defined");
6365
+ }
6366
+ return value;
6367
+ }
6383
6368
  var INPUT_PROMPT = "\u203A ";
6384
6369
  function selectedSlashCommandLine(picker) {
6385
6370
  if (!picker.open || picker.matches.length === 0) return null;
@@ -6388,15 +6373,16 @@ function selectedSlashCommandLine(picker) {
6388
6373
  }
6389
6374
  function rehydrateHistory(messages, startId) {
6390
6375
  const entries = [];
6376
+ let nextId = startId;
6391
6377
  for (const msg of messages) {
6392
6378
  if (msg.role === "system") continue;
6393
6379
  const text = typeof msg.content === "string" ? msg.content : msg.content.filter((b) => b.type === "text").map((b) => b.text).join("");
6394
6380
  const trimmed = text.trim();
6395
6381
  if (!trimmed) continue;
6396
6382
  if (msg.role === "user") {
6397
- entries.push({ id: startId++, kind: "user", text: trimmed });
6383
+ entries.push({ id: nextId++, kind: "user", text: trimmed });
6398
6384
  } else if (msg.role === "assistant") {
6399
- entries.push({ id: startId++, kind: "assistant", text: trimmed });
6385
+ entries.push({ id: nextId++, kind: "assistant", text: trimmed });
6400
6386
  }
6401
6387
  }
6402
6388
  return entries;
@@ -6450,7 +6436,6 @@ function App({
6450
6436
  initialGoal,
6451
6437
  initialAsk,
6452
6438
  sessionsDir,
6453
- managed = false,
6454
6439
  modeLabel,
6455
6440
  getModeLabel
6456
6441
  }) {
@@ -6467,18 +6452,6 @@ function App({
6467
6452
  setIndexState(getIndexState());
6468
6453
  return onIndexStateChange((next) => setIndexState(next));
6469
6454
  }, []);
6470
- const { stdout } = useStdout();
6471
- const [termRows, setTermRows] = useState(stdout?.rows ?? 24);
6472
- useEffect(() => {
6473
- const onResize = () => {
6474
- setTermRows(process.stdout.rows ?? 24);
6475
- };
6476
- process.stdout.on("resize", onResize);
6477
- return () => {
6478
- process.stdout.off("resize", onResize);
6479
- };
6480
- }, []);
6481
- const [managedLive, setManagedLive] = useState(managed);
6482
6455
  useEffect(() => {
6483
6456
  setHiddenItems(statuslineHiddenItems);
6484
6457
  }, [statuslineHiddenItems]);
@@ -6628,19 +6601,6 @@ function App({
6628
6601
  stateRef.current = state;
6629
6602
  const draftRef = useRef({ buffer: state.buffer, cursor: state.cursor });
6630
6603
  draftRef.current = { buffer: state.buffer, cursor: state.cursor };
6631
- const bottomRef = useRef(null);
6632
- React6.useLayoutEffect(() => {
6633
- if (!managedLive) return;
6634
- const node = bottomRef.current;
6635
- if (!node) return;
6636
- const { height } = measureElement(node);
6637
- const s2 = stateRef.current;
6638
- const affordance = s2.scrollOffset > 0 && s2.pendingNewLines > 0 ? 1 : 0;
6639
- const vp = Math.max(MIN_VIEWPORT, termRows - height - affordance - 1);
6640
- if (vp !== s2.viewportRows) {
6641
- dispatch({ type: "setViewportRows", rows: vp });
6642
- }
6643
- }, [managedLive, termRows]);
6644
6604
  const handleKeyRef = useRef(null);
6645
6605
  const handleRewindTo = React6.useCallback(
6646
6606
  async (checkpointIndex) => {
@@ -6663,10 +6623,21 @@ function App({
6663
6623
  const startedAtRef = useRef(Date.now());
6664
6624
  const [nowTick, setNowTick] = React6.useState(Date.now());
6665
6625
  useEffect(() => {
6666
- const t = setInterval(() => setNowTick(Date.now()), 1e3);
6626
+ const t = setInterval(() => setNowTick(Date.now()), 1e4);
6667
6627
  return () => clearInterval(t);
6668
6628
  }, []);
6669
- const elapsedMs = nowTick - startedAtRef.current;
6629
+ const todosRef = useRef(JSON.stringify([]));
6630
+ useEffect(() => {
6631
+ const poll = () => {
6632
+ const snap = JSON.stringify(agent.ctx.todos.map((t2) => ({ s: t2.status })));
6633
+ if (snap !== todosRef.current) {
6634
+ todosRef.current = snap;
6635
+ setNowTick(Date.now());
6636
+ }
6637
+ };
6638
+ const t = setInterval(poll, 2e3);
6639
+ return () => clearInterval(t);
6640
+ }, [agent.ctx.todos]);
6670
6641
  const [gitInfo, setGitInfo] = React6.useState(null);
6671
6642
  useEffect(() => {
6672
6643
  let cancelled = false;
@@ -6744,7 +6715,7 @@ function App({
6744
6715
  const n = m.size + 1;
6745
6716
  const v = {
6746
6717
  label: name && name !== id ? name : `AGENT#${n}`,
6747
- color: STREAM_COLORS2[(n - 1) % STREAM_COLORS2.length]
6718
+ color: STREAM_COLORS2[(n - 1) % STREAM_COLORS2.length] ?? "cyan"
6748
6719
  };
6749
6720
  m.set(id, v);
6750
6721
  return v;
@@ -6826,7 +6797,7 @@ function App({
6826
6797
  }, [eraseLiveRegion]);
6827
6798
  React6.useLayoutEffect(() => {
6828
6799
  if (state.enhanceBusy || state.enhance != null) eraseLiveRegion();
6829
- }, [nowTick, state.enhanceBusy, state.enhance, eraseLiveRegion]);
6800
+ }, [state.enhanceBusy, state.enhance, eraseLiveRegion]);
6830
6801
  useEffect(() => {
6831
6802
  const detected = detectAtToken(state.buffer, state.cursor);
6832
6803
  if (!detected) {
@@ -6858,16 +6829,22 @@ function App({
6858
6829
  }
6859
6830
  const query = trimmed.slice(1).toLowerCase();
6860
6831
  const allCommands = slashRegistry.listWithOwner();
6832
+ const CATEGORY_ORDER = ["Run", "Session", "Inspect", "Agent", "Config", "App"];
6861
6833
  const matches = allCommands.filter(({ cmd }) => {
6862
6834
  const name = cmd.name.toLowerCase();
6863
6835
  const aliases = cmd.aliases ?? [];
6864
6836
  return name.includes(query) || aliases.some((a) => a.toLowerCase().includes(query));
6865
- }).slice(0, 12).map(({ cmd, owner }) => ({
6837
+ }).map(({ cmd, owner }) => ({
6866
6838
  name: cmd.name,
6867
6839
  description: cmd.description,
6868
6840
  argsHint: cmd.argsHint,
6869
- isBuiltin: owner === "core"
6870
- }));
6841
+ isBuiltin: owner === "core",
6842
+ category: cmd.category ?? "App"
6843
+ })).sort((a, b) => {
6844
+ const catDiff = CATEGORY_ORDER.indexOf(a.category) - CATEGORY_ORDER.indexOf(b.category);
6845
+ if (catDiff !== 0) return catDiff;
6846
+ return a.name.localeCompare(b.name);
6847
+ });
6871
6848
  if (!state.slashPicker.open) {
6872
6849
  dispatch({ type: "slashPickerOpen", query, matches });
6873
6850
  } else if (state.slashPicker.query !== query) {
@@ -6999,44 +6976,6 @@ function App({
6999
6976
  getProcessRegistry().killAll();
7000
6977
  };
7001
6978
  }, []);
7002
- useEffect(() => {
7003
- const ALT_OFF = "\x1B[?1049l";
7004
- const ALT_ON = "\x1B[?1049h";
7005
- const cmd = {
7006
- name: "altscreen",
7007
- description: "Toggle the alt-screen buffer. Default is OFF (native scroll); /altscreen on for full-screen mode.",
7008
- async run(args) {
7009
- const arg = args.trim().toLowerCase();
7010
- if (arg === "off") {
7011
- try {
7012
- writeOut(ALT_OFF);
7013
- } catch {
7014
- return { message: "Failed to exit alt-screen." };
7015
- }
7016
- setManagedLive(false);
7017
- return {
7018
- message: "Alt-screen disabled. New entries will land in normal scrollback (mouse wheel / Shift+PgUp work). On-screen history rendered before this command is no longer reachable via terminal scroll. Resize may now leak the live region \u2014 `/altscreen on` to re-enable."
7019
- };
7020
- }
7021
- if (arg === "on") {
7022
- try {
7023
- writeOut(ALT_ON);
7024
- } catch {
7025
- return { message: "Failed to re-enter alt-screen." };
7026
- }
7027
- setManagedLive(true);
7028
- return {
7029
- message: "Alt-screen re-enabled. Managed scroll (PgUp/PgDn) is now active; native scroll is off."
7030
- };
7031
- }
7032
- return { message: "Usage: /altscreen on|off" };
7033
- }
7034
- };
7035
- slashRegistry.register(cmd);
7036
- return () => {
7037
- slashRegistry.unregister("altscreen");
7038
- };
7039
- }, [slashRegistry]);
7040
6979
  useEffect(() => {
7041
6980
  const cmd = {
7042
6981
  name: "steer",
@@ -8138,26 +8077,7 @@ function App({
8138
8077
  if (key.escape || input === "?" || input === "q") dispatch({ type: "toggleHelp" });
8139
8078
  return;
8140
8079
  }
8141
- state.todosMonitorOpen || state.monitorOpen || state.agentsMonitorOpen || state.worktreeMonitorOpen || !!state.autoPhase?.monitorOpen;
8142
8080
  if (inputGateRef.current) return;
8143
- if (managedLive) {
8144
- if (key.pageUp) {
8145
- dispatch({ type: "scrollPage", dir: "up" });
8146
- return;
8147
- }
8148
- if (key.pageDown) {
8149
- dispatch({ type: "scrollPage", dir: "down" });
8150
- return;
8151
- }
8152
- if (key.ctrl && key.home) {
8153
- dispatch({ type: "scrollToTop" });
8154
- return;
8155
- }
8156
- if (key.ctrl && key.end) {
8157
- dispatch({ type: "scrollToBottom" });
8158
- return;
8159
- }
8160
- }
8161
8081
  if (key.escape) {
8162
8082
  const now = Date.now();
8163
8083
  if (state.buffer.length > 0 && now - lastEscAtRef.current < ESC_DOUBLE_PRESS_MS) {
@@ -8692,19 +8612,11 @@ function App({
8692
8612
  return;
8693
8613
  }
8694
8614
  if (key.home) {
8695
- if (managedLive && buffer.length === 0) {
8696
- dispatch({ type: "scrollToTop" });
8697
- } else {
8698
- setDraft(buffer, 0);
8699
- }
8615
+ setDraft(buffer, 0);
8700
8616
  return;
8701
8617
  }
8702
8618
  if (key.end) {
8703
- if (managedLive && buffer.length === 0) {
8704
- dispatch({ type: "scrollToBottom" });
8705
- } else {
8706
- setDraft(buffer, buffer.length);
8707
- }
8619
+ setDraft(buffer, buffer.length);
8708
8620
  return;
8709
8621
  }
8710
8622
  if (key.upArrow) {
@@ -9178,21 +9090,9 @@ User message:
9178
9090
  if (state.picker.open) return "";
9179
9091
  return "";
9180
9092
  }, [state.buffer, state.status, state.picker.open]);
9181
- const affordanceShown = managedLive && state.scrollOffset > 0 && state.pendingNewLines > 0;
9182
9093
  const enhanceActive = state.enhanceBusy || state.enhance != null;
9183
- return /* @__PURE__ */ jsx(Box, { flexDirection: "column", height: managedLive ? termRows : void 0, children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", flexGrow: 1, flexShrink: 0, children: [
9184
- managedLive ? /* @__PURE__ */ jsx(
9185
- ScrollableHistory,
9186
- {
9187
- entries: state.entries,
9188
- streamingText: state.streamingText,
9189
- toolStream: state.toolStream,
9190
- scrollOffset: state.scrollOffset,
9191
- viewportRows: state.viewportRows || Math.max(MIN_VIEWPORT, termRows - 8),
9192
- totalLines: state.totalLines,
9193
- onMeasure: (total) => dispatch({ type: "setMeasuredLines", totalLines: total })
9194
- }
9195
- ) : /* @__PURE__ */ jsx(
9094
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", flexGrow: 1, flexShrink: 0, children: [
9095
+ /* @__PURE__ */ jsx(
9196
9096
  History,
9197
9097
  {
9198
9098
  entries: state.entries,
@@ -9200,8 +9100,7 @@ User message:
9200
9100
  toolStream: state.toolStream
9201
9101
  }
9202
9102
  ),
9203
- affordanceShown ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u2193 ${state.pendingNewLines} new line${state.pendingNewLines === 1 ? "" : "s"} \u2014 PgDn or click to jump to bottom` }) : null,
9204
- /* @__PURE__ */ jsxs(Box, { ref: managedLive ? bottomRef : void 0, flexDirection: "column", flexShrink: 0, children: [
9103
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [
9205
9104
  /* @__PURE__ */ jsx(LiveActivityStrip, { entries: state.fleet, nowTick }),
9206
9105
  /* @__PURE__ */ jsx(
9207
9106
  Input,
@@ -9277,16 +9176,22 @@ User message:
9277
9176
  hint: state.settingsPicker.hint
9278
9177
  }
9279
9178
  ) : null,
9280
- state.rewindOverlay ? /* @__PURE__ */ jsx(
9281
- CheckpointTimeline,
9282
- {
9283
- checkpoints: state.rewindOverlay.checkpoints,
9284
- selected: state.rewindOverlay.selected,
9285
- onSelect: (i) => dispatch({ type: "rewindOverlayMove", delta: i - state.rewindOverlay.selected }),
9286
- onConfirm: (i) => handleRewindTo(state.rewindOverlay.checkpoints[i].promptIndex),
9287
- onClose: () => dispatch({ type: "rewindOverlayClose" })
9288
- }
9289
- ) : null,
9179
+ state.rewindOverlay ? (() => {
9180
+ const overlay = state.rewindOverlay;
9181
+ return /* @__PURE__ */ jsx(
9182
+ CheckpointTimeline,
9183
+ {
9184
+ checkpoints: overlay.checkpoints,
9185
+ selected: overlay.selected,
9186
+ onSelect: (i) => dispatch({ type: "rewindOverlayMove", delta: i - overlay.selected }),
9187
+ onConfirm: (i) => {
9188
+ const checkpoint = overlay.checkpoints[i];
9189
+ if (checkpoint) handleRewindTo(checkpoint.promptIndex);
9190
+ },
9191
+ onClose: () => dispatch({ type: "rewindOverlayClose" })
9192
+ }
9193
+ );
9194
+ })() : null,
9290
9195
  state.brainPrompt ? /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginY: 1, flexShrink: 0, children: /* @__PURE__ */ jsx(
9291
9196
  BrainDecisionPrompt,
9292
9197
  {
@@ -9298,7 +9203,7 @@ User message:
9298
9203
  }
9299
9204
  ) }) : null,
9300
9205
  state.confirmQueue.length > 0 && (() => {
9301
- const head = state.confirmQueue[0];
9206
+ const head = expectDefined6(state.confirmQueue[0]);
9302
9207
  let resolved = false;
9303
9208
  const onDecision = (decision) => {
9304
9209
  if (resolved) return;
@@ -9346,7 +9251,7 @@ User message:
9346
9251
  queueCount: state.queue.length,
9347
9252
  yolo: yoloLive,
9348
9253
  autonomy: autonomyLive,
9349
- elapsedMs,
9254
+ startedAt: startedAtRef.current,
9350
9255
  todos,
9351
9256
  plan: planCounts ?? void 0,
9352
9257
  fleet: fleetCounts,
@@ -9363,18 +9268,7 @@ User message:
9363
9268
  modeLabel: liveModeLabel || void 0
9364
9269
  }
9365
9270
  ),
9366
- managedLive ? /* @__PURE__ */ jsx(
9367
- KeyHintBar,
9368
- {
9369
- context: {
9370
- confirm: state.confirmQueue.length > 0,
9371
- picker: state.picker.open || state.slashPicker.open || state.modelPicker.open || state.autonomyPicker.open || state.settingsPicker.open || !!state.rewindOverlay,
9372
- monitor: state.agentsMonitorOpen || state.monitorOpen || state.worktreeMonitorOpen || state.todosMonitorOpen || !!state.autoPhase?.monitorOpen,
9373
- managed: managedLive
9374
- }
9375
- }
9376
- ) : null,
9377
- state.helpOpen ? /* @__PURE__ */ jsx(HelpOverlay, { managed: managedLive }) : null,
9271
+ state.helpOpen ? /* @__PURE__ */ jsx(HelpOverlay, {}) : null,
9378
9272
  state.agentsMonitorOpen ? /* @__PURE__ */ jsx(
9379
9273
  AgentsMonitor,
9380
9274
  {
@@ -9401,7 +9295,7 @@ User message:
9401
9295
  nowTick,
9402
9296
  onClose: () => dispatch({ type: "worktreeMonitorToggle" })
9403
9297
  }
9404
- ) : state.todosMonitorOpen && !managedLive ? /* @__PURE__ */ jsx(TodosMonitor, { todos: agent.ctx.todos }) : state.monitorOpen ? /* @__PURE__ */ jsx(
9298
+ ) : state.todosMonitorOpen ? /* @__PURE__ */ jsx(TodosMonitor, { todos: agent.ctx.todos }) : state.monitorOpen ? /* @__PURE__ */ jsx(
9405
9299
  FleetMonitor,
9406
9300
  {
9407
9301
  entries: state.fleet,
@@ -9531,9 +9425,6 @@ function startTerminalTitle(opts) {
9531
9425
  // src/run-tui.ts
9532
9426
  var BRACKETED_PASTE_ON = "\x1B[?2004h";
9533
9427
  var BRACKETED_PASTE_OFF = "\x1B[?2004l";
9534
- var ALT_SCREEN_ON = "\x1B[?1049h";
9535
- var ALT_SCREEN_OFF = "\x1B[?1049l";
9536
- var CURSOR_HOME = "\x1B[H";
9537
9428
  async function runTui(opts) {
9538
9429
  const stdout = process.stdout;
9539
9430
  const stdin = process.stdin;
@@ -9543,12 +9434,8 @@ async function runTui(opts) {
9543
9434
  );
9544
9435
  return 2;
9545
9436
  }
9546
- const useAltScreen = opts.altScreen === true;
9547
- if (useAltScreen) {
9548
- stdout.write(ALT_SCREEN_ON);
9549
- stdout.write(CURSOR_HOME);
9550
- }
9551
9437
  stdout.write(BRACKETED_PASTE_ON);
9438
+ stdout.write("\x1B[2J\x1B[H");
9552
9439
  const inkStdin = stdin;
9553
9440
  const stopTitle = opts.titleAnimation !== false ? startTerminalTitle({ stdout, events: opts.events, model: opts.model }) : (() => {
9554
9441
  });
@@ -9571,9 +9458,6 @@ async function runTui(opts) {
9571
9458
  }
9572
9459
  try {
9573
9460
  stdout.write(BRACKETED_PASTE_OFF);
9574
- if (useAltScreen) {
9575
- stdout.write(ALT_SCREEN_OFF);
9576
- }
9577
9461
  } catch {
9578
9462
  }
9579
9463
  };
@@ -9600,12 +9484,6 @@ async function runTui(opts) {
9600
9484
  const settle = (code) => {
9601
9485
  cleanup();
9602
9486
  detachListeners();
9603
- if (useAltScreen && opts.onAfterExit) {
9604
- try {
9605
- opts.onAfterExit();
9606
- } catch {
9607
- }
9608
- }
9609
9487
  resolve(code);
9610
9488
  };
9611
9489
  let instance;
@@ -9641,7 +9519,7 @@ async function runTui(opts) {
9641
9519
  onExit,
9642
9520
  director: opts.director ?? null,
9643
9521
  fleetRoster: opts.fleetRoster,
9644
- onClearHistory: opts.onClearHistory ? (dispatch) => opts.onClearHistory(dispatch) : void 0,
9522
+ onClearHistory: opts.onClearHistory ? (dispatch) => opts.onClearHistory?.(dispatch) : void 0,
9645
9523
  fleetStreamController: opts.fleetStreamController,
9646
9524
  enhanceController: opts.enhanceController,
9647
9525
  enhanceEnabled: opts.enhanceController?.enabled ?? true,
@@ -9657,9 +9535,6 @@ async function runTui(opts) {
9657
9535
  getSettings: opts.getSettings,
9658
9536
  saveSettings: opts.saveSettings,
9659
9537
  predictNext: opts.predictNext,
9660
- // Managed viewport (in-app scroll + collapsibility) follows
9661
- // alt-screen: it owns the screen, so there's no native-scrollback leak.
9662
- managed: useAltScreen,
9663
9538
  chime: opts.chime,
9664
9539
  confirmExit: opts.confirmExit,
9665
9540
  modeLabel: opts.modeLabel,
@@ -9676,16 +9551,14 @@ async function runTui(opts) {
9676
9551
  return;
9677
9552
  }
9678
9553
  let detachResize = null;
9679
- if (!useAltScreen) {
9680
- const onResize = () => {
9681
- try {
9682
- stdout.write("\x1B[J");
9683
- } catch {
9684
- }
9685
- };
9686
- stdout.on("resize", onResize);
9687
- detachResize = () => stdout.off("resize", onResize);
9688
- }
9554
+ const onResize = () => {
9555
+ try {
9556
+ stdout.write("\x1B[J");
9557
+ } catch {
9558
+ }
9559
+ };
9560
+ stdout.on("resize", onResize);
9561
+ detachResize = () => stdout.off("resize", onResize);
9689
9562
  instance.waitUntilExit().then(() => {
9690
9563
  detachResize?.();
9691
9564
  settle(exitCode);