@nomad-e/bluma-cli 0.22.2 → 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 +406 -112
  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,11 +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
- }
2282
+ if (useCache && cacheVersion === cachedVersion) {
2283
+ return cachedCount;
2298
2284
  }
2299
2285
  const enc = getO200kEncoding();
2300
2286
  let total = CONVERSATION_BASE_OVERHEAD;
@@ -2306,13 +2292,11 @@ function countTokens(messages, useCache = true) {
2306
2292
  total += nBody + nExtra + MESSAGE_OVERHEAD_TOKENS;
2307
2293
  }
2308
2294
  if (useCache) {
2309
- tokenCountCache = { hash: hashHistory(messages), count: total };
2295
+ cachedVersion = cacheVersion;
2296
+ cachedCount = total;
2310
2297
  }
2311
2298
  return total;
2312
2299
  }
2313
- function clearTokenCountCache() {
2314
- tokenCountCache = null;
2315
- }
2316
2300
  function countToolDefinitionsTokens(tools) {
2317
2301
  if (!tools || tools.length === 0) {
2318
2302
  return 0;
@@ -2337,14 +2321,16 @@ function computeEffectiveInputBudget(rawBudget, toolDefinitions = [], options) {
2337
2321
  effectiveBudget
2338
2322
  };
2339
2323
  }
2340
- 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;
2341
2325
  var init_token_counter = __esm({
2342
2326
  "src/app/agent/core/context-api/token_counter.ts"() {
2343
2327
  "use strict";
2344
2328
  MESSAGE_OVERHEAD_TOKENS = 4;
2345
2329
  CONVERSATION_BASE_OVERHEAD = 3;
2346
2330
  cachedEncoding = null;
2347
- tokenCountCache = null;
2331
+ cacheVersion = 0;
2332
+ cachedVersion = -1;
2333
+ cachedCount = 0;
2348
2334
  DEFAULT_OUTPUT_TOKEN_RESERVE = 8192;
2349
2335
  DEFAULT_PROTOCOL_OVERHEAD_TOKENS = 512;
2350
2336
  }
@@ -14307,7 +14293,7 @@ import { v4 as uuidv412 } from "uuid";
14307
14293
  import chalk3 from "chalk";
14308
14294
 
14309
14295
  // src/app/ui/BlumaSession.tsx
14310
- 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";
14311
14297
 
14312
14298
  // src/app/ui/layout.tsx
14313
14299
  import { memo as memo2 } from "react";
@@ -18151,6 +18137,43 @@ async function readMcpResource(args) {
18151
18137
  });
18152
18138
  }
18153
18139
  }
18140
+ async function listMcpPrompts(args) {
18141
+ const mcp = mcpRef;
18142
+ if (!mcp) {
18143
+ return JSON.stringify({ success: false, error: "MCP client not registered." });
18144
+ }
18145
+ try {
18146
+ const rows = await mcp.listPromptsFromAllServers();
18147
+ const server = args?.server?.trim();
18148
+ const filtered = server ? rows.filter((r) => r.server === server) : rows;
18149
+ return JSON.stringify({ success: true, servers: filtered });
18150
+ } catch (e) {
18151
+ return JSON.stringify({
18152
+ success: false,
18153
+ error: e?.message || String(e)
18154
+ });
18155
+ }
18156
+ }
18157
+ async function getMcpPrompt(args) {
18158
+ const mcp = mcpRef;
18159
+ if (!mcp) {
18160
+ return JSON.stringify({ success: false, error: "MCP client not registered." });
18161
+ }
18162
+ const server = String(args?.server || "").trim();
18163
+ const name = String(args?.name || "").trim();
18164
+ if (!server || !name) {
18165
+ return JSON.stringify({ success: false, error: "server and name are required." });
18166
+ }
18167
+ try {
18168
+ const result = await mcp.getPromptFromServer(server, name, args.arguments);
18169
+ return JSON.stringify({ success: true, ...result });
18170
+ } catch (e) {
18171
+ return JSON.stringify({
18172
+ success: false,
18173
+ error: e?.message || String(e)
18174
+ });
18175
+ }
18176
+ }
18154
18177
 
18155
18178
  // src/app/agent/tools/SessionCronTool/SessionCronTool.ts
18156
18179
  import { randomBytes } from "crypto";
@@ -21244,6 +21267,28 @@ var NATIVE_TOOL_ENTRIES = [
21244
21267
  },
21245
21268
  implementation: readMcpResource
21246
21269
  },
21270
+ {
21271
+ metadata: {
21272
+ name: "list_mcp_prompts",
21273
+ category: "knowledge",
21274
+ riskLevel: "safe",
21275
+ autoApproveInLocal: true,
21276
+ autoApproveInSandbox: true,
21277
+ description: "List prompts from connected MCP servers. Each prompt has a name, description, and optional arguments."
21278
+ },
21279
+ implementation: listMcpPrompts
21280
+ },
21281
+ {
21282
+ metadata: {
21283
+ name: "get_mcp_prompt",
21284
+ category: "knowledge",
21285
+ riskLevel: "safe",
21286
+ autoApproveInLocal: true,
21287
+ autoApproveInSandbox: true,
21288
+ description: "Get a specific prompt from an MCP server by server name and prompt name. Returns messages content."
21289
+ },
21290
+ implementation: getMcpPrompt
21291
+ },
21247
21292
  {
21248
21293
  metadata: {
21249
21294
  name: "cron_create",
@@ -21665,8 +21710,33 @@ function normalizeToolResult(value) {
21665
21710
  }
21666
21711
  return { status: "success", data: value };
21667
21712
  }
21713
+ var MAX_TOOL_RESULT_CHARS = 12e3;
21714
+ function truncateToolResultData(result) {
21715
+ if (result.status === "error") return result;
21716
+ const raw = JSON.stringify(result, null, 2);
21717
+ if (raw.length <= MAX_TOOL_RESULT_CHARS) return result;
21718
+ const d = result.data;
21719
+ if (typeof d === "string") {
21720
+ const budget = MAX_TOOL_RESULT_CHARS - 80;
21721
+ return { ...result, data: `${d.slice(0, budget)}
21722
+
21723
+ [\u2026 truncated \u2014 ${d.length} chars total \u2026]` };
21724
+ }
21725
+ if (d != null) {
21726
+ try {
21727
+ const full = JSON.stringify(d, null, 2);
21728
+ const budget = MAX_TOOL_RESULT_CHARS - 80;
21729
+ const truncated = `${full.slice(0, budget)}
21730
+
21731
+ [\u2026 truncated \u2014 ${full.length} chars total \u2026]`;
21732
+ return { ...result, data: truncated };
21733
+ } catch {
21734
+ }
21735
+ }
21736
+ return result;
21737
+ }
21668
21738
  function toolResultToString(value) {
21669
- const result = normalizeToolResult(value);
21739
+ const result = truncateToolResultData(normalizeToolResult(value));
21670
21740
  return JSON.stringify(result, null, 2);
21671
21741
  }
21672
21742
  function extractMessageToolPayload(parsed) {
@@ -21944,6 +22014,37 @@ var MCPClient = class {
21944
22014
  }
21945
22015
  return client.readResource({ uri });
21946
22016
  }
22017
+ // --- Prompts API ---
22018
+ /** List prompts from every connected MCP server. */
22019
+ async listPromptsFromAllServers() {
22020
+ const out = [];
22021
+ for (const [serverName, client] of this.sessions) {
22022
+ try {
22023
+ const r = await client.listPrompts({});
22024
+ const prompts = (r.prompts || []).map((p) => ({
22025
+ name: p.name,
22026
+ description: p.description,
22027
+ arguments: p.arguments
22028
+ }));
22029
+ out.push({ server: serverName, prompts });
22030
+ } catch (e) {
22031
+ out.push({
22032
+ server: serverName,
22033
+ prompts: [],
22034
+ error: e?.message || String(e)
22035
+ });
22036
+ }
22037
+ }
22038
+ return out;
22039
+ }
22040
+ /** Get a specific prompt from a specific server. */
22041
+ async getPromptFromServer(server, promptName, args) {
22042
+ const client = this.sessions.get(server);
22043
+ if (!client) {
22044
+ throw new Error(`Unknown MCP server: ${server}`);
22045
+ }
22046
+ return client.getPrompt({ name: promptName, arguments: args });
22047
+ }
21947
22048
  // New: detailed list for UI with origin metadata
21948
22049
  getAvailableToolsDetailed() {
21949
22050
  const isSandboxMode = process.env.BLUMA_SANDBOX === "true";
@@ -24912,8 +25013,8 @@ Operating rule: continue from the next steps and the latest user instruction. If
24912
25013
  // src/app/agent/core/context-api/history_compressor.ts
24913
25014
  init_token_counter();
24914
25015
  var COMPRESSION_TIMEOUT_MS = 15e3;
24915
- var COMPRESS_THRESHOLD = 0.95;
24916
- var KEEP_RECENT_TURNS = 10;
25016
+ var COMPRESS_THRESHOLD = 0.85;
25017
+ var KEEP_RECENT_TURNS = 8;
24917
25018
  var DEFAULT_CONTEXT_INPUT_BUDGET = 1e5;
24918
25019
  var HistoryCompressor = class {
24919
25020
  anchor;
@@ -24961,8 +25062,13 @@ var HistoryCompressor = class {
24961
25062
  const tokenBudget = budgetBreakdown.effectiveBudget;
24962
25063
  const compressThreshold = options?.compressThreshold ?? COMPRESS_THRESHOLD;
24963
25064
  const keepRecentTurns = options?.keepRecentTurns ?? KEEP_RECENT_TURNS;
24964
- const sanitized = sanitizeConversationForProvider(fullHistory);
24965
- 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
+ }
24966
25072
  const systemMessages = [];
24967
25073
  let historyStartIndex = 0;
24968
25074
  while (historyStartIndex < safeHistory.length && safeHistory[historyStartIndex].role === "system") {
@@ -24979,7 +25085,6 @@ var HistoryCompressor = class {
24979
25085
  const thresholdTokens = tokenBudget * compressThreshold;
24980
25086
  let pendingSlices = turnSlices.slice(sliceCount, recentStart);
24981
25087
  let pendingFlat = pendingSlices.flat();
24982
- clearTokenCountCache();
24983
25088
  let messages = this.assembleMessages(systemMessages, pendingFlat, recentFlat);
24984
25089
  let tokens = countTokens(messages, true);
24985
25090
  contextEventBus.emit("context:compression_start", {
@@ -25026,12 +25131,12 @@ var HistoryCompressor = class {
25026
25131
  reason: compressError?.message || "timeout"
25027
25132
  });
25028
25133
  }
25029
- clearTokenCountCache();
25134
+ invalidateTokenCache();
25030
25135
  messages = this.assembleMessages(systemMessages, pendingFlat, recentFlat);
25031
25136
  tokens = countTokens(messages, true);
25032
25137
  }
25033
25138
  if (tokens > tokenBudget) {
25034
- clearTokenCountCache();
25139
+ invalidateTokenCache();
25035
25140
  const { capped, slices: cappedSlices } = this.enforceHardCap(
25036
25141
  systemMessages,
25037
25142
  recentSlices,
@@ -27123,7 +27228,9 @@ var BluMaTurnCoordinator = class {
27123
27228
  this.deps.getLlmUserContext(),
27124
27229
  {
27125
27230
  tokenBudget,
27126
- toolDefinitions: this.deps.mcpClient.getAvailableTools()
27231
+ toolDefinitions: this.deps.mcpClient.getAvailableTools(),
27232
+ skipSanitize: true
27233
+ // already sanitized above
27127
27234
  }
27128
27235
  );
27129
27236
  this.turnLog.debug("Context window prepared", {
@@ -27780,6 +27887,7 @@ function toolResultToContent(result) {
27780
27887
  return String(result);
27781
27888
  }
27782
27889
  }
27890
+ var MAX_FORK_HISTORY_MESSAGES = 60;
27783
27891
  async function runMemorySubagent(params) {
27784
27892
  const {
27785
27893
  llm,
@@ -27794,8 +27902,12 @@ async function runMemorySubagent(params) {
27794
27902
  const allTools = mcpClient.getAvailableTools();
27795
27903
  const toolSet = new Set(SUBAGENT_TOOLS);
27796
27904
  const tools = allTools.filter((t) => toolSet.has(t.function?.name ?? ""));
27905
+ const systemMsg = forkHistory.find((m) => m.role === "system");
27906
+ const nonSystem = forkHistory.filter((m) => m.role !== "system");
27907
+ const recentHistory = nonSystem.slice(-MAX_FORK_HISTORY_MESSAGES);
27908
+ const forkedHistory = systemMsg ? [systemMsg, ...recentHistory] : recentHistory;
27797
27909
  const messages = [
27798
- ...forkHistory,
27910
+ ...forkedHistory,
27799
27911
  { role: "user", content: userPrompt }
27800
27912
  ];
27801
27913
  const systemNote = "You are a background memory subagent. Complete the memory task and stop. Do not chat with the user." + (systemSuffix ? `
@@ -28009,11 +28121,15 @@ function hasMetUpdateThreshold(currentTokens) {
28009
28121
  // src/app/agent/memory/session_memory_update.ts
28010
28122
  init_logger();
28011
28123
  var log2 = logger.child("session_memory");
28124
+ var MIN_COOLDOWN_MS = 6e4;
28125
+ var lastUpdateTime = 0;
28012
28126
  function isSessionMemoryEnabled() {
28013
28127
  if (process.env.BLUMA_DISABLE_SESSION_MEMORY === "1") return false;
28014
28128
  return true;
28015
28129
  }
28016
28130
  function shouldUpdateSessionMemory(history) {
28131
+ const now2 = Date.now();
28132
+ if (now2 - lastUpdateTime < MIN_COOLDOWN_MS) return false;
28017
28133
  const tokens = estimateHistoryTokens(history);
28018
28134
  if (!isSessionMemoryInitialized()) {
28019
28135
  if (!hasMetInitializationThreshold(tokens)) return false;
@@ -28063,6 +28179,7 @@ async function runSessionMemoryUpdate(deps) {
28063
28179
  });
28064
28180
  recordExtractionTokenCount(estimateHistoryTokens(history));
28065
28181
  setLastCursorIndex(history.length);
28182
+ lastUpdateTime = Date.now();
28066
28183
  log2.debug("finished");
28067
28184
  } catch (err) {
28068
28185
  log2.warn("failed", { error: err instanceof Error ? err.message : String(err) });
@@ -28077,8 +28194,10 @@ function scheduleSessionMemoryUpdate(deps) {
28077
28194
 
28078
28195
  // src/app/agent/memory/background_memory.ts
28079
28196
  function runBackgroundMemoryAfterTurn(params) {
28197
+ const MAX_SNAPSHOT = 80;
28198
+ const snapshot = params.history.length > MAX_SNAPSHOT ? params.history.slice(-MAX_SNAPSHOT) : params.history;
28080
28199
  const deps = {
28081
- history: [...params.history],
28200
+ history: snapshot,
28082
28201
  sessionId: params.sessionId,
28083
28202
  llm: params.llm,
28084
28203
  mcpClient: params.mcpClient,
@@ -40884,7 +41003,7 @@ var renderPluginsSnapshot = () => {
40884
41003
  "global: ",
40885
41004
  dirs.global
40886
41005
  ] }),
40887
- /* @__PURE__ */ jsx94(Box_default, { marginTop: 1, flexDirection: "column", children: plugins.length === 0 ? /* @__PURE__ */ jsx94(Text, { dimColor: true, children: "No plugins installed." }) : plugins.slice(0, 12).map((plugin) => /* @__PURE__ */ jsxs77(Text, { dimColor: true, children: [
41006
+ /* @__PURE__ */ jsx94(Box_default, { marginTop: 1, flexDirection: "column", children: plugins.length === 0 ? /* @__PURE__ */ jsx94(Text, { dimColor: true, children: "No plugins installed." }) : plugins.map((plugin) => /* @__PURE__ */ jsxs77(Text, { dimColor: true, children: [
40888
41007
  plugin.name,
40889
41008
  " \xB7 ",
40890
41009
  plugin.source,
@@ -41273,18 +41392,11 @@ var renderMcp = (filter) => {
41273
41392
  filter ? `matching "${filter}"` : "total"
41274
41393
  ] })
41275
41394
  ] }),
41276
- /* @__PURE__ */ jsxs78(Box_default, { flexDirection: "column", children: [
41277
- filtered.length === 0 ? /* @__PURE__ */ jsx95(Text, { dimColor: true, children: "No MCP tools found." }) : filtered.slice(0, 15).map((tool) => /* @__PURE__ */ jsxs78(Text, { dimColor: true, children: [
41278
- tool.name,
41279
- " \xB7 ",
41280
- tool.description || "no description"
41281
- ] }, tool.name)),
41282
- filtered.length > 15 && /* @__PURE__ */ jsxs78(Text, { dimColor: true, children: [
41283
- "\u2026 +",
41284
- filtered.length - 15,
41285
- " more"
41286
- ] })
41287
- ] })
41395
+ /* @__PURE__ */ jsx95(Box_default, { flexDirection: "column", children: filtered.length === 0 ? /* @__PURE__ */ jsx95(Text, { dimColor: true, children: "No MCP tools found." }) : filtered.map((tool) => /* @__PURE__ */ jsxs78(Text, { dimColor: true, children: [
41396
+ tool.name,
41397
+ " \xB7 ",
41398
+ tool.description || "no description"
41399
+ ] }, tool.name)) })
41288
41400
  ] })
41289
41401
  );
41290
41402
  };
@@ -41302,18 +41414,11 @@ var renderTools = (filter) => {
41302
41414
  filter ? `matching "${filter}"` : "total"
41303
41415
  ] })
41304
41416
  ] }),
41305
- /* @__PURE__ */ jsxs78(Box_default, { flexDirection: "column", children: [
41306
- filtered.length === 0 ? /* @__PURE__ */ jsx95(Text, { dimColor: true, children: "No native tools found." }) : filtered.slice(0, 15).map((tool) => /* @__PURE__ */ jsxs78(Text, { dimColor: true, children: [
41307
- tool.name,
41308
- " \xB7 ",
41309
- tool.description || "no description"
41310
- ] }, tool.name)),
41311
- filtered.length > 15 && /* @__PURE__ */ jsxs78(Text, { dimColor: true, children: [
41312
- "\u2026 +",
41313
- filtered.length - 15,
41314
- " more"
41315
- ] })
41316
- ] })
41417
+ /* @__PURE__ */ jsx95(Box_default, { flexDirection: "column", children: filtered.length === 0 ? /* @__PURE__ */ jsx95(Text, { dimColor: true, children: "No native tools found." }) : filtered.map((tool) => /* @__PURE__ */ jsxs78(Text, { dimColor: true, children: [
41418
+ tool.name,
41419
+ " \xB7 ",
41420
+ tool.description || "no description"
41421
+ ] }, tool.name)) })
41317
41422
  ] })
41318
41423
  );
41319
41424
  };
@@ -45263,6 +45368,167 @@ function ScrollBox({
45263
45368
  }
45264
45369
  var ScrollBox_default = ScrollBox;
45265
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
+
45266
45532
  // src/app/ui/BlumaSession.tsx
45267
45533
  import { Fragment as Fragment19, jsx as jsx104, jsxs as jsxs85 } from "react/jsx-runtime";
45268
45534
  var blumaUpdateRegistryCheckStarted = false;
@@ -45328,23 +45594,23 @@ function UserMessageWithOptionalImages({
45328
45594
  ) : /* @__PURE__ */ jsx104(Text, { color: BLUMA_TERMINAL.m3OnSurface, wrap: "wrap", children: displayRaw }) });
45329
45595
  }
45330
45596
  var BlumaSessionComponent = ({ eventBus, sessionId, cliVersion }) => {
45331
- const agentInstance = useRef12(null);
45332
- const [history, setHistory] = useState24([]);
45333
- const [statusMessage, setStatusMessage] = useState24(
45597
+ const agentInstance = useRef13(null);
45598
+ const [history, setHistory] = useState25([]);
45599
+ const [statusMessage, setStatusMessage] = useState25(
45334
45600
  "Initializing agent..."
45335
45601
  );
45336
- const [toolsCount, setToolsCount] = useState24(null);
45337
- const [mcpStatus, setMcpStatus] = useState24(
45602
+ const [toolsCount, setToolsCount] = useState25(null);
45603
+ const [mcpStatus, setMcpStatus] = useState25(
45338
45604
  "connecting"
45339
45605
  );
45340
- const [isProcessing, setIsProcessing] = useState24(true);
45341
- const [pendingConfirmation, setPendingConfirmation] = useState24(
45606
+ const [isProcessing, setIsProcessing] = useState25(true);
45607
+ const [pendingConfirmation, setPendingConfirmation] = useState25(
45342
45608
  null
45343
45609
  );
45344
- const [confirmationPreview, setConfirmationPreview] = useState24(
45610
+ const [confirmationPreview, setConfirmationPreview] = useState25(
45345
45611
  null
45346
45612
  );
45347
- const [pendingAskUserQuestions, setPendingAskUserQuestions] = useState24(null);
45613
+ const [pendingAskUserQuestions, setPendingAskUserQuestions] = useState25(null);
45348
45614
  const {
45349
45615
  tokenCount,
45350
45616
  tokenBudget,
@@ -45357,7 +45623,7 @@ var BlumaSessionComponent = ({ eventBus, sessionId, cliVersion }) => {
45357
45623
  const { currentPlan, isPlanMode, parseMessage, resetPlan } = usePlanMode();
45358
45624
  const { agentMode } = useAgentMode();
45359
45625
  const workdir = getSandboxPolicy().workspaceRoot;
45360
- const lastUpdateRef = useRef12(0);
45626
+ const lastUpdateRef = useRef13(0);
45361
45627
  useEffect22(() => {
45362
45628
  const now2 = Date.now();
45363
45629
  if (now2 - lastUpdateRef.current < 250) {
@@ -45376,29 +45642,29 @@ var BlumaSessionComponent = ({ eventBus, sessionId, cliVersion }) => {
45376
45642
  activeProjects
45377
45643
  });
45378
45644
  }, [tokenCount, tokenBudget, isCompressing, compressionProgress, agentMode, isPlanMode, workdir, activeProjects]);
45379
- const [isInitAgentActive, setIsInitAgentActive] = useState24(false);
45380
- const [liveToolName, setLiveToolName] = useState24(null);
45381
- const [liveToolArgs, setLiveToolArgs] = useState24(void 0);
45382
- const [isReasoning, setIsReasoning] = useState24(false);
45383
- const alwaysAcceptList = useRef12([]);
45384
- const turnStartedAtRef = useRef12(null);
45385
- const [processingStartMs, setProcessingStartMs] = useState24(null);
45386
- const [lastTurnDurationLabel, setLastTurnDurationLabel] = useState24(null);
45387
- 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(() => {
45388
45654
  const t = Date.now();
45389
45655
  turnStartedAtRef.current = t;
45390
45656
  setProcessingStartMs(t);
45391
45657
  setLastTurnDurationLabel(null);
45392
45658
  }, []);
45393
- const handleWorkComplete = useCallback10((elapsedMs) => {
45659
+ const handleWorkComplete = useCallback11((elapsedMs) => {
45394
45660
  const label = formatTurnDurationMs(elapsedMs);
45395
45661
  setLastTurnDurationLabel(label);
45396
45662
  }, []);
45397
- const lastReasoningTextRef = useRef12(null);
45398
- const streamedReasoningTextRef = useRef12("");
45399
- const activeReasoningHistoryIdRef = useRef12(null);
45400
- const lastStreamAssistantKeyRef = useRef12(null);
45401
- 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(() => {
45402
45668
  const p = peekLatestExpandable();
45403
45669
  setHistory((prev) => {
45404
45670
  const id = nextHistoryId(prev);
@@ -45443,7 +45709,7 @@ var BlumaSessionComponent = ({ eventBus, sessionId, cliVersion }) => {
45443
45709
  });
45444
45710
  });
45445
45711
  }, []);
45446
- const handleInterrupt = useCallback10(() => {
45712
+ const handleInterrupt = useCallback11(() => {
45447
45713
  if (!isProcessing) return;
45448
45714
  eventBus.emit("user_interrupt");
45449
45715
  turnStartedAtRef.current = null;
@@ -45460,7 +45726,7 @@ var BlumaSessionComponent = ({ eventBus, sessionId, cliVersion }) => {
45460
45726
  ];
45461
45727
  });
45462
45728
  }, [isProcessing]);
45463
- const handleSubmit = useCallback10(
45729
+ const handleSubmit = useCallback11(
45464
45730
  (text) => {
45465
45731
  if (!text || !agentInstance.current) return;
45466
45732
  const trimmedForSlash = text.trim();
@@ -45709,7 +45975,7 @@ Please use command_status to check the result and report back to the user.`;
45709
45975
  [isProcessing, markTurnStarted]
45710
45976
  );
45711
45977
  const { snapshot: queuedMessages, clear: clearQueue } = useMessageQueue_default();
45712
- const isAgentIdle = useMemo7(
45978
+ const isAgentIdle = useMemo8(
45713
45979
  () => !isProcessing && !pendingConfirmation && !pendingAskUserQuestions?.length && !isInitAgentActive,
45714
45980
  [isProcessing, pendingConfirmation, pendingAskUserQuestions, isInitAgentActive]
45715
45981
  );
@@ -45741,7 +46007,7 @@ Please use command_status to check the result and report back to the user.`;
45741
46007
  subscription.off("clear_queue", handleClearQueue);
45742
46008
  };
45743
46009
  }, [queuedMessages.length, clearQueue]);
45744
- const handleConfirmation = useCallback10(
46010
+ const handleConfirmation = useCallback11(
45745
46011
  async (decision, toolCalls, opts) => {
45746
46012
  if (!agentInstance.current) return;
45747
46013
  setPendingConfirmation(null);
@@ -45764,7 +46030,7 @@ Please use command_status to check the result and report back to the user.`;
45764
46030
  },
45765
46031
  []
45766
46032
  );
45767
- const appendStreamedReasoning = useCallback10((reasoning) => {
46033
+ const appendStreamedReasoning = useCallback11((reasoning) => {
45768
46034
  const r = String(reasoning ?? "").trim();
45769
46035
  if (!r) return;
45770
46036
  setHistory((prev) => {
@@ -45782,7 +46048,7 @@ Please use command_status to check the result and report back to the user.`;
45782
46048
  return [...prev, { id, component }];
45783
46049
  });
45784
46050
  }, []);
45785
- const appendStreamedAssistant = useCallback10((content) => {
46051
+ const appendStreamedAssistant = useCallback11((content) => {
45786
46052
  const t = String(content ?? "").trim();
45787
46053
  if (!t) return;
45788
46054
  const key = reasoningDedupeKey(t);
@@ -45808,9 +46074,9 @@ Please use command_status to check the result and report back to the user.`;
45808
46074
  return [...prev, { id, component: nextComponent }];
45809
46075
  });
45810
46076
  }, []);
45811
- const handleConfirmationRef = useRef12(handleConfirmation);
46077
+ const handleConfirmationRef = useRef13(handleConfirmation);
45812
46078
  handleConfirmationRef.current = handleConfirmation;
45813
- const sessionIdRef = useRef12(sessionId);
46079
+ const sessionIdRef = useRef13(sessionId);
45814
46080
  sessionIdRef.current = sessionId;
45815
46081
  useEffect22(() => {
45816
46082
  const initializeAgent = async () => {
@@ -46103,17 +46369,17 @@ Please use command_status to check the result and report back to the user.`;
46103
46369
  eventBus.off("backend_message", handleBackendMessage);
46104
46370
  };
46105
46371
  }, [eventBus, handleConfirmation]);
46106
- const handleAnswerQuestion = useCallback10((answerJson) => {
46372
+ const handleAnswerQuestion = useCallback11((answerJson) => {
46107
46373
  setPendingAskUserQuestions(null);
46108
46374
  eventBus.emit("ask_user_question_answer", { answer: answerJson });
46109
46375
  }, [eventBus]);
46110
- const handleCancelQuestion = useCallback10(() => {
46376
+ const handleCancelQuestion = useCallback11(() => {
46111
46377
  setPendingAskUserQuestions(null);
46112
46378
  eventBus.emit("ask_user_question_answer", {
46113
46379
  answer: JSON.stringify({ success: false, cancelled: true })
46114
46380
  });
46115
46381
  }, [eventBus]);
46116
- const handleDecision = useCallback10(async (decision) => {
46382
+ const handleDecision = useCallback11(async (decision) => {
46117
46383
  if (!pendingConfirmation || pendingConfirmation.length === 0) return;
46118
46384
  const [currentToolCall, ...remainingToolCalls] = pendingConfirmation;
46119
46385
  if (!currentToolCall) return;
@@ -46130,8 +46396,33 @@ Please use command_status to check the result and report back to the user.`;
46130
46396
  setIsProcessing(false);
46131
46397
  }
46132
46398
  }, [handleConfirmation, pendingConfirmation]);
46133
- 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: [
46134
- 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 }),
46135
46426
  /* @__PURE__ */ jsx104(
46136
46427
  StreamingText,
46137
46428
  {
@@ -46144,10 +46435,13 @@ Please use command_status to check the result and report back to the user.`;
46144
46435
  appendStreamedAssistant,
46145
46436
  appendStreamedReasoning,
46146
46437
  eventBus,
46147
- history,
46438
+ mountedItems,
46439
+ topSpacer,
46440
+ bottomSpacer,
46441
+ measureRef,
46148
46442
  liveToolName
46149
46443
  ]);
46150
- const bottomDock = useMemo7(() => /* @__PURE__ */ jsx104(
46444
+ const bottomDock = useMemo8(() => /* @__PURE__ */ jsx104(
46151
46445
  BlumaBottomDock,
46152
46446
  {
46153
46447
  eventBus,
@@ -46197,9 +46491,9 @@ Please use command_status to check the result and report back to the user.`;
46197
46491
  statusMessage,
46198
46492
  workdir
46199
46493
  ]);
46200
- const header = useMemo7(() => /* @__PURE__ */ jsx104(Header, { sessionId, workdir, cliVersion }), [cliVersion, sessionId, workdir]);
46201
- const overlay = useMemo7(() => /* @__PURE__ */ jsx104(BlumaWorkersOverlay, { sessionId }), [sessionId]);
46202
- 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, {}), []);
46203
46497
  return /* @__PURE__ */ jsx104(
46204
46498
  BlumaViewport,
46205
46499
  {
@@ -46383,7 +46677,7 @@ import React39 from "react";
46383
46677
  init_agent_session_paths();
46384
46678
 
46385
46679
  // src/app/ui/components/SessionResumePicker.tsx
46386
- 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";
46387
46681
 
46388
46682
  // src/app/agent/session_manager/session_resume_browser.ts
46389
46683
  init_bluma_app_dir();
@@ -46530,12 +46824,12 @@ var SessionResumePickerComponent = ({
46530
46824
  4,
46531
46825
  Math.min(VISIBLE_DEFAULT, (stdout?.rows ?? 24) - 14)
46532
46826
  );
46533
- const [cwd2, setCwd] = useState25("");
46534
- const [entries, setEntries] = useState25([]);
46535
- const [loading, setLoading] = useState25(true);
46536
- const [selectedIndex, setSelectedIndex] = useState25(0);
46537
- const [scrollTop, setScrollTop] = useState25(0);
46538
- 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) => {
46539
46833
  setLoading(true);
46540
46834
  try {
46541
46835
  const list = await listSessionBrowserEntries(rel);
@@ -46561,7 +46855,7 @@ var SessionResumePickerComponent = ({
46561
46855
  setScrollTop(selectedIndex - visibleCount + 1);
46562
46856
  }
46563
46857
  }, [selectedIndex, scrollTop, visibleCount]);
46564
- const windowItems = useMemo8(
46858
+ const windowItems = useMemo9(
46565
46859
  () => entries.slice(scrollTop, scrollTop + visibleCount),
46566
46860
  [entries, scrollTop, visibleCount]
46567
46861
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nomad-e/bluma-cli",
3
- "version": "0.22.2",
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",