@bubblebrain-ai/bubble 0.0.12 → 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 (180) hide show
  1. package/dist/agent/execution-governor.js +1 -1
  2. package/dist/agent/input-controller.d.ts +11 -0
  3. package/dist/agent/input-controller.js +30 -0
  4. package/dist/agent/tool-intent.js +1 -0
  5. package/dist/agent.d.ts +8 -4
  6. package/dist/agent.js +623 -312
  7. package/dist/approval/controller.d.ts +1 -0
  8. package/dist/approval/controller.js +20 -3
  9. package/dist/approval/tool-helper.js +2 -0
  10. package/dist/approval/types.d.ts +14 -1
  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 +86 -9
  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 +39 -0
  33. package/dist/slash-commands/types.d.ts +12 -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/clipboard.d.ts +1 -0
  55. package/dist/tui/clipboard.js +53 -0
  56. package/dist/tui/detect-theme.d.ts +2 -0
  57. package/dist/tui/detect-theme.js +87 -0
  58. package/dist/tui/display-history.d.ts +63 -0
  59. package/dist/tui/display-history.js +306 -0
  60. package/dist/tui/edit-diff.d.ts +11 -0
  61. package/dist/tui/edit-diff.js +57 -0
  62. package/dist/tui/escape-confirmation.d.ts +15 -0
  63. package/dist/tui/escape-confirmation.js +30 -0
  64. package/dist/tui/file-mentions.d.ts +29 -0
  65. package/dist/tui/file-mentions.js +174 -0
  66. package/dist/tui/global-key-router.d.ts +3 -0
  67. package/dist/tui/global-key-router.js +87 -0
  68. package/dist/tui/image-paste.d.ts +95 -0
  69. package/dist/tui/image-paste.js +505 -0
  70. package/dist/tui/input-history.d.ts +16 -0
  71. package/dist/tui/input-history.js +79 -0
  72. package/dist/tui/markdown-inline.d.ts +22 -0
  73. package/dist/tui/markdown-inline.js +68 -0
  74. package/dist/tui/markdown-theme-rules.d.ts +23 -0
  75. package/dist/tui/markdown-theme-rules.js +164 -0
  76. package/dist/tui/markdown-theme.d.ts +5 -0
  77. package/dist/tui/markdown-theme.js +27 -0
  78. package/dist/tui/model-picker-data.d.ts +10 -0
  79. package/dist/tui/model-picker-data.js +32 -0
  80. package/dist/tui/opencode-spinner.d.ts +22 -0
  81. package/dist/tui/opencode-spinner.js +216 -0
  82. package/dist/tui/prompt-keybindings.d.ts +42 -0
  83. package/dist/tui/prompt-keybindings.js +35 -0
  84. package/dist/tui/recent-activity.d.ts +8 -0
  85. package/dist/tui/recent-activity.js +71 -0
  86. package/dist/tui/render-signature.d.ts +1 -0
  87. package/dist/tui/render-signature.js +7 -0
  88. package/dist/tui/run.d.ts +45 -0
  89. package/dist/tui/run.js +9359 -0
  90. package/dist/tui/session-display.d.ts +6 -0
  91. package/dist/tui/session-display.js +12 -0
  92. package/dist/tui/sidebar-mcp.d.ts +31 -0
  93. package/dist/tui/sidebar-mcp.js +62 -0
  94. package/dist/tui/sidebar-state.d.ts +12 -0
  95. package/dist/tui/sidebar-state.js +69 -0
  96. package/dist/tui/streaming-tool-args.d.ts +15 -0
  97. package/dist/tui/streaming-tool-args.js +30 -0
  98. package/dist/tui/tool-renderers/fallback.d.ts +2 -0
  99. package/dist/tui/tool-renderers/fallback.js +75 -0
  100. package/dist/tui/tool-renderers/registry.d.ts +3 -0
  101. package/dist/tui/tool-renderers/registry.js +11 -0
  102. package/dist/tui/tool-renderers/subagent.d.ts +2 -0
  103. package/dist/tui/tool-renderers/subagent.js +135 -0
  104. package/dist/tui/tool-renderers/types.d.ts +36 -0
  105. package/dist/tui/tool-renderers/types.js +1 -0
  106. package/dist/tui/tool-renderers/write-preview.d.ts +12 -0
  107. package/dist/tui/tool-renderers/write-preview.js +32 -0
  108. package/dist/tui/tool-renderers/write.d.ts +6 -0
  109. package/dist/tui/tool-renderers/write.js +88 -0
  110. package/dist/tui/trace-groups.d.ts +27 -0
  111. package/dist/tui/trace-groups.js +419 -0
  112. package/dist/tui/wordmark.d.ts +15 -0
  113. package/dist/tui/wordmark.js +179 -0
  114. package/dist/tui-ink/app.js +45 -9
  115. package/dist/tui-ink/approval/approval-dialog.js +7 -1
  116. package/dist/tui-ink/display-history.d.ts +1 -0
  117. package/dist/tui-ink/display-history.js +5 -4
  118. package/dist/tui-ink/message-list.js +23 -9
  119. package/dist/tui-ink/theme.d.ts +3 -9
  120. package/dist/tui-ink/theme.js +39 -45
  121. package/dist/tui-ink/trace-groups.js +1 -1
  122. package/dist/tui-ink/welcome.js +22 -78
  123. package/dist/tui-opentui/app.d.ts +54 -0
  124. package/dist/tui-opentui/app.js +1365 -0
  125. package/dist/tui-opentui/approval/approval-dialog.d.ts +15 -0
  126. package/dist/tui-opentui/approval/approval-dialog.js +145 -0
  127. package/dist/tui-opentui/approval/diff-view.d.ts +9 -0
  128. package/dist/tui-opentui/approval/diff-view.js +43 -0
  129. package/dist/tui-opentui/approval/select.d.ts +37 -0
  130. package/dist/tui-opentui/approval/select.js +91 -0
  131. package/dist/tui-opentui/detect-theme.d.ts +2 -0
  132. package/dist/tui-opentui/detect-theme.js +87 -0
  133. package/dist/tui-opentui/display-history.d.ts +56 -0
  134. package/dist/tui-opentui/display-history.js +130 -0
  135. package/dist/tui-opentui/edit-diff.d.ts +11 -0
  136. package/dist/tui-opentui/edit-diff.js +57 -0
  137. package/dist/tui-opentui/feedback-dialog.d.ts +21 -0
  138. package/dist/tui-opentui/feedback-dialog.js +164 -0
  139. package/dist/tui-opentui/feishu-setup-picker.d.ts +7 -0
  140. package/dist/tui-opentui/feishu-setup-picker.js +272 -0
  141. package/dist/tui-opentui/file-mentions.d.ts +29 -0
  142. package/dist/tui-opentui/file-mentions.js +174 -0
  143. package/dist/tui-opentui/footer.d.ts +26 -0
  144. package/dist/tui-opentui/footer.js +40 -0
  145. package/dist/tui-opentui/image-paste.d.ts +54 -0
  146. package/dist/tui-opentui/image-paste.js +288 -0
  147. package/dist/tui-opentui/input-box.d.ts +34 -0
  148. package/dist/tui-opentui/input-box.js +471 -0
  149. package/dist/tui-opentui/input-history.d.ts +16 -0
  150. package/dist/tui-opentui/input-history.js +79 -0
  151. package/dist/tui-opentui/markdown.d.ts +66 -0
  152. package/dist/tui-opentui/markdown.js +127 -0
  153. package/dist/tui-opentui/message-list.d.ts +31 -0
  154. package/dist/tui-opentui/message-list.js +128 -0
  155. package/dist/tui-opentui/model-picker.d.ts +63 -0
  156. package/dist/tui-opentui/model-picker.js +450 -0
  157. package/dist/tui-opentui/plan-confirm.d.ts +9 -0
  158. package/dist/tui-opentui/plan-confirm.js +124 -0
  159. package/dist/tui-opentui/question-dialog.d.ts +10 -0
  160. package/dist/tui-opentui/question-dialog.js +110 -0
  161. package/dist/tui-opentui/recent-activity.d.ts +8 -0
  162. package/dist/tui-opentui/recent-activity.js +71 -0
  163. package/dist/tui-opentui/run-session-picker.d.ts +10 -0
  164. package/dist/tui-opentui/run-session-picker.js +28 -0
  165. package/dist/tui-opentui/run.d.ts +38 -0
  166. package/dist/tui-opentui/run.js +48 -0
  167. package/dist/tui-opentui/session-picker.d.ts +12 -0
  168. package/dist/tui-opentui/session-picker.js +120 -0
  169. package/dist/tui-opentui/theme.d.ts +89 -0
  170. package/dist/tui-opentui/theme.js +157 -0
  171. package/dist/tui-opentui/todos.d.ts +9 -0
  172. package/dist/tui-opentui/todos.js +45 -0
  173. package/dist/tui-opentui/trace-groups.d.ts +27 -0
  174. package/dist/tui-opentui/trace-groups.js +419 -0
  175. package/dist/tui-opentui/use-terminal-size.d.ts +4 -0
  176. package/dist/tui-opentui/use-terminal-size.js +5 -0
  177. package/dist/tui-opentui/welcome.d.ts +25 -0
  178. package/dist/tui-opentui/welcome.js +77 -0
  179. package/dist/types.d.ts +36 -2
  180. package/package.json +5 -1
package/dist/main.js CHANGED
@@ -25,6 +25,9 @@ import { McpManager } from "./mcp/manager.js";
25
25
  import { QuestionController } from "./question/index.js";
26
26
  import { buildMemoryPrompt, formatMemoryStartupResult, recordMemoryCitations, runMemoryPhase2, runMemoryStartupPipeline, startMemoryStartupTask, } from "./memory/index.js";
27
27
  import { basename } from "node:path";
28
+ import { normalizeSingleLine, truncateVisual } from "./text-display.js";
29
+ import { BUBBLE_WORDMARK } from "./tui/wordmark.js";
30
+ import { configureDebugTrace, summarizeAgentEventForTrace, summarizeTraceMessage, traceEvent, } from "./debug-trace.js";
28
31
  async function main() {
29
32
  const args = parseArgs(process.argv.slice(2));
30
33
  if (process.argv.includes("-h") || process.argv.includes("--help")) {
@@ -70,6 +73,7 @@ async function main() {
70
73
  baseURL: defaultProvider.baseURL,
71
74
  thinkingLevel: args.thinkingLevel,
72
75
  promptCacheKey: sessionPromptCacheKey,
76
+ openAICodexAuth: registry.createOpenAICodexAuthAdapter(defaultProvider.id),
73
77
  })
74
78
  : createUnavailableProvider(unavailableProviderMessage);
75
79
  const createProvider = (providerId, apiKey, baseURL) => createProviderInstance({
@@ -78,6 +82,7 @@ async function main() {
78
82
  baseURL,
79
83
  thinkingLevel: args.thinkingLevel,
80
84
  promptCacheKey: sessionPromptCacheKey,
85
+ openAICodexAuth: registry.createOpenAICodexAuthAdapter(providerId),
81
86
  });
82
87
  const createProviderForRoute = async (route) => {
83
88
  const providerId = route.providerId;
@@ -183,7 +188,7 @@ async function main() {
183
188
  // - --resume (no name): show interactive picker
184
189
  let sessionManager;
185
190
  let resumedExistingSession = false;
186
- // Resolved before any Ink render so picker and main TUI share the same value
191
+ // Resolved before any TUI render so picker and main TUI share the same value
187
192
  // and we only run OSC 11 once.
188
193
  let preResolvedTheme;
189
194
  if (args.resume && !args.sessionName) {
@@ -195,13 +200,13 @@ async function main() {
195
200
  else {
196
201
  const themeConfig = userConfig.getTheme();
197
202
  if (themeConfig.mode === "auto") {
198
- const { detectTerminalTheme } = await import("./tui-ink/detect-theme.js");
203
+ const { detectTerminalTheme } = await import("./tui/detect-theme.js");
199
204
  preResolvedTheme = await detectTerminalTheme();
200
205
  }
201
206
  else {
202
207
  preResolvedTheme = themeConfig.mode;
203
208
  }
204
- const { runSessionPicker } = await import("./tui-ink/run-session-picker.js");
209
+ const { runSessionPicker } = await import("./tui-opentui/run-session-picker.js");
205
210
  const picked = await runSessionPicker({
206
211
  currentCwd: args.cwd,
207
212
  currentSessions,
@@ -274,6 +279,25 @@ async function main() {
274
279
  tools: tools.map((tool) => tool.name),
275
280
  memoryPrompt,
276
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
+ }
277
301
  const budgetLedger = new BudgetLedger();
278
302
  let sessionTitleUpdater;
279
303
  const agent = new Agent({
@@ -299,6 +323,9 @@ async function main() {
299
323
  if (message.role === "meta")
300
324
  return;
301
325
  sessionManager.appendMessage(message);
326
+ traceEvent("session_message_persisted", {
327
+ message: summarizeTraceMessage(message),
328
+ });
302
329
  sessionTitleUpdater?.handlePersistedMessage(message);
303
330
  if (message.role === "assistant") {
304
331
  recordMemoryCitations(args.cwd, message.content);
@@ -402,6 +429,7 @@ async function main() {
402
429
  process.exit(1);
403
430
  }
404
431
  for await (const event of agent.run(prompt, args.cwd)) {
432
+ traceEvent("print_agent_event", summarizeAgentEventForTrace(event));
405
433
  if (event.type === "text_delta") {
406
434
  process.stdout.write(event.content);
407
435
  }
@@ -422,9 +450,9 @@ async function main() {
422
450
  detectedTheme = preResolvedTheme;
423
451
  }
424
452
  else if (themeConfig.mode === "auto") {
425
- // Probe before either TUI runtime owns stdin. OSC 11 needs raw mode, and
426
- // runtime renderers can consume the reply before startup code sees it.
427
- const { detectTerminalTheme } = await import("./tui-ink/detect-theme.js");
453
+ // Probe before OpenTUI owns stdin. OSC 11 needs raw mode, and the
454
+ // runtime renderer can consume the reply before startup code sees it.
455
+ const { detectTerminalTheme } = await import("./tui/detect-theme.js");
428
456
  detectedTheme = await detectTerminalTheme();
429
457
  }
430
458
  else {
@@ -447,7 +475,7 @@ async function main() {
447
475
  runMemorySummary,
448
476
  runMemoryRefresh,
449
477
  };
450
- const { runTui } = await import("./tui-ink/run.js");
478
+ const { runTui } = await import("./tui/run.js");
451
479
  await runTui(agent, args, {
452
480
  ...commonOptions,
453
481
  themeMode: themeConfig.mode,
@@ -456,13 +484,62 @@ async function main() {
456
484
  onThemeModeChange: (mode) => userConfig.setThemeMode(mode),
457
485
  });
458
486
  if (sessionManager) {
459
- const sessionName = basename(sessionManager.getSessionFile());
460
- console.log(chalk.dim(`To resume: bubble --resume (or --resume --session ${sessionName})`));
487
+ printOpenTuiExitSummary(sessionManager, {
488
+ resumed: resumedExistingSession,
489
+ theme: detectedTheme,
490
+ });
461
491
  }
462
492
  }
463
493
  finally {
494
+ traceEvent("run_shutdown_start");
464
495
  await shutdownRuntime();
496
+ traceEvent("run_shutdown_end");
497
+ }
498
+ }
499
+ function printOpenTuiExitSummary(sessionManager, options) {
500
+ if (!process.stdout.isTTY)
501
+ return;
502
+ const sessionName = basename(sessionManager.getSessionFile());
503
+ const sessionId = sessionName.replace(/\.jsonl$/, "");
504
+ const title = truncateVisual(normalizeSingleLine(sessionManager.getMetadata().title ?? ""), 64);
505
+ const sessionLabel = title || `${options.resumed ? "Session" : "New session"} - ${sessionId}`;
506
+ const continueCommand = `bubble --resume --session ${sessionName}`;
507
+ const colors = options.theme === "light"
508
+ ? {
509
+ markMuted: chalk.hex("#8C8C8C"),
510
+ markStrong: chalk.hex("#1C1C1C"),
511
+ markBrand: chalk.hex("#8B4A00"),
512
+ label: chalk.hex("#6F7377"),
513
+ value: chalk.hex("#171717").bold,
514
+ }
515
+ : {
516
+ markMuted: chalk.hex("#9CA3AF"),
517
+ markStrong: chalk.hex("#F4F4F5"),
518
+ markBrand: chalk.hex("#F5A742"),
519
+ label: chalk.hex("#808080"),
520
+ value: chalk.hex("#EEEEEE").bold,
521
+ };
522
+ const label = (value) => colors.label(value.padEnd(10));
523
+ const logoColor = (tone) => {
524
+ switch (tone) {
525
+ case "brand": return colors.markBrand;
526
+ case "ink": return colors.markStrong;
527
+ case "stone": return colors.markMuted;
528
+ case "soft": return colors.label;
529
+ case "caption": return colors.label;
530
+ }
531
+ };
532
+ for (const line of BUBBLE_WORDMARK) {
533
+ if (line.segments) {
534
+ console.log(line.segments.map((segment) => logoColor(segment.tone)(segment.text)).join(""));
535
+ }
536
+ else {
537
+ console.log(logoColor(line.tone ?? "caption")(line.text ?? ""));
538
+ }
465
539
  }
540
+ console.log();
541
+ console.log(`${label("Session")}${colors.value(sessionLabel)}`);
542
+ console.log(`${label("Continue")}${colors.value(continueCommand)}`);
466
543
  }
467
544
  async function readPipedStdin() {
468
545
  if (process.stdin.isTTY)
@@ -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: {