@bubblebrain-ai/bubble 0.0.15 → 0.0.16

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.
@@ -61,11 +61,15 @@ export function formatCompactNumber(value) {
61
61
  return String(Math.round(value));
62
62
  }
63
63
  export function formatCurrency(value) {
64
- if (value >= 1)
65
- return `$${value.toFixed(2)}`;
66
- if (value >= 0.01)
67
- return `$${value.toFixed(3)}`;
68
- return `$${value.toFixed(4)}`;
64
+ return formatCurrencyFor(value, "USD");
65
+ }
66
+ function formatCurrencyFor(value, currency) {
67
+ const amount = value >= 1
68
+ ? value.toFixed(2)
69
+ : value >= 0.01
70
+ ? value.toFixed(3)
71
+ : value.toFixed(4);
72
+ return currency === "USD" ? `$${amount}` : `CNY ${amount}`;
69
73
  }
70
74
  function createAccumulator(range, days, now) {
71
75
  const end = startOfLocalDay(now);
@@ -146,7 +150,11 @@ function finalizeAccumulator(accumulator) {
146
150
  .filter((model) => model.totalTokens > 0)
147
151
  .sort((a, b) => b.totalTokens - a.totalTokens);
148
152
  const totalTokens = models.reduce((sum, model) => sum + model.totalTokens, 0);
149
- const trackedCost = models.reduce((sum, model) => sum + (model.cost ?? 0), 0);
153
+ const trackedCosts = aggregateCosts(models);
154
+ const trackedCostEntries = trackedCosts
155
+ ? Object.entries(trackedCosts)
156
+ : [];
157
+ const trackedCostEntry = trackedCostEntries.length === 1 ? trackedCostEntries[0] : undefined;
150
158
  return {
151
159
  range: accumulator.range,
152
160
  days: accumulator.days,
@@ -156,7 +164,9 @@ function finalizeAccumulator(accumulator) {
156
164
  heatmap: buildHeatmap(daily),
157
165
  models,
158
166
  totalTokens,
159
- trackedCost: trackedCost > 0 ? trackedCost : undefined,
167
+ trackedCosts,
168
+ trackedCost: trackedCostEntry ? trackedCostEntry[1] : undefined,
169
+ trackedCostCurrency: trackedCostEntry ? trackedCostEntry[0] : undefined,
160
170
  activeDays: daily.filter((day) => day.active).length,
161
171
  sessionsScanned: accumulator.sessionsScanned,
162
172
  sessionsWithoutTokenData: accumulator.sessionsWithoutTokenData,
@@ -189,8 +199,10 @@ function addModelUsage(accumulator, model, message, usage) {
189
199
  existing.totalTokens += tokenTotal(usage);
190
200
  if (providerId && modelId) {
191
201
  const cost = calculateUsageCost(providerId, modelId, usage);
192
- if (cost)
202
+ if (cost) {
193
203
  existing.cost = (existing.cost ?? 0) + cost.cost;
204
+ existing.costCurrency = cost.currency;
205
+ }
194
206
  }
195
207
  accumulator.modelUsage.set(key, existing);
196
208
  }
@@ -254,7 +266,9 @@ function formatModelUsageLines(stats, width) {
254
266
  const percentText = `${Math.round(percent * 100)}%`.padStart(4, " ");
255
267
  const tokenText = formatCompactNumber(model.totalTokens).padStart(6, " ");
256
268
  const turnsText = `${model.turns}t`.padStart(4, " ");
257
- const costText = showCost ? ` ${(model.cost !== undefined ? formatCurrency(model.cost) : "").padStart(7, " ")}` : "";
269
+ const costText = showCost
270
+ ? ` ${(model.cost !== undefined ? formatCurrencyFor(model.cost, model.costCurrency ?? "USD") : "").padStart(7, " ")}`
271
+ : "";
258
272
  return ` ${truncate(model.displayName, labelWidth).padEnd(labelWidth, " ")} ${bar} ${percentText} ${tokenText} ${turnsText}${costText}`.trimEnd();
259
273
  });
260
274
  if (stats.models.length > MAX_MODEL_ROWS) {
@@ -270,14 +284,37 @@ function formatSummaryLines(stats, width) {
270
284
  if (favorite) {
271
285
  lines.push(` Favorite model ${truncate(favorite, Math.max(12, width - 17))}`);
272
286
  }
273
- if (stats.trackedCost !== undefined)
274
- lines.push(` Tracked cost ${formatCurrency(stats.trackedCost)}`);
287
+ const trackedCostText = formatTrackedCosts(stats);
288
+ if (trackedCostText)
289
+ lines.push(` Tracked cost ${trackedCostText}`);
275
290
  lines.push(` Sessions scanned ${stats.sessionsScanned}`);
276
291
  if (stats.sessionsWithoutTokenData > 0) {
277
292
  lines.push(` Sessions without token data ${stats.sessionsWithoutTokenData}`);
278
293
  }
279
294
  return lines;
280
295
  }
296
+ function aggregateCosts(models) {
297
+ const totals = {};
298
+ for (const model of models) {
299
+ if (model.cost === undefined)
300
+ continue;
301
+ const currency = model.costCurrency ?? "USD";
302
+ totals[currency] = (totals[currency] ?? 0) + model.cost;
303
+ }
304
+ return Object.keys(totals).length > 0 ? totals : undefined;
305
+ }
306
+ function formatTrackedCosts(stats) {
307
+ if (stats.trackedCosts) {
308
+ const parts = Object.entries(stats.trackedCosts)
309
+ .filter(([, value]) => value > 0)
310
+ .map(([currency, value]) => formatCurrencyFor(value, currency));
311
+ return parts.length > 0 ? parts.join(" + ") : undefined;
312
+ }
313
+ if (stats.trackedCost !== undefined) {
314
+ return formatCurrencyFor(stats.trackedCost, stats.trackedCostCurrency ?? "USD");
315
+ }
316
+ return undefined;
317
+ }
281
318
  function heatmapCell(day, maxTokens) {
282
319
  if (!day)
283
320
  return " ";
@@ -65,6 +65,7 @@ export function createGlobTool(cwd) {
65
65
  }
66
66
  files.sort((a, b) => b.mtimeMs - a.mtimeMs || a.path.localeCompare(b.path));
67
67
  const matches = files.slice(0, MAX_RESULTS).map((item) => item.path);
68
+ const absoluteMatches = matches.map((item) => resolve(root, item));
68
69
  const wasTruncated = truncated.value || files.length > MAX_RESULTS;
69
70
  if (matches.length === 0) {
70
71
  return {
@@ -78,6 +79,7 @@ export function createGlobTool(cwd) {
78
79
  truncated: false,
79
80
  searchSignature: `glob:${root}:${pattern}`,
80
81
  searchFamily: `glob:${pattern}`,
82
+ paths: [],
81
83
  },
82
84
  };
83
85
  }
@@ -92,6 +94,7 @@ export function createGlobTool(cwd) {
92
94
  truncated: wasTruncated,
93
95
  searchSignature: `glob:${root}:${pattern}`,
94
96
  searchFamily: `glob:${pattern}`,
97
+ paths: absoluteMatches,
95
98
  },
96
99
  };
97
100
  },
@@ -2,6 +2,7 @@
2
2
  * Grep tool - search file contents using ripgrep.
3
3
  */
4
4
  import { execFile } from "node:child_process";
5
+ import { resolve as resolvePath } from "node:path";
5
6
  import { isSensitivePath } from "./sensitive-paths.js";
6
7
  import { analyzeToolIntent } from "../agent/tool-intent.js";
7
8
  import { resolveToolPath } from "./path-utils.js";
@@ -57,6 +58,7 @@ export function createGrepTool(cwd) {
57
58
  // rg returns exit code 1 when no matches found, which is not an error for us
58
59
  const lines = stdout.split("\n").filter((l) => l.trim() !== "");
59
60
  const matches = [];
61
+ const matchedPaths = new Set();
60
62
  for (const line of lines) {
61
63
  try {
62
64
  const obj = JSON.parse(line);
@@ -64,6 +66,9 @@ export function createGrepTool(cwd) {
64
66
  const path = obj.data.path.text;
65
67
  const lineNum = obj.data.line_number;
66
68
  const text = obj.data.lines.text?.trim() ?? "";
69
+ if (typeof path === "string" && path.trim()) {
70
+ matchedPaths.add(resolvePath(cwd, path));
71
+ }
67
72
  matches.push(`${path}:${lineNum}: ${text}`);
68
73
  }
69
74
  }
@@ -83,6 +88,7 @@ export function createGrepTool(cwd) {
83
88
  truncated: false,
84
89
  searchSignature: intent.search?.signature,
85
90
  searchFamily: intent.search?.familyKey,
91
+ paths: [],
86
92
  },
87
93
  });
88
94
  return;
@@ -103,6 +109,7 @@ export function createGrepTool(cwd) {
103
109
  truncated,
104
110
  searchSignature: intent.search?.signature,
105
111
  searchFamily: intent.search?.familyKey,
112
+ paths: [...matchedPaths],
106
113
  },
107
114
  });
108
115
  });
package/dist/tui/run.js CHANGED
@@ -9,6 +9,8 @@ import { homedir } from "node:os";
9
9
  import { AgentAbortError } from "../agent.js";
10
10
  import { AgentRunInputQueue } from "../agent/input-controller.js";
11
11
  import { debugReasoningStream, summarizeDebugText } from "../reasoning-debug.js";
12
+ import { isHiddenToolMetadata } from "../agent/discovery-barrier.js";
13
+ import { sanitizeInternalReminderBlocks } from "../agent/internal-reminder-sanitizer.js";
12
14
  import { summarizeAgentEventForTrace, summarizeTraceError, summarizeTraceValue, traceEvent, } from "../debug-trace.js";
13
15
  import { BUILTIN_PROVIDERS, decodeModel, displayModel, isUserVisibleProvider } from "../provider-registry.js";
14
16
  import { calculateUsageCost } from "../model-pricing.js";
@@ -60,6 +62,7 @@ const PROVIDER_PRIORITY = new Map([
60
62
  ["zai", 5],
61
63
  ["zai-coding-plan", 6],
62
64
  ["kimi-for-coding", 7],
65
+ ["stepfun", 8],
63
66
  ]);
64
67
  const DEFAULT_THEME = {
65
68
  primary: "#fab283",
@@ -5075,6 +5078,7 @@ function OpenTuiApp(props) {
5075
5078
  "zhipuai-coding-plan": "Coding Plan",
5076
5079
  "zai-coding-plan": "Coding Plan",
5077
5080
  "kimi-for-coding": "Coding Plan",
5081
+ stepfun: "Step Plan API key",
5078
5082
  local: "OpenAI-compatible local endpoint",
5079
5083
  };
5080
5084
  return descriptions[providerId] ?? "API key";
@@ -6581,7 +6585,7 @@ function OpenTuiApp(props) {
6581
6585
  completionTokens: usage.completionTokens,
6582
6586
  reasoningTokens: usage.reasoningTokens,
6583
6587
  turns: usage.turns,
6584
- costText: cost ? `${formatCurrency(cost.cost)} spent${cost.estimated ? " est." : ""}` : "cost unavailable",
6588
+ costText: cost ? `${formatCurrency(cost.cost, cost.currency)} spent${cost.estimated ? " est." : ""}` : "cost unavailable",
6585
6589
  };
6586
6590
  }
6587
6591
  function sidebarMcpStates() {
@@ -6950,12 +6954,14 @@ function renderUserMessage(message, index) {
6950
6954
  }, h("box", { paddingTop: 1, paddingBottom: 1, paddingLeft: 2, backgroundColor: theme.backgroundPanel, flexShrink: 0, flexDirection: "column" }, ...userChildren));
6951
6955
  }
6952
6956
  function renderAssistantMessage(message, syntaxStyle, subtleSyntaxStyle, showThinking = true, verboseTrace = false, width = 80) {
6957
+ const visibleReasoning = showThinking
6958
+ ? sanitizeInternalReminderBlocks(message.reasoning ?? "").trim()
6959
+ : "";
6953
6960
  const modelSwitch = parseModelSwitchMessage(message.content);
6954
- if (modelSwitch && !message.reasoning?.trim() && !(message.toolCalls?.length)) {
6961
+ if (modelSwitch && !visibleReasoning && !(message.toolCalls?.length)) {
6955
6962
  return renderModelSwitchMessage(modelSwitch);
6956
6963
  }
6957
6964
  const children = [];
6958
- const visibleReasoning = showThinking ? message.reasoning?.trim() : "";
6959
6965
  const parts = message.parts ?? [];
6960
6966
  const hasParts = parts.length > 0;
6961
6967
  if (message.status && !visibleReasoning && !message.content.trim() && !(message.toolCalls?.length) && !hasParts) {
@@ -8433,12 +8439,14 @@ function pickerTitle(kind, providerId) {
8433
8439
  function getModelPickerReasoningLevels(providerId, modelId) {
8434
8440
  // Only expand into one picker row per effort for models that genuinely have a
8435
8441
  // reasoning-effort spectrum: OpenAI's reasoning models (codex gpt-5.x:
8436
- // off/minimal/low/medium/high/xhigh) and DeepSeek's v4 models. Other providers
8442
+ // off/minimal/low/medium/high/xhigh), DeepSeek's v4 models, and StepFun
8443
+ // Step Plan models. Other providers
8437
8444
  // (e.g. GLM, Moonshot/Kimi) only have a thinking on/off toggle, not an effort
8438
8445
  // control, so they stay as a single row.
8439
8446
  const isOpenAIReasoning = providerId === "openai" || providerId === "openai-codex";
8440
8447
  const isDeepseekReasoning = providerId === "deepseek" && (modelId === "deepseek-v4-flash" || modelId === "deepseek-v4-pro");
8441
- if (!isOpenAIReasoning && !isDeepseekReasoning)
8448
+ const isStepFunReasoning = providerId === "stepfun";
8449
+ if (!isOpenAIReasoning && !isDeepseekReasoning && !isStepFunReasoning)
8442
8450
  return [];
8443
8451
  const levels = getAvailableThinkingLevels(providerId, modelId);
8444
8452
  // gpt-4o and friends report only ["off"] — keep those as a single row too.
@@ -8451,8 +8459,9 @@ function displayModelWithThinking(model, thinkingLevel) {
8451
8459
  if (!providerId)
8452
8460
  return displayModel(model);
8453
8461
  // Use the same scoping as the picker: only models with a real reasoning-effort
8454
- // spectrum (OpenAI codex gpt-5.x, deepseek v4) get the "(level)" suffix. The
8455
- // on/off thinking toggle on GLM / Moonshot(Kimi) is not an effort control.
8462
+ // spectrum (OpenAI codex gpt-5.x, deepseek v4, StepFun Step Plan) get the
8463
+ // "(level)" suffix. The on/off thinking toggle on GLM / Moonshot(Kimi) is
8464
+ // not an effort control.
8456
8465
  const levels = getModelPickerReasoningLevels(providerId, modelId);
8457
8466
  if (levels.length > 1 && thinkingLevel !== "off") {
8458
8467
  return `${displayModel(model)} (${thinkingLevel})`;
@@ -8649,6 +8658,8 @@ function reconstructDisplayMessages(agentMessages) {
8649
8658
  }
8650
8659
  catch { }
8651
8660
  const toolResult = agentMessages.find((candidate) => candidate.role === "tool" && candidate.toolCallId === tc.id);
8661
+ if (isHiddenToolMetadata(toolResult ? toolResult.metadata : undefined))
8662
+ continue;
8652
8663
  toolCalls.push({
8653
8664
  id: tc.id,
8654
8665
  name: tc.name,
@@ -9389,12 +9400,11 @@ function formatCompactNumber(value) {
9389
9400
  return `${(value / 1_000).toFixed(1)}K`;
9390
9401
  return String(value);
9391
9402
  }
9392
- function formatCurrency(value) {
9403
+ function formatCurrency(value, currency = "USD") {
9393
9404
  if (value < 0.0001)
9394
- return "$0.0000";
9395
- if (value < 1)
9396
- return `$${value.toFixed(4)}`;
9397
- return `$${value.toFixed(2)}`;
9405
+ return currency === "USD" ? "$0.0000" : "CNY 0.0000";
9406
+ const amount = value < 1 ? value.toFixed(4) : value.toFixed(2);
9407
+ return currency === "USD" ? `$${amount}` : `CNY ${amount}`;
9398
9408
  }
9399
9409
  function sidebarStatusColor(kind) {
9400
9410
  if (kind === "connected")
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
3
  import { Box, Text, useApp, useInput } from "ink";
4
4
  import { AgentAbortError } from "../agent.js";
5
+ import { isHiddenToolMetadata } from "../agent/discovery-barrier.js";
5
6
  import { registry as slashRegistry } from "../slash-commands/index.js";
6
7
  import { UserConfig, maskKey } from "../config.js";
7
8
  import { createPastedContentMarker, InputBox, isCtrlCInput, shouldCollapsePastedContent, } from "./input-box.js";
@@ -82,6 +83,8 @@ function reconstructDisplayMessages(agentMessages) {
82
83
  args = {};
83
84
  }
84
85
  const toolResult = agentMessages.find((tm) => tm.role === "tool" && tm.toolCallId === tc.id);
86
+ if (isHiddenToolMetadata(toolResult ? toolResult.metadata : undefined))
87
+ continue;
85
88
  toolCalls.push({
86
89
  id: tc.id,
87
90
  name: tc.name,
@@ -7,6 +7,7 @@ import { MarkdownContent, StreamingMarkdown } from "./markdown.js";
7
7
  import { buildTraceGroups, formatTracePath, traceGroupLabel } from "./trace-groups.js";
8
8
  import { EDIT_COLLAPSED_DIFF_LINES, formatEditSuccessSummary, getEditDiffDetails } from "./edit-diff.js";
9
9
  import { formatSubagentRoute } from "../agent/subagent-route-format.js";
10
+ import { sanitizeInternalReminderBlocks } from "../agent/internal-reminder-sanitizer.js";
10
11
  export function MessageList({ messages, streamingContent, streamingReasoning, streamingTools, streamingParts, terminalColumns, verboseTrace, pendingApproval, nowTick, welcomeBanner, }) {
11
12
  const hasStreaming = !!(streamingContent ||
12
13
  streamingReasoning ||
@@ -44,22 +45,24 @@ function MessageItem({ message, terminalColumns, verboseTrace, showExpandHint, n
44
45
  if (message.syntheticKind === "ui_compact_summary") {
45
46
  return _jsx(CompactionSummaryBlock, { message: message });
46
47
  }
48
+ const visibleReasoning = sanitizeInternalReminderBlocks(message.reasoning ?? "").trim();
47
49
  const hasVisibleAssistantContent = !!message.content ||
48
50
  (message.toolCalls?.length ?? 0) > 0 ||
49
51
  (message.parts?.length ?? 0) > 0 ||
50
- (!!message.reasoning && verboseTrace);
52
+ (!!visibleReasoning && verboseTrace);
51
53
  if (!hasVisibleAssistantContent)
52
54
  return null;
53
- return (_jsxs(Box, { marginTop: 1, marginBottom: 1, flexDirection: "column", children: [message.reasoning && verboseTrace && _jsx(ReasoningTraceBlock, { reasoning: message.reasoning }), message.parts && message.parts.length > 0 ? (_jsx(MessageParts, { parts: message.parts, terminalColumns: terminalColumns, verboseTrace: verboseTrace, pendingApproval: undefined, showExpandHint: showExpandHint, nowTick: nowTick })) : (_jsxs(_Fragment, { children: [message.toolCalls && (_jsx(ToolsPart, { toolCalls: message.toolCalls, terminalColumns: terminalColumns, verboseTrace: verboseTrace, pendingApproval: undefined, showExpandHint: showExpandHint, nowTick: nowTick })), message.content && _jsx(MarkdownContent, { content: message.content })] })), verboseTrace && message.toolCalls && message.toolCalls.length > 0 && (_jsx(TurnDigest, { toolCalls: message.toolCalls })), message.taskElapsedMs !== undefined && (_jsx(TaskDurationLine, { elapsedMs: message.taskElapsedMs }))] }));
55
+ return (_jsxs(Box, { marginTop: 1, marginBottom: 1, flexDirection: "column", children: [visibleReasoning && verboseTrace && _jsx(ReasoningTraceBlock, { reasoning: visibleReasoning }), message.parts && message.parts.length > 0 ? (_jsx(MessageParts, { parts: message.parts, terminalColumns: terminalColumns, verboseTrace: verboseTrace, pendingApproval: undefined, showExpandHint: showExpandHint, nowTick: nowTick })) : (_jsxs(_Fragment, { children: [message.toolCalls && (_jsx(ToolsPart, { toolCalls: message.toolCalls, terminalColumns: terminalColumns, verboseTrace: verboseTrace, pendingApproval: undefined, showExpandHint: showExpandHint, nowTick: nowTick })), message.content && _jsx(MarkdownContent, { content: message.content })] })), verboseTrace && message.toolCalls && message.toolCalls.length > 0 && (_jsx(TurnDigest, { toolCalls: message.toolCalls })), message.taskElapsedMs !== undefined && (_jsx(TaskDurationLine, { elapsedMs: message.taskElapsedMs }))] }));
54
56
  }
55
57
  function StreamingMessage({ content, reasoning, tools, parts, terminalColumns, verboseTrace, pendingApproval, nowTick, }) {
56
58
  const deferredContent = React.useDeferredValue(content);
57
59
  const deferredReasoning = React.useDeferredValue(reasoning);
58
60
  const deferredParts = React.useDeferredValue(parts);
61
+ const visibleReasoning = sanitizeInternalReminderBlocks(deferredReasoning).trim();
59
62
  const visibleParts = deferredParts.length > 0
60
63
  ? deferredParts
61
64
  : fallbackStreamingParts(deferredContent, tools);
62
- return (_jsxs(Box, { flexDirection: "column", children: [deferredReasoning && verboseTrace && (_jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsx(ReasoningTraceBlock, { reasoning: deferredReasoning }) })), visibleParts.length > 0 && (
65
+ return (_jsxs(Box, { flexDirection: "column", children: [visibleReasoning && verboseTrace && (_jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsx(ReasoningTraceBlock, { reasoning: visibleReasoning }) })), visibleParts.length > 0 && (
63
66
  // marginTop intentionally 0: this Box only mounts on the first non-empty
64
67
  // streaming frame, so a marginTop=1 here would visibly insert a blank
65
68
  // line under the user message right at that moment (the "spinner sits
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "@opentui/reac
3
3
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
4
4
  import { useKeyboard, useRenderer } from "@opentui/react";
5
5
  import { AgentAbortError } from "../agent.js";
6
+ import { isHiddenToolMetadata } from "../agent/discovery-barrier.js";
6
7
  import { registry as slashRegistry } from "../slash-commands/index.js";
7
8
  import { UserConfig, maskKey } from "../config.js";
8
9
  import { createPastedContentMarker, InputBox, shouldCollapsePastedContent, } from "./input-box.js";
@@ -96,6 +97,8 @@ function reconstructDisplayMessages(agentMessages) {
96
97
  args = {};
97
98
  }
98
99
  const toolResult = agentMessages.find((tm) => tm.role === "tool" && tm.toolCallId === tc.id);
100
+ if (isHiddenToolMetadata(toolResult ? toolResult.metadata : undefined))
101
+ continue;
99
102
  toolCalls.push({
100
103
  id: tc.id,
101
104
  name: tc.name,
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "@opentui/reac
2
2
  import { useTheme } from "./theme.js";
3
3
  import { MarkdownContent, StreamingMarkdown } from "./markdown.js";
4
4
  import { EDIT_COLLAPSED_DIFF_LINES, getEditDiffDetails } from "./edit-diff.js";
5
+ import { sanitizeInternalReminderBlocks } from "../agent/internal-reminder-sanitizer.js";
5
6
  /**
6
7
  * Scrollback-style message list following opencode visual rules:
7
8
  *
@@ -29,18 +30,20 @@ function MessageItem({ message, terminalColumns, verboseTrace, }) {
29
30
  if (message.syntheticKind === "ui_compact_summary") {
30
31
  return _jsx(CompactionSummaryBlock, { message: message, theme: theme });
31
32
  }
33
+ const visibleReasoning = sanitizeInternalReminderBlocks(message.reasoning ?? "").trim();
32
34
  const hasVisible = !!message.content ||
33
35
  (message.toolCalls?.length ?? 0) > 0 ||
34
36
  (message.parts?.length ?? 0) > 0 ||
35
- (!!message.reasoning && verboseTrace);
37
+ (!!visibleReasoning && verboseTrace);
36
38
  if (!hasVisible)
37
39
  return null;
38
- return (_jsxs("box", { style: { marginTop: 1, marginBottom: 1, flexDirection: "column" }, children: [message.reasoning && verboseTrace && _jsx(ReasoningBlock, { reasoning: message.reasoning, theme: theme }), message.parts && message.parts.length > 0 ? (_jsx(MessageParts, { parts: message.parts, terminalColumns: terminalColumns, verboseTrace: verboseTrace, theme: theme })) : (_jsxs(_Fragment, { children: [message.toolCalls && _jsx(ToolsPart, { toolCalls: message.toolCalls, terminalColumns: terminalColumns, verboseTrace: verboseTrace, theme: theme }), message.content && _jsx(MarkdownContent, { content: message.content, terminalColumns: terminalColumns })] }))] }));
40
+ return (_jsxs("box", { style: { marginTop: 1, marginBottom: 1, flexDirection: "column" }, children: [visibleReasoning && verboseTrace && _jsx(ReasoningBlock, { reasoning: visibleReasoning, theme: theme }), message.parts && message.parts.length > 0 ? (_jsx(MessageParts, { parts: message.parts, terminalColumns: terminalColumns, verboseTrace: verboseTrace, theme: theme })) : (_jsxs(_Fragment, { children: [message.toolCalls && _jsx(ToolsPart, { toolCalls: message.toolCalls, terminalColumns: terminalColumns, verboseTrace: verboseTrace, theme: theme }), message.content && _jsx(MarkdownContent, { content: message.content, terminalColumns: terminalColumns })] }))] }));
39
41
  }
40
42
  function StreamingMessage({ content, reasoning, tools, parts, terminalColumns, verboseTrace, }) {
41
43
  const theme = useTheme();
44
+ const visibleReasoning = sanitizeInternalReminderBlocks(reasoning).trim();
42
45
  const visibleParts = parts.length > 0 ? parts : fallbackStreamingParts(content, tools);
43
- return (_jsxs("box", { style: { flexDirection: "column", marginTop: 1 }, children: [reasoning && verboseTrace && _jsx(ReasoningBlock, { reasoning: reasoning, theme: theme }), visibleParts.length > 0 && (_jsx(MessageParts, { parts: visibleParts, terminalColumns: terminalColumns, verboseTrace: verboseTrace, streaming: true, theme: theme }))] }));
46
+ return (_jsxs("box", { style: { flexDirection: "column", marginTop: 1 }, children: [visibleReasoning && verboseTrace && _jsx(ReasoningBlock, { reasoning: visibleReasoning, theme: theme }), visibleParts.length > 0 && (_jsx(MessageParts, { parts: visibleParts, terminalColumns: terminalColumns, verboseTrace: verboseTrace, streaming: true, theme: theme }))] }));
44
47
  }
45
48
  function fallbackStreamingParts(content, tools) {
46
49
  const out = [];
package/dist/types.d.ts CHANGED
@@ -102,7 +102,7 @@ export interface ParsedToolCall extends ToolCall {
102
102
  }
103
103
  export type ToolResultStatus = "success" | "no_match" | "partial" | "timeout" | "blocked" | "cancelled" | "command_error";
104
104
  export interface ToolResultMetadata {
105
- kind?: "search" | "read" | "write" | "edit" | "patch" | "shell" | "server" | "web" | "security" | "lsp" | "question" | "subagent";
105
+ kind?: "search" | "read" | "write" | "edit" | "patch" | "shell" | "server" | "web" | "security" | "lsp" | "question" | "subagent" | "internal";
106
106
  path?: string;
107
107
  pattern?: string;
108
108
  matches?: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bubblebrain-ai/bubble",
3
- "version": "0.0.15",
3
+ "version": "0.0.16",
4
4
  "description": "A terminal coding agent",
5
5
  "type": "module",
6
6
  "engines": {
@@ -44,6 +44,7 @@
44
44
  "solid-js": "^1.9.12",
45
45
  "string-width": "^8.2.1",
46
46
  "typescript-language-server": "^5.1.3",
47
+ "undici": "^6.26.0",
47
48
  "vscode-jsonrpc": "^8.2.1",
48
49
  "vscode-langservers-extracted": "^4.10.0"
49
50
  },