@nomad-e/bluma-cli 0.23.0 → 0.24.1

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 +341 -101
  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;
@@ -2286,41 +2275,40 @@ function messageExtraForTokens(msg) {
2286
2275
  }
2287
2276
  return parts.join("\0");
2288
2277
  }
2278
+ function tokensForString(s) {
2279
+ if (s.length === 0) return 0;
2280
+ let cached = tokenStringCache.get(s);
2281
+ if (cached !== void 0) return cached;
2282
+ cached = getO200kEncoding().encode(s).length;
2283
+ if (tokenStringCache.size >= TOKEN_STRING_CACHE_MAX) {
2284
+ const first = tokenStringCache.keys().next().value;
2285
+ if (first !== void 0) tokenStringCache.delete(first);
2286
+ }
2287
+ tokenStringCache.set(s, cached);
2288
+ return cached;
2289
+ }
2290
+ function tokensForMessage(msg) {
2291
+ const body = messageBodyForTokens(msg);
2292
+ const extra = messageExtraForTokens(msg);
2293
+ return tokensForString(body) + tokensForString(extra) + MESSAGE_OVERHEAD_TOKENS;
2294
+ }
2289
2295
  function countTokens(messages, useCache = true) {
2290
2296
  if (messages.length === 0) {
2291
2297
  return CONVERSATION_BASE_OVERHEAD;
2292
2298
  }
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;
2299
+ if (useCache && cacheVersion === cachedVersion) {
2300
+ return cachedCount;
2309
2301
  }
2310
- const enc = getO200kEncoding();
2311
2302
  let total = CONVERSATION_BASE_OVERHEAD;
2312
2303
  for (const msg of messages) {
2313
- const body = messageBodyForTokens(msg);
2314
- const extra = messageExtraForTokens(msg);
2315
- const nBody = body ? enc.encode(body).length : 0;
2316
- const nExtra = extra ? enc.encode(extra).length : 0;
2317
- total += nBody + nExtra + MESSAGE_OVERHEAD_TOKENS;
2304
+ total += tokensForMessage(msg);
2305
+ }
2306
+ if (useCache) {
2307
+ cachedVersion = cacheVersion;
2308
+ cachedCount = total;
2318
2309
  }
2319
2310
  return total;
2320
2311
  }
2321
- function clearTokenCountCache() {
2322
- tokenCountCache = null;
2323
- }
2324
2312
  function countToolDefinitionsTokens(tools) {
2325
2313
  if (!tools || tools.length === 0) {
2326
2314
  return 0;
@@ -2345,14 +2333,18 @@ function computeEffectiveInputBudget(rawBudget, toolDefinitions = [], options) {
2345
2333
  effectiveBudget
2346
2334
  };
2347
2335
  }
2348
- var MESSAGE_OVERHEAD_TOKENS, CONVERSATION_BASE_OVERHEAD, cachedEncoding, tokenCountCache, DEFAULT_OUTPUT_TOKEN_RESERVE, DEFAULT_PROTOCOL_OVERHEAD_TOKENS;
2336
+ var MESSAGE_OVERHEAD_TOKENS, CONVERSATION_BASE_OVERHEAD, cachedEncoding, cacheVersion, cachedVersion, cachedCount, tokenStringCache, TOKEN_STRING_CACHE_MAX, DEFAULT_OUTPUT_TOKEN_RESERVE, DEFAULT_PROTOCOL_OVERHEAD_TOKENS;
2349
2337
  var init_token_counter = __esm({
2350
2338
  "src/app/agent/core/context-api/token_counter.ts"() {
2351
2339
  "use strict";
2352
2340
  MESSAGE_OVERHEAD_TOKENS = 4;
2353
2341
  CONVERSATION_BASE_OVERHEAD = 3;
2354
2342
  cachedEncoding = null;
2355
- tokenCountCache = null;
2343
+ cacheVersion = 0;
2344
+ cachedVersion = -1;
2345
+ cachedCount = 0;
2346
+ tokenStringCache = /* @__PURE__ */ new Map();
2347
+ TOKEN_STRING_CACHE_MAX = 4096;
2356
2348
  DEFAULT_OUTPUT_TOKEN_RESERVE = 8192;
2357
2349
  DEFAULT_PROTOCOL_OVERHEAD_TOKENS = 512;
2358
2350
  }
@@ -14315,7 +14307,7 @@ import { v4 as uuidv412 } from "uuid";
14315
14307
  import chalk3 from "chalk";
14316
14308
 
14317
14309
  // 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";
14310
+ import { useState as useState25, useEffect as useEffect22, useRef as useRef13, useCallback as useCallback11, memo as memo25, useMemo as useMemo8 } from "react";
14319
14311
 
14320
14312
  // src/app/ui/layout.tsx
14321
14313
  import { memo as memo2 } from "react";
@@ -25084,8 +25076,13 @@ var HistoryCompressor = class {
25084
25076
  const tokenBudget = budgetBreakdown.effectiveBudget;
25085
25077
  const compressThreshold = options?.compressThreshold ?? COMPRESS_THRESHOLD;
25086
25078
  const keepRecentTurns = options?.keepRecentTurns ?? KEEP_RECENT_TURNS;
25087
- const sanitized = sanitizeConversationForProvider(fullHistory);
25088
- const safeHistory = sanitized.messages;
25079
+ let safeHistory;
25080
+ if (options?.skipSanitize) {
25081
+ safeHistory = fullHistory;
25082
+ } else {
25083
+ const sanitized = sanitizeConversationForProvider(fullHistory);
25084
+ safeHistory = sanitized.messages;
25085
+ }
25089
25086
  const systemMessages = [];
25090
25087
  let historyStartIndex = 0;
25091
25088
  while (historyStartIndex < safeHistory.length && safeHistory[historyStartIndex].role === "system") {
@@ -25102,7 +25099,6 @@ var HistoryCompressor = class {
25102
25099
  const thresholdTokens = tokenBudget * compressThreshold;
25103
25100
  let pendingSlices = turnSlices.slice(sliceCount, recentStart);
25104
25101
  let pendingFlat = pendingSlices.flat();
25105
- clearTokenCountCache();
25106
25102
  let messages = this.assembleMessages(systemMessages, pendingFlat, recentFlat);
25107
25103
  let tokens = countTokens(messages, true);
25108
25104
  contextEventBus.emit("context:compression_start", {
@@ -25149,12 +25145,12 @@ var HistoryCompressor = class {
25149
25145
  reason: compressError?.message || "timeout"
25150
25146
  });
25151
25147
  }
25152
- clearTokenCountCache();
25148
+ invalidateTokenCache();
25153
25149
  messages = this.assembleMessages(systemMessages, pendingFlat, recentFlat);
25154
25150
  tokens = countTokens(messages, true);
25155
25151
  }
25156
25152
  if (tokens > tokenBudget) {
25157
- clearTokenCountCache();
25153
+ invalidateTokenCache();
25158
25154
  const { capped, slices: cappedSlices } = this.enforceHardCap(
25159
25155
  systemMessages,
25160
25156
  recentSlices,
@@ -26432,7 +26428,14 @@ function cleanupOldEntries() {
26432
26428
  }
26433
26429
  function setToolResultStatus(toolCallId, status, result) {
26434
26430
  if (!toolCallId) return;
26435
- const parsedResult = typeof result === "string" ? JSON.parse(result) : result;
26431
+ let parsedResult = result;
26432
+ if (typeof result === "string") {
26433
+ try {
26434
+ parsedResult = JSON.parse(result);
26435
+ } catch {
26436
+ parsedResult = result;
26437
+ }
26438
+ }
26436
26439
  store.set(toolCallId, { status, timestamp: Date.now(), result: parsedResult });
26437
26440
  cleanupOldEntries();
26438
26441
  changed.emit();
@@ -27246,7 +27249,9 @@ var BluMaTurnCoordinator = class {
27246
27249
  this.deps.getLlmUserContext(),
27247
27250
  {
27248
27251
  tokenBudget,
27249
- toolDefinitions: this.deps.mcpClient.getAvailableTools()
27252
+ toolDefinitions: this.deps.mcpClient.getAvailableTools(),
27253
+ skipSanitize: true
27254
+ // already sanitized above
27250
27255
  }
27251
27256
  );
27252
27257
  this.turnLog.debug("Context window prepared", {
@@ -28137,11 +28142,15 @@ function hasMetUpdateThreshold(currentTokens) {
28137
28142
  // src/app/agent/memory/session_memory_update.ts
28138
28143
  init_logger();
28139
28144
  var log2 = logger.child("session_memory");
28145
+ var MIN_COOLDOWN_MS = 6e4;
28146
+ var lastUpdateTime = 0;
28140
28147
  function isSessionMemoryEnabled() {
28141
28148
  if (process.env.BLUMA_DISABLE_SESSION_MEMORY === "1") return false;
28142
28149
  return true;
28143
28150
  }
28144
28151
  function shouldUpdateSessionMemory(history) {
28152
+ const now2 = Date.now();
28153
+ if (now2 - lastUpdateTime < MIN_COOLDOWN_MS) return false;
28145
28154
  const tokens = estimateHistoryTokens(history);
28146
28155
  if (!isSessionMemoryInitialized()) {
28147
28156
  if (!hasMetInitializationThreshold(tokens)) return false;
@@ -28191,6 +28200,7 @@ async function runSessionMemoryUpdate(deps) {
28191
28200
  });
28192
28201
  recordExtractionTokenCount(estimateHistoryTokens(history));
28193
28202
  setLastCursorIndex(history.length);
28203
+ lastUpdateTime = Date.now();
28194
28204
  log2.debug("finished");
28195
28205
  } catch (err) {
28196
28206
  log2.warn("failed", { error: err instanceof Error ? err.message : String(err) });
@@ -28205,8 +28215,10 @@ function scheduleSessionMemoryUpdate(deps) {
28205
28215
 
28206
28216
  // src/app/agent/memory/background_memory.ts
28207
28217
  function runBackgroundMemoryAfterTurn(params) {
28218
+ const MAX_SNAPSHOT = 80;
28219
+ const snapshot = params.history.length > MAX_SNAPSHOT ? params.history.slice(-MAX_SNAPSHOT) : params.history;
28208
28220
  const deps = {
28209
- history: [...params.history],
28221
+ history: snapshot,
28210
28222
  sessionId: params.sessionId,
28211
28223
  llm: params.llm,
28212
28224
  mcpClient: params.mcpClient,
@@ -31761,8 +31773,37 @@ function ToolUseLoader({ state: state2 }) {
31761
31773
  }
31762
31774
 
31763
31775
  // src/app/agent/tools/shared/toolResultUtils.ts
31776
+ function sanitizeJsonString(s) {
31777
+ return s.replace(/[\x00-\x1f]/g, (ch) => {
31778
+ switch (ch) {
31779
+ case "\n":
31780
+ return "\\n";
31781
+ case "\r":
31782
+ return "\\r";
31783
+ case " ":
31784
+ return "\\t";
31785
+ case "\b":
31786
+ return "\\b";
31787
+ case "\f":
31788
+ return "\\f";
31789
+ default:
31790
+ return `\\u${ch.charCodeAt(0).toString(16).padStart(4, "0")}`;
31791
+ }
31792
+ });
31793
+ }
31764
31794
  function resolveToolPayload(result) {
31765
- const raw = typeof result === "string" ? JSON.parse(result) : result;
31795
+ let raw = result;
31796
+ if (typeof result === "string") {
31797
+ try {
31798
+ raw = JSON.parse(result);
31799
+ } catch {
31800
+ try {
31801
+ raw = JSON.parse(sanitizeJsonString(result));
31802
+ } catch {
31803
+ return result;
31804
+ }
31805
+ }
31806
+ }
31766
31807
  if (!raw) return null;
31767
31808
  if (raw?.status === "error") return { error: raw.error };
31768
31809
  if (raw?.status === "success" && "data" in raw) {
@@ -45377,6 +45418,177 @@ function ScrollBox({
45377
45418
  }
45378
45419
  var ScrollBox_default = ScrollBox;
45379
45420
 
45421
+ // src/app/ui/hooks/useVirtualScroll.ts
45422
+ import {
45423
+ useCallback as useCallback10,
45424
+ useLayoutEffect as useLayoutEffect3,
45425
+ useMemo as useMemo7,
45426
+ useRef as useRef12,
45427
+ useState as useState24
45428
+ } from "react";
45429
+ var DEFAULT_ESTIMATE = 3;
45430
+ var OVERSCAN_ROWS = 40;
45431
+ var COLD_START_COUNT = 20;
45432
+ var MAX_MOUNTED_ITEMS = 200;
45433
+ var PESSIMISTIC_HEIGHT = 1;
45434
+ function useVirtualScroll(scrollRef, itemKeys, columns) {
45435
+ const heightCache = useRef12(/* @__PURE__ */ new Map());
45436
+ const offsetVersionRef = useRef12(0);
45437
+ const lastScrollTopRef = useRef12(0);
45438
+ const offsetsRef = useRef12({
45439
+ arr: new Float64Array(0),
45440
+ version: -1,
45441
+ n: -1
45442
+ });
45443
+ const itemRefs = useRef12(/* @__PURE__ */ new Map());
45444
+ const refCache = useRef12(/* @__PURE__ */ new Map());
45445
+ const prevColumns = useRef12(columns);
45446
+ const skipMeasurementRef = useRef12(false);
45447
+ if (prevColumns.current !== columns) {
45448
+ const ratio = prevColumns.current / columns;
45449
+ prevColumns.current = columns;
45450
+ for (const [k, h] of heightCache.current) {
45451
+ heightCache.current.set(k, Math.max(1, Math.round(h * ratio)));
45452
+ }
45453
+ offsetVersionRef.current++;
45454
+ skipMeasurementRef.current = true;
45455
+ }
45456
+ const SCROLL_QUANTUM = OVERSCAN_ROWS >> 1;
45457
+ const subscribe = useCallback10(
45458
+ (cb) => scrollRef.current?.subscribe(cb) ?? (() => {
45459
+ }),
45460
+ [scrollRef]
45461
+ );
45462
+ const getSnapshot = useCallback10(() => {
45463
+ const sb = scrollRef.current;
45464
+ if (!sb) return NaN;
45465
+ const raw = sb.getScrollTop();
45466
+ const sticky = sb.isSticky();
45467
+ const quantized = Math.floor(raw / SCROLL_QUANTUM) * SCROLL_QUANTUM;
45468
+ return sticky ? -(quantized | 1) : quantized;
45469
+ }, [scrollRef, SCROLL_QUANTUM]);
45470
+ const getServerSnapshot = useCallback10(() => NaN, []);
45471
+ const scrollTop = useSyncExternalStore5(subscribe, getSnapshot, getServerSnapshot);
45472
+ const [viewportHeight, setViewportHeight] = useState24(
45473
+ () => scrollRef.current?.getViewportHeight() ?? 0
45474
+ );
45475
+ const prevVpRef = useRef12(viewportHeight);
45476
+ useLayoutEffect3(() => {
45477
+ const next = scrollRef.current?.getViewportHeight() ?? 0;
45478
+ if (next !== prevVpRef.current) {
45479
+ prevVpRef.current = next;
45480
+ setViewportHeight(next);
45481
+ }
45482
+ });
45483
+ const isSticky = scrollRef.current?.isSticky() ?? true;
45484
+ const n = itemKeys.length;
45485
+ const computeOffsets = useCallback10(() => {
45486
+ const cached = offsetsRef.current;
45487
+ if (cached.n === n && cached.version === offsetVersionRef.current && cached.arr.length === n + 1) {
45488
+ return cached.arr;
45489
+ }
45490
+ const offsets2 = new Float64Array(n + 1);
45491
+ for (let i = 0; i < n; i++) {
45492
+ const h = heightCache.current.get(itemKeys[i]) ?? DEFAULT_ESTIMATE;
45493
+ offsets2[i + 1] = offsets2[i] + h;
45494
+ }
45495
+ offsetsRef.current = { arr: offsets2, version: offsetVersionRef.current, n };
45496
+ return offsets2;
45497
+ }, [n, itemKeys, offsetVersionRef.current]);
45498
+ const offsets = computeOffsets();
45499
+ const totalHeight = offsets[n] ?? 0;
45500
+ const range = useMemo7(() => {
45501
+ if (n === 0) return [0, 0];
45502
+ if (viewportHeight <= 0) {
45503
+ return [0, Math.min(n, COLD_START_COUNT)];
45504
+ }
45505
+ const absScrollTop = Math.abs(scrollTop);
45506
+ if (isSticky) {
45507
+ let hi3 = n;
45508
+ let remaining = viewportHeight + OVERSCAN_ROWS;
45509
+ while (hi3 > 0 && remaining > 0) {
45510
+ const h = heightCache.current.get(itemKeys[hi3 - 1]) ?? DEFAULT_ESTIMATE;
45511
+ remaining -= h;
45512
+ hi3--;
45513
+ }
45514
+ const lo3 = Math.max(0, hi3 - OVERSCAN_ROWS);
45515
+ return [lo3, n];
45516
+ }
45517
+ let lo2 = 0;
45518
+ {
45519
+ const target = Math.max(0, absScrollTop - OVERSCAN_ROWS);
45520
+ let lo_ = 0;
45521
+ let hi_ = n;
45522
+ while (lo_ < hi_) {
45523
+ const mid = lo_ + hi_ >>> 1;
45524
+ if (offsets[mid] < target) lo_ = mid + 1;
45525
+ else hi_ = mid;
45526
+ }
45527
+ lo2 = lo_;
45528
+ }
45529
+ let hi2 = lo2;
45530
+ let accumulated = 0;
45531
+ const needed = viewportHeight + 2 * OVERSCAN_ROWS;
45532
+ while (hi2 < n && accumulated < needed) {
45533
+ const h = heightCache.current.get(itemKeys[hi2]) ?? PESSIMISTIC_HEIGHT;
45534
+ accumulated += h;
45535
+ hi2++;
45536
+ }
45537
+ return [lo2, Math.min(hi2, lo2 + MAX_MOUNTED_ITEMS)];
45538
+ }, [scrollTop, viewportHeight, isSticky, n, offsets, itemKeys]);
45539
+ const measureRef = useCallback10((key) => {
45540
+ if (refCache.current.has(key)) return refCache.current.get(key);
45541
+ const refFn = (el) => {
45542
+ if (skipMeasurementRef.current) return;
45543
+ if (el) {
45544
+ itemRefs.current.set(key, el);
45545
+ const h = el.yogaNode?.getComputedHeight();
45546
+ if (h != null && h > 0) {
45547
+ const prev = heightCache.current.get(key);
45548
+ if (prev !== h) {
45549
+ heightCache.current.set(key, h);
45550
+ offsetVersionRef.current++;
45551
+ }
45552
+ }
45553
+ }
45554
+ };
45555
+ refCache.current.set(key, refFn);
45556
+ return refFn;
45557
+ }, []);
45558
+ const [lo, hi] = range;
45559
+ const topSpacer = offsets[lo] ?? 0;
45560
+ const bottomSpacer = totalHeight - (offsets[hi] ?? totalHeight);
45561
+ return {
45562
+ range,
45563
+ topSpacer,
45564
+ bottomSpacer,
45565
+ offsets,
45566
+ measureRef
45567
+ };
45568
+ }
45569
+ function useSyncExternalStore5(subscribe, getSnapshot, getServerSnapshot) {
45570
+ const [value, setValue] = useState24(getSnapshot);
45571
+ const getSnapshotRef = useRef12(getSnapshot);
45572
+ getSnapshotRef.current = getSnapshot;
45573
+ useLayoutEffect3(() => {
45574
+ let didSubscribe = false;
45575
+ const checkForUpdates2 = () => {
45576
+ if (didSubscribe) {
45577
+ const newValue = getSnapshotRef.current();
45578
+ setValue(newValue);
45579
+ }
45580
+ };
45581
+ didSubscribe = true;
45582
+ checkForUpdates2();
45583
+ const unsubscribe = subscribe(checkForUpdates2);
45584
+ return () => {
45585
+ didSubscribe = false;
45586
+ unsubscribe();
45587
+ };
45588
+ }, [subscribe]);
45589
+ return value;
45590
+ }
45591
+
45380
45592
  // src/app/ui/BlumaSession.tsx
45381
45593
  import { Fragment as Fragment19, jsx as jsx104, jsxs as jsxs85 } from "react/jsx-runtime";
45382
45594
  var blumaUpdateRegistryCheckStarted = false;
@@ -45442,23 +45654,23 @@ function UserMessageWithOptionalImages({
45442
45654
  ) : /* @__PURE__ */ jsx104(Text, { color: BLUMA_TERMINAL.m3OnSurface, wrap: "wrap", children: displayRaw }) });
45443
45655
  }
45444
45656
  var BlumaSessionComponent = ({ eventBus, sessionId, cliVersion }) => {
45445
- const agentInstance = useRef12(null);
45446
- const [history, setHistory] = useState24([]);
45447
- const [statusMessage, setStatusMessage] = useState24(
45657
+ const agentInstance = useRef13(null);
45658
+ const [history, setHistory] = useState25([]);
45659
+ const [statusMessage, setStatusMessage] = useState25(
45448
45660
  "Initializing agent..."
45449
45661
  );
45450
- const [toolsCount, setToolsCount] = useState24(null);
45451
- const [mcpStatus, setMcpStatus] = useState24(
45662
+ const [toolsCount, setToolsCount] = useState25(null);
45663
+ const [mcpStatus, setMcpStatus] = useState25(
45452
45664
  "connecting"
45453
45665
  );
45454
- const [isProcessing, setIsProcessing] = useState24(true);
45455
- const [pendingConfirmation, setPendingConfirmation] = useState24(
45666
+ const [isProcessing, setIsProcessing] = useState25(true);
45667
+ const [pendingConfirmation, setPendingConfirmation] = useState25(
45456
45668
  null
45457
45669
  );
45458
- const [confirmationPreview, setConfirmationPreview] = useState24(
45670
+ const [confirmationPreview, setConfirmationPreview] = useState25(
45459
45671
  null
45460
45672
  );
45461
- const [pendingAskUserQuestions, setPendingAskUserQuestions] = useState24(null);
45673
+ const [pendingAskUserQuestions, setPendingAskUserQuestions] = useState25(null);
45462
45674
  const {
45463
45675
  tokenCount,
45464
45676
  tokenBudget,
@@ -45471,7 +45683,7 @@ var BlumaSessionComponent = ({ eventBus, sessionId, cliVersion }) => {
45471
45683
  const { currentPlan, isPlanMode, parseMessage, resetPlan } = usePlanMode();
45472
45684
  const { agentMode } = useAgentMode();
45473
45685
  const workdir = getSandboxPolicy().workspaceRoot;
45474
- const lastUpdateRef = useRef12(0);
45686
+ const lastUpdateRef = useRef13(0);
45475
45687
  useEffect22(() => {
45476
45688
  const now2 = Date.now();
45477
45689
  if (now2 - lastUpdateRef.current < 250) {
@@ -45490,29 +45702,29 @@ var BlumaSessionComponent = ({ eventBus, sessionId, cliVersion }) => {
45490
45702
  activeProjects
45491
45703
  });
45492
45704
  }, [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(() => {
45705
+ const [isInitAgentActive, setIsInitAgentActive] = useState25(false);
45706
+ const [liveToolName, setLiveToolName] = useState25(null);
45707
+ const [liveToolArgs, setLiveToolArgs] = useState25(void 0);
45708
+ const [isReasoning, setIsReasoning] = useState25(false);
45709
+ const alwaysAcceptList = useRef13([]);
45710
+ const turnStartedAtRef = useRef13(null);
45711
+ const [processingStartMs, setProcessingStartMs] = useState25(null);
45712
+ const [lastTurnDurationLabel, setLastTurnDurationLabel] = useState25(null);
45713
+ const markTurnStarted = useCallback11(() => {
45502
45714
  const t = Date.now();
45503
45715
  turnStartedAtRef.current = t;
45504
45716
  setProcessingStartMs(t);
45505
45717
  setLastTurnDurationLabel(null);
45506
45718
  }, []);
45507
- const handleWorkComplete = useCallback10((elapsedMs) => {
45719
+ const handleWorkComplete = useCallback11((elapsedMs) => {
45508
45720
  const label = formatTurnDurationMs(elapsedMs);
45509
45721
  setLastTurnDurationLabel(label);
45510
45722
  }, []);
45511
- const lastReasoningTextRef = useRef12(null);
45512
- const streamedReasoningTextRef = useRef12("");
45513
- const activeReasoningHistoryIdRef = useRef12(null);
45514
- const lastStreamAssistantKeyRef = useRef12(null);
45515
- const appendExpandPreviewToHistory = useCallback10(() => {
45723
+ const lastReasoningTextRef = useRef13(null);
45724
+ const streamedReasoningTextRef = useRef13("");
45725
+ const activeReasoningHistoryIdRef = useRef13(null);
45726
+ const lastStreamAssistantKeyRef = useRef13(null);
45727
+ const appendExpandPreviewToHistory = useCallback11(() => {
45516
45728
  const p = peekLatestExpandable();
45517
45729
  setHistory((prev) => {
45518
45730
  const id = nextHistoryId(prev);
@@ -45557,7 +45769,7 @@ var BlumaSessionComponent = ({ eventBus, sessionId, cliVersion }) => {
45557
45769
  });
45558
45770
  });
45559
45771
  }, []);
45560
- const handleInterrupt = useCallback10(() => {
45772
+ const handleInterrupt = useCallback11(() => {
45561
45773
  if (!isProcessing) return;
45562
45774
  eventBus.emit("user_interrupt");
45563
45775
  turnStartedAtRef.current = null;
@@ -45574,7 +45786,7 @@ var BlumaSessionComponent = ({ eventBus, sessionId, cliVersion }) => {
45574
45786
  ];
45575
45787
  });
45576
45788
  }, [isProcessing]);
45577
- const handleSubmit = useCallback10(
45789
+ const handleSubmit = useCallback11(
45578
45790
  (text) => {
45579
45791
  if (!text || !agentInstance.current) return;
45580
45792
  const trimmedForSlash = text.trim();
@@ -45823,7 +46035,7 @@ Please use command_status to check the result and report back to the user.`;
45823
46035
  [isProcessing, markTurnStarted]
45824
46036
  );
45825
46037
  const { snapshot: queuedMessages, clear: clearQueue } = useMessageQueue_default();
45826
- const isAgentIdle = useMemo7(
46038
+ const isAgentIdle = useMemo8(
45827
46039
  () => !isProcessing && !pendingConfirmation && !pendingAskUserQuestions?.length && !isInitAgentActive,
45828
46040
  [isProcessing, pendingConfirmation, pendingAskUserQuestions, isInitAgentActive]
45829
46041
  );
@@ -45855,7 +46067,7 @@ Please use command_status to check the result and report back to the user.`;
45855
46067
  subscription.off("clear_queue", handleClearQueue);
45856
46068
  };
45857
46069
  }, [queuedMessages.length, clearQueue]);
45858
- const handleConfirmation = useCallback10(
46070
+ const handleConfirmation = useCallback11(
45859
46071
  async (decision, toolCalls, opts) => {
45860
46072
  if (!agentInstance.current) return;
45861
46073
  setPendingConfirmation(null);
@@ -45878,7 +46090,7 @@ Please use command_status to check the result and report back to the user.`;
45878
46090
  },
45879
46091
  []
45880
46092
  );
45881
- const appendStreamedReasoning = useCallback10((reasoning) => {
46093
+ const appendStreamedReasoning = useCallback11((reasoning) => {
45882
46094
  const r = String(reasoning ?? "").trim();
45883
46095
  if (!r) return;
45884
46096
  setHistory((prev) => {
@@ -45896,7 +46108,7 @@ Please use command_status to check the result and report back to the user.`;
45896
46108
  return [...prev, { id, component }];
45897
46109
  });
45898
46110
  }, []);
45899
- const appendStreamedAssistant = useCallback10((content) => {
46111
+ const appendStreamedAssistant = useCallback11((content) => {
45900
46112
  const t = String(content ?? "").trim();
45901
46113
  if (!t) return;
45902
46114
  const key = reasoningDedupeKey(t);
@@ -45922,9 +46134,9 @@ Please use command_status to check the result and report back to the user.`;
45922
46134
  return [...prev, { id, component: nextComponent }];
45923
46135
  });
45924
46136
  }, []);
45925
- const handleConfirmationRef = useRef12(handleConfirmation);
46137
+ const handleConfirmationRef = useRef13(handleConfirmation);
45926
46138
  handleConfirmationRef.current = handleConfirmation;
45927
- const sessionIdRef = useRef12(sessionId);
46139
+ const sessionIdRef = useRef13(sessionId);
45928
46140
  sessionIdRef.current = sessionId;
45929
46141
  useEffect22(() => {
45930
46142
  const initializeAgent = async () => {
@@ -46217,17 +46429,17 @@ Please use command_status to check the result and report back to the user.`;
46217
46429
  eventBus.off("backend_message", handleBackendMessage);
46218
46430
  };
46219
46431
  }, [eventBus, handleConfirmation]);
46220
- const handleAnswerQuestion = useCallback10((answerJson) => {
46432
+ const handleAnswerQuestion = useCallback11((answerJson) => {
46221
46433
  setPendingAskUserQuestions(null);
46222
46434
  eventBus.emit("ask_user_question_answer", { answer: answerJson });
46223
46435
  }, [eventBus]);
46224
- const handleCancelQuestion = useCallback10(() => {
46436
+ const handleCancelQuestion = useCallback11(() => {
46225
46437
  setPendingAskUserQuestions(null);
46226
46438
  eventBus.emit("ask_user_question_answer", {
46227
46439
  answer: JSON.stringify({ success: false, cancelled: true })
46228
46440
  });
46229
46441
  }, [eventBus]);
46230
- const handleDecision = useCallback10(async (decision) => {
46442
+ const handleDecision = useCallback11(async (decision) => {
46231
46443
  if (!pendingConfirmation || pendingConfirmation.length === 0) return;
46232
46444
  const [currentToolCall, ...remainingToolCalls] = pendingConfirmation;
46233
46445
  if (!currentToolCall) return;
@@ -46244,8 +46456,33 @@ Please use command_status to check the result and report back to the user.`;
46244
46456
  setIsProcessing(false);
46245
46457
  }
46246
46458
  }, [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)),
46459
+ const scrollBoxRef = useRef13(null);
46460
+ const { columns } = useTerminalSize();
46461
+ const historyKeys = useMemo8(
46462
+ () => history.map((item) => String(item.id)),
46463
+ [history.length, history.length > 0 ? history[history.length - 1].id : 0]
46464
+ );
46465
+ const { range, topSpacer, bottomSpacer, measureRef } = useVirtualScroll(
46466
+ scrollBoxRef,
46467
+ historyKeys,
46468
+ columns
46469
+ );
46470
+ const [lo, hi] = range;
46471
+ const mountedItems = useMemo8(() => {
46472
+ return history.slice(lo, hi);
46473
+ }, [history, lo, hi]);
46474
+ 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: [
46475
+ topSpacer > 0 && /* @__PURE__ */ jsx104(Box_default, { height: topSpacer }),
46476
+ mountedItems.map((item) => /* @__PURE__ */ jsx104(
46477
+ Box_default,
46478
+ {
46479
+ flexDirection: "column",
46480
+ ref: measureRef(String(item.id)),
46481
+ children: item.component
46482
+ },
46483
+ item.id
46484
+ )),
46485
+ bottomSpacer > 0 && /* @__PURE__ */ jsx104(Box_default, { height: bottomSpacer }),
46249
46486
  /* @__PURE__ */ jsx104(
46250
46487
  StreamingText,
46251
46488
  {
@@ -46258,10 +46495,13 @@ Please use command_status to check the result and report back to the user.`;
46258
46495
  appendStreamedAssistant,
46259
46496
  appendStreamedReasoning,
46260
46497
  eventBus,
46261
- history,
46498
+ mountedItems,
46499
+ topSpacer,
46500
+ bottomSpacer,
46501
+ measureRef,
46262
46502
  liveToolName
46263
46503
  ]);
46264
- const bottomDock = useMemo7(() => /* @__PURE__ */ jsx104(
46504
+ const bottomDock = useMemo8(() => /* @__PURE__ */ jsx104(
46265
46505
  BlumaBottomDock,
46266
46506
  {
46267
46507
  eventBus,
@@ -46311,9 +46551,9 @@ Please use command_status to check the result and report back to the user.`;
46311
46551
  statusMessage,
46312
46552
  workdir
46313
46553
  ]);
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, {}), []);
46554
+ const header = useMemo8(() => /* @__PURE__ */ jsx104(Header, { sessionId, workdir, cliVersion }), [cliVersion, sessionId, workdir]);
46555
+ const overlay = useMemo8(() => /* @__PURE__ */ jsx104(BlumaWorkersOverlay, { sessionId }), [sessionId]);
46556
+ const floating = useMemo8(() => /* @__PURE__ */ jsx104(CoordinatorTaskPanel, {}), []);
46317
46557
  return /* @__PURE__ */ jsx104(
46318
46558
  BlumaViewport,
46319
46559
  {
@@ -46497,7 +46737,7 @@ import React39 from "react";
46497
46737
  init_agent_session_paths();
46498
46738
 
46499
46739
  // 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";
46740
+ import { memo as memo26, useCallback as useCallback12, useEffect as useEffect23, useMemo as useMemo9, useState as useState26 } from "react";
46501
46741
 
46502
46742
  // src/app/agent/session_manager/session_resume_browser.ts
46503
46743
  init_bluma_app_dir();
@@ -46644,12 +46884,12 @@ var SessionResumePickerComponent = ({
46644
46884
  4,
46645
46885
  Math.min(VISIBLE_DEFAULT, (stdout?.rows ?? 24) - 14)
46646
46886
  );
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) => {
46887
+ const [cwd2, setCwd] = useState26("");
46888
+ const [entries, setEntries] = useState26([]);
46889
+ const [loading, setLoading] = useState26(true);
46890
+ const [selectedIndex, setSelectedIndex] = useState26(0);
46891
+ const [scrollTop, setScrollTop] = useState26(0);
46892
+ const reload = useCallback12(async (rel) => {
46653
46893
  setLoading(true);
46654
46894
  try {
46655
46895
  const list = await listSessionBrowserEntries(rel);
@@ -46675,7 +46915,7 @@ var SessionResumePickerComponent = ({
46675
46915
  setScrollTop(selectedIndex - visibleCount + 1);
46676
46916
  }
46677
46917
  }, [selectedIndex, scrollTop, visibleCount]);
46678
- const windowItems = useMemo8(
46918
+ const windowItems = useMemo9(
46679
46919
  () => entries.slice(scrollTop, scrollTop + visibleCount),
46680
46920
  [entries, scrollTop, visibleCount]
46681
46921
  );
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.1",
4
4
  "description": "BluMa independent agent for automation and advanced software engineering.",
5
5
  "author": "Alex Fonseca",
6
6
  "license": "Apache-2.0",