@nomad-e/bluma-cli 0.1.28 → 0.1.31

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/main.js CHANGED
@@ -328,33 +328,71 @@ var init_async_command = __esm({
328
328
  });
329
329
 
330
330
  // src/main.ts
331
- import React11 from "react";
331
+ import React12 from "react";
332
332
  import { render } from "ink";
333
- import { EventEmitter as EventEmitter2 } from "events";
333
+ import { EventEmitter as EventEmitter3 } from "events";
334
334
  import fs15 from "fs";
335
335
  import { v4 as uuidv46 } from "uuid";
336
336
 
337
337
  // src/app/ui/App.tsx
338
- import { useState as useState7, useEffect as useEffect6, useRef as useRef5, useCallback as useCallback2, memo as memo11 } from "react";
339
- import { Box as Box17, Text as Text16, Static } from "ink";
338
+ import { useState as useState6, useEffect as useEffect7, useRef as useRef5, useCallback as useCallback2, memo as memo12 } from "react";
339
+ import { Box as Box20, Text as Text19, Static } from "ink";
340
340
 
341
341
  // src/app/ui/layout.tsx
342
342
  import { Box, Text } from "ink";
343
343
  import { memo } from "react";
344
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
344
+
345
+ // src/app/ui/theme/blumaTerminal.ts
346
+ var BLUMA_TERMINAL = {
347
+ brandBlue: "blue",
348
+ brandMagenta: "magenta",
349
+ text: "white",
350
+ muted: "gray",
351
+ dim: "gray",
352
+ code: "gray",
353
+ codeLabel: "blue",
354
+ link: "blue",
355
+ linkUnderline: true,
356
+ success: "green",
357
+ warn: "yellow",
358
+ err: "red",
359
+ panelBorder: "blue",
360
+ toolLabel: "magenta",
361
+ toolMeta: "gray",
362
+ heading1: "blue",
363
+ heading2: "blue",
364
+ headingDeep: "magenta",
365
+ listBullet: "magenta",
366
+ listBulletSub: "blue",
367
+ rule: "gray",
368
+ /** M3 CLI — superfícies e hierarquia */
369
+ m3Outline: "blue",
370
+ m3TonalOutline: "magenta",
371
+ m3Rail: "blue",
372
+ m3Label: "gray",
373
+ m3OnSurface: "white"
374
+ };
375
+
376
+ // src/app/ui/layout.tsx
377
+ import { jsx, jsxs } from "react/jsx-runtime";
345
378
  var HeaderComponent = ({
346
379
  sessionId,
347
380
  workdir
348
381
  }) => {
349
382
  const dirName = workdir.split("/").pop() || workdir;
350
383
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
351
- /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, { color: "magenta", bold: true, children: "bluma \u2014 coding agent" }) }),
352
- /* @__PURE__ */ jsxs(Box, { children: [
384
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", flexWrap: "wrap", children: [
385
+ /* @__PURE__ */ jsx(Text, { color: BLUMA_TERMINAL.brandBlue, bold: true, children: "BluMa" }),
386
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2014 " }),
387
+ /* @__PURE__ */ jsx(Text, { children: "Base Language Unit" }),
388
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \xB7 " }),
389
+ /* @__PURE__ */ jsx(Text, { color: BLUMA_TERMINAL.brandMagenta, bold: true, children: "Model Agent" })
390
+ ] }),
391
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", flexWrap: "wrap", children: [
353
392
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "cwd " }),
354
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: dirName }),
355
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: " | " }),
356
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "session " }),
357
- /* @__PURE__ */ jsx(Text, { color: "gray", children: sessionId.slice(0, 8) })
393
+ /* @__PURE__ */ jsx(Text, { color: BLUMA_TERMINAL.brandBlue, children: dirName }),
394
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2502 session " }),
395
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: sessionId.slice(0, 8) })
358
396
  ] })
359
397
  ] });
360
398
  };
@@ -366,12 +404,12 @@ var SessionInfoComponent = ({
366
404
  mcpStatus
367
405
  }) => /* @__PURE__ */ jsxs(Box, { paddingX: 1, children: [
368
406
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "mcp " }),
369
- /* @__PURE__ */ jsx(Text, { color: mcpStatus === "connected" ? "green" : "yellow", children: mcpStatus === "connected" ? "+" : "-" }),
370
- toolsCount !== null && /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
407
+ /* @__PURE__ */ jsx(Text, { color: mcpStatus === "connected" ? BLUMA_TERMINAL.success : BLUMA_TERMINAL.warn, children: mcpStatus === "connected" ? "on" : "\u2026" }),
408
+ toolsCount !== null ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
371
409
  " ",
372
410
  toolsCount,
373
411
  " tools"
374
- ] }) })
412
+ ] }) : null
375
413
  ] });
376
414
  var SessionInfo = memo(SessionInfoComponent);
377
415
  var TaskStatusBarComponent = ({
@@ -380,9 +418,9 @@ var TaskStatusBarComponent = ({
380
418
  status
381
419
  }) => {
382
420
  const modeColors = {
383
- PLANNING: "blue",
384
- EXECUTION: "green",
385
- VERIFICATION: "yellow"
421
+ PLANNING: BLUMA_TERMINAL.brandBlue,
422
+ EXECUTION: BLUMA_TERMINAL.brandBlue,
423
+ VERIFICATION: BLUMA_TERMINAL.brandMagenta
386
424
  };
387
425
  return /* @__PURE__ */ jsxs(Box, { paddingX: 1, children: [
388
426
  /* @__PURE__ */ jsxs(Text, { color: modeColors[mode], bold: true, children: [
@@ -394,10 +432,10 @@ var TaskStatusBarComponent = ({
394
432
  " ",
395
433
  taskName
396
434
  ] }),
397
- status && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
398
- " - ",
435
+ status ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
436
+ " \u2014 ",
399
437
  status
400
- ] })
438
+ ] }) : null
401
439
  ] });
402
440
  };
403
441
  var TaskStatusBar = memo(TaskStatusBarComponent);
@@ -408,6 +446,12 @@ import { Box as Box2, Text as Text2, useStdout, useInput as useInput2 } from "in
408
446
  // src/app/ui/utils/useSimpleInputBuffer.ts
409
447
  import { useReducer, useRef, useCallback, useEffect } from "react";
410
448
  import { useInput } from "ink";
449
+
450
+ // src/app/ui/utils/expandPreviewHotkey.ts
451
+ import { EventEmitter } from "events";
452
+ var expandPreviewHotkeyBus = new EventEmitter();
453
+
454
+ // src/app/ui/utils/useSimpleInputBuffer.ts
411
455
  function getLineStart(text, cursorPos) {
412
456
  let pos = cursorPos - 1;
413
457
  while (pos >= 0 && text[pos] !== "\n") {
@@ -546,7 +590,12 @@ function inputReducer(state, action, viewWidth) {
546
590
  return state;
547
591
  }
548
592
  }
549
- var useCustomInput = ({ onSubmit, viewWidth, isReadOnly, onInterrupt }) => {
593
+ var useCustomInput = ({
594
+ onSubmit,
595
+ viewWidth,
596
+ isReadOnly,
597
+ onInterrupt
598
+ }) => {
550
599
  const [state, dispatch] = useReducer(
551
600
  (s, a) => inputReducer(s, a, viewWidth),
552
601
  { text: "", cursorPosition: 0, viewStart: 0 }
@@ -597,6 +646,10 @@ var useCustomInput = ({ onSubmit, viewWidth, isReadOnly, onInterrupt }) => {
597
646
  if (inputBuffer.current.length > 0 && (key.ctrl || key.meta || key.escape || key.return || key.leftArrow || key.rightArrow || key.upArrow || key.downArrow || key.tab || key.shift)) {
598
647
  flushInputBuffer();
599
648
  }
649
+ if (key.ctrl && input === "o") {
650
+ expandPreviewHotkeyBus.emit("expand");
651
+ return;
652
+ }
600
653
  if (key.escape) {
601
654
  onInterrupt();
602
655
  return;
@@ -685,7 +738,7 @@ var useCustomInput = ({ onSubmit, viewWidth, isReadOnly, onInterrupt }) => {
685
738
 
686
739
  // src/app/ui/components/InputPrompt.tsx
687
740
  import { useEffect as useEffect3, useMemo, useState as useState2, memo as memo2 } from "react";
688
- import { EventEmitter } from "events";
741
+ import { EventEmitter as EventEmitter2 } from "events";
689
742
 
690
743
  // src/app/ui/utils/slashRegistry.ts
691
744
  var getSlashCommands = () => [
@@ -884,9 +937,14 @@ function useAtCompletion({
884
937
  return { open, suggestions, selected, setSelected, insertAtSelection, close, update };
885
938
  }
886
939
 
940
+ // src/app/ui/constants/toolUiPreview.ts
941
+ var TOOL_PREVIEW_MAX_LINES = 8;
942
+ var EXPAND_OVERLAY_MAX_LINES = 60;
943
+ var TERMINAL_RULE_CHAR = "\u2500";
944
+
887
945
  // src/app/ui/components/InputPrompt.tsx
888
- import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
889
- var uiEventBus = global.__bluma_ui_eventbus__ || new EventEmitter();
946
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
947
+ var uiEventBus = global.__bluma_ui_eventbus__ || new EventEmitter2();
890
948
  global.__bluma_ui_eventbus__ = uiEventBus;
891
949
  var TextLine = memo2(({
892
950
  line,
@@ -931,8 +989,8 @@ var PathSuggestions = memo2(({
931
989
  const realIdx = start + idx;
932
990
  const isSelected = realIdx === selected;
933
991
  return /* @__PURE__ */ jsxs2(Box2, { paddingLeft: 1, paddingY: 0, children: [
934
- /* @__PURE__ */ jsx2(Text2, { color: isSelected ? "magenta" : "gray", children: isSelected ? "\u276F " : " " }),
935
- /* @__PURE__ */ jsx2(Text2, { color: isSelected ? "magenta" : "white", bold: isSelected, dimColor: !isSelected, children: s.label })
992
+ /* @__PURE__ */ jsx2(Text2, { color: isSelected ? BLUMA_TERMINAL.brandMagenta : BLUMA_TERMINAL.muted, children: isSelected ? "> " : " " }),
993
+ /* @__PURE__ */ jsx2(Text2, { color: isSelected ? BLUMA_TERMINAL.brandBlue : void 0, bold: isSelected, dimColor: !isSelected, children: s.label })
936
994
  ] }, s.fullPath);
937
995
  }) });
938
996
  });
@@ -943,11 +1001,11 @@ var SlashSuggestions = memo2(({
943
1001
  }) => /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", marginTop: 1, children: suggestions.map((s, idx) => {
944
1002
  const isSelected = idx === selectedIndex;
945
1003
  return /* @__PURE__ */ jsxs2(Box2, { paddingLeft: 1, paddingY: 0, children: [
946
- /* @__PURE__ */ jsx2(Text2, { color: isSelected ? "magenta" : "gray", children: isSelected ? "\u276F " : " " }),
947
- /* @__PURE__ */ jsxs2(Text2, { color: isSelected ? "magenta" : "white", bold: isSelected, dimColor: !isSelected, children: [
1004
+ /* @__PURE__ */ jsx2(Text2, { color: isSelected ? BLUMA_TERMINAL.brandMagenta : BLUMA_TERMINAL.muted, children: isSelected ? "> " : " " }),
1005
+ /* @__PURE__ */ jsxs2(Text2, { color: isSelected ? BLUMA_TERMINAL.brandBlue : void 0, bold: isSelected, dimColor: !isSelected, children: [
948
1006
  s.name,
949
1007
  " ",
950
- /* @__PURE__ */ jsxs2(Text2, { color: "gray", children: [
1008
+ /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
951
1009
  "- ",
952
1010
  s.description
953
1011
  ] })
@@ -955,7 +1013,14 @@ var SlashSuggestions = memo2(({
955
1013
  ] }, s.name);
956
1014
  }) }));
957
1015
  SlashSuggestions.displayName = "SlashSuggestions";
958
- var Footer = memo2(({ isReadOnly }) => /* @__PURE__ */ jsx2(Box2, { paddingX: 1, justifyContent: "center", children: /* @__PURE__ */ jsx2(Text2, { color: "gray", dimColor: true, children: isReadOnly ? "ctrl+c to exit | Enter to send message | Shift+Enter for new line | esc interrupt" : "ctrl+c to exit | Enter to submit | Shift+Enter for new line | /help commands | esc interrupt" }) }));
1016
+ var InputRuleLine = memo2(() => {
1017
+ const { stdout } = useStdout();
1018
+ const cols = stdout?.columns ?? 80;
1019
+ const n = Math.max(8, cols);
1020
+ return /* @__PURE__ */ jsx2(Text2, { color: "white", children: TERMINAL_RULE_CHAR.repeat(n) });
1021
+ });
1022
+ InputRuleLine.displayName = "InputRuleLine";
1023
+ var Footer = memo2(({ isReadOnly }) => /* @__PURE__ */ jsx2(Box2, { justifyContent: "center", children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: isReadOnly ? "ctrl+c to exit | Enter to send message | Shift+Enter for new line | esc interrupt | Ctrl+O expand last clip" : "ctrl+c to exit | Enter to submit | Shift+Enter for new line | /help commands | esc interrupt | Ctrl+O expand last clip" }) }));
959
1024
  Footer.displayName = "Footer";
960
1025
  var TextLinesRenderer = memo2(({
961
1026
  lines,
@@ -965,17 +1030,14 @@ var TextLinesRenderer = memo2(({
965
1030
  showPlaceholder,
966
1031
  placeholder
967
1032
  }) => {
968
- return /* @__PURE__ */ jsx2(Fragment2, { children: lines.map((line, idx) => {
1033
+ return /* @__PURE__ */ jsx2(Fragment, { children: lines.map((line, idx) => {
969
1034
  const isFirstLine = idx === 0;
970
- return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "row", paddingX: 1, children: [
971
- isFirstLine && /* @__PURE__ */ jsxs2(Text2, { color: "white", children: [
1035
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "row", children: [
1036
+ isFirstLine && /* @__PURE__ */ jsxs2(Text2, { color: "white", bold: true, children: [
972
1037
  ">",
973
1038
  " "
974
1039
  ] }),
975
- !isFirstLine && /* @__PURE__ */ jsxs2(Text2, { color: "gray", children: [
976
- "\u2502",
977
- " "
978
- ] }),
1040
+ !isFirstLine && /* @__PURE__ */ jsx2(Text2, { color: BLUMA_TERMINAL.brandMagenta, children: "\u2502 " }),
979
1041
  showPlaceholder && isFirstLine && line.length === 0 ? /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: placeholder }) : /* @__PURE__ */ jsx2(
980
1042
  TextLine,
981
1043
  {
@@ -1116,14 +1178,15 @@ var InputPrompt = memo2(({
1116
1178
  return;
1117
1179
  }
1118
1180
  }, { isActive: true });
1119
- return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
1120
- disableWhileProcessing ? /* @__PURE__ */ jsx2(Box2, { children: /* @__PURE__ */ jsxs2(Box2, { flexDirection: "row", paddingX: 1, flexWrap: "nowrap", children: [
1121
- /* @__PURE__ */ jsxs2(Text2, { color: "white", children: [
1181
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginTop: 1, children: [
1182
+ /* @__PURE__ */ jsx2(InputRuleLine, {}),
1183
+ /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", marginY: 0, children: disableWhileProcessing ? /* @__PURE__ */ jsxs2(Box2, { flexDirection: "row", flexWrap: "nowrap", children: [
1184
+ /* @__PURE__ */ jsxs2(Text2, { color: "white", bold: true, children: [
1122
1185
  ">",
1123
1186
  " "
1124
1187
  ] }),
1125
1188
  /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "ctrl+c to exit" })
1126
- ] }) }) : /* @__PURE__ */ jsxs2(Fragment2, { children: [
1189
+ ] }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
1127
1190
  /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: /* @__PURE__ */ jsx2(
1128
1191
  TextLinesRenderer,
1129
1192
  {
@@ -1142,14 +1205,9 @@ var InputPrompt = memo2(({
1142
1205
  selected: pathAutocomplete.selected
1143
1206
  }
1144
1207
  ),
1145
- slashOpen && slashSuggestions.length > 0 && /* @__PURE__ */ jsx2(
1146
- SlashSuggestions,
1147
- {
1148
- suggestions: slashSuggestions,
1149
- selectedIndex: slashIndex
1150
- }
1151
- )
1152
- ] }),
1208
+ slashOpen && slashSuggestions.length > 0 && /* @__PURE__ */ jsx2(SlashSuggestions, { suggestions: slashSuggestions, selectedIndex: slashIndex })
1209
+ ] }) }),
1210
+ /* @__PURE__ */ jsx2(InputRuleLine, {}),
1153
1211
  /* @__PURE__ */ jsx2(Footer, { isReadOnly })
1154
1212
  ] });
1155
1213
  });
@@ -1157,7 +1215,7 @@ InputPrompt.displayName = "InputPrompt";
1157
1215
 
1158
1216
  // src/app/ui/ConfirmationPrompt.tsx
1159
1217
  import { memo as memo4 } from "react";
1160
- import { Box as Box6, Text as Text6 } from "ink";
1218
+ import { Box as Box7, Text as Text7 } from "ink";
1161
1219
 
1162
1220
  // src/app/ui/InteractiveMenu.tsx
1163
1221
  import { useState as useState3, memo as memo3 } from "react";
@@ -1165,9 +1223,9 @@ import { Box as Box3, Text as Text3, useInput as useInput3 } from "ink";
1165
1223
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1166
1224
  var InteractiveMenuComponent = ({ onDecision }) => {
1167
1225
  const options = [
1168
- { key: "y", label: "yes", value: "accept", color: "green" },
1169
- { key: "n", label: "no", value: "decline", color: "red" },
1170
- { key: "a", label: "always", value: "accept_always", color: "yellow" }
1226
+ { key: "y", label: "yes", value: "accept" },
1227
+ { key: "n", label: "no", value: "decline" },
1228
+ { key: "a", label: "always", value: "accept_always" }
1171
1229
  ];
1172
1230
  const [selected, setSelected] = useState3(0);
1173
1231
  useInput3((input, key) => {
@@ -1188,12 +1246,12 @@ var InteractiveMenuComponent = ({ onDecision }) => {
1188
1246
  onDecision(opt.value);
1189
1247
  }
1190
1248
  });
1191
- return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", paddingX: 1, marginTop: 1, children: [
1192
- /* @__PURE__ */ jsxs3(Box3, { children: [
1193
- /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "approve? " }),
1249
+ return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginTop: 1, children: [
1250
+ /* @__PURE__ */ jsxs3(Box3, { flexDirection: "row", flexWrap: "wrap", children: [
1251
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "approve \xB7 " }),
1194
1252
  options.map((opt, idx) => {
1195
1253
  const isSelected = idx === selected;
1196
- return /* @__PURE__ */ jsx3(Box3, { marginRight: 1, children: /* @__PURE__ */ jsxs3(Text3, { color: isSelected ? opt.color : "gray", bold: isSelected, children: [
1254
+ return /* @__PURE__ */ jsx3(Box3, { marginRight: 1, children: /* @__PURE__ */ jsxs3(Text3, { color: isSelected ? BLUMA_TERMINAL.brandMagenta : BLUMA_TERMINAL.muted, bold: isSelected, children: [
1197
1255
  "[",
1198
1256
  opt.key,
1199
1257
  "]",
@@ -1201,7 +1259,7 @@ var InteractiveMenuComponent = ({ onDecision }) => {
1201
1259
  ] }) }, opt.value);
1202
1260
  })
1203
1261
  ] }),
1204
- /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "arrows to select, enter to confirm, esc to cancel" }) })
1262
+ /* @__PURE__ */ jsx3(Box3, { marginTop: 0, children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "arrows \xB7 enter \xB7 esc cancel" }) })
1205
1263
  ] });
1206
1264
  };
1207
1265
  var InteractiveMenu = memo3(InteractiveMenuComponent);
@@ -1354,7 +1412,7 @@ var renderEditTool = ({ toolCall, preview }) => {
1354
1412
  var renderGeneric = ({ toolCall }) => {
1355
1413
  const toolName = toolCall.function.name;
1356
1414
  const rawArguments = toolCall.function.arguments;
1357
- const MAX_LINES2 = 5;
1415
+ const MAX_LINES = 5;
1358
1416
  let formattedArgsString;
1359
1417
  if (!rawArguments) {
1360
1418
  formattedArgsString = "";
@@ -1369,9 +1427,9 @@ var renderGeneric = ({ toolCall }) => {
1369
1427
  formattedArgsString = JSON.stringify(rawArguments, null, 2);
1370
1428
  }
1371
1429
  const lines = formattedArgsString.split("\n");
1372
- const isTruncated = lines.length > MAX_LINES2;
1373
- const visibleLines = isTruncated ? lines.slice(0, MAX_LINES2) : lines;
1374
- const remainingCount = lines.length - MAX_LINES2;
1430
+ const isTruncated = lines.length > MAX_LINES;
1431
+ const visibleLines = isTruncated ? lines.slice(0, MAX_LINES) : lines;
1432
+ const remainingCount = lines.length - MAX_LINES;
1375
1433
  return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
1376
1434
  /* @__PURE__ */ jsxs5(Box5, { children: [
1377
1435
  /* @__PURE__ */ jsx5(Text5, { color: "blue", children: "\u25B8" }),
@@ -1411,12 +1469,12 @@ var renderTodoTool = ({ toolCall }) => {
1411
1469
  /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: " todo" })
1412
1470
  ] }),
1413
1471
  /* @__PURE__ */ jsxs5(Box5, { paddingLeft: 2, flexDirection: "column", children: [
1414
- /* @__PURE__ */ jsxs5(Text5, { color: "magenta", children: [
1415
- "\u{1F4CB} ",
1472
+ /* @__PURE__ */ jsxs5(Text5, { color: "blue", children: [
1473
+ "todo ",
1416
1474
  pending,
1417
- " pending, ",
1475
+ " open \xB7 ",
1418
1476
  completed,
1419
- " completed"
1477
+ " done"
1420
1478
  ] }),
1421
1479
  tasks.length > 0 && tasks.length <= 10 && /* @__PURE__ */ jsx5(Box5, { paddingLeft: 2, flexDirection: "column", marginTop: 1, children: tasks.map((task, idx) => {
1422
1480
  const isComplete = task.isComplete === true;
@@ -1466,19 +1524,60 @@ var promptRenderers = {
1466
1524
  // <--- ADICIONE ESTA LINHA
1467
1525
  };
1468
1526
 
1469
- // src/app/ui/ConfirmationPrompt.tsx
1527
+ // src/app/ui/theme/m3Layout.tsx
1528
+ import { Box as Box6, Text as Text6 } from "ink";
1470
1529
  import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1530
+ function ChatBlock({
1531
+ children,
1532
+ marginBottom = 1
1533
+ }) {
1534
+ return /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", marginBottom, children });
1535
+ }
1536
+ function ChatUserMessage({ children }) {
1537
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "row", marginBottom: 1, flexWrap: "wrap", alignItems: "flex-start", children: [
1538
+ /* @__PURE__ */ jsxs6(Text6, { color: "white", bold: true, children: [
1539
+ ">",
1540
+ " "
1541
+ ] }),
1542
+ /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", flexGrow: 1, children })
1543
+ ] });
1544
+ }
1545
+ function ChatMeta({ children }) {
1546
+ return /* @__PURE__ */ jsx6(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsx6(Text6, { dimColor: true, children }) });
1547
+ }
1548
+ function ChatStatusRow({ children }) {
1549
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "row", marginBottom: 1, flexWrap: "wrap", children: [
1550
+ /* @__PURE__ */ jsxs6(Text6, { color: BLUMA_TERMINAL.brandMagenta, bold: true, children: [
1551
+ "*",
1552
+ " "
1553
+ ] }),
1554
+ children
1555
+ ] });
1556
+ }
1557
+ function ChatTurnDuration({ secondsFormatted }) {
1558
+ return /* @__PURE__ */ jsx6(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1559
+ /* @__PURE__ */ jsx6(Text6, { color: BLUMA_TERMINAL.brandBlue, children: "\xB7 " }),
1560
+ /* @__PURE__ */ jsxs6(Text6, { color: BLUMA_TERMINAL.brandMagenta, children: [
1561
+ secondsFormatted,
1562
+ "s"
1563
+ ] })
1564
+ ] }) });
1565
+ }
1566
+ var M3StatusStrip = ChatStatusRow;
1567
+
1568
+ // src/app/ui/ConfirmationPrompt.tsx
1569
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1471
1570
  var ConfirmationPromptComponent = ({ toolCalls, preview, onDecision }) => {
1472
- const toolCall = toolCalls && toolCalls.length > 0 ? toolCalls[0] : null;
1473
- if (!toolCall) {
1474
- return /* @__PURE__ */ jsx6(Box6, { paddingX: 1, children: /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "waiting..." }) });
1571
+ const toolCall = toolCalls && Array.isArray(toolCalls) && toolCalls.length > 0 ? toolCalls[0] : null;
1572
+ if (!toolCall?.function) {
1573
+ return /* @__PURE__ */ jsx7(Box7, { paddingX: 1, children: /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "waiting\u2026" }) });
1475
1574
  }
1476
- const toolName = toolCall.function.name;
1575
+ const toolName = toolCall.function.name || "tool";
1477
1576
  const renderFunction = promptRenderers[toolName] || renderGeneric;
1478
- return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", paddingX: 1, children: [
1577
+ return /* @__PURE__ */ jsx7(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", paddingLeft: 1, children: [
1479
1578
  renderFunction({ toolCall, preview }),
1480
- /* @__PURE__ */ jsx6(InteractiveMenu, { onDecision })
1481
- ] });
1579
+ /* @__PURE__ */ jsx7(InteractiveMenu, { onDecision })
1580
+ ] }) });
1482
1581
  };
1483
1582
  var ConfirmationPrompt = memo4(ConfirmationPromptComponent);
1484
1583
 
@@ -3605,14 +3704,84 @@ function searchNotes(args) {
3605
3704
  matched: matches
3606
3705
  };
3607
3706
  }
3608
- function clearNotes() {
3707
+ function removeNote(args) {
3609
3708
  loadMemoryFromFile();
3610
- memoryStore = [];
3611
- nextId2 = 1;
3709
+ const id = args.id;
3710
+ if (id == null || typeof id !== "number" || !Number.isFinite(id)) {
3711
+ return {
3712
+ success: false,
3713
+ message: "id is required for action=remove (use list or search to get ids)",
3714
+ entries: memoryStore
3715
+ };
3716
+ }
3717
+ const idx = memoryStore.findIndex((e) => e.id === id);
3718
+ if (idx === -1) {
3719
+ return {
3720
+ success: false,
3721
+ message: `No coding memory entry with id=${id}`,
3722
+ entries: memoryStore
3723
+ };
3724
+ }
3725
+ memoryStore.splice(idx, 1);
3612
3726
  saveMemoryToFile();
3613
3727
  return {
3614
3728
  success: true,
3615
- message: "All coding memory entries cleared",
3729
+ message: `Removed coding memory entry id=${id}`,
3730
+ entries: memoryStore
3731
+ };
3732
+ }
3733
+ function updateNote(args) {
3734
+ loadMemoryFromFile();
3735
+ const id = args.id;
3736
+ if (id == null || typeof id !== "number" || !Number.isFinite(id)) {
3737
+ return {
3738
+ success: false,
3739
+ message: "id is required for action=update",
3740
+ entries: memoryStore
3741
+ };
3742
+ }
3743
+ const entry = memoryStore.find((e) => e.id === id);
3744
+ if (!entry) {
3745
+ return {
3746
+ success: false,
3747
+ message: `No coding memory entry with id=${id}`,
3748
+ entries: memoryStore
3749
+ };
3750
+ }
3751
+ const hasNote = typeof args.note === "string";
3752
+ const hasTags = args.tags !== void 0;
3753
+ if (!hasNote && !hasTags) {
3754
+ return {
3755
+ success: false,
3756
+ message: "update requires `note` and/or `tags` (omit fields you do not want to change)",
3757
+ entries: memoryStore
3758
+ };
3759
+ }
3760
+ if (hasNote) {
3761
+ const newNote = args.note.trim();
3762
+ if (!newNote) {
3763
+ return {
3764
+ success: false,
3765
+ message: "note cannot be empty; omit `note` to keep the existing text",
3766
+ entries: memoryStore
3767
+ };
3768
+ }
3769
+ if (newNote.length > 4e3) {
3770
+ return {
3771
+ success: false,
3772
+ message: "note too long (max 4000 chars)",
3773
+ entries: memoryStore
3774
+ };
3775
+ }
3776
+ entry.note = newNote;
3777
+ }
3778
+ if (hasTags) {
3779
+ entry.tags = normalizeTags(args.tags);
3780
+ }
3781
+ saveMemoryToFile();
3782
+ return {
3783
+ success: true,
3784
+ message: `Updated coding memory id=${id}`,
3616
3785
  entries: memoryStore
3617
3786
  };
3618
3787
  }
@@ -3625,8 +3794,10 @@ async function coding_memory(args) {
3625
3794
  return listNotes();
3626
3795
  case "search":
3627
3796
  return searchNotes(args);
3628
- case "clear":
3629
- return clearNotes();
3797
+ case "remove":
3798
+ return removeNote(args);
3799
+ case "update":
3800
+ return updateNote(args);
3630
3801
  default:
3631
3802
  return {
3632
3803
  success: false,
@@ -3939,20 +4110,21 @@ async function withFileLock(file, fn) {
3939
4110
  }
3940
4111
  var pendingSaves = /* @__PURE__ */ new Map();
3941
4112
  var DEBOUNCE_DELAY_MS = 100;
3942
- function debouncedSave(sessionFile, history) {
4113
+ function debouncedSave(sessionFile, history, memory) {
3943
4114
  const existing = pendingSaves.get(sessionFile);
3944
4115
  if (existing) {
3945
4116
  clearTimeout(existing.timer);
3946
4117
  }
4118
+ const resolvedMemory = memory !== void 0 ? memory : existing?.memory;
3947
4119
  const timer = setTimeout(async () => {
3948
4120
  pendingSaves.delete(sessionFile);
3949
4121
  try {
3950
- await doSaveSessionHistory(sessionFile, history);
4122
+ await doSaveSessionHistory(sessionFile, history, resolvedMemory);
3951
4123
  } catch (e) {
3952
4124
  console.warn(`Debounced save failed for ${sessionFile}: ${e.message}`);
3953
4125
  }
3954
4126
  }, DEBOUNCE_DELAY_MS);
3955
- pendingSaves.set(sessionFile, { history: [...history], timer });
4127
+ pendingSaves.set(sessionFile, { history: [...history], memory: resolvedMemory, timer });
3956
4128
  }
3957
4129
  function expandHome(p) {
3958
4130
  if (!p) return p;
@@ -4017,7 +4189,11 @@ async function loadOrcreateSession(sessionId) {
4017
4189
  await fs11.access(sessionFile);
4018
4190
  const fileContent = await fs11.readFile(sessionFile, "utf-8");
4019
4191
  const sessionData = JSON.parse(fileContent);
4020
- return [sessionFile, sessionData.conversation_history || [], []];
4192
+ const memory = {
4193
+ historyAnchor: sessionData.history_anchor ?? null,
4194
+ compressedTurnSliceCount: sessionData.compressed_turn_slice_count ?? 0
4195
+ };
4196
+ return [sessionFile, sessionData.conversation_history || [], memory];
4021
4197
  } catch (error) {
4022
4198
  const newSessionData = {
4023
4199
  session_id: sessionId,
@@ -4025,10 +4201,14 @@ async function loadOrcreateSession(sessionId) {
4025
4201
  conversation_history: []
4026
4202
  };
4027
4203
  await fs11.writeFile(sessionFile, JSON.stringify(newSessionData, null, 2), "utf-8");
4028
- return [sessionFile, [], []];
4204
+ const emptyMemory = {
4205
+ historyAnchor: null,
4206
+ compressedTurnSliceCount: 0
4207
+ };
4208
+ return [sessionFile, [], emptyMemory];
4029
4209
  }
4030
4210
  }
4031
- async function doSaveSessionHistory(sessionFile, history) {
4211
+ async function doSaveSessionHistory(sessionFile, history, memory) {
4032
4212
  await withFileLock(sessionFile, async () => {
4033
4213
  let sessionData;
4034
4214
  try {
@@ -4061,6 +4241,14 @@ async function doSaveSessionHistory(sessionFile, history) {
4061
4241
  }
4062
4242
  sessionData.conversation_history = history;
4063
4243
  sessionData.last_updated = (/* @__PURE__ */ new Date()).toISOString();
4244
+ if (memory) {
4245
+ if (memory.historyAnchor) {
4246
+ sessionData.history_anchor = memory.historyAnchor;
4247
+ } else {
4248
+ delete sessionData.history_anchor;
4249
+ }
4250
+ sessionData.compressed_turn_slice_count = memory.compressedTurnSliceCount;
4251
+ }
4064
4252
  const tempSessionFile = `${sessionFile}.${Date.now()}.tmp`;
4065
4253
  try {
4066
4254
  await fs11.writeFile(tempSessionFile, JSON.stringify(sessionData, null, 2), "utf-8");
@@ -4078,14 +4266,14 @@ async function doSaveSessionHistory(sessionFile, history) {
4078
4266
  }
4079
4267
  });
4080
4268
  }
4081
- async function saveSessionHistory(sessionFile, history) {
4269
+ async function saveSessionHistory(sessionFile, history, memory) {
4082
4270
  const cleanHistory = history.filter((msg) => {
4083
4271
  if (msg.role === "user" && typeof msg.content === "string") {
4084
4272
  return !msg.content.startsWith("[SKILL:");
4085
4273
  }
4086
4274
  return true;
4087
4275
  });
4088
- debouncedSave(sessionFile, cleanHistory);
4276
+ debouncedSave(sessionFile, cleanHistory, memory);
4089
4277
  }
4090
4278
 
4091
4279
  // src/app/agent/core/prompt/prompt_builder.ts
@@ -4098,7 +4286,6 @@ import { execSync } from "child_process";
4098
4286
  import fs12 from "fs";
4099
4287
  import path14 from "path";
4100
4288
  import os7 from "os";
4101
- import { fileURLToPath as fileURLToPath3 } from "url";
4102
4289
  var SkillLoader = class _SkillLoader {
4103
4290
  bundledSkillsDir;
4104
4291
  projectSkillsDir;
@@ -4115,19 +4302,26 @@ var SkillLoader = class _SkillLoader {
4115
4302
  * Funciona tanto em ESM como quando executado a partir de dist/.
4116
4303
  */
4117
4304
  static resolveBundledDir() {
4118
- if (process.env.NODE_ENV === "test" || process.env.JEST_WORKER_ID !== void 0) {
4119
- if (typeof __dirname !== "undefined") {
4120
- return path14.join(__dirname, "config", "skills");
4121
- }
4305
+ if (process.env.JEST_WORKER_ID !== void 0 || process.env.NODE_ENV === "test") {
4122
4306
  return path14.join(process.cwd(), "dist", "config", "skills");
4123
4307
  }
4124
- try {
4125
- const currentFile = fileURLToPath3(import.meta.url);
4126
- const distDir = path14.dirname(currentFile);
4127
- return path14.join(distDir, "config", "skills");
4128
- } catch {
4129
- return path14.join(process.cwd(), "dist", "config", "skills");
4308
+ const candidates = [
4309
+ path14.join(process.cwd(), "dist", "config", "skills"),
4310
+ path14.join(process.cwd(), "node_modules", "@nomad-e", "bluma-cli", "dist", "config", "skills")
4311
+ ];
4312
+ if (typeof __dirname !== "undefined") {
4313
+ candidates.push(
4314
+ path14.join(__dirname, "config", "skills"),
4315
+ path14.join(__dirname, "..", "..", "..", "config", "skills")
4316
+ );
4130
4317
  }
4318
+ for (const c of candidates) {
4319
+ const abs = path14.resolve(c);
4320
+ if (fs12.existsSync(abs)) {
4321
+ return abs;
4322
+ }
4323
+ }
4324
+ return path14.join(process.cwd(), "dist", "config", "skills");
4131
4325
  }
4132
4326
  /**
4133
4327
  * Lista skills disponíveis de todas as fontes.
@@ -4553,19 +4747,35 @@ You MUST adapt all commands to this environment. Use the correct package manager
4553
4747
  <coding_memory>
4554
4748
  ## Persistent coding memory (tool: \`coding_memory\`)
4555
4749
 
4556
- You have **durable notes** on disk (typically \`~/.bluma/coding_memory.json\`), separate from chat history. They **survive new sessions**.
4750
+ This is your **long-term scratchpad** (usually \`~/.bluma/coding_memory.json\`). It is **not** the chat log: it persists across sessions so you can behave like a coding agent that **actually recalls** stable project facts\u2014**but only if you use the tool**.
4751
+
4752
+ ### Ground rules
4753
+ - **Never invent** what is stored: if you are not sure, run \`action: "search"\` (or \`list\`) before stating a remembered fact.
4754
+ - The **<coding_memory_snapshot>** below is a **one-time snapshot at session start**. After any \`add\`, \`remove\`, or \`update\` in this session, treat the snapshot as **possibly stale** and **search** or **list** before relying on it.
4755
+ - Do **not** ask the user for permission to search memory when it reduces risk (e.g. they mention a prior decision, branch name, or "as we agreed").
4756
+ - **There is no "clear all"**: you can only **remove** entries **one id at a time** (\`action: "remove"\`, \`id\`). To fix bad data, \`update\` or \`remove\` specific ids.
4757
+
4758
+ ### When to search first
4759
+ - User: "lembra-te / remember / last time / o que combin\xE1mos / qual era o comando".
4760
+ - You are about to edit, run tests, or refactor using **assumptions** that came from earlier in this project (stack, scripts, env vars, URLs).
4761
+ - You return to a task after many unrelated turns\u2014the chat may have been compressed; **memory on disk still holds** the durable notes if you saved them.
4557
4762
 
4558
- ### Baseline vs live refresh
4559
- - The **<coding_memory_snapshot>** block (appended after this prompt) is loaded **once at session start** from disk. It can become stale if memory changes later or if the topic was never saved.
4560
- - Act like a teammate who **checks their notes**: before relying on "what we agreed" or project-specific lore, use \`coding_memory\` with \`action: "search"\` and a short \`query\` \u2014 especially when the user asks if you remember something, or when switching back to a topic after a long stretch of work.
4763
+ ### When to add
4764
+ - **Stable** facts: architecture, naming conventions, test commands, ports, API bases, repo layout, user preferences that repeat.
4765
+ - **Pointers**, not novels: one short \`note\` + \`tags\` (e.g. \`auth\`, \`deploy\`, \`bluma\`). Put long specs in the repo; here only **reminders**.
4561
4766
 
4562
- ### When to write
4563
- - After you learn **stable** facts (architecture, conventions, important URLs, decisions, repeated user preferences), call \`coding_memory\` with \`action: "add"\`, a concise \`note\`, and \`tags\` for later search.
4564
- - When something **changes**, add an updated note (and do not treat outdated snapshot text as truth without searching).
4767
+ ### CRUD (tool actions)
4768
+ - **Create:** \`add\` + \`note\` (+ optional \`tags\`).
4769
+ - **Read:** \`list\` (all ids) or \`search\` + \`query\`.
4770
+ - **Update:** \`update\` + \`id\` + new \`note\` and/or \`tags\` (omit fields you leave unchanged).
4771
+ - **Delete:** \`remove\` + \`id\` only \u2014 **never** bulk-wipe; the product intentionally forbids it.
4772
+
4773
+ ### When something changes
4774
+ - Prefer \`update\` on the same \`id\` when correcting a note; add a **new** note if the old one should remain as history; \`remove\` obsolete ids.
4565
4775
 
4566
4776
  ### Habits (continuity)
4567
- - Prefer **search** or **list** over guessing when answering "remember / last time / what we did".
4568
- - Long specs belong in the repo or artifacts; use \`coding_memory\` for **short** reminders and pointers.
4777
+ - Prefer **search** or **list** over guessing whenever "memory" matters.
4778
+ - After important milestones (feature done, bug fixed, migration applied), consider a **brief add** so future-you in a new session is not blind.
4569
4779
  </coding_memory>
4570
4780
 
4571
4781
  ---
@@ -4601,6 +4811,7 @@ Run tests when modifying code. If a testing skill is listed in available_skills,
4601
4811
  - **ALWAYS read a file before editing** - Use read_file_lines or ls_tool first
4602
4812
  - **Use absolute paths** when possible to avoid ambiguity
4603
4813
  - **For edit_tool**: Provide exact content with correct whitespace (read first!)
4814
+ - **Truncated CLI preview** (user may press Ctrl+O for more lines): still not a substitute for \`read_file_lines\` \u2014 use the tool for authoritative content before editing
4604
4815
  - **Check file exists** before attempting edits
4605
4816
 
4606
4817
  ### Safe Auto-Approved Tools (no confirmation needed):
@@ -5080,7 +5291,7 @@ User: "Publish the package"
5080
5291
  <coding_memory_snapshot>
5081
5292
  ## Persistent notes (loaded at session start from disk)
5082
5293
 
5083
- ${memorySnapshot.trim().length > 0 ? memorySnapshot : "(No entries yet. Use coding_memory with action add or search to build continuity across sessions.)"}
5294
+ ${memorySnapshot.trim().length > 0 ? memorySnapshot : "(No entries yet. Use coding_memory: add, list, search \u2014 remove/update by id when needed.)"}
5084
5295
  </coding_memory_snapshot>
5085
5296
  `;
5086
5297
  return prompt;
@@ -5094,6 +5305,161 @@ function isGitRepo(dir) {
5094
5305
  }
5095
5306
  }
5096
5307
 
5308
+ // src/app/agent/core/context-api/token_counter.ts
5309
+ import { getEncoding } from "js-tiktoken";
5310
+ var MESSAGE_OVERHEAD_TOKENS = 4;
5311
+ var CONVERSATION_BASE_OVERHEAD = 3;
5312
+ var cachedEncoding = null;
5313
+ function getO200kEncoding() {
5314
+ if (!cachedEncoding) {
5315
+ cachedEncoding = getEncoding("o200k_base");
5316
+ }
5317
+ return cachedEncoding;
5318
+ }
5319
+ function messageBodyForTokens(msg) {
5320
+ const c = msg.content;
5321
+ if (c == null) {
5322
+ return "";
5323
+ }
5324
+ if (typeof c === "string") {
5325
+ return c;
5326
+ }
5327
+ return JSON.stringify(c);
5328
+ }
5329
+ function messageExtraForTokens(msg) {
5330
+ const m = msg;
5331
+ const parts = [String(m.role ?? "")];
5332
+ if (Array.isArray(m.tool_calls)) {
5333
+ parts.push(JSON.stringify(m.tool_calls));
5334
+ }
5335
+ if (typeof m.tool_call_id === "string") {
5336
+ parts.push(m.tool_call_id);
5337
+ }
5338
+ if (typeof m.name === "string") {
5339
+ parts.push(m.name);
5340
+ }
5341
+ return parts.join("\0");
5342
+ }
5343
+ function countTokens(messages) {
5344
+ if (messages.length === 0) {
5345
+ return CONVERSATION_BASE_OVERHEAD;
5346
+ }
5347
+ const enc = getO200kEncoding();
5348
+ let total = CONVERSATION_BASE_OVERHEAD;
5349
+ for (const msg of messages) {
5350
+ const body = messageBodyForTokens(msg);
5351
+ const extra = messageExtraForTokens(msg);
5352
+ const nBody = body ? enc.encode(body).length : 0;
5353
+ const nExtra = extra ? enc.encode(extra).length : 0;
5354
+ total += nBody + nExtra + MESSAGE_OVERHEAD_TOKENS;
5355
+ }
5356
+ return total;
5357
+ }
5358
+
5359
+ // src/app/agent/core/context-api/history_anchor.ts
5360
+ function stripJsonFence(text) {
5361
+ let s = text.trim();
5362
+ const fence = /^```(?:json)?\s*([\s\S]*?)```\s*$/im.exec(s);
5363
+ if (fence) {
5364
+ s = fence[1].trim();
5365
+ }
5366
+ return s;
5367
+ }
5368
+ function parseAnchorJson(raw) {
5369
+ const cleaned = stripJsonFence(raw);
5370
+ const start = cleaned.indexOf("{");
5371
+ const end = cleaned.lastIndexOf("}");
5372
+ if (start === -1 || end === -1 || end <= start) {
5373
+ throw new Error("compressToAnchor: resposta sem JSON object");
5374
+ }
5375
+ const parsed = JSON.parse(cleaned.slice(start, end + 1));
5376
+ return {
5377
+ intent: String(parsed.intent ?? ""),
5378
+ filesModified: Array.isArray(parsed.filesModified) ? parsed.filesModified.map(String) : [],
5379
+ decisionsMade: Array.isArray(parsed.decisionsMade) ? parsed.decisionsMade.map(String) : [],
5380
+ errorsEncountered: Array.isArray(parsed.errorsEncountered) ? parsed.errorsEncountered.map(String) : [],
5381
+ currentState: String(parsed.currentState ?? ""),
5382
+ nextSteps: String(parsed.nextSteps ?? ""),
5383
+ compressedAt: typeof parsed.compressedAt === "number" ? parsed.compressedAt : Date.now(),
5384
+ turnsCompressed: typeof parsed.turnsCompressed === "number" ? parsed.turnsCompressed : 0
5385
+ };
5386
+ }
5387
+ function formatMessagesForPrompt(msgs) {
5388
+ return msgs.map((m, i) => {
5389
+ const role = m.role ?? "?";
5390
+ const content = m.content;
5391
+ const text = typeof content === "string" ? content : content == null ? "" : JSON.stringify(content);
5392
+ const toolCalls = m.tool_calls;
5393
+ const tail = toolCalls ? `
5394
+ [tool_calls]: ${JSON.stringify(toolCalls)}` : "";
5395
+ return `--- msg ${i + 1} (${role}) ---
5396
+ ${text.slice(0, 12e4)}${tail}`;
5397
+ }).join("\n\n");
5398
+ }
5399
+ async function compressToAnchor(spansToCompress, existingAnchor, llmService, userContext, turnSlicesInBatch) {
5400
+ const messagesBlock = formatMessagesForPrompt(spansToCompress);
5401
+ const existingBlock = existingAnchor ? `ANCHOR EXISTENTE (estende este, n\xE3o substitui \u2014 merge sem perder informa\xE7\xE3o):
5402
+ ${JSON.stringify(existingAnchor, null, 2)}` : "N\xE3o existe anchor anterior.";
5403
+ const userPrompt = `Tu \xE9s um compressor de hist\xF3rico para um agente de c\xF3digo CLI.
5404
+
5405
+ ${existingBlock}
5406
+
5407
+ MENSAGENS A COMPRIMIR (turnos antigos da conversa):
5408
+ ${messagesBlock}
5409
+
5410
+ Produz um JSON com esta estrutura exata:
5411
+ {
5412
+ "intent": "objetivo principal da sess\xE3o",
5413
+ "filesModified": ["path1", "path2"],
5414
+ "decisionsMade": ["decis\xE3o 1", "decis\xE3o 2"],
5415
+ "errorsEncountered": ["erro X \u2192 resolvido com Y"],
5416
+ "currentState": "onde estamos agora em 2-3 frases",
5417
+ "nextSteps": "o que est\xE1 pendente",
5418
+ "compressedAt": ${Date.now()},
5419
+ "turnsCompressed": 0
5420
+ }
5421
+
5422
+ Regras:
5423
+ - Responde APENAS com JSON v\xE1lido, sem markdown, sem texto extra
5424
+ - filesModified: inclui TODOS os paths de ficheiros mencionados
5425
+ - decisionsMade: preserva decis\xF5es de arquitetura, abordagem, estrutura
5426
+ - errorsEncountered: formato "erro \u2192 solu\xE7\xE3o"
5427
+ - Se j\xE1 existe anchor, faz merge \u2014 n\xE3o percas informa\xE7\xE3o anterior
5428
+ - O campo turnsCompressed na resposta ser\xE1 ignorado; usa 0.`;
5429
+ const resp = await llmService.chatCompletion({
5430
+ messages: [
5431
+ {
5432
+ role: "system",
5433
+ content: "\xC9s um extrator factual. Sa\xEDda: apenas um objeto JSON v\xE1lido, sem markdown."
5434
+ },
5435
+ { role: "user", content: userPrompt }
5436
+ ],
5437
+ temperature: 0,
5438
+ max_tokens: 4096,
5439
+ userContext
5440
+ });
5441
+ const text = resp.choices[0]?.message?.content;
5442
+ if (typeof text !== "string" || !text.trim()) {
5443
+ throw new Error("compressToAnchor: modelo n\xE3o devolveu conte\xFAdo textual");
5444
+ }
5445
+ const anchor = parseAnchorJson(text);
5446
+ const prevTurns = existingAnchor?.turnsCompressed ?? 0;
5447
+ anchor.turnsCompressed = prevTurns + Math.max(0, turnSlicesInBatch);
5448
+ anchor.compressedAt = Date.now();
5449
+ return anchor;
5450
+ }
5451
+ function anchorToSystemMessage(anchor) {
5452
+ const date = new Date(anchor.compressedAt).toISOString();
5453
+ const content = `[MEM\xD3RIA DE SESS\xC3O \u2014 ${anchor.turnsCompressed} turnos comprimidos em ${date}]
5454
+ Objetivo: ${anchor.intent}
5455
+ Ficheiros modificados: ${anchor.filesModified.join(", ") || "(nenhum)"}
5456
+ Decis\xF5es tomadas: ${anchor.decisionsMade.join(" | ") || "(nenhuma)"}
5457
+ Erros resolvidos: ${anchor.errorsEncountered.join(" | ") || "(nenhum)"}
5458
+ Estado atual: ${anchor.currentState}
5459
+ Pr\xF3ximos passos: ${anchor.nextSteps}`;
5460
+ return { role: "system", content };
5461
+ }
5462
+
5097
5463
  // src/app/agent/core/context-api/context_manager.ts
5098
5464
  function isEndTurnToolCall(tc) {
5099
5465
  if (tc.function.name === "agent_end_turn") {
@@ -5109,40 +5475,26 @@ function isEndTurnToolCall(tc) {
5109
5475
  }
5110
5476
  return false;
5111
5477
  }
5112
- function createApiContextWindow(fullHistory, maxTurns) {
5113
- if (!fullHistory.length) {
5114
- return [];
5115
- }
5116
- if (maxTurns === null || maxTurns === void 0) {
5117
- return [...fullHistory];
5118
- }
5119
- const systemMessages = [];
5120
- let historyStartIndex = 0;
5121
- while (historyStartIndex < fullHistory.length && fullHistory[historyStartIndex].role === "system") {
5122
- systemMessages.push(fullHistory[historyStartIndex]);
5123
- historyStartIndex++;
5124
- }
5125
- const conversationHistory = fullHistory.slice(historyStartIndex);
5478
+ function partitionConversationIntoTurnSlices(conversationHistory) {
5126
5479
  const turns = [];
5127
5480
  let currentTurn = [];
5128
- let turnsFound = 0;
5129
5481
  const isDevOverlay = (m) => m?.role === "user" && m?.name === "user_overlay";
5130
5482
  for (let i = conversationHistory.length - 1; i >= 0; i--) {
5131
5483
  const msg = conversationHistory[i];
5132
5484
  currentTurn.unshift(msg);
5133
- const endsWithAgentEnd = msg.role === "assistant" && msg.tool_calls?.some((tc) => isEndTurnToolCall(tc));
5485
+ const endsWithAgentEnd = msg.role === "assistant" && msg.tool_calls?.some(
5486
+ (tc) => isEndTurnToolCall(tc)
5487
+ );
5134
5488
  if (endsWithAgentEnd) {
5135
5489
  turns.unshift([...currentTurn]);
5136
5490
  currentTurn = [];
5137
- turnsFound++;
5138
- if (turnsFound >= maxTurns) {
5139
- break;
5140
- }
5141
5491
  continue;
5142
5492
  }
5143
5493
  const prev = conversationHistory[i - 1];
5144
5494
  if (msg.role === "user" && !isDevOverlay(msg)) {
5145
- if (prev && prev.role === "assistant" && !prev.tool_calls?.some((tc) => isEndTurnToolCall(tc))) {
5495
+ if (prev && prev.role === "assistant" && !prev.tool_calls?.some(
5496
+ (tc) => isEndTurnToolCall(tc)
5497
+ )) {
5146
5498
  const hasNonOverlay = currentTurn.some((m) => m.role !== "user" || !isDevOverlay(m));
5147
5499
  if (hasNonOverlay) {
5148
5500
  turns.unshift([...currentTurn]);
@@ -5154,8 +5506,76 @@ function createApiContextWindow(fullHistory, maxTurns) {
5154
5506
  if (currentTurn.length > 0) {
5155
5507
  turns.unshift(currentTurn);
5156
5508
  }
5157
- const finalContext = systemMessages.concat(turns.flat());
5158
- return finalContext;
5509
+ return turns;
5510
+ }
5511
+ var DEFAULT_TOKEN_BUDGET = 6e4;
5512
+ var DEFAULT_COMPRESS_THRESHOLD = 0.7;
5513
+ var DEFAULT_KEEP_RECENT_TURNS = 8;
5514
+ function readContextOptionInt(envKey, fallback) {
5515
+ const raw = (process.env[envKey] ?? "").trim();
5516
+ if (!raw) return fallback;
5517
+ const n = Number(raw);
5518
+ return Number.isFinite(n) && n > 0 ? Math.floor(n) : fallback;
5519
+ }
5520
+ function readContextOptionFloat(envKey, fallback) {
5521
+ const raw = (process.env[envKey] ?? "").trim();
5522
+ if (!raw) return fallback;
5523
+ const n = Number(raw);
5524
+ return Number.isFinite(n) && n > 0 && n <= 1 ? n : fallback;
5525
+ }
5526
+ function buildContextMessages(systemMessages, anchor, pendingRaw, recentFlat) {
5527
+ const anchorMsg = anchor ? [anchorToSystemMessage(anchor)] : [];
5528
+ return [...systemMessages, ...anchorMsg, ...pendingRaw, ...recentFlat];
5529
+ }
5530
+ async function createApiContextWindow(fullHistory, currentAnchor, compressedTurnSliceCount, llmService, userContext, options) {
5531
+ if (!fullHistory.length) {
5532
+ return {
5533
+ messages: [],
5534
+ newAnchor: currentAnchor,
5535
+ newCompressedTurnSliceCount: compressedTurnSliceCount
5536
+ };
5537
+ }
5538
+ const tokenBudget = options?.tokenBudget ?? readContextOptionInt("BLUMA_CONTEXT_TOKEN_BUDGET", DEFAULT_TOKEN_BUDGET);
5539
+ const compressThreshold = options?.compressThreshold ?? readContextOptionFloat("BLUMA_COMPRESS_THRESHOLD", DEFAULT_COMPRESS_THRESHOLD);
5540
+ const keepRecentTurns = options?.keepRecentTurns ?? readContextOptionInt("BLUMA_KEEP_RECENT_TURNS", DEFAULT_KEEP_RECENT_TURNS);
5541
+ const systemMessages = [];
5542
+ let historyStartIndex = 0;
5543
+ while (historyStartIndex < fullHistory.length && fullHistory[historyStartIndex].role === "system") {
5544
+ systemMessages.push(fullHistory[historyStartIndex]);
5545
+ historyStartIndex++;
5546
+ }
5547
+ const conversationHistory = fullHistory.slice(historyStartIndex);
5548
+ const turnSlices = partitionConversationIntoTurnSlices(conversationHistory);
5549
+ const n = turnSlices.length;
5550
+ const recentStart = Math.max(0, n - keepRecentTurns);
5551
+ let sliceCount = Math.min(Math.max(0, compressedTurnSliceCount), recentStart);
5552
+ let anchor = currentAnchor;
5553
+ const recentSlices = turnSlices.slice(recentStart);
5554
+ const recentFlat = recentSlices.flat();
5555
+ const thresholdTokens = tokenBudget * compressThreshold;
5556
+ let pendingSlices = turnSlices.slice(sliceCount, recentStart);
5557
+ let pendingFlat = pendingSlices.flat();
5558
+ let messages = buildContextMessages(systemMessages, anchor, pendingFlat, recentFlat);
5559
+ let tokens = countTokens(messages);
5560
+ while (tokens >= thresholdTokens && pendingSlices.length > 0) {
5561
+ anchor = await compressToAnchor(
5562
+ pendingFlat,
5563
+ anchor,
5564
+ llmService,
5565
+ userContext,
5566
+ pendingSlices.length
5567
+ );
5568
+ sliceCount = recentStart;
5569
+ pendingSlices = [];
5570
+ pendingFlat = [];
5571
+ messages = buildContextMessages(systemMessages, anchor, pendingFlat, recentFlat);
5572
+ tokens = countTokens(messages);
5573
+ }
5574
+ return {
5575
+ messages,
5576
+ newAnchor: anchor,
5577
+ newCompressedTurnSliceCount: sliceCount
5578
+ };
5159
5579
  }
5160
5580
 
5161
5581
  // src/app/agent/core/llm/llm.ts
@@ -5538,10 +5958,14 @@ var BluMaAgent = class {
5538
5958
  mcpClient;
5539
5959
  feedbackSystem;
5540
5960
  skillLoader;
5541
- maxContextTurns = 5;
5961
+ /** Memória comprimida persistida (turnos antigos) + cursor de fatias já absorvidas. */
5962
+ sessionAnchor = null;
5963
+ compressedTurnSliceCount = 0;
5542
5964
  isInterrupted = false;
5543
5965
  /** Mesmo turnId durante processTurn + todo o loop de tool_calls (FactorRouter). */
5544
5966
  activeTurnContext = null;
5967
+ /** Evita POST /turns/.../end duplicado no mesmo turno (ex.: Esc após message_result). */
5968
+ factorRouterTurnClosed = false;
5545
5969
  constructor(sessionId, eventBus, llm, mcpClient, feedbackSystem) {
5546
5970
  this.sessionId = sessionId;
5547
5971
  this.eventBus = eventBus;
@@ -5551,6 +5975,7 @@ var BluMaAgent = class {
5551
5975
  this.skillLoader = new SkillLoader(process.cwd());
5552
5976
  this.eventBus.on("user_interrupt", () => {
5553
5977
  this.isInterrupted = true;
5978
+ void this.notifyFactorTurnEndIfNeeded("user_interrupt");
5554
5979
  this.eventBus.emit("backend_message", { type: "done", status: "interrupted" });
5555
5980
  });
5556
5981
  this.eventBus.on("user_overlay", async (data) => {
@@ -5559,23 +5984,38 @@ var BluMaAgent = class {
5559
5984
  this.eventBus.emit("backend_message", { type: "user_overlay", payload: clean, ts: data.ts || Date.now() });
5560
5985
  try {
5561
5986
  if (this.sessionFile) {
5562
- await saveSessionHistory(this.sessionFile, this.history);
5987
+ this.persistSession();
5563
5988
  } else {
5564
- const [sessionFile, _] = await loadOrcreateSession(this.sessionId);
5989
+ const [sessionFile, , mem] = await loadOrcreateSession(this.sessionId);
5565
5990
  this.sessionFile = sessionFile;
5566
- await saveSessionHistory(this.sessionFile, this.history);
5991
+ this.sessionAnchor = mem.historyAnchor;
5992
+ this.compressedTurnSliceCount = mem.compressedTurnSliceCount;
5993
+ this.persistSession();
5567
5994
  }
5568
5995
  } catch (e) {
5569
5996
  this.eventBus.emit("backend_message", { type: "error", message: `Falha ao salvar hist\xF3rico ap\xF3s user_overlay: ${e.message}` });
5570
5997
  }
5571
5998
  });
5572
5999
  }
6000
+ getMemorySnapshot() {
6001
+ return {
6002
+ historyAnchor: this.sessionAnchor,
6003
+ compressedTurnSliceCount: this.compressedTurnSliceCount
6004
+ };
6005
+ }
6006
+ /** Debounced: grava histórico + estado de compressão no mesmo ficheiro de sessão. */
6007
+ persistSession() {
6008
+ if (!this.sessionFile) return;
6009
+ void saveSessionHistory(this.sessionFile, this.history, this.getMemorySnapshot());
6010
+ }
5573
6011
  async initialize() {
5574
6012
  await this.mcpClient.nativeToolInvoker.initialize();
5575
6013
  await this.mcpClient.initialize();
5576
- const [sessionFile, history] = await loadOrcreateSession(this.sessionId);
6014
+ const [sessionFile, history, mem] = await loadOrcreateSession(this.sessionId);
5577
6015
  this.sessionFile = sessionFile;
5578
6016
  this.history = history;
6017
+ this.sessionAnchor = mem.historyAnchor;
6018
+ this.compressedTurnSliceCount = mem.compressedTurnSliceCount;
5579
6019
  initializeSkillContext({
5580
6020
  history: this.history,
5581
6021
  skillLoader: this.skillLoader
@@ -5601,7 +6041,7 @@ var BluMaAgent = class {
5601
6041
  }
5602
6042
  const systemPrompt = getUnifiedSystemPrompt(availableSkills);
5603
6043
  this.history.push({ role: "system", content: systemPrompt });
5604
- await saveSessionHistory(this.sessionFile, this.history);
6044
+ this.persistSession();
5605
6045
  }
5606
6046
  }
5607
6047
  getAvailableTools() {
@@ -5612,6 +6052,7 @@ var BluMaAgent = class {
5612
6052
  }
5613
6053
  async processTurn(userInput, userContextInput) {
5614
6054
  this.isInterrupted = false;
6055
+ this.factorRouterTurnClosed = false;
5615
6056
  const inputText = String(userInput.content || "").trim();
5616
6057
  const turnId = uuidv43();
5617
6058
  this.activeTurnContext = {
@@ -5630,11 +6071,13 @@ var BluMaAgent = class {
5630
6071
  let toolResultContent;
5631
6072
  let shouldContinueConversation = true;
5632
6073
  if (!this.sessionFile) {
5633
- const [sessionFile, history] = await loadOrcreateSession(this.sessionId);
6074
+ const [sessionFile, history, mem] = await loadOrcreateSession(this.sessionId);
5634
6075
  this.sessionFile = sessionFile;
5635
6076
  if (this.history.length === 0 && history.length > 0) {
5636
6077
  this.history = history;
5637
6078
  }
6079
+ this.sessionAnchor = mem.historyAnchor;
6080
+ this.compressedTurnSliceCount = mem.compressedTurnSliceCount;
5638
6081
  }
5639
6082
  if (decisionData.type === "user_decision_execute") {
5640
6083
  const toolName = toolCall.function.name;
@@ -5656,7 +6099,7 @@ var BluMaAgent = class {
5656
6099
  raw_arguments: toolCall.function.arguments
5657
6100
  });
5658
6101
  this.history.push({ role: "tool", tool_call_id: toolCall.id, content: toolResultContent });
5659
- await saveSessionHistory(this.sessionFile, this.history);
6102
+ this.persistSession();
5660
6103
  await this._continueConversation();
5661
6104
  return;
5662
6105
  }
@@ -5668,7 +6111,7 @@ var BluMaAgent = class {
5668
6111
  received: toolArgs
5669
6112
  });
5670
6113
  this.history.push({ role: "tool", tool_call_id: toolCall.id, content: toolResultContent });
5671
- await saveSessionHistory(this.sessionFile, this.history);
6114
+ this.persistSession();
5672
6115
  await this._continueConversation();
5673
6116
  return;
5674
6117
  }
@@ -5679,7 +6122,7 @@ var BluMaAgent = class {
5679
6122
  received: toolArgs
5680
6123
  });
5681
6124
  this.history.push({ role: "tool", tool_call_id: toolCall.id, content: toolResultContent });
5682
- await saveSessionHistory(this.sessionFile, this.history);
6125
+ this.persistSession();
5683
6126
  await this._continueConversation();
5684
6127
  return;
5685
6128
  }
@@ -5738,13 +6181,7 @@ var BluMaAgent = class {
5738
6181
  try {
5739
6182
  const resultObj = typeof toolResultContent === "string" ? JSON.parse(toolResultContent) : toolResultContent;
5740
6183
  if (resultObj.message_type === "result") {
5741
- if (this.activeTurnContext) {
5742
- await notifyFactorRouterTurnEnd({
5743
- turnId: this.activeTurnContext.turnId,
5744
- userContext: this.activeTurnContext,
5745
- reason: "message_result"
5746
- });
5747
- }
6184
+ await this.notifyFactorTurnEndIfNeeded("message_result");
5748
6185
  shouldContinueConversation = false;
5749
6186
  this.eventBus.emit("backend_message", { type: "done", status: "completed" });
5750
6187
  }
@@ -5755,7 +6192,7 @@ var BluMaAgent = class {
5755
6192
  toolResultContent = "The system rejected this action. Verify that the command you are executing contributes to the tasks intent and try again.";
5756
6193
  }
5757
6194
  this.history.push({ role: "tool", tool_call_id: toolCall.id, content: toolResultContent });
5758
- await saveSessionHistory(this.sessionFile, this.history);
6195
+ this.persistSession();
5759
6196
  if (shouldContinueConversation && !this.isInterrupted) {
5760
6197
  await this._continueConversation();
5761
6198
  }
@@ -5788,13 +6225,33 @@ ${editData.error.display}`;
5788
6225
  }
5789
6226
  return this.activeTurnContext;
5790
6227
  }
6228
+ /** Um único aviso ao Factor Router por turno (fim normal ou interrupção). */
6229
+ async notifyFactorTurnEndIfNeeded(reason) {
6230
+ if (this.factorRouterTurnClosed || !this.activeTurnContext) return;
6231
+ this.factorRouterTurnClosed = true;
6232
+ const ctx = this.activeTurnContext;
6233
+ await notifyFactorRouterTurnEnd({
6234
+ turnId: ctx.turnId,
6235
+ userContext: ctx,
6236
+ reason
6237
+ });
6238
+ }
5791
6239
  async _continueConversation() {
5792
6240
  try {
5793
6241
  if (this.isInterrupted) {
5794
6242
  this.eventBus.emit("backend_message", { type: "info", message: "Task Canceled." });
5795
6243
  return;
5796
6244
  }
5797
- const contextWindow = createApiContextWindow(this.history, this.maxContextTurns);
6245
+ const { messages: contextWindow, newAnchor, newCompressedTurnSliceCount } = await createApiContextWindow(
6246
+ this.history,
6247
+ this.sessionAnchor,
6248
+ this.compressedTurnSliceCount,
6249
+ this.llm,
6250
+ this.getLlmUserContext()
6251
+ );
6252
+ this.sessionAnchor = newAnchor;
6253
+ this.compressedTurnSliceCount = newCompressedTurnSliceCount;
6254
+ this.persistSession();
5798
6255
  const llmService = this.llm;
5799
6256
  if (typeof llmService.chatCompletionStream === "function") {
5800
6257
  await this._handleStreamingResponse(contextWindow);
@@ -5805,7 +6262,7 @@ ${editData.error.display}`;
5805
6262
  const errorMessage = error instanceof Error ? error.message : "An unknown API error occurred.";
5806
6263
  this.eventBus.emit("backend_message", { type: "error", message: errorMessage });
5807
6264
  } finally {
5808
- await saveSessionHistory(this.sessionFile, this.history);
6265
+ this.persistSession();
5809
6266
  }
5810
6267
  }
5811
6268
  async _handleStreamingResponse(contextWindow) {
@@ -6614,18 +7071,15 @@ var Agent = class {
6614
7071
 
6615
7072
  // src/app/ui/WorkingTimer.tsx
6616
7073
  import { useState as useState4, useEffect as useEffect4, memo as memo5 } from "react";
6617
- import { Box as Box7, Text as Text7 } from "ink";
6618
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
7074
+ import { Box as Box8, Text as Text8 } from "ink";
7075
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
6619
7076
  var WorkingTimerComponent = ({ eventBus, taskName, taskStatus }) => {
6620
- const [currentAction, setCurrentAction] = useState4("Thinking...");
7077
+ const [currentAction, setCurrentAction] = useState4("working");
6621
7078
  const [shinePosition, setShinePosition] = useState4(0);
6622
- const [dots, setDots] = useState4("");
6623
7079
  useEffect4(() => {
6624
7080
  if (!eventBus) return;
6625
7081
  const handleActionStatus = (data) => {
6626
- if (data.action) {
6627
- setCurrentAction(data.action);
6628
- }
7082
+ if (data.action) setCurrentAction(data.action);
6629
7083
  };
6630
7084
  eventBus.on("action_status", handleActionStatus);
6631
7085
  return () => {
@@ -6638,12 +7092,6 @@ var WorkingTimerComponent = ({ eventBus, taskName, taskStatus }) => {
6638
7092
  }, 120);
6639
7093
  return () => clearInterval(shineTimer);
6640
7094
  }, []);
6641
- useEffect4(() => {
6642
- const dotsTimer = setInterval(() => {
6643
- setDots((prev) => prev.length >= 3 ? "" : prev + ".");
6644
- }, 500);
6645
- return () => clearInterval(dotsTimer);
6646
- }, []);
6647
7095
  const displayAction = taskStatus || currentAction;
6648
7096
  const renderShineText = (text) => {
6649
7097
  const chars = text.split("");
@@ -6652,29 +7100,28 @@ var WorkingTimerComponent = ({ eventBus, taskName, taskStatus }) => {
6652
7100
  return chars.map((char, i) => {
6653
7101
  const distance = Math.abs(i - shineIdx);
6654
7102
  if (distance <= 1) {
6655
- return /* @__PURE__ */ jsx7(Text7, { color: "white", dimColor: true, children: char }, i);
6656
- } else {
6657
- return /* @__PURE__ */ jsx7(Text7, { color: "gray", dimColor: true, children: char }, i);
7103
+ return /* @__PURE__ */ jsx8(Text8, { color: BLUMA_TERMINAL.brandMagenta, children: char }, i);
6658
7104
  }
7105
+ return /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: char }, i);
6659
7106
  });
6660
7107
  };
6661
- return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", paddingX: 1, children: [
6662
- /* @__PURE__ */ jsx7(Box7, { children: renderShineText(displayAction) }),
6663
- taskName && /* @__PURE__ */ jsx7(Box7, { paddingLeft: 2, children: /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
6664
- "\u203A ",
7108
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginBottom: 1, children: [
7109
+ /* @__PURE__ */ jsx8(M3StatusStrip, { children: renderShineText(displayAction) }),
7110
+ taskName ? /* @__PURE__ */ jsx8(Box8, { paddingLeft: 2, children: /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
7111
+ /* @__PURE__ */ jsx8(Text8, { color: BLUMA_TERMINAL.brandBlue, children: "\u2514 " }),
6665
7112
  taskName
6666
- ] }) })
7113
+ ] }) }) : null
6667
7114
  ] });
6668
7115
  };
6669
7116
  var WorkingTimer = memo5(WorkingTimerComponent);
6670
7117
 
6671
7118
  // src/app/ui/components/ToolCallDisplay.tsx
6672
7119
  import { memo as memo6 } from "react";
6673
- import { Box as Box9 } from "ink";
7120
+ import { Box as Box10 } from "ink";
6674
7121
 
6675
7122
  // src/app/ui/components/toolCallRenderers.tsx
6676
- import { Box as Box8, Text as Text8 } from "ink";
6677
- import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
7123
+ import { Box as Box9, Text as Text9 } from "ink";
7124
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
6678
7125
  var parseArgs = (args) => {
6679
7126
  if (typeof args === "string") {
6680
7127
  try {
@@ -6696,9 +7143,9 @@ var getBasename = (filepath) => {
6696
7143
  var renderShellCommand2 = ({ args }) => {
6697
7144
  const parsed = parseArgs(args);
6698
7145
  const command = parsed.command || "[no command]";
6699
- return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
6700
- /* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "$" }),
6701
- /* @__PURE__ */ jsxs8(Text8, { color: "white", children: [
7146
+ return /* @__PURE__ */ jsxs9(Box9, { children: [
7147
+ /* @__PURE__ */ jsx9(Text9, { color: BLUMA_TERMINAL.brandBlue, bold: true, children: "$" }),
7148
+ /* @__PURE__ */ jsxs9(Text9, { children: [
6702
7149
  " ",
6703
7150
  truncate(command, 70)
6704
7151
  ] })
@@ -6707,9 +7154,9 @@ var renderShellCommand2 = ({ args }) => {
6707
7154
  var renderLsTool2 = ({ args }) => {
6708
7155
  const parsed = parseArgs(args);
6709
7156
  const path19 = parsed.directory_path || ".";
6710
- return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
6711
- /* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "ls" }),
6712
- /* @__PURE__ */ jsxs8(Text8, { children: [
7157
+ return /* @__PURE__ */ jsxs9(Box9, { children: [
7158
+ /* @__PURE__ */ jsx9(Text9, { color: BLUMA_TERMINAL.brandBlue, bold: true, children: "ls" }),
7159
+ /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
6713
7160
  " ",
6714
7161
  path19
6715
7162
  ] })
@@ -6718,9 +7165,9 @@ var renderLsTool2 = ({ args }) => {
6718
7165
  var renderCountFilesLines = ({ args }) => {
6719
7166
  const parsed = parseArgs(args);
6720
7167
  const filepath = parsed.filepath || "[no file]";
6721
- return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
6722
- /* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "wc -l" }),
6723
- /* @__PURE__ */ jsxs8(Text8, { children: [
7168
+ return /* @__PURE__ */ jsxs9(Box9, { children: [
7169
+ /* @__PURE__ */ jsx9(Text9, { color: BLUMA_TERMINAL.brandBlue, bold: true, children: "wc -l" }),
7170
+ /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
6724
7171
  " ",
6725
7172
  getBasename(filepath)
6726
7173
  ] })
@@ -6731,14 +7178,15 @@ var renderReadFileLines2 = ({ args }) => {
6731
7178
  const filepath = parsed.filepath || "[no file]";
6732
7179
  const start = parsed.start_line || 1;
6733
7180
  const end = parsed.end_line || start;
6734
- return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
6735
- /* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "cat" }),
6736
- /* @__PURE__ */ jsxs8(Text8, { children: [
7181
+ return /* @__PURE__ */ jsxs9(Box9, { children: [
7182
+ /* @__PURE__ */ jsx9(Text9, { color: BLUMA_TERMINAL.brandBlue, bold: true, children: "cat" }),
7183
+ /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
6737
7184
  " ",
6738
7185
  getBasename(filepath)
6739
7186
  ] }),
6740
- /* @__PURE__ */ jsxs8(Text8, { color: "white", children: [
6741
- " :",
7187
+ /* @__PURE__ */ jsxs9(Text9, { color: BLUMA_TERMINAL.brandMagenta, children: [
7188
+ " ",
7189
+ ":",
6742
7190
  start,
6743
7191
  "-",
6744
7192
  end
@@ -6749,9 +7197,9 @@ var renderBlumaNotebook = ({ args }) => {
6749
7197
  const parsed = parseArgs(args);
6750
7198
  const thought = parsed.thought || parsed.content?.thought || "[thinking...]";
6751
7199
  const truncated = truncate(thought, 100);
6752
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 1, children: [
6753
- /* @__PURE__ */ jsx8(Box8, { children: /* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "thinking" }) }),
6754
- /* @__PURE__ */ jsx8(Box8, { paddingLeft: 2, children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, italic: true, children: truncated }) })
7200
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
7201
+ /* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsx9(Text9, { color: BLUMA_TERMINAL.brandMagenta, bold: true, children: "thinking" }) }),
7202
+ /* @__PURE__ */ jsx9(Box9, { paddingLeft: 2, children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, italic: true, children: truncated }) })
6755
7203
  ] });
6756
7204
  };
6757
7205
  var renderEditToolCall = ({ args, preview }) => {
@@ -6759,20 +7207,20 @@ var renderEditToolCall = ({ args, preview }) => {
6759
7207
  const filepath = parsed.file_path || "[no file]";
6760
7208
  const oldStr = parsed.old_string || "";
6761
7209
  const newStr = parsed.new_string || "";
6762
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 1, children: [
6763
- /* @__PURE__ */ jsxs8(Box8, { children: [
6764
- /* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "edit" }),
6765
- /* @__PURE__ */ jsxs8(Text8, { children: [
7210
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
7211
+ /* @__PURE__ */ jsxs9(Box9, { children: [
7212
+ /* @__PURE__ */ jsx9(Text9, { color: BLUMA_TERMINAL.brandMagenta, bold: true, children: "edit" }),
7213
+ /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
6766
7214
  " ",
6767
7215
  getBasename(filepath)
6768
7216
  ] })
6769
7217
  ] }),
6770
- preview ? /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(SimpleDiff, { text: preview, maxHeight: 8 }) }) : /* @__PURE__ */ jsxs8(Box8, { paddingLeft: 2, flexDirection: "column", children: [
6771
- /* @__PURE__ */ jsxs8(Text8, { color: "red", dimColor: true, children: [
7218
+ preview ? /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(SimpleDiff, { text: preview, maxHeight: 8 }) }) : /* @__PURE__ */ jsxs9(Box9, { paddingLeft: 2, flexDirection: "column", children: [
7219
+ /* @__PURE__ */ jsxs9(Text9, { color: "red", dimColor: true, children: [
6772
7220
  "- ",
6773
7221
  truncate(oldStr, 50)
6774
7222
  ] }),
6775
- /* @__PURE__ */ jsxs8(Text8, { color: "green", bold: true, children: [
7223
+ /* @__PURE__ */ jsxs9(Text9, { color: "green", bold: true, children: [
6776
7224
  "+ ",
6777
7225
  truncate(newStr, 50)
6778
7226
  ] })
@@ -6788,36 +7236,40 @@ var renderTodoTool2 = ({ args }) => {
6788
7236
  const barWidth = 10;
6789
7237
  const filled = Math.round(progress / 100 * barWidth);
6790
7238
  const bar = "=".repeat(filled) + " ".repeat(barWidth - filled);
6791
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 1, children: [
6792
- /* @__PURE__ */ jsxs8(Box8, { children: [
6793
- /* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "todo" }),
6794
- /* @__PURE__ */ jsxs8(Text8, { children: [
6795
- " [",
7239
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
7240
+ /* @__PURE__ */ jsxs9(Box9, { flexDirection: "row", flexWrap: "wrap", children: [
7241
+ /* @__PURE__ */ jsx9(Text9, { color: BLUMA_TERMINAL.brandBlue, bold: true, children: pending }),
7242
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: " open \xB7 " }),
7243
+ /* @__PURE__ */ jsx9(Text9, { color: BLUMA_TERMINAL.brandMagenta, bold: true, children: completed }),
7244
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: " done \xB7 " }),
7245
+ /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
7246
+ "[",
6796
7247
  bar,
6797
7248
  "] ",
6798
7249
  progress,
6799
7250
  "%"
6800
7251
  ] })
6801
7252
  ] }),
6802
- tasks.length > 0 && /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingLeft: 2, children: [
6803
- tasks.slice(0, 15).map((task, i) => /* @__PURE__ */ jsxs8(
6804
- Text8,
6805
- {
6806
- color: task.isComplete === true ? "gray" : "white",
6807
- dimColor: task.isComplete === true,
6808
- strikethrough: task.isComplete === true,
6809
- children: [
6810
- task.isComplete === true ? "[x]" : "[ ]",
6811
- " ",
6812
- task.description
6813
- ]
6814
- },
6815
- i
6816
- )),
6817
- tasks.length > 15 && /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
6818
- "... +",
7253
+ tasks.length > 0 && /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginTop: 1, paddingLeft: 1, children: [
7254
+ tasks.slice(0, 15).map((task, i) => {
7255
+ const done = task.isComplete === true;
7256
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "row", children: [
7257
+ /* @__PURE__ */ jsx9(Text9, { color: done ? BLUMA_TERMINAL.brandBlue : BLUMA_TERMINAL.brandMagenta, children: done ? "\u25A0 " : "\u25A1 " }),
7258
+ /* @__PURE__ */ jsx9(
7259
+ Text9,
7260
+ {
7261
+ dimColor: done,
7262
+ strikethrough: done,
7263
+ color: done ? BLUMA_TERMINAL.muted : void 0,
7264
+ children: task.description
7265
+ }
7266
+ )
7267
+ ] }, i);
7268
+ }),
7269
+ tasks.length > 15 && /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
7270
+ "\u2026 +",
6819
7271
  tasks.length - 15,
6820
- " more tasks"
7272
+ " tasks"
6821
7273
  ] })
6822
7274
  ] })
6823
7275
  ] });
@@ -6826,14 +7278,14 @@ var renderFindByName = ({ args }) => {
6826
7278
  const parsed = parseArgs(args);
6827
7279
  const pattern = parsed.pattern || "*";
6828
7280
  const dir = parsed.directory || ".";
6829
- return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
6830
- /* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "find" }),
6831
- /* @__PURE__ */ jsxs8(Text8, { color: "white", children: [
7281
+ return /* @__PURE__ */ jsxs9(Box9, { children: [
7282
+ /* @__PURE__ */ jsx9(Text9, { color: BLUMA_TERMINAL.brandBlue, bold: true, children: "find" }),
7283
+ /* @__PURE__ */ jsxs9(Text9, { color: BLUMA_TERMINAL.brandMagenta, children: [
6832
7284
  ' "',
6833
7285
  pattern,
6834
7286
  '"'
6835
7287
  ] }),
6836
- /* @__PURE__ */ jsxs8(Text8, { children: [
7288
+ /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
6837
7289
  " in ",
6838
7290
  dir
6839
7291
  ] })
@@ -6843,14 +7295,14 @@ var renderGrepSearch = ({ args }) => {
6843
7295
  const parsed = parseArgs(args);
6844
7296
  const query = parsed.query || "";
6845
7297
  const path19 = parsed.path || ".";
6846
- return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
6847
- /* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "grep" }),
6848
- /* @__PURE__ */ jsxs8(Text8, { color: "white", children: [
7298
+ return /* @__PURE__ */ jsxs9(Box9, { children: [
7299
+ /* @__PURE__ */ jsx9(Text9, { color: BLUMA_TERMINAL.brandBlue, bold: true, children: "grep" }),
7300
+ /* @__PURE__ */ jsxs9(Text9, { color: BLUMA_TERMINAL.brandMagenta, children: [
6849
7301
  ' "',
6850
7302
  truncate(query, 30),
6851
7303
  '"'
6852
7304
  ] }),
6853
- /* @__PURE__ */ jsxs8(Text8, { children: [
7305
+ /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
6854
7306
  " ",
6855
7307
  path19
6856
7308
  ] })
@@ -6859,9 +7311,9 @@ var renderGrepSearch = ({ args }) => {
6859
7311
  var renderViewFileOutline = ({ args }) => {
6860
7312
  const parsed = parseArgs(args);
6861
7313
  const filepath = parsed.file_path || "[no file]";
6862
- return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
6863
- /* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "outline" }),
6864
- /* @__PURE__ */ jsxs8(Text8, { children: [
7314
+ return /* @__PURE__ */ jsxs9(Box9, { children: [
7315
+ /* @__PURE__ */ jsx9(Text9, { color: BLUMA_TERMINAL.brandBlue, bold: true, children: "outline" }),
7316
+ /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
6865
7317
  " ",
6866
7318
  getBasename(filepath)
6867
7319
  ] })
@@ -6870,9 +7322,9 @@ var renderViewFileOutline = ({ args }) => {
6870
7322
  var renderCommandStatus = ({ args }) => {
6871
7323
  const parsed = parseArgs(args);
6872
7324
  const id = parsed.command_id || "[no id]";
6873
- return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
6874
- /* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "status" }),
6875
- /* @__PURE__ */ jsxs8(Text8, { children: [
7325
+ return /* @__PURE__ */ jsxs9(Box9, { children: [
7326
+ /* @__PURE__ */ jsx9(Text9, { color: BLUMA_TERMINAL.brandBlue, bold: true, children: "status" }),
7327
+ /* @__PURE__ */ jsxs9(Text9, { color: BLUMA_TERMINAL.brandMagenta, children: [
6876
7328
  " #",
6877
7329
  id
6878
7330
  ] })
@@ -6884,36 +7336,36 @@ var renderTaskBoundary = ({ args }) => {
6884
7336
  const mode = parsed.mode || "EXECUTION";
6885
7337
  const status = parsed.task_status || "";
6886
7338
  const modeColors = {
6887
- PLANNING: "blue",
6888
- EXECUTION: "green",
6889
- VERIFICATION: "yellow"
7339
+ PLANNING: BLUMA_TERMINAL.brandBlue,
7340
+ EXECUTION: BLUMA_TERMINAL.brandBlue,
7341
+ VERIFICATION: BLUMA_TERMINAL.brandMagenta
6890
7342
  };
6891
7343
  const modeSymbols = {
6892
7344
  PLANNING: "P",
6893
7345
  EXECUTION: "E",
6894
7346
  VERIFICATION: "V"
6895
7347
  };
6896
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 1, children: [
6897
- /* @__PURE__ */ jsxs8(Box8, { children: [
6898
- /* @__PURE__ */ jsxs8(Text8, { color: modeColors[mode] || "gray", bold: true, children: [
7348
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
7349
+ /* @__PURE__ */ jsxs9(Box9, { children: [
7350
+ /* @__PURE__ */ jsxs9(Text9, { color: modeColors[mode] || BLUMA_TERMINAL.muted, bold: true, children: [
6899
7351
  "[",
6900
7352
  modeSymbols[mode] || "?",
6901
7353
  "]"
6902
7354
  ] }),
6903
- /* @__PURE__ */ jsxs8(Text8, { children: [
7355
+ /* @__PURE__ */ jsxs9(Text9, { children: [
6904
7356
  " ",
6905
7357
  name
6906
7358
  ] })
6907
7359
  ] }),
6908
- status && /* @__PURE__ */ jsx8(Box8, { paddingLeft: 4, children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: truncate(status, 60) }) })
7360
+ status && /* @__PURE__ */ jsx9(Box9, { paddingLeft: 4, children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: truncate(status, 60) }) })
6909
7361
  ] });
6910
7362
  };
6911
7363
  var renderSearchWeb = ({ args }) => {
6912
7364
  const parsed = parseArgs(args);
6913
7365
  const query = parsed.query || "[no query]";
6914
- return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
6915
- /* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "search" }),
6916
- /* @__PURE__ */ jsxs8(Text8, { color: "white", children: [
7366
+ return /* @__PURE__ */ jsxs9(Box9, { children: [
7367
+ /* @__PURE__ */ jsx9(Text9, { color: BLUMA_TERMINAL.brandBlue, bold: true, children: "search" }),
7368
+ /* @__PURE__ */ jsxs9(Text9, { color: BLUMA_TERMINAL.brandMagenta, children: [
6917
7369
  ' "',
6918
7370
  truncate(query, 40),
6919
7371
  '"'
@@ -6923,9 +7375,9 @@ var renderSearchWeb = ({ args }) => {
6923
7375
  var renderLoadSkill = ({ args }) => {
6924
7376
  const parsed = parseArgs(args);
6925
7377
  const skillName = parsed.skill_name || "[no skill]";
6926
- return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
6927
- /* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "load skill" }),
6928
- /* @__PURE__ */ jsxs8(Text8, { color: "white", children: [
7378
+ return /* @__PURE__ */ jsxs9(Box9, { children: [
7379
+ /* @__PURE__ */ jsx9(Text9, { color: BLUMA_TERMINAL.brandBlue, bold: true, children: "load skill" }),
7380
+ /* @__PURE__ */ jsxs9(Text9, { color: BLUMA_TERMINAL.brandMagenta, children: [
6929
7381
  " ",
6930
7382
  skillName
6931
7383
  ] })
@@ -6934,13 +7386,7 @@ var renderLoadSkill = ({ args }) => {
6934
7386
  var renderGeneric2 = ({ toolName, args }) => {
6935
7387
  const parsed = parseArgs(args);
6936
7388
  const keys = Object.keys(parsed).slice(0, 2);
6937
- return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
6938
- /* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: toolName }),
6939
- keys.length > 0 && /* @__PURE__ */ jsxs8(Text8, { children: [
6940
- " ",
6941
- keys.map((k) => `${k}:${truncate(String(parsed[k]), 20)}`).join(" ")
6942
- ] })
6943
- ] });
7389
+ return /* @__PURE__ */ jsx9(Box9, { children: keys.length > 0 ? /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: keys.map((k) => `${k}:${truncate(String(parsed[k]), 20)}`).join(" ") }) : /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "\u2014" }) });
6944
7390
  };
6945
7391
  var ToolRenderDisplay = {
6946
7392
  shell_command: renderShellCommand2,
@@ -6960,76 +7406,230 @@ var ToolRenderDisplay = {
6960
7406
  };
6961
7407
 
6962
7408
  // src/app/ui/components/ToolCallDisplay.tsx
6963
- import { jsx as jsx9 } from "react/jsx-runtime";
7409
+ import { jsx as jsx10 } from "react/jsx-runtime";
6964
7410
  var ToolCallDisplayComponent = ({ toolName, args, preview }) => {
6965
- if (toolName.includes("message")) {
7411
+ if (toolName.includes("message") || toolName.includes("task_boundary") || toolName === "todo") {
6966
7412
  return null;
6967
7413
  }
6968
7414
  const Renderer = ToolRenderDisplay[toolName] || ((props) => renderGeneric2({ ...props, toolName }));
6969
- return /* @__PURE__ */ jsx9(Box9, { marginBottom: 1, children: /* @__PURE__ */ jsx9(Renderer, { toolName, args, preview }) });
7415
+ return /* @__PURE__ */ jsx10(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", paddingLeft: 1, children: /* @__PURE__ */ jsx10(Renderer, { toolName, args, preview }) }) });
6970
7416
  };
6971
7417
  var ToolCallDisplay = memo6(ToolCallDisplayComponent);
6972
7418
 
6973
7419
  // src/app/ui/components/ToolResultDisplay.tsx
6974
- import { memo as memo8 } from "react";
6975
- import { Box as Box11, Text as Text10 } from "ink";
7420
+ import { memo as memo8, useEffect as useEffect5 } from "react";
7421
+ import { Box as Box12, Text as Text11 } from "ink";
6976
7422
 
6977
7423
  // src/app/ui/components/MarkdownRenderer.tsx
6978
- import { memo as memo7 } from "react";
6979
- import { Box as Box10, Text as Text9 } from "ink";
7424
+ import { cloneElement, isValidElement, memo as memo7 } from "react";
7425
+ import { Box as Box11, Text as Text10 } from "ink";
6980
7426
  import { marked } from "marked";
6981
7427
  import { highlight } from "cli-highlight";
6982
- import { Fragment as Fragment3, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
6983
- var COLORS = {
6984
- heading: "cyan",
6985
- code: "gray",
6986
- link: "blue",
6987
- quote: "gray",
6988
- listBullet: "gray"
6989
- };
6990
- function renderTokens(tokens) {
7428
+ import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
7429
+ marked.setOptions({ gfm: true });
7430
+ function styleInlineNodes(nodes, keyPrefix, style) {
7431
+ return nodes.map(
7432
+ (n, j) => isValidElement(n) ? cloneElement(n, {
7433
+ key: `${keyPrefix}-${j}`,
7434
+ ...style
7435
+ }) : n
7436
+ );
7437
+ }
7438
+ function headingColor(depth) {
7439
+ if (depth <= 1) return BLUMA_TERMINAL.heading1;
7440
+ if (depth === 2) return BLUMA_TERMINAL.heading2;
7441
+ return BLUMA_TERMINAL.headingDeep;
7442
+ }
7443
+ function bulletGlyph(ordered, index, start, task, checked, depth) {
7444
+ if (task) return checked ? "[x]" : "[ ]";
7445
+ if (ordered) {
7446
+ const n = typeof start === "number" ? start + index : index + 1;
7447
+ return `${n}.`;
7448
+ }
7449
+ return depth === 0 ? "\xB7" : "\u2514";
7450
+ }
7451
+ function walkInline(tokens, keyBase, opts = {}) {
7452
+ if (!tokens?.length) return [];
7453
+ const out = [];
7454
+ tokens.forEach((t, i) => {
7455
+ const k = `${keyBase}-i${i}`;
7456
+ switch (t.type) {
7457
+ case "text":
7458
+ out.push(
7459
+ /* @__PURE__ */ jsx11(
7460
+ Text10,
7461
+ {
7462
+ bold: opts.bold,
7463
+ italic: opts.italic,
7464
+ strikethrough: opts.strike,
7465
+ underline: opts.link,
7466
+ color: opts.link ? BLUMA_TERMINAL.link : opts.bold ? BLUMA_TERMINAL.brandBlue : void 0,
7467
+ children: t.text
7468
+ },
7469
+ k
7470
+ )
7471
+ );
7472
+ break;
7473
+ case "strong":
7474
+ out.push(...walkInline(t.tokens, k, { ...opts, bold: true }));
7475
+ break;
7476
+ case "em":
7477
+ out.push(...walkInline(t.tokens, k, { ...opts, italic: true }));
7478
+ break;
7479
+ case "codespan":
7480
+ out.push(
7481
+ /* @__PURE__ */ jsx11(
7482
+ Text10,
7483
+ {
7484
+ color: opts.link ? BLUMA_TERMINAL.link : BLUMA_TERMINAL.code,
7485
+ backgroundColor: opts.link ? void 0 : "black",
7486
+ underline: opts.link,
7487
+ children: opts.link ? t.text : `\xA0${t.text}\xA0`
7488
+ },
7489
+ k
7490
+ )
7491
+ );
7492
+ break;
7493
+ case "link": {
7494
+ const L = t;
7495
+ const inner = walkInline(L.tokens, k + "l", { ...opts, link: true });
7496
+ out.push(
7497
+ /* @__PURE__ */ jsx11(Box11, { flexDirection: "row", flexWrap: "wrap", children: inner.length > 0 ? inner : /* @__PURE__ */ jsx11(Text10, { color: BLUMA_TERMINAL.link, underline: true, children: L.text }) }, k)
7498
+ );
7499
+ break;
7500
+ }
7501
+ case "br":
7502
+ out.push(
7503
+ /* @__PURE__ */ jsx11(Text10, { dimColor: true, children: "\n" }, k)
7504
+ );
7505
+ break;
7506
+ case "escape":
7507
+ out.push(
7508
+ /* @__PURE__ */ jsx11(
7509
+ Text10,
7510
+ {
7511
+ bold: opts.bold,
7512
+ italic: opts.italic,
7513
+ underline: opts.link,
7514
+ color: opts.link ? BLUMA_TERMINAL.link : void 0,
7515
+ children: t.text
7516
+ },
7517
+ k
7518
+ )
7519
+ );
7520
+ break;
7521
+ case "del":
7522
+ out.push(...walkInline(t.tokens, k, { ...opts, strike: true }));
7523
+ break;
7524
+ case "image": {
7525
+ const Im = t;
7526
+ out.push(
7527
+ /* @__PURE__ */ jsxs10(Text10, { dimColor: true, italic: true, children: [
7528
+ "[",
7529
+ Im.text || "img",
7530
+ "]"
7531
+ ] }, k)
7532
+ );
7533
+ break;
7534
+ }
7535
+ default:
7536
+ if ("text" in t && typeof t.text === "string") {
7537
+ out.push(
7538
+ /* @__PURE__ */ jsx11(Text10, { bold: opts.bold, italic: opts.italic, children: t.text }, k)
7539
+ );
7540
+ }
7541
+ }
7542
+ });
7543
+ return out;
7544
+ }
7545
+ function renderParagraph(p, key) {
7546
+ const nodes = walkInline(p.tokens, key);
7547
+ return /* @__PURE__ */ jsx11(Box11, { marginBottom: 1, flexDirection: "row", flexWrap: "wrap", children: nodes.length > 0 ? nodes : /* @__PURE__ */ jsx11(Text10, { dimColor: true, children: p.text }) }, key);
7548
+ }
7549
+ function renderListItemBlocks(item, depth, keyBase) {
7550
+ if (!item.tokens?.length) {
7551
+ const nodes = walkInline(
7552
+ [{ type: "text", raw: item.text, text: item.text }],
7553
+ keyBase
7554
+ );
7555
+ return nodes.length ? [/* @__PURE__ */ jsx11(Box11, { flexDirection: "row", flexWrap: "wrap", children: nodes }, keyBase)] : [];
7556
+ }
7557
+ const blocks = [];
7558
+ item.tokens.forEach((sub, si) => {
7559
+ const sk = `${keyBase}-b${si}`;
7560
+ if (sub.type === "paragraph") {
7561
+ blocks.push(renderParagraph(sub, sk));
7562
+ } else if (sub.type === "list") {
7563
+ blocks.push(renderListBlock(sub, depth + 1, sk));
7564
+ } else if (sub.type === "text") {
7565
+ const n = walkInline([sub], sk);
7566
+ if (n.length) blocks.push(/* @__PURE__ */ jsx11(Box11, { flexDirection: "row", flexWrap: "wrap", children: n }, sk));
7567
+ }
7568
+ });
7569
+ return blocks;
7570
+ }
7571
+ function renderListBlock(list, depth, keyBase) {
7572
+ return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", marginBottom: 1, paddingLeft: depth > 0 ? 2 : 0, children: list.items.map((item, idx) => {
7573
+ const glyph = bulletGlyph(
7574
+ list.ordered,
7575
+ idx,
7576
+ list.start,
7577
+ item.task,
7578
+ item.checked,
7579
+ depth
7580
+ );
7581
+ const gColor = depth === 0 ? BLUMA_TERMINAL.listBullet : BLUMA_TERMINAL.listBulletSub;
7582
+ const body = renderListItemBlocks(item, depth, `${keyBase}-it${idx}`);
7583
+ return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "row", alignItems: "flex-start", marginBottom: 0, children: [
7584
+ /* @__PURE__ */ jsx11(Text10, { color: gColor, children: (item.task ? glyph : `${glyph} `).padEnd(item.task ? 4 : 3, " ") }),
7585
+ /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", flexGrow: 1, children: body.length > 0 ? body : /* @__PURE__ */ jsx11(Text10, { dimColor: true, children: item.text }) })
7586
+ ] }, `${keyBase}-row-${idx}`);
7587
+ }) }, keyBase);
7588
+ }
7589
+ function renderBlockquote(q, key) {
7590
+ const inner = q.tokens && q.tokens.length > 0 ? renderBlockTokens(q.tokens, `${key}-inner`) : /* @__PURE__ */ jsx11(Text10, { dimColor: true, italic: true, children: q.text });
7591
+ return /* @__PURE__ */ jsxs10(
7592
+ Box11,
7593
+ {
7594
+ flexDirection: "row",
7595
+ marginBottom: 1,
7596
+ borderStyle: "single",
7597
+ borderColor: BLUMA_TERMINAL.brandMagenta,
7598
+ paddingLeft: 1,
7599
+ paddingY: 0,
7600
+ children: [
7601
+ /* @__PURE__ */ jsx11(Text10, { color: BLUMA_TERMINAL.brandMagenta, children: "\u2502 " }),
7602
+ /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", flexGrow: 1, children: inner })
7603
+ ]
7604
+ },
7605
+ key
7606
+ );
7607
+ }
7608
+ function renderBlockTokens(tokens, keyRoot) {
6991
7609
  const elements = [];
6992
7610
  for (let i = 0; i < tokens.length; i++) {
6993
7611
  const token = tokens[i];
6994
- const key = `token-${i}`;
7612
+ const key = `${keyRoot}-${i}`;
6995
7613
  switch (token.type) {
6996
7614
  case "heading": {
6997
- const heading = token;
6998
- const prefix = "#".repeat(heading.depth);
7615
+ const h = token;
7616
+ const color = headingColor(h.depth);
7617
+ const prefix = h.depth <= 2 ? `${"#".repeat(h.depth)} ` : "";
7618
+ const inner = walkInline(h.tokens, `${key}-h`);
6999
7619
  elements.push(
7000
- /* @__PURE__ */ jsx10(Box10, { marginBottom: 1, children: /* @__PURE__ */ jsxs9(Text9, { color: COLORS.heading, bold: true, children: [
7001
- prefix,
7002
- " ",
7003
- heading.text
7004
- ] }) }, key)
7620
+ /* @__PURE__ */ jsxs10(Box11, { marginBottom: 1, flexDirection: "row", flexWrap: "wrap", alignItems: "flex-start", children: [
7621
+ /* @__PURE__ */ jsx11(Text10, { color, bold: true, children: prefix }),
7622
+ inner.length > 0 ? inner : /* @__PURE__ */ jsx11(Text10, { color, bold: true, children: h.text })
7623
+ ] }, key)
7005
7624
  );
7006
7625
  break;
7007
7626
  }
7008
- case "paragraph": {
7009
- const para = token;
7010
- elements.push(
7011
- /* @__PURE__ */ jsx10(Box10, { marginBottom: 1, children: renderInline(para.text) }, key)
7012
- );
7627
+ case "paragraph":
7628
+ elements.push(renderParagraph(token, key));
7013
7629
  break;
7014
- }
7015
- case "list": {
7016
- const list = token;
7017
- list.items.forEach((item, idx) => {
7018
- const bullet = list.ordered ? `${idx + 1}.` : "\u2022";
7019
- const text = item.tokens?.filter((t) => t.type === "text").map((t) => t.text).join("") || item.text;
7020
- elements.push(
7021
- /* @__PURE__ */ jsxs9(Box10, { paddingLeft: 1, children: [
7022
- /* @__PURE__ */ jsx10(Text9, { color: COLORS.listBullet, children: bullet }),
7023
- /* @__PURE__ */ jsxs9(Text9, { children: [
7024
- " ",
7025
- renderInline(text)
7026
- ] })
7027
- ] }, `${key}-item-${idx}`)
7028
- );
7029
- });
7030
- elements.push(/* @__PURE__ */ jsx10(Box10, { marginBottom: 1 }, `${key}-spacer`));
7630
+ case "list":
7631
+ elements.push(renderListBlock(token, 0, key));
7031
7632
  break;
7032
- }
7033
7633
  case "code": {
7034
7634
  const code = token;
7035
7635
  let highlighted;
@@ -7042,110 +7642,144 @@ function renderTokens(tokens) {
7042
7642
  highlighted = code.text;
7043
7643
  }
7044
7644
  elements.push(
7045
- /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", marginBottom: 1, children: [
7046
- code.lang && /* @__PURE__ */ jsx10(Text9, { dimColor: true, children: code.lang }),
7047
- /* @__PURE__ */ jsx10(Box10, { paddingLeft: 1, children: /* @__PURE__ */ jsx10(Text9, { children: highlighted }) })
7048
- ] }, key)
7645
+ /* @__PURE__ */ jsxs10(
7646
+ Box11,
7647
+ {
7648
+ flexDirection: "column",
7649
+ marginBottom: 1,
7650
+ borderStyle: "single",
7651
+ borderColor: BLUMA_TERMINAL.panelBorder,
7652
+ paddingX: 1,
7653
+ children: [
7654
+ code.lang ? /* @__PURE__ */ jsx11(Text10, { color: BLUMA_TERMINAL.codeLabel, dimColor: true, children: code.lang }) : null,
7655
+ /* @__PURE__ */ jsx11(Text10, { dimColor: true, children: highlighted })
7656
+ ]
7657
+ },
7658
+ key
7659
+ )
7049
7660
  );
7050
7661
  break;
7051
7662
  }
7052
- case "blockquote": {
7053
- const quote = token;
7054
- elements.push(
7055
- /* @__PURE__ */ jsxs9(Box10, { marginBottom: 1, children: [
7056
- /* @__PURE__ */ jsx10(Text9, { color: COLORS.quote, children: "\u2502 " }),
7057
- /* @__PURE__ */ jsx10(Text9, { dimColor: true, italic: true, children: quote.text })
7058
- ] }, key)
7059
- );
7663
+ case "blockquote":
7664
+ elements.push(renderBlockquote(token, key));
7060
7665
  break;
7061
- }
7062
7666
  case "table": {
7063
7667
  const table = token;
7064
7668
  elements.push(
7065
- /* @__PURE__ */ jsx10(Box10, { flexDirection: "row", children: table.header.map((cell, idx) => /* @__PURE__ */ jsx10(Box10, { paddingRight: 2, children: /* @__PURE__ */ jsx10(Text9, { bold: true, children: cell.text }) }, idx)) }, `${key}-header`)
7669
+ /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", marginBottom: 1, children: [
7670
+ /* @__PURE__ */ jsx11(Box11, { flexDirection: "row", children: table.header.map((cell, idx) => {
7671
+ const headerNodes = walkInline(cell.tokens, `${key}-h${idx}`);
7672
+ const styled = headerNodes.length > 0 ? styleInlineNodes(headerNodes, `${key}-h${idx}`, { bold: true, color: BLUMA_TERMINAL.brandBlue }) : [/* @__PURE__ */ jsx11(Text10, { bold: true, color: BLUMA_TERMINAL.brandBlue, children: cell.text }, `${key}-h${idx}-t`)];
7673
+ return /* @__PURE__ */ jsx11(Box11, { paddingRight: 2, flexDirection: "row", flexWrap: "wrap", children: styled }, idx);
7674
+ }) }),
7675
+ table.rows.map((row, rowIdx) => /* @__PURE__ */ jsx11(Box11, { flexDirection: "row", children: row.map((cell, cellIdx) => {
7676
+ const cellNodes = walkInline(cell.tokens, `${key}-c${rowIdx}-${cellIdx}`);
7677
+ const styled = cellNodes.length > 0 ? styleInlineNodes(cellNodes, `${key}-c${rowIdx}-${cellIdx}`, { dimColor: true }) : [/* @__PURE__ */ jsx11(Text10, { dimColor: true, children: cell.text }, `${key}-c${rowIdx}-${cellIdx}-t`)];
7678
+ return /* @__PURE__ */ jsx11(Box11, { paddingRight: 2, flexDirection: "row", flexWrap: "wrap", children: styled }, cellIdx);
7679
+ }) }, `${key}-r${rowIdx}`))
7680
+ ] }, key)
7066
7681
  );
7067
- table.rows.forEach((row, rowIdx) => {
7068
- elements.push(
7069
- /* @__PURE__ */ jsx10(Box10, { flexDirection: "row", children: row.map((cell, cellIdx) => /* @__PURE__ */ jsx10(Box10, { paddingRight: 2, children: /* @__PURE__ */ jsx10(Text9, { children: cell.text }) }, cellIdx)) }, `${key}-row-${rowIdx}`)
7070
- );
7071
- });
7072
- elements.push(/* @__PURE__ */ jsx10(Box10, { marginBottom: 1 }, `${key}-spacer`));
7073
7682
  break;
7074
7683
  }
7075
- case "hr": {
7684
+ case "hr":
7076
7685
  elements.push(
7077
- /* @__PURE__ */ jsx10(Box10, { marginBottom: 1, children: /* @__PURE__ */ jsx10(Text9, { dimColor: true, children: "\u2500".repeat(40) }) }, key)
7686
+ /* @__PURE__ */ jsx11(Box11, { marginY: 1, children: /* @__PURE__ */ jsx11(Text10, { dimColor: true, children: "\u2500".repeat(42) }) }, key)
7078
7687
  );
7079
7688
  break;
7080
- }
7081
- case "space": {
7689
+ case "space":
7082
7690
  break;
7083
- }
7084
- default: {
7691
+ default:
7085
7692
  if ("text" in token && typeof token.text === "string") {
7086
- elements.push(/* @__PURE__ */ jsx10(Text9, { children: token.text }, key));
7693
+ elements.push(
7694
+ /* @__PURE__ */ jsx11(Text10, { dimColor: true, children: token.text }, key)
7695
+ );
7087
7696
  }
7088
- }
7089
7697
  }
7090
7698
  }
7091
7699
  return elements;
7092
7700
  }
7093
- function renderInline(text) {
7094
- const parts = [];
7095
- let remaining = text;
7096
- let partIndex = 0;
7097
- const inlineRegex = /(\*\*(.+?)\*\*|\*(.+?)\*|`(.+?)`|\[(.+?)\]\((.+?)\))/;
7098
- while (remaining.length > 0) {
7099
- const match = remaining.match(inlineRegex);
7100
- if (!match) {
7101
- parts.push(/* @__PURE__ */ jsx10(Text9, { children: remaining }, `inline-${partIndex++}`));
7102
- break;
7103
- }
7104
- const matchIndex = match.index ?? 0;
7105
- if (matchIndex > 0) {
7106
- parts.push(
7107
- /* @__PURE__ */ jsx10(Text9, { children: remaining.slice(0, matchIndex) }, `inline-${partIndex++}`)
7108
- );
7109
- }
7110
- const fullMatch = match[0];
7111
- if (fullMatch.startsWith("**")) {
7112
- parts.push(
7113
- /* @__PURE__ */ jsx10(Text9, { bold: true, children: match[2] }, `inline-${partIndex++}`)
7114
- );
7115
- } else if (fullMatch.startsWith("*")) {
7116
- parts.push(
7117
- /* @__PURE__ */ jsx10(Text9, { italic: true, children: match[3] }, `inline-${partIndex++}`)
7118
- );
7119
- } else if (fullMatch.startsWith("`")) {
7120
- parts.push(
7121
- /* @__PURE__ */ jsxs9(Text9, { color: COLORS.code, children: [
7122
- "`",
7123
- match[4],
7124
- "`"
7125
- ] }, `inline-${partIndex++}`)
7126
- );
7127
- } else if (fullMatch.startsWith("[")) {
7128
- parts.push(
7129
- /* @__PURE__ */ jsx10(Text9, { color: COLORS.link, underline: true, children: match[5] }, `inline-${partIndex++}`)
7130
- );
7131
- }
7132
- remaining = remaining.slice(matchIndex + fullMatch.length);
7133
- }
7134
- return parts.length === 1 ? parts[0] : /* @__PURE__ */ jsx10(Fragment3, { children: parts });
7135
- }
7136
7701
  var MarkdownRendererComponent = ({ markdown }) => {
7137
7702
  if (!markdown || markdown.trim() === "") {
7138
7703
  return null;
7139
7704
  }
7140
7705
  const tokens = marked.lexer(markdown);
7141
- return /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", children: renderTokens(tokens) });
7706
+ return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", paddingRight: 1, children: renderBlockTokens(tokens, "md") });
7142
7707
  };
7143
7708
  var MarkdownRenderer = memo7(MarkdownRendererComponent);
7144
7709
 
7710
+ // src/app/ui/utils/expandablePreviewStore.ts
7711
+ var latest = null;
7712
+ function parseResult(result) {
7713
+ try {
7714
+ return JSON.parse(result);
7715
+ } catch {
7716
+ return null;
7717
+ }
7718
+ }
7719
+ function truncatedLineCount(text, maxVisible) {
7720
+ const allLines = text.split("\n").filter((l) => l.trim());
7721
+ if (allLines.length <= maxVisible) {
7722
+ return { truncated: 0, totalKept: allLines.length };
7723
+ }
7724
+ return { truncated: allLines.length - maxVisible, totalKept: maxVisible };
7725
+ }
7726
+ function refreshExpandableFromToolResult(toolName, result) {
7727
+ if (toolName.includes("task_boundary")) {
7728
+ return;
7729
+ }
7730
+ const parsed = parseResult(result);
7731
+ if (toolName.includes("read_file") && parsed && typeof parsed.content === "string") {
7732
+ const { truncated } = truncatedLineCount(parsed.content, TOOL_PREVIEW_MAX_LINES);
7733
+ if (truncated > 0) {
7734
+ const fp = typeof parsed.filepath === "string" ? parsed.filepath : "file";
7735
+ const sl = parsed.start_line;
7736
+ const el = parsed.end_line;
7737
+ const range = typeof sl === "number" && typeof el === "number" ? ` :${sl}-${el}` : "";
7738
+ latest = {
7739
+ fullText: parsed.content,
7740
+ title: `${fp}${range}`,
7741
+ toolName,
7742
+ linesHidden: truncated
7743
+ };
7744
+ } else {
7745
+ latest = null;
7746
+ }
7747
+ return;
7748
+ }
7749
+ if ((toolName.includes("shell_command") || toolName.includes("run_command") || toolName.includes("command_status")) && parsed) {
7750
+ const output = String(parsed.stdout || parsed.output || "");
7751
+ const stderr = String(parsed.stderr || "");
7752
+ const status = String(parsed.status || "");
7753
+ if (parsed.command_id && !output && !stderr && !status) {
7754
+ return;
7755
+ }
7756
+ if (status === "running") {
7757
+ return;
7758
+ }
7759
+ const blob = output || stderr;
7760
+ if (!blob) {
7761
+ return;
7762
+ }
7763
+ const { truncated } = truncatedLineCount(blob, TOOL_PREVIEW_MAX_LINES);
7764
+ if (truncated > 0) {
7765
+ latest = {
7766
+ fullText: blob,
7767
+ title: "shell output",
7768
+ toolName,
7769
+ linesHidden: truncated
7770
+ };
7771
+ } else {
7772
+ latest = null;
7773
+ }
7774
+ }
7775
+ }
7776
+ function peekLatestExpandable() {
7777
+ return latest;
7778
+ }
7779
+
7145
7780
  // src/app/ui/components/ToolResultDisplay.tsx
7146
- import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
7147
- var MAX_LINES = 8;
7148
- var parseResult = (result) => {
7781
+ import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
7782
+ var parseResult2 = (result) => {
7149
7783
  try {
7150
7784
  return JSON.parse(result);
7151
7785
  } catch {
@@ -7162,153 +7796,222 @@ var truncateLines = (text, max) => {
7162
7796
  truncated: allLines.length - max
7163
7797
  };
7164
7798
  };
7799
+ function TodoTaskLine({
7800
+ done,
7801
+ label
7802
+ }) {
7803
+ return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "row", children: [
7804
+ /* @__PURE__ */ jsx12(Text11, { color: done ? BLUMA_TERMINAL.brandBlue : BLUMA_TERMINAL.brandMagenta, children: done ? "\u25A0 " : "\u25A1 " }),
7805
+ /* @__PURE__ */ jsx12(Text11, { dimColor: done, strikethrough: done, color: done ? BLUMA_TERMINAL.muted : void 0, children: label })
7806
+ ] });
7807
+ }
7165
7808
  var ToolResultDisplayComponent = ({ toolName, result }) => {
7809
+ useEffect5(() => {
7810
+ refreshExpandableFromToolResult(toolName, result);
7811
+ }, [toolName, result]);
7166
7812
  if (toolName.includes("task_boundary")) {
7167
7813
  return null;
7168
7814
  }
7169
- const parsed = parseResult(result);
7815
+ const parsed = parseResult2(result);
7170
7816
  if (toolName.includes("message")) {
7171
- const body = parsed?.content?.body || parsed?.body || parsed?.message;
7817
+ const body = parsed?.content?.body ?? parsed?.body ?? parsed?.message;
7172
7818
  if (!body) return null;
7173
- return /* @__PURE__ */ jsx11(Box11, { paddingX: 1, marginBottom: 1, flexDirection: "column", children: /* @__PURE__ */ jsx11(MarkdownRenderer, { markdown: body }) });
7174
- }
7175
- if (toolName.includes("read_file") && parsed?.content) {
7176
- const { lines, truncated } = truncateLines(parsed.content, MAX_LINES);
7177
- return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", paddingLeft: 3, children: [
7178
- lines.map((line, i) => /* @__PURE__ */ jsx11(Text10, { dimColor: true, color: "gray", children: line.slice(0, 80) }, i)),
7179
- truncated > 0 && /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
7180
- "... +",
7819
+ return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsx12(Box12, { paddingLeft: 2, flexDirection: "column", children: /* @__PURE__ */ jsx12(MarkdownRenderer, { markdown: String(body) }) }) });
7820
+ }
7821
+ if (toolName.includes("read_file") && parsed && typeof parsed.content === "string") {
7822
+ const { lines, truncated } = truncateLines(parsed.content, TOOL_PREVIEW_MAX_LINES);
7823
+ return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", paddingLeft: 2, children: [
7824
+ lines.map((line, i) => /* @__PURE__ */ jsx12(Text11, { dimColor: true, children: line.slice(0, 120) }, i)),
7825
+ truncated > 0 ? /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
7826
+ "\u2026 +",
7181
7827
  truncated,
7182
- " lines"
7183
- ] })
7184
- ] });
7828
+ " lines \xB7 Ctrl+O expand"
7829
+ ] }) : null
7830
+ ] }) });
7185
7831
  }
7186
7832
  if ((toolName.includes("shell_command") || toolName.includes("run_command") || toolName.includes("command_status")) && parsed) {
7187
- const output = parsed.stdout || parsed.output || "";
7188
- const stderr = parsed.stderr || "";
7833
+ const output = String(parsed.stdout || parsed.output || "");
7834
+ const stderr = String(parsed.stderr || "");
7189
7835
  const exitCode = parsed.exit_code ?? parsed.exitCode ?? 0;
7190
- const status = parsed.status || "";
7836
+ const status = String(parsed.status || "");
7191
7837
  if (parsed.command_id && !output && !stderr && !status) {
7192
- return /* @__PURE__ */ jsx11(Box11, { paddingLeft: 3, children: /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
7838
+ return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsx12(Box12, { paddingLeft: 2, children: /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
7193
7839
  "started #",
7194
- parsed.command_id.slice(0, 8)
7195
- ] }) });
7840
+ String(parsed.command_id).slice(0, 8)
7841
+ ] }) }) });
7196
7842
  }
7197
7843
  if (status === "running") {
7198
- return /* @__PURE__ */ jsx11(Box11, { paddingLeft: 3, children: /* @__PURE__ */ jsx11(Text10, { dimColor: true, color: "yellow", children: "still running..." }) });
7844
+ return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsx12(Box12, { paddingLeft: 2, children: /* @__PURE__ */ jsx12(Text11, { color: BLUMA_TERMINAL.warn, dimColor: true, children: "still running\u2026" }) }) });
7199
7845
  }
7200
7846
  if (!output && !stderr) return null;
7201
- const { lines, truncated } = truncateLines(output || stderr, MAX_LINES);
7847
+ const { lines, truncated } = truncateLines(output || stderr, TOOL_PREVIEW_MAX_LINES);
7202
7848
  const isError = exitCode !== 0;
7203
7849
  const isSuccess = exitCode === 0 && status !== "running";
7204
- const textColor = isError ? "red" : isSuccess ? "green" : "gray";
7205
- return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", paddingLeft: 3, children: [
7206
- lines.map((line, i) => /* @__PURE__ */ jsx11(Text10, { dimColor: true, color: textColor, children: line.slice(0, 80) }, i)),
7207
- truncated > 0 && /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
7208
- "... +",
7850
+ const lineColor = isError ? BLUMA_TERMINAL.err : isSuccess ? BLUMA_TERMINAL.success : BLUMA_TERMINAL.muted;
7851
+ return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", paddingLeft: 2, children: [
7852
+ lines.map((line, i) => /* @__PURE__ */ jsx12(Text11, { dimColor: true, color: lineColor, children: line.slice(0, 120) }, i)),
7853
+ truncated > 0 ? /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
7854
+ "\u2026 +",
7209
7855
  truncated,
7210
- " lines"
7211
- ] }),
7212
- exitCode !== 0 && /* @__PURE__ */ jsxs10(Text10, { dimColor: true, color: "red", children: [
7213
- "exit code: ",
7856
+ " lines \xB7 Ctrl+O expand"
7857
+ ] }) : null,
7858
+ exitCode !== 0 ? /* @__PURE__ */ jsxs11(Text11, { dimColor: true, color: BLUMA_TERMINAL.err, children: [
7859
+ "exit ",
7214
7860
  exitCode
7215
- ] })
7216
- ] });
7861
+ ] }) : null
7862
+ ] }) });
7217
7863
  }
7218
7864
  if ((toolName.includes("grep") || toolName.includes("find_by_name")) && parsed) {
7219
7865
  const matches = parsed.matches || parsed.results || parsed.files || [];
7220
7866
  if (matches.length === 0) {
7221
- return /* @__PURE__ */ jsx11(Box11, { paddingLeft: 3, children: /* @__PURE__ */ jsx11(Text10, { dimColor: true, children: "no matches" }) });
7867
+ return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsx12(Box12, { paddingLeft: 2, children: /* @__PURE__ */ jsx12(Text11, { dimColor: true, children: "no matches" }) }) });
7222
7868
  }
7223
- return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", paddingLeft: 3, children: [
7224
- /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
7869
+ return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", paddingLeft: 2, children: [
7870
+ /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
7225
7871
  matches.length,
7226
7872
  " matches"
7227
7873
  ] }),
7228
- matches.slice(0, 5).map((m, i) => /* @__PURE__ */ jsxs10(Text10, { dimColor: true, color: "gray", children: [
7229
- m.file || m.path || m.name || m,
7230
- m.line ? `:${m.line}` : ""
7231
- ] }, i)),
7232
- matches.length > 5 && /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
7233
- "... +",
7234
- matches.length - 5,
7235
- " more"
7236
- ] })
7237
- ] });
7874
+ matches.slice(0, 5).map((m, i) => {
7875
+ const row = m;
7876
+ const path19 = row.file || row.path || row.name || m;
7877
+ const line = row.line;
7878
+ return /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
7879
+ String(path19),
7880
+ line != null ? `:${line}` : ""
7881
+ ] }, i);
7882
+ }),
7883
+ matches.length > 5 ? /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
7884
+ "\u2026 +",
7885
+ matches.length - 5
7886
+ ] }) : null
7887
+ ] }) });
7238
7888
  }
7239
7889
  if (toolName.includes("ls_tool") && parsed) {
7240
7890
  const entries = parsed.entries || parsed.files || [];
7241
7891
  if (entries.length === 0) return null;
7242
- return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", paddingLeft: 3, children: [
7243
- entries.slice(0, 6).map((e, i) => /* @__PURE__ */ jsxs10(Text10, { dimColor: true, color: e.isDirectory ? "blue" : "gray", children: [
7244
- e.isDirectory ? "d" : "f",
7245
- " ",
7246
- e.name || e
7247
- ] }, i)),
7248
- entries.length > 6 && /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
7249
- "... +",
7250
- entries.length - 6,
7251
- " more"
7252
- ] })
7253
- ] });
7892
+ return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", paddingLeft: 2, children: [
7893
+ entries.slice(0, 6).map((e, i) => {
7894
+ const row = e;
7895
+ const name = row.name ?? e;
7896
+ const isDir = Boolean(row.isDirectory);
7897
+ return /* @__PURE__ */ jsxs11(Text11, { dimColor: true, color: isDir ? BLUMA_TERMINAL.brandBlue : BLUMA_TERMINAL.muted, children: [
7898
+ isDir ? "d " : "f ",
7899
+ String(name)
7900
+ ] }, i);
7901
+ }),
7902
+ entries.length > 6 ? /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
7903
+ "\u2026 +",
7904
+ entries.length - 6
7905
+ ] }) : null
7906
+ ] }) });
7254
7907
  }
7255
7908
  if (toolName.includes("load_skill") && parsed) {
7256
7909
  if (!parsed.success) {
7257
- return /* @__PURE__ */ jsx11(Box11, { paddingLeft: 3, children: /* @__PURE__ */ jsxs10(Text10, { dimColor: true, color: "red", children: [
7258
- "\u2717 Skill not found: ",
7259
- parsed.message
7260
- ] }) });
7261
- }
7262
- return /* @__PURE__ */ jsxs10(Box11, { paddingLeft: 3, marginBottom: 2, children: [
7263
- /* @__PURE__ */ jsx11(Text10, { dimColor: true, color: "green", children: "Skill loaded: " }),
7264
- /* @__PURE__ */ jsx11(Text10, { color: "green", bold: true, children: parsed.skill_name }),
7265
- /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
7266
- " \u2014 ",
7267
- parsed.description?.slice(0, 50),
7268
- parsed.description?.length > 100 ? "..." : ""
7910
+ return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsx12(Box12, { paddingLeft: 2, children: /* @__PURE__ */ jsxs11(Text11, { dimColor: true, color: BLUMA_TERMINAL.err, children: [
7911
+ "Not found: ",
7912
+ String(parsed.message || "")
7913
+ ] }) }) });
7914
+ }
7915
+ const desc = parsed.description ? String(parsed.description) : "";
7916
+ return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs11(Box12, { paddingLeft: 2, flexDirection: "row", flexWrap: "wrap", children: [
7917
+ /* @__PURE__ */ jsx12(Text11, { color: BLUMA_TERMINAL.brandMagenta, bold: true, children: String(parsed.skill_name || "") }),
7918
+ /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
7919
+ " ",
7920
+ "\u2014 ",
7921
+ desc.slice(0, 80),
7922
+ desc.length > 80 ? "\u2026" : ""
7269
7923
  ] })
7270
- ] });
7924
+ ] }) });
7271
7925
  }
7272
7926
  if (toolName.includes("todo")) {
7273
- return null;
7927
+ if (parsed && Array.isArray(parsed.tasks)) {
7928
+ const tasks = parsed.tasks;
7929
+ const stats = parsed.stats;
7930
+ const msg = typeof parsed.message === "string" ? parsed.message : "";
7931
+ return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", paddingLeft: 2, children: [
7932
+ msg ? /* @__PURE__ */ jsx12(Box12, { marginBottom: 1, children: /* @__PURE__ */ jsx12(Text11, { dimColor: true, children: msg }) }) : null,
7933
+ stats && typeof stats.progress === "number" ? /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
7934
+ stats.completed ?? 0,
7935
+ "/",
7936
+ stats.total ?? tasks.length,
7937
+ " done \xB7 ",
7938
+ stats.progress,
7939
+ "%"
7940
+ ] }) : null,
7941
+ /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", marginTop: 1, children: [
7942
+ tasks.slice(0, 12).map((t, i) => {
7943
+ const done = t.status === "completed" || t.isComplete === true;
7944
+ const line = `#${t.id ?? i + 1} ${t.description ?? ""}`;
7945
+ return /* @__PURE__ */ jsx12(TodoTaskLine, { done, label: line }, i);
7946
+ }),
7947
+ tasks.length > 12 ? /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
7948
+ "\u2026 +",
7949
+ tasks.length - 12,
7950
+ " tasks"
7951
+ ] }) : null
7952
+ ] })
7953
+ ] }) });
7954
+ }
7955
+ const lines = result.split("\n").filter(Boolean);
7956
+ if (lines.length === 0) return null;
7957
+ return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsx12(Box12, { flexDirection: "column", paddingLeft: 2, children: lines.slice(0, 14).map((line, i) => /* @__PURE__ */ jsx12(Text11, { dimColor: true, children: line.slice(0, 100) }, i)) }) });
7274
7958
  }
7275
7959
  if (toolName.includes("view_file_outline") && parsed) {
7276
7960
  const symbols = parsed.symbols || parsed.outline || [];
7277
7961
  if (symbols.length === 0) return null;
7278
- return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", paddingLeft: 3, children: [
7279
- symbols.slice(0, 5).map((s, i) => /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
7280
- s.kind || "sym",
7281
- " ",
7282
- s.name
7283
- ] }, i)),
7284
- symbols.length > 5 && /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
7285
- "... +",
7286
- symbols.length - 5,
7287
- " more"
7288
- ] })
7289
- ] });
7962
+ return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", paddingLeft: 2, children: [
7963
+ symbols.slice(0, 5).map((s, i) => {
7964
+ const row = s;
7965
+ return /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
7966
+ String(row.kind || "sym"),
7967
+ " ",
7968
+ String(row.name || "")
7969
+ ] }, i);
7970
+ }),
7971
+ symbols.length > 5 ? /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
7972
+ "\u2026 +",
7973
+ symbols.length - 5
7974
+ ] }) : null
7975
+ ] }) });
7290
7976
  }
7291
7977
  return null;
7292
7978
  };
7293
7979
  var ToolResultDisplay = memo8(ToolResultDisplayComponent);
7294
7980
 
7981
+ // src/app/ui/SessionInfoConnectingMCP.tsx
7982
+ import { Box as Box13, Text as Text12 } from "ink";
7983
+ import Spinner from "ink-spinner";
7984
+ import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
7985
+ var SessionInfoConnectingMCP = ({
7986
+ sessionId,
7987
+ workdir,
7988
+ statusMessage
7989
+ }) => /* @__PURE__ */ jsx13(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", paddingLeft: 1, children: [
7990
+ /* @__PURE__ */ jsxs12(Text12, { children: [
7991
+ /* @__PURE__ */ jsx13(Text12, { bold: true, color: BLUMA_TERMINAL.brandBlue, children: "session" }),
7992
+ /* @__PURE__ */ jsx13(Text12, { dimColor: true, children: " \xB7 " }),
7993
+ /* @__PURE__ */ jsx13(Text12, { color: BLUMA_TERMINAL.brandMagenta, children: sessionId.slice(0, 12) })
7994
+ ] }),
7995
+ /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
7996
+ /* @__PURE__ */ jsx13(Text12, { color: BLUMA_TERMINAL.brandMagenta, children: "\u2514 " }),
7997
+ workdir
7998
+ ] }),
7999
+ /* @__PURE__ */ jsxs12(Box13, { marginTop: 1, flexDirection: "row", flexWrap: "wrap", children: [
8000
+ /* @__PURE__ */ jsxs12(Text12, { color: BLUMA_TERMINAL.warn, children: [
8001
+ /* @__PURE__ */ jsx13(Spinner, { type: "dots" }),
8002
+ " "
8003
+ ] }),
8004
+ /* @__PURE__ */ jsx13(Text12, { dimColor: true, children: statusMessage || "Establishing MCP\u2026" })
8005
+ ] })
8006
+ ] }) });
8007
+ var SessionInfoConnectingMCP_default = SessionInfoConnectingMCP;
8008
+
7295
8009
  // src/app/ui/components/SlashCommands.tsx
7296
- import { Box as Box12, Text as Text11 } from "ink";
7297
- import { Fragment as Fragment4, jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
8010
+ import { Box as Box14, Text as Text13 } from "ink";
8011
+ import { Fragment as Fragment2, jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
7298
8012
  var SlashCommands = ({ input, setHistory, agentRef }) => {
7299
8013
  const [cmd, ...args] = input.slice(1).trim().split(/\s+/);
7300
- const outBox = (children) => /* @__PURE__ */ jsx12(
7301
- Box12,
7302
- {
7303
- borderStyle: "single",
7304
- borderColor: "gray",
7305
- paddingX: 2,
7306
- paddingY: 0,
7307
- marginBottom: 1,
7308
- flexDirection: "column",
7309
- children
7310
- }
7311
- );
8014
+ const outBox = (children) => /* @__PURE__ */ jsx14(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsx14(Box14, { paddingLeft: 1, flexDirection: "column", children }) });
7312
8015
  const render2 = () => {
7313
8016
  if (!cmd) {
7314
8017
  return null;
@@ -7316,11 +8019,11 @@ var SlashCommands = ({ input, setHistory, agentRef }) => {
7316
8019
  if (cmd === "help") {
7317
8020
  const cmds = getSlashCommands();
7318
8021
  return outBox(
7319
- /* @__PURE__ */ jsxs11(Fragment4, { children: [
7320
- /* @__PURE__ */ jsx12(Box12, { marginBottom: 1, children: /* @__PURE__ */ jsx12(Text11, { bold: true, color: "magenta", children: "Available Commands" }) }),
7321
- /* @__PURE__ */ jsx12(Box12, { flexDirection: "column", children: cmds.map((c, i) => /* @__PURE__ */ jsxs11(Box12, { children: [
7322
- /* @__PURE__ */ jsx12(Text11, { color: "cyan", children: c.name.padEnd(12) }),
7323
- /* @__PURE__ */ jsx12(Text11, { dimColor: true, children: c.description })
8022
+ /* @__PURE__ */ jsxs13(Fragment2, { children: [
8023
+ /* @__PURE__ */ jsx14(Box14, { marginBottom: 1, children: /* @__PURE__ */ jsx14(Text13, { bold: true, color: BLUMA_TERMINAL.brandMagenta, children: "Available Commands" }) }),
8024
+ /* @__PURE__ */ jsx14(Box14, { flexDirection: "column", children: cmds.map((c, i) => /* @__PURE__ */ jsxs13(Box14, { children: [
8025
+ /* @__PURE__ */ jsx14(Text13, { color: BLUMA_TERMINAL.brandBlue, children: c.name.padEnd(12) }),
8026
+ /* @__PURE__ */ jsx14(Text13, { dimColor: true, children: c.description })
7324
8027
  ] }, i)) })
7325
8028
  ] })
7326
8029
  );
@@ -7328,9 +8031,9 @@ var SlashCommands = ({ input, setHistory, agentRef }) => {
7328
8031
  if (cmd === "clear") {
7329
8032
  setHistory((prev) => prev.filter((item) => item.id === 0 || item.id === 1));
7330
8033
  return outBox(
7331
- /* @__PURE__ */ jsxs11(Box12, { children: [
7332
- /* @__PURE__ */ jsx12(Text11, { color: "green", children: "\u2713" }),
7333
- /* @__PURE__ */ jsx12(Text11, { dimColor: true, children: " History cleared" })
8034
+ /* @__PURE__ */ jsxs13(Box14, { children: [
8035
+ /* @__PURE__ */ jsx14(Text13, { color: "green", children: "[ok]" }),
8036
+ /* @__PURE__ */ jsx14(Text13, { dimColor: true, children: " History cleared" })
7334
8037
  ] })
7335
8038
  );
7336
8039
  }
@@ -7342,8 +8045,8 @@ var SlashCommands = ({ input, setHistory, agentRef }) => {
7342
8045
  setHistory((prev) => prev.concat({
7343
8046
  id: Date.now(),
7344
8047
  component: outBox(
7345
- /* @__PURE__ */ jsx12(Box12, { children: /* @__PURE__ */ jsxs11(Text11, { color: "red", children: [
7346
- "\u2716 Failed to execute /init: ",
8048
+ /* @__PURE__ */ jsx14(Box14, { children: /* @__PURE__ */ jsxs13(Text13, { color: "red", children: [
8049
+ "Failed to execute /init: ",
7347
8050
  e?.message || String(e)
7348
8051
  ] }) })
7349
8052
  )
@@ -7363,41 +8066,41 @@ var SlashCommands = ({ input, setHistory, agentRef }) => {
7363
8066
  const colType = 10;
7364
8067
  const colSource = 18;
7365
8068
  return outBox(
7366
- /* @__PURE__ */ jsxs11(Fragment4, { children: [
7367
- /* @__PURE__ */ jsxs11(Box12, { marginBottom: 1, children: [
7368
- /* @__PURE__ */ jsx12(Text11, { bold: true, color: "magenta", children: "MCP Tools" }),
7369
- /* @__PURE__ */ jsx12(Text11, { dimColor: true, children: " \u2022 " }),
7370
- /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
8069
+ /* @__PURE__ */ jsxs13(Fragment2, { children: [
8070
+ /* @__PURE__ */ jsxs13(Box14, { marginBottom: 1, children: [
8071
+ /* @__PURE__ */ jsx14(Text13, { bold: true, color: BLUMA_TERMINAL.brandMagenta, children: "MCP Tools" }),
8072
+ /* @__PURE__ */ jsx14(Text13, { dimColor: true, children: " \u2022 " }),
8073
+ /* @__PURE__ */ jsxs13(Text13, { dimColor: true, children: [
7371
8074
  tools.length,
7372
8075
  " total"
7373
8076
  ] }),
7374
- term && /* @__PURE__ */ jsxs11(Fragment4, { children: [
7375
- /* @__PURE__ */ jsx12(Text11, { dimColor: true, children: " \u2022 filter: " }),
7376
- /* @__PURE__ */ jsxs11(Text11, { color: "cyan", children: [
8077
+ term && /* @__PURE__ */ jsxs13(Fragment2, { children: [
8078
+ /* @__PURE__ */ jsx14(Text13, { dimColor: true, children: " \u2022 filter: " }),
8079
+ /* @__PURE__ */ jsxs13(Text13, { color: BLUMA_TERMINAL.brandBlue, children: [
7377
8080
  '"',
7378
8081
  term,
7379
8082
  '"'
7380
8083
  ] }),
7381
- /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
8084
+ /* @__PURE__ */ jsxs13(Text13, { dimColor: true, children: [
7382
8085
  " \u2022 showing: ",
7383
8086
  filtered.length
7384
8087
  ] })
7385
8088
  ] })
7386
8089
  ] }),
7387
- filtered.length === 0 ? /* @__PURE__ */ jsx12(Text11, { color: "yellow", children: "No MCP tools found" }) : /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", children: [
7388
- /* @__PURE__ */ jsx12(Box12, { children: /* @__PURE__ */ jsxs11(Text11, { color: "gray", children: [
8090
+ filtered.length === 0 ? /* @__PURE__ */ jsx14(Text13, { color: "yellow", children: "No MCP tools found" }) : /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", children: [
8091
+ /* @__PURE__ */ jsx14(Box14, { children: /* @__PURE__ */ jsxs13(Text13, { color: "gray", children: [
7389
8092
  pad("Name", colName),
7390
8093
  " \u2502 ",
7391
8094
  pad("Type", colType),
7392
8095
  " \u2502 ",
7393
8096
  pad("Source", colSource)
7394
8097
  ] }) }),
7395
- /* @__PURE__ */ jsx12(Text11, { color: "gray", children: "\u2500".repeat(colName + colType + colSource + 6) }),
8098
+ /* @__PURE__ */ jsx14(Text13, { color: "gray", children: "\u2500".repeat(colName + colType + colSource + 6) }),
7396
8099
  filtered.map((t, i) => {
7397
8100
  const name = t.function?.name || t.name || "tool";
7398
8101
  const type = t.function?.name ? "fn" : t.type || "tool";
7399
8102
  const source = t.source || t.provider || "mcp";
7400
- return /* @__PURE__ */ jsxs11(Text11, { color: "white", children: [
8103
+ return /* @__PURE__ */ jsxs13(Text13, { color: "white", children: [
7401
8104
  pad(name, colName),
7402
8105
  " \u2502 ",
7403
8106
  pad(String(type), colType),
@@ -7420,22 +8123,22 @@ var SlashCommands = ({ input, setHistory, agentRef }) => {
7420
8123
  const colType = 10;
7421
8124
  const colSource = 18;
7422
8125
  return outBox(
7423
- /* @__PURE__ */ jsxs11(Fragment4, { children: [
7424
- /* @__PURE__ */ jsx12(Text11, { color: "magenta", bold: true, children: "Native Tools" }),
7425
- /* @__PURE__ */ jsxs11(Text11, { color: "gray", children: [
8126
+ /* @__PURE__ */ jsxs13(Fragment2, { children: [
8127
+ /* @__PURE__ */ jsx14(Text13, { color: BLUMA_TERMINAL.brandMagenta, bold: true, children: "Native Tools" }),
8128
+ /* @__PURE__ */ jsxs13(Text13, { color: "gray", children: [
7426
8129
  "Total Native: ",
7427
8130
  tools.length,
7428
8131
  term ? ` | Filter: "${term}" | Showing: ${filtered.length}` : ""
7429
8132
  ] }),
7430
- filtered.length === 0 ? /* @__PURE__ */ jsx12(Text11, { color: "yellow", children: "No native tools to display." }) : /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", children: [
7431
- /* @__PURE__ */ jsxs11(Text11, { color: "gray", children: [
8133
+ filtered.length === 0 ? /* @__PURE__ */ jsx14(Text13, { color: "yellow", children: "No native tools to display." }) : /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", children: [
8134
+ /* @__PURE__ */ jsxs13(Text13, { color: "gray", children: [
7432
8135
  pad("Name", colName),
7433
8136
  " | ",
7434
8137
  pad("Type", colType),
7435
8138
  " | ",
7436
8139
  pad("Source", colSource)
7437
8140
  ] }),
7438
- /* @__PURE__ */ jsxs11(Text11, { color: "gray", children: [
8141
+ /* @__PURE__ */ jsxs13(Text13, { color: "gray", children: [
7439
8142
  "".padEnd(colName, "-"),
7440
8143
  "---",
7441
8144
  "".padEnd(colType, "-"),
@@ -7446,7 +8149,7 @@ var SlashCommands = ({ input, setHistory, agentRef }) => {
7446
8149
  const name = t.function?.name || t.name || "tool";
7447
8150
  const type = t.function?.name ? "fn" : t.type || "tool";
7448
8151
  const source = t.source || "native";
7449
- return /* @__PURE__ */ jsxs11(Text11, { color: "white", children: [
8152
+ return /* @__PURE__ */ jsxs13(Text13, { color: "white", children: [
7450
8153
  pad(name, colName),
7451
8154
  " | ",
7452
8155
  pad(String(type), colType),
@@ -7458,18 +8161,18 @@ var SlashCommands = ({ input, setHistory, agentRef }) => {
7458
8161
  ] })
7459
8162
  );
7460
8163
  }
7461
- return outBox(/* @__PURE__ */ jsxs11(Text11, { color: "red", children: [
8164
+ return outBox(/* @__PURE__ */ jsxs13(Text13, { color: "red", children: [
7462
8165
  "Command not recognized: /",
7463
8166
  cmd
7464
8167
  ] }));
7465
8168
  };
7466
- return /* @__PURE__ */ jsx12(Fragment4, { children: render2() });
8169
+ return /* @__PURE__ */ jsx14(Fragment2, { children: render2() });
7467
8170
  };
7468
8171
  var SlashCommands_default = SlashCommands;
7469
8172
 
7470
8173
  // src/app/agent/utils/update_check.ts
7471
8174
  import updateNotifier from "update-notifier";
7472
- import { fileURLToPath as fileURLToPath4 } from "url";
8175
+ import { fileURLToPath as fileURLToPath3 } from "url";
7473
8176
  import path18 from "path";
7474
8177
  import fs14 from "fs";
7475
8178
  var BLUMA_PACKAGE_NAME = "@nomad-e/bluma-cli";
@@ -7504,7 +8207,7 @@ async function checkForUpdates() {
7504
8207
  pkg = findBlumaPackageJson(path18.dirname(binPath));
7505
8208
  }
7506
8209
  if (!pkg) {
7507
- const __filename = fileURLToPath4(import.meta.url);
8210
+ const __filename = fileURLToPath3(import.meta.url);
7508
8211
  const __dirname2 = path18.dirname(__filename);
7509
8212
  pkg = findBlumaPackageJson(__dirname2);
7510
8213
  }
@@ -7533,86 +8236,88 @@ Run: npm i -g ${BLUMA_PACKAGE_NAME} to update.`;
7533
8236
  }
7534
8237
 
7535
8238
  // src/app/ui/components/UpdateNotice.tsx
7536
- import { Box as Box13, Text as Text12 } from "ink";
7537
- import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
8239
+ import { Box as Box15, Text as Text14 } from "ink";
8240
+ import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
7538
8241
  function parseUpdateMessage(msg) {
7539
8242
  const lines = msg.split(/\r?\n/).map((l) => l.trim());
7540
8243
  const first = lines[0] || "";
7541
8244
  const hintLine = lines.slice(1).join(" ") || "";
7542
8245
  const nameMatch = first.match(/Update available for\s+([^!]+)!/i);
7543
8246
  const versionMatch = first.match(/!\s*([^\s]+)\s*→\s*([^\s]+)/);
7544
- const name = nameMatch?.[1]?.trim();
7545
- const current = versionMatch?.[1]?.trim();
7546
- const latest = versionMatch?.[2]?.trim();
7547
8247
  return {
7548
- name,
7549
- current,
7550
- latest,
8248
+ name: nameMatch?.[1]?.trim(),
8249
+ current: versionMatch?.[1]?.trim(),
8250
+ latest: versionMatch?.[2]?.trim(),
7551
8251
  hint: hintLine || void 0
7552
8252
  };
7553
8253
  }
7554
8254
  var UpdateNotice = ({ message: message2 }) => {
7555
- const { name, current, latest, hint } = parseUpdateMessage(message2);
7556
- return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", marginBottom: 1, children: [
7557
- /* @__PURE__ */ jsx13(Text12, { color: "yellow", bold: true, children: "Update Available" }),
7558
- name && current && latest ? /* @__PURE__ */ jsx13(Text12, { color: "gray", children: `${name}: ${current} \u2192 ${latest}` }) : /* @__PURE__ */ jsx13(Text12, { color: "gray", children: message2 }),
7559
- hint ? /* @__PURE__ */ jsx13(Text12, { color: "gray", children: hint }) : null
7560
- ] });
8255
+ const { name, current, latest: latest2, hint } = parseUpdateMessage(message2);
8256
+ return /* @__PURE__ */ jsx15(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", paddingLeft: 2, children: [
8257
+ name && current && latest2 ? /* @__PURE__ */ jsx15(Text14, { color: BLUMA_TERMINAL.brandMagenta, bold: true, children: name }) : null,
8258
+ name && current && latest2 ? /* @__PURE__ */ jsxs14(Text14, { dimColor: true, children: [
8259
+ current,
8260
+ " \u2192 ",
8261
+ latest2
8262
+ ] }) : /* @__PURE__ */ jsx15(Text14, { dimColor: true, children: message2 }),
8263
+ hint ? /* @__PURE__ */ jsx15(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx15(Text14, { dimColor: true, children: hint }) }) : null
8264
+ ] }) });
7561
8265
  };
7562
8266
  var UpdateNotice_default = UpdateNotice;
7563
8267
 
7564
8268
  // src/app/ui/components/ErrorMessage.tsx
7565
- import { Box as Box14, Text as Text13 } from "ink";
7566
- import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
7567
- var ErrorMessage = ({ message: message2, details, hint }) => {
7568
- return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
7569
- /* @__PURE__ */ jsxs13(Box14, { children: [
7570
- /* @__PURE__ */ jsx14(Text13, { color: "red", children: "\u2717" }),
7571
- /* @__PURE__ */ jsx14(Text13, { color: "red", bold: true, children: " Error" })
7572
- ] }),
7573
- /* @__PURE__ */ jsx14(Box14, { paddingLeft: 2, children: /* @__PURE__ */ jsx14(Text13, { color: "red", children: message2 }) }),
7574
- details && /* @__PURE__ */ jsx14(Box14, { paddingLeft: 2, children: /* @__PURE__ */ jsx14(Text13, { dimColor: true, children: details }) }),
7575
- hint && /* @__PURE__ */ jsx14(Box14, { paddingLeft: 2, children: /* @__PURE__ */ jsxs13(Text13, { color: "gray", children: [
7576
- "hint: ",
7577
- hint
7578
- ] }) })
7579
- ] });
7580
- };
8269
+ import { Box as Box16, Text as Text15 } from "ink";
8270
+ import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
8271
+ var ErrorMessage = ({ message: message2, details, hint }) => /* @__PURE__ */ jsx16(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", paddingLeft: 2, children: [
8272
+ /* @__PURE__ */ jsx16(Text15, { color: BLUMA_TERMINAL.err, children: message2 }),
8273
+ details ? /* @__PURE__ */ jsx16(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx16(Text15, { dimColor: true, children: details }) }) : null,
8274
+ hint ? /* @__PURE__ */ jsx16(Box16, { marginTop: 1, children: /* @__PURE__ */ jsxs15(Text15, { dimColor: true, children: [
8275
+ "hint: ",
8276
+ hint
8277
+ ] }) }) : null
8278
+ ] }) });
7581
8279
  var ErrorMessage_default = ErrorMessage;
7582
8280
 
7583
8281
  // src/app/ui/components/ReasoningDisplay.tsx
7584
- import { memo as memo9, useState as useState5 } from "react";
7585
- import { Box as Box15, Text as Text14 } from "ink";
7586
- import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
8282
+ import { memo as memo9 } from "react";
8283
+ import { Box as Box17, Text as Text16 } from "ink";
8284
+ import { jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
7587
8285
  var ReasoningDisplayComponent = ({
7588
8286
  reasoning,
7589
8287
  collapsed = false
7590
8288
  }) => {
7591
- const [isExpanded, setIsExpanded] = useState5(!collapsed);
7592
8289
  if (!reasoning || reasoning.trim() === "") {
7593
8290
  return null;
7594
8291
  }
7595
- const maxLines = 5;
8292
+ const maxLines = collapsed ? 6 : 200;
7596
8293
  const lines = reasoning.split("\n");
7597
- const displayText = isExpanded ? reasoning : lines.slice(0, maxLines).join("\n") + (lines.length > maxLines ? "..." : "");
7598
- return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", paddingX: 1, marginBottom: 1, marginTop: 1, children: [
7599
- /* @__PURE__ */ jsx15(Box15, {}),
7600
- /* @__PURE__ */ jsx15(Box15, { paddingLeft: 2, flexDirection: "column", children: displayText.split("\n").map((line, i) => /* @__PURE__ */ jsx15(Text14, { color: "gray", dimColor: true, children: line }, i)) })
7601
- ] });
8294
+ const displayLines = lines.slice(0, maxLines);
8295
+ const truncated = lines.length > maxLines;
8296
+ return /* @__PURE__ */ jsx17(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", paddingLeft: 2, children: [
8297
+ displayLines.map((line, i) => /* @__PURE__ */ jsx17(Text16, { dimColor: true, children: line }, i)),
8298
+ truncated ? /* @__PURE__ */ jsxs16(Text16, { dimColor: true, children: [
8299
+ "\u2026 +",
8300
+ lines.length - maxLines,
8301
+ " lines"
8302
+ ] }) : null
8303
+ ] }) });
7602
8304
  };
7603
8305
  var ReasoningDisplay = memo9(ReasoningDisplayComponent);
7604
8306
 
7605
8307
  // src/app/ui/components/StreamingText.tsx
7606
- import { useState as useState6, useEffect as useEffect5, useRef as useRef4, memo as memo10 } from "react";
7607
- import { Box as Box16, Text as Text15 } from "ink";
7608
- import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
7609
- var StreamingTextComponent = ({ eventBus, onReasoningComplete }) => {
7610
- const [reasoning, setReasoning] = useState6("");
7611
- const [isStreaming, setIsStreaming] = useState6(false);
8308
+ import { useState as useState5, useEffect as useEffect6, useRef as useRef4, memo as memo10 } from "react";
8309
+ import { Box as Box18, Text as Text17 } from "ink";
8310
+ import { jsx as jsx18, jsxs as jsxs17 } from "react/jsx-runtime";
8311
+ var StreamingTextComponent = ({
8312
+ eventBus,
8313
+ onReasoningComplete
8314
+ }) => {
8315
+ const [reasoning, setReasoning] = useState5("");
8316
+ const [isStreaming, setIsStreaming] = useState5(false);
7612
8317
  const reasoningRef = useRef4("");
7613
8318
  const lastUpdateRef = useRef4(0);
7614
8319
  const pendingUpdateRef = useRef4(null);
7615
- useEffect5(() => {
8320
+ useEffect6(() => {
7616
8321
  const handleStart = () => {
7617
8322
  setReasoning("");
7618
8323
  reasoningRef.current = "";
@@ -7668,19 +8373,56 @@ var StreamingTextComponent = ({ eventBus, onReasoningComplete }) => {
7668
8373
  truncatedCount = lines.length - MAX_VISIBLE_LINES;
7669
8374
  displayLines = lines.slice(-MAX_VISIBLE_LINES);
7670
8375
  }
7671
- return /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", paddingX: 1, marginBottom: 1, marginTop: 1, children: [
7672
- truncatedCount > 0 && /* @__PURE__ */ jsxs15(Text15, { color: "gray", dimColor: true, children: [
7673
- "... (ocultando ",
8376
+ return /* @__PURE__ */ jsx18(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs17(Box18, { flexDirection: "column", paddingLeft: 2, children: [
8377
+ truncatedCount > 0 ? /* @__PURE__ */ jsxs17(Text17, { dimColor: true, children: [
8378
+ "\u2026 ",
7674
8379
  truncatedCount,
7675
- " linhas anteriores para performance) ..."
7676
- ] }),
7677
- /* @__PURE__ */ jsx16(Box16, { paddingLeft: 2, flexDirection: "column", children: displayLines.map((line, i) => /* @__PURE__ */ jsx16(Text15, { color: "gray", dimColor: true, children: line }, i)) })
7678
- ] });
8380
+ " lines above hidden"
8381
+ ] }) : null,
8382
+ displayLines.map((line, i) => /* @__PURE__ */ jsx18(Text17, { dimColor: true, children: line }, i))
8383
+ ] }) });
7679
8384
  };
7680
8385
  var StreamingText = memo10(StreamingTextComponent);
7681
8386
 
8387
+ // src/app/ui/components/ExpandedPreviewBlock.tsx
8388
+ import { memo as memo11 } from "react";
8389
+ import { Box as Box19, Text as Text18 } from "ink";
8390
+ import { jsx as jsx19, jsxs as jsxs18 } from "react/jsx-runtime";
8391
+ function ExpandedPreviewBlockComponent({ data }) {
8392
+ const cols = typeof process.stdout?.columns === "number" ? process.stdout.columns : 80;
8393
+ const rule = TERMINAL_RULE_CHAR.repeat(Math.max(8, cols));
8394
+ const lines = data.fullText.split("\n");
8395
+ const cap = EXPAND_OVERLAY_MAX_LINES;
8396
+ const shown = lines.slice(0, cap);
8397
+ const rest = lines.length - cap;
8398
+ return /* @__PURE__ */ jsxs18(ChatBlock, { marginBottom: 1, children: [
8399
+ /* @__PURE__ */ jsx19(Text18, { color: "white", children: rule }),
8400
+ /* @__PURE__ */ jsxs18(Box19, { flexDirection: "column", paddingLeft: 1, children: [
8401
+ /* @__PURE__ */ jsx19(Text18, { color: BLUMA_TERMINAL.brandMagenta, bold: true, children: "expand (Ctrl+O)" }),
8402
+ /* @__PURE__ */ jsx19(Text18, { dimColor: true, children: data.title }),
8403
+ /* @__PURE__ */ jsxs18(Text18, { dimColor: true, children: [
8404
+ "+",
8405
+ data.linesHidden,
8406
+ " lines were clipped in chat \xB7 below: up to ",
8407
+ cap,
8408
+ " lines \xB7 use read_file_lines before edit_tool"
8409
+ ] }),
8410
+ /* @__PURE__ */ jsxs18(Box19, { flexDirection: "column", marginTop: 1, children: [
8411
+ shown.map((line, i) => /* @__PURE__ */ jsx19(Text18, { dimColor: true, children: line.slice(0, 200) }, i)),
8412
+ rest > 0 ? /* @__PURE__ */ jsxs18(Text18, { dimColor: true, children: [
8413
+ "\u2026 +",
8414
+ rest,
8415
+ " more lines in this chunk"
8416
+ ] }) : null
8417
+ ] })
8418
+ ] }),
8419
+ /* @__PURE__ */ jsx19(Text18, { color: "white", children: rule })
8420
+ ] });
8421
+ }
8422
+ var ExpandedPreviewBlock = memo11(ExpandedPreviewBlockComponent);
8423
+
7682
8424
  // src/app/ui/App.tsx
7683
- import { jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
8425
+ import { jsx as jsx20, jsxs as jsxs19 } from "react/jsx-runtime";
7684
8426
  var SAFE_AUTO_APPROVE_TOOLS = [
7685
8427
  // Comunicação/UI
7686
8428
  "message",
@@ -7698,36 +8440,64 @@ var SAFE_AUTO_APPROVE_TOOLS = [
7698
8440
  ];
7699
8441
  var AppComponent = ({ eventBus, sessionId }) => {
7700
8442
  const agentInstance = useRef5(null);
7701
- const [history, setHistory] = useState7([]);
7702
- const [statusMessage, setStatusMessage] = useState7(
8443
+ const [history, setHistory] = useState6([]);
8444
+ const [statusMessage, setStatusMessage] = useState6(
7703
8445
  "Initializing agent..."
7704
8446
  );
7705
- const [toolsCount, setToolsCount] = useState7(null);
7706
- const [mcpStatus, setMcpStatus] = useState7(
8447
+ const [toolsCount, setToolsCount] = useState6(null);
8448
+ const [mcpStatus, setMcpStatus] = useState6(
7707
8449
  "connecting"
7708
8450
  );
7709
- const [isProcessing, setIsProcessing] = useState7(true);
7710
- const [pendingConfirmation, setPendingConfirmation] = useState7(
8451
+ const [isProcessing, setIsProcessing] = useState6(true);
8452
+ const [pendingConfirmation, setPendingConfirmation] = useState6(
7711
8453
  null
7712
8454
  );
7713
- const [confirmationPreview, setConfirmationPreview] = useState7(
8455
+ const [confirmationPreview, setConfirmationPreview] = useState6(
7714
8456
  null
7715
8457
  );
7716
- const [isInitAgentActive, setIsInitAgentActive] = useState7(false);
8458
+ const [isInitAgentActive, setIsInitAgentActive] = useState6(false);
7717
8459
  const alwaysAcceptList = useRef5([]);
7718
8460
  const workdir = process.cwd();
7719
8461
  const updateCheckRan = useRef5(false);
7720
- const sessionStartTime = useRef5(Date.now());
7721
- const [toolCallCount, setToolCallCount] = useState7(0);
8462
+ const turnStartedAtRef = useRef5(null);
8463
+ const appendExpandPreviewToHistory = useCallback2(() => {
8464
+ const p = peekLatestExpandable();
8465
+ setHistory((prev) => {
8466
+ const id = prev.length;
8467
+ if (!p) {
8468
+ return [
8469
+ ...prev,
8470
+ {
8471
+ id,
8472
+ component: /* @__PURE__ */ jsx20(ChatMeta, { children: "Ctrl+O: no truncated preview to expand" }, id)
8473
+ }
8474
+ ];
8475
+ }
8476
+ return [
8477
+ ...prev,
8478
+ {
8479
+ id,
8480
+ component: /* @__PURE__ */ jsx20(ExpandedPreviewBlock, { data: p }, id)
8481
+ }
8482
+ ];
8483
+ });
8484
+ }, []);
8485
+ useEffect7(() => {
8486
+ expandPreviewHotkeyBus.on("expand", appendExpandPreviewToHistory);
8487
+ return () => {
8488
+ expandPreviewHotkeyBus.off("expand", appendExpandPreviewToHistory);
8489
+ };
8490
+ }, [appendExpandPreviewToHistory]);
7722
8491
  const handleInterrupt = useCallback2(() => {
7723
8492
  if (!isProcessing) return;
7724
8493
  eventBus.emit("user_interrupt");
8494
+ turnStartedAtRef.current = null;
7725
8495
  setIsProcessing(false);
7726
8496
  setHistory((prev) => [
7727
8497
  ...prev,
7728
8498
  {
7729
8499
  id: prev.length,
7730
- component: /* @__PURE__ */ jsx17(Text16, { color: "yellow", children: "-- Task cancelled by dev. --" })
8500
+ component: /* @__PURE__ */ jsx20(ChatMeta, { children: "cancelled (Esc)" })
7731
8501
  }
7732
8502
  ]);
7733
8503
  }, [isProcessing, eventBus]);
@@ -7743,6 +8513,7 @@ var AppComponent = ({ eventBus, sessionId }) => {
7743
8513
  if (cmd === "init") {
7744
8514
  setIsInitAgentActive(true);
7745
8515
  setIsProcessing(true);
8516
+ turnStartedAtRef.current = Date.now();
7746
8517
  } else {
7747
8518
  setIsProcessing(false);
7748
8519
  setIsInitAgentActive(false);
@@ -7751,11 +8522,11 @@ var AppComponent = ({ eventBus, sessionId }) => {
7751
8522
  ...prev,
7752
8523
  {
7753
8524
  id: prev.length,
7754
- component: /* @__PURE__ */ jsx17(Box17, { marginBottom: 1, children: /* @__PURE__ */ jsx17(Text16, { color: "white", dimColor: true, children: text }) })
8525
+ component: /* @__PURE__ */ jsx20(ChatUserMessage, { children: /* @__PURE__ */ jsx20(Text19, { dimColor: true, children: text }) })
7755
8526
  },
7756
8527
  {
7757
8528
  id: prev.length + 1,
7758
- component: /* @__PURE__ */ jsx17(
8529
+ component: /* @__PURE__ */ jsx20(
7759
8530
  SlashCommands_default,
7760
8531
  {
7761
8532
  input: text,
@@ -7774,17 +8545,15 @@ var AppComponent = ({ eventBus, sessionId }) => {
7774
8545
  return;
7775
8546
  }
7776
8547
  setIsProcessing(true);
8548
+ turnStartedAtRef.current = Date.now();
7777
8549
  setHistory((prev) => [
7778
8550
  ...prev,
7779
8551
  {
7780
8552
  id: prev.length,
7781
- component: /* @__PURE__ */ jsxs16(Box17, { marginBottom: 1, children: [
7782
- /* @__PURE__ */ jsx17(Text16, { color: "white", bold: true, children: "$ " }),
7783
- /* @__PURE__ */ jsxs16(Text16, { color: "white", children: [
7784
- "!",
7785
- command
7786
- ] })
7787
- ] })
8553
+ component: /* @__PURE__ */ jsx20(ChatUserMessage, { children: /* @__PURE__ */ jsxs19(Text19, { bold: true, color: "white", children: [
8554
+ "$ !",
8555
+ command
8556
+ ] }) })
7788
8557
  }
7789
8558
  ]);
7790
8559
  Promise.resolve().then(() => (init_async_command(), async_command_exports)).then(async ({ runCommandAsync: runCommandAsync2 }) => {
@@ -7798,11 +8567,12 @@ Command ID: ${result.command_id}
7798
8567
  Please use command_status to check the result and report back to the user.`;
7799
8568
  agentInstance.current?.processTurn({ content: contextMessage });
7800
8569
  } else {
8570
+ turnStartedAtRef.current = null;
7801
8571
  setHistory((prev) => [
7802
8572
  ...prev,
7803
8573
  {
7804
8574
  id: prev.length,
7805
- component: /* @__PURE__ */ jsxs16(Text16, { color: "red", children: [
8575
+ component: /* @__PURE__ */ jsxs19(Text19, { color: "red", children: [
7806
8576
  "Failed to execute: ",
7807
8577
  result.error || result.message
7808
8578
  ] })
@@ -7811,11 +8581,12 @@ Please use command_status to check the result and report back to the user.`;
7811
8581
  setIsProcessing(false);
7812
8582
  }
7813
8583
  } catch (err) {
8584
+ turnStartedAtRef.current = null;
7814
8585
  setHistory((prev) => [
7815
8586
  ...prev,
7816
8587
  {
7817
8588
  id: prev.length,
7818
- component: /* @__PURE__ */ jsxs16(Text16, { color: "red", children: [
8589
+ component: /* @__PURE__ */ jsxs19(Text19, { color: "red", children: [
7819
8590
  "Error: ",
7820
8591
  err.message
7821
8592
  ] })
@@ -7827,21 +8598,13 @@ Please use command_status to check the result and report back to the user.`;
7827
8598
  return;
7828
8599
  }
7829
8600
  setIsProcessing(true);
8601
+ turnStartedAtRef.current = Date.now();
7830
8602
  const displayText = text.length > 1e4 ? text.substring(0, 1e4) + "..." : text;
7831
8603
  setHistory((prev) => [
7832
8604
  ...prev,
7833
8605
  {
7834
8606
  id: prev.length,
7835
- component: (
7836
- // Uma única Box para o espaçamento
7837
- /* @__PURE__ */ jsx17(Box17, { marginBottom: 1, children: /* @__PURE__ */ jsxs16(Text16, { color: "white", dimColor: true, children: [
7838
- /* @__PURE__ */ jsxs16(Text16, { color: "white", children: [
7839
- ">",
7840
- " "
7841
- ] }),
7842
- displayText
7843
- ] }) })
7844
- )
8607
+ component: /* @__PURE__ */ jsx20(ChatUserMessage, { children: /* @__PURE__ */ jsx20(Text19, { children: displayText }) })
7845
8608
  }
7846
8609
  ]);
7847
8610
  agentInstance.current.processTurn({ content: text });
@@ -7869,8 +8632,8 @@ Please use command_status to check the result and report back to the user.`;
7869
8632
  },
7870
8633
  []
7871
8634
  );
7872
- useEffect6(() => {
7873
- setHistory([{ id: 0, component: /* @__PURE__ */ jsx17(Header, { sessionId, workdir }) }]);
8635
+ useEffect7(() => {
8636
+ setHistory([{ id: 0, component: /* @__PURE__ */ jsx20(Header, { sessionId, workdir }) }]);
7874
8637
  const initializeAgent = async () => {
7875
8638
  try {
7876
8639
  agentInstance.current = new Agent(sessionId, eventBus);
@@ -7890,6 +8653,20 @@ Please use command_status to check the result and report back to the user.`;
7890
8653
  };
7891
8654
  const handleBackendMessage = (parsed) => {
7892
8655
  try {
8656
+ const appendTurnDurationIfAny = () => {
8657
+ const t = turnStartedAtRef.current;
8658
+ if (t == null) return;
8659
+ turnStartedAtRef.current = null;
8660
+ const ms = Date.now() - t;
8661
+ const sec = ms < 1e4 ? (ms / 1e3).toFixed(1) : String(Math.round(ms / 1e3));
8662
+ setHistory((prev) => [
8663
+ ...prev,
8664
+ {
8665
+ id: prev.length,
8666
+ component: /* @__PURE__ */ jsx20(ChatTurnDuration, { secondsFormatted: sec })
8667
+ }
8668
+ ]);
8669
+ };
7893
8670
  if (parsed.type === "done" || parsed.type === "error") {
7894
8671
  setIsInitAgentActive(false);
7895
8672
  }
@@ -7915,6 +8692,7 @@ Please use command_status to check the result and report back to the user.`;
7915
8692
  if (parsed.type === "done") {
7916
8693
  if (parsed.status !== "awaiting_confirmation") {
7917
8694
  setStatusMessage(null);
8695
+ appendTurnDurationIfAny();
7918
8696
  }
7919
8697
  setIsProcessing(false);
7920
8698
  return;
@@ -7936,7 +8714,7 @@ Please use command_status to check the result and report back to the user.`;
7936
8714
  ...prev,
7937
8715
  {
7938
8716
  id: prev.length,
7939
- component: /* @__PURE__ */ jsx17(UpdateNotice_default, { message: msg })
8717
+ component: /* @__PURE__ */ jsx20(UpdateNotice_default, { message: msg })
7940
8718
  }
7941
8719
  ]);
7942
8720
  }
@@ -7950,25 +8728,14 @@ Please use command_status to check the result and report back to the user.`;
7950
8728
  }
7951
8729
  let newComponent = null;
7952
8730
  if (parsed.type === "debug") {
7953
- newComponent = /* @__PURE__ */ jsx17(Text16, { color: "gray", children: parsed.message });
8731
+ newComponent = /* @__PURE__ */ jsx20(ChatMeta, { children: parsed.message });
7954
8732
  } else if (parsed.type === "protocol_violation") {
7955
- newComponent = /* @__PURE__ */ jsxs16(
7956
- Box17,
7957
- {
7958
- borderStyle: "round",
7959
- borderColor: "yellow",
7960
- flexDirection: "column",
7961
- marginBottom: 1,
7962
- paddingX: 1,
7963
- children: [
7964
- /* @__PURE__ */ jsx17(Text16, { color: "yellow", bold: true, children: "Protocol Violation" }),
7965
- /* @__PURE__ */ jsx17(Text16, { color: "gray", children: parsed.content }),
7966
- /* @__PURE__ */ jsx17(Text16, { color: "yellow", children: parsed.message })
7967
- ]
7968
- }
7969
- );
8733
+ newComponent = /* @__PURE__ */ jsx20(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs19(Box20, { flexDirection: "column", paddingLeft: 2, children: [
8734
+ /* @__PURE__ */ jsx20(Text19, { dimColor: true, children: parsed.content }),
8735
+ /* @__PURE__ */ jsx20(Text19, { dimColor: true, children: parsed.message })
8736
+ ] }) });
7970
8737
  } else if (parsed.type === "error") {
7971
- newComponent = /* @__PURE__ */ jsx17(
8738
+ newComponent = /* @__PURE__ */ jsx20(
7972
8739
  ErrorMessage_default,
7973
8740
  {
7974
8741
  message: parsed.message,
@@ -7978,7 +8745,7 @@ Please use command_status to check the result and report back to the user.`;
7978
8745
  );
7979
8746
  } else if (parsed.type === "tool_call") {
7980
8747
  const nextId3 = history.length;
7981
- newComponent = /* @__PURE__ */ jsx17(
8748
+ newComponent = /* @__PURE__ */ jsx20(
7982
8749
  ToolCallDisplay,
7983
8750
  {
7984
8751
  toolName: parsed.tool_name,
@@ -7987,7 +8754,7 @@ Please use command_status to check the result and report back to the user.`;
7987
8754
  }
7988
8755
  );
7989
8756
  } else if (parsed.type === "tool_result") {
7990
- newComponent = /* @__PURE__ */ jsx17(
8757
+ newComponent = /* @__PURE__ */ jsx20(
7991
8758
  ToolResultDisplay,
7992
8759
  {
7993
8760
  toolName: parsed.tool_name,
@@ -7995,18 +8762,11 @@ Please use command_status to check the result and report back to the user.`;
7995
8762
  }
7996
8763
  );
7997
8764
  } else if (parsed.type === "user_overlay") {
7998
- newComponent = /* @__PURE__ */ jsx17(Box17, { marginBottom: 1, children: /* @__PURE__ */ jsxs16(Text16, { color: "gray", children: [
7999
- /* @__PURE__ */ jsxs16(Text16, { color: "magenta", children: [
8000
- ">",
8001
- " "
8002
- ] }),
8003
- parsed.payload
8004
- ] }) });
8765
+ newComponent = /* @__PURE__ */ jsx20(ChatUserMessage, { children: /* @__PURE__ */ jsx20(Text19, { dimColor: true, children: parsed.payload }) });
8005
8766
  } else if (parsed.type === "reasoning") {
8006
- newComponent = /* @__PURE__ */ jsx17(ReasoningDisplay, { reasoning: parsed.content });
8767
+ newComponent = /* @__PURE__ */ jsx20(ReasoningDisplay, { reasoning: parsed.content });
8007
8768
  } else if (parsed.type === "log") {
8008
- newComponent = /* @__PURE__ */ jsxs16(Text16, { color: "gray", children: [
8009
- "\u2139\uFE0F ",
8769
+ newComponent = /* @__PURE__ */ jsxs19(ChatMeta, { children: [
8010
8770
  parsed.message,
8011
8771
  parsed.payload ? `: ${parsed.payload}` : ""
8012
8772
  ] });
@@ -8014,10 +8774,25 @@ Please use command_status to check the result and report back to the user.`;
8014
8774
  newComponent = null;
8015
8775
  }
8016
8776
  if (newComponent) {
8017
- setHistory((prev) => [
8018
- ...prev,
8019
- { id: prev.length, component: newComponent }
8020
- ]);
8777
+ setHistory((prev) => {
8778
+ const next = [
8779
+ ...prev,
8780
+ { id: prev.length, component: newComponent }
8781
+ ];
8782
+ if (parsed.type === "error") {
8783
+ const t = turnStartedAtRef.current;
8784
+ if (t != null) {
8785
+ turnStartedAtRef.current = null;
8786
+ const ms = Date.now() - t;
8787
+ const sec = ms < 1e4 ? (ms / 1e3).toFixed(1) : String(Math.round(ms / 1e3));
8788
+ next.push({
8789
+ id: next.length,
8790
+ component: /* @__PURE__ */ jsx20(ChatTurnDuration, { secondsFormatted: sec })
8791
+ });
8792
+ }
8793
+ }
8794
+ return next;
8795
+ });
8021
8796
  }
8022
8797
  } catch (error) {
8023
8798
  }
@@ -8035,10 +8810,17 @@ Please use command_status to check the result and report back to the user.`;
8035
8810
  }, [eventBus, sessionId, handleConfirmation]);
8036
8811
  const renderInteractiveComponent = () => {
8037
8812
  if (mcpStatus !== "connected") {
8038
- return;
8813
+ return /* @__PURE__ */ jsx20(
8814
+ SessionInfoConnectingMCP_default,
8815
+ {
8816
+ sessionId,
8817
+ workdir,
8818
+ statusMessage
8819
+ }
8820
+ );
8039
8821
  }
8040
8822
  if (pendingConfirmation) {
8041
- return /* @__PURE__ */ jsx17(
8823
+ return /* @__PURE__ */ jsx20(
8042
8824
  ConfirmationPrompt,
8043
8825
  {
8044
8826
  toolCalls: pendingConfirmation,
@@ -8050,9 +8832,9 @@ Please use command_status to check the result and report back to the user.`;
8050
8832
  }
8051
8833
  );
8052
8834
  }
8053
- return /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", children: [
8054
- isProcessing && !pendingConfirmation && /* @__PURE__ */ jsx17(WorkingTimer, { eventBus }),
8055
- /* @__PURE__ */ jsx17(
8835
+ return /* @__PURE__ */ jsxs19(Box20, { flexDirection: "column", children: [
8836
+ isProcessing && !pendingConfirmation && /* @__PURE__ */ jsx20(WorkingTimer, { eventBus }),
8837
+ /* @__PURE__ */ jsx20(
8056
8838
  InputPrompt,
8057
8839
  {
8058
8840
  onSubmit: handleSubmit,
@@ -8063,9 +8845,9 @@ Please use command_status to check the result and report back to the user.`;
8063
8845
  )
8064
8846
  ] });
8065
8847
  };
8066
- return /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", children: [
8067
- /* @__PURE__ */ jsx17(Static, { items: history, children: (item) => /* @__PURE__ */ jsx17(Box17, { children: item.component }, item.id) }),
8068
- /* @__PURE__ */ jsx17(
8848
+ return /* @__PURE__ */ jsxs19(Box20, { flexDirection: "column", children: [
8849
+ /* @__PURE__ */ jsx20(Static, { items: history, children: (item) => /* @__PURE__ */ jsx20(Box20, { children: item.component }, item.id) }),
8850
+ /* @__PURE__ */ jsx20(
8069
8851
  StreamingText,
8070
8852
  {
8071
8853
  eventBus,
@@ -8075,7 +8857,7 @@ Please use command_status to check the result and report back to the user.`;
8075
8857
  ...prev,
8076
8858
  {
8077
8859
  id: prev.length,
8078
- component: /* @__PURE__ */ jsx17(ReasoningDisplay, { reasoning })
8860
+ component: /* @__PURE__ */ jsx20(ReasoningDisplay, { reasoning })
8079
8861
  }
8080
8862
  ]);
8081
8863
  }
@@ -8085,7 +8867,7 @@ Please use command_status to check the result and report back to the user.`;
8085
8867
  renderInteractiveComponent()
8086
8868
  ] });
8087
8869
  };
8088
- var App = memo11(AppComponent);
8870
+ var App = memo12(AppComponent);
8089
8871
  var App_default = App;
8090
8872
 
8091
8873
  // src/app/ui/utils/terminalTitle.ts
@@ -8209,7 +8991,7 @@ async function runAgentMode() {
8209
8991
  process.env.BLUMA_SANDBOX_NAME = String(envelope.metadata.sandbox_name);
8210
8992
  }
8211
8993
  }
8212
- const eventBus = new EventEmitter2();
8994
+ const eventBus = new EventEmitter3();
8213
8995
  const sessionId = envelope.session_id || envelope.message_id || uuidv46();
8214
8996
  const uc = envelope.user_context;
8215
8997
  const userContextInput = {
@@ -8341,13 +9123,13 @@ async function runAgentMode() {
8341
9123
  function runCliMode() {
8342
9124
  const BLUMA_TITLE = process.env.BLUMA_TITLE || "BluMa - NomadEngenuity";
8343
9125
  startTitleKeeper(BLUMA_TITLE);
8344
- const eventBus = new EventEmitter2();
9126
+ const eventBus = new EventEmitter3();
8345
9127
  const sessionId = uuidv46();
8346
9128
  const props = {
8347
9129
  eventBus,
8348
9130
  sessionId
8349
9131
  };
8350
- render(React11.createElement(App_default, props));
9132
+ render(React12.createElement(App_default, props));
8351
9133
  }
8352
9134
  var argv = process.argv.slice(2);
8353
9135
  if (argv[0] === "agent") {