@botconnector/bccli 0.4.5 → 0.4.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.
@@ -37,10 +37,10 @@ import {
37
37
  } from "./chunk-27REMLDV.js";
38
38
  import {
39
39
  Session
40
- } from "./chunk-YNI4GHBI.js";
40
+ } from "./chunk-BL2KOEXJ.js";
41
41
  import {
42
42
  contentText
43
- } from "./chunk-BR63GX6T.js";
43
+ } from "./chunk-BBZLRWPR.js";
44
44
  import {
45
45
  REASONING_LEVELS,
46
46
  assertReasoningSupported,
@@ -77,7 +77,7 @@ import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, wr
77
77
  import { homedir, tmpdir } from "os";
78
78
  import { dirname, join as join3, relative as relative2, resolve as resolve2 } from "path";
79
79
  import { Box as Box13, Static, Text as Text16, measureElement, useApp, useInput as useInput9, useStdin, useStdout as useStdout2 } from "ink";
80
- import { useCallback, useEffect as useEffect3, useRef, useState as useState8 } from "react";
80
+ import { useCallback, useEffect as useEffect3, useRef as useRef2, useState as useState10 } from "react";
81
81
 
82
82
  // src/commands.ts
83
83
  var SLASH_COMMANDS = [
@@ -775,14 +775,38 @@ function masked(s) {
775
775
 
776
776
  // src/ui/EditableText.tsx
777
777
  import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
778
+ var MAX_VISIBLE_LINES = 10;
779
+ function windowedState(s) {
780
+ const lines = s.value.split("\n");
781
+ if (lines.length <= MAX_VISIBLE_LINES) return { view: s, hiddenAbove: 0, hiddenBelow: 0 };
782
+ const cursorLine = s.value.slice(0, s.cursor).split("\n").length - 1;
783
+ const startLine = Math.max(0, Math.min(cursorLine - Math.floor(MAX_VISIBLE_LINES / 2), lines.length - MAX_VISIBLE_LINES));
784
+ const endLine = Math.min(lines.length, startLine + MAX_VISIBLE_LINES);
785
+ let charStart = 0;
786
+ for (let i = 0; i < startLine; i++) charStart += lines[i].length + 1;
787
+ const sliced = lines.slice(startLine, endLine).join("\n");
788
+ const relCursor = Math.max(0, Math.min(sliced.length, s.cursor - charStart));
789
+ return {
790
+ view: { value: sliced, cursor: relCursor },
791
+ hiddenAbove: startLine,
792
+ hiddenBelow: lines.length - endLine
793
+ };
794
+ }
778
795
  function EditableText({ state, mask = false, showCursor = true }) {
779
- const view = mask ? masked(state) : state;
780
- if (!showCursor) return /* @__PURE__ */ jsx2(Fragment, { children: view.value });
796
+ const raw = mask ? masked(state) : state;
797
+ const { view, hiddenAbove, hiddenBelow } = windowedState(raw);
798
+ const prefix = hiddenAbove > 0 ? `\u2026 (+${hiddenAbove} lines above)
799
+ ` : "";
800
+ const suffix = hiddenBelow > 0 ? `
801
+ \u2026 (+${hiddenBelow} lines below)` : "";
802
+ if (!showCursor) return /* @__PURE__ */ jsx2(Fragment, { children: `${prefix}${view.value}${suffix}` });
781
803
  const { before, at, after } = splitAtCursor(view);
782
804
  return /* @__PURE__ */ jsxs2(Fragment, { children: [
805
+ prefix ? /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: prefix }) : null,
783
806
  before,
784
807
  /* @__PURE__ */ jsx2(Text2, { inverse: true, children: at }),
785
- after
808
+ after,
809
+ suffix ? /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: suffix }) : null
786
810
  ] });
787
811
  }
788
812
 
@@ -819,8 +843,18 @@ function clickedThinking(click, cursorRow, rootHeight, target) {
819
843
  const col = target.x + 1;
820
844
  return (click.y === row || click.y === row - 1) && click.x >= col && click.x < col + Math.min(target.width, 46);
821
845
  }
846
+ function parseMouseWheel(input) {
847
+ const match = /^\[<?(\d+);\d+;\d+[mM]$/.exec(input);
848
+ if (!match) return void 0;
849
+ const button = Number(match[1]);
850
+ if ((button & 64) === 0) return void 0;
851
+ const dir = button & 3;
852
+ if (dir === 0) return "up";
853
+ if (dir === 1) return "down";
854
+ return void 0;
855
+ }
822
856
  function isMouseControlInput(input) {
823
- return /^\[<(?:\d+;){2}\d+[mM]$/.test(input) || /^\[(?:\d+;)\d+R$/.test(input);
857
+ return /^\[<[\d;]*[mM]?$/.test(input) || /^\[(?:\d+;){2}\d+[mM]$/.test(input) || /^\[(?:\d+;)+\d*R?$/.test(input) || /^\[M[\s\S]{0,3}$/.test(input) || input.includes("\x1B[<");
824
858
  }
825
859
  function parseThinkingReports(value) {
826
860
  const events = [];
@@ -875,9 +909,20 @@ function ListPicker({ title, items, onPick }) {
875
909
  const shown = items.filter((i) => i.label.toLowerCase().includes(filter.toLowerCase()));
876
910
  const cursor = Math.min(index, Math.max(0, shown.length - 1));
877
911
  useInput2((input, key) => {
912
+ const wheel = parseMouseWheel(input);
913
+ if (wheel === "up") {
914
+ setIndex(Math.max(0, cursor - 1));
915
+ return;
916
+ }
917
+ if (wheel === "down") {
918
+ setIndex(Math.min(Math.max(0, shown.length - 1), cursor + 1));
919
+ return;
920
+ }
878
921
  if (isMouseControlInput(input)) return;
879
922
  if (key.upArrow) setIndex(Math.max(0, cursor - 1));
880
- else if (key.downArrow) setIndex(Math.min(shown.length - 1, cursor + 1));
923
+ else if (key.downArrow) setIndex(Math.min(Math.max(0, shown.length - 1), cursor + 1));
924
+ else if (key.pageUp) setIndex(Math.max(0, cursor - VISIBLE));
925
+ else if (key.pageDown) setIndex(Math.min(Math.max(0, shown.length - 1), cursor + VISIBLE));
881
926
  else if (key.return && !shown[cursor]?.disabled) onPick(shown[cursor]?.id);
882
927
  else if (key.escape) onPick(void 0);
883
928
  else if (key.backspace || key.delete) {
@@ -917,11 +962,23 @@ function ModelPicker({ groups, current, onPick }) {
917
962
  for (const m of models) rows.push({ kind: "model", ref: `${g.providerId}/${m}`, label: m });
918
963
  }
919
964
  const selectable = rows.flatMap((r, i) => r.kind === "model" ? [i] : []);
920
- const cursor = selectable[Math.min(index, selectable.length - 1)] ?? -1;
965
+ const clampedIndex = Math.min(index, Math.max(0, selectable.length - 1));
966
+ const cursor = selectable[clampedIndex] ?? -1;
921
967
  useInput3((input, key) => {
968
+ const wheel = parseMouseWheel(input);
969
+ if (wheel === "up") {
970
+ setIndex(Math.max(0, clampedIndex - 1));
971
+ return;
972
+ }
973
+ if (wheel === "down") {
974
+ setIndex(Math.min(Math.max(0, selectable.length - 1), clampedIndex + 1));
975
+ return;
976
+ }
922
977
  if (isMouseControlInput(input)) return;
923
- if (key.upArrow) setIndex((i) => Math.max(0, i - 1));
924
- else if (key.downArrow) setIndex((i) => Math.min(selectable.length - 1, i + 1));
978
+ if (key.upArrow) setIndex(Math.max(0, clampedIndex - 1));
979
+ else if (key.downArrow) setIndex(Math.min(Math.max(0, selectable.length - 1), clampedIndex + 1));
980
+ else if (key.pageUp) setIndex(Math.max(0, clampedIndex - VISIBLE2));
981
+ else if (key.pageDown) setIndex(Math.min(Math.max(0, selectable.length - 1), clampedIndex + VISIBLE2));
925
982
  else if (key.return) {
926
983
  const row = rows[cursor];
927
984
  if (row?.kind === "model") onPick(row.ref);
@@ -952,9 +1009,21 @@ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
952
1009
  function ProviderMenu({ entries, onPick }) {
953
1010
  const rows = [...entries.map((e) => e.id), "custom"];
954
1011
  const [index, setIndex] = useState4(0);
955
- useInput4((_input, key) => {
1012
+ useInput4((input, key) => {
1013
+ const wheel = parseMouseWheel(input);
1014
+ if (wheel === "up") {
1015
+ setIndex((i) => Math.max(0, i - 1));
1016
+ return;
1017
+ }
1018
+ if (wheel === "down") {
1019
+ setIndex((i) => Math.min(rows.length - 1, i + 1));
1020
+ return;
1021
+ }
1022
+ if (isMouseControlInput(input) || isFocusReport(input)) return;
956
1023
  if (key.upArrow) setIndex((i) => Math.max(0, i - 1));
957
1024
  else if (key.downArrow) setIndex((i) => Math.min(rows.length - 1, i + 1));
1025
+ else if (key.pageUp) setIndex((i) => Math.max(0, i - 10));
1026
+ else if (key.pageDown) setIndex((i) => Math.min(rows.length - 1, i + 10));
958
1027
  else if (key.return) onPick(rows[index]);
959
1028
  else if (key.escape) onPick(void 0);
960
1029
  });
@@ -970,6 +1039,7 @@ function ProviderMenu({ entries, onPick }) {
970
1039
 
971
1040
  // src/ui/PermissionPrompt.tsx
972
1041
  import { Box as Box7, Text as Text8, useInput as useInput5 } from "ink";
1042
+ import { useState as useState5 } from "react";
973
1043
 
974
1044
  // src/ui/DiffView.tsx
975
1045
  import { Box as Box6, Text as Text7 } from "ink";
@@ -985,11 +1055,41 @@ function DiffView({ diff }) {
985
1055
  // src/ui/PermissionPrompt.tsx
986
1056
  import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
987
1057
  var VERB = { edit: "edit", write: "write", bash: "run", fetch: "fetch" };
1058
+ var MAX_DIFF_LINES = 16;
988
1059
  function PermissionPrompt({
989
1060
  request,
990
1061
  onAnswer
991
1062
  }) {
1063
+ const diffLines = request.preview ? request.preview.split("\n") : [];
1064
+ const maxOffset = Math.max(0, diffLines.length - MAX_DIFF_LINES);
1065
+ const [offset, setOffset] = useState5(0);
992
1066
  useInput5((input, key) => {
1067
+ const wheel = parseMouseWheel(input);
1068
+ if (wheel === "up") {
1069
+ setOffset((o) => Math.max(0, o - 2));
1070
+ return;
1071
+ }
1072
+ if (wheel === "down") {
1073
+ setOffset((o) => Math.min(maxOffset, o + 2));
1074
+ return;
1075
+ }
1076
+ if (isMouseControlInput(input) || isFocusReport(input)) return;
1077
+ if (key.upArrow) {
1078
+ setOffset((o) => Math.max(0, o - 1));
1079
+ return;
1080
+ }
1081
+ if (key.downArrow) {
1082
+ setOffset((o) => Math.min(maxOffset, o + 1));
1083
+ return;
1084
+ }
1085
+ if (key.pageUp) {
1086
+ setOffset((o) => Math.max(0, o - MAX_DIFF_LINES));
1087
+ return;
1088
+ }
1089
+ if (key.pageDown) {
1090
+ setOffset((o) => Math.min(maxOffset, o + MAX_DIFF_LINES));
1091
+ return;
1092
+ }
993
1093
  const k2 = input.toLowerCase();
994
1094
  if (k2 === "y") onAnswer("yes");
995
1095
  else if (k2 === "a" && request.sessionRules?.length) onAnswer("session");
@@ -998,9 +1098,11 @@ function PermissionPrompt({
998
1098
  });
999
1099
  const rules = request.sessionRules ?? [];
1000
1100
  const sessionLabel = !rules.length ? null : request.kind === "edit" ? t("yes for all edits in this project, this session") : t("yes this session for {rules}", { rules: rules.join(", ") });
1101
+ const visibleDiff = diffLines.length > MAX_DIFF_LINES ? diffLines.slice(offset, offset + MAX_DIFF_LINES).join("\n") : request.preview;
1001
1102
  return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", borderStyle: "round", borderColor: color("yellow"), paddingX: 1, children: [
1002
1103
  /* @__PURE__ */ jsx8(Text8, { bold: true, children: `${request.agent ? `[${request.agent}] ` : ""}${t("Allow {verb} {target}?", { verb: t(VERB[request.tool] ?? request.tool), target: request.target })}` }),
1003
- request.preview ? /* @__PURE__ */ jsx8(DiffView, { diff: request.preview }) : null,
1104
+ visibleDiff ? /* @__PURE__ */ jsx8(DiffView, { diff: visibleDiff }) : null,
1105
+ diffLines.length > MAX_DIFF_LINES ? /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: `\u2191\u2193 scroll (${offset + 1}\u2013${Math.min(diffLines.length, offset + MAX_DIFF_LINES)} / ${diffLines.length})` }) : null,
1004
1106
  /* @__PURE__ */ jsxs7(Text8, { children: [
1005
1107
  /* @__PURE__ */ jsx8(Text8, { color: color("green"), children: t("[y] yes") }),
1006
1108
  " ",
@@ -1013,19 +1115,19 @@ function PermissionPrompt({
1013
1115
 
1014
1116
  // src/ui/PromptInput.tsx
1015
1117
  import { Box as Box8, Text as Text9, useInput as useInput6 } from "ink";
1016
- import { useEffect, useState as useState5 } from "react";
1118
+ import { useEffect, useRef, useState as useState6 } from "react";
1017
1119
 
1018
1120
  // src/ui/complete.ts
1019
1121
  import { existsSync, globSync, readFileSync, statSync } from "fs";
1020
1122
  import { isAbsolute, join, relative, resolve } from "path";
1021
1123
  var MAX_ATTACH_BYTES = 32768;
1022
- function completeFile(value, cwd) {
1124
+ function listFileSuggestions(value, cwd, limit = 8) {
1023
1125
  const match = /(^|\s)@([^\s]*)$/.exec(value);
1024
- if (!match) return value;
1126
+ if (!match) return [];
1025
1127
  const prefix = match[2];
1026
1128
  const escaped = prefix.replace(/([*?[\]{}()!])/g, "\\$1");
1027
1129
  const pattern = `${escaped}*`;
1028
- if (validateGlobPattern(pattern)) return value;
1130
+ if (validateGlobPattern(pattern)) return [];
1029
1131
  const exclude = ignorePatterns(cwd);
1030
1132
  const formatEntry = (entry2) => {
1031
1133
  const normalized = normalizeGlobPath(entry2);
@@ -1035,17 +1137,30 @@ function completeFile(value, cwd) {
1035
1137
  return normalized;
1036
1138
  }
1037
1139
  };
1038
- let found = globSync(pattern, { cwd, exclude }).map(formatEntry).sort();
1039
- if (!found.length && prefix && !prefix.includes("/")) {
1040
- const deepPattern = `**/*${escaped}*`;
1041
- if (!validateGlobPattern(deepPattern)) {
1042
- found = globSync(deepPattern, { cwd, exclude }).map(formatEntry).sort((a, b) => a.split("/").length - b.split("/").length || a.localeCompare(b));
1140
+ try {
1141
+ let found = globSync(pattern, { cwd, exclude }).map(formatEntry).sort();
1142
+ if (!found.length && prefix && !prefix.includes("/")) {
1143
+ const deepPattern = `**/*${escaped}*`;
1144
+ if (!validateGlobPattern(deepPattern)) {
1145
+ found = globSync(deepPattern, { cwd, exclude }).map(formatEntry).sort((a, b) => a.split("/").length - b.split("/").length || a.localeCompare(b));
1146
+ }
1043
1147
  }
1148
+ return found.slice(0, limit);
1149
+ } catch {
1150
+ return [];
1044
1151
  }
1045
- if (!found.length) return value;
1046
- const pick = found[0];
1152
+ }
1153
+ function applyFileSuggestion(value, pick) {
1154
+ const match = /(^|\s)@([^\s]*)$/.exec(value);
1155
+ if (!match) return value;
1156
+ const prefix = match[2];
1047
1157
  return `${value.slice(0, value.length - prefix.length)}${pick}${pick.endsWith("/") ? "" : " "}`;
1048
1158
  }
1159
+ function completeFile(value, cwd) {
1160
+ const found = listFileSuggestions(value, cwd, 1);
1161
+ if (!found.length) return value;
1162
+ return applyFileSuggestion(value, found[0]);
1163
+ }
1049
1164
  function expandAtFileMentions(prompt, cwd, readFiles) {
1050
1165
  const matches = [...prompt.matchAll(/(?:^|\s)@([^\s]+)/g)];
1051
1166
  if (!matches.length) return prompt;
@@ -1181,14 +1296,15 @@ ${s.value.slice(start)}`, cursor: start }, mode: "insert", handled: true };
1181
1296
  // src/ui/PromptInput.tsx
1182
1297
  import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
1183
1298
  function PromptInput({ disabled, history, cwd, onSubmit, extraCommands, injected, vim }) {
1184
- const [line, setLine] = useState5({ value: "", cursor: 0 });
1299
+ const [line, setLine] = useState6({ value: "", cursor: 0 });
1185
1300
  const { value } = line;
1186
- const [historyIndex, setHistoryIndex] = useState5(null);
1187
- const [selected, setSelected] = useState5(0);
1188
- const [dismissed, setDismissed] = useState5(false);
1189
- const [vimMode, setVimMode] = useState5("insert");
1190
- const [vimPending, setVimPending] = useState5(void 0);
1191
- const injectedSeen = useState5(0);
1301
+ const [historyIndex, setHistoryIndex] = useState6(null);
1302
+ const draftRef = useRef("");
1303
+ const [selected, setSelected] = useState6(0);
1304
+ const [dismissed, setDismissed] = useState6(false);
1305
+ const [vimMode, setVimMode] = useState6("insert");
1306
+ const [vimPending, setVimPending] = useState6(void 0);
1307
+ const injectedSeen = useState6(0);
1192
1308
  useEffect(() => {
1193
1309
  if (injected && injected.n !== injectedSeen[0]) {
1194
1310
  injectedSeen[1](injected.n);
@@ -1198,7 +1314,10 @@ function PromptInput({ disabled, history, cwd, onSubmit, extraCommands, injected
1198
1314
  }, [injected, injectedSeen]);
1199
1315
  const all = [...SLASH_COMMANDS, ...(extraCommands ?? []).filter((c) => !SLASH_COMMANDS.some((b) => b.name === c.name))];
1200
1316
  const suggestions = historyIndex === null && !dismissed && value.startsWith("/") && !/\s/.test(value) ? all.filter((c) => c.name.startsWith(value.slice(1))).slice(0, 8) : [];
1201
- const highlighted = Math.min(selected, Math.max(0, suggestions.length - 1));
1317
+ const beforeCursor = value.slice(0, line.cursor);
1318
+ const fileSuggestions = historyIndex === null && !dismissed && !suggestions.length && /(^|\s)@([^\s]*)$/.test(beforeCursor) ? listFileSuggestions(beforeCursor, cwd, 8) : [];
1319
+ const activeCount = suggestions.length || fileSuggestions.length;
1320
+ const highlighted = Math.min(selected, Math.max(0, activeCount - 1));
1202
1321
  const typed = (text) => ({ value: text, cursor: text.length });
1203
1322
  const edit = (next) => {
1204
1323
  setLine(next);
@@ -1208,6 +1327,13 @@ function PromptInput({ disabled, history, cwd, onSubmit, extraCommands, injected
1208
1327
  };
1209
1328
  useInput6(
1210
1329
  (input, key) => {
1330
+ const wheel = parseMouseWheel(input);
1331
+ if (wheel) {
1332
+ if (activeCount > 0) {
1333
+ setSelected((current) => wheel === "up" ? Math.max(0, Math.min(current, activeCount - 1) - 1) : Math.min(activeCount - 1, Math.min(current, activeCount - 1) + 1));
1334
+ }
1335
+ return;
1336
+ }
1211
1337
  if (isMouseControlInput(input)) return;
1212
1338
  if (suggestions.length) {
1213
1339
  if (key.escape) {
@@ -1226,13 +1352,33 @@ function PromptInput({ disabled, history, cwd, onSubmit, extraCommands, injected
1226
1352
  edit(typed(`/${suggestions[highlighted].name} `));
1227
1353
  return;
1228
1354
  }
1229
- if (key.return && !key.meta) {
1355
+ if (key.return && !key.meta && !key.shift) {
1230
1356
  edit(typed(""));
1231
1357
  setHistoryIndex(null);
1358
+ draftRef.current = "";
1232
1359
  onSubmit(`/${suggestions[highlighted].name}`);
1233
1360
  return;
1234
1361
  }
1235
1362
  }
1363
+ if (fileSuggestions.length) {
1364
+ if (key.escape) {
1365
+ setDismissed(true);
1366
+ return;
1367
+ }
1368
+ if (key.upArrow) {
1369
+ setSelected(Math.max(0, highlighted - 1));
1370
+ return;
1371
+ }
1372
+ if (key.downArrow) {
1373
+ setSelected(Math.min(fileSuggestions.length - 1, highlighted + 1));
1374
+ return;
1375
+ }
1376
+ if (key.tab && !key.shift) {
1377
+ const completed = applyFileSuggestion(beforeCursor, fileSuggestions[highlighted]);
1378
+ edit({ value: completed + value.slice(line.cursor), cursor: completed.length });
1379
+ return;
1380
+ }
1381
+ }
1236
1382
  if (vim) {
1237
1383
  const r = applyVim(line, vimMode, input, key, vimPending);
1238
1384
  if (r.handled) {
@@ -1243,7 +1389,7 @@ function PromptInput({ disabled, history, cwd, onSubmit, extraCommands, injected
1243
1389
  return;
1244
1390
  }
1245
1391
  }
1246
- if (key.return && key.meta || key.ctrl && input.toLowerCase() === "j") {
1392
+ if (key.return && (key.meta || key.shift) || key.ctrl && input.toLowerCase() === "j" || input === "\n") {
1247
1393
  edit((current) => insert(current, "\n"));
1248
1394
  return;
1249
1395
  }
@@ -1258,6 +1404,7 @@ ${value.slice(line.cursor)}`, cursor: before.length });
1258
1404
  if (!text2) return;
1259
1405
  edit(typed(""));
1260
1406
  setHistoryIndex(null);
1407
+ draftRef.current = "";
1261
1408
  setVimMode("insert");
1262
1409
  setVimPending(void 0);
1263
1410
  onSubmit(text2);
@@ -1277,6 +1424,7 @@ ${value.slice(line.cursor)}`, cursor: before.length });
1277
1424
  }
1278
1425
  if (key.upArrow) {
1279
1426
  if (!history.length) return;
1427
+ if (historyIndex === null) draftRef.current = value;
1280
1428
  const i = historyIndex === null ? history.length - 1 : Math.max(0, historyIndex - 1);
1281
1429
  setHistoryIndex(i);
1282
1430
  setLine(typed(history[i]));
@@ -1286,7 +1434,7 @@ ${value.slice(line.cursor)}`, cursor: before.length });
1286
1434
  if (historyIndex === null) return;
1287
1435
  const i = historyIndex + 1;
1288
1436
  setHistoryIndex(i >= history.length ? null : i);
1289
- setLine(typed(i >= history.length ? "" : history[i]));
1437
+ setLine(typed(i >= history.length ? draftRef.current : history[i]));
1290
1438
  return;
1291
1439
  }
1292
1440
  if (key.tab && !key.shift) {
@@ -1310,17 +1458,18 @@ ${value.slice(line.cursor)}`, cursor: before.length });
1310
1458
  t("NORMAL")
1311
1459
  ] })
1312
1460
  ] }) }),
1313
- suggestions.map((s, i) => /* @__PURE__ */ jsx9(Text9, { color: i === highlighted ? color("green") : void 0, dimColor: i !== highlighted, children: `${i === highlighted ? "\u203A" : " "} /${s.name} ${t(s.description)}` }, s.name))
1461
+ suggestions.map((s, i) => /* @__PURE__ */ jsx9(Text9, { color: i === highlighted ? color("green") : void 0, dimColor: i !== highlighted, children: `${i === highlighted ? "\u203A" : " "} /${s.name} ${t(s.description)}` }, s.name)),
1462
+ fileSuggestions.map((f, i) => /* @__PURE__ */ jsx9(Text9, { color: i === highlighted ? color("green") : void 0, dimColor: i !== highlighted, children: `${i === highlighted ? "\u203A" : " "} @${f}` }, f))
1314
1463
  ] });
1315
1464
  }
1316
1465
 
1317
1466
  // src/ui/Spinner.tsx
1318
1467
  import { Text as Text10 } from "ink";
1319
- import { useEffect as useEffect2, useState as useState6 } from "react";
1468
+ import { useEffect as useEffect2, useState as useState7 } from "react";
1320
1469
  import { jsx as jsx10 } from "react/jsx-runtime";
1321
1470
  var FRAMES = ["\xB7", "\u2722", "\u2733", "\u2736", "\u273B", "\u273D"];
1322
1471
  function Spinner({ label, startedAt }) {
1323
- const [tick, setTick] = useState6(0);
1472
+ const [tick, setTick] = useState7(0);
1324
1473
  useEffect2(() => {
1325
1474
  const id = setInterval(() => setTick((t2) => t2 + 1), 120);
1326
1475
  return () => clearInterval(id);
@@ -1348,14 +1497,26 @@ function StatusBar({ mode, tokens, busy, model, reasoning = "auto" }) {
1348
1497
 
1349
1498
  // src/ui/McpMenu.tsx
1350
1499
  import { Box as Box9, Text as Text12, useInput as useInput7 } from "ink";
1351
- import { useState as useState7 } from "react";
1500
+ import { useState as useState8 } from "react";
1352
1501
  import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
1353
1502
  var MARK = { ready: "\u2713", error: "\u2717", starting: "\u2026", off: "\u25CB" };
1354
1503
  function McpMenu({ items, onPick }) {
1355
- const [index, setIndex] = useState7(0);
1356
- useInput7((_input, key) => {
1504
+ const [index, setIndex] = useState8(0);
1505
+ useInput7((input, key) => {
1506
+ const wheel = parseMouseWheel(input);
1507
+ if (wheel === "up") {
1508
+ setIndex((i) => Math.max(0, i - 1));
1509
+ return;
1510
+ }
1511
+ if (wheel === "down") {
1512
+ setIndex((i) => Math.min(items.length - 1, i + 1));
1513
+ return;
1514
+ }
1515
+ if (isMouseControlInput(input) || isFocusReport(input)) return;
1357
1516
  if (key.upArrow) setIndex((i) => Math.max(0, i - 1));
1358
1517
  else if (key.downArrow) setIndex((i) => Math.min(items.length - 1, i + 1));
1518
+ else if (key.pageUp) setIndex((i) => Math.max(0, i - 10));
1519
+ else if (key.pageDown) setIndex((i) => Math.min(items.length - 1, i + 10));
1359
1520
  else if (key.return) onPick(items[index]?.name);
1360
1521
  else if (key.escape) onPick(void 0);
1361
1522
  });
@@ -1370,17 +1531,50 @@ function McpMenu({ items, onPick }) {
1370
1531
 
1371
1532
  // src/ui/PlanApproval.tsx
1372
1533
  import { Box as Box10, Text as Text13, useInput as useInput8 } from "ink";
1534
+ import { useState as useState9 } from "react";
1373
1535
  import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
1536
+ var MAX_PLAN_LINES = 16;
1374
1537
  function PlanApproval({ plan, onAnswer }) {
1538
+ const planLines = plan.split("\n");
1539
+ const maxOffset = Math.max(0, planLines.length - MAX_PLAN_LINES);
1540
+ const [offset, setOffset] = useState9(0);
1375
1541
  useInput8((input, key) => {
1542
+ const wheel = parseMouseWheel(input);
1543
+ if (wheel === "up") {
1544
+ setOffset((o) => Math.max(0, o - 2));
1545
+ return;
1546
+ }
1547
+ if (wheel === "down") {
1548
+ setOffset((o) => Math.min(maxOffset, o + 2));
1549
+ return;
1550
+ }
1551
+ if (isMouseControlInput(input) || isFocusReport(input)) return;
1552
+ if (key.upArrow) {
1553
+ setOffset((o) => Math.max(0, o - 1));
1554
+ return;
1555
+ }
1556
+ if (key.downArrow) {
1557
+ setOffset((o) => Math.min(maxOffset, o + 1));
1558
+ return;
1559
+ }
1560
+ if (key.pageUp) {
1561
+ setOffset((o) => Math.max(0, o - MAX_PLAN_LINES));
1562
+ return;
1563
+ }
1564
+ if (key.pageDown) {
1565
+ setOffset((o) => Math.min(maxOffset, o + MAX_PLAN_LINES));
1566
+ return;
1567
+ }
1376
1568
  const k2 = input.toLowerCase();
1377
1569
  if (k2 === "a") onAnswer("acceptEdits");
1378
1570
  else if (k2 === "y") onAnswer("default");
1379
1571
  else if (k2 === "n" || key.escape) onAnswer("no");
1380
1572
  });
1573
+ const visiblePlan = planLines.length > MAX_PLAN_LINES ? planLines.slice(offset, offset + MAX_PLAN_LINES).join("\n") : plan;
1381
1574
  return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
1382
1575
  /* @__PURE__ */ jsx13(Text13, { bold: true, children: t("Plan from the agent") }),
1383
- /* @__PURE__ */ jsx13(Markdown, { text: plan, indent: 5 }),
1576
+ /* @__PURE__ */ jsx13(Markdown, { text: visiblePlan, indent: 5 }),
1577
+ planLines.length > MAX_PLAN_LINES ? /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: `\u2191\u2193 scroll (${offset + 1}\u2013${Math.min(planLines.length, offset + MAX_PLAN_LINES)} / ${planLines.length})` }) : null,
1384
1578
  /* @__PURE__ */ jsxs10(Text13, { children: [
1385
1579
  /* @__PURE__ */ jsx13(Text13, { color: color("green"), children: t("[a] yes, auto-edit") }),
1386
1580
  " ",
@@ -1402,8 +1596,9 @@ function TodoList({ items }) {
1402
1596
 
1403
1597
  // src/ui/ToolBlock.tsx
1404
1598
  import { Box as Box12, Text as Text15 } from "ink";
1405
- import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
1599
+ import { Fragment as Fragment2, jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
1406
1600
  var PREVIEW_LINES = 5;
1601
+ var DIFF_PREVIEW_LINES = 20;
1407
1602
  function ToolBlock({ tool, target, output, display, isError, done, expanded, sub, moreBind = "ctrl+o" }) {
1408
1603
  const isTask = tool === "task";
1409
1604
  const isDiff = !!display && /^\s*\d+ [+\- ] /.test(display);
@@ -1421,7 +1616,14 @@ function ToolBlock({ tool, target, output, display, isError, done, expanded, sub
1421
1616
  sub.length > 6 ? /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: t("\u2026 {n} earlier steps", { n: sub.length - 6 }) }) : null
1422
1617
  ] }) : null,
1423
1618
  isTask && done ? /* @__PURE__ */ jsx15(Box12, { marginLeft: 3, children: /* @__PURE__ */ jsx15(Text15, { color: isError ? color("red") : color("green"), children: `${isError ? t("\u2717 failed") : t("\u2713 done")}${display ? ` \xB7 ${display}` : ""}` }) }) : null,
1424
- isDiff ? /* @__PURE__ */ jsx15(Box12, { marginLeft: 3, children: /* @__PURE__ */ jsx15(DiffView, { diff: display }) }) : output && (isError || tool === "bash" || expanded) && !(isTask && !isError) ? /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", marginLeft: 3, children: [
1619
+ isDiff ? /* @__PURE__ */ jsx15(Box12, { flexDirection: "column", marginLeft: 3, children: (() => {
1620
+ const diffLines = display.split("\n");
1621
+ const shownDiff = expanded ? diffLines : diffLines.slice(0, DIFF_PREVIEW_LINES);
1622
+ return /* @__PURE__ */ jsxs11(Fragment2, { children: [
1623
+ /* @__PURE__ */ jsx15(DiffView, { diff: shownDiff.join("\n") }),
1624
+ diffLines.length > shownDiff.length ? /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: t("\u2026 {n} more lines ({bind})", { n: diffLines.length - shownDiff.length, bind: moreBind }) }) : expanded && diffLines.length > DIFF_PREVIEW_LINES ? /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: t("\u2026 ({bind} to collapse)", { bind: moreBind }) }) : null
1625
+ ] });
1626
+ })() }) : output && (isError || tool === "bash" || expanded) && !(isTask && !isError) ? /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", marginLeft: 3, children: [
1425
1627
  shown.map((line, i) => /* @__PURE__ */ jsx15(Text15, { dimColor: !isError, color: isError ? color("red") : void 0, children: line }, i)),
1426
1628
  lines.length > shown.length ? /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: t("\u2026 {n} more lines ({bind})", { n: lines.length - shown.length, bind: moreBind }) }) : expanded && lines.length > PREVIEW_LINES ? /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: t("\u2026 ({bind} to collapse)", { bind: moreBind }) }) : null
1427
1629
  ] }) : null
@@ -1470,7 +1672,10 @@ function applyEvent(t2, event) {
1470
1672
  case "text": {
1471
1673
  const last = t2.live.at(-1);
1472
1674
  if (last?.kind === "assistant") return flushLines({ ...t2, live: [...t2.live.slice(0, -1), { ...last, text: last.text + event.delta }] });
1473
- return flushLines({ ...t2, live: [...t2.live, entry({ kind: "assistant", text: event.delta })] });
1675
+ return flushLines({
1676
+ done: [...t2.done, ...t2.live.filter((e) => !isEmptyCont(e))],
1677
+ live: [entry({ kind: "assistant", text: event.delta })]
1678
+ });
1474
1679
  }
1475
1680
  case "thinking": {
1476
1681
  const last = t2.live.at(-1);
@@ -1618,54 +1823,56 @@ var INIT_PROMPT = "Study this project (folder structure, package/build files, te
1618
1823
  var labelFor = (runtime, ref) => `${ref.slice(ref.indexOf("/") + 1)} \xB7 ${runtime.providerLabel()}`;
1619
1824
  function App({ runtime, initialPrompt, version, updateMessage }) {
1620
1825
  const { exit } = useApp();
1621
- const [transcript, setTranscript] = useState8(() => ({
1826
+ const [transcript, setTranscript] = useState10(() => ({
1622
1827
  done: [entry({ kind: "header" }), ...updateMessage ? [entry({ kind: "notice", text: updateMessage, tone: "warn" })] : [], ...runtime.agent.messages.length ? replayEntries(runtime.session.loadHistory()) : []],
1623
1828
  live: []
1624
1829
  }));
1625
- const [busy, setBusy] = useState8(false);
1626
- const [cancelling, setCancelling] = useState8(false);
1627
- const [startedAt, setStartedAt] = useState8(0);
1628
- const [mode, setMode] = useState8(runtime.agent.permissions.mode);
1629
- const [reasoning, setReasoning] = useState8(runtime.reasoning);
1630
- const [tokens, setTokens] = useState8(runtime.agent.totalUsage.inputTokens + runtime.agent.totalUsage.outputTokens);
1631
- const [asks, setAsks] = useState8([]);
1830
+ const [busy, setBusy] = useState10(false);
1831
+ const [cancelling, setCancelling] = useState10(false);
1832
+ const [startedAt, setStartedAt] = useState10(0);
1833
+ const [mode, setMode] = useState10(runtime.agent.permissions.mode);
1834
+ const [reasoning, setReasoning] = useState10(runtime.reasoning);
1835
+ const [tokens, setTokens] = useState10(runtime.agent.totalUsage.inputTokens + runtime.agent.totalUsage.outputTokens);
1836
+ const [asks, setAsks] = useState10([]);
1632
1837
  const pending = asks[0] ?? null;
1633
- const askId = useRef(0);
1634
- const [picker, setPicker] = useState8(null);
1635
- const [providerMenu, setProviderMenu] = useState8(null);
1636
- const [prompt, setPrompt] = useState8(null);
1838
+ const askId = useRef2(0);
1839
+ const [picker, setPicker] = useState10(null);
1840
+ const [providerMenu, setProviderMenu] = useState10(null);
1841
+ const [prompt, setPrompt] = useState10(null);
1637
1842
  const ask = useCallback((label, mask = false) => new Promise((resolve3) => setPrompt({ label, mask, resolve: resolve3 })), []);
1638
- const [modelLabel, setModelLabel] = useState8(() => labelFor(runtime, runtime.modelRef));
1639
- const [history, setHistory] = useState8([]);
1640
- const controller = useRef(null);
1641
- const [todos, setTodos] = useState8(runtime.todos.items);
1642
- const [mcpMenu, setMcpMenu] = useState8(null);
1643
- const [listPicker, setListPicker] = useState8(null);
1644
- const [planAsk, setPlanAsk] = useState8(null);
1645
- const [queued, setQueued] = useState8([]);
1646
- const [showThinking, setShowThinking] = useState8(runtime.config.thinkingVisible === true);
1647
- const showThinkingRef = useRef(showThinking);
1648
- const [showToolOutput, setShowToolOutput] = useState8(false);
1649
- const [, setConfigRev] = useState8(0);
1650
- const lastUserIdx = transcript.done.findLastIndex((e) => e.kind === "user");
1651
- const staticDone = lastUserIdx > 0 ? transcript.done.slice(0, lastUserIdx) : [];
1652
- const activeTurnDone = lastUserIdx > 0 ? transcript.done.slice(lastUserIdx) : transcript.done;
1653
- const activeTurnEntries = [...activeTurnDone, ...transcript.live];
1654
- const lastThinkingEntryId = activeTurnEntries.findLast((e) => e.kind === "thinking" && !!e.text.trim())?.id;
1655
- const hasThinking = lastThinkingEntryId !== void 0;
1843
+ const [modelLabel, setModelLabel] = useState10(() => labelFor(runtime, runtime.modelRef));
1844
+ const [history, setHistory] = useState10([]);
1845
+ const controller = useRef2(null);
1846
+ const [todos, setTodos] = useState10(runtime.todos.items);
1847
+ const [mcpMenu, setMcpMenu] = useState10(null);
1848
+ const [listPicker, setListPicker] = useState10(null);
1849
+ const [planAsk, setPlanAsk] = useState10(null);
1850
+ const [queued, setQueued] = useState10([]);
1851
+ const [showThinking, setShowThinking] = useState10(runtime.config.thinkingVisible === true);
1852
+ const showThinkingRef = useRef2(showThinking);
1853
+ const [showToolOutput, setShowToolOutput] = useState10(false);
1854
+ const [staticKey, setStaticKey] = useState10(0);
1855
+ const [, setConfigRev] = useState10(0);
1856
+ const liveThinkingEntryId = transcript.live.findLast((e) => e.kind === "thinking" && !!e.text.trim())?.id;
1656
1857
  const { stdin } = useStdin();
1657
1858
  const { stdout } = useStdout2();
1658
- const liveRootRef = useRef(null);
1659
- const thinkingTargetRef = useRef(null);
1660
- const [injected, setInjected] = useState8();
1859
+ const refreshStatic = useCallback(() => {
1860
+ if (stdout?.isTTY && "fd" in stdout) {
1861
+ stdout.write("\x1B[2J\x1B[3J\x1B[H");
1862
+ }
1863
+ setStaticKey((k2) => k2 + 1);
1864
+ }, [stdout]);
1865
+ const liveRootRef = useRef2(null);
1866
+ const thinkingTargetRef = useRef2(null);
1867
+ const [injected, setInjected] = useState10();
1661
1868
  useEffect3(() => runtime.todos.subscribe(setTodos), [runtime]);
1662
1869
  const extraCommands = [
1663
1870
  ...runtime.commands.filter((c) => !SLASH_COMMANDS.some((b) => b.name === c.name.toLowerCase())).map((c) => ({ name: c.name.toLowerCase(), description: c.description ?? t("custom command") })),
1664
1871
  ...runtime.skills.filter((s) => !runtime.commands.some((c) => c.name.toLowerCase() === s.name.toLowerCase())).filter((s) => !SLASH_COMMANDS.some((b) => b.name === s.name.toLowerCase())).map((s) => ({ name: s.name, description: t("skill \xB7 {description}", { description: s.description.slice(0, 50) }) }))
1665
1872
  ];
1666
- const lastTool = useRef(null);
1667
- const toolTargets = useRef(/* @__PURE__ */ new Map());
1668
- const editedInTurn = useRef(false);
1873
+ const lastTool = useRef2(null);
1874
+ const toolTargets = useRef2(/* @__PURE__ */ new Map());
1875
+ const editedInTurn = useRef2(false);
1669
1876
  const notice = useCallback((text, tone = "info") => {
1670
1877
  setTranscript((t2) => ({ ...t2, done: [...t2.done, entry({ kind: "notice", text, tone })] }));
1671
1878
  }, []);
@@ -1752,7 +1959,8 @@ function App({ runtime, initialPrompt, version, updateMessage }) {
1752
1959
  runtime.agent.clear();
1753
1960
  setTokens(0);
1754
1961
  setShowToolOutput(false);
1755
- setTranscript((t2) => ({ ...t2, done: t2.done.filter((e) => e.kind !== "thinking") }));
1962
+ setTranscript({ done: [entry({ kind: "header" })], live: [] });
1963
+ refreshStatic();
1756
1964
  notice(t("Conversation cleared."));
1757
1965
  return;
1758
1966
  case "cost": {
@@ -1848,7 +2056,8 @@ function App({ runtime, initialPrompt, version, updateMessage }) {
1848
2056
  runtime.newSession();
1849
2057
  setTokens(0);
1850
2058
  setShowToolOutput(false);
1851
- setTranscript((t2) => ({ ...t2, done: t2.done.filter((e) => e.kind !== "thinking") }));
2059
+ setTranscript({ done: [entry({ kind: "header" })], live: [] });
2060
+ refreshStatic();
1852
2061
  notice(t("New session started. The previous session stays saved (/resume)."));
1853
2062
  return;
1854
2063
  case "session":
@@ -1880,9 +2089,11 @@ function App({ runtime, initialPrompt, version, updateMessage }) {
1880
2089
  const chosen = others.find((s) => s.session.file === id);
1881
2090
  if (!chosen) return;
1882
2091
  runtime.resume(chosen.session);
2092
+ setModelLabel(labelFor(runtime, runtime.modelRef));
1883
2093
  setTokens(0);
1884
2094
  setShowToolOutput(false);
1885
2095
  setTranscript({ done: [entry({ kind: "header" }), ...replayEntries(chosen.session.loadHistory())], live: [] });
2096
+ refreshStatic();
1886
2097
  }
1887
2098
  });
1888
2099
  return;
@@ -2084,6 +2295,15 @@ function App({ runtime, initialPrompt, version, updateMessage }) {
2084
2295
  notice(t("Nothing to rewind."));
2085
2296
  return;
2086
2297
  }
2298
+ setTranscript((tr) => {
2299
+ const userIndices = [];
2300
+ tr.done.forEach((e, idx) => {
2301
+ if (e.kind === "user") userIndices.push(idx);
2302
+ });
2303
+ const cutIdx = userIndices[Math.max(0, userIndices.length - result.turns)];
2304
+ return cutIdx !== void 0 ? { done: tr.done.slice(0, cutIdx), live: [] } : tr;
2305
+ });
2306
+ refreshStatic();
2087
2307
  const rel = (p) => relative2(runtime.cwd, p) || p;
2088
2308
  notice(
2089
2309
  [
@@ -2146,6 +2366,7 @@ function App({ runtime, initialPrompt, version, updateMessage }) {
2146
2366
  writeGlobalConfig({ thinkingVisible: next }, runtime.env);
2147
2367
  showThinkingRef.current = next;
2148
2368
  setShowThinking(next);
2369
+ refreshStatic();
2149
2370
  } else if (key === "thinkingClickCollapse") writeGlobalConfig({ thinkingClickCollapse: val === "true" || val === "on" }, runtime.env);
2150
2371
  else if (key === "sandbox" && (val === "on" || val === "off")) writeGlobalConfig({ sandbox: val }, runtime.env);
2151
2372
  else if (key === "networkPolicy" && (val === "allow" || val === "offline")) writeGlobalConfig({ networkPolicy: val }, runtime.env);
@@ -2182,7 +2403,7 @@ function App({ runtime, initialPrompt, version, updateMessage }) {
2182
2403
  notice(t("Unknown command: /{name}. Type /help.", { name }), "warn");
2183
2404
  }
2184
2405
  },
2185
- [runtime, notice, exit, version, runTurn, ask]
2406
+ [runtime, notice, exit, version, runTurn, ask, refreshStatic]
2186
2407
  );
2187
2408
  const runSlash = useCallback(
2188
2409
  (name, args) => runSlashCommand(name, args).catch((error) => notice(`/${name}: ${error.message}`, "error")),
@@ -2198,8 +2419,12 @@ function App({ runtime, initialPrompt, version, updateMessage }) {
2198
2419
  if (text.startsWith("!") && text.slice(1).trim()) {
2199
2420
  const cmd = text.slice(1).trim();
2200
2421
  setTranscript((tr) => ({ ...tr, done: [...tr.done, entry({ kind: "user", text })] }));
2422
+ setBusy(true);
2423
+ setStartedAt(Date.now());
2424
+ const ctrl = new AbortController();
2425
+ controller.current = ctrl;
2201
2426
  const shell = resolveShell({ platform: process.platform, env: runtime.env, exists: existsSync3 });
2202
- void runCommand(cmd, { cwd: runtime.cwd, timeoutMs: 6e4, signal: new AbortController().signal, shell }).then((r) => {
2427
+ void runCommand(cmd, { cwd: runtime.cwd, timeoutMs: 6e4, signal: ctrl.signal, shell }).then((r) => {
2203
2428
  const out = r.output.trim() || "(no output)";
2204
2429
  notice(`$ ${cmd}
2205
2430
  ${out}`, r.exitCode ? "error" : "info");
@@ -2208,6 +2433,10 @@ $ ${cmd}
2208
2433
  ${out}` };
2209
2434
  runtime.agent.messages.push(ctxMsg);
2210
2435
  runtime.session.append(ctxMsg);
2436
+ }).finally(() => {
2437
+ controller.current = null;
2438
+ setBusy(false);
2439
+ setCancelling(false);
2211
2440
  });
2212
2441
  return;
2213
2442
  }
@@ -2248,7 +2477,7 @@ ARGUMENTS: ${slash.args}`);
2248
2477
  setQueued(rest);
2249
2478
  submit(next);
2250
2479
  }, [busy, pending, planAsk, queued, submit]);
2251
- const initialSent = useRef(false);
2480
+ const initialSent = useRef2(false);
2252
2481
  useEffect3(() => {
2253
2482
  if (initialPrompt && !initialSent.current) {
2254
2483
  initialSent.current = true;
@@ -2259,15 +2488,31 @@ ARGUMENTS: ${slash.args}`);
2259
2488
  const next = !showThinkingRef.current;
2260
2489
  showThinkingRef.current = next;
2261
2490
  setShowThinking(next);
2491
+ refreshStatic();
2262
2492
  try {
2263
2493
  writeGlobalConfig({ thinkingVisible: next }, runtime.env);
2264
2494
  } catch (error) {
2265
2495
  notice(t("Could not save thinking visibility: {error}", { error: error.message }), "warn");
2266
2496
  }
2267
- }, [runtime, notice]);
2497
+ }, [runtime, notice, refreshStatic]);
2498
+ useEffect3(() => {
2499
+ if (!stdout?.isTTY || !("fd" in stdout)) return;
2500
+ stdout.write("\x1B[?1000l\x1B[?1002l\x1B[?1003l\x1B[?1006l\x1B[?2004h");
2501
+ return () => {
2502
+ stdout.write("\x1B[?1000l\x1B[?1002l\x1B[?1003l\x1B[?1006l\x1B[?2004l");
2503
+ };
2504
+ }, [stdout]);
2505
+ const scrollableModalOpen = !!(picker || listPicker || mcpMenu || providerMenu || pending || planAsk);
2506
+ useEffect3(() => {
2507
+ if (!scrollableModalOpen || !stdout?.isTTY || !("fd" in stdout)) return;
2508
+ stdout.write("\x1B[?1000h\x1B[?1006h");
2509
+ return () => {
2510
+ stdout.write("\x1B[?1000l\x1B[?1006l");
2511
+ };
2512
+ }, [scrollableModalOpen, stdout]);
2268
2513
  useEffect3(() => {
2269
2514
  const modalOpen = !!(pending || planAsk || picker || listPicker || mcpMenu || providerMenu || prompt);
2270
- if (!runtime.config.thinkingClickCollapse || !busy && !hasThinking || modalOpen || !stdin.isTTY || !stdout.isTTY || !thinkingTargetRef.current) return;
2515
+ if (!runtime.config.thinkingClickCollapse || !busy || modalOpen || !stdin.isTTY || !stdout.isTTY || !thinkingTargetRef.current) return;
2271
2516
  let click;
2272
2517
  let mouseBuffer = "";
2273
2518
  const onData = (data) => {
@@ -2293,7 +2538,7 @@ ARGUMENTS: ${slash.args}`);
2293
2538
  stdin.off("data", onData);
2294
2539
  stdout.write("\x1B[?1000l\x1B[?1006l");
2295
2540
  };
2296
- }, [busy, pending, planAsk, picker, listPicker, mcpMenu, providerMenu, prompt, stdin, stdout, toggleThinking, hasThinking, runtime.config.thinkingClickCollapse]);
2541
+ }, [busy, pending, planAsk, picker, listPicker, mcpMenu, providerMenu, prompt, stdin, stdout, toggleThinking, runtime.config.thinkingClickCollapse]);
2297
2542
  useInput9((input, key) => {
2298
2543
  if (key.tab && key.shift) {
2299
2544
  const next = nextMode(runtime.agent.permissions.mode);
@@ -2320,6 +2565,9 @@ ARGUMENTS: ${slash.args}`);
2320
2565
  toggleThinking();
2321
2566
  } else if (matchKeybind(runtime.config.keybinds.toolOutput, input, key)) {
2322
2567
  setShowToolOutput((prev) => !prev);
2568
+ refreshStatic();
2569
+ } else if (key.ctrl && input.toLowerCase() === "l") {
2570
+ refreshStatic();
2323
2571
  }
2324
2572
  });
2325
2573
  const pickMcp = async (name) => {
@@ -2384,8 +2632,23 @@ ARGUMENTS: ${slash.args}`);
2384
2632
  pending?.resolve(a);
2385
2633
  setAsks((q) => q.slice(1));
2386
2634
  };
2635
+ const isRealTty = Boolean(stdout?.isTTY && "fd" in stdout);
2636
+ const maxLiveLines = Math.max(8, (stdout?.rows ?? 24) - 10);
2637
+ const capLiveMarkdown = (raw) => {
2638
+ if (!isRealTty) return raw;
2639
+ const lines = raw.split("\n");
2640
+ if (lines.length <= maxLiveLines) return raw;
2641
+ const hidden = lines.length - maxLiveLines;
2642
+ const headFence = lines.slice(0, hidden).find((l) => l.trimStart().startsWith("```"));
2643
+ const prefix = headFence ? `${headFence}
2644
+ \u2026 (${hidden} lines above) \u2026
2645
+ ` : `\u2026 (${hidden} lines above) \u2026
2646
+ `;
2647
+ return `${prefix}${lines.slice(-maxLiveLines).join("\n")}`;
2648
+ };
2387
2649
  const cwd = runtime.cwd.startsWith(homedir()) ? `~${runtime.cwd.slice(homedir().length)}` : runtime.cwd;
2388
2650
  const renderEntry = (e) => {
2651
+ const isLive = transcript.live.some((le) => le.id === e.id);
2389
2652
  switch (e.kind) {
2390
2653
  case "header":
2391
2654
  return /* @__PURE__ */ jsx16(Box13, { marginBottom: 1, children: /* @__PURE__ */ jsxs12(Text16, { children: [
@@ -2395,20 +2658,25 @@ ARGUMENTS: ${slash.args}`);
2395
2658
  ] }) }, e.id);
2396
2659
  case "user":
2397
2660
  return /* @__PURE__ */ jsx16(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: `> ${e.text}` }) }, e.id);
2398
- case "assistant":
2661
+ case "assistant": {
2662
+ const displayText = isLive ? capLiveMarkdown(e.text) : e.text;
2399
2663
  if (e.cont) {
2400
- return e.text ? /* @__PURE__ */ jsx16(Box13, { paddingLeft: 2, children: /* @__PURE__ */ jsx16(Markdown, { text: e.text }) }, e.id) : null;
2664
+ return displayText ? /* @__PURE__ */ jsx16(Box13, { paddingLeft: 2, children: /* @__PURE__ */ jsx16(Markdown, { text: displayText }) }, e.id) : null;
2401
2665
  }
2402
- if (!e.text.trim()) return null;
2666
+ if (!displayText.trim()) return null;
2403
2667
  return /* @__PURE__ */ jsxs12(Box13, { marginTop: 1, children: [
2404
2668
  /* @__PURE__ */ jsx16(Text16, { color: color(ACCENT), children: "\u25CF " }),
2405
- /* @__PURE__ */ jsx16(Markdown, { text: e.text.replace(/^\s+/, "") })
2669
+ /* @__PURE__ */ jsx16(Markdown, { text: displayText.replace(/^\s+/, "") })
2406
2670
  ] }, e.id);
2671
+ }
2407
2672
  case "thinking": {
2408
2673
  const text = e.text.trim();
2409
2674
  if (!text) return null;
2410
- const lines = text.split("\n").length;
2411
- const isTarget = e.id === lastThinkingEntryId;
2675
+ const allLines = text.split("\n");
2676
+ const lines = allLines.length;
2677
+ const shownText = isLive && isRealTty && allLines.length > maxLiveLines ? `\u2026 (${allLines.length - maxLiveLines} lines above) \u2026
2678
+ ${allLines.slice(-maxLiveLines).join("\n")}` : text;
2679
+ const isTarget = e.id === liveThinkingEntryId;
2412
2680
  return /* @__PURE__ */ jsxs12(Box13, { ref: isTarget ? thinkingTargetRef : void 0, marginTop: 1, flexDirection: "column", children: [
2413
2681
  /* @__PURE__ */ jsxs12(Box13, { flexDirection: "row", children: [
2414
2682
  /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: showThinking ? t("\u273B Thinking") : t("\u273B Thinking \xB7 {n} lines", { n: lines }) }),
@@ -2420,7 +2688,7 @@ ARGUMENTS: ${slash.args}`);
2420
2688
  ] }),
2421
2689
  showThinking ? /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", paddingLeft: 2, children: [
2422
2690
  /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: t("Model: {model} \xB7 reasoning: {level}", { model: runtime.modelRef, level: reasoning }) }),
2423
- /* @__PURE__ */ jsx16(Text16, { dimColor: true, italic: true, children: text })
2691
+ /* @__PURE__ */ jsx16(Text16, { dimColor: true, italic: true, children: shownText })
2424
2692
  ] }) : null
2425
2693
  ] }, e.id);
2426
2694
  }
@@ -2436,10 +2704,9 @@ ARGUMENTS: ${slash.args}`);
2436
2704
  const activeSpinnerSummary = activeToolEntry ? `${toolLabel(activeToolEntry.tool)}${activeTarget ? ` ${activeTarget.length > 20 ? `${activeTarget.slice(0, 20)}\u2026` : activeTarget}` : ""}` : "";
2437
2705
  const activeToolSummary = activeToolEntry ? `${toolLabel(activeToolEntry.tool)}${activeTarget ? ` ${activeTarget.length > 68 ? `${activeTarget.slice(0, 68)}\u2026` : activeTarget}` : ""}` : "";
2438
2706
  return /* @__PURE__ */ jsxs12(Box13, { ref: liveRootRef, flexDirection: "column", children: [
2439
- /* @__PURE__ */ jsx16(Static, { items: staticDone, children: renderEntry }),
2440
- activeTurnDone.map(renderEntry),
2707
+ /* @__PURE__ */ jsx16(Static, { items: transcript.done, children: renderEntry }, staticKey),
2441
2708
  transcript.live.map(renderEntry),
2442
- busy && !pending && !planAsk ? /* @__PURE__ */ jsxs12(Box13, { ref: lastThinkingEntryId === void 0 ? thinkingTargetRef : void 0, marginTop: 1, flexDirection: "column", children: [
2709
+ busy && !pending && !planAsk ? /* @__PURE__ */ jsxs12(Box13, { ref: liveThinkingEntryId === void 0 ? thinkingTargetRef : void 0, marginTop: 1, flexDirection: "column", children: [
2443
2710
  /* @__PURE__ */ jsxs12(Box13, { flexDirection: "row", children: [
2444
2711
  /* @__PURE__ */ jsx16(Spinner, { label: cancelling ? t("Cancelling") : activeSpinnerSummary ? `${t("Thinking")} \xB7 ${activeSpinnerSummary}` : t("Thinking"), startedAt }),
2445
2712
  /* @__PURE__ */ jsxs12(Text16, { dimColor: true, children: [
@@ -2448,7 +2715,7 @@ ARGUMENTS: ${slash.args}`);
2448
2715
  runtime.config.thinkingClickCollapse ? ` \xB7 ${t("click to toggle")}` : ""
2449
2716
  ] })
2450
2717
  ] }),
2451
- showThinking && lastThinkingEntryId === void 0 ? /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", paddingLeft: 2, children: [
2718
+ showThinking && liveThinkingEntryId === void 0 ? /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", paddingLeft: 2, children: [
2452
2719
  /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: t("Model: {model} \xB7 reasoning: {level}", { model: runtime.modelRef, level: reasoning }) }),
2453
2720
  activeToolSummary ? /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: `\u25B6 ${activeToolSummary}` }) : null,
2454
2721
  !activeToolSummary ? /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: t("No reasoning summary received yet; tool activity remains visible above.") }) : null
@@ -2506,7 +2773,7 @@ ARGUMENTS: ${slash.args}`);
2506
2773
  import { jsx as jsx17 } from "react/jsx-runtime";
2507
2774
  async function startInteractive(rt, opts) {
2508
2775
  if (opts.resume) {
2509
- const { Session: Session2 } = await import("./session-ML2DPI7P.js");
2776
+ const { Session: Session2 } = await import("./session-NPKTEDKV.js");
2510
2777
  const list = Session2.list(rt.home, rt.cwd).slice(0, 20);
2511
2778
  if (!list.length) console.log(t("No sessions in this folder yet; starting a new one."));
2512
2779
  else {