@bubblebrain-ai/bubble 0.0.13 → 0.0.14

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 (75) 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/context/compact.js +9 -3
  10. package/dist/context/projector.js +27 -12
  11. package/dist/debug-trace.d.ts +27 -0
  12. package/dist/debug-trace.js +385 -0
  13. package/dist/feishu/agent-host/approval-card.js +9 -0
  14. package/dist/feishu/serve.js +7 -1
  15. package/dist/main.js +28 -0
  16. package/dist/model-catalog.js +1 -0
  17. package/dist/orchestrator/default-hooks.js +19 -8
  18. package/dist/orchestrator/hooks.d.ts +1 -0
  19. package/dist/prompt/environment.js +2 -0
  20. package/dist/prompt/reminders.d.ts +5 -6
  21. package/dist/prompt/reminders.js +8 -9
  22. package/dist/prompt/runtime.js +2 -2
  23. package/dist/provider-openai-codex.d.ts +7 -0
  24. package/dist/provider-openai-codex.js +265 -124
  25. package/dist/provider-registry.d.ts +2 -0
  26. package/dist/provider-registry.js +58 -9
  27. package/dist/provider.d.ts +3 -0
  28. package/dist/provider.js +5 -1
  29. package/dist/session-log.js +13 -1
  30. package/dist/slash-commands/commands.js +12 -0
  31. package/dist/slash-commands/types.d.ts +2 -0
  32. package/dist/stats/usage.d.ts +52 -0
  33. package/dist/stats/usage.js +414 -0
  34. package/dist/tools/apply-patch.d.ts +9 -0
  35. package/dist/tools/apply-patch.js +330 -0
  36. package/dist/tools/bash.js +205 -44
  37. package/dist/tools/edit-apply.d.ts +5 -2
  38. package/dist/tools/edit-apply.js +221 -31
  39. package/dist/tools/edit.js +12 -3
  40. package/dist/tools/file-mutation-queue.d.ts +1 -0
  41. package/dist/tools/file-mutation-queue.js +12 -1
  42. package/dist/tools/index.d.ts +2 -0
  43. package/dist/tools/index.js +7 -1
  44. package/dist/tools/patch-apply.d.ts +41 -0
  45. package/dist/tools/patch-apply.js +312 -0
  46. package/dist/tools/server-manager.d.ts +36 -0
  47. package/dist/tools/server-manager.js +234 -0
  48. package/dist/tools/server.d.ts +6 -0
  49. package/dist/tools/server.js +245 -0
  50. package/dist/tools/write.d.ts +3 -6
  51. package/dist/tools/write.js +26 -46
  52. package/dist/tui/display-history.d.ts +1 -0
  53. package/dist/tui/display-history.js +5 -4
  54. package/dist/tui/edit-diff.js +6 -1
  55. package/dist/tui/model-picker-data.d.ts +10 -0
  56. package/dist/tui/model-picker-data.js +32 -0
  57. package/dist/tui/run.js +632 -89
  58. package/dist/tui/tool-renderers/fallback.js +1 -1
  59. package/dist/tui/tool-renderers/write-preview.js +2 -0
  60. package/dist/tui/trace-groups.js +10 -3
  61. package/dist/tui-ink/app.js +1 -4
  62. package/dist/tui-ink/approval/approval-dialog.js +7 -1
  63. package/dist/tui-ink/display-history.d.ts +1 -0
  64. package/dist/tui-ink/display-history.js +5 -4
  65. package/dist/tui-ink/message-list.js +14 -8
  66. package/dist/tui-ink/trace-groups.js +1 -1
  67. package/dist/tui-opentui/app.js +2 -0
  68. package/dist/tui-opentui/approval/approval-dialog.js +7 -1
  69. package/dist/tui-opentui/display-history.d.ts +1 -0
  70. package/dist/tui-opentui/display-history.js +5 -4
  71. package/dist/tui-opentui/edit-diff.js +6 -1
  72. package/dist/tui-opentui/message-list.js +6 -3
  73. package/dist/tui-opentui/trace-groups.js +10 -3
  74. package/dist/types.d.ts +12 -2
  75. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -27,6 +27,7 @@ import { buildMemoryPrompt, formatMemoryStartupResult, recordMemoryCitations, ru
27
27
  import { basename } from "node:path";
28
28
  import { normalizeSingleLine, truncateVisual } from "./text-display.js";
29
29
  import { BUBBLE_WORDMARK } from "./tui/wordmark.js";
30
+ import { configureDebugTrace, summarizeAgentEventForTrace, summarizeTraceMessage, traceEvent, } from "./debug-trace.js";
30
31
  async function main() {
31
32
  const args = parseArgs(process.argv.slice(2));
32
33
  if (process.argv.includes("-h") || process.argv.includes("--help")) {
@@ -72,6 +73,7 @@ async function main() {
72
73
  baseURL: defaultProvider.baseURL,
73
74
  thinkingLevel: args.thinkingLevel,
74
75
  promptCacheKey: sessionPromptCacheKey,
76
+ openAICodexAuth: registry.createOpenAICodexAuthAdapter(defaultProvider.id),
75
77
  })
76
78
  : createUnavailableProvider(unavailableProviderMessage);
77
79
  const createProvider = (providerId, apiKey, baseURL) => createProviderInstance({
@@ -80,6 +82,7 @@ async function main() {
80
82
  baseURL,
81
83
  thinkingLevel: args.thinkingLevel,
82
84
  promptCacheKey: sessionPromptCacheKey,
85
+ openAICodexAuth: registry.createOpenAICodexAuthAdapter(providerId),
83
86
  });
84
87
  const createProviderForRoute = async (route) => {
85
88
  const providerId = route.providerId;
@@ -276,6 +279,25 @@ async function main() {
276
279
  tools: tools.map((tool) => tool.name),
277
280
  memoryPrompt,
278
281
  });
282
+ const traceInfo = configureDebugTrace({
283
+ cwd: args.cwd,
284
+ sessionFile: sessionManager?.getSessionFile(),
285
+ provider: activeProviderId || "none",
286
+ model: activeModel || "none",
287
+ renderer: printMode ? "print" : "opentui-core",
288
+ });
289
+ if (traceInfo.enabled) {
290
+ traceEvent("run_start", {
291
+ tracePath: traceInfo.path,
292
+ rawEnabled: traceInfo.rawEnabled,
293
+ resumed: resumedExistingSession,
294
+ printMode,
295
+ mode: initialMode,
296
+ thinkingLevel: initialThinkingLevel,
297
+ tools: tools.length,
298
+ cwd: args.cwd,
299
+ });
300
+ }
279
301
  const budgetLedger = new BudgetLedger();
280
302
  let sessionTitleUpdater;
281
303
  const agent = new Agent({
@@ -301,6 +323,9 @@ async function main() {
301
323
  if (message.role === "meta")
302
324
  return;
303
325
  sessionManager.appendMessage(message);
326
+ traceEvent("session_message_persisted", {
327
+ message: summarizeTraceMessage(message),
328
+ });
304
329
  sessionTitleUpdater?.handlePersistedMessage(message);
305
330
  if (message.role === "assistant") {
306
331
  recordMemoryCitations(args.cwd, message.content);
@@ -404,6 +429,7 @@ async function main() {
404
429
  process.exit(1);
405
430
  }
406
431
  for await (const event of agent.run(prompt, args.cwd)) {
432
+ traceEvent("print_agent_event", summarizeAgentEventForTrace(event));
407
433
  if (event.type === "text_delta") {
408
434
  process.stdout.write(event.content);
409
435
  }
@@ -465,7 +491,9 @@ async function main() {
465
491
  }
466
492
  }
467
493
  finally {
494
+ traceEvent("run_shutdown_start");
468
495
  await shutdownRuntime();
496
+ traceEvent("run_shutdown_end");
469
497
  }
470
498
  }
471
499
  function printOpenTuiExitSummary(sessionManager, options) {
@@ -25,6 +25,7 @@ const OPENAI_CHAT_LEVELS = ["off"];
25
25
  const TOGGLE_THINKING_LEVELS = ["off", "medium"];
26
26
  const DEEPSEEK_V4_LEVELS = ["high", "max"];
27
27
  export const BUILTIN_MODELS = [
28
+ { id: "gpt-5.5", name: "gpt-5.5", providerId: "openai-codex", reasoningLevels: ALL_OPENAI_LEVELS, contextWindow: 272000, toolOutputTokenLimit: 10000 },
28
29
  { id: "gpt-5.4", name: "gpt-5.4", providerId: "openai-codex", reasoningLevels: ALL_OPENAI_LEVELS, contextWindow: 272000 },
29
30
  { id: "gpt-5.4-mini", name: "gpt-5.4-mini", providerId: "openai-codex", reasoningLevels: ALL_OPENAI_LEVELS, contextWindow: 272000 },
30
31
  { id: "gpt-5.3-codex", name: "gpt-5.3-codex", providerId: "openai-codex", reasoningLevels: ALL_OPENAI_LEVELS, contextWindow: 272000 },
@@ -78,11 +78,18 @@ export function createDefaultHooks() {
78
78
  }
79
79
  ctx.state.evidenceTracker?.observe(ctx.toolCall, ctx.result);
80
80
  ctx.state.governor?.afterToolResult(ctx.toolCall, ctx.result);
81
- // Edit/write retry-escalation: if the same tool with the same args
82
- // failed twice in a row, models especially thinking-heavy ones
83
- // can spiral on "identical content" / "not found" errors. Nudge them
84
- // to change strategy.
85
- if ((ctx.toolCall.name === "edit" || ctx.toolCall.name === "write") && ctx.result.isError) {
81
+ // Edit/write retry-escalation: models can spiral on "identical content"
82
+ // or "not found" errors. Nudge them to re-ground or switch strategy.
83
+ if (isMutationTool(ctx.toolCall.name) && ctx.result.isError) {
84
+ if (ctx.toolCall.name === "edit" && ctx.result.status === "no_match" && ctx.result.metadata?.kind === "edit") {
85
+ const path = typeof ctx.result.metadata.path === "string" ? ctx.result.metadata.path : "";
86
+ const reminded = ctx.state.editNoMatchReminderPaths ?? (ctx.state.editNoMatchReminderPaths = []);
87
+ if (path && !reminded.includes(path)) {
88
+ reminded.push(path);
89
+ const summary = ctx.result.content.split("\n")[0] || "";
90
+ ctx.queueReminder(buildEditRetryEscalationReminder(`Edit oldText did not match ${path}. ${summary}`));
91
+ }
92
+ }
86
93
  const hash = hashEditCall(ctx.toolCall);
87
94
  const history = ctx.state.recentEditFailures ?? (ctx.state.recentEditFailures = []);
88
95
  history.push(hash);
@@ -96,10 +103,11 @@ export function createDefaultHooks() {
96
103
  ctx.queueReminder(buildEditRetryEscalationReminder(`Last failure: ${ctx.toolCall.name} on the same target with identical arguments. ${summary}`));
97
104
  }
98
105
  }
99
- else if ((ctx.toolCall.name === "edit" || ctx.toolCall.name === "write") && !ctx.result.isError) {
106
+ else if (isMutationTool(ctx.toolCall.name) && !ctx.result.isError) {
100
107
  // Successful mutation resets the dedup state so a later, unrelated
101
108
  // failure won't fire the reminder spuriously.
102
109
  ctx.state.recentEditFailures = [];
110
+ ctx.state.editNoMatchReminderPaths = [];
103
111
  ctx.state.editRetryReminderSent = false;
104
112
  }
105
113
  // Redundant-Read detection moved into the read tool itself: it now
@@ -151,10 +159,13 @@ function markCodeChanged(state) {
151
159
  state.codeChanged = true;
152
160
  }
153
161
  function isCodeWriteResult(_toolCall, result) {
154
- if (result.isError || result.status === "blocked" || result.status === "command_error") {
162
+ if (result.isError || result.status === "blocked" || result.status === "cancelled" || result.status === "command_error") {
155
163
  return false;
156
164
  }
157
- return result.metadata?.kind === "write" || result.metadata?.kind === "edit";
165
+ return result.metadata?.kind === "write" || result.metadata?.kind === "edit" || result.metadata?.kind === "patch";
166
+ }
167
+ function isMutationTool(name) {
168
+ return name === "edit" || name === "write" || name === "apply_patch";
158
169
  }
159
170
  function hasSubagentLifecycleActivity(toolCalls, toolResults) {
160
171
  return toolCalls.some((toolCall) => isSubagentLifecycleTool(toolCall.name))
@@ -16,6 +16,7 @@ export interface TurnHookState {
16
16
  codeChanged?: boolean;
17
17
  smallTaskHintSent?: boolean;
18
18
  recentEditFailures?: string[];
19
+ editNoMatchReminderPaths?: string[];
19
20
  editRetryReminderSent?: boolean;
20
21
  recentReadPaths?: string[];
21
22
  redundantReadReminded?: Set<string>;
@@ -3,6 +3,7 @@ export const defaultToolSnippets = {
3
3
  read: "Read the contents of a file",
4
4
  bash: "Execute a bash command",
5
5
  edit: "Apply targeted string replacements to a file",
6
+ apply_patch: "Apply a structured patch for multi-file or larger code changes",
6
7
  write: "Write a new file or overwrite an existing one",
7
8
  glob: "Find files by glob pattern without using bash",
8
9
  grep: "Search file contents using regex",
@@ -23,6 +24,7 @@ export const defaultToolNames = [
23
24
  "glob",
24
25
  "bash",
25
26
  "edit",
27
+ "apply_patch",
26
28
  "write",
27
29
  "grep",
28
30
  "lsp",
@@ -31,12 +31,11 @@ export declare function buildWorkflowPhaseReminder(input: {
31
31
  }): string;
32
32
  export declare function buildTaskSummaryReminder(): string;
33
33
  /**
34
- * Fired when the same edit/write tool call (identical tool name + args) has
35
- * just failed for the second time in a row. Models — especially thinking-heavy
36
- * ones — can otherwise spiral on `No changes made: identical content` or
37
- * `oldText not found` because their internal reasoning convinces them they
38
- * are typing the change correctly even though the JSON args arrive identical.
39
- * This nudge forces a strategy change.
34
+ * Fired when a file mutation failure suggests the model may be relying on stale
35
+ * local memory instead of the current file bytes. Models — especially
36
+ * thinking-heavy ones — can otherwise spiral on `No changes made: identical
37
+ * content` or `oldText not found` because their internal reasoning convinces
38
+ * them they are typing the change correctly.
40
39
  */
41
40
  export declare function buildEditRetryEscalationReminder(reason: string): string;
42
41
  /**
@@ -178,23 +178,22 @@ Treat the task output as a bounded subtask result:
178
178
  // validation scripts that found the bug but could never fix it. CC trusts the
179
179
  // model to decide when verification is meaningful; we follow that.
180
180
  /**
181
- * Fired when the same edit/write tool call (identical tool name + args) has
182
- * just failed for the second time in a row. Models — especially thinking-heavy
183
- * ones — can otherwise spiral on `No changes made: identical content` or
184
- * `oldText not found` because their internal reasoning convinces them they
185
- * are typing the change correctly even though the JSON args arrive identical.
186
- * This nudge forces a strategy change.
181
+ * Fired when a file mutation failure suggests the model may be relying on stale
182
+ * local memory instead of the current file bytes. Models — especially
183
+ * thinking-heavy ones — can otherwise spiral on `No changes made: identical
184
+ * content` or `oldText not found` because their internal reasoning convinces
185
+ * them they are typing the change correctly.
187
186
  */
188
187
  export function buildEditRetryEscalationReminder(reason) {
189
188
  return wrapInSystemReminder(`
190
- The same edit/write call has failed twice with identical arguments.
189
+ A file mutation just failed in a way that usually means your local view of the file is stale or the edit anchor is wrong.
191
190
 
192
191
  ${reason}
193
192
 
194
- Stop retrying the same call. Pick one of:
193
+ Stop retrying from memory. Pick one of:
195
194
  - Re-read the target file and compare the actual bytes to your intended oldText / newText. Trailing whitespace, unicode lookalikes, or off-by-one boundaries are common causes.
196
195
  - If you intended to add a single character (e.g. fixing a 5-digit hex color to 6 digits), confirm that your newText string actually contains the added character before sending again.
197
- - Use the write tool with overwrite=true and the full new content instead of edit — useful when the change spans many lines or the diff anchor is ambiguous. Existing files must be read or modified in this session before full-file replacement.
196
+ - Use the write tool with the full new content instead of edit — useful when the change spans many lines or the diff anchor is ambiguous.
198
197
  - If you cannot determine the cause, ask the user for clarification.
199
198
  `);
200
199
  }
@@ -6,9 +6,9 @@
6
6
  const defaultGuidelines = [
7
7
  "Ground decisions in the codebase: inspect relevant files, command output, or runtime state before making claims about behavior. Separate confirmed facts from inference when evidence is incomplete.",
8
8
  "Choose the smallest coherent change. Edit only the files required for the requested change; do not refactor or improve adjacent code unprompted.",
9
- "For modifications to existing code, read the file first. For brand-new files whose target path is known and does not exist, write directly without exploratory reading. Use edit for small targeted changes; use write with overwrite=true for intentional full-file replacement of an existing file. Never delete and recreate a file just to overwrite it.",
9
+ "For modifications to existing code, read the file first. For brand-new files whose target path is known and does not exist, write directly without exploratory reading. Use edit for small targeted changes, apply_patch for related multi-file or larger structured changes, and write for intentional full-file replacement of an existing file. Never delete and recreate a file just to overwrite it.",
10
10
  "Prefer structured tools (glob, grep, lsp, read) over bash for search and inspection. Do not repeat a near-identical search or re-read the same file unless new evidence changes the question.",
11
- "If a tool fails, diagnose the error before switching tactics. Do not retry the identical call with identical arguments. After two equivalent failures, switch approach — re-read the file, use a different tool, rewrite the whole file with write overwrite=true, or ask the user.",
11
+ "If a tool fails, diagnose the error before switching tactics. Do not retry the identical call with identical arguments. After two equivalent failures, switch approach — re-read the file, use a different tool, rewrite the whole file with write, or ask the user.",
12
12
  "Before reporting a task complete, verify it works when verification is meaningful and cheap — run the existing test, execute the script, check the output. If no test exists, the change is purely declarative (static HTML/markdown/config), or running the code is not practical, state that explicitly rather than inventing a verification step. Do not write throwaway validation scripts to prove correctness; if there is no real check to run, report the change and stop.",
13
13
  ];
14
14
  export function buildRuntimePrompt(options = {}) {
@@ -1,4 +1,5 @@
1
1
  import type { Provider, ReasoningEffort, ThinkingLevel, TokenUsage } from "./types.js";
2
+ import type { OAuthCredentials } from "./oauth/types.js";
2
3
  export interface CodexModelDescriptor {
3
4
  id: string;
4
5
  displayName?: string;
@@ -11,6 +12,11 @@ export interface CodexModelDescriptor {
11
12
  }
12
13
  export declare function isOpenAICodexBaseUrl(baseURL: string): boolean;
13
14
  export declare function getOpenAICodexFallbackModels(): string[];
15
+ export interface OpenAICodexAuthAdapter {
16
+ getCredentials: () => OAuthCredentials | undefined | Promise<OAuthCredentials | undefined>;
17
+ refreshCredentials: (current?: OAuthCredentials) => Promise<OAuthCredentials>;
18
+ isExpired?: (credentials: OAuthCredentials, graceMs: number) => boolean;
19
+ }
14
20
  export declare function extractChatGptAccountId(accessToken: string): string | undefined;
15
21
  export declare function createOpenAICodexProvider(options: {
16
22
  providerId?: string;
@@ -18,6 +24,7 @@ export declare function createOpenAICodexProvider(options: {
18
24
  baseURL: string;
19
25
  thinkingLevel?: ThinkingLevel;
20
26
  promptCacheKey?: string;
27
+ auth?: OpenAICodexAuthAdapter;
21
28
  }): Provider;
22
29
  export declare function normalizeOpenAICodexUsage(usage: any): TokenUsage;
23
30
  export declare function buildOpenAICodexPromptCacheKey(input: {