@nomad-e/bluma-cli 0.23.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/main.js +273 -93
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -2242,25 +2242,14 @@ var init_ListMailboxMessagesTool = __esm({
2242
2242
 
2243
2243
  // src/app/agent/core/context-api/token_counter.ts
2244
2244
  import { getEncoding } from "js-tiktoken";
2245
- import { createHash as createHash2 } from "crypto";
2246
2245
  function getO200kEncoding() {
2247
2246
  if (!cachedEncoding) {
2248
2247
  cachedEncoding = getEncoding("o200k_base");
2249
2248
  }
2250
2249
  return cachedEncoding;
2251
2250
  }
2252
- function hashHistory(messages) {
2253
- const hash = createHash2("sha256");
2254
- hash.update(`len:${messages.length}:`);
2255
- for (const msg of messages) {
2256
- const m = msg;
2257
- hash.update(`role:${m.role ?? ""}:`);
2258
- hash.update(`content:${JSON.stringify(m.content ?? "")}:`);
2259
- hash.update(`tool_calls:${JSON.stringify(m.tool_calls ?? "")}:`);
2260
- hash.update(`tool_call_id:${m.tool_call_id ?? ""}:`);
2261
- hash.update(`name:${m.name ?? ""}:`);
2262
- }
2263
- return hash.digest("hex");
2251
+ function invalidateTokenCache() {
2252
+ cacheVersion++;
2264
2253
  }
2265
2254
  function messageBodyForTokens(msg) {
2266
2255
  const c = msg.content;
@@ -2290,22 +2279,8 @@ function countTokens(messages, useCache = true) {
2290
2279
  if (messages.length === 0) {
2291
2280
  return CONVERSATION_BASE_OVERHEAD;
2292
2281
  }
2293
- if (useCache) {
2294
- const currentHash = hashHistory(messages);
2295
- if (tokenCountCache && tokenCountCache.hash === currentHash) {
2296
- return tokenCountCache.count;
2297
- }
2298
- const enc2 = getO200kEncoding();
2299
- let total2 = CONVERSATION_BASE_OVERHEAD;
2300
- for (const msg of messages) {
2301
- const body = messageBodyForTokens(msg);
2302
- const extra = messageExtraForTokens(msg);
2303
- const nBody = body ? enc2.encode(body).length : 0;
2304
- const nExtra = extra ? enc2.encode(extra).length : 0;
2305
- total2 += nBody + nExtra + MESSAGE_OVERHEAD_TOKENS;
2306
- }
2307
- tokenCountCache = { hash: currentHash, count: total2 };
2308
- return total2;
2282
+ if (useCache && cacheVersion === cachedVersion) {
2283
+ return cachedCount;
2309
2284
  }
2310
2285
  const enc = getO200kEncoding();
2311
2286
  let total = CONVERSATION_BASE_OVERHEAD;
@@ -2316,11 +2291,12 @@ function countTokens(messages, useCache = true) {
2316
2291
  const nExtra = extra ? enc.encode(extra).length : 0;
2317
2292
  total += nBody + nExtra + MESSAGE_OVERHEAD_TOKENS;
2318
2293
  }
2294
+ if (useCache) {
2295
+ cachedVersion = cacheVersion;
2296
+ cachedCount = total;
2297
+ }
2319
2298
  return total;
2320
2299
  }
2321
- function clearTokenCountCache() {
2322
- tokenCountCache = null;
2323
- }
2324
2300
  function countToolDefinitionsTokens(tools) {
2325
2301
  if (!tools || tools.length === 0) {
2326
2302
  return 0;
@@ -2345,14 +2321,16 @@ function computeEffectiveInputBudget(rawBudget, toolDefinitions = [], options) {
2345
2321
  effectiveBudget
2346
2322
  };
2347
2323
  }
2348
- var MESSAGE_OVERHEAD_TOKENS, CONVERSATION_BASE_OVERHEAD, cachedEncoding, tokenCountCache, DEFAULT_OUTPUT_TOKEN_RESERVE, DEFAULT_PROTOCOL_OVERHEAD_TOKENS;
2324
+ var MESSAGE_OVERHEAD_TOKENS, CONVERSATION_BASE_OVERHEAD, cachedEncoding, cacheVersion, cachedVersion, cachedCount, DEFAULT_OUTPUT_TOKEN_RESERVE, DEFAULT_PROTOCOL_OVERHEAD_TOKENS;
2349
2325
  var init_token_counter = __esm({
2350
2326
  "src/app/agent/core/context-api/token_counter.ts"() {
2351
2327
  "use strict";
2352
2328
  MESSAGE_OVERHEAD_TOKENS = 4;
2353
2329
  CONVERSATION_BASE_OVERHEAD = 3;
2354
2330
  cachedEncoding = null;
2355
- tokenCountCache = null;
2331
+ cacheVersion = 0;
2332
+ cachedVersion = -1;
2333
+ cachedCount = 0;
2356
2334
  DEFAULT_OUTPUT_TOKEN_RESERVE = 8192;
2357
2335
  DEFAULT_PROTOCOL_OVERHEAD_TOKENS = 512;
2358
2336
  }
@@ -14315,7 +14293,7 @@ import { v4 as uuidv412 } from "uuid";
14315
14293
  import chalk3 from "chalk";
14316
14294
 
14317
14295
  // src/app/ui/BlumaSession.tsx
14318
- import React37, { useState as useState24, useEffect as useEffect22, useRef as useRef12, useCallback as useCallback10, memo as memo25, useMemo as useMemo7 } from "react";
14296
+ import { useState as useState25, useEffect as useEffect22, useRef as useRef13, useCallback as useCallback11, memo as memo25, useMemo as useMemo8 } from "react";
14319
14297
 
14320
14298
  // src/app/ui/layout.tsx
14321
14299
  import { memo as memo2 } from "react";
@@ -25084,8 +25062,13 @@ var HistoryCompressor = class {
25084
25062
  const tokenBudget = budgetBreakdown.effectiveBudget;
25085
25063
  const compressThreshold = options?.compressThreshold ?? COMPRESS_THRESHOLD;
25086
25064
  const keepRecentTurns = options?.keepRecentTurns ?? KEEP_RECENT_TURNS;
25087
- const sanitized = sanitizeConversationForProvider(fullHistory);
25088
- const safeHistory = sanitized.messages;
25065
+ let safeHistory;
25066
+ if (options?.skipSanitize) {
25067
+ safeHistory = fullHistory;
25068
+ } else {
25069
+ const sanitized = sanitizeConversationForProvider(fullHistory);
25070
+ safeHistory = sanitized.messages;
25071
+ }
25089
25072
  const systemMessages = [];
25090
25073
  let historyStartIndex = 0;
25091
25074
  while (historyStartIndex < safeHistory.length && safeHistory[historyStartIndex].role === "system") {
@@ -25102,7 +25085,6 @@ var HistoryCompressor = class {
25102
25085
  const thresholdTokens = tokenBudget * compressThreshold;
25103
25086
  let pendingSlices = turnSlices.slice(sliceCount, recentStart);
25104
25087
  let pendingFlat = pendingSlices.flat();
25105
- clearTokenCountCache();
25106
25088
  let messages = this.assembleMessages(systemMessages, pendingFlat, recentFlat);
25107
25089
  let tokens = countTokens(messages, true);
25108
25090
  contextEventBus.emit("context:compression_start", {
@@ -25149,12 +25131,12 @@ var HistoryCompressor = class {
25149
25131
  reason: compressError?.message || "timeout"
25150
25132
  });
25151
25133
  }
25152
- clearTokenCountCache();
25134
+ invalidateTokenCache();
25153
25135
  messages = this.assembleMessages(systemMessages, pendingFlat, recentFlat);
25154
25136
  tokens = countTokens(messages, true);
25155
25137
  }
25156
25138
  if (tokens > tokenBudget) {
25157
- clearTokenCountCache();
25139
+ invalidateTokenCache();
25158
25140
  const { capped, slices: cappedSlices } = this.enforceHardCap(
25159
25141
  systemMessages,
25160
25142
  recentSlices,
@@ -27246,7 +27228,9 @@ var BluMaTurnCoordinator = class {
27246
27228
  this.deps.getLlmUserContext(),
27247
27229
  {
27248
27230
  tokenBudget,
27249
- toolDefinitions: this.deps.mcpClient.getAvailableTools()
27231
+ toolDefinitions: this.deps.mcpClient.getAvailableTools(),
27232
+ skipSanitize: true
27233
+ // already sanitized above
27250
27234
  }
27251
27235
  );
27252
27236
  this.turnLog.debug("Context window prepared", {
@@ -28137,11 +28121,15 @@ function hasMetUpdateThreshold(currentTokens) {
28137
28121
  // src/app/agent/memory/session_memory_update.ts
28138
28122
  init_logger();
28139
28123
  var log2 = logger.child("session_memory");
28124
+ var MIN_COOLDOWN_MS = 6e4;
28125
+ var lastUpdateTime = 0;
28140
28126
  function isSessionMemoryEnabled() {
28141
28127
  if (process.env.BLUMA_DISABLE_SESSION_MEMORY === "1") return false;
28142
28128
  return true;
28143
28129
  }
28144
28130
  function shouldUpdateSessionMemory(history) {
28131
+ const now2 = Date.now();
28132
+ if (now2 - lastUpdateTime < MIN_COOLDOWN_MS) return false;
28145
28133
  const tokens = estimateHistoryTokens(history);
28146
28134
  if (!isSessionMemoryInitialized()) {
28147
28135
  if (!hasMetInitializationThreshold(tokens)) return false;
@@ -28191,6 +28179,7 @@ async function runSessionMemoryUpdate(deps) {
28191
28179
  });
28192
28180
  recordExtractionTokenCount(estimateHistoryTokens(history));
28193
28181
  setLastCursorIndex(history.length);
28182
+ lastUpdateTime = Date.now();
28194
28183
  log2.debug("finished");
28195
28184
  } catch (err) {
28196
28185
  log2.warn("failed", { error: err instanceof Error ? err.message : String(err) });
@@ -28205,8 +28194,10 @@ function scheduleSessionMemoryUpdate(deps) {
28205
28194
 
28206
28195
  // src/app/agent/memory/background_memory.ts
28207
28196
  function runBackgroundMemoryAfterTurn(params) {
28197
+ const MAX_SNAPSHOT = 80;
28198
+ const snapshot = params.history.length > MAX_SNAPSHOT ? params.history.slice(-MAX_SNAPSHOT) : params.history;
28208
28199
  const deps = {
28209
- history: [...params.history],
28200
+ history: snapshot,
28210
28201
  sessionId: params.sessionId,
28211
28202
  llm: params.llm,
28212
28203
  mcpClient: params.mcpClient,
@@ -45377,6 +45368,167 @@ function ScrollBox({
45377
45368
  }
45378
45369
  var ScrollBox_default = ScrollBox;
45379
45370
 
45371
+ // src/app/ui/hooks/useVirtualScroll.ts
45372
+ import {
45373
+ useCallback as useCallback10,
45374
+ useLayoutEffect as useLayoutEffect3,
45375
+ useMemo as useMemo7,
45376
+ useRef as useRef12,
45377
+ useState as useState24
45378
+ } from "react";
45379
+ var DEFAULT_ESTIMATE = 3;
45380
+ var OVERSCAN_ROWS = 40;
45381
+ var COLD_START_COUNT = 20;
45382
+ var MAX_MOUNTED_ITEMS = 200;
45383
+ var PESSIMISTIC_HEIGHT = 1;
45384
+ function useVirtualScroll(scrollRef, itemKeys, columns) {
45385
+ const heightCache = useRef12(/* @__PURE__ */ new Map());
45386
+ const offsetVersionRef = useRef12(0);
45387
+ const lastScrollTopRef = useRef12(0);
45388
+ const offsetsRef = useRef12({
45389
+ arr: new Float64Array(0),
45390
+ version: -1,
45391
+ n: -1
45392
+ });
45393
+ const itemRefs = useRef12(/* @__PURE__ */ new Map());
45394
+ const refCache = useRef12(/* @__PURE__ */ new Map());
45395
+ const prevColumns = useRef12(columns);
45396
+ const skipMeasurementRef = useRef12(false);
45397
+ if (prevColumns.current !== columns) {
45398
+ const ratio = prevColumns.current / columns;
45399
+ prevColumns.current = columns;
45400
+ for (const [k, h] of heightCache.current) {
45401
+ heightCache.current.set(k, Math.max(1, Math.round(h * ratio)));
45402
+ }
45403
+ offsetVersionRef.current++;
45404
+ skipMeasurementRef.current = true;
45405
+ }
45406
+ const SCROLL_QUANTUM = OVERSCAN_ROWS >> 1;
45407
+ const subscribe = useCallback10(
45408
+ (cb) => scrollRef.current?.subscribe(cb) ?? (() => {
45409
+ }),
45410
+ [scrollRef]
45411
+ );
45412
+ const getSnapshot = useCallback10(() => {
45413
+ const sb = scrollRef.current;
45414
+ if (!sb) return NaN;
45415
+ const raw = sb.getScrollTop();
45416
+ const sticky = sb.isSticky();
45417
+ const quantized = Math.floor(raw / SCROLL_QUANTUM) * SCROLL_QUANTUM;
45418
+ return sticky ? -(quantized | 1) : quantized;
45419
+ }, [scrollRef, SCROLL_QUANTUM]);
45420
+ const getServerSnapshot = useCallback10(() => NaN, []);
45421
+ const scrollTop = useSyncExternalStore5(subscribe, getSnapshot, getServerSnapshot);
45422
+ const viewportHeight = scrollRef.current?.getViewportHeight() ?? 0;
45423
+ const isSticky = scrollRef.current?.isSticky() ?? true;
45424
+ const n = itemKeys.length;
45425
+ const computeOffsets = useCallback10(() => {
45426
+ const cached = offsetsRef.current;
45427
+ if (cached.n === n && cached.version === offsetVersionRef.current && cached.arr.length === n + 1) {
45428
+ return cached.arr;
45429
+ }
45430
+ const offsets2 = new Float64Array(n + 1);
45431
+ for (let i = 0; i < n; i++) {
45432
+ const h = heightCache.current.get(itemKeys[i]) ?? DEFAULT_ESTIMATE;
45433
+ offsets2[i + 1] = offsets2[i] + h;
45434
+ }
45435
+ offsetsRef.current = { arr: offsets2, version: offsetVersionRef.current, n };
45436
+ return offsets2;
45437
+ }, [n, itemKeys, offsetVersionRef.current]);
45438
+ const offsets = computeOffsets();
45439
+ const totalHeight = offsets[n] ?? 0;
45440
+ const range = useMemo7(() => {
45441
+ if (n === 0) return [0, 0];
45442
+ if (viewportHeight <= 0) {
45443
+ return [0, Math.min(n, COLD_START_COUNT)];
45444
+ }
45445
+ const absScrollTop = Math.abs(scrollTop);
45446
+ if (isSticky) {
45447
+ let hi3 = n;
45448
+ let remaining = viewportHeight + OVERSCAN_ROWS;
45449
+ while (hi3 > 0 && remaining > 0) {
45450
+ const h = heightCache.current.get(itemKeys[hi3 - 1]) ?? DEFAULT_ESTIMATE;
45451
+ remaining -= h;
45452
+ hi3--;
45453
+ }
45454
+ const lo3 = Math.max(0, hi3 - OVERSCAN_ROWS);
45455
+ return [lo3, n];
45456
+ }
45457
+ let lo2 = 0;
45458
+ {
45459
+ const target = Math.max(0, absScrollTop - OVERSCAN_ROWS);
45460
+ let lo_ = 0;
45461
+ let hi_ = n;
45462
+ while (lo_ < hi_) {
45463
+ const mid = lo_ + hi_ >>> 1;
45464
+ if (offsets[mid] < target) lo_ = mid + 1;
45465
+ else hi_ = mid;
45466
+ }
45467
+ lo2 = lo_;
45468
+ }
45469
+ let hi2 = lo2;
45470
+ let accumulated = 0;
45471
+ const needed = viewportHeight + 2 * OVERSCAN_ROWS;
45472
+ while (hi2 < n && accumulated < needed) {
45473
+ const h = heightCache.current.get(itemKeys[hi2]) ?? PESSIMISTIC_HEIGHT;
45474
+ accumulated += h;
45475
+ hi2++;
45476
+ }
45477
+ return [lo2, Math.min(hi2, lo2 + MAX_MOUNTED_ITEMS)];
45478
+ }, [scrollTop, viewportHeight, isSticky, n, offsets, itemKeys]);
45479
+ const measureRef = useCallback10((key) => {
45480
+ if (refCache.current.has(key)) return refCache.current.get(key);
45481
+ const refFn = (el) => {
45482
+ if (skipMeasurementRef.current) return;
45483
+ if (el) {
45484
+ itemRefs.current.set(key, el);
45485
+ const h = el.yogaNode?.getComputedHeight();
45486
+ if (h != null && h > 0) {
45487
+ const prev = heightCache.current.get(key);
45488
+ if (prev !== h) {
45489
+ heightCache.current.set(key, h);
45490
+ offsetVersionRef.current++;
45491
+ }
45492
+ }
45493
+ }
45494
+ };
45495
+ refCache.current.set(key, refFn);
45496
+ return refFn;
45497
+ }, []);
45498
+ const [lo, hi] = range;
45499
+ const topSpacer = offsets[lo] ?? 0;
45500
+ const bottomSpacer = totalHeight - (offsets[hi] ?? totalHeight);
45501
+ return {
45502
+ range,
45503
+ topSpacer,
45504
+ bottomSpacer,
45505
+ offsets,
45506
+ measureRef
45507
+ };
45508
+ }
45509
+ function useSyncExternalStore5(subscribe, getSnapshot, getServerSnapshot) {
45510
+ const [value, setValue] = useState24(getSnapshot);
45511
+ const getSnapshotRef = useRef12(getSnapshot);
45512
+ getSnapshotRef.current = getSnapshot;
45513
+ useLayoutEffect3(() => {
45514
+ let didSubscribe = false;
45515
+ const checkForUpdates2 = () => {
45516
+ if (didSubscribe) {
45517
+ const newValue = getSnapshotRef.current();
45518
+ setValue(newValue);
45519
+ }
45520
+ };
45521
+ didSubscribe = true;
45522
+ checkForUpdates2();
45523
+ const unsubscribe = subscribe(checkForUpdates2);
45524
+ return () => {
45525
+ didSubscribe = false;
45526
+ unsubscribe();
45527
+ };
45528
+ }, [subscribe]);
45529
+ return value;
45530
+ }
45531
+
45380
45532
  // src/app/ui/BlumaSession.tsx
45381
45533
  import { Fragment as Fragment19, jsx as jsx104, jsxs as jsxs85 } from "react/jsx-runtime";
45382
45534
  var blumaUpdateRegistryCheckStarted = false;
@@ -45442,23 +45594,23 @@ function UserMessageWithOptionalImages({
45442
45594
  ) : /* @__PURE__ */ jsx104(Text, { color: BLUMA_TERMINAL.m3OnSurface, wrap: "wrap", children: displayRaw }) });
45443
45595
  }
45444
45596
  var BlumaSessionComponent = ({ eventBus, sessionId, cliVersion }) => {
45445
- const agentInstance = useRef12(null);
45446
- const [history, setHistory] = useState24([]);
45447
- const [statusMessage, setStatusMessage] = useState24(
45597
+ const agentInstance = useRef13(null);
45598
+ const [history, setHistory] = useState25([]);
45599
+ const [statusMessage, setStatusMessage] = useState25(
45448
45600
  "Initializing agent..."
45449
45601
  );
45450
- const [toolsCount, setToolsCount] = useState24(null);
45451
- const [mcpStatus, setMcpStatus] = useState24(
45602
+ const [toolsCount, setToolsCount] = useState25(null);
45603
+ const [mcpStatus, setMcpStatus] = useState25(
45452
45604
  "connecting"
45453
45605
  );
45454
- const [isProcessing, setIsProcessing] = useState24(true);
45455
- const [pendingConfirmation, setPendingConfirmation] = useState24(
45606
+ const [isProcessing, setIsProcessing] = useState25(true);
45607
+ const [pendingConfirmation, setPendingConfirmation] = useState25(
45456
45608
  null
45457
45609
  );
45458
- const [confirmationPreview, setConfirmationPreview] = useState24(
45610
+ const [confirmationPreview, setConfirmationPreview] = useState25(
45459
45611
  null
45460
45612
  );
45461
- const [pendingAskUserQuestions, setPendingAskUserQuestions] = useState24(null);
45613
+ const [pendingAskUserQuestions, setPendingAskUserQuestions] = useState25(null);
45462
45614
  const {
45463
45615
  tokenCount,
45464
45616
  tokenBudget,
@@ -45471,7 +45623,7 @@ var BlumaSessionComponent = ({ eventBus, sessionId, cliVersion }) => {
45471
45623
  const { currentPlan, isPlanMode, parseMessage, resetPlan } = usePlanMode();
45472
45624
  const { agentMode } = useAgentMode();
45473
45625
  const workdir = getSandboxPolicy().workspaceRoot;
45474
- const lastUpdateRef = useRef12(0);
45626
+ const lastUpdateRef = useRef13(0);
45475
45627
  useEffect22(() => {
45476
45628
  const now2 = Date.now();
45477
45629
  if (now2 - lastUpdateRef.current < 250) {
@@ -45490,29 +45642,29 @@ var BlumaSessionComponent = ({ eventBus, sessionId, cliVersion }) => {
45490
45642
  activeProjects
45491
45643
  });
45492
45644
  }, [tokenCount, tokenBudget, isCompressing, compressionProgress, agentMode, isPlanMode, workdir, activeProjects]);
45493
- const [isInitAgentActive, setIsInitAgentActive] = useState24(false);
45494
- const [liveToolName, setLiveToolName] = useState24(null);
45495
- const [liveToolArgs, setLiveToolArgs] = useState24(void 0);
45496
- const [isReasoning, setIsReasoning] = useState24(false);
45497
- const alwaysAcceptList = useRef12([]);
45498
- const turnStartedAtRef = useRef12(null);
45499
- const [processingStartMs, setProcessingStartMs] = useState24(null);
45500
- const [lastTurnDurationLabel, setLastTurnDurationLabel] = useState24(null);
45501
- const markTurnStarted = useCallback10(() => {
45645
+ const [isInitAgentActive, setIsInitAgentActive] = useState25(false);
45646
+ const [liveToolName, setLiveToolName] = useState25(null);
45647
+ const [liveToolArgs, setLiveToolArgs] = useState25(void 0);
45648
+ const [isReasoning, setIsReasoning] = useState25(false);
45649
+ const alwaysAcceptList = useRef13([]);
45650
+ const turnStartedAtRef = useRef13(null);
45651
+ const [processingStartMs, setProcessingStartMs] = useState25(null);
45652
+ const [lastTurnDurationLabel, setLastTurnDurationLabel] = useState25(null);
45653
+ const markTurnStarted = useCallback11(() => {
45502
45654
  const t = Date.now();
45503
45655
  turnStartedAtRef.current = t;
45504
45656
  setProcessingStartMs(t);
45505
45657
  setLastTurnDurationLabel(null);
45506
45658
  }, []);
45507
- const handleWorkComplete = useCallback10((elapsedMs) => {
45659
+ const handleWorkComplete = useCallback11((elapsedMs) => {
45508
45660
  const label = formatTurnDurationMs(elapsedMs);
45509
45661
  setLastTurnDurationLabel(label);
45510
45662
  }, []);
45511
- const lastReasoningTextRef = useRef12(null);
45512
- const streamedReasoningTextRef = useRef12("");
45513
- const activeReasoningHistoryIdRef = useRef12(null);
45514
- const lastStreamAssistantKeyRef = useRef12(null);
45515
- const appendExpandPreviewToHistory = useCallback10(() => {
45663
+ const lastReasoningTextRef = useRef13(null);
45664
+ const streamedReasoningTextRef = useRef13("");
45665
+ const activeReasoningHistoryIdRef = useRef13(null);
45666
+ const lastStreamAssistantKeyRef = useRef13(null);
45667
+ const appendExpandPreviewToHistory = useCallback11(() => {
45516
45668
  const p = peekLatestExpandable();
45517
45669
  setHistory((prev) => {
45518
45670
  const id = nextHistoryId(prev);
@@ -45557,7 +45709,7 @@ var BlumaSessionComponent = ({ eventBus, sessionId, cliVersion }) => {
45557
45709
  });
45558
45710
  });
45559
45711
  }, []);
45560
- const handleInterrupt = useCallback10(() => {
45712
+ const handleInterrupt = useCallback11(() => {
45561
45713
  if (!isProcessing) return;
45562
45714
  eventBus.emit("user_interrupt");
45563
45715
  turnStartedAtRef.current = null;
@@ -45574,7 +45726,7 @@ var BlumaSessionComponent = ({ eventBus, sessionId, cliVersion }) => {
45574
45726
  ];
45575
45727
  });
45576
45728
  }, [isProcessing]);
45577
- const handleSubmit = useCallback10(
45729
+ const handleSubmit = useCallback11(
45578
45730
  (text) => {
45579
45731
  if (!text || !agentInstance.current) return;
45580
45732
  const trimmedForSlash = text.trim();
@@ -45823,7 +45975,7 @@ Please use command_status to check the result and report back to the user.`;
45823
45975
  [isProcessing, markTurnStarted]
45824
45976
  );
45825
45977
  const { snapshot: queuedMessages, clear: clearQueue } = useMessageQueue_default();
45826
- const isAgentIdle = useMemo7(
45978
+ const isAgentIdle = useMemo8(
45827
45979
  () => !isProcessing && !pendingConfirmation && !pendingAskUserQuestions?.length && !isInitAgentActive,
45828
45980
  [isProcessing, pendingConfirmation, pendingAskUserQuestions, isInitAgentActive]
45829
45981
  );
@@ -45855,7 +46007,7 @@ Please use command_status to check the result and report back to the user.`;
45855
46007
  subscription.off("clear_queue", handleClearQueue);
45856
46008
  };
45857
46009
  }, [queuedMessages.length, clearQueue]);
45858
- const handleConfirmation = useCallback10(
46010
+ const handleConfirmation = useCallback11(
45859
46011
  async (decision, toolCalls, opts) => {
45860
46012
  if (!agentInstance.current) return;
45861
46013
  setPendingConfirmation(null);
@@ -45878,7 +46030,7 @@ Please use command_status to check the result and report back to the user.`;
45878
46030
  },
45879
46031
  []
45880
46032
  );
45881
- const appendStreamedReasoning = useCallback10((reasoning) => {
46033
+ const appendStreamedReasoning = useCallback11((reasoning) => {
45882
46034
  const r = String(reasoning ?? "").trim();
45883
46035
  if (!r) return;
45884
46036
  setHistory((prev) => {
@@ -45896,7 +46048,7 @@ Please use command_status to check the result and report back to the user.`;
45896
46048
  return [...prev, { id, component }];
45897
46049
  });
45898
46050
  }, []);
45899
- const appendStreamedAssistant = useCallback10((content) => {
46051
+ const appendStreamedAssistant = useCallback11((content) => {
45900
46052
  const t = String(content ?? "").trim();
45901
46053
  if (!t) return;
45902
46054
  const key = reasoningDedupeKey(t);
@@ -45922,9 +46074,9 @@ Please use command_status to check the result and report back to the user.`;
45922
46074
  return [...prev, { id, component: nextComponent }];
45923
46075
  });
45924
46076
  }, []);
45925
- const handleConfirmationRef = useRef12(handleConfirmation);
46077
+ const handleConfirmationRef = useRef13(handleConfirmation);
45926
46078
  handleConfirmationRef.current = handleConfirmation;
45927
- const sessionIdRef = useRef12(sessionId);
46079
+ const sessionIdRef = useRef13(sessionId);
45928
46080
  sessionIdRef.current = sessionId;
45929
46081
  useEffect22(() => {
45930
46082
  const initializeAgent = async () => {
@@ -46217,17 +46369,17 @@ Please use command_status to check the result and report back to the user.`;
46217
46369
  eventBus.off("backend_message", handleBackendMessage);
46218
46370
  };
46219
46371
  }, [eventBus, handleConfirmation]);
46220
- const handleAnswerQuestion = useCallback10((answerJson) => {
46372
+ const handleAnswerQuestion = useCallback11((answerJson) => {
46221
46373
  setPendingAskUserQuestions(null);
46222
46374
  eventBus.emit("ask_user_question_answer", { answer: answerJson });
46223
46375
  }, [eventBus]);
46224
- const handleCancelQuestion = useCallback10(() => {
46376
+ const handleCancelQuestion = useCallback11(() => {
46225
46377
  setPendingAskUserQuestions(null);
46226
46378
  eventBus.emit("ask_user_question_answer", {
46227
46379
  answer: JSON.stringify({ success: false, cancelled: true })
46228
46380
  });
46229
46381
  }, [eventBus]);
46230
- const handleDecision = useCallback10(async (decision) => {
46382
+ const handleDecision = useCallback11(async (decision) => {
46231
46383
  if (!pendingConfirmation || pendingConfirmation.length === 0) return;
46232
46384
  const [currentToolCall, ...remainingToolCalls] = pendingConfirmation;
46233
46385
  if (!currentToolCall) return;
@@ -46244,8 +46396,33 @@ Please use command_status to check the result and report back to the user.`;
46244
46396
  setIsProcessing(false);
46245
46397
  }
46246
46398
  }, [handleConfirmation, pendingConfirmation]);
46247
- const transcript = useMemo7(() => /* @__PURE__ */ jsx104(Fragment19, { children: /* @__PURE__ */ jsx104(ScrollBox_default, { stickyScroll: true, flexDirection: "column", flexGrow: 1, minHeight: 0, width: "100%", children: /* @__PURE__ */ jsxs85(Box_default, { flexDirection: "column", minHeight: 0, width: "100%", children: [
46248
- history.map((item) => /* @__PURE__ */ jsx104(React37.Fragment, { children: item.component }, item.id)),
46399
+ const scrollBoxRef = useRef13(null);
46400
+ const { columns } = useTerminalSize();
46401
+ const historyKeys = useMemo8(
46402
+ () => history.map((item) => String(item.id)),
46403
+ [history.length, history.length > 0 ? history[history.length - 1].id : 0]
46404
+ );
46405
+ const { range, topSpacer, bottomSpacer, measureRef } = useVirtualScroll(
46406
+ scrollBoxRef,
46407
+ historyKeys,
46408
+ columns
46409
+ );
46410
+ const [lo, hi] = range;
46411
+ const mountedItems = useMemo8(() => {
46412
+ return history.slice(lo, hi);
46413
+ }, [history, lo, hi]);
46414
+ const transcript = useMemo8(() => /* @__PURE__ */ jsx104(Fragment19, { children: /* @__PURE__ */ jsx104(ScrollBox_default, { ref: scrollBoxRef, stickyScroll: true, flexDirection: "column", flexGrow: 1, minHeight: 0, width: "100%", children: /* @__PURE__ */ jsxs85(Box_default, { flexDirection: "column", minHeight: 0, width: "100%", children: [
46415
+ topSpacer > 0 && /* @__PURE__ */ jsx104(Box_default, { height: topSpacer }),
46416
+ mountedItems.map((item) => /* @__PURE__ */ jsx104(
46417
+ Box_default,
46418
+ {
46419
+ flexDirection: "column",
46420
+ ref: measureRef(String(item.id)),
46421
+ children: item.component
46422
+ },
46423
+ item.id
46424
+ )),
46425
+ bottomSpacer > 0 && /* @__PURE__ */ jsx104(Box_default, { height: bottomSpacer }),
46249
46426
  /* @__PURE__ */ jsx104(
46250
46427
  StreamingText,
46251
46428
  {
@@ -46258,10 +46435,13 @@ Please use command_status to check the result and report back to the user.`;
46258
46435
  appendStreamedAssistant,
46259
46436
  appendStreamedReasoning,
46260
46437
  eventBus,
46261
- history,
46438
+ mountedItems,
46439
+ topSpacer,
46440
+ bottomSpacer,
46441
+ measureRef,
46262
46442
  liveToolName
46263
46443
  ]);
46264
- const bottomDock = useMemo7(() => /* @__PURE__ */ jsx104(
46444
+ const bottomDock = useMemo8(() => /* @__PURE__ */ jsx104(
46265
46445
  BlumaBottomDock,
46266
46446
  {
46267
46447
  eventBus,
@@ -46311,9 +46491,9 @@ Please use command_status to check the result and report back to the user.`;
46311
46491
  statusMessage,
46312
46492
  workdir
46313
46493
  ]);
46314
- const header = useMemo7(() => /* @__PURE__ */ jsx104(Header, { sessionId, workdir, cliVersion }), [cliVersion, sessionId, workdir]);
46315
- const overlay = useMemo7(() => /* @__PURE__ */ jsx104(BlumaWorkersOverlay, { sessionId }), [sessionId]);
46316
- const floating = useMemo7(() => /* @__PURE__ */ jsx104(CoordinatorTaskPanel, {}), []);
46494
+ const header = useMemo8(() => /* @__PURE__ */ jsx104(Header, { sessionId, workdir, cliVersion }), [cliVersion, sessionId, workdir]);
46495
+ const overlay = useMemo8(() => /* @__PURE__ */ jsx104(BlumaWorkersOverlay, { sessionId }), [sessionId]);
46496
+ const floating = useMemo8(() => /* @__PURE__ */ jsx104(CoordinatorTaskPanel, {}), []);
46317
46497
  return /* @__PURE__ */ jsx104(
46318
46498
  BlumaViewport,
46319
46499
  {
@@ -46497,7 +46677,7 @@ import React39 from "react";
46497
46677
  init_agent_session_paths();
46498
46678
 
46499
46679
  // src/app/ui/components/SessionResumePicker.tsx
46500
- import { memo as memo26, useCallback as useCallback11, useEffect as useEffect23, useMemo as useMemo8, useState as useState25 } from "react";
46680
+ import { memo as memo26, useCallback as useCallback12, useEffect as useEffect23, useMemo as useMemo9, useState as useState26 } from "react";
46501
46681
 
46502
46682
  // src/app/agent/session_manager/session_resume_browser.ts
46503
46683
  init_bluma_app_dir();
@@ -46644,12 +46824,12 @@ var SessionResumePickerComponent = ({
46644
46824
  4,
46645
46825
  Math.min(VISIBLE_DEFAULT, (stdout?.rows ?? 24) - 14)
46646
46826
  );
46647
- const [cwd2, setCwd] = useState25("");
46648
- const [entries, setEntries] = useState25([]);
46649
- const [loading, setLoading] = useState25(true);
46650
- const [selectedIndex, setSelectedIndex] = useState25(0);
46651
- const [scrollTop, setScrollTop] = useState25(0);
46652
- const reload = useCallback11(async (rel) => {
46827
+ const [cwd2, setCwd] = useState26("");
46828
+ const [entries, setEntries] = useState26([]);
46829
+ const [loading, setLoading] = useState26(true);
46830
+ const [selectedIndex, setSelectedIndex] = useState26(0);
46831
+ const [scrollTop, setScrollTop] = useState26(0);
46832
+ const reload = useCallback12(async (rel) => {
46653
46833
  setLoading(true);
46654
46834
  try {
46655
46835
  const list = await listSessionBrowserEntries(rel);
@@ -46675,7 +46855,7 @@ var SessionResumePickerComponent = ({
46675
46855
  setScrollTop(selectedIndex - visibleCount + 1);
46676
46856
  }
46677
46857
  }, [selectedIndex, scrollTop, visibleCount]);
46678
- const windowItems = useMemo8(
46858
+ const windowItems = useMemo9(
46679
46859
  () => entries.slice(scrollTop, scrollTop + visibleCount),
46680
46860
  [entries, scrollTop, visibleCount]
46681
46861
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nomad-e/bluma-cli",
3
- "version": "0.23.0",
3
+ "version": "0.24.0",
4
4
  "description": "BluMa independent agent for automation and advanced software engineering.",
5
5
  "author": "Alex Fonseca",
6
6
  "license": "Apache-2.0",