@nomad-e/bluma-cli 0.1.30 → 0.1.32

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);
3726
+ saveMemoryToFile();
3727
+ return {
3728
+ success: true,
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
+ }
3612
3781
  saveMemoryToFile();
3613
3782
  return {
3614
3783
  success: true,
3615
- message: "All coding memory entries cleared",
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,35 @@ 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
+ const argv1 = process.argv[1];
4310
+ if (argv1 && !argv1.startsWith("-")) {
4311
+ try {
4312
+ const scriptDir = path14.dirname(path14.resolve(argv1));
4313
+ candidates.push(path14.join(scriptDir, "config", "skills"));
4314
+ } catch {
4315
+ }
4130
4316
  }
4317
+ candidates.push(
4318
+ path14.join(process.cwd(), "dist", "config", "skills"),
4319
+ path14.join(process.cwd(), "node_modules", "@nomad-e", "bluma-cli", "dist", "config", "skills")
4320
+ );
4321
+ if (typeof __dirname !== "undefined") {
4322
+ candidates.push(
4323
+ path14.join(__dirname, "config", "skills"),
4324
+ path14.join(__dirname, "..", "..", "..", "config", "skills")
4325
+ );
4326
+ }
4327
+ for (const c of candidates) {
4328
+ const abs = path14.resolve(c);
4329
+ if (fs12.existsSync(abs)) {
4330
+ return abs;
4331
+ }
4332
+ }
4333
+ return path14.join(process.cwd(), "dist", "config", "skills");
4131
4334
  }
4132
4335
  /**
4133
4336
  * Lista skills disponíveis de todas as fontes.
@@ -4501,6 +4704,10 @@ var SYSTEM_PROMPT = `
4501
4704
  - NEVER try to \`load_skill\` with a name that isn't in \`<available_skills>\`
4502
4705
  - Your base knowledge (testing, git, docker...) is NOT a skill - it's just knowledge you have
4503
4706
 
4707
+ **Git / commits (when \`git-commit\` is listed in \`<available_skills>\`):**
4708
+ - For "commit", "push", "commit message", or "conventional commit": call \`load_skill({ skill_name: "git-commit" })\` **before** \`git commit\`, then follow the skill body (scopes, types, optional validator script).
4709
+ - The bundled skill name is exactly \`git-commit\`. Do **not** use \`git-conventional\` or other invented names.
4710
+
4504
4711
  **Progressive Disclosure (Skills with references and scripts):**
4505
4712
 
4506
4713
  Skills may include additional assets beyond the SKILL.md body:
@@ -4553,19 +4760,35 @@ You MUST adapt all commands to this environment. Use the correct package manager
4553
4760
  <coding_memory>
4554
4761
  ## Persistent coding memory (tool: \`coding_memory\`)
4555
4762
 
4556
- You have **durable notes** on disk (typically \`~/.bluma/coding_memory.json\`), separate from chat history. They **survive new sessions**.
4763
+ 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**.
4764
+
4765
+ ### Ground rules
4766
+ - **Never invent** what is stored: if you are not sure, run \`action: "search"\` (or \`list\`) before stating a remembered fact.
4767
+ - 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.
4768
+ - 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").
4769
+ - **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.
4770
+
4771
+ ### When to search first
4772
+ - User: "lembra-te / remember / last time / o que combin\xE1mos / qual era o comando".
4773
+ - You are about to edit, run tests, or refactor using **assumptions** that came from earlier in this project (stack, scripts, env vars, URLs).
4774
+ - 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
4775
 
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.
4776
+ ### When to add
4777
+ - **Stable** facts: architecture, naming conventions, test commands, ports, API bases, repo layout, user preferences that repeat.
4778
+ - **Pointers**, not novels: one short \`note\` + \`tags\` (e.g. \`auth\`, \`deploy\`, \`bluma\`). Put long specs in the repo; here only **reminders**.
4561
4779
 
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).
4780
+ ### CRUD (tool actions)
4781
+ - **Create:** \`add\` + \`note\` (+ optional \`tags\`).
4782
+ - **Read:** \`list\` (all ids) or \`search\` + \`query\`.
4783
+ - **Update:** \`update\` + \`id\` + new \`note\` and/or \`tags\` (omit fields you leave unchanged).
4784
+ - **Delete:** \`remove\` + \`id\` only \u2014 **never** bulk-wipe; the product intentionally forbids it.
4785
+
4786
+ ### When something changes
4787
+ - 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
4788
 
4566
4789
  ### 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.
4790
+ - Prefer **search** or **list** over guessing whenever "memory" matters.
4791
+ - After important milestones (feature done, bug fixed, migration applied), consider a **brief add** so future-you in a new session is not blind.
4569
4792
  </coding_memory>
4570
4793
 
4571
4794
  ---
@@ -4601,6 +4824,7 @@ Run tests when modifying code. If a testing skill is listed in available_skills,
4601
4824
  - **ALWAYS read a file before editing** - Use read_file_lines or ls_tool first
4602
4825
  - **Use absolute paths** when possible to avoid ambiguity
4603
4826
  - **For edit_tool**: Provide exact content with correct whitespace (read first!)
4827
+ - **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
4828
  - **Check file exists** before attempting edits
4605
4829
 
4606
4830
  ### Safe Auto-Approved Tools (no confirmation needed):
@@ -5007,7 +5231,7 @@ tools:
5007
5231
 
5008
5232
  2. READ FRONTMATTER FIRST
5009
5233
  - name: npm-publish
5010
- - depends_on: [git-conventional]
5234
+ - depends_on: [git-commit]
5011
5235
  - tools.required: [shell_command, command_status]
5012
5236
  - tools.recommended: [read_file_lines, message]
5013
5237
 
@@ -5044,17 +5268,17 @@ User: "Publish the package"
5044
5268
  2. load_skill({ skill_name: "npm-publish" })
5045
5269
 
5046
5270
  3. Read frontmatter:
5047
- - depends_on: [git-conventional]
5271
+ - depends_on: [git-commit]
5048
5272
  - tools.required: [shell_command, command_status]
5049
5273
 
5050
5274
  4. Read body -> Workflow says:
5051
- "Step 1: If uncommitted changes, delegate to git-conventional"
5275
+ "Step 1: If uncommitted changes, delegate to git-commit"
5052
5276
 
5053
5277
  5. Check git status with shell_command (required tool)
5054
5278
  Found changes
5055
5279
 
5056
- 6. Delegate: load_skill({ skill_name: "git-conventional" })
5057
- - Read git-conventional frontmatter
5280
+ 6. Delegate: load_skill({ skill_name: "git-commit" })
5281
+ - Read git-commit frontmatter
5058
5282
  - Follow its workflow for commit
5059
5283
  - Commit done
5060
5284
 
@@ -5080,7 +5304,7 @@ User: "Publish the package"
5080
5304
  <coding_memory_snapshot>
5081
5305
  ## Persistent notes (loaded at session start from disk)
5082
5306
 
5083
- ${memorySnapshot.trim().length > 0 ? memorySnapshot : "(No entries yet. Use coding_memory with action add or search to build continuity across sessions.)"}
5307
+ ${memorySnapshot.trim().length > 0 ? memorySnapshot : "(No entries yet. Use coding_memory: add, list, search \u2014 remove/update by id when needed.)"}
5084
5308
  </coding_memory_snapshot>
5085
5309
  `;
5086
5310
  return prompt;
@@ -5094,6 +5318,161 @@ function isGitRepo(dir) {
5094
5318
  }
5095
5319
  }
5096
5320
 
5321
+ // src/app/agent/core/context-api/token_counter.ts
5322
+ import { getEncoding } from "js-tiktoken";
5323
+ var MESSAGE_OVERHEAD_TOKENS = 4;
5324
+ var CONVERSATION_BASE_OVERHEAD = 3;
5325
+ var cachedEncoding = null;
5326
+ function getO200kEncoding() {
5327
+ if (!cachedEncoding) {
5328
+ cachedEncoding = getEncoding("o200k_base");
5329
+ }
5330
+ return cachedEncoding;
5331
+ }
5332
+ function messageBodyForTokens(msg) {
5333
+ const c = msg.content;
5334
+ if (c == null) {
5335
+ return "";
5336
+ }
5337
+ if (typeof c === "string") {
5338
+ return c;
5339
+ }
5340
+ return JSON.stringify(c);
5341
+ }
5342
+ function messageExtraForTokens(msg) {
5343
+ const m = msg;
5344
+ const parts = [String(m.role ?? "")];
5345
+ if (Array.isArray(m.tool_calls)) {
5346
+ parts.push(JSON.stringify(m.tool_calls));
5347
+ }
5348
+ if (typeof m.tool_call_id === "string") {
5349
+ parts.push(m.tool_call_id);
5350
+ }
5351
+ if (typeof m.name === "string") {
5352
+ parts.push(m.name);
5353
+ }
5354
+ return parts.join("\0");
5355
+ }
5356
+ function countTokens(messages) {
5357
+ if (messages.length === 0) {
5358
+ return CONVERSATION_BASE_OVERHEAD;
5359
+ }
5360
+ const enc = getO200kEncoding();
5361
+ let total = CONVERSATION_BASE_OVERHEAD;
5362
+ for (const msg of messages) {
5363
+ const body = messageBodyForTokens(msg);
5364
+ const extra = messageExtraForTokens(msg);
5365
+ const nBody = body ? enc.encode(body).length : 0;
5366
+ const nExtra = extra ? enc.encode(extra).length : 0;
5367
+ total += nBody + nExtra + MESSAGE_OVERHEAD_TOKENS;
5368
+ }
5369
+ return total;
5370
+ }
5371
+
5372
+ // src/app/agent/core/context-api/history_anchor.ts
5373
+ function stripJsonFence(text) {
5374
+ let s = text.trim();
5375
+ const fence = /^```(?:json)?\s*([\s\S]*?)```\s*$/im.exec(s);
5376
+ if (fence) {
5377
+ s = fence[1].trim();
5378
+ }
5379
+ return s;
5380
+ }
5381
+ function parseAnchorJson(raw) {
5382
+ const cleaned = stripJsonFence(raw);
5383
+ const start = cleaned.indexOf("{");
5384
+ const end = cleaned.lastIndexOf("}");
5385
+ if (start === -1 || end === -1 || end <= start) {
5386
+ throw new Error("compressToAnchor: resposta sem JSON object");
5387
+ }
5388
+ const parsed = JSON.parse(cleaned.slice(start, end + 1));
5389
+ return {
5390
+ intent: String(parsed.intent ?? ""),
5391
+ filesModified: Array.isArray(parsed.filesModified) ? parsed.filesModified.map(String) : [],
5392
+ decisionsMade: Array.isArray(parsed.decisionsMade) ? parsed.decisionsMade.map(String) : [],
5393
+ errorsEncountered: Array.isArray(parsed.errorsEncountered) ? parsed.errorsEncountered.map(String) : [],
5394
+ currentState: String(parsed.currentState ?? ""),
5395
+ nextSteps: String(parsed.nextSteps ?? ""),
5396
+ compressedAt: typeof parsed.compressedAt === "number" ? parsed.compressedAt : Date.now(),
5397
+ turnsCompressed: typeof parsed.turnsCompressed === "number" ? parsed.turnsCompressed : 0
5398
+ };
5399
+ }
5400
+ function formatMessagesForPrompt(msgs) {
5401
+ return msgs.map((m, i) => {
5402
+ const role = m.role ?? "?";
5403
+ const content = m.content;
5404
+ const text = typeof content === "string" ? content : content == null ? "" : JSON.stringify(content);
5405
+ const toolCalls = m.tool_calls;
5406
+ const tail = toolCalls ? `
5407
+ [tool_calls]: ${JSON.stringify(toolCalls)}` : "";
5408
+ return `--- msg ${i + 1} (${role}) ---
5409
+ ${text.slice(0, 12e4)}${tail}`;
5410
+ }).join("\n\n");
5411
+ }
5412
+ async function compressToAnchor(spansToCompress, existingAnchor, llmService, userContext, turnSlicesInBatch) {
5413
+ const messagesBlock = formatMessagesForPrompt(spansToCompress);
5414
+ const existingBlock = existingAnchor ? `ANCHOR EXISTENTE (estende este, n\xE3o substitui \u2014 merge sem perder informa\xE7\xE3o):
5415
+ ${JSON.stringify(existingAnchor, null, 2)}` : "N\xE3o existe anchor anterior.";
5416
+ const userPrompt = `Tu \xE9s um compressor de hist\xF3rico para um agente de c\xF3digo CLI.
5417
+
5418
+ ${existingBlock}
5419
+
5420
+ MENSAGENS A COMPRIMIR (turnos antigos da conversa):
5421
+ ${messagesBlock}
5422
+
5423
+ Produz um JSON com esta estrutura exata:
5424
+ {
5425
+ "intent": "objetivo principal da sess\xE3o",
5426
+ "filesModified": ["path1", "path2"],
5427
+ "decisionsMade": ["decis\xE3o 1", "decis\xE3o 2"],
5428
+ "errorsEncountered": ["erro X \u2192 resolvido com Y"],
5429
+ "currentState": "onde estamos agora em 2-3 frases",
5430
+ "nextSteps": "o que est\xE1 pendente",
5431
+ "compressedAt": ${Date.now()},
5432
+ "turnsCompressed": 0
5433
+ }
5434
+
5435
+ Regras:
5436
+ - Responde APENAS com JSON v\xE1lido, sem markdown, sem texto extra
5437
+ - filesModified: inclui TODOS os paths de ficheiros mencionados
5438
+ - decisionsMade: preserva decis\xF5es de arquitetura, abordagem, estrutura
5439
+ - errorsEncountered: formato "erro \u2192 solu\xE7\xE3o"
5440
+ - Se j\xE1 existe anchor, faz merge \u2014 n\xE3o percas informa\xE7\xE3o anterior
5441
+ - O campo turnsCompressed na resposta ser\xE1 ignorado; usa 0.`;
5442
+ const resp = await llmService.chatCompletion({
5443
+ messages: [
5444
+ {
5445
+ role: "system",
5446
+ content: "\xC9s um extrator factual. Sa\xEDda: apenas um objeto JSON v\xE1lido, sem markdown."
5447
+ },
5448
+ { role: "user", content: userPrompt }
5449
+ ],
5450
+ temperature: 0,
5451
+ max_tokens: 4096,
5452
+ userContext
5453
+ });
5454
+ const text = resp.choices[0]?.message?.content;
5455
+ if (typeof text !== "string" || !text.trim()) {
5456
+ throw new Error("compressToAnchor: modelo n\xE3o devolveu conte\xFAdo textual");
5457
+ }
5458
+ const anchor = parseAnchorJson(text);
5459
+ const prevTurns = existingAnchor?.turnsCompressed ?? 0;
5460
+ anchor.turnsCompressed = prevTurns + Math.max(0, turnSlicesInBatch);
5461
+ anchor.compressedAt = Date.now();
5462
+ return anchor;
5463
+ }
5464
+ function anchorToSystemMessage(anchor) {
5465
+ const date = new Date(anchor.compressedAt).toISOString();
5466
+ const content = `[MEM\xD3RIA DE SESS\xC3O \u2014 ${anchor.turnsCompressed} turnos comprimidos em ${date}]
5467
+ Objetivo: ${anchor.intent}
5468
+ Ficheiros modificados: ${anchor.filesModified.join(", ") || "(nenhum)"}
5469
+ Decis\xF5es tomadas: ${anchor.decisionsMade.join(" | ") || "(nenhuma)"}
5470
+ Erros resolvidos: ${anchor.errorsEncountered.join(" | ") || "(nenhum)"}
5471
+ Estado atual: ${anchor.currentState}
5472
+ Pr\xF3ximos passos: ${anchor.nextSteps}`;
5473
+ return { role: "system", content };
5474
+ }
5475
+
5097
5476
  // src/app/agent/core/context-api/context_manager.ts
5098
5477
  function isEndTurnToolCall(tc) {
5099
5478
  if (tc.function.name === "agent_end_turn") {
@@ -5109,40 +5488,26 @@ function isEndTurnToolCall(tc) {
5109
5488
  }
5110
5489
  return false;
5111
5490
  }
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);
5491
+ function partitionConversationIntoTurnSlices(conversationHistory) {
5126
5492
  const turns = [];
5127
5493
  let currentTurn = [];
5128
- let turnsFound = 0;
5129
5494
  const isDevOverlay = (m) => m?.role === "user" && m?.name === "user_overlay";
5130
5495
  for (let i = conversationHistory.length - 1; i >= 0; i--) {
5131
5496
  const msg = conversationHistory[i];
5132
5497
  currentTurn.unshift(msg);
5133
- const endsWithAgentEnd = msg.role === "assistant" && msg.tool_calls?.some((tc) => isEndTurnToolCall(tc));
5498
+ const endsWithAgentEnd = msg.role === "assistant" && msg.tool_calls?.some(
5499
+ (tc) => isEndTurnToolCall(tc)
5500
+ );
5134
5501
  if (endsWithAgentEnd) {
5135
5502
  turns.unshift([...currentTurn]);
5136
5503
  currentTurn = [];
5137
- turnsFound++;
5138
- if (turnsFound >= maxTurns) {
5139
- break;
5140
- }
5141
5504
  continue;
5142
5505
  }
5143
5506
  const prev = conversationHistory[i - 1];
5144
5507
  if (msg.role === "user" && !isDevOverlay(msg)) {
5145
- if (prev && prev.role === "assistant" && !prev.tool_calls?.some((tc) => isEndTurnToolCall(tc))) {
5508
+ if (prev && prev.role === "assistant" && !prev.tool_calls?.some(
5509
+ (tc) => isEndTurnToolCall(tc)
5510
+ )) {
5146
5511
  const hasNonOverlay = currentTurn.some((m) => m.role !== "user" || !isDevOverlay(m));
5147
5512
  if (hasNonOverlay) {
5148
5513
  turns.unshift([...currentTurn]);
@@ -5154,8 +5519,76 @@ function createApiContextWindow(fullHistory, maxTurns) {
5154
5519
  if (currentTurn.length > 0) {
5155
5520
  turns.unshift(currentTurn);
5156
5521
  }
5157
- const finalContext = systemMessages.concat(turns.flat());
5158
- return finalContext;
5522
+ return turns;
5523
+ }
5524
+ var DEFAULT_TOKEN_BUDGET = 6e4;
5525
+ var DEFAULT_COMPRESS_THRESHOLD = 0.7;
5526
+ var DEFAULT_KEEP_RECENT_TURNS = 8;
5527
+ function readContextOptionInt(envKey, fallback) {
5528
+ const raw = (process.env[envKey] ?? "").trim();
5529
+ if (!raw) return fallback;
5530
+ const n = Number(raw);
5531
+ return Number.isFinite(n) && n > 0 ? Math.floor(n) : fallback;
5532
+ }
5533
+ function readContextOptionFloat(envKey, fallback) {
5534
+ const raw = (process.env[envKey] ?? "").trim();
5535
+ if (!raw) return fallback;
5536
+ const n = Number(raw);
5537
+ return Number.isFinite(n) && n > 0 && n <= 1 ? n : fallback;
5538
+ }
5539
+ function buildContextMessages(systemMessages, anchor, pendingRaw, recentFlat) {
5540
+ const anchorMsg = anchor ? [anchorToSystemMessage(anchor)] : [];
5541
+ return [...systemMessages, ...anchorMsg, ...pendingRaw, ...recentFlat];
5542
+ }
5543
+ async function createApiContextWindow(fullHistory, currentAnchor, compressedTurnSliceCount, llmService, userContext, options) {
5544
+ if (!fullHistory.length) {
5545
+ return {
5546
+ messages: [],
5547
+ newAnchor: currentAnchor,
5548
+ newCompressedTurnSliceCount: compressedTurnSliceCount
5549
+ };
5550
+ }
5551
+ const tokenBudget = options?.tokenBudget ?? readContextOptionInt("BLUMA_CONTEXT_TOKEN_BUDGET", DEFAULT_TOKEN_BUDGET);
5552
+ const compressThreshold = options?.compressThreshold ?? readContextOptionFloat("BLUMA_COMPRESS_THRESHOLD", DEFAULT_COMPRESS_THRESHOLD);
5553
+ const keepRecentTurns = options?.keepRecentTurns ?? readContextOptionInt("BLUMA_KEEP_RECENT_TURNS", DEFAULT_KEEP_RECENT_TURNS);
5554
+ const systemMessages = [];
5555
+ let historyStartIndex = 0;
5556
+ while (historyStartIndex < fullHistory.length && fullHistory[historyStartIndex].role === "system") {
5557
+ systemMessages.push(fullHistory[historyStartIndex]);
5558
+ historyStartIndex++;
5559
+ }
5560
+ const conversationHistory = fullHistory.slice(historyStartIndex);
5561
+ const turnSlices = partitionConversationIntoTurnSlices(conversationHistory);
5562
+ const n = turnSlices.length;
5563
+ const recentStart = Math.max(0, n - keepRecentTurns);
5564
+ let sliceCount = Math.min(Math.max(0, compressedTurnSliceCount), recentStart);
5565
+ let anchor = currentAnchor;
5566
+ const recentSlices = turnSlices.slice(recentStart);
5567
+ const recentFlat = recentSlices.flat();
5568
+ const thresholdTokens = tokenBudget * compressThreshold;
5569
+ let pendingSlices = turnSlices.slice(sliceCount, recentStart);
5570
+ let pendingFlat = pendingSlices.flat();
5571
+ let messages = buildContextMessages(systemMessages, anchor, pendingFlat, recentFlat);
5572
+ let tokens = countTokens(messages);
5573
+ while (tokens >= thresholdTokens && pendingSlices.length > 0) {
5574
+ anchor = await compressToAnchor(
5575
+ pendingFlat,
5576
+ anchor,
5577
+ llmService,
5578
+ userContext,
5579
+ pendingSlices.length
5580
+ );
5581
+ sliceCount = recentStart;
5582
+ pendingSlices = [];
5583
+ pendingFlat = [];
5584
+ messages = buildContextMessages(systemMessages, anchor, pendingFlat, recentFlat);
5585
+ tokens = countTokens(messages);
5586
+ }
5587
+ return {
5588
+ messages,
5589
+ newAnchor: anchor,
5590
+ newCompressedTurnSliceCount: sliceCount
5591
+ };
5159
5592
  }
5160
5593
 
5161
5594
  // src/app/agent/core/llm/llm.ts
@@ -5538,7 +5971,9 @@ var BluMaAgent = class {
5538
5971
  mcpClient;
5539
5972
  feedbackSystem;
5540
5973
  skillLoader;
5541
- maxContextTurns = 5;
5974
+ /** Memória comprimida persistida (turnos antigos) + cursor de fatias já absorvidas. */
5975
+ sessionAnchor = null;
5976
+ compressedTurnSliceCount = 0;
5542
5977
  isInterrupted = false;
5543
5978
  /** Mesmo turnId durante processTurn + todo o loop de tool_calls (FactorRouter). */
5544
5979
  activeTurnContext = null;
@@ -5562,50 +5997,74 @@ var BluMaAgent = class {
5562
5997
  this.eventBus.emit("backend_message", { type: "user_overlay", payload: clean, ts: data.ts || Date.now() });
5563
5998
  try {
5564
5999
  if (this.sessionFile) {
5565
- await saveSessionHistory(this.sessionFile, this.history);
6000
+ this.persistSession();
5566
6001
  } else {
5567
- const [sessionFile, _] = await loadOrcreateSession(this.sessionId);
6002
+ const [sessionFile, , mem] = await loadOrcreateSession(this.sessionId);
5568
6003
  this.sessionFile = sessionFile;
5569
- await saveSessionHistory(this.sessionFile, this.history);
6004
+ this.sessionAnchor = mem.historyAnchor;
6005
+ this.compressedTurnSliceCount = mem.compressedTurnSliceCount;
6006
+ this.persistSession();
5570
6007
  }
5571
6008
  } catch (e) {
5572
6009
  this.eventBus.emit("backend_message", { type: "error", message: `Falha ao salvar hist\xF3rico ap\xF3s user_overlay: ${e.message}` });
5573
6010
  }
5574
6011
  });
5575
6012
  }
6013
+ getMemorySnapshot() {
6014
+ return {
6015
+ historyAnchor: this.sessionAnchor,
6016
+ compressedTurnSliceCount: this.compressedTurnSliceCount
6017
+ };
6018
+ }
6019
+ /** Debounced: grava histórico + estado de compressão no mesmo ficheiro de sessão. */
6020
+ persistSession() {
6021
+ if (!this.sessionFile) return;
6022
+ void saveSessionHistory(this.sessionFile, this.history, this.getMemorySnapshot());
6023
+ }
5576
6024
  async initialize() {
5577
6025
  await this.mcpClient.nativeToolInvoker.initialize();
5578
6026
  await this.mcpClient.initialize();
5579
- const [sessionFile, history] = await loadOrcreateSession(this.sessionId);
6027
+ const [sessionFile, history, mem] = await loadOrcreateSession(this.sessionId);
5580
6028
  this.sessionFile = sessionFile;
5581
6029
  this.history = history;
6030
+ this.sessionAnchor = mem.historyAnchor;
6031
+ this.compressedTurnSliceCount = mem.compressedTurnSliceCount;
5582
6032
  initializeSkillContext({
5583
6033
  history: this.history,
5584
6034
  skillLoader: this.skillLoader
5585
6035
  });
5586
- if (this.history.length === 0) {
5587
- const dirs = this.skillLoader.getSkillsDirs();
5588
- this.eventBus.emit("backend_message", {
5589
- type: "info",
5590
- message: `Skills dirs \u2014 bundled: ${dirs.bundled} | project: ${dirs.project} | global: ${dirs.global}`
5591
- });
5592
- const availableSkills = this.skillLoader.listAvailable();
5593
- this.eventBus.emit("backend_message", {
5594
- type: "info",
5595
- message: `Skills loaded: ${availableSkills.length} \u2014 ${availableSkills.map((s) => `${s.name} (${s.source})`).join(", ") || "none"}`
5596
- });
5597
- if (this.skillLoader.hasConflicts()) {
5598
- for (const warning of this.skillLoader.formatConflictWarnings()) {
5599
- this.eventBus.emit("backend_message", {
5600
- type: "warning",
5601
- message: warning
5602
- });
5603
- }
6036
+ const dirs = this.skillLoader.getSkillsDirs();
6037
+ this.eventBus.emit("backend_message", {
6038
+ type: "info",
6039
+ message: `Skills dirs \u2014 bundled: ${dirs.bundled} | project: ${dirs.project} | global: ${dirs.global}`
6040
+ });
6041
+ const availableSkills = this.skillLoader.listAvailable();
6042
+ this.eventBus.emit("backend_message", {
6043
+ type: "info",
6044
+ message: `Skills loaded: ${availableSkills.length} \u2014 ${availableSkills.map((s) => `${s.name} (${s.source})`).join(", ") || "none"}`
6045
+ });
6046
+ if (this.skillLoader.hasConflicts()) {
6047
+ for (const warning of this.skillLoader.formatConflictWarnings()) {
6048
+ this.eventBus.emit("backend_message", {
6049
+ type: "warning",
6050
+ message: warning
6051
+ });
5604
6052
  }
5605
- const systemPrompt = getUnifiedSystemPrompt(availableSkills);
6053
+ }
6054
+ const systemPrompt = getUnifiedSystemPrompt(availableSkills);
6055
+ if (this.history.length === 0) {
5606
6056
  this.history.push({ role: "system", content: systemPrompt });
5607
- await saveSessionHistory(this.sessionFile, this.history);
6057
+ } else {
6058
+ const sysIdx = this.history.findIndex(
6059
+ (m) => m.role === "system" && typeof m.content === "string" && String(m.content).includes("<identity>")
6060
+ );
6061
+ if (sysIdx >= 0) {
6062
+ this.history[sysIdx] = { ...this.history[sysIdx], content: systemPrompt };
6063
+ } else {
6064
+ this.history.unshift({ role: "system", content: systemPrompt });
6065
+ }
5608
6066
  }
6067
+ this.persistSession();
5609
6068
  }
5610
6069
  getAvailableTools() {
5611
6070
  return this.mcpClient.getAvailableTools();
@@ -5634,11 +6093,13 @@ var BluMaAgent = class {
5634
6093
  let toolResultContent;
5635
6094
  let shouldContinueConversation = true;
5636
6095
  if (!this.sessionFile) {
5637
- const [sessionFile, history] = await loadOrcreateSession(this.sessionId);
6096
+ const [sessionFile, history, mem] = await loadOrcreateSession(this.sessionId);
5638
6097
  this.sessionFile = sessionFile;
5639
6098
  if (this.history.length === 0 && history.length > 0) {
5640
6099
  this.history = history;
5641
6100
  }
6101
+ this.sessionAnchor = mem.historyAnchor;
6102
+ this.compressedTurnSliceCount = mem.compressedTurnSliceCount;
5642
6103
  }
5643
6104
  if (decisionData.type === "user_decision_execute") {
5644
6105
  const toolName = toolCall.function.name;
@@ -5660,7 +6121,7 @@ var BluMaAgent = class {
5660
6121
  raw_arguments: toolCall.function.arguments
5661
6122
  });
5662
6123
  this.history.push({ role: "tool", tool_call_id: toolCall.id, content: toolResultContent });
5663
- await saveSessionHistory(this.sessionFile, this.history);
6124
+ this.persistSession();
5664
6125
  await this._continueConversation();
5665
6126
  return;
5666
6127
  }
@@ -5672,7 +6133,7 @@ var BluMaAgent = class {
5672
6133
  received: toolArgs
5673
6134
  });
5674
6135
  this.history.push({ role: "tool", tool_call_id: toolCall.id, content: toolResultContent });
5675
- await saveSessionHistory(this.sessionFile, this.history);
6136
+ this.persistSession();
5676
6137
  await this._continueConversation();
5677
6138
  return;
5678
6139
  }
@@ -5683,7 +6144,7 @@ var BluMaAgent = class {
5683
6144
  received: toolArgs
5684
6145
  });
5685
6146
  this.history.push({ role: "tool", tool_call_id: toolCall.id, content: toolResultContent });
5686
- await saveSessionHistory(this.sessionFile, this.history);
6147
+ this.persistSession();
5687
6148
  await this._continueConversation();
5688
6149
  return;
5689
6150
  }
@@ -5753,7 +6214,7 @@ var BluMaAgent = class {
5753
6214
  toolResultContent = "The system rejected this action. Verify that the command you are executing contributes to the tasks intent and try again.";
5754
6215
  }
5755
6216
  this.history.push({ role: "tool", tool_call_id: toolCall.id, content: toolResultContent });
5756
- await saveSessionHistory(this.sessionFile, this.history);
6217
+ this.persistSession();
5757
6218
  if (shouldContinueConversation && !this.isInterrupted) {
5758
6219
  await this._continueConversation();
5759
6220
  }
@@ -5803,7 +6264,16 @@ ${editData.error.display}`;
5803
6264
  this.eventBus.emit("backend_message", { type: "info", message: "Task Canceled." });
5804
6265
  return;
5805
6266
  }
5806
- const contextWindow = createApiContextWindow(this.history, this.maxContextTurns);
6267
+ const { messages: contextWindow, newAnchor, newCompressedTurnSliceCount } = await createApiContextWindow(
6268
+ this.history,
6269
+ this.sessionAnchor,
6270
+ this.compressedTurnSliceCount,
6271
+ this.llm,
6272
+ this.getLlmUserContext()
6273
+ );
6274
+ this.sessionAnchor = newAnchor;
6275
+ this.compressedTurnSliceCount = newCompressedTurnSliceCount;
6276
+ this.persistSession();
5807
6277
  const llmService = this.llm;
5808
6278
  if (typeof llmService.chatCompletionStream === "function") {
5809
6279
  await this._handleStreamingResponse(contextWindow);
@@ -5814,7 +6284,7 @@ ${editData.error.display}`;
5814
6284
  const errorMessage = error instanceof Error ? error.message : "An unknown API error occurred.";
5815
6285
  this.eventBus.emit("backend_message", { type: "error", message: errorMessage });
5816
6286
  } finally {
5817
- await saveSessionHistory(this.sessionFile, this.history);
6287
+ this.persistSession();
5818
6288
  }
5819
6289
  }
5820
6290
  async _handleStreamingResponse(contextWindow) {
@@ -6623,18 +7093,15 @@ var Agent = class {
6623
7093
 
6624
7094
  // src/app/ui/WorkingTimer.tsx
6625
7095
  import { useState as useState4, useEffect as useEffect4, memo as memo5 } from "react";
6626
- import { Box as Box7, Text as Text7 } from "ink";
6627
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
7096
+ import { Box as Box8, Text as Text8 } from "ink";
7097
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
6628
7098
  var WorkingTimerComponent = ({ eventBus, taskName, taskStatus }) => {
6629
- const [currentAction, setCurrentAction] = useState4("Thinking...");
7099
+ const [currentAction, setCurrentAction] = useState4("working");
6630
7100
  const [shinePosition, setShinePosition] = useState4(0);
6631
- const [dots, setDots] = useState4("");
6632
7101
  useEffect4(() => {
6633
7102
  if (!eventBus) return;
6634
7103
  const handleActionStatus = (data) => {
6635
- if (data.action) {
6636
- setCurrentAction(data.action);
6637
- }
7104
+ if (data.action) setCurrentAction(data.action);
6638
7105
  };
6639
7106
  eventBus.on("action_status", handleActionStatus);
6640
7107
  return () => {
@@ -6647,12 +7114,6 @@ var WorkingTimerComponent = ({ eventBus, taskName, taskStatus }) => {
6647
7114
  }, 120);
6648
7115
  return () => clearInterval(shineTimer);
6649
7116
  }, []);
6650
- useEffect4(() => {
6651
- const dotsTimer = setInterval(() => {
6652
- setDots((prev) => prev.length >= 3 ? "" : prev + ".");
6653
- }, 500);
6654
- return () => clearInterval(dotsTimer);
6655
- }, []);
6656
7117
  const displayAction = taskStatus || currentAction;
6657
7118
  const renderShineText = (text) => {
6658
7119
  const chars = text.split("");
@@ -6661,29 +7122,28 @@ var WorkingTimerComponent = ({ eventBus, taskName, taskStatus }) => {
6661
7122
  return chars.map((char, i) => {
6662
7123
  const distance = Math.abs(i - shineIdx);
6663
7124
  if (distance <= 1) {
6664
- return /* @__PURE__ */ jsx7(Text7, { color: "white", dimColor: true, children: char }, i);
6665
- } else {
6666
- return /* @__PURE__ */ jsx7(Text7, { color: "gray", dimColor: true, children: char }, i);
7125
+ return /* @__PURE__ */ jsx8(Text8, { color: BLUMA_TERMINAL.brandMagenta, children: char }, i);
6667
7126
  }
7127
+ return /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: char }, i);
6668
7128
  });
6669
7129
  };
6670
- return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", paddingX: 1, children: [
6671
- /* @__PURE__ */ jsx7(Box7, { children: renderShineText(displayAction) }),
6672
- taskName && /* @__PURE__ */ jsx7(Box7, { paddingLeft: 2, children: /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
6673
- "\u203A ",
7130
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginBottom: 1, children: [
7131
+ /* @__PURE__ */ jsx8(M3StatusStrip, { children: renderShineText(displayAction) }),
7132
+ taskName ? /* @__PURE__ */ jsx8(Box8, { paddingLeft: 2, children: /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
7133
+ /* @__PURE__ */ jsx8(Text8, { color: BLUMA_TERMINAL.brandBlue, children: "\u2514 " }),
6674
7134
  taskName
6675
- ] }) })
7135
+ ] }) }) : null
6676
7136
  ] });
6677
7137
  };
6678
7138
  var WorkingTimer = memo5(WorkingTimerComponent);
6679
7139
 
6680
7140
  // src/app/ui/components/ToolCallDisplay.tsx
6681
7141
  import { memo as memo6 } from "react";
6682
- import { Box as Box9 } from "ink";
7142
+ import { Box as Box10 } from "ink";
6683
7143
 
6684
7144
  // src/app/ui/components/toolCallRenderers.tsx
6685
- import { Box as Box8, Text as Text8 } from "ink";
6686
- import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
7145
+ import { Box as Box9, Text as Text9 } from "ink";
7146
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
6687
7147
  var parseArgs = (args) => {
6688
7148
  if (typeof args === "string") {
6689
7149
  try {
@@ -6705,9 +7165,9 @@ var getBasename = (filepath) => {
6705
7165
  var renderShellCommand2 = ({ args }) => {
6706
7166
  const parsed = parseArgs(args);
6707
7167
  const command = parsed.command || "[no command]";
6708
- return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
6709
- /* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "$" }),
6710
- /* @__PURE__ */ jsxs8(Text8, { color: "white", children: [
7168
+ return /* @__PURE__ */ jsxs9(Box9, { children: [
7169
+ /* @__PURE__ */ jsx9(Text9, { color: BLUMA_TERMINAL.brandBlue, bold: true, children: "$" }),
7170
+ /* @__PURE__ */ jsxs9(Text9, { children: [
6711
7171
  " ",
6712
7172
  truncate(command, 70)
6713
7173
  ] })
@@ -6716,9 +7176,9 @@ var renderShellCommand2 = ({ args }) => {
6716
7176
  var renderLsTool2 = ({ args }) => {
6717
7177
  const parsed = parseArgs(args);
6718
7178
  const path19 = parsed.directory_path || ".";
6719
- return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
6720
- /* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "ls" }),
6721
- /* @__PURE__ */ jsxs8(Text8, { children: [
7179
+ return /* @__PURE__ */ jsxs9(Box9, { children: [
7180
+ /* @__PURE__ */ jsx9(Text9, { color: BLUMA_TERMINAL.brandBlue, bold: true, children: "ls" }),
7181
+ /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
6722
7182
  " ",
6723
7183
  path19
6724
7184
  ] })
@@ -6727,9 +7187,9 @@ var renderLsTool2 = ({ args }) => {
6727
7187
  var renderCountFilesLines = ({ args }) => {
6728
7188
  const parsed = parseArgs(args);
6729
7189
  const filepath = parsed.filepath || "[no file]";
6730
- return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
6731
- /* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "wc -l" }),
6732
- /* @__PURE__ */ jsxs8(Text8, { children: [
7190
+ return /* @__PURE__ */ jsxs9(Box9, { children: [
7191
+ /* @__PURE__ */ jsx9(Text9, { color: BLUMA_TERMINAL.brandBlue, bold: true, children: "wc -l" }),
7192
+ /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
6733
7193
  " ",
6734
7194
  getBasename(filepath)
6735
7195
  ] })
@@ -6740,14 +7200,15 @@ var renderReadFileLines2 = ({ args }) => {
6740
7200
  const filepath = parsed.filepath || "[no file]";
6741
7201
  const start = parsed.start_line || 1;
6742
7202
  const end = parsed.end_line || start;
6743
- return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
6744
- /* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "cat" }),
6745
- /* @__PURE__ */ jsxs8(Text8, { children: [
7203
+ return /* @__PURE__ */ jsxs9(Box9, { children: [
7204
+ /* @__PURE__ */ jsx9(Text9, { color: BLUMA_TERMINAL.brandBlue, bold: true, children: "cat" }),
7205
+ /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
6746
7206
  " ",
6747
7207
  getBasename(filepath)
6748
7208
  ] }),
6749
- /* @__PURE__ */ jsxs8(Text8, { color: "white", children: [
6750
- " :",
7209
+ /* @__PURE__ */ jsxs9(Text9, { color: BLUMA_TERMINAL.brandMagenta, children: [
7210
+ " ",
7211
+ ":",
6751
7212
  start,
6752
7213
  "-",
6753
7214
  end
@@ -6758,9 +7219,9 @@ var renderBlumaNotebook = ({ args }) => {
6758
7219
  const parsed = parseArgs(args);
6759
7220
  const thought = parsed.thought || parsed.content?.thought || "[thinking...]";
6760
7221
  const truncated = truncate(thought, 100);
6761
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 1, children: [
6762
- /* @__PURE__ */ jsx8(Box8, { children: /* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "thinking" }) }),
6763
- /* @__PURE__ */ jsx8(Box8, { paddingLeft: 2, children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, italic: true, children: truncated }) })
7222
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
7223
+ /* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsx9(Text9, { color: BLUMA_TERMINAL.brandMagenta, bold: true, children: "thinking" }) }),
7224
+ /* @__PURE__ */ jsx9(Box9, { paddingLeft: 2, children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, italic: true, children: truncated }) })
6764
7225
  ] });
6765
7226
  };
6766
7227
  var renderEditToolCall = ({ args, preview }) => {
@@ -6768,20 +7229,20 @@ var renderEditToolCall = ({ args, preview }) => {
6768
7229
  const filepath = parsed.file_path || "[no file]";
6769
7230
  const oldStr = parsed.old_string || "";
6770
7231
  const newStr = parsed.new_string || "";
6771
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 1, children: [
6772
- /* @__PURE__ */ jsxs8(Box8, { children: [
6773
- /* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "edit" }),
6774
- /* @__PURE__ */ jsxs8(Text8, { children: [
7232
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
7233
+ /* @__PURE__ */ jsxs9(Box9, { children: [
7234
+ /* @__PURE__ */ jsx9(Text9, { color: BLUMA_TERMINAL.brandMagenta, bold: true, children: "edit" }),
7235
+ /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
6775
7236
  " ",
6776
7237
  getBasename(filepath)
6777
7238
  ] })
6778
7239
  ] }),
6779
- preview ? /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(SimpleDiff, { text: preview, maxHeight: 8 }) }) : /* @__PURE__ */ jsxs8(Box8, { paddingLeft: 2, flexDirection: "column", children: [
6780
- /* @__PURE__ */ jsxs8(Text8, { color: "red", dimColor: true, children: [
7240
+ preview ? /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(SimpleDiff, { text: preview, maxHeight: 8 }) }) : /* @__PURE__ */ jsxs9(Box9, { paddingLeft: 2, flexDirection: "column", children: [
7241
+ /* @__PURE__ */ jsxs9(Text9, { color: "red", dimColor: true, children: [
6781
7242
  "- ",
6782
7243
  truncate(oldStr, 50)
6783
7244
  ] }),
6784
- /* @__PURE__ */ jsxs8(Text8, { color: "green", bold: true, children: [
7245
+ /* @__PURE__ */ jsxs9(Text9, { color: "green", bold: true, children: [
6785
7246
  "+ ",
6786
7247
  truncate(newStr, 50)
6787
7248
  ] })
@@ -6797,36 +7258,40 @@ var renderTodoTool2 = ({ args }) => {
6797
7258
  const barWidth = 10;
6798
7259
  const filled = Math.round(progress / 100 * barWidth);
6799
7260
  const bar = "=".repeat(filled) + " ".repeat(barWidth - filled);
6800
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 1, children: [
6801
- /* @__PURE__ */ jsxs8(Box8, { children: [
6802
- /* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "todo" }),
6803
- /* @__PURE__ */ jsxs8(Text8, { children: [
6804
- " [",
7261
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
7262
+ /* @__PURE__ */ jsxs9(Box9, { flexDirection: "row", flexWrap: "wrap", children: [
7263
+ /* @__PURE__ */ jsx9(Text9, { color: BLUMA_TERMINAL.brandBlue, bold: true, children: pending }),
7264
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: " open \xB7 " }),
7265
+ /* @__PURE__ */ jsx9(Text9, { color: BLUMA_TERMINAL.brandMagenta, bold: true, children: completed }),
7266
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: " done \xB7 " }),
7267
+ /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
7268
+ "[",
6805
7269
  bar,
6806
7270
  "] ",
6807
7271
  progress,
6808
7272
  "%"
6809
7273
  ] })
6810
7274
  ] }),
6811
- tasks.length > 0 && /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingLeft: 2, children: [
6812
- tasks.slice(0, 15).map((task, i) => /* @__PURE__ */ jsxs8(
6813
- Text8,
6814
- {
6815
- color: task.isComplete === true ? "gray" : "white",
6816
- dimColor: task.isComplete === true,
6817
- strikethrough: task.isComplete === true,
6818
- children: [
6819
- task.isComplete === true ? "[x]" : "[ ]",
6820
- " ",
6821
- task.description
6822
- ]
6823
- },
6824
- i
6825
- )),
6826
- tasks.length > 15 && /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
6827
- "... +",
7275
+ tasks.length > 0 && /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginTop: 1, paddingLeft: 1, children: [
7276
+ tasks.slice(0, 15).map((task, i) => {
7277
+ const done = task.isComplete === true;
7278
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "row", children: [
7279
+ /* @__PURE__ */ jsx9(Text9, { color: done ? BLUMA_TERMINAL.brandBlue : BLUMA_TERMINAL.brandMagenta, children: done ? "\u25A0 " : "\u25A1 " }),
7280
+ /* @__PURE__ */ jsx9(
7281
+ Text9,
7282
+ {
7283
+ dimColor: done,
7284
+ strikethrough: done,
7285
+ color: done ? BLUMA_TERMINAL.muted : void 0,
7286
+ children: task.description
7287
+ }
7288
+ )
7289
+ ] }, i);
7290
+ }),
7291
+ tasks.length > 15 && /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
7292
+ "\u2026 +",
6828
7293
  tasks.length - 15,
6829
- " more tasks"
7294
+ " tasks"
6830
7295
  ] })
6831
7296
  ] })
6832
7297
  ] });
@@ -6835,14 +7300,14 @@ var renderFindByName = ({ args }) => {
6835
7300
  const parsed = parseArgs(args);
6836
7301
  const pattern = parsed.pattern || "*";
6837
7302
  const dir = parsed.directory || ".";
6838
- return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
6839
- /* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "find" }),
6840
- /* @__PURE__ */ jsxs8(Text8, { color: "white", children: [
7303
+ return /* @__PURE__ */ jsxs9(Box9, { children: [
7304
+ /* @__PURE__ */ jsx9(Text9, { color: BLUMA_TERMINAL.brandBlue, bold: true, children: "find" }),
7305
+ /* @__PURE__ */ jsxs9(Text9, { color: BLUMA_TERMINAL.brandMagenta, children: [
6841
7306
  ' "',
6842
7307
  pattern,
6843
7308
  '"'
6844
7309
  ] }),
6845
- /* @__PURE__ */ jsxs8(Text8, { children: [
7310
+ /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
6846
7311
  " in ",
6847
7312
  dir
6848
7313
  ] })
@@ -6852,14 +7317,14 @@ var renderGrepSearch = ({ args }) => {
6852
7317
  const parsed = parseArgs(args);
6853
7318
  const query = parsed.query || "";
6854
7319
  const path19 = parsed.path || ".";
6855
- return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
6856
- /* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "grep" }),
6857
- /* @__PURE__ */ jsxs8(Text8, { color: "white", children: [
7320
+ return /* @__PURE__ */ jsxs9(Box9, { children: [
7321
+ /* @__PURE__ */ jsx9(Text9, { color: BLUMA_TERMINAL.brandBlue, bold: true, children: "grep" }),
7322
+ /* @__PURE__ */ jsxs9(Text9, { color: BLUMA_TERMINAL.brandMagenta, children: [
6858
7323
  ' "',
6859
7324
  truncate(query, 30),
6860
7325
  '"'
6861
7326
  ] }),
6862
- /* @__PURE__ */ jsxs8(Text8, { children: [
7327
+ /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
6863
7328
  " ",
6864
7329
  path19
6865
7330
  ] })
@@ -6868,9 +7333,9 @@ var renderGrepSearch = ({ args }) => {
6868
7333
  var renderViewFileOutline = ({ args }) => {
6869
7334
  const parsed = parseArgs(args);
6870
7335
  const filepath = parsed.file_path || "[no file]";
6871
- return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
6872
- /* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "outline" }),
6873
- /* @__PURE__ */ jsxs8(Text8, { children: [
7336
+ return /* @__PURE__ */ jsxs9(Box9, { children: [
7337
+ /* @__PURE__ */ jsx9(Text9, { color: BLUMA_TERMINAL.brandBlue, bold: true, children: "outline" }),
7338
+ /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
6874
7339
  " ",
6875
7340
  getBasename(filepath)
6876
7341
  ] })
@@ -6879,9 +7344,9 @@ var renderViewFileOutline = ({ args }) => {
6879
7344
  var renderCommandStatus = ({ args }) => {
6880
7345
  const parsed = parseArgs(args);
6881
7346
  const id = parsed.command_id || "[no id]";
6882
- return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
6883
- /* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "status" }),
6884
- /* @__PURE__ */ jsxs8(Text8, { children: [
7347
+ return /* @__PURE__ */ jsxs9(Box9, { children: [
7348
+ /* @__PURE__ */ jsx9(Text9, { color: BLUMA_TERMINAL.brandBlue, bold: true, children: "status" }),
7349
+ /* @__PURE__ */ jsxs9(Text9, { color: BLUMA_TERMINAL.brandMagenta, children: [
6885
7350
  " #",
6886
7351
  id
6887
7352
  ] })
@@ -6893,36 +7358,36 @@ var renderTaskBoundary = ({ args }) => {
6893
7358
  const mode = parsed.mode || "EXECUTION";
6894
7359
  const status = parsed.task_status || "";
6895
7360
  const modeColors = {
6896
- PLANNING: "blue",
6897
- EXECUTION: "green",
6898
- VERIFICATION: "yellow"
7361
+ PLANNING: BLUMA_TERMINAL.brandBlue,
7362
+ EXECUTION: BLUMA_TERMINAL.brandBlue,
7363
+ VERIFICATION: BLUMA_TERMINAL.brandMagenta
6899
7364
  };
6900
7365
  const modeSymbols = {
6901
7366
  PLANNING: "P",
6902
7367
  EXECUTION: "E",
6903
7368
  VERIFICATION: "V"
6904
7369
  };
6905
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 1, children: [
6906
- /* @__PURE__ */ jsxs8(Box8, { children: [
6907
- /* @__PURE__ */ jsxs8(Text8, { color: modeColors[mode] || "gray", bold: true, children: [
7370
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
7371
+ /* @__PURE__ */ jsxs9(Box9, { children: [
7372
+ /* @__PURE__ */ jsxs9(Text9, { color: modeColors[mode] || BLUMA_TERMINAL.muted, bold: true, children: [
6908
7373
  "[",
6909
7374
  modeSymbols[mode] || "?",
6910
7375
  "]"
6911
7376
  ] }),
6912
- /* @__PURE__ */ jsxs8(Text8, { children: [
7377
+ /* @__PURE__ */ jsxs9(Text9, { children: [
6913
7378
  " ",
6914
7379
  name
6915
7380
  ] })
6916
7381
  ] }),
6917
- status && /* @__PURE__ */ jsx8(Box8, { paddingLeft: 4, children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: truncate(status, 60) }) })
7382
+ status && /* @__PURE__ */ jsx9(Box9, { paddingLeft: 4, children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: truncate(status, 60) }) })
6918
7383
  ] });
6919
7384
  };
6920
7385
  var renderSearchWeb = ({ args }) => {
6921
7386
  const parsed = parseArgs(args);
6922
7387
  const query = parsed.query || "[no query]";
6923
- return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
6924
- /* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "search" }),
6925
- /* @__PURE__ */ jsxs8(Text8, { color: "white", children: [
7388
+ return /* @__PURE__ */ jsxs9(Box9, { children: [
7389
+ /* @__PURE__ */ jsx9(Text9, { color: BLUMA_TERMINAL.brandBlue, bold: true, children: "search" }),
7390
+ /* @__PURE__ */ jsxs9(Text9, { color: BLUMA_TERMINAL.brandMagenta, children: [
6926
7391
  ' "',
6927
7392
  truncate(query, 40),
6928
7393
  '"'
@@ -6932,9 +7397,9 @@ var renderSearchWeb = ({ args }) => {
6932
7397
  var renderLoadSkill = ({ args }) => {
6933
7398
  const parsed = parseArgs(args);
6934
7399
  const skillName = parsed.skill_name || "[no skill]";
6935
- return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
6936
- /* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: "load skill" }),
6937
- /* @__PURE__ */ jsxs8(Text8, { color: "white", children: [
7400
+ return /* @__PURE__ */ jsxs9(Box9, { children: [
7401
+ /* @__PURE__ */ jsx9(Text9, { color: BLUMA_TERMINAL.brandBlue, bold: true, children: "load skill" }),
7402
+ /* @__PURE__ */ jsxs9(Text9, { color: BLUMA_TERMINAL.brandMagenta, children: [
6938
7403
  " ",
6939
7404
  skillName
6940
7405
  ] })
@@ -6943,13 +7408,7 @@ var renderLoadSkill = ({ args }) => {
6943
7408
  var renderGeneric2 = ({ toolName, args }) => {
6944
7409
  const parsed = parseArgs(args);
6945
7410
  const keys = Object.keys(parsed).slice(0, 2);
6946
- return /* @__PURE__ */ jsxs8(Box8, { paddingX: 1, children: [
6947
- /* @__PURE__ */ jsx8(Text8, { color: "white", bold: true, children: toolName }),
6948
- keys.length > 0 && /* @__PURE__ */ jsxs8(Text8, { children: [
6949
- " ",
6950
- keys.map((k) => `${k}:${truncate(String(parsed[k]), 20)}`).join(" ")
6951
- ] })
6952
- ] });
7411
+ 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" }) });
6953
7412
  };
6954
7413
  var ToolRenderDisplay = {
6955
7414
  shell_command: renderShellCommand2,
@@ -6969,76 +7428,230 @@ var ToolRenderDisplay = {
6969
7428
  };
6970
7429
 
6971
7430
  // src/app/ui/components/ToolCallDisplay.tsx
6972
- import { jsx as jsx9 } from "react/jsx-runtime";
7431
+ import { jsx as jsx10 } from "react/jsx-runtime";
6973
7432
  var ToolCallDisplayComponent = ({ toolName, args, preview }) => {
6974
- if (toolName.includes("message")) {
7433
+ if (toolName.includes("message") || toolName.includes("task_boundary") || toolName === "todo") {
6975
7434
  return null;
6976
7435
  }
6977
7436
  const Renderer = ToolRenderDisplay[toolName] || ((props) => renderGeneric2({ ...props, toolName }));
6978
- return /* @__PURE__ */ jsx9(Box9, { marginBottom: 1, children: /* @__PURE__ */ jsx9(Renderer, { toolName, args, preview }) });
7437
+ return /* @__PURE__ */ jsx10(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", paddingLeft: 1, children: /* @__PURE__ */ jsx10(Renderer, { toolName, args, preview }) }) });
6979
7438
  };
6980
7439
  var ToolCallDisplay = memo6(ToolCallDisplayComponent);
6981
7440
 
6982
7441
  // src/app/ui/components/ToolResultDisplay.tsx
6983
- import { memo as memo8 } from "react";
6984
- import { Box as Box11, Text as Text10 } from "ink";
7442
+ import { memo as memo8, useEffect as useEffect5 } from "react";
7443
+ import { Box as Box12, Text as Text11 } from "ink";
6985
7444
 
6986
7445
  // src/app/ui/components/MarkdownRenderer.tsx
6987
- import { memo as memo7 } from "react";
6988
- import { Box as Box10, Text as Text9 } from "ink";
7446
+ import { cloneElement, isValidElement, memo as memo7 } from "react";
7447
+ import { Box as Box11, Text as Text10 } from "ink";
6989
7448
  import { marked } from "marked";
6990
7449
  import { highlight } from "cli-highlight";
6991
- import { Fragment as Fragment3, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
6992
- var COLORS = {
6993
- heading: "cyan",
6994
- code: "gray",
6995
- link: "blue",
6996
- quote: "gray",
6997
- listBullet: "gray"
6998
- };
6999
- function renderTokens(tokens) {
7450
+ import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
7451
+ marked.setOptions({ gfm: true });
7452
+ function styleInlineNodes(nodes, keyPrefix, style) {
7453
+ return nodes.map(
7454
+ (n, j) => isValidElement(n) ? cloneElement(n, {
7455
+ key: `${keyPrefix}-${j}`,
7456
+ ...style
7457
+ }) : n
7458
+ );
7459
+ }
7460
+ function headingColor(depth) {
7461
+ if (depth <= 1) return BLUMA_TERMINAL.heading1;
7462
+ if (depth === 2) return BLUMA_TERMINAL.heading2;
7463
+ return BLUMA_TERMINAL.headingDeep;
7464
+ }
7465
+ function bulletGlyph(ordered, index, start, task, checked, depth) {
7466
+ if (task) return checked ? "[x]" : "[ ]";
7467
+ if (ordered) {
7468
+ const n = typeof start === "number" ? start + index : index + 1;
7469
+ return `${n}.`;
7470
+ }
7471
+ return depth === 0 ? "\xB7" : "\u2514";
7472
+ }
7473
+ function walkInline(tokens, keyBase, opts = {}) {
7474
+ if (!tokens?.length) return [];
7475
+ const out = [];
7476
+ tokens.forEach((t, i) => {
7477
+ const k = `${keyBase}-i${i}`;
7478
+ switch (t.type) {
7479
+ case "text":
7480
+ out.push(
7481
+ /* @__PURE__ */ jsx11(
7482
+ Text10,
7483
+ {
7484
+ bold: opts.bold,
7485
+ italic: opts.italic,
7486
+ strikethrough: opts.strike,
7487
+ underline: opts.link,
7488
+ color: opts.link ? BLUMA_TERMINAL.link : opts.bold ? BLUMA_TERMINAL.brandBlue : void 0,
7489
+ children: t.text
7490
+ },
7491
+ k
7492
+ )
7493
+ );
7494
+ break;
7495
+ case "strong":
7496
+ out.push(...walkInline(t.tokens, k, { ...opts, bold: true }));
7497
+ break;
7498
+ case "em":
7499
+ out.push(...walkInline(t.tokens, k, { ...opts, italic: true }));
7500
+ break;
7501
+ case "codespan":
7502
+ out.push(
7503
+ /* @__PURE__ */ jsx11(
7504
+ Text10,
7505
+ {
7506
+ color: opts.link ? BLUMA_TERMINAL.link : BLUMA_TERMINAL.code,
7507
+ backgroundColor: opts.link ? void 0 : "black",
7508
+ underline: opts.link,
7509
+ children: opts.link ? t.text : `\xA0${t.text}\xA0`
7510
+ },
7511
+ k
7512
+ )
7513
+ );
7514
+ break;
7515
+ case "link": {
7516
+ const L = t;
7517
+ const inner = walkInline(L.tokens, k + "l", { ...opts, link: true });
7518
+ out.push(
7519
+ /* @__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)
7520
+ );
7521
+ break;
7522
+ }
7523
+ case "br":
7524
+ out.push(
7525
+ /* @__PURE__ */ jsx11(Text10, { dimColor: true, children: "\n" }, k)
7526
+ );
7527
+ break;
7528
+ case "escape":
7529
+ out.push(
7530
+ /* @__PURE__ */ jsx11(
7531
+ Text10,
7532
+ {
7533
+ bold: opts.bold,
7534
+ italic: opts.italic,
7535
+ underline: opts.link,
7536
+ color: opts.link ? BLUMA_TERMINAL.link : void 0,
7537
+ children: t.text
7538
+ },
7539
+ k
7540
+ )
7541
+ );
7542
+ break;
7543
+ case "del":
7544
+ out.push(...walkInline(t.tokens, k, { ...opts, strike: true }));
7545
+ break;
7546
+ case "image": {
7547
+ const Im = t;
7548
+ out.push(
7549
+ /* @__PURE__ */ jsxs10(Text10, { dimColor: true, italic: true, children: [
7550
+ "[",
7551
+ Im.text || "img",
7552
+ "]"
7553
+ ] }, k)
7554
+ );
7555
+ break;
7556
+ }
7557
+ default:
7558
+ if ("text" in t && typeof t.text === "string") {
7559
+ out.push(
7560
+ /* @__PURE__ */ jsx11(Text10, { bold: opts.bold, italic: opts.italic, children: t.text }, k)
7561
+ );
7562
+ }
7563
+ }
7564
+ });
7565
+ return out;
7566
+ }
7567
+ function renderParagraph(p, key) {
7568
+ const nodes = walkInline(p.tokens, key);
7569
+ return /* @__PURE__ */ jsx11(Box11, { marginBottom: 1, flexDirection: "row", flexWrap: "wrap", children: nodes.length > 0 ? nodes : /* @__PURE__ */ jsx11(Text10, { dimColor: true, children: p.text }) }, key);
7570
+ }
7571
+ function renderListItemBlocks(item, depth, keyBase) {
7572
+ if (!item.tokens?.length) {
7573
+ const nodes = walkInline(
7574
+ [{ type: "text", raw: item.text, text: item.text }],
7575
+ keyBase
7576
+ );
7577
+ return nodes.length ? [/* @__PURE__ */ jsx11(Box11, { flexDirection: "row", flexWrap: "wrap", children: nodes }, keyBase)] : [];
7578
+ }
7579
+ const blocks = [];
7580
+ item.tokens.forEach((sub, si) => {
7581
+ const sk = `${keyBase}-b${si}`;
7582
+ if (sub.type === "paragraph") {
7583
+ blocks.push(renderParagraph(sub, sk));
7584
+ } else if (sub.type === "list") {
7585
+ blocks.push(renderListBlock(sub, depth + 1, sk));
7586
+ } else if (sub.type === "text") {
7587
+ const n = walkInline([sub], sk);
7588
+ if (n.length) blocks.push(/* @__PURE__ */ jsx11(Box11, { flexDirection: "row", flexWrap: "wrap", children: n }, sk));
7589
+ }
7590
+ });
7591
+ return blocks;
7592
+ }
7593
+ function renderListBlock(list, depth, keyBase) {
7594
+ return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", marginBottom: 1, paddingLeft: depth > 0 ? 2 : 0, children: list.items.map((item, idx) => {
7595
+ const glyph = bulletGlyph(
7596
+ list.ordered,
7597
+ idx,
7598
+ list.start,
7599
+ item.task,
7600
+ item.checked,
7601
+ depth
7602
+ );
7603
+ const gColor = depth === 0 ? BLUMA_TERMINAL.listBullet : BLUMA_TERMINAL.listBulletSub;
7604
+ const body = renderListItemBlocks(item, depth, `${keyBase}-it${idx}`);
7605
+ return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "row", alignItems: "flex-start", marginBottom: 0, children: [
7606
+ /* @__PURE__ */ jsx11(Text10, { color: gColor, children: (item.task ? glyph : `${glyph} `).padEnd(item.task ? 4 : 3, " ") }),
7607
+ /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", flexGrow: 1, children: body.length > 0 ? body : /* @__PURE__ */ jsx11(Text10, { dimColor: true, children: item.text }) })
7608
+ ] }, `${keyBase}-row-${idx}`);
7609
+ }) }, keyBase);
7610
+ }
7611
+ function renderBlockquote(q, key) {
7612
+ const inner = q.tokens && q.tokens.length > 0 ? renderBlockTokens(q.tokens, `${key}-inner`) : /* @__PURE__ */ jsx11(Text10, { dimColor: true, italic: true, children: q.text });
7613
+ return /* @__PURE__ */ jsxs10(
7614
+ Box11,
7615
+ {
7616
+ flexDirection: "row",
7617
+ marginBottom: 1,
7618
+ borderStyle: "single",
7619
+ borderColor: BLUMA_TERMINAL.brandMagenta,
7620
+ paddingLeft: 1,
7621
+ paddingY: 0,
7622
+ children: [
7623
+ /* @__PURE__ */ jsx11(Text10, { color: BLUMA_TERMINAL.brandMagenta, children: "\u2502 " }),
7624
+ /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", flexGrow: 1, children: inner })
7625
+ ]
7626
+ },
7627
+ key
7628
+ );
7629
+ }
7630
+ function renderBlockTokens(tokens, keyRoot) {
7000
7631
  const elements = [];
7001
7632
  for (let i = 0; i < tokens.length; i++) {
7002
7633
  const token = tokens[i];
7003
- const key = `token-${i}`;
7634
+ const key = `${keyRoot}-${i}`;
7004
7635
  switch (token.type) {
7005
7636
  case "heading": {
7006
- const heading = token;
7007
- const prefix = "#".repeat(heading.depth);
7637
+ const h = token;
7638
+ const color = headingColor(h.depth);
7639
+ const prefix = h.depth <= 2 ? `${"#".repeat(h.depth)} ` : "";
7640
+ const inner = walkInline(h.tokens, `${key}-h`);
7008
7641
  elements.push(
7009
- /* @__PURE__ */ jsx10(Box10, { marginBottom: 1, children: /* @__PURE__ */ jsxs9(Text9, { color: COLORS.heading, bold: true, children: [
7010
- prefix,
7011
- " ",
7012
- heading.text
7013
- ] }) }, key)
7642
+ /* @__PURE__ */ jsxs10(Box11, { marginBottom: 1, flexDirection: "row", flexWrap: "wrap", alignItems: "flex-start", children: [
7643
+ /* @__PURE__ */ jsx11(Text10, { color, bold: true, children: prefix }),
7644
+ inner.length > 0 ? inner : /* @__PURE__ */ jsx11(Text10, { color, bold: true, children: h.text })
7645
+ ] }, key)
7014
7646
  );
7015
7647
  break;
7016
7648
  }
7017
- case "paragraph": {
7018
- const para = token;
7019
- elements.push(
7020
- /* @__PURE__ */ jsx10(Box10, { marginBottom: 1, children: renderInline(para.text) }, key)
7021
- );
7649
+ case "paragraph":
7650
+ elements.push(renderParagraph(token, key));
7022
7651
  break;
7023
- }
7024
- case "list": {
7025
- const list = token;
7026
- list.items.forEach((item, idx) => {
7027
- const bullet = list.ordered ? `${idx + 1}.` : "\u2022";
7028
- const text = item.tokens?.filter((t) => t.type === "text").map((t) => t.text).join("") || item.text;
7029
- elements.push(
7030
- /* @__PURE__ */ jsxs9(Box10, { paddingLeft: 1, children: [
7031
- /* @__PURE__ */ jsx10(Text9, { color: COLORS.listBullet, children: bullet }),
7032
- /* @__PURE__ */ jsxs9(Text9, { children: [
7033
- " ",
7034
- renderInline(text)
7035
- ] })
7036
- ] }, `${key}-item-${idx}`)
7037
- );
7038
- });
7039
- elements.push(/* @__PURE__ */ jsx10(Box10, { marginBottom: 1 }, `${key}-spacer`));
7652
+ case "list":
7653
+ elements.push(renderListBlock(token, 0, key));
7040
7654
  break;
7041
- }
7042
7655
  case "code": {
7043
7656
  const code = token;
7044
7657
  let highlighted;
@@ -7051,110 +7664,144 @@ function renderTokens(tokens) {
7051
7664
  highlighted = code.text;
7052
7665
  }
7053
7666
  elements.push(
7054
- /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", marginBottom: 1, children: [
7055
- code.lang && /* @__PURE__ */ jsx10(Text9, { dimColor: true, children: code.lang }),
7056
- /* @__PURE__ */ jsx10(Box10, { paddingLeft: 1, children: /* @__PURE__ */ jsx10(Text9, { children: highlighted }) })
7057
- ] }, key)
7667
+ /* @__PURE__ */ jsxs10(
7668
+ Box11,
7669
+ {
7670
+ flexDirection: "column",
7671
+ marginBottom: 1,
7672
+ borderStyle: "single",
7673
+ borderColor: BLUMA_TERMINAL.panelBorder,
7674
+ paddingX: 1,
7675
+ children: [
7676
+ code.lang ? /* @__PURE__ */ jsx11(Text10, { color: BLUMA_TERMINAL.codeLabel, dimColor: true, children: code.lang }) : null,
7677
+ /* @__PURE__ */ jsx11(Text10, { dimColor: true, children: highlighted })
7678
+ ]
7679
+ },
7680
+ key
7681
+ )
7058
7682
  );
7059
7683
  break;
7060
7684
  }
7061
- case "blockquote": {
7062
- const quote = token;
7063
- elements.push(
7064
- /* @__PURE__ */ jsxs9(Box10, { marginBottom: 1, children: [
7065
- /* @__PURE__ */ jsx10(Text9, { color: COLORS.quote, children: "\u2502 " }),
7066
- /* @__PURE__ */ jsx10(Text9, { dimColor: true, italic: true, children: quote.text })
7067
- ] }, key)
7068
- );
7685
+ case "blockquote":
7686
+ elements.push(renderBlockquote(token, key));
7069
7687
  break;
7070
- }
7071
7688
  case "table": {
7072
7689
  const table = token;
7073
7690
  elements.push(
7074
- /* @__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`)
7691
+ /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", marginBottom: 1, children: [
7692
+ /* @__PURE__ */ jsx11(Box11, { flexDirection: "row", children: table.header.map((cell, idx) => {
7693
+ const headerNodes = walkInline(cell.tokens, `${key}-h${idx}`);
7694
+ 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`)];
7695
+ return /* @__PURE__ */ jsx11(Box11, { paddingRight: 2, flexDirection: "row", flexWrap: "wrap", children: styled }, idx);
7696
+ }) }),
7697
+ table.rows.map((row, rowIdx) => /* @__PURE__ */ jsx11(Box11, { flexDirection: "row", children: row.map((cell, cellIdx) => {
7698
+ const cellNodes = walkInline(cell.tokens, `${key}-c${rowIdx}-${cellIdx}`);
7699
+ 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`)];
7700
+ return /* @__PURE__ */ jsx11(Box11, { paddingRight: 2, flexDirection: "row", flexWrap: "wrap", children: styled }, cellIdx);
7701
+ }) }, `${key}-r${rowIdx}`))
7702
+ ] }, key)
7075
7703
  );
7076
- table.rows.forEach((row, rowIdx) => {
7077
- elements.push(
7078
- /* @__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}`)
7079
- );
7080
- });
7081
- elements.push(/* @__PURE__ */ jsx10(Box10, { marginBottom: 1 }, `${key}-spacer`));
7082
7704
  break;
7083
7705
  }
7084
- case "hr": {
7706
+ case "hr":
7085
7707
  elements.push(
7086
- /* @__PURE__ */ jsx10(Box10, { marginBottom: 1, children: /* @__PURE__ */ jsx10(Text9, { dimColor: true, children: "\u2500".repeat(40) }) }, key)
7708
+ /* @__PURE__ */ jsx11(Box11, { marginY: 1, children: /* @__PURE__ */ jsx11(Text10, { dimColor: true, children: "\u2500".repeat(42) }) }, key)
7087
7709
  );
7088
7710
  break;
7089
- }
7090
- case "space": {
7711
+ case "space":
7091
7712
  break;
7092
- }
7093
- default: {
7713
+ default:
7094
7714
  if ("text" in token && typeof token.text === "string") {
7095
- elements.push(/* @__PURE__ */ jsx10(Text9, { children: token.text }, key));
7715
+ elements.push(
7716
+ /* @__PURE__ */ jsx11(Text10, { dimColor: true, children: token.text }, key)
7717
+ );
7096
7718
  }
7097
- }
7098
7719
  }
7099
7720
  }
7100
7721
  return elements;
7101
7722
  }
7102
- function renderInline(text) {
7103
- const parts = [];
7104
- let remaining = text;
7105
- let partIndex = 0;
7106
- const inlineRegex = /(\*\*(.+?)\*\*|\*(.+?)\*|`(.+?)`|\[(.+?)\]\((.+?)\))/;
7107
- while (remaining.length > 0) {
7108
- const match = remaining.match(inlineRegex);
7109
- if (!match) {
7110
- parts.push(/* @__PURE__ */ jsx10(Text9, { children: remaining }, `inline-${partIndex++}`));
7111
- break;
7112
- }
7113
- const matchIndex = match.index ?? 0;
7114
- if (matchIndex > 0) {
7115
- parts.push(
7116
- /* @__PURE__ */ jsx10(Text9, { children: remaining.slice(0, matchIndex) }, `inline-${partIndex++}`)
7117
- );
7118
- }
7119
- const fullMatch = match[0];
7120
- if (fullMatch.startsWith("**")) {
7121
- parts.push(
7122
- /* @__PURE__ */ jsx10(Text9, { bold: true, children: match[2] }, `inline-${partIndex++}`)
7123
- );
7124
- } else if (fullMatch.startsWith("*")) {
7125
- parts.push(
7126
- /* @__PURE__ */ jsx10(Text9, { italic: true, children: match[3] }, `inline-${partIndex++}`)
7127
- );
7128
- } else if (fullMatch.startsWith("`")) {
7129
- parts.push(
7130
- /* @__PURE__ */ jsxs9(Text9, { color: COLORS.code, children: [
7131
- "`",
7132
- match[4],
7133
- "`"
7134
- ] }, `inline-${partIndex++}`)
7135
- );
7136
- } else if (fullMatch.startsWith("[")) {
7137
- parts.push(
7138
- /* @__PURE__ */ jsx10(Text9, { color: COLORS.link, underline: true, children: match[5] }, `inline-${partIndex++}`)
7139
- );
7140
- }
7141
- remaining = remaining.slice(matchIndex + fullMatch.length);
7142
- }
7143
- return parts.length === 1 ? parts[0] : /* @__PURE__ */ jsx10(Fragment3, { children: parts });
7144
- }
7145
7723
  var MarkdownRendererComponent = ({ markdown }) => {
7146
7724
  if (!markdown || markdown.trim() === "") {
7147
7725
  return null;
7148
7726
  }
7149
7727
  const tokens = marked.lexer(markdown);
7150
- return /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", children: renderTokens(tokens) });
7728
+ return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", paddingRight: 1, children: renderBlockTokens(tokens, "md") });
7151
7729
  };
7152
7730
  var MarkdownRenderer = memo7(MarkdownRendererComponent);
7153
7731
 
7732
+ // src/app/ui/utils/expandablePreviewStore.ts
7733
+ var latest = null;
7734
+ function parseResult(result) {
7735
+ try {
7736
+ return JSON.parse(result);
7737
+ } catch {
7738
+ return null;
7739
+ }
7740
+ }
7741
+ function truncatedLineCount(text, maxVisible) {
7742
+ const allLines = text.split("\n").filter((l) => l.trim());
7743
+ if (allLines.length <= maxVisible) {
7744
+ return { truncated: 0, totalKept: allLines.length };
7745
+ }
7746
+ return { truncated: allLines.length - maxVisible, totalKept: maxVisible };
7747
+ }
7748
+ function refreshExpandableFromToolResult(toolName, result) {
7749
+ if (toolName.includes("task_boundary")) {
7750
+ return;
7751
+ }
7752
+ const parsed = parseResult(result);
7753
+ if (toolName.includes("read_file") && parsed && typeof parsed.content === "string") {
7754
+ const { truncated } = truncatedLineCount(parsed.content, TOOL_PREVIEW_MAX_LINES);
7755
+ if (truncated > 0) {
7756
+ const fp = typeof parsed.filepath === "string" ? parsed.filepath : "file";
7757
+ const sl = parsed.start_line;
7758
+ const el = parsed.end_line;
7759
+ const range = typeof sl === "number" && typeof el === "number" ? ` :${sl}-${el}` : "";
7760
+ latest = {
7761
+ fullText: parsed.content,
7762
+ title: `${fp}${range}`,
7763
+ toolName,
7764
+ linesHidden: truncated
7765
+ };
7766
+ } else {
7767
+ latest = null;
7768
+ }
7769
+ return;
7770
+ }
7771
+ if ((toolName.includes("shell_command") || toolName.includes("run_command") || toolName.includes("command_status")) && parsed) {
7772
+ const output = String(parsed.stdout || parsed.output || "");
7773
+ const stderr = String(parsed.stderr || "");
7774
+ const status = String(parsed.status || "");
7775
+ if (parsed.command_id && !output && !stderr && !status) {
7776
+ return;
7777
+ }
7778
+ if (status === "running") {
7779
+ return;
7780
+ }
7781
+ const blob = output || stderr;
7782
+ if (!blob) {
7783
+ return;
7784
+ }
7785
+ const { truncated } = truncatedLineCount(blob, TOOL_PREVIEW_MAX_LINES);
7786
+ if (truncated > 0) {
7787
+ latest = {
7788
+ fullText: blob,
7789
+ title: "shell output",
7790
+ toolName,
7791
+ linesHidden: truncated
7792
+ };
7793
+ } else {
7794
+ latest = null;
7795
+ }
7796
+ }
7797
+ }
7798
+ function peekLatestExpandable() {
7799
+ return latest;
7800
+ }
7801
+
7154
7802
  // src/app/ui/components/ToolResultDisplay.tsx
7155
- import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
7156
- var MAX_LINES = 8;
7157
- var parseResult = (result) => {
7803
+ import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
7804
+ var parseResult2 = (result) => {
7158
7805
  try {
7159
7806
  return JSON.parse(result);
7160
7807
  } catch {
@@ -7171,153 +7818,222 @@ var truncateLines = (text, max) => {
7171
7818
  truncated: allLines.length - max
7172
7819
  };
7173
7820
  };
7821
+ function TodoTaskLine({
7822
+ done,
7823
+ label
7824
+ }) {
7825
+ return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "row", children: [
7826
+ /* @__PURE__ */ jsx12(Text11, { color: done ? BLUMA_TERMINAL.brandBlue : BLUMA_TERMINAL.brandMagenta, children: done ? "\u25A0 " : "\u25A1 " }),
7827
+ /* @__PURE__ */ jsx12(Text11, { dimColor: done, strikethrough: done, color: done ? BLUMA_TERMINAL.muted : void 0, children: label })
7828
+ ] });
7829
+ }
7174
7830
  var ToolResultDisplayComponent = ({ toolName, result }) => {
7831
+ useEffect5(() => {
7832
+ refreshExpandableFromToolResult(toolName, result);
7833
+ }, [toolName, result]);
7175
7834
  if (toolName.includes("task_boundary")) {
7176
7835
  return null;
7177
7836
  }
7178
- const parsed = parseResult(result);
7837
+ const parsed = parseResult2(result);
7179
7838
  if (toolName.includes("message")) {
7180
- const body = parsed?.content?.body || parsed?.body || parsed?.message;
7839
+ const body = parsed?.content?.body ?? parsed?.body ?? parsed?.message;
7181
7840
  if (!body) return null;
7182
- return /* @__PURE__ */ jsx11(Box11, { paddingX: 1, marginBottom: 1, flexDirection: "column", children: /* @__PURE__ */ jsx11(MarkdownRenderer, { markdown: body }) });
7183
- }
7184
- if (toolName.includes("read_file") && parsed?.content) {
7185
- const { lines, truncated } = truncateLines(parsed.content, MAX_LINES);
7186
- return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", paddingLeft: 3, children: [
7187
- lines.map((line, i) => /* @__PURE__ */ jsx11(Text10, { dimColor: true, color: "gray", children: line.slice(0, 80) }, i)),
7188
- truncated > 0 && /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
7189
- "... +",
7841
+ return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsx12(Box12, { paddingLeft: 2, flexDirection: "column", children: /* @__PURE__ */ jsx12(MarkdownRenderer, { markdown: String(body) }) }) });
7842
+ }
7843
+ if (toolName.includes("read_file") && parsed && typeof parsed.content === "string") {
7844
+ const { lines, truncated } = truncateLines(parsed.content, TOOL_PREVIEW_MAX_LINES);
7845
+ return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", paddingLeft: 2, children: [
7846
+ lines.map((line, i) => /* @__PURE__ */ jsx12(Text11, { dimColor: true, children: line.slice(0, 120) }, i)),
7847
+ truncated > 0 ? /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
7848
+ "\u2026 +",
7190
7849
  truncated,
7191
- " lines"
7192
- ] })
7193
- ] });
7850
+ " lines \xB7 Ctrl+O expand"
7851
+ ] }) : null
7852
+ ] }) });
7194
7853
  }
7195
7854
  if ((toolName.includes("shell_command") || toolName.includes("run_command") || toolName.includes("command_status")) && parsed) {
7196
- const output = parsed.stdout || parsed.output || "";
7197
- const stderr = parsed.stderr || "";
7855
+ const output = String(parsed.stdout || parsed.output || "");
7856
+ const stderr = String(parsed.stderr || "");
7198
7857
  const exitCode = parsed.exit_code ?? parsed.exitCode ?? 0;
7199
- const status = parsed.status || "";
7858
+ const status = String(parsed.status || "");
7200
7859
  if (parsed.command_id && !output && !stderr && !status) {
7201
- return /* @__PURE__ */ jsx11(Box11, { paddingLeft: 3, children: /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
7860
+ return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsx12(Box12, { paddingLeft: 2, children: /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
7202
7861
  "started #",
7203
- parsed.command_id.slice(0, 8)
7204
- ] }) });
7862
+ String(parsed.command_id).slice(0, 8)
7863
+ ] }) }) });
7205
7864
  }
7206
7865
  if (status === "running") {
7207
- return /* @__PURE__ */ jsx11(Box11, { paddingLeft: 3, children: /* @__PURE__ */ jsx11(Text10, { dimColor: true, color: "yellow", children: "still running..." }) });
7866
+ 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" }) }) });
7208
7867
  }
7209
7868
  if (!output && !stderr) return null;
7210
- const { lines, truncated } = truncateLines(output || stderr, MAX_LINES);
7869
+ const { lines, truncated } = truncateLines(output || stderr, TOOL_PREVIEW_MAX_LINES);
7211
7870
  const isError = exitCode !== 0;
7212
7871
  const isSuccess = exitCode === 0 && status !== "running";
7213
- const textColor = isError ? "red" : isSuccess ? "green" : "gray";
7214
- return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", paddingLeft: 3, children: [
7215
- lines.map((line, i) => /* @__PURE__ */ jsx11(Text10, { dimColor: true, color: textColor, children: line.slice(0, 80) }, i)),
7216
- truncated > 0 && /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
7217
- "... +",
7872
+ const lineColor = isError ? BLUMA_TERMINAL.err : isSuccess ? BLUMA_TERMINAL.success : BLUMA_TERMINAL.muted;
7873
+ return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", paddingLeft: 2, children: [
7874
+ lines.map((line, i) => /* @__PURE__ */ jsx12(Text11, { dimColor: true, color: lineColor, children: line.slice(0, 120) }, i)),
7875
+ truncated > 0 ? /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
7876
+ "\u2026 +",
7218
7877
  truncated,
7219
- " lines"
7220
- ] }),
7221
- exitCode !== 0 && /* @__PURE__ */ jsxs10(Text10, { dimColor: true, color: "red", children: [
7222
- "exit code: ",
7878
+ " lines \xB7 Ctrl+O expand"
7879
+ ] }) : null,
7880
+ exitCode !== 0 ? /* @__PURE__ */ jsxs11(Text11, { dimColor: true, color: BLUMA_TERMINAL.err, children: [
7881
+ "exit ",
7223
7882
  exitCode
7224
- ] })
7225
- ] });
7883
+ ] }) : null
7884
+ ] }) });
7226
7885
  }
7227
7886
  if ((toolName.includes("grep") || toolName.includes("find_by_name")) && parsed) {
7228
7887
  const matches = parsed.matches || parsed.results || parsed.files || [];
7229
7888
  if (matches.length === 0) {
7230
- return /* @__PURE__ */ jsx11(Box11, { paddingLeft: 3, children: /* @__PURE__ */ jsx11(Text10, { dimColor: true, children: "no matches" }) });
7889
+ return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsx12(Box12, { paddingLeft: 2, children: /* @__PURE__ */ jsx12(Text11, { dimColor: true, children: "no matches" }) }) });
7231
7890
  }
7232
- return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", paddingLeft: 3, children: [
7233
- /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
7891
+ return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", paddingLeft: 2, children: [
7892
+ /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
7234
7893
  matches.length,
7235
7894
  " matches"
7236
7895
  ] }),
7237
- matches.slice(0, 5).map((m, i) => /* @__PURE__ */ jsxs10(Text10, { dimColor: true, color: "gray", children: [
7238
- m.file || m.path || m.name || m,
7239
- m.line ? `:${m.line}` : ""
7240
- ] }, i)),
7241
- matches.length > 5 && /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
7242
- "... +",
7243
- matches.length - 5,
7244
- " more"
7245
- ] })
7246
- ] });
7896
+ matches.slice(0, 5).map((m, i) => {
7897
+ const row = m;
7898
+ const path19 = row.file || row.path || row.name || m;
7899
+ const line = row.line;
7900
+ return /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
7901
+ String(path19),
7902
+ line != null ? `:${line}` : ""
7903
+ ] }, i);
7904
+ }),
7905
+ matches.length > 5 ? /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
7906
+ "\u2026 +",
7907
+ matches.length - 5
7908
+ ] }) : null
7909
+ ] }) });
7247
7910
  }
7248
7911
  if (toolName.includes("ls_tool") && parsed) {
7249
7912
  const entries = parsed.entries || parsed.files || [];
7250
7913
  if (entries.length === 0) return null;
7251
- return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", paddingLeft: 3, children: [
7252
- entries.slice(0, 6).map((e, i) => /* @__PURE__ */ jsxs10(Text10, { dimColor: true, color: e.isDirectory ? "blue" : "gray", children: [
7253
- e.isDirectory ? "d" : "f",
7254
- " ",
7255
- e.name || e
7256
- ] }, i)),
7257
- entries.length > 6 && /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
7258
- "... +",
7259
- entries.length - 6,
7260
- " more"
7261
- ] })
7262
- ] });
7914
+ return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", paddingLeft: 2, children: [
7915
+ entries.slice(0, 6).map((e, i) => {
7916
+ const row = e;
7917
+ const name = row.name ?? e;
7918
+ const isDir = Boolean(row.isDirectory);
7919
+ return /* @__PURE__ */ jsxs11(Text11, { dimColor: true, color: isDir ? BLUMA_TERMINAL.brandBlue : BLUMA_TERMINAL.muted, children: [
7920
+ isDir ? "d " : "f ",
7921
+ String(name)
7922
+ ] }, i);
7923
+ }),
7924
+ entries.length > 6 ? /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
7925
+ "\u2026 +",
7926
+ entries.length - 6
7927
+ ] }) : null
7928
+ ] }) });
7263
7929
  }
7264
7930
  if (toolName.includes("load_skill") && parsed) {
7265
7931
  if (!parsed.success) {
7266
- return /* @__PURE__ */ jsx11(Box11, { paddingLeft: 3, children: /* @__PURE__ */ jsxs10(Text10, { dimColor: true, color: "red", children: [
7267
- "\u2717 Skill not found: ",
7268
- parsed.message
7269
- ] }) });
7270
- }
7271
- return /* @__PURE__ */ jsxs10(Box11, { paddingLeft: 3, marginBottom: 2, children: [
7272
- /* @__PURE__ */ jsx11(Text10, { dimColor: true, color: "green", children: "Skill loaded: " }),
7273
- /* @__PURE__ */ jsx11(Text10, { color: "green", bold: true, children: parsed.skill_name }),
7274
- /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
7275
- " \u2014 ",
7276
- parsed.description?.slice(0, 50),
7277
- parsed.description?.length > 100 ? "..." : ""
7932
+ return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsx12(Box12, { paddingLeft: 2, children: /* @__PURE__ */ jsxs11(Text11, { dimColor: true, color: BLUMA_TERMINAL.err, children: [
7933
+ "Not found: ",
7934
+ String(parsed.message || "")
7935
+ ] }) }) });
7936
+ }
7937
+ const desc = parsed.description ? String(parsed.description) : "";
7938
+ return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs11(Box12, { paddingLeft: 2, flexDirection: "row", flexWrap: "wrap", children: [
7939
+ /* @__PURE__ */ jsx12(Text11, { color: BLUMA_TERMINAL.brandMagenta, bold: true, children: String(parsed.skill_name || "") }),
7940
+ /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
7941
+ " ",
7942
+ "\u2014 ",
7943
+ desc.slice(0, 80),
7944
+ desc.length > 80 ? "\u2026" : ""
7278
7945
  ] })
7279
- ] });
7946
+ ] }) });
7280
7947
  }
7281
7948
  if (toolName.includes("todo")) {
7282
- return null;
7949
+ if (parsed && Array.isArray(parsed.tasks)) {
7950
+ const tasks = parsed.tasks;
7951
+ const stats = parsed.stats;
7952
+ const msg = typeof parsed.message === "string" ? parsed.message : "";
7953
+ return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", paddingLeft: 2, children: [
7954
+ msg ? /* @__PURE__ */ jsx12(Box12, { marginBottom: 1, children: /* @__PURE__ */ jsx12(Text11, { dimColor: true, children: msg }) }) : null,
7955
+ stats && typeof stats.progress === "number" ? /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
7956
+ stats.completed ?? 0,
7957
+ "/",
7958
+ stats.total ?? tasks.length,
7959
+ " done \xB7 ",
7960
+ stats.progress,
7961
+ "%"
7962
+ ] }) : null,
7963
+ /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", marginTop: 1, children: [
7964
+ tasks.slice(0, 12).map((t, i) => {
7965
+ const done = t.status === "completed" || t.isComplete === true;
7966
+ const line = `#${t.id ?? i + 1} ${t.description ?? ""}`;
7967
+ return /* @__PURE__ */ jsx12(TodoTaskLine, { done, label: line }, i);
7968
+ }),
7969
+ tasks.length > 12 ? /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
7970
+ "\u2026 +",
7971
+ tasks.length - 12,
7972
+ " tasks"
7973
+ ] }) : null
7974
+ ] })
7975
+ ] }) });
7976
+ }
7977
+ const lines = result.split("\n").filter(Boolean);
7978
+ if (lines.length === 0) return null;
7979
+ 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)) }) });
7283
7980
  }
7284
7981
  if (toolName.includes("view_file_outline") && parsed) {
7285
7982
  const symbols = parsed.symbols || parsed.outline || [];
7286
7983
  if (symbols.length === 0) return null;
7287
- return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", paddingLeft: 3, children: [
7288
- symbols.slice(0, 5).map((s, i) => /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
7289
- s.kind || "sym",
7290
- " ",
7291
- s.name
7292
- ] }, i)),
7293
- symbols.length > 5 && /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
7294
- "... +",
7295
- symbols.length - 5,
7296
- " more"
7297
- ] })
7298
- ] });
7984
+ return /* @__PURE__ */ jsx12(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", paddingLeft: 2, children: [
7985
+ symbols.slice(0, 5).map((s, i) => {
7986
+ const row = s;
7987
+ return /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
7988
+ String(row.kind || "sym"),
7989
+ " ",
7990
+ String(row.name || "")
7991
+ ] }, i);
7992
+ }),
7993
+ symbols.length > 5 ? /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
7994
+ "\u2026 +",
7995
+ symbols.length - 5
7996
+ ] }) : null
7997
+ ] }) });
7299
7998
  }
7300
7999
  return null;
7301
8000
  };
7302
8001
  var ToolResultDisplay = memo8(ToolResultDisplayComponent);
7303
8002
 
8003
+ // src/app/ui/SessionInfoConnectingMCP.tsx
8004
+ import { Box as Box13, Text as Text12 } from "ink";
8005
+ import Spinner from "ink-spinner";
8006
+ import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
8007
+ var SessionInfoConnectingMCP = ({
8008
+ sessionId,
8009
+ workdir,
8010
+ statusMessage
8011
+ }) => /* @__PURE__ */ jsx13(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", paddingLeft: 1, children: [
8012
+ /* @__PURE__ */ jsxs12(Text12, { children: [
8013
+ /* @__PURE__ */ jsx13(Text12, { bold: true, color: BLUMA_TERMINAL.brandBlue, children: "session" }),
8014
+ /* @__PURE__ */ jsx13(Text12, { dimColor: true, children: " \xB7 " }),
8015
+ /* @__PURE__ */ jsx13(Text12, { color: BLUMA_TERMINAL.brandMagenta, children: sessionId.slice(0, 12) })
8016
+ ] }),
8017
+ /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
8018
+ /* @__PURE__ */ jsx13(Text12, { color: BLUMA_TERMINAL.brandMagenta, children: "\u2514 " }),
8019
+ workdir
8020
+ ] }),
8021
+ /* @__PURE__ */ jsxs12(Box13, { marginTop: 1, flexDirection: "row", flexWrap: "wrap", children: [
8022
+ /* @__PURE__ */ jsxs12(Text12, { color: BLUMA_TERMINAL.warn, children: [
8023
+ /* @__PURE__ */ jsx13(Spinner, { type: "dots" }),
8024
+ " "
8025
+ ] }),
8026
+ /* @__PURE__ */ jsx13(Text12, { dimColor: true, children: statusMessage || "Establishing MCP\u2026" })
8027
+ ] })
8028
+ ] }) });
8029
+ var SessionInfoConnectingMCP_default = SessionInfoConnectingMCP;
8030
+
7304
8031
  // src/app/ui/components/SlashCommands.tsx
7305
- import { Box as Box12, Text as Text11 } from "ink";
7306
- import { Fragment as Fragment4, jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
8032
+ import { Box as Box14, Text as Text13 } from "ink";
8033
+ import { Fragment as Fragment2, jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
7307
8034
  var SlashCommands = ({ input, setHistory, agentRef }) => {
7308
8035
  const [cmd, ...args] = input.slice(1).trim().split(/\s+/);
7309
- const outBox = (children) => /* @__PURE__ */ jsx12(
7310
- Box12,
7311
- {
7312
- borderStyle: "single",
7313
- borderColor: "gray",
7314
- paddingX: 2,
7315
- paddingY: 0,
7316
- marginBottom: 1,
7317
- flexDirection: "column",
7318
- children
7319
- }
7320
- );
8036
+ const outBox = (children) => /* @__PURE__ */ jsx14(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsx14(Box14, { paddingLeft: 1, flexDirection: "column", children }) });
7321
8037
  const render2 = () => {
7322
8038
  if (!cmd) {
7323
8039
  return null;
@@ -7325,11 +8041,11 @@ var SlashCommands = ({ input, setHistory, agentRef }) => {
7325
8041
  if (cmd === "help") {
7326
8042
  const cmds = getSlashCommands();
7327
8043
  return outBox(
7328
- /* @__PURE__ */ jsxs11(Fragment4, { children: [
7329
- /* @__PURE__ */ jsx12(Box12, { marginBottom: 1, children: /* @__PURE__ */ jsx12(Text11, { bold: true, color: "magenta", children: "Available Commands" }) }),
7330
- /* @__PURE__ */ jsx12(Box12, { flexDirection: "column", children: cmds.map((c, i) => /* @__PURE__ */ jsxs11(Box12, { children: [
7331
- /* @__PURE__ */ jsx12(Text11, { color: "cyan", children: c.name.padEnd(12) }),
7332
- /* @__PURE__ */ jsx12(Text11, { dimColor: true, children: c.description })
8044
+ /* @__PURE__ */ jsxs13(Fragment2, { children: [
8045
+ /* @__PURE__ */ jsx14(Box14, { marginBottom: 1, children: /* @__PURE__ */ jsx14(Text13, { bold: true, color: BLUMA_TERMINAL.brandMagenta, children: "Available Commands" }) }),
8046
+ /* @__PURE__ */ jsx14(Box14, { flexDirection: "column", children: cmds.map((c, i) => /* @__PURE__ */ jsxs13(Box14, { children: [
8047
+ /* @__PURE__ */ jsx14(Text13, { color: BLUMA_TERMINAL.brandBlue, children: c.name.padEnd(12) }),
8048
+ /* @__PURE__ */ jsx14(Text13, { dimColor: true, children: c.description })
7333
8049
  ] }, i)) })
7334
8050
  ] })
7335
8051
  );
@@ -7337,9 +8053,9 @@ var SlashCommands = ({ input, setHistory, agentRef }) => {
7337
8053
  if (cmd === "clear") {
7338
8054
  setHistory((prev) => prev.filter((item) => item.id === 0 || item.id === 1));
7339
8055
  return outBox(
7340
- /* @__PURE__ */ jsxs11(Box12, { children: [
7341
- /* @__PURE__ */ jsx12(Text11, { color: "green", children: "\u2713" }),
7342
- /* @__PURE__ */ jsx12(Text11, { dimColor: true, children: " History cleared" })
8056
+ /* @__PURE__ */ jsxs13(Box14, { children: [
8057
+ /* @__PURE__ */ jsx14(Text13, { color: "green", children: "[ok]" }),
8058
+ /* @__PURE__ */ jsx14(Text13, { dimColor: true, children: " History cleared" })
7343
8059
  ] })
7344
8060
  );
7345
8061
  }
@@ -7351,8 +8067,8 @@ var SlashCommands = ({ input, setHistory, agentRef }) => {
7351
8067
  setHistory((prev) => prev.concat({
7352
8068
  id: Date.now(),
7353
8069
  component: outBox(
7354
- /* @__PURE__ */ jsx12(Box12, { children: /* @__PURE__ */ jsxs11(Text11, { color: "red", children: [
7355
- "\u2716 Failed to execute /init: ",
8070
+ /* @__PURE__ */ jsx14(Box14, { children: /* @__PURE__ */ jsxs13(Text13, { color: "red", children: [
8071
+ "Failed to execute /init: ",
7356
8072
  e?.message || String(e)
7357
8073
  ] }) })
7358
8074
  )
@@ -7372,41 +8088,41 @@ var SlashCommands = ({ input, setHistory, agentRef }) => {
7372
8088
  const colType = 10;
7373
8089
  const colSource = 18;
7374
8090
  return outBox(
7375
- /* @__PURE__ */ jsxs11(Fragment4, { children: [
7376
- /* @__PURE__ */ jsxs11(Box12, { marginBottom: 1, children: [
7377
- /* @__PURE__ */ jsx12(Text11, { bold: true, color: "magenta", children: "MCP Tools" }),
7378
- /* @__PURE__ */ jsx12(Text11, { dimColor: true, children: " \u2022 " }),
7379
- /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
8091
+ /* @__PURE__ */ jsxs13(Fragment2, { children: [
8092
+ /* @__PURE__ */ jsxs13(Box14, { marginBottom: 1, children: [
8093
+ /* @__PURE__ */ jsx14(Text13, { bold: true, color: BLUMA_TERMINAL.brandMagenta, children: "MCP Tools" }),
8094
+ /* @__PURE__ */ jsx14(Text13, { dimColor: true, children: " \u2022 " }),
8095
+ /* @__PURE__ */ jsxs13(Text13, { dimColor: true, children: [
7380
8096
  tools.length,
7381
8097
  " total"
7382
8098
  ] }),
7383
- term && /* @__PURE__ */ jsxs11(Fragment4, { children: [
7384
- /* @__PURE__ */ jsx12(Text11, { dimColor: true, children: " \u2022 filter: " }),
7385
- /* @__PURE__ */ jsxs11(Text11, { color: "cyan", children: [
8099
+ term && /* @__PURE__ */ jsxs13(Fragment2, { children: [
8100
+ /* @__PURE__ */ jsx14(Text13, { dimColor: true, children: " \u2022 filter: " }),
8101
+ /* @__PURE__ */ jsxs13(Text13, { color: BLUMA_TERMINAL.brandBlue, children: [
7386
8102
  '"',
7387
8103
  term,
7388
8104
  '"'
7389
8105
  ] }),
7390
- /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
8106
+ /* @__PURE__ */ jsxs13(Text13, { dimColor: true, children: [
7391
8107
  " \u2022 showing: ",
7392
8108
  filtered.length
7393
8109
  ] })
7394
8110
  ] })
7395
8111
  ] }),
7396
- filtered.length === 0 ? /* @__PURE__ */ jsx12(Text11, { color: "yellow", children: "No MCP tools found" }) : /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", children: [
7397
- /* @__PURE__ */ jsx12(Box12, { children: /* @__PURE__ */ jsxs11(Text11, { color: "gray", children: [
8112
+ filtered.length === 0 ? /* @__PURE__ */ jsx14(Text13, { color: "yellow", children: "No MCP tools found" }) : /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", children: [
8113
+ /* @__PURE__ */ jsx14(Box14, { children: /* @__PURE__ */ jsxs13(Text13, { color: "gray", children: [
7398
8114
  pad("Name", colName),
7399
8115
  " \u2502 ",
7400
8116
  pad("Type", colType),
7401
8117
  " \u2502 ",
7402
8118
  pad("Source", colSource)
7403
8119
  ] }) }),
7404
- /* @__PURE__ */ jsx12(Text11, { color: "gray", children: "\u2500".repeat(colName + colType + colSource + 6) }),
8120
+ /* @__PURE__ */ jsx14(Text13, { color: "gray", children: "\u2500".repeat(colName + colType + colSource + 6) }),
7405
8121
  filtered.map((t, i) => {
7406
8122
  const name = t.function?.name || t.name || "tool";
7407
8123
  const type = t.function?.name ? "fn" : t.type || "tool";
7408
8124
  const source = t.source || t.provider || "mcp";
7409
- return /* @__PURE__ */ jsxs11(Text11, { color: "white", children: [
8125
+ return /* @__PURE__ */ jsxs13(Text13, { color: "white", children: [
7410
8126
  pad(name, colName),
7411
8127
  " \u2502 ",
7412
8128
  pad(String(type), colType),
@@ -7429,22 +8145,22 @@ var SlashCommands = ({ input, setHistory, agentRef }) => {
7429
8145
  const colType = 10;
7430
8146
  const colSource = 18;
7431
8147
  return outBox(
7432
- /* @__PURE__ */ jsxs11(Fragment4, { children: [
7433
- /* @__PURE__ */ jsx12(Text11, { color: "magenta", bold: true, children: "Native Tools" }),
7434
- /* @__PURE__ */ jsxs11(Text11, { color: "gray", children: [
8148
+ /* @__PURE__ */ jsxs13(Fragment2, { children: [
8149
+ /* @__PURE__ */ jsx14(Text13, { color: BLUMA_TERMINAL.brandMagenta, bold: true, children: "Native Tools" }),
8150
+ /* @__PURE__ */ jsxs13(Text13, { color: "gray", children: [
7435
8151
  "Total Native: ",
7436
8152
  tools.length,
7437
8153
  term ? ` | Filter: "${term}" | Showing: ${filtered.length}` : ""
7438
8154
  ] }),
7439
- filtered.length === 0 ? /* @__PURE__ */ jsx12(Text11, { color: "yellow", children: "No native tools to display." }) : /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", children: [
7440
- /* @__PURE__ */ jsxs11(Text11, { color: "gray", children: [
8155
+ filtered.length === 0 ? /* @__PURE__ */ jsx14(Text13, { color: "yellow", children: "No native tools to display." }) : /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", children: [
8156
+ /* @__PURE__ */ jsxs13(Text13, { color: "gray", children: [
7441
8157
  pad("Name", colName),
7442
8158
  " | ",
7443
8159
  pad("Type", colType),
7444
8160
  " | ",
7445
8161
  pad("Source", colSource)
7446
8162
  ] }),
7447
- /* @__PURE__ */ jsxs11(Text11, { color: "gray", children: [
8163
+ /* @__PURE__ */ jsxs13(Text13, { color: "gray", children: [
7448
8164
  "".padEnd(colName, "-"),
7449
8165
  "---",
7450
8166
  "".padEnd(colType, "-"),
@@ -7455,7 +8171,7 @@ var SlashCommands = ({ input, setHistory, agentRef }) => {
7455
8171
  const name = t.function?.name || t.name || "tool";
7456
8172
  const type = t.function?.name ? "fn" : t.type || "tool";
7457
8173
  const source = t.source || "native";
7458
- return /* @__PURE__ */ jsxs11(Text11, { color: "white", children: [
8174
+ return /* @__PURE__ */ jsxs13(Text13, { color: "white", children: [
7459
8175
  pad(name, colName),
7460
8176
  " | ",
7461
8177
  pad(String(type), colType),
@@ -7467,18 +8183,18 @@ var SlashCommands = ({ input, setHistory, agentRef }) => {
7467
8183
  ] })
7468
8184
  );
7469
8185
  }
7470
- return outBox(/* @__PURE__ */ jsxs11(Text11, { color: "red", children: [
8186
+ return outBox(/* @__PURE__ */ jsxs13(Text13, { color: "red", children: [
7471
8187
  "Command not recognized: /",
7472
8188
  cmd
7473
8189
  ] }));
7474
8190
  };
7475
- return /* @__PURE__ */ jsx12(Fragment4, { children: render2() });
8191
+ return /* @__PURE__ */ jsx14(Fragment2, { children: render2() });
7476
8192
  };
7477
8193
  var SlashCommands_default = SlashCommands;
7478
8194
 
7479
8195
  // src/app/agent/utils/update_check.ts
7480
8196
  import updateNotifier from "update-notifier";
7481
- import { fileURLToPath as fileURLToPath4 } from "url";
8197
+ import { fileURLToPath as fileURLToPath3 } from "url";
7482
8198
  import path18 from "path";
7483
8199
  import fs14 from "fs";
7484
8200
  var BLUMA_PACKAGE_NAME = "@nomad-e/bluma-cli";
@@ -7513,7 +8229,7 @@ async function checkForUpdates() {
7513
8229
  pkg = findBlumaPackageJson(path18.dirname(binPath));
7514
8230
  }
7515
8231
  if (!pkg) {
7516
- const __filename = fileURLToPath4(import.meta.url);
8232
+ const __filename = fileURLToPath3(import.meta.url);
7517
8233
  const __dirname2 = path18.dirname(__filename);
7518
8234
  pkg = findBlumaPackageJson(__dirname2);
7519
8235
  }
@@ -7542,86 +8258,88 @@ Run: npm i -g ${BLUMA_PACKAGE_NAME} to update.`;
7542
8258
  }
7543
8259
 
7544
8260
  // src/app/ui/components/UpdateNotice.tsx
7545
- import { Box as Box13, Text as Text12 } from "ink";
7546
- import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
8261
+ import { Box as Box15, Text as Text14 } from "ink";
8262
+ import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
7547
8263
  function parseUpdateMessage(msg) {
7548
8264
  const lines = msg.split(/\r?\n/).map((l) => l.trim());
7549
8265
  const first = lines[0] || "";
7550
8266
  const hintLine = lines.slice(1).join(" ") || "";
7551
8267
  const nameMatch = first.match(/Update available for\s+([^!]+)!/i);
7552
8268
  const versionMatch = first.match(/!\s*([^\s]+)\s*→\s*([^\s]+)/);
7553
- const name = nameMatch?.[1]?.trim();
7554
- const current = versionMatch?.[1]?.trim();
7555
- const latest = versionMatch?.[2]?.trim();
7556
8269
  return {
7557
- name,
7558
- current,
7559
- latest,
8270
+ name: nameMatch?.[1]?.trim(),
8271
+ current: versionMatch?.[1]?.trim(),
8272
+ latest: versionMatch?.[2]?.trim(),
7560
8273
  hint: hintLine || void 0
7561
8274
  };
7562
8275
  }
7563
8276
  var UpdateNotice = ({ message: message2 }) => {
7564
- const { name, current, latest, hint } = parseUpdateMessage(message2);
7565
- return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", marginBottom: 1, children: [
7566
- /* @__PURE__ */ jsx13(Text12, { color: "yellow", bold: true, children: "Update Available" }),
7567
- name && current && latest ? /* @__PURE__ */ jsx13(Text12, { color: "gray", children: `${name}: ${current} \u2192 ${latest}` }) : /* @__PURE__ */ jsx13(Text12, { color: "gray", children: message2 }),
7568
- hint ? /* @__PURE__ */ jsx13(Text12, { color: "gray", children: hint }) : null
7569
- ] });
8277
+ const { name, current, latest: latest2, hint } = parseUpdateMessage(message2);
8278
+ return /* @__PURE__ */ jsx15(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", paddingLeft: 2, children: [
8279
+ name && current && latest2 ? /* @__PURE__ */ jsx15(Text14, { color: BLUMA_TERMINAL.brandMagenta, bold: true, children: name }) : null,
8280
+ name && current && latest2 ? /* @__PURE__ */ jsxs14(Text14, { dimColor: true, children: [
8281
+ current,
8282
+ " \u2192 ",
8283
+ latest2
8284
+ ] }) : /* @__PURE__ */ jsx15(Text14, { dimColor: true, children: message2 }),
8285
+ hint ? /* @__PURE__ */ jsx15(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx15(Text14, { dimColor: true, children: hint }) }) : null
8286
+ ] }) });
7570
8287
  };
7571
8288
  var UpdateNotice_default = UpdateNotice;
7572
8289
 
7573
8290
  // src/app/ui/components/ErrorMessage.tsx
7574
- import { Box as Box14, Text as Text13 } from "ink";
7575
- import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
7576
- var ErrorMessage = ({ message: message2, details, hint }) => {
7577
- return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
7578
- /* @__PURE__ */ jsxs13(Box14, { children: [
7579
- /* @__PURE__ */ jsx14(Text13, { color: "red", children: "\u2717" }),
7580
- /* @__PURE__ */ jsx14(Text13, { color: "red", bold: true, children: " Error" })
7581
- ] }),
7582
- /* @__PURE__ */ jsx14(Box14, { paddingLeft: 2, children: /* @__PURE__ */ jsx14(Text13, { color: "red", children: message2 }) }),
7583
- details && /* @__PURE__ */ jsx14(Box14, { paddingLeft: 2, children: /* @__PURE__ */ jsx14(Text13, { dimColor: true, children: details }) }),
7584
- hint && /* @__PURE__ */ jsx14(Box14, { paddingLeft: 2, children: /* @__PURE__ */ jsxs13(Text13, { color: "gray", children: [
7585
- "hint: ",
7586
- hint
7587
- ] }) })
7588
- ] });
7589
- };
8291
+ import { Box as Box16, Text as Text15 } from "ink";
8292
+ import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
8293
+ var ErrorMessage = ({ message: message2, details, hint }) => /* @__PURE__ */ jsx16(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", paddingLeft: 2, children: [
8294
+ /* @__PURE__ */ jsx16(Text15, { color: BLUMA_TERMINAL.err, children: message2 }),
8295
+ details ? /* @__PURE__ */ jsx16(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx16(Text15, { dimColor: true, children: details }) }) : null,
8296
+ hint ? /* @__PURE__ */ jsx16(Box16, { marginTop: 1, children: /* @__PURE__ */ jsxs15(Text15, { dimColor: true, children: [
8297
+ "hint: ",
8298
+ hint
8299
+ ] }) }) : null
8300
+ ] }) });
7590
8301
  var ErrorMessage_default = ErrorMessage;
7591
8302
 
7592
8303
  // src/app/ui/components/ReasoningDisplay.tsx
7593
- import { memo as memo9, useState as useState5 } from "react";
7594
- import { Box as Box15, Text as Text14 } from "ink";
7595
- import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
8304
+ import { memo as memo9 } from "react";
8305
+ import { Box as Box17, Text as Text16 } from "ink";
8306
+ import { jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
7596
8307
  var ReasoningDisplayComponent = ({
7597
8308
  reasoning,
7598
8309
  collapsed = false
7599
8310
  }) => {
7600
- const [isExpanded, setIsExpanded] = useState5(!collapsed);
7601
8311
  if (!reasoning || reasoning.trim() === "") {
7602
8312
  return null;
7603
8313
  }
7604
- const maxLines = 5;
8314
+ const maxLines = collapsed ? 6 : 200;
7605
8315
  const lines = reasoning.split("\n");
7606
- const displayText = isExpanded ? reasoning : lines.slice(0, maxLines).join("\n") + (lines.length > maxLines ? "..." : "");
7607
- return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", paddingX: 1, marginBottom: 1, marginTop: 1, children: [
7608
- /* @__PURE__ */ jsx15(Box15, {}),
7609
- /* @__PURE__ */ jsx15(Box15, { paddingLeft: 2, flexDirection: "column", children: displayText.split("\n").map((line, i) => /* @__PURE__ */ jsx15(Text14, { color: "gray", dimColor: true, children: line }, i)) })
7610
- ] });
8316
+ const displayLines = lines.slice(0, maxLines);
8317
+ const truncated = lines.length > maxLines;
8318
+ return /* @__PURE__ */ jsx17(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", paddingLeft: 2, children: [
8319
+ displayLines.map((line, i) => /* @__PURE__ */ jsx17(Text16, { dimColor: true, children: line }, i)),
8320
+ truncated ? /* @__PURE__ */ jsxs16(Text16, { dimColor: true, children: [
8321
+ "\u2026 +",
8322
+ lines.length - maxLines,
8323
+ " lines"
8324
+ ] }) : null
8325
+ ] }) });
7611
8326
  };
7612
8327
  var ReasoningDisplay = memo9(ReasoningDisplayComponent);
7613
8328
 
7614
8329
  // src/app/ui/components/StreamingText.tsx
7615
- import { useState as useState6, useEffect as useEffect5, useRef as useRef4, memo as memo10 } from "react";
7616
- import { Box as Box16, Text as Text15 } from "ink";
7617
- import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
7618
- var StreamingTextComponent = ({ eventBus, onReasoningComplete }) => {
7619
- const [reasoning, setReasoning] = useState6("");
7620
- const [isStreaming, setIsStreaming] = useState6(false);
8330
+ import { useState as useState5, useEffect as useEffect6, useRef as useRef4, memo as memo10 } from "react";
8331
+ import { Box as Box18, Text as Text17 } from "ink";
8332
+ import { jsx as jsx18, jsxs as jsxs17 } from "react/jsx-runtime";
8333
+ var StreamingTextComponent = ({
8334
+ eventBus,
8335
+ onReasoningComplete
8336
+ }) => {
8337
+ const [reasoning, setReasoning] = useState5("");
8338
+ const [isStreaming, setIsStreaming] = useState5(false);
7621
8339
  const reasoningRef = useRef4("");
7622
8340
  const lastUpdateRef = useRef4(0);
7623
8341
  const pendingUpdateRef = useRef4(null);
7624
- useEffect5(() => {
8342
+ useEffect6(() => {
7625
8343
  const handleStart = () => {
7626
8344
  setReasoning("");
7627
8345
  reasoningRef.current = "";
@@ -7677,19 +8395,56 @@ var StreamingTextComponent = ({ eventBus, onReasoningComplete }) => {
7677
8395
  truncatedCount = lines.length - MAX_VISIBLE_LINES;
7678
8396
  displayLines = lines.slice(-MAX_VISIBLE_LINES);
7679
8397
  }
7680
- return /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", paddingX: 1, marginBottom: 1, marginTop: 1, children: [
7681
- truncatedCount > 0 && /* @__PURE__ */ jsxs15(Text15, { color: "gray", dimColor: true, children: [
7682
- "... (ocultando ",
8398
+ return /* @__PURE__ */ jsx18(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs17(Box18, { flexDirection: "column", paddingLeft: 2, children: [
8399
+ truncatedCount > 0 ? /* @__PURE__ */ jsxs17(Text17, { dimColor: true, children: [
8400
+ "\u2026 ",
7683
8401
  truncatedCount,
7684
- " linhas anteriores para performance) ..."
7685
- ] }),
7686
- /* @__PURE__ */ jsx16(Box16, { paddingLeft: 2, flexDirection: "column", children: displayLines.map((line, i) => /* @__PURE__ */ jsx16(Text15, { color: "gray", dimColor: true, children: line }, i)) })
7687
- ] });
8402
+ " lines above hidden"
8403
+ ] }) : null,
8404
+ displayLines.map((line, i) => /* @__PURE__ */ jsx18(Text17, { dimColor: true, children: line }, i))
8405
+ ] }) });
7688
8406
  };
7689
8407
  var StreamingText = memo10(StreamingTextComponent);
7690
8408
 
8409
+ // src/app/ui/components/ExpandedPreviewBlock.tsx
8410
+ import { memo as memo11 } from "react";
8411
+ import { Box as Box19, Text as Text18 } from "ink";
8412
+ import { jsx as jsx19, jsxs as jsxs18 } from "react/jsx-runtime";
8413
+ function ExpandedPreviewBlockComponent({ data }) {
8414
+ const cols = typeof process.stdout?.columns === "number" ? process.stdout.columns : 80;
8415
+ const rule = TERMINAL_RULE_CHAR.repeat(Math.max(8, cols));
8416
+ const lines = data.fullText.split("\n");
8417
+ const cap = EXPAND_OVERLAY_MAX_LINES;
8418
+ const shown = lines.slice(0, cap);
8419
+ const rest = lines.length - cap;
8420
+ return /* @__PURE__ */ jsxs18(ChatBlock, { marginBottom: 1, children: [
8421
+ /* @__PURE__ */ jsx19(Text18, { color: "white", children: rule }),
8422
+ /* @__PURE__ */ jsxs18(Box19, { flexDirection: "column", paddingLeft: 1, children: [
8423
+ /* @__PURE__ */ jsx19(Text18, { color: BLUMA_TERMINAL.brandMagenta, bold: true, children: "expand (Ctrl+O)" }),
8424
+ /* @__PURE__ */ jsx19(Text18, { dimColor: true, children: data.title }),
8425
+ /* @__PURE__ */ jsxs18(Text18, { dimColor: true, children: [
8426
+ "+",
8427
+ data.linesHidden,
8428
+ " lines were clipped in chat \xB7 below: up to ",
8429
+ cap,
8430
+ " lines \xB7 use read_file_lines before edit_tool"
8431
+ ] }),
8432
+ /* @__PURE__ */ jsxs18(Box19, { flexDirection: "column", marginTop: 1, children: [
8433
+ shown.map((line, i) => /* @__PURE__ */ jsx19(Text18, { dimColor: true, children: line.slice(0, 200) }, i)),
8434
+ rest > 0 ? /* @__PURE__ */ jsxs18(Text18, { dimColor: true, children: [
8435
+ "\u2026 +",
8436
+ rest,
8437
+ " more lines in this chunk"
8438
+ ] }) : null
8439
+ ] })
8440
+ ] }),
8441
+ /* @__PURE__ */ jsx19(Text18, { color: "white", children: rule })
8442
+ ] });
8443
+ }
8444
+ var ExpandedPreviewBlock = memo11(ExpandedPreviewBlockComponent);
8445
+
7691
8446
  // src/app/ui/App.tsx
7692
- import { jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
8447
+ import { jsx as jsx20, jsxs as jsxs19 } from "react/jsx-runtime";
7693
8448
  var SAFE_AUTO_APPROVE_TOOLS = [
7694
8449
  // Comunicação/UI
7695
8450
  "message",
@@ -7707,36 +8462,64 @@ var SAFE_AUTO_APPROVE_TOOLS = [
7707
8462
  ];
7708
8463
  var AppComponent = ({ eventBus, sessionId }) => {
7709
8464
  const agentInstance = useRef5(null);
7710
- const [history, setHistory] = useState7([]);
7711
- const [statusMessage, setStatusMessage] = useState7(
8465
+ const [history, setHistory] = useState6([]);
8466
+ const [statusMessage, setStatusMessage] = useState6(
7712
8467
  "Initializing agent..."
7713
8468
  );
7714
- const [toolsCount, setToolsCount] = useState7(null);
7715
- const [mcpStatus, setMcpStatus] = useState7(
8469
+ const [toolsCount, setToolsCount] = useState6(null);
8470
+ const [mcpStatus, setMcpStatus] = useState6(
7716
8471
  "connecting"
7717
8472
  );
7718
- const [isProcessing, setIsProcessing] = useState7(true);
7719
- const [pendingConfirmation, setPendingConfirmation] = useState7(
8473
+ const [isProcessing, setIsProcessing] = useState6(true);
8474
+ const [pendingConfirmation, setPendingConfirmation] = useState6(
7720
8475
  null
7721
8476
  );
7722
- const [confirmationPreview, setConfirmationPreview] = useState7(
8477
+ const [confirmationPreview, setConfirmationPreview] = useState6(
7723
8478
  null
7724
8479
  );
7725
- const [isInitAgentActive, setIsInitAgentActive] = useState7(false);
8480
+ const [isInitAgentActive, setIsInitAgentActive] = useState6(false);
7726
8481
  const alwaysAcceptList = useRef5([]);
7727
8482
  const workdir = process.cwd();
7728
8483
  const updateCheckRan = useRef5(false);
7729
- const sessionStartTime = useRef5(Date.now());
7730
- const [toolCallCount, setToolCallCount] = useState7(0);
8484
+ const turnStartedAtRef = useRef5(null);
8485
+ const appendExpandPreviewToHistory = useCallback2(() => {
8486
+ const p = peekLatestExpandable();
8487
+ setHistory((prev) => {
8488
+ const id = prev.length;
8489
+ if (!p) {
8490
+ return [
8491
+ ...prev,
8492
+ {
8493
+ id,
8494
+ component: /* @__PURE__ */ jsx20(ChatMeta, { children: "Ctrl+O: no truncated preview to expand" }, id)
8495
+ }
8496
+ ];
8497
+ }
8498
+ return [
8499
+ ...prev,
8500
+ {
8501
+ id,
8502
+ component: /* @__PURE__ */ jsx20(ExpandedPreviewBlock, { data: p }, id)
8503
+ }
8504
+ ];
8505
+ });
8506
+ }, []);
8507
+ useEffect7(() => {
8508
+ expandPreviewHotkeyBus.on("expand", appendExpandPreviewToHistory);
8509
+ return () => {
8510
+ expandPreviewHotkeyBus.off("expand", appendExpandPreviewToHistory);
8511
+ };
8512
+ }, [appendExpandPreviewToHistory]);
7731
8513
  const handleInterrupt = useCallback2(() => {
7732
8514
  if (!isProcessing) return;
7733
8515
  eventBus.emit("user_interrupt");
8516
+ turnStartedAtRef.current = null;
7734
8517
  setIsProcessing(false);
7735
8518
  setHistory((prev) => [
7736
8519
  ...prev,
7737
8520
  {
7738
8521
  id: prev.length,
7739
- component: /* @__PURE__ */ jsx17(Text16, { color: "yellow", children: "-- Task cancelled by dev. --" })
8522
+ component: /* @__PURE__ */ jsx20(ChatMeta, { children: "cancelled (Esc)" })
7740
8523
  }
7741
8524
  ]);
7742
8525
  }, [isProcessing, eventBus]);
@@ -7752,6 +8535,7 @@ var AppComponent = ({ eventBus, sessionId }) => {
7752
8535
  if (cmd === "init") {
7753
8536
  setIsInitAgentActive(true);
7754
8537
  setIsProcessing(true);
8538
+ turnStartedAtRef.current = Date.now();
7755
8539
  } else {
7756
8540
  setIsProcessing(false);
7757
8541
  setIsInitAgentActive(false);
@@ -7760,11 +8544,11 @@ var AppComponent = ({ eventBus, sessionId }) => {
7760
8544
  ...prev,
7761
8545
  {
7762
8546
  id: prev.length,
7763
- component: /* @__PURE__ */ jsx17(Box17, { marginBottom: 1, children: /* @__PURE__ */ jsx17(Text16, { color: "white", dimColor: true, children: text }) })
8547
+ component: /* @__PURE__ */ jsx20(ChatUserMessage, { children: /* @__PURE__ */ jsx20(Text19, { dimColor: true, children: text }) })
7764
8548
  },
7765
8549
  {
7766
8550
  id: prev.length + 1,
7767
- component: /* @__PURE__ */ jsx17(
8551
+ component: /* @__PURE__ */ jsx20(
7768
8552
  SlashCommands_default,
7769
8553
  {
7770
8554
  input: text,
@@ -7783,17 +8567,15 @@ var AppComponent = ({ eventBus, sessionId }) => {
7783
8567
  return;
7784
8568
  }
7785
8569
  setIsProcessing(true);
8570
+ turnStartedAtRef.current = Date.now();
7786
8571
  setHistory((prev) => [
7787
8572
  ...prev,
7788
8573
  {
7789
8574
  id: prev.length,
7790
- component: /* @__PURE__ */ jsxs16(Box17, { marginBottom: 1, children: [
7791
- /* @__PURE__ */ jsx17(Text16, { color: "white", bold: true, children: "$ " }),
7792
- /* @__PURE__ */ jsxs16(Text16, { color: "white", children: [
7793
- "!",
7794
- command
7795
- ] })
7796
- ] })
8575
+ component: /* @__PURE__ */ jsx20(ChatUserMessage, { children: /* @__PURE__ */ jsxs19(Text19, { bold: true, color: "white", children: [
8576
+ "$ !",
8577
+ command
8578
+ ] }) })
7797
8579
  }
7798
8580
  ]);
7799
8581
  Promise.resolve().then(() => (init_async_command(), async_command_exports)).then(async ({ runCommandAsync: runCommandAsync2 }) => {
@@ -7807,11 +8589,12 @@ Command ID: ${result.command_id}
7807
8589
  Please use command_status to check the result and report back to the user.`;
7808
8590
  agentInstance.current?.processTurn({ content: contextMessage });
7809
8591
  } else {
8592
+ turnStartedAtRef.current = null;
7810
8593
  setHistory((prev) => [
7811
8594
  ...prev,
7812
8595
  {
7813
8596
  id: prev.length,
7814
- component: /* @__PURE__ */ jsxs16(Text16, { color: "red", children: [
8597
+ component: /* @__PURE__ */ jsxs19(Text19, { color: "red", children: [
7815
8598
  "Failed to execute: ",
7816
8599
  result.error || result.message
7817
8600
  ] })
@@ -7820,11 +8603,12 @@ Please use command_status to check the result and report back to the user.`;
7820
8603
  setIsProcessing(false);
7821
8604
  }
7822
8605
  } catch (err) {
8606
+ turnStartedAtRef.current = null;
7823
8607
  setHistory((prev) => [
7824
8608
  ...prev,
7825
8609
  {
7826
8610
  id: prev.length,
7827
- component: /* @__PURE__ */ jsxs16(Text16, { color: "red", children: [
8611
+ component: /* @__PURE__ */ jsxs19(Text19, { color: "red", children: [
7828
8612
  "Error: ",
7829
8613
  err.message
7830
8614
  ] })
@@ -7836,21 +8620,13 @@ Please use command_status to check the result and report back to the user.`;
7836
8620
  return;
7837
8621
  }
7838
8622
  setIsProcessing(true);
8623
+ turnStartedAtRef.current = Date.now();
7839
8624
  const displayText = text.length > 1e4 ? text.substring(0, 1e4) + "..." : text;
7840
8625
  setHistory((prev) => [
7841
8626
  ...prev,
7842
8627
  {
7843
8628
  id: prev.length,
7844
- component: (
7845
- // Uma única Box para o espaçamento
7846
- /* @__PURE__ */ jsx17(Box17, { marginBottom: 1, children: /* @__PURE__ */ jsxs16(Text16, { color: "white", dimColor: true, children: [
7847
- /* @__PURE__ */ jsxs16(Text16, { color: "white", children: [
7848
- ">",
7849
- " "
7850
- ] }),
7851
- displayText
7852
- ] }) })
7853
- )
8629
+ component: /* @__PURE__ */ jsx20(ChatUserMessage, { children: /* @__PURE__ */ jsx20(Text19, { children: displayText }) })
7854
8630
  }
7855
8631
  ]);
7856
8632
  agentInstance.current.processTurn({ content: text });
@@ -7878,8 +8654,8 @@ Please use command_status to check the result and report back to the user.`;
7878
8654
  },
7879
8655
  []
7880
8656
  );
7881
- useEffect6(() => {
7882
- setHistory([{ id: 0, component: /* @__PURE__ */ jsx17(Header, { sessionId, workdir }) }]);
8657
+ useEffect7(() => {
8658
+ setHistory([{ id: 0, component: /* @__PURE__ */ jsx20(Header, { sessionId, workdir }) }]);
7883
8659
  const initializeAgent = async () => {
7884
8660
  try {
7885
8661
  agentInstance.current = new Agent(sessionId, eventBus);
@@ -7899,6 +8675,20 @@ Please use command_status to check the result and report back to the user.`;
7899
8675
  };
7900
8676
  const handleBackendMessage = (parsed) => {
7901
8677
  try {
8678
+ const appendTurnDurationIfAny = () => {
8679
+ const t = turnStartedAtRef.current;
8680
+ if (t == null) return;
8681
+ turnStartedAtRef.current = null;
8682
+ const ms = Date.now() - t;
8683
+ const sec = ms < 1e4 ? (ms / 1e3).toFixed(1) : String(Math.round(ms / 1e3));
8684
+ setHistory((prev) => [
8685
+ ...prev,
8686
+ {
8687
+ id: prev.length,
8688
+ component: /* @__PURE__ */ jsx20(ChatTurnDuration, { secondsFormatted: sec })
8689
+ }
8690
+ ]);
8691
+ };
7902
8692
  if (parsed.type === "done" || parsed.type === "error") {
7903
8693
  setIsInitAgentActive(false);
7904
8694
  }
@@ -7924,6 +8714,7 @@ Please use command_status to check the result and report back to the user.`;
7924
8714
  if (parsed.type === "done") {
7925
8715
  if (parsed.status !== "awaiting_confirmation") {
7926
8716
  setStatusMessage(null);
8717
+ appendTurnDurationIfAny();
7927
8718
  }
7928
8719
  setIsProcessing(false);
7929
8720
  return;
@@ -7945,7 +8736,7 @@ Please use command_status to check the result and report back to the user.`;
7945
8736
  ...prev,
7946
8737
  {
7947
8738
  id: prev.length,
7948
- component: /* @__PURE__ */ jsx17(UpdateNotice_default, { message: msg })
8739
+ component: /* @__PURE__ */ jsx20(UpdateNotice_default, { message: msg })
7949
8740
  }
7950
8741
  ]);
7951
8742
  }
@@ -7959,25 +8750,14 @@ Please use command_status to check the result and report back to the user.`;
7959
8750
  }
7960
8751
  let newComponent = null;
7961
8752
  if (parsed.type === "debug") {
7962
- newComponent = /* @__PURE__ */ jsx17(Text16, { color: "gray", children: parsed.message });
8753
+ newComponent = /* @__PURE__ */ jsx20(ChatMeta, { children: parsed.message });
7963
8754
  } else if (parsed.type === "protocol_violation") {
7964
- newComponent = /* @__PURE__ */ jsxs16(
7965
- Box17,
7966
- {
7967
- borderStyle: "round",
7968
- borderColor: "yellow",
7969
- flexDirection: "column",
7970
- marginBottom: 1,
7971
- paddingX: 1,
7972
- children: [
7973
- /* @__PURE__ */ jsx17(Text16, { color: "yellow", bold: true, children: "Protocol Violation" }),
7974
- /* @__PURE__ */ jsx17(Text16, { color: "gray", children: parsed.content }),
7975
- /* @__PURE__ */ jsx17(Text16, { color: "yellow", children: parsed.message })
7976
- ]
7977
- }
7978
- );
8755
+ newComponent = /* @__PURE__ */ jsx20(ChatBlock, { marginBottom: 1, children: /* @__PURE__ */ jsxs19(Box20, { flexDirection: "column", paddingLeft: 2, children: [
8756
+ /* @__PURE__ */ jsx20(Text19, { dimColor: true, children: parsed.content }),
8757
+ /* @__PURE__ */ jsx20(Text19, { dimColor: true, children: parsed.message })
8758
+ ] }) });
7979
8759
  } else if (parsed.type === "error") {
7980
- newComponent = /* @__PURE__ */ jsx17(
8760
+ newComponent = /* @__PURE__ */ jsx20(
7981
8761
  ErrorMessage_default,
7982
8762
  {
7983
8763
  message: parsed.message,
@@ -7987,7 +8767,7 @@ Please use command_status to check the result and report back to the user.`;
7987
8767
  );
7988
8768
  } else if (parsed.type === "tool_call") {
7989
8769
  const nextId3 = history.length;
7990
- newComponent = /* @__PURE__ */ jsx17(
8770
+ newComponent = /* @__PURE__ */ jsx20(
7991
8771
  ToolCallDisplay,
7992
8772
  {
7993
8773
  toolName: parsed.tool_name,
@@ -7996,7 +8776,7 @@ Please use command_status to check the result and report back to the user.`;
7996
8776
  }
7997
8777
  );
7998
8778
  } else if (parsed.type === "tool_result") {
7999
- newComponent = /* @__PURE__ */ jsx17(
8779
+ newComponent = /* @__PURE__ */ jsx20(
8000
8780
  ToolResultDisplay,
8001
8781
  {
8002
8782
  toolName: parsed.tool_name,
@@ -8004,18 +8784,11 @@ Please use command_status to check the result and report back to the user.`;
8004
8784
  }
8005
8785
  );
8006
8786
  } else if (parsed.type === "user_overlay") {
8007
- newComponent = /* @__PURE__ */ jsx17(Box17, { marginBottom: 1, children: /* @__PURE__ */ jsxs16(Text16, { color: "gray", children: [
8008
- /* @__PURE__ */ jsxs16(Text16, { color: "magenta", children: [
8009
- ">",
8010
- " "
8011
- ] }),
8012
- parsed.payload
8013
- ] }) });
8787
+ newComponent = /* @__PURE__ */ jsx20(ChatUserMessage, { children: /* @__PURE__ */ jsx20(Text19, { dimColor: true, children: parsed.payload }) });
8014
8788
  } else if (parsed.type === "reasoning") {
8015
- newComponent = /* @__PURE__ */ jsx17(ReasoningDisplay, { reasoning: parsed.content });
8789
+ newComponent = /* @__PURE__ */ jsx20(ReasoningDisplay, { reasoning: parsed.content });
8016
8790
  } else if (parsed.type === "log") {
8017
- newComponent = /* @__PURE__ */ jsxs16(Text16, { color: "gray", children: [
8018
- "\u2139\uFE0F ",
8791
+ newComponent = /* @__PURE__ */ jsxs19(ChatMeta, { children: [
8019
8792
  parsed.message,
8020
8793
  parsed.payload ? `: ${parsed.payload}` : ""
8021
8794
  ] });
@@ -8023,10 +8796,25 @@ Please use command_status to check the result and report back to the user.`;
8023
8796
  newComponent = null;
8024
8797
  }
8025
8798
  if (newComponent) {
8026
- setHistory((prev) => [
8027
- ...prev,
8028
- { id: prev.length, component: newComponent }
8029
- ]);
8799
+ setHistory((prev) => {
8800
+ const next = [
8801
+ ...prev,
8802
+ { id: prev.length, component: newComponent }
8803
+ ];
8804
+ if (parsed.type === "error") {
8805
+ const t = turnStartedAtRef.current;
8806
+ if (t != null) {
8807
+ turnStartedAtRef.current = null;
8808
+ const ms = Date.now() - t;
8809
+ const sec = ms < 1e4 ? (ms / 1e3).toFixed(1) : String(Math.round(ms / 1e3));
8810
+ next.push({
8811
+ id: next.length,
8812
+ component: /* @__PURE__ */ jsx20(ChatTurnDuration, { secondsFormatted: sec })
8813
+ });
8814
+ }
8815
+ }
8816
+ return next;
8817
+ });
8030
8818
  }
8031
8819
  } catch (error) {
8032
8820
  }
@@ -8044,10 +8832,17 @@ Please use command_status to check the result and report back to the user.`;
8044
8832
  }, [eventBus, sessionId, handleConfirmation]);
8045
8833
  const renderInteractiveComponent = () => {
8046
8834
  if (mcpStatus !== "connected") {
8047
- return;
8835
+ return /* @__PURE__ */ jsx20(
8836
+ SessionInfoConnectingMCP_default,
8837
+ {
8838
+ sessionId,
8839
+ workdir,
8840
+ statusMessage
8841
+ }
8842
+ );
8048
8843
  }
8049
8844
  if (pendingConfirmation) {
8050
- return /* @__PURE__ */ jsx17(
8845
+ return /* @__PURE__ */ jsx20(
8051
8846
  ConfirmationPrompt,
8052
8847
  {
8053
8848
  toolCalls: pendingConfirmation,
@@ -8059,9 +8854,9 @@ Please use command_status to check the result and report back to the user.`;
8059
8854
  }
8060
8855
  );
8061
8856
  }
8062
- return /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", children: [
8063
- isProcessing && !pendingConfirmation && /* @__PURE__ */ jsx17(WorkingTimer, { eventBus }),
8064
- /* @__PURE__ */ jsx17(
8857
+ return /* @__PURE__ */ jsxs19(Box20, { flexDirection: "column", children: [
8858
+ isProcessing && !pendingConfirmation && /* @__PURE__ */ jsx20(WorkingTimer, { eventBus }),
8859
+ /* @__PURE__ */ jsx20(
8065
8860
  InputPrompt,
8066
8861
  {
8067
8862
  onSubmit: handleSubmit,
@@ -8072,9 +8867,9 @@ Please use command_status to check the result and report back to the user.`;
8072
8867
  )
8073
8868
  ] });
8074
8869
  };
8075
- return /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", children: [
8076
- /* @__PURE__ */ jsx17(Static, { items: history, children: (item) => /* @__PURE__ */ jsx17(Box17, { children: item.component }, item.id) }),
8077
- /* @__PURE__ */ jsx17(
8870
+ return /* @__PURE__ */ jsxs19(Box20, { flexDirection: "column", children: [
8871
+ /* @__PURE__ */ jsx20(Static, { items: history, children: (item) => /* @__PURE__ */ jsx20(Box20, { children: item.component }, item.id) }),
8872
+ /* @__PURE__ */ jsx20(
8078
8873
  StreamingText,
8079
8874
  {
8080
8875
  eventBus,
@@ -8084,7 +8879,7 @@ Please use command_status to check the result and report back to the user.`;
8084
8879
  ...prev,
8085
8880
  {
8086
8881
  id: prev.length,
8087
- component: /* @__PURE__ */ jsx17(ReasoningDisplay, { reasoning })
8882
+ component: /* @__PURE__ */ jsx20(ReasoningDisplay, { reasoning })
8088
8883
  }
8089
8884
  ]);
8090
8885
  }
@@ -8094,7 +8889,7 @@ Please use command_status to check the result and report back to the user.`;
8094
8889
  renderInteractiveComponent()
8095
8890
  ] });
8096
8891
  };
8097
- var App = memo11(AppComponent);
8892
+ var App = memo12(AppComponent);
8098
8893
  var App_default = App;
8099
8894
 
8100
8895
  // src/app/ui/utils/terminalTitle.ts
@@ -8218,7 +9013,7 @@ async function runAgentMode() {
8218
9013
  process.env.BLUMA_SANDBOX_NAME = String(envelope.metadata.sandbox_name);
8219
9014
  }
8220
9015
  }
8221
- const eventBus = new EventEmitter2();
9016
+ const eventBus = new EventEmitter3();
8222
9017
  const sessionId = envelope.session_id || envelope.message_id || uuidv46();
8223
9018
  const uc = envelope.user_context;
8224
9019
  const userContextInput = {
@@ -8350,13 +9145,13 @@ async function runAgentMode() {
8350
9145
  function runCliMode() {
8351
9146
  const BLUMA_TITLE = process.env.BLUMA_TITLE || "BluMa - NomadEngenuity";
8352
9147
  startTitleKeeper(BLUMA_TITLE);
8353
- const eventBus = new EventEmitter2();
9148
+ const eventBus = new EventEmitter3();
8354
9149
  const sessionId = uuidv46();
8355
9150
  const props = {
8356
9151
  eventBus,
8357
9152
  sessionId
8358
9153
  };
8359
- render(React11.createElement(App_default, props));
9154
+ render(React12.createElement(App_default, props));
8360
9155
  }
8361
9156
  var argv = process.argv.slice(2);
8362
9157
  if (argv[0] === "agent") {