@bubblebrain-ai/bubble 0.0.18 → 0.0.20

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 (58) hide show
  1. package/dist/agent/internal-reminder-sanitizer.d.ts +1 -0
  2. package/dist/agent/internal-reminder-sanitizer.js +46 -0
  3. package/dist/agent.d.ts +9 -0
  4. package/dist/agent.js +305 -17
  5. package/dist/approval/controller.d.ts +6 -0
  6. package/dist/approval/controller.js +104 -11
  7. package/dist/debug-trace.js +4 -0
  8. package/dist/feishu/agent-host/run-driver.js +28 -0
  9. package/dist/hooks/config.d.ts +9 -0
  10. package/dist/hooks/config.js +278 -0
  11. package/dist/hooks/controller.d.ts +24 -0
  12. package/dist/hooks/controller.js +254 -0
  13. package/dist/hooks/index.d.ts +6 -0
  14. package/dist/hooks/index.js +4 -0
  15. package/dist/hooks/log.d.ts +14 -0
  16. package/dist/hooks/log.js +54 -0
  17. package/dist/hooks/runner.d.ts +5 -0
  18. package/dist/hooks/runner.js +225 -0
  19. package/dist/hooks/trust.d.ts +37 -0
  20. package/dist/hooks/trust.js +143 -0
  21. package/dist/hooks/types.d.ts +173 -0
  22. package/dist/hooks/types.js +46 -0
  23. package/dist/main.js +32 -0
  24. package/dist/memory/prompts.js +3 -1
  25. package/dist/model-catalog.js +2 -0
  26. package/dist/model-pricing.js +8 -0
  27. package/dist/network/chatgpt-transport.js +34 -9
  28. package/dist/network/provider-transport.d.ts +32 -0
  29. package/dist/network/provider-transport.js +265 -0
  30. package/dist/network/retry.d.ts +29 -0
  31. package/dist/network/retry.js +88 -0
  32. package/dist/network/system-proxy.d.ts +18 -0
  33. package/dist/network/system-proxy.js +175 -0
  34. package/dist/provider-anthropic.d.ts +1 -0
  35. package/dist/provider-anthropic.js +127 -52
  36. package/dist/provider-openai-codex.js +19 -29
  37. package/dist/session-log.js +3 -3
  38. package/dist/slash-commands/commands.js +84 -0
  39. package/dist/slash-commands/types.d.ts +2 -0
  40. package/dist/tools/edit-apply.js +63 -3
  41. package/dist/tools/edit.js +4 -4
  42. package/dist/tui/display-history.d.ts +4 -3
  43. package/dist/tui/display-history.js +34 -57
  44. package/dist/tui/display-sanitizer.d.ts +3 -0
  45. package/dist/tui/display-sanitizer.js +38 -0
  46. package/dist/tui/paste-placeholder.d.ts +1 -0
  47. package/dist/tui/paste-placeholder.js +7 -0
  48. package/dist/tui/run.d.ts +2 -0
  49. package/dist/tui/run.js +260 -155
  50. package/dist/tui/trace-groups.js +40 -4
  51. package/dist/tui/wordmark.d.ts +1 -0
  52. package/dist/tui/wordmark.js +56 -54
  53. package/dist/tui-ink/app.js +2 -1
  54. package/dist/tui-ink/trace-groups.js +40 -4
  55. package/dist/tui-opentui/app.js +2 -1
  56. package/dist/tui-opentui/trace-groups.js +40 -4
  57. package/dist/types.d.ts +27 -0
  58. package/package.json +1 -1
@@ -1,3 +1,19 @@
1
+ export function userInputStatusBadgeLabel(status) {
2
+ switch (status) {
3
+ case "queued":
4
+ return "QUEUED";
5
+ case "pending_steer":
6
+ return "STEER";
7
+ default:
8
+ return undefined;
9
+ }
10
+ }
11
+ export function setUserInputStatus(message, inputStatus) {
12
+ if (inputStatus)
13
+ return { ...message, inputStatus };
14
+ const { inputStatus: _inputStatus, ...rest } = message;
15
+ return rest;
16
+ }
1
17
  export function appendTextPart(parts, content) {
2
18
  if (!content)
3
19
  return;
@@ -40,18 +56,21 @@ export function toolCallsFromParts(parts) {
40
56
  }
41
57
  const MAX_VISIBLE_MESSAGES = 80;
42
58
  const FULL_DETAIL_WINDOW = 24;
43
- const MAX_OLD_CONTENT_CHARS = 1200;
44
- const MAX_OLD_REASONING_CHARS = 600;
45
59
  const COMPACTION_SUMMARY_ITEMS = 6;
46
60
  const COMPACTION_FILE_LIMIT = 8;
47
61
  const TOOL_PATH_KEYS = ["file", "path", "paths", "filePath"];
62
+ // Display-history folding policy: message text is NEVER rewritten or truncated.
63
+ // Visible messages keep their content verbatim (older ones only collapse bulky
64
+ // tool-result bodies, which the UI can re-expand). When history exceeds
65
+ // MAX_VISIBLE_MESSAGES, the entire older span is folded behind a single summary
66
+ // card — mirroring how mainstream coding agents present compacted history —
67
+ // instead of clipping individual messages mid-sentence.
48
68
  export function compactDisplayMessages(messages) {
49
69
  if (messages.length === 0) {
50
70
  return messages;
51
71
  }
52
72
  let hiddenCount = 0;
53
73
  let accumulatedTurns = 0;
54
- let accumulatedTokens = 0;
55
74
  const summarySections = [];
56
75
  const withoutSynthetic = messages.filter((message) => {
57
76
  if (message.syntheticKind !== "ui_compact_card") {
@@ -60,7 +79,6 @@ export function compactDisplayMessages(messages) {
60
79
  hiddenCount += message.hiddenCount ?? 0;
61
80
  if (message.compactionMeta) {
62
81
  accumulatedTurns += message.compactionMeta.turns;
63
- accumulatedTokens += message.compactionMeta.tokensSaved;
64
82
  for (const section of message.compactionMeta.summarySections) {
65
83
  summarySections.push(section);
66
84
  }
@@ -69,36 +87,31 @@ export function compactDisplayMessages(messages) {
69
87
  });
70
88
  const overflow = Math.max(0, withoutSynthetic.length - MAX_VISIBLE_MESSAGES);
71
89
  hiddenCount += overflow;
90
+ const hiddenMessages = overflow > 0 ? withoutSynthetic.slice(0, overflow) : [];
72
91
  const visible = overflow > 0 ? withoutSynthetic.slice(overflow) : withoutSynthetic;
73
92
  const detailStart = Math.max(0, visible.length - FULL_DETAIL_WINDOW);
74
93
  const compacted = visible.map((message, index) => {
75
94
  if (message.syntheticKind === "ui_compact_card") {
76
95
  return message;
77
96
  }
78
- return index < detailStart ? compactDisplayMessage(message) : message;
97
+ return index < detailStart ? collapseToolResults(message) : message;
79
98
  });
80
99
  if (hiddenCount === 0) {
81
100
  return compacted;
82
101
  }
83
- const truncatedMessages = visible.slice(0, Math.max(1, detailStart));
84
- const extractedMeta = extractCompactionMeta(truncatedMessages, hiddenCount, accumulatedTurns, accumulatedTokens, summarySections);
102
+ const extractedMeta = extractCompactionMeta(hiddenMessages, hiddenCount, accumulatedTurns, summarySections);
85
103
  return [buildCompactCard(extractedMeta), ...compacted];
86
104
  }
87
- function extractCompactionMeta(truncatedMessages, hiddenCount, previousTurns, previousTokens, previousSections) {
88
- const turnsInBatch = countUserTurns(truncatedMessages);
105
+ function extractCompactionMeta(hiddenMessages, hiddenCount, previousTurns, previousSections) {
106
+ const turnsInBatch = countUserTurns(hiddenMessages);
89
107
  const totalTurns = previousTurns + turnsInBatch;
90
- const messagesInBatch = truncatedMessages.length;
91
- const totalMessages = hiddenCount;
92
- const estimatedTokens = estimateTokenSavings(truncatedMessages);
93
- const totalTokens = previousTokens + estimatedTokens;
94
108
  const sections = [
95
109
  ...previousSections,
96
- ...extractSummarySections(truncatedMessages),
110
+ ...extractSummarySections(hiddenMessages),
97
111
  ];
98
112
  return {
99
113
  turns: totalTurns,
100
- messages: totalMessages,
101
- tokensSaved: totalTokens > 0 ? totalTokens : estimatedTokens,
114
+ messages: hiddenCount,
102
115
  summarySections: mergeSummarySections(sections, COMPACTION_SUMMARY_ITEMS),
103
116
  compactedAt: Date.now(),
104
117
  };
@@ -106,18 +119,6 @@ function extractCompactionMeta(truncatedMessages, hiddenCount, previousTurns, pr
106
119
  function countUserTurns(messages) {
107
120
  return messages.filter((message) => message.role === "user").length;
108
121
  }
109
- function estimateTokenSavings(messages) {
110
- let chars = 0;
111
- for (const message of messages) {
112
- chars += message.content.length;
113
- chars += (message.reasoning?.length ?? 0);
114
- for (const tool of message.toolCalls ?? []) {
115
- chars += (tool.result?.length ?? 0);
116
- chars += JSON.stringify(tool.args).length;
117
- }
118
- }
119
- return Math.ceil(chars / 4);
120
- }
121
122
  function extractSummarySections(messages) {
122
123
  const sections = [];
123
124
  const userMessages = messages
@@ -206,13 +207,6 @@ function mergeSummarySections(sections, maxItems) {
206
207
  .slice(0, maxItems);
207
208
  }
208
209
  function buildCompactCard(meta) {
209
- const formatNum = (n) => {
210
- if (n >= 1_000_000)
211
- return `${(n / 1_000_000).toFixed(1)}M`;
212
- if (n >= 1_000)
213
- return `${(n / 1_000).toFixed(1)}K`;
214
- return String(n);
215
- };
216
210
  const parts = [];
217
211
  if (meta.turns > 0) {
218
212
  parts.push(`${meta.turns} turn${meta.turns === 1 ? "" : "s"}`);
@@ -220,9 +214,6 @@ function buildCompactCard(meta) {
220
214
  if (meta.messages > 0) {
221
215
  parts.push(`${meta.messages} message${meta.messages === 1 ? "" : "s"}`);
222
216
  }
223
- if (meta.tokensSaved > 0) {
224
- parts.push(`~${formatNum(meta.tokensSaved)} tokens`);
225
- }
226
217
  const statsLine = parts.length > 0 ? `┃ ${parts.join(" · ")}` : "";
227
218
  const sectionLines = [];
228
219
  for (const section of meta.summarySections) {
@@ -238,16 +229,15 @@ function buildCompactCard(meta) {
238
229
  status: "responding",
239
230
  };
240
231
  }
241
- function compactDisplayMessage(message) {
232
+ // Collapses bulky tool-result bodies on older messages while leaving the
233
+ // message text (content, reasoning) verbatim — never truncate what the user
234
+ // or the assistant actually said.
235
+ function collapseToolResults(message) {
242
236
  if (message.syntheticKind === "ui_compact_card") {
243
237
  return message;
244
238
  }
245
239
  return {
246
240
  ...message,
247
- content: truncateText(message.content, MAX_OLD_CONTENT_CHARS),
248
- reasoning: message.reasoning
249
- ? truncateText(message.reasoning, MAX_OLD_REASONING_CHARS)
250
- : message.reasoning,
251
241
  toolCalls: message.toolCalls?.map(compactToolCall),
252
242
  parts: message.parts?.map(compactDisplayPart),
253
243
  };
@@ -260,10 +250,7 @@ function cloneToolCall(toolCall) {
260
250
  }
261
251
  function compactDisplayPart(part) {
262
252
  if (part.type === "text") {
263
- return {
264
- ...part,
265
- content: truncateText(part.content, MAX_OLD_CONTENT_CHARS),
266
- };
253
+ return part;
267
254
  }
268
255
  return {
269
256
  type: "tools",
@@ -280,16 +267,6 @@ function compactToolCall(toolCall) {
280
267
  resultCollapsed: true,
281
268
  };
282
269
  }
283
- export function truncateText(value, maxChars) {
284
- if (value.length <= maxChars) {
285
- return value;
286
- }
287
- const head = Math.max(1, Math.floor(maxChars * 0.7));
288
- const tail = Math.max(1, maxChars - head - 32);
289
- const omitted = value.length - head - tail;
290
- const separator = "─".repeat(12);
291
- return `${value.slice(0, head)}\n${separator} ✂ ${omitted} chars truncated ${separator}\n${value.slice(-tail)}`;
292
- }
293
270
  function shorten(text, maxChars) {
294
271
  const normalized = text.replace(/\s+/g, " ").trim();
295
272
  if (normalized.length <= maxChars) {
@@ -0,0 +1,3 @@
1
+ import type { DisplayMessage } from "./display-history.js";
2
+ export declare function sanitizeDisplayMessages(messages: DisplayMessage[]): DisplayMessage[];
3
+ export declare function sanitizeDisplayMessage(message: DisplayMessage): DisplayMessage;
@@ -0,0 +1,38 @@
1
+ import { sanitizeInternalReasoningText, sanitizeInternalReminderBlocks, } from "../agent/internal-reminder-sanitizer.js";
2
+ export function sanitizeDisplayMessages(messages) {
3
+ return messages.map(sanitizeDisplayMessage);
4
+ }
5
+ export function sanitizeDisplayMessage(message) {
6
+ if (message.role !== "assistant")
7
+ return message;
8
+ const content = sanitizeInternalReminderBlocks(message.content);
9
+ const reasoning = message.reasoning !== undefined
10
+ ? sanitizeInternalReasoningText(message.reasoning)
11
+ : undefined;
12
+ const sanitizedParts = message.parts
13
+ ?.map(sanitizeDisplayPart)
14
+ .filter(isRenderableDisplayPart);
15
+ const parts = sanitizedParts && sanitizedParts.length > 0 ? sanitizedParts : undefined;
16
+ if (content === message.content
17
+ && reasoning === message.reasoning
18
+ && parts === message.parts) {
19
+ return message;
20
+ }
21
+ return {
22
+ ...message,
23
+ content,
24
+ reasoning,
25
+ parts,
26
+ };
27
+ }
28
+ function sanitizeDisplayPart(part) {
29
+ if (part.type !== "text")
30
+ return part;
31
+ const content = sanitizeInternalReminderBlocks(part.content);
32
+ return content === part.content ? part : { ...part, content };
33
+ }
34
+ function isRenderableDisplayPart(part) {
35
+ if (part.type === "text")
36
+ return !!part.content.trim();
37
+ return part.toolCalls.length > 0;
38
+ }
@@ -7,4 +7,5 @@ export interface PastedContentReference {
7
7
  export declare function countTextLines(text: string): number;
8
8
  export declare function shouldCollapsePastedContent(text: string): boolean;
9
9
  export declare function createPastedContentMarker(content: string, index?: number): string;
10
+ export declare function decodePastedBytes(bytes: unknown): string;
10
11
  export declare function expandPastedContentMarkers(displayText: string, references: PastedContentReference[]): string;
@@ -16,6 +16,13 @@ export function createPastedContentMarker(content, index = 1) {
16
16
  : `${content.length} ${content.length === 1 ? "char" : "chars"}`;
17
17
  return `[Pasted text #${safeIndex} +${size}]`;
18
18
  }
19
+ export function decodePastedBytes(bytes) {
20
+ if (typeof bytes === "string")
21
+ return bytes;
22
+ if (bytes instanceof Uint8Array)
23
+ return new TextDecoder().decode(bytes);
24
+ return "";
25
+ }
19
26
  export function expandPastedContentMarkers(displayText, references) {
20
27
  if (references.length === 0 || displayText.length === 0)
21
28
  return displayText;
package/dist/tui/run.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { type Agent } from "../agent.js";
2
2
  import type { CliArgs } from "../cli.js";
3
3
  import type { ThemeMode } from "../config.js";
4
+ import type { ExternalHookController } from "../hooks/controller.js";
4
5
  import type { SessionManager } from "../session.js";
5
6
  import type { PlanDecision, Provider } from "../types.js";
6
7
  import type { ProviderRegistry } from "../provider-registry.js";
@@ -29,6 +30,7 @@ export interface RunTuiOptions {
29
30
  questionController?: QuestionController;
30
31
  bashAllowlist?: BashAllowlist;
31
32
  settingsManager?: SettingsManager;
33
+ hookController?: ExternalHookController;
32
34
  lspService?: LspService;
33
35
  mcpManager?: McpManager;
34
36
  themeMode?: ThemeMode;