@bubblebrain-ai/bubble 0.0.13 → 0.0.15

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 (80) hide show
  1. package/dist/agent/execution-governor.js +1 -1
  2. package/dist/agent/tool-intent.js +1 -0
  3. package/dist/agent.d.ts +2 -0
  4. package/dist/agent.js +589 -316
  5. package/dist/approval/controller.d.ts +1 -0
  6. package/dist/approval/controller.js +20 -3
  7. package/dist/approval/tool-helper.js +2 -0
  8. package/dist/approval/types.d.ts +14 -1
  9. package/dist/cli.d.ts +3 -1
  10. package/dist/cli.js +12 -0
  11. package/dist/context/compact.js +9 -3
  12. package/dist/context/projector.js +27 -12
  13. package/dist/debug-trace.d.ts +27 -0
  14. package/dist/debug-trace.js +385 -0
  15. package/dist/feishu/agent-host/approval-card.js +9 -0
  16. package/dist/feishu/serve.js +7 -1
  17. package/dist/main.js +41 -0
  18. package/dist/model-catalog.js +1 -0
  19. package/dist/orchestrator/default-hooks.js +19 -8
  20. package/dist/orchestrator/hooks.d.ts +1 -0
  21. package/dist/prompt/environment.js +2 -0
  22. package/dist/prompt/reminders.d.ts +5 -6
  23. package/dist/prompt/reminders.js +8 -9
  24. package/dist/prompt/runtime.js +2 -2
  25. package/dist/provider-openai-codex.d.ts +7 -0
  26. package/dist/provider-openai-codex.js +265 -124
  27. package/dist/provider-registry.d.ts +2 -0
  28. package/dist/provider-registry.js +58 -9
  29. package/dist/provider.d.ts +3 -0
  30. package/dist/provider.js +5 -1
  31. package/dist/session-log.js +13 -1
  32. package/dist/slash-commands/commands.js +12 -0
  33. package/dist/slash-commands/types.d.ts +2 -0
  34. package/dist/stats/usage.d.ts +52 -0
  35. package/dist/stats/usage.js +414 -0
  36. package/dist/tools/apply-patch.d.ts +9 -0
  37. package/dist/tools/apply-patch.js +330 -0
  38. package/dist/tools/bash.js +205 -44
  39. package/dist/tools/edit-apply.d.ts +5 -2
  40. package/dist/tools/edit-apply.js +221 -31
  41. package/dist/tools/edit.js +12 -3
  42. package/dist/tools/file-mutation-queue.d.ts +1 -0
  43. package/dist/tools/file-mutation-queue.js +12 -1
  44. package/dist/tools/index.d.ts +2 -0
  45. package/dist/tools/index.js +7 -1
  46. package/dist/tools/patch-apply.d.ts +41 -0
  47. package/dist/tools/patch-apply.js +312 -0
  48. package/dist/tools/server-manager.d.ts +36 -0
  49. package/dist/tools/server-manager.js +234 -0
  50. package/dist/tools/server.d.ts +6 -0
  51. package/dist/tools/server.js +245 -0
  52. package/dist/tools/write.d.ts +3 -6
  53. package/dist/tools/write.js +26 -46
  54. package/dist/tui/display-history.d.ts +1 -0
  55. package/dist/tui/display-history.js +5 -4
  56. package/dist/tui/edit-diff.js +6 -1
  57. package/dist/tui/model-picker-data.d.ts +10 -0
  58. package/dist/tui/model-picker-data.js +32 -0
  59. package/dist/tui/run.d.ts +2 -0
  60. package/dist/tui/run.js +717 -122
  61. package/dist/tui/tool-renderers/fallback.js +1 -1
  62. package/dist/tui/tool-renderers/write-preview.js +2 -0
  63. package/dist/tui/trace-groups.js +10 -3
  64. package/dist/tui-ink/app.js +1 -4
  65. package/dist/tui-ink/approval/approval-dialog.js +7 -1
  66. package/dist/tui-ink/display-history.d.ts +1 -0
  67. package/dist/tui-ink/display-history.js +5 -4
  68. package/dist/tui-ink/message-list.js +14 -8
  69. package/dist/tui-ink/trace-groups.js +1 -1
  70. package/dist/tui-opentui/app.js +2 -0
  71. package/dist/tui-opentui/approval/approval-dialog.js +7 -1
  72. package/dist/tui-opentui/display-history.d.ts +1 -0
  73. package/dist/tui-opentui/display-history.js +5 -4
  74. package/dist/tui-opentui/edit-diff.js +6 -1
  75. package/dist/tui-opentui/message-list.js +6 -3
  76. package/dist/tui-opentui/trace-groups.js +10 -3
  77. package/dist/types.d.ts +12 -2
  78. package/dist/update/index.d.ts +46 -0
  79. package/dist/update/index.js +240 -0
  80. package/package.json +1 -1
@@ -10,7 +10,7 @@ function renderFallbackTool({ ctx, tool, syntaxStyle, width, helpers }) {
10
10
  const header = helpers.toolHeader(tool);
11
11
  const diff = helpers.extractToolDiff(tool);
12
12
  const isError = tool.isError === true || tool.status === "error";
13
- if (diff && !isError && tool.name === "edit") {
13
+ if (diff && !isError && (tool.name === "edit" || tool.name === "apply_patch")) {
14
14
  return helpers.createBox(ctx, {
15
15
  paddingLeft: 3,
16
16
  marginTop: 1,
@@ -1,6 +1,8 @@
1
1
  const WRITE_PREVIEW_LINE_LIMIT = 10;
2
2
  export const WRITE_PREVIEW_CHAR_LIMIT = 5000;
3
3
  export function isWritePreviewTool(tool) {
4
+ if (tool.resultCollapsed)
5
+ return false;
4
6
  if (tool.isError)
5
7
  return false;
6
8
  if (tool.name !== "write")
@@ -226,8 +226,8 @@ function buildExecuteGroup(classifier, tool, options, pending, startedAt, hasErr
226
226
  function buildMutationGroup(classifier, raw, options, pending, startedAt, hasError, errorCount) {
227
227
  const items = raw
228
228
  .map((tool) => {
229
- const path = formatTracePath(tool.args.path ?? "", options.homeDir);
230
- const details = tool.name === "edit" ? getEditDiffDetails(tool) : null;
229
+ const path = formatTracePath(tool.args.path ?? firstMetadataPath(tool) ?? "", options.homeDir);
230
+ const details = tool.name === "edit" || tool.name === "apply_patch" ? getEditDiffDetails(tool) : null;
231
231
  const suffix = details ? ` ${formatCompactEditStats(details.added, details.removed)}` : "";
232
232
  return path ? `${path}${suffix}` : "";
233
233
  })
@@ -323,7 +323,7 @@ function isFailedSubagent(subagent) {
323
323
  return subagent.status === "failed" || subagent.status === "blocked" || subagent.status === "cancelled";
324
324
  }
325
325
  function isToolPending(tool) {
326
- return tool.result === undefined;
326
+ return tool.result === undefined && tool.resultCollapsed !== true;
327
327
  }
328
328
  function isDirectoryLikeGlob(pattern) {
329
329
  const normalized = pattern.trim();
@@ -400,8 +400,15 @@ function toolHeader(tool, homeDir) {
400
400
  return formatTracePath(value, homeDir);
401
401
  }
402
402
  }
403
+ const path = firstMetadataPath(tool);
404
+ if (path)
405
+ return formatTracePath(path, homeDir);
403
406
  return undefined;
404
407
  }
408
+ function firstMetadataPath(tool) {
409
+ const paths = tool.metadata?.paths;
410
+ return Array.isArray(paths) && typeof paths[0] === "string" ? paths[0] : undefined;
411
+ }
405
412
  function formatCompactEditStats(added, removed) {
406
413
  const parts = [];
407
414
  if (added > 0)
@@ -996,10 +996,7 @@ export function App({ agent, args, sessionManager, createProvider, registry, ski
996
996
  catch (err) {
997
997
  commitAssistantMessage();
998
998
  if (err instanceof AgentAbortError || err?.name === "AbortError") {
999
- updateDisplayMessages((prev) => [
1000
- ...prev,
1001
- withMessageKey({ role: "assistant", content: "Cancelled." }),
1002
- ]);
999
+ updateDisplayMessages(() => reconstructDisplayMessages(agent.messages));
1003
1000
  }
1004
1001
  else {
1005
1002
  updateDisplayMessages((prev) => [
@@ -53,7 +53,7 @@ function buildOptions(request) {
53
53
  },
54
54
  ];
55
55
  }
56
- // edit / write
56
+ // edit / write / patch
57
57
  return [
58
58
  { id: "yes", label: "Yes", allowAmend: true, amendPlaceholder: "and tell Claude what to do next" },
59
59
  {
@@ -69,6 +69,8 @@ function dialogTitle(req) {
69
69
  switch (req.type) {
70
70
  case "edit":
71
71
  return "Edit file";
72
+ case "patch":
73
+ return "Apply patch";
72
74
  case "write":
73
75
  return req.fileExists ? "Overwrite file" : "Create file";
74
76
  case "bash":
@@ -81,6 +83,8 @@ function dialogQuestion(req) {
81
83
  switch (req.type) {
82
84
  case "edit":
83
85
  return `Do you want to make this edit to ${basename(req.path)}?`;
86
+ case "patch":
87
+ return `Do you want to apply this patch to ${req.paths.length} file${req.paths.length === 1 ? "" : "s"}?`;
84
88
  case "write":
85
89
  return `Do you want to ${req.fileExists ? "overwrite" : "create"} ${basename(req.path)}?`;
86
90
  case "bash":
@@ -99,6 +103,8 @@ function RequestPreview({ request }) {
99
103
  return _jsx(BashPreview, { command: request.command, cwd: request.cwd });
100
104
  case "edit":
101
105
  return _jsx(DiffView, { diff: request.diff });
106
+ case "patch":
107
+ return _jsx(DiffView, { diff: request.diff });
102
108
  case "write":
103
109
  return _jsx(WritePreview, { path: request.path, content: request.content });
104
110
  }
@@ -33,6 +33,7 @@ export interface DisplayToolCall {
33
33
  */
34
34
  rawArguments?: string;
35
35
  result?: string;
36
+ resultCollapsed?: boolean;
36
37
  isError?: boolean;
37
38
  metadata?: ToolResultMetadata;
38
39
  /** Set when the tool_start event was received. Used to render elapsed time. */
@@ -46,7 +46,6 @@ export function toolCallsFromParts(parts) {
46
46
  const FULL_DETAIL_WINDOW = 24;
47
47
  const MAX_OLD_CONTENT_CHARS = 1200;
48
48
  const MAX_OLD_REASONING_CHARS = 600;
49
- const MAX_OLD_TOOL_RESULT_CHARS = 800;
50
49
  export function compactDisplayMessages(messages) {
51
50
  if (messages.length === 0) {
52
51
  return messages;
@@ -97,11 +96,13 @@ function compactDisplayPart(part) {
97
96
  };
98
97
  }
99
98
  function compactToolCall(toolCall) {
99
+ if (toolCall.result === undefined) {
100
+ return toolCall;
101
+ }
100
102
  return {
101
103
  ...toolCall,
102
- result: toolCall.result
103
- ? truncateText(toolCall.result, MAX_OLD_TOOL_RESULT_CHARS)
104
- : toolCall.result,
104
+ result: undefined,
105
+ resultCollapsed: true,
105
106
  };
106
107
  }
107
108
  const PREVIOUS_SUMMARY_PREFIX = /^Previous conversation summary:\s*\n?([\s\S]*)$/;
@@ -105,8 +105,8 @@ function ToolsPart({ toolCalls, terminalColumns, verboseTrace, pendingApproval,
105
105
  }
106
106
  const lastIdx = toolCalls.length - 1;
107
107
  return (_jsx(Box, { flexDirection: "column", children: toolCalls.map((tc, idx) => {
108
- const isWaitingApproval = tc.result === undefined && !!pendingApproval && approvalMatchesTool(pendingApproval, tc);
109
- return (_jsx(ToolCallDisplay, { toolCall: tc, isStreaming: tc.result === undefined, verbose: verboseTrace, terminalColumns: terminalColumns, showExpandHint: showExpandHint && idx === lastIdx, waitingApproval: isWaitingApproval, compactTop: idx === 0 && compactTop, nowTick: nowTick }, tc.id));
108
+ const isWaitingApproval = isToolPending(tc) && !!pendingApproval && approvalMatchesTool(pendingApproval, tc);
109
+ return (_jsx(ToolCallDisplay, { toolCall: tc, isStreaming: isToolPending(tc), verbose: verboseTrace, terminalColumns: terminalColumns, showExpandHint: showExpandHint && idx === lastIdx, waitingApproval: isWaitingApproval, compactTop: idx === 0 && compactTop, nowTick: nowTick }, tc.id));
110
110
  }) }));
111
111
  }
112
112
  function fallbackStreamingParts(content, tools) {
@@ -192,7 +192,7 @@ function findActiveTraceGroup(groups, pendingApproval) {
192
192
  return undefined;
193
193
  }
194
194
  function isTraceGroupWaitingForApproval(group, pendingApproval) {
195
- return !!pendingApproval && group.raw.some((tool) => tool.result === undefined && approvalMatchesTool(pendingApproval, tool));
195
+ return !!pendingApproval && group.raw.some((tool) => isToolPending(tool) && approvalMatchesTool(pendingApproval, tool));
196
196
  }
197
197
  function approvalMatchesTool(hint, tc) {
198
198
  if (hint.toolName !== tc.name)
@@ -202,6 +202,9 @@ function approvalMatchesTool(hint, tc) {
202
202
  }
203
203
  return !hint.path || hint.path === tc.args.path;
204
204
  }
205
+ function isToolPending(tool) {
206
+ return tool.result === undefined && tool.resultCollapsed !== true;
207
+ }
205
208
  function ReasoningTraceBlock({ reasoning }) {
206
209
  const theme = useTheme();
207
210
  const lines = React.useMemo(() => reasoning.split("\n").filter((l) => l.trim() !== ""), [reasoning]);
@@ -280,6 +283,8 @@ function getToolHeader(toolCall) {
280
283
  }
281
284
  }
282
285
  function summarizeToolResult(tc) {
286
+ if (tc.resultCollapsed)
287
+ return tc.isError ? "error output collapsed" : "result collapsed";
283
288
  if (tc.result === undefined)
284
289
  return "pending";
285
290
  const raw = tc.result.replace(/\r\n/g, "\n");
@@ -441,7 +446,7 @@ function ToolCallDisplay({ toolCall, isStreaming, verbose, terminalColumns, show
441
446
  summary = "⏸ waiting for approval";
442
447
  summaryColor = theme.warning;
443
448
  }
444
- else if (toolCall.result === undefined && toolCall.startedAt) {
449
+ else if (isToolPending(toolCall) && toolCall.startedAt) {
445
450
  void nowTick;
446
451
  summary = "running";
447
452
  summaryColor = theme.toolPending;
@@ -454,18 +459,19 @@ function ToolCallDisplay({ toolCall, isStreaming, verbose, terminalColumns, show
454
459
  summaryColor = theme.success;
455
460
  }
456
461
  const editDetails = getEditDiffDetails(toolCall);
457
- const isEditDiff = editDetails !== null && toolCall.result !== undefined;
462
+ const isEditDiff = editDetails !== null && toolCall.result !== undefined && !toolCall.resultCollapsed;
463
+ const showSummary = !toolCall.resultCollapsed || waitingApproval;
458
464
  // Only show the file preview once the tool actually executed. During the
459
465
  // streaming-args phase, args.content is incomplete and re-rendering the
460
466
  // entire body per delta both looks chaotic and breaks on partial escapes.
461
- const isWritePreview = toolCall.name === "write" && !toolCall.isError && toolCall.result !== undefined;
462
- return (_jsxs(Box, { flexDirection: "column", marginLeft: 2, marginTop: compactTop ? 0 : 1, children: [_jsxs(Box, { children: [_jsxs(Text, { color: bulletColor, children: [glyph, " "] }), _jsx(Text, { bold: true, color: theme.toolName, children: name }), header && _jsxs(Text, { color: theme.muted, children: ["(", header, ")"] })] }), _jsx(Box, { marginLeft: 2, children: _jsxs(Text, { color: summaryColor, children: ["\u23BF ", summary] }) }), toolCall.isError && toolCall.result && (_jsx(Box, { marginLeft: 4, flexDirection: "column", children: toolCall.result.replace(/\r\n/g, "\n").split("\n").slice(0, 6).map((line, i) => (_jsx(Text, { color: theme.error, children: line }, i))) })), isEditDiff && (_jsx(DiffBlock, { diff: editDetails.diff, terminalColumns: terminalColumns, maxLines: maxLines, verbose: verbose, showExpandHint: showExpandHint })), isWritePreview && (_jsx(WritePreview, { content: String(toolCall.args.content || ""), maxLines: maxLines, verbose: verbose, showExpandHint: showExpandHint })), !toolCall.isError && !isEditDiff && !isWritePreview && highlighted && (_jsx(OutputPreview, { text: highlighted, maxLines: maxLines, verbose: verbose, showExpandHint: showExpandHint }))] }));
467
+ const isWritePreview = toolCall.name === "write" && !toolCall.isError && toolCall.result !== undefined && !toolCall.resultCollapsed;
468
+ return (_jsxs(Box, { flexDirection: "column", marginLeft: 2, marginTop: compactTop ? 0 : 1, children: [_jsxs(Box, { children: [_jsxs(Text, { color: bulletColor, children: [glyph, " "] }), _jsx(Text, { bold: true, color: theme.toolName, children: name }), header && _jsxs(Text, { color: theme.muted, children: ["(", header, ")"] })] }), showSummary && (_jsx(Box, { marginLeft: 2, children: _jsxs(Text, { color: summaryColor, children: ["\u23BF ", summary] }) })), toolCall.isError && toolCall.result && (_jsx(Box, { marginLeft: 4, flexDirection: "column", children: toolCall.result.replace(/\r\n/g, "\n").split("\n").slice(0, 6).map((line, i) => (_jsx(Text, { color: theme.error, children: line }, i))) })), isEditDiff && (_jsx(DiffBlock, { diff: editDetails.diff, terminalColumns: terminalColumns, maxLines: maxLines, verbose: verbose, showExpandHint: showExpandHint })), isWritePreview && (_jsx(WritePreview, { content: String(toolCall.args.content || ""), maxLines: maxLines, verbose: verbose, showExpandHint: showExpandHint })), !toolCall.isError && !isEditDiff && !isWritePreview && highlighted && (_jsx(OutputPreview, { text: highlighted, maxLines: maxLines, verbose: verbose, showExpandHint: showExpandHint }))] }));
463
469
  }
464
470
  function SubagentToolDisplay({ toolCall, verbose, terminalColumns, compactTop, }) {
465
471
  const theme = useTheme();
466
472
  const subagents = subagentsFrom(toolCall);
467
473
  const hasError = toolCall.isError || subagents.some((subagent) => (subagent.status === "failed" || subagent.status === "blocked" || subagent.status === "cancelled"));
468
- const bulletColor = hasError ? theme.error : toolCall.result === undefined ? theme.toolPending : theme.user;
474
+ const bulletColor = hasError ? theme.error : isToolPending(toolCall) ? theme.toolPending : theme.user;
469
475
  const detailWidth = Math.max(24, terminalColumns - 10);
470
476
  const rows = verbose ? sortSubagents(subagents) : sortSubagents(subagents).slice(0, 4);
471
477
  const omitted = Math.max(0, subagents.length - rows.length);
@@ -323,7 +323,7 @@ function isFailedSubagent(subagent) {
323
323
  return subagent.status === "failed" || subagent.status === "blocked" || subagent.status === "cancelled";
324
324
  }
325
325
  function isToolPending(tool) {
326
- return tool.result === undefined;
326
+ return tool.result === undefined && tool.resultCollapsed !== true;
327
327
  }
328
328
  function isDirectoryLikeGlob(pattern) {
329
329
  const normalized = pattern.trim();
@@ -1147,6 +1147,8 @@ export function App({ agent, args, sessionManager, createProvider, registry, ski
1147
1147
  return { toolName: "bash", command: r.command };
1148
1148
  if (r.type === "edit")
1149
1149
  return { toolName: "edit", path: r.path };
1150
+ if (r.type === "patch")
1151
+ return { toolName: "edit", path: r.paths[0] ?? r.path };
1150
1152
  if (r.type === "write")
1151
1153
  return { toolName: "write", path: r.path };
1152
1154
  return null;
@@ -60,7 +60,7 @@ function buildOptions(request) {
60
60
  },
61
61
  ];
62
62
  }
63
- // edit / write
63
+ // edit / write / patch
64
64
  return [
65
65
  { id: "yes", label: "Yes", allowAmend: true, amendPlaceholder: "and tell Claude what to do next" },
66
66
  {
@@ -76,6 +76,8 @@ function dialogTitle(req) {
76
76
  switch (req.type) {
77
77
  case "edit":
78
78
  return "Edit file";
79
+ case "patch":
80
+ return "Apply patch";
79
81
  case "write":
80
82
  return req.fileExists ? "Overwrite file" : "Create file";
81
83
  case "bash":
@@ -88,6 +90,8 @@ function dialogQuestion(req) {
88
90
  switch (req.type) {
89
91
  case "edit":
90
92
  return `Do you want to make this edit to ${basename(req.path)}?`;
93
+ case "patch":
94
+ return `Do you want to apply this patch to ${req.paths.length} file${req.paths.length === 1 ? "" : "s"}?`;
91
95
  case "write":
92
96
  return `Do you want to ${req.fileExists ? "overwrite" : "create"} ${basename(req.path)}?`;
93
97
  case "bash":
@@ -106,6 +110,8 @@ function RequestPreview({ request }) {
106
110
  return _jsx(BashPreview, { command: request.command, cwd: request.cwd });
107
111
  case "edit":
108
112
  return _jsx(DiffView, { diff: request.diff });
113
+ case "patch":
114
+ return _jsx(DiffView, { diff: request.diff });
109
115
  case "write":
110
116
  return _jsx(WritePreview, { path: request.path, content: request.content });
111
117
  }
@@ -33,6 +33,7 @@ export interface DisplayToolCall {
33
33
  */
34
34
  rawArguments?: string;
35
35
  result?: string;
36
+ resultCollapsed?: boolean;
36
37
  isError?: boolean;
37
38
  metadata?: ToolResultMetadata;
38
39
  /** Set when the tool_start event was received. Used to render elapsed time. */
@@ -46,7 +46,6 @@ export function toolCallsFromParts(parts) {
46
46
  const FULL_DETAIL_WINDOW = 24;
47
47
  const MAX_OLD_CONTENT_CHARS = 1200;
48
48
  const MAX_OLD_REASONING_CHARS = 600;
49
- const MAX_OLD_TOOL_RESULT_CHARS = 800;
50
49
  export function compactDisplayMessages(messages) {
51
50
  if (messages.length === 0) {
52
51
  return messages;
@@ -97,11 +96,13 @@ function compactDisplayPart(part) {
97
96
  };
98
97
  }
99
98
  function compactToolCall(toolCall) {
99
+ if (toolCall.result === undefined) {
100
+ return toolCall;
101
+ }
100
102
  return {
101
103
  ...toolCall,
102
- result: toolCall.result
103
- ? truncateText(toolCall.result, MAX_OLD_TOOL_RESULT_CHARS)
104
- : toolCall.result,
104
+ result: undefined,
105
+ resultCollapsed: true,
105
106
  };
106
107
  }
107
108
  const PREVIOUS_SUMMARY_PREFIX = /^Previous conversation summary:\s*\n?([\s\S]*)$/;
@@ -1,7 +1,7 @@
1
1
  import { countUnifiedDiffChanges } from "../diff-stats.js";
2
2
  export const EDIT_COLLAPSED_DIFF_LINES = 20;
3
3
  export function getEditDiffDetails(tool) {
4
- if (tool.name !== "edit" || tool.isError)
4
+ if ((tool.name !== "edit" && tool.name !== "apply_patch") || tool.isError)
5
5
  return null;
6
6
  const metadata = tool.metadata;
7
7
  const metadataDiff = readMetadataString(metadata, "diff");
@@ -12,9 +12,14 @@ export function getEditDiffDetails(tool) {
12
12
  const added = readMetadataNumber(metadata, "addedLines") ?? counted.added;
13
13
  const removed = readMetadataNumber(metadata, "removedLines") ?? counted.removed;
14
14
  const path = readMetadataString(metadata, "path")
15
+ ?? readFirstMetadataPath(metadata)
15
16
  ?? (typeof tool.args.path === "string" ? tool.args.path : undefined);
16
17
  return { diff, added, removed, path };
17
18
  }
19
+ function readFirstMetadataPath(metadata) {
20
+ const value = metadata?.paths;
21
+ return Array.isArray(value) && typeof value[0] === "string" ? value[0] : undefined;
22
+ }
18
23
  export function formatEditSuccessSummary(details) {
19
24
  const stats = details ? formatEditStats(details.added, details.removed) : "";
20
25
  return `Succeeded. File edited.${stats ? ` ${stats}` : ""}`;
@@ -84,15 +84,18 @@ function ToolsPart({ toolCalls, terminalColumns, verboseTrace, theme, }) {
84
84
  * body as content. Matches opencode's `theme.block` pattern.
85
85
  */
86
86
  function ToolCard({ tool, terminalColumns, verboseTrace, theme, }) {
87
- const pending = tool.result === undefined;
87
+ const pending = isToolPending(tool);
88
88
  const error = tool.isError;
89
89
  const target = describeToolTarget(tool);
90
90
  const headerColor = pending ? theme.toolPending : error ? theme.toolError : theme.textMuted;
91
91
  const titleText = target ? `${tool.name} ${target}` : tool.name;
92
- const editDetails = (tool.name === "edit" || tool.name === "multiedit" || tool.name === "write")
92
+ const editDetails = (tool.name === "edit" || tool.name === "apply_patch" || tool.name === "multiedit" || tool.name === "write")
93
93
  ? getEditDiffDetails(tool)
94
94
  : null;
95
- return (_jsxs("box", { style: { flexDirection: "column", marginBottom: 1 }, children: [_jsx("text", { fg: headerColor, content: titleText }), _jsxs("box", { style: { paddingLeft: 1, flexDirection: "column" }, children: [!pending && tool.result && verboseTrace && (_jsx(ToolResultPreview, { result: tool.result, error: error, terminalColumns: terminalColumns, theme: theme })), editDetails && editDetails.diff && (_jsx(EditDiffPreview, { diff: editDetails.diff, theme: theme }))] })] }));
95
+ return (_jsxs("box", { style: { flexDirection: "column", marginBottom: 1 }, children: [_jsx("text", { fg: headerColor, content: titleText }), _jsxs("box", { style: { paddingLeft: 1, flexDirection: "column" }, children: [!pending && tool.result && verboseTrace && (_jsx(ToolResultPreview, { result: tool.result, error: error, terminalColumns: terminalColumns, theme: theme })), !tool.resultCollapsed && editDetails && editDetails.diff && (_jsx(EditDiffPreview, { diff: editDetails.diff, theme: theme }))] })] }));
96
+ }
97
+ function isToolPending(tool) {
98
+ return tool.result === undefined && tool.resultCollapsed !== true;
96
99
  }
97
100
  function describeToolTarget(tool) {
98
101
  const args = tool.args || {};
@@ -226,8 +226,8 @@ function buildExecuteGroup(classifier, tool, options, pending, startedAt, hasErr
226
226
  function buildMutationGroup(classifier, raw, options, pending, startedAt, hasError, errorCount) {
227
227
  const items = raw
228
228
  .map((tool) => {
229
- const path = formatTracePath(tool.args.path ?? "", options.homeDir);
230
- const details = tool.name === "edit" ? getEditDiffDetails(tool) : null;
229
+ const path = formatTracePath(tool.args.path ?? firstMetadataPath(tool) ?? "", options.homeDir);
230
+ const details = tool.name === "edit" || tool.name === "apply_patch" ? getEditDiffDetails(tool) : null;
231
231
  const suffix = details ? ` ${formatCompactEditStats(details.added, details.removed)}` : "";
232
232
  return path ? `${path}${suffix}` : "";
233
233
  })
@@ -323,7 +323,7 @@ function isFailedSubagent(subagent) {
323
323
  return subagent.status === "failed" || subagent.status === "blocked" || subagent.status === "cancelled";
324
324
  }
325
325
  function isToolPending(tool) {
326
- return tool.result === undefined;
326
+ return tool.result === undefined && tool.resultCollapsed !== true;
327
327
  }
328
328
  function isDirectoryLikeGlob(pattern) {
329
329
  const normalized = pattern.trim();
@@ -400,8 +400,15 @@ function toolHeader(tool, homeDir) {
400
400
  return formatTracePath(value, homeDir);
401
401
  }
402
402
  }
403
+ const path = firstMetadataPath(tool);
404
+ if (path)
405
+ return formatTracePath(path, homeDir);
403
406
  return undefined;
404
407
  }
408
+ function firstMetadataPath(tool) {
409
+ const paths = tool.metadata?.paths;
410
+ return Array.isArray(paths) && typeof paths[0] === "string" ? paths[0] : undefined;
411
+ }
405
412
  function formatCompactEditStats(added, removed) {
406
413
  const parts = [];
407
414
  if (added > 0)
package/dist/types.d.ts CHANGED
@@ -23,6 +23,16 @@ export interface AssistantMessage {
23
23
  content: string;
24
24
  reasoning?: string;
25
25
  toolCalls?: ToolCall[];
26
+ /** Model metadata captured for local usage statistics. */
27
+ model?: string;
28
+ providerId?: string;
29
+ modelId?: string;
30
+ usage?: TokenUsage;
31
+ error?: {
32
+ name: string;
33
+ message: string;
34
+ aborted?: boolean;
35
+ };
26
36
  }
27
37
  export interface ToolMessage {
28
38
  role: "tool";
@@ -90,9 +100,9 @@ export interface ParsedToolCall extends ToolCall {
90
100
  */
91
101
  argsCorrupt?: boolean;
92
102
  }
93
- export type ToolResultStatus = "success" | "no_match" | "partial" | "timeout" | "blocked" | "command_error";
103
+ export type ToolResultStatus = "success" | "no_match" | "partial" | "timeout" | "blocked" | "cancelled" | "command_error";
94
104
  export interface ToolResultMetadata {
95
- kind?: "search" | "read" | "write" | "edit" | "shell" | "web" | "security" | "lsp" | "question" | "subagent";
105
+ kind?: "search" | "read" | "write" | "edit" | "patch" | "shell" | "server" | "web" | "security" | "lsp" | "question" | "subagent";
96
106
  path?: string;
97
107
  pattern?: string;
98
108
  matches?: number;
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Self-update: `bubble update` / `bubble upgrade`, plus a cached startup
3
+ * "update available" check.
4
+ *
5
+ * Bubble ships as the npm package `@bubblebrain-ai/bubble`. Updating just means
6
+ * re-installing it globally with whatever package manager put it there, so we
7
+ * detect the install method and run the matching command.
8
+ */
9
+ export declare const PACKAGE_NAME = "@bubblebrain-ai/bubble";
10
+ export declare function getCurrentVersion(): string;
11
+ export declare function fetchLatestVersion(timeoutMs?: number): Promise<string | null>;
12
+ /**
13
+ * Compare two semver-ish strings. Returns 1 if a > b, -1 if a < b, 0 if equal.
14
+ * Handles `x.y.z` and a single pre-release tag (`x.y.z-beta.1`); a release
15
+ * always outranks a pre-release of the same numeric version.
16
+ */
17
+ export declare function compareVersions(a: string, b: string): number;
18
+ export type PackageManager = "npm" | "bun" | "pnpm" | "yarn" | "homebrew" | "unknown";
19
+ export interface InstallInfo {
20
+ manager: PackageManager;
21
+ isGlobal: boolean;
22
+ isLocalCheckout: boolean;
23
+ installPath: string;
24
+ }
25
+ /**
26
+ * Figure out how this copy of Bubble was installed by inspecting the real path
27
+ * of the package directory (two levels up from this module: dist/update -> pkg).
28
+ */
29
+ export declare function detectInstall(): InstallInfo;
30
+ export declare function upgradeCommandFor(manager: PackageManager): {
31
+ cmd: string;
32
+ args: string[];
33
+ } | null;
34
+ /**
35
+ * `bubble update` entry point. Returns a process exit code.
36
+ */
37
+ export declare function runUpdateCommand(opts?: {
38
+ checkOnly?: boolean;
39
+ }): Promise<number>;
40
+ /**
41
+ * Returns a one-line "update available" notice if the cached latest version is
42
+ * newer than the running one. Reads only a local cache file (fast, no network
43
+ * on the hot path); a stale cache triggers a fire-and-forget refresh so the
44
+ * next launch is accurate. Never throws.
45
+ */
46
+ export declare function getStartupUpdateNotice(): Promise<string | null>;