@bubblebrain-ai/bubble 0.0.58 → 0.0.60

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 (177) hide show
  1. package/README.md +56 -6
  2. package/dist/agent/child-runner.js +5 -1
  3. package/dist/agent/internal-reminder-sanitizer.d.ts +1 -1
  4. package/dist/agent/internal-reminder-sanitizer.js +7 -1
  5. package/dist/agent/profiles.js +4 -4
  6. package/dist/agent/routing-catalog.d.ts +19 -1
  7. package/dist/agent/routing-catalog.js +32 -13
  8. package/dist/agent/subagent/runtime.d.ts +28 -0
  9. package/dist/agent/subagent/runtime.js +34 -30
  10. package/dist/agent/subagent-control.d.ts +7 -0
  11. package/dist/agent/subagent-control.js +2 -0
  12. package/dist/agent/subagent-lifecycle-reminder.js +2 -1
  13. package/dist/agent/subagent-wait-guidance.d.ts +2 -0
  14. package/dist/agent/subagent-wait-guidance.js +8 -0
  15. package/dist/agent/subtask-policy.js +4 -4
  16. package/dist/agent.d.ts +20 -11
  17. package/dist/agent.js +200 -117
  18. package/dist/anthropic-thinking.d.ts +4 -0
  19. package/dist/anthropic-thinking.js +11 -0
  20. package/dist/approval/controller.d.ts +20 -6
  21. package/dist/approval/controller.js +67 -8
  22. package/dist/approval/protected-paths.d.ts +1 -0
  23. package/dist/approval/protected-paths.js +35 -0
  24. package/dist/approval/session-cache.d.ts +12 -26
  25. package/dist/approval/session-cache.js +20 -50
  26. package/dist/approval/types.d.ts +27 -1
  27. package/dist/cli.d.ts +5 -0
  28. package/dist/cli.js +25 -2
  29. package/dist/config.d.ts +3 -0
  30. package/dist/config.js +7 -0
  31. package/dist/context/checkpoint.d.ts +18 -0
  32. package/dist/context/checkpoint.js +117 -0
  33. package/dist/context/compact-llm.js +7 -0
  34. package/dist/context/compact.d.ts +8 -0
  35. package/dist/context/compact.js +40 -7
  36. package/dist/context/llm-compactor.d.ts +36 -2
  37. package/dist/context/llm-compactor.js +227 -44
  38. package/dist/context/session-write-lock.d.ts +20 -0
  39. package/dist/context/session-write-lock.js +162 -0
  40. package/dist/context/tool-images.d.ts +3 -0
  41. package/dist/context/tool-images.js +35 -0
  42. package/dist/context/usage.js +22 -5
  43. package/dist/feishu/agent-host/run-driver.js +56 -9
  44. package/dist/lsp/service.d.ts +13 -1
  45. package/dist/lsp/service.js +163 -43
  46. package/dist/main.js +82 -16
  47. package/dist/mcp/config.d.ts +4 -0
  48. package/dist/mcp/config.js +17 -0
  49. package/dist/mcp/manager.d.ts +13 -1
  50. package/dist/mcp/manager.js +32 -1
  51. package/dist/mcp/transports.js +11 -0
  52. package/dist/memory/db.d.ts +1 -0
  53. package/dist/memory/db.js +20 -4
  54. package/dist/memory/phase1.js +122 -16
  55. package/dist/memory/prompts.js +3 -0
  56. package/dist/model-catalog.js +19 -3
  57. package/dist/model-config.d.ts +1 -0
  58. package/dist/model-config.js +5 -5
  59. package/dist/model-pricing.js +26 -9
  60. package/dist/network/provider-transport-log.d.ts +12 -0
  61. package/dist/network/provider-transport-log.js +32 -0
  62. package/dist/network/provider-transport.js +6 -1
  63. package/dist/network/retry.d.ts +1 -1
  64. package/dist/network/retry.js +1 -1
  65. package/dist/oauth/grok.d.ts +1 -0
  66. package/dist/oauth/grok.js +20 -16
  67. package/dist/oauth/openai-codex.d.ts +1 -0
  68. package/dist/oauth/openai-codex.js +29 -25
  69. package/dist/oauth/refresh-control.d.ts +12 -0
  70. package/dist/oauth/refresh-control.js +42 -0
  71. package/dist/oauth/storage.d.ts +55 -1
  72. package/dist/oauth/storage.js +281 -16
  73. package/dist/permissions/rule.d.ts +6 -3
  74. package/dist/permissions/rule.js +71 -12
  75. package/dist/permissions/settings.d.ts +28 -1
  76. package/dist/permissions/settings.js +93 -3
  77. package/dist/permissions/shell-command.d.ts +36 -0
  78. package/dist/permissions/shell-command.js +226 -0
  79. package/dist/permissions/trust-prompt.d.ts +10 -0
  80. package/dist/permissions/trust-prompt.js +34 -0
  81. package/dist/permissions/trust.d.ts +26 -0
  82. package/dist/permissions/trust.js +113 -0
  83. package/dist/prompt/compose.d.ts +3 -0
  84. package/dist/prompt/compose.js +24 -7
  85. package/dist/prompt/environment.js +2 -2
  86. package/dist/prompt/reminders.js +1 -1
  87. package/dist/prompt/runtime.js +1 -1
  88. package/dist/provider-anthropic.d.ts +6 -2
  89. package/dist/provider-anthropic.js +98 -18
  90. package/dist/provider-error-record.d.ts +9 -0
  91. package/dist/provider-error-record.js +42 -14
  92. package/dist/provider-grok.js +5 -1
  93. package/dist/provider-openai-codex.d.ts +8 -0
  94. package/dist/provider-openai-codex.js +107 -24
  95. package/dist/provider-registry.d.ts +45 -2
  96. package/dist/provider-registry.js +300 -88
  97. package/dist/provider-transform.js +8 -0
  98. package/dist/provider-zen-errors.d.ts +9 -0
  99. package/dist/provider-zen-errors.js +36 -0
  100. package/dist/provider.js +36 -5
  101. package/dist/sdk/index.d.ts +69 -8
  102. package/dist/sdk/index.js +252 -39
  103. package/dist/sdk/replay-event-log.d.ts +7 -0
  104. package/dist/sdk/replay-event-log.js +66 -16
  105. package/dist/session-context-fence.d.ts +16 -0
  106. package/dist/session-context-fence.js +26 -0
  107. package/dist/session-log.d.ts +11 -2
  108. package/dist/session-log.js +187 -39
  109. package/dist/session-types.d.ts +5 -1
  110. package/dist/session.d.ts +39 -7
  111. package/dist/session.js +341 -93
  112. package/dist/skills/format.d.ts +31 -1
  113. package/dist/skills/format.js +45 -24
  114. package/dist/slash-commands/commands.d.ts +3 -0
  115. package/dist/slash-commands/commands.js +29 -14
  116. package/dist/system-prompt.d.ts +3 -1
  117. package/dist/tasks/manager.d.ts +7 -0
  118. package/dist/tasks/manager.js +97 -18
  119. package/dist/tools/agent-lifecycle.js +11 -7
  120. package/dist/tools/child-tools.d.ts +1 -1
  121. package/dist/tools/child-tools.js +4 -4
  122. package/dist/tools/edit-apply.d.ts +0 -3
  123. package/dist/tools/edit-apply.js +45 -249
  124. package/dist/tools/edit.js +3 -3
  125. package/dist/tools/exit-plan-mode.d.ts +5 -0
  126. package/dist/tools/exit-plan-mode.js +3 -2
  127. package/dist/tools/file-state.d.ts +4 -0
  128. package/dist/tools/file-state.js +12 -3
  129. package/dist/tools/grep.js +20 -1
  130. package/dist/tools/index.d.ts +1 -1
  131. package/dist/tools/index.js +3 -3
  132. package/dist/tools/ls.d.ts +3 -0
  133. package/dist/tools/ls.js +85 -0
  134. package/dist/tools/path-utils.d.ts +6 -0
  135. package/dist/tools/path-utils.js +38 -1
  136. package/dist/tools/sensitive-paths.d.ts +8 -0
  137. package/dist/tools/sensitive-paths.js +85 -3
  138. package/dist/tools/skill.d.ts +2 -0
  139. package/dist/tools/skill.js +34 -2
  140. package/dist/tools/task-tools.js +11 -7
  141. package/dist/tui/app.js +22 -12
  142. package/dist/tui/components/approval-dialog.d.ts +5 -5
  143. package/dist/tui/components/approval-dialog.js +16 -51
  144. package/dist/tui/components/skills-panel.d.ts +4 -0
  145. package/dist/tui/components/skills-panel.js +15 -1
  146. package/dist/tui/components/streaming-message.js +7 -2
  147. package/dist/tui/components/tasks-pane.d.ts +38 -0
  148. package/dist/tui/components/tasks-pane.js +182 -32
  149. package/dist/tui/components/transcript.d.ts +11 -4
  150. package/dist/tui/components/transcript.js +158 -71
  151. package/dist/tui/composer-autocomplete.d.ts +17 -2
  152. package/dist/tui/composer-autocomplete.js +79 -2
  153. package/dist/tui/controller/controller.d.ts +9 -4
  154. package/dist/tui/controller/controller.js +87 -95
  155. package/dist/tui/controller/goal-runtime-controller.js +1 -1
  156. package/dist/tui/controller/session-transition.js +1 -1
  157. package/dist/tui/model/subagent-view.d.ts +11 -19
  158. package/dist/tui/model/subagent-view.js +56 -85
  159. package/dist/tui/model/task-lifecycle.d.ts +57 -0
  160. package/dist/tui/model/task-lifecycle.js +124 -0
  161. package/dist/tui/model/trace-groups.js +10 -9
  162. package/dist/tui/trace-groups.js +10 -9
  163. package/dist/types.d.ts +20 -0
  164. package/dist/variant/variant-resolver.js +2 -0
  165. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-alt-screen.d.ts +1 -0
  166. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-alt-screen.d.ts.map +1 -1
  167. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-alt-screen.js +6 -0
  168. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-alt-screen.js.map +1 -1
  169. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui.d.ts +2 -0
  170. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui.d.ts.map +1 -1
  171. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui.js +8 -4
  172. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui.js.map +1 -1
  173. package/package.json +11 -3
  174. package/dist/prompt/skills.d.ts +0 -2
  175. package/dist/prompt/skills.js +0 -4
  176. package/dist/tools/glob.d.ts +0 -5
  177. package/dist/tools/glob.js +0 -199
@@ -0,0 +1,26 @@
1
+ export interface PermissionTrustOptions {
2
+ bubbleHome?: string;
3
+ }
4
+ /** Parsed contents of the two repository settings files (null when absent). */
5
+ export interface RepoSettingsRaw {
6
+ project: unknown;
7
+ local: unknown;
8
+ }
9
+ export interface RepoCapabilities {
10
+ allow: string[];
11
+ mcpServers: string[];
12
+ /** LSP server ids whose definition carries more than an on/off switch. */
13
+ lspServers: string[];
14
+ }
15
+ export declare function getPermissionTrustPath(options?: PermissionTrustOptions): string;
16
+ export declare function repoCapabilities(raw: unknown): RepoCapabilities;
17
+ /** Everything the given repository settings would enable, both scopes merged. */
18
+ export declare function mergedRepoCapabilities(raw: RepoSettingsRaw): RepoCapabilities;
19
+ export declare function describeRepoCapabilities(caps: RepoCapabilities): string[];
20
+ export declare function hasRepoCapabilities(raw: RepoSettingsRaw): boolean;
21
+ export declare function repoConfigFingerprint(cwd: string, raw: RepoSettingsRaw): string;
22
+ /** True when the repository grants nothing, or the user trusted exactly this content. */
23
+ export declare function isRepoConfigTrusted(cwd: string, raw: RepoSettingsRaw, options?: PermissionTrustOptions): boolean;
24
+ export declare function trustRepoConfig(cwd: string, raw: RepoSettingsRaw, options?: PermissionTrustOptions): void;
25
+ /** Reads both repository settings files; unparsable files count as absent. */
26
+ export declare function readRepoSettings(cwd: string): RepoSettingsRaw;
@@ -0,0 +1,113 @@
1
+ import { createHash } from "node:crypto";
2
+ import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
3
+ import { dirname, join, resolve } from "node:path";
4
+ import { getBubbleHome } from "../bubble-home.js";
5
+ export function getPermissionTrustPath(options = {}) {
6
+ return join(options.bubbleHome ?? getBubbleHome(), "permissions-trust.json");
7
+ }
8
+ export function repoCapabilities(raw) {
9
+ const settings = isRecord(raw) ? raw : {};
10
+ const permissions = isRecord(settings.permissions) ? settings.permissions : {};
11
+ const allow = Array.isArray(permissions.allow)
12
+ ? permissions.allow.filter((rule) => typeof rule === "string")
13
+ : [];
14
+ const mcpServers = isRecord(settings.mcpServers) ? Object.keys(settings.mcpServers) : [];
15
+ const lspServers = isRecord(settings.lsp)
16
+ ? Object.entries(settings.lsp)
17
+ .filter(([, server]) => isRecord(server) && Object.keys(server).some((key) => key !== "disabled"))
18
+ .map(([id]) => id)
19
+ : [];
20
+ return { allow, mcpServers, lspServers };
21
+ }
22
+ /** Everything the given repository settings would enable, both scopes merged. */
23
+ export function mergedRepoCapabilities(raw) {
24
+ const project = repoCapabilities(raw.project);
25
+ const local = repoCapabilities(raw.local);
26
+ return {
27
+ allow: [...project.allow, ...local.allow],
28
+ mcpServers: [...new Set([...project.mcpServers, ...local.mcpServers])],
29
+ lspServers: [...new Set([...project.lspServers, ...local.lspServers])],
30
+ };
31
+ }
32
+ export function describeRepoCapabilities(caps) {
33
+ return [
34
+ ...caps.allow.map((rule) => `allow rule ${rule}`),
35
+ ...caps.mcpServers.map((name) => `MCP server ${name} (Bubble starts it)`),
36
+ ...caps.lspServers.map((id) => `LSP server ${id} (custom command, env or options)`),
37
+ ];
38
+ }
39
+ export function hasRepoCapabilities(raw) {
40
+ return [raw.project, raw.local].some((scope) => {
41
+ const caps = repoCapabilities(scope);
42
+ return caps.allow.length + caps.mcpServers.length + caps.lspServers.length > 0;
43
+ });
44
+ }
45
+ export function repoConfigFingerprint(cwd, raw) {
46
+ const pick = (scope) => {
47
+ const settings = isRecord(scope) ? scope : {};
48
+ const permissions = isRecord(settings.permissions) ? settings.permissions : {};
49
+ return { allow: permissions.allow ?? null, mcpServers: settings.mcpServers ?? null, lsp: settings.lsp ?? null };
50
+ };
51
+ return createHash("sha256")
52
+ .update(JSON.stringify({ cwd: safeRealpath(cwd), project: pick(raw.project), local: pick(raw.local) }))
53
+ .digest("hex");
54
+ }
55
+ /** True when the repository grants nothing, or the user trusted exactly this content. */
56
+ export function isRepoConfigTrusted(cwd, raw, options = {}) {
57
+ if (!hasRepoCapabilities(raw))
58
+ return true;
59
+ return readStore(options).projects[safeRealpath(cwd)]?.fingerprint === repoConfigFingerprint(cwd, raw);
60
+ }
61
+ export function trustRepoConfig(cwd, raw, options = {}) {
62
+ const store = readStore(options);
63
+ store.projects[safeRealpath(cwd)] = {
64
+ fingerprint: repoConfigFingerprint(cwd, raw),
65
+ trustedAt: new Date().toISOString(),
66
+ };
67
+ const path = getPermissionTrustPath(options);
68
+ mkdirSync(dirname(path), { recursive: true });
69
+ writeFileSync(path, JSON.stringify(store, null, 2) + "\n", "utf-8");
70
+ }
71
+ /** Reads both repository settings files; unparsable files count as absent. */
72
+ export function readRepoSettings(cwd) {
73
+ const read = (path) => {
74
+ if (!existsSync(path))
75
+ return null;
76
+ try {
77
+ return JSON.parse(readFileSync(path, "utf-8"));
78
+ }
79
+ catch {
80
+ return null;
81
+ }
82
+ };
83
+ return {
84
+ project: read(join(cwd, ".bubble", "settings.json")),
85
+ local: read(join(cwd, ".bubble", "settings.local.json")),
86
+ };
87
+ }
88
+ function readStore(options) {
89
+ const path = getPermissionTrustPath(options);
90
+ if (!existsSync(path))
91
+ return { version: 1, projects: {} };
92
+ try {
93
+ const parsed = JSON.parse(readFileSync(path, "utf-8"));
94
+ if (parsed && typeof parsed.projects === "object" && parsed.projects !== null) {
95
+ return { version: 1, projects: parsed.projects };
96
+ }
97
+ }
98
+ catch {
99
+ // A corrupt store trusts nothing.
100
+ }
101
+ return { version: 1, projects: {} };
102
+ }
103
+ function isRecord(value) {
104
+ return typeof value === "object" && value !== null && !Array.isArray(value);
105
+ }
106
+ function safeRealpath(path) {
107
+ try {
108
+ return realpathSync(path);
109
+ }
110
+ catch {
111
+ return resolve(path);
112
+ }
113
+ }
@@ -6,7 +6,10 @@ export interface ComposeSystemPromptOptions extends EnvironmentPromptOptions {
6
6
  guidelines?: string[];
7
7
  thinkingLevel?: ThinkingLevel;
8
8
  mode?: PermissionMode;
9
+ /** Prompt-visible skills; when given (and `skill` is a tool), listed as a catalog. */
9
10
  skills?: SkillSummary[];
11
+ /** Catalog budget in characters (default SKILL_CATALOG_CHARS); 0 falls back to search only. */
12
+ skillCatalogChars?: number;
10
13
  memoryPrompt?: string;
11
14
  agentProfilePrompt?: string;
12
15
  /** Rendered subagent model-routing menu (design §4). Placed right after the
@@ -9,6 +9,7 @@ import { buildKimiProviderPrompt } from "./provider-prompts/kimi.js";
9
9
  import { buildDelegationPolicyPrompt } from "./delegation.js";
10
10
  import { buildEnvironmentPrompt, defaultToolNames } from "./environment.js";
11
11
  import { buildRuntimePrompt } from "./runtime.js";
12
+ import { buildSkillCatalog } from "../skills/format.js";
12
13
  export function composeSystemPrompt(options = {}) {
13
14
  const agentName = options.agentName ?? "Bubble";
14
15
  const providerPrompt = buildProviderPrompt(agentName, options.configuredProvider, options.configuredModelId, options.configuredModel);
@@ -21,10 +22,20 @@ export function composeSystemPrompt(options = {}) {
21
22
  tools: options.tools ?? defaultToolNames,
22
23
  toolSnippets: options.toolSnippets,
23
24
  });
25
+ const tools = options.tools ?? defaultToolNames;
26
+ // Level one of skill loading: names and one-line descriptions are always in
27
+ // context; bodies load on demand with `skill`. Last section, so the prefix
28
+ // above stays byte-identical while the skill set is unchanged.
29
+ const skillCatalog = options.skills && tools.includes("skill")
30
+ ? buildSkillCatalog(options.skills, {
31
+ budgetChars: options.skillCatalogChars,
32
+ searchable: tools.includes("skill_search"),
33
+ })
34
+ : undefined;
24
35
  const runtimePrompt = buildRuntimePrompt({
25
36
  thinkingLevel: options.thinkingLevel,
26
37
  mode: options.mode,
27
- guidelines: buildGuidelines(options.tools ?? defaultToolNames, options.guidelines ?? []),
38
+ guidelines: buildGuidelines(tools, options.guidelines ?? [], skillCatalog),
28
39
  });
29
40
  const delegationPrompt = buildDelegationPolicyPrompt(options.tools ?? defaultToolNames);
30
41
  // Same gate as the delegation policy: children (no spawn_agent) never see the menu.
@@ -37,6 +48,7 @@ export function composeSystemPrompt(options = {}) {
37
48
  modelRoutingPrompt,
38
49
  options.agentProfilePrompt,
39
50
  options.memoryPrompt,
51
+ skillCatalog?.text,
40
52
  ].filter(Boolean).join("\n\n");
41
53
  }
42
54
  function buildProviderPrompt(agentName, providerId, modelId, modelName) {
@@ -67,23 +79,28 @@ function buildProviderPrompt(agentName, providerId, modelId, modelName) {
67
79
  }
68
80
  return buildDefaultProviderPrompt(agentName);
69
81
  }
70
- function buildGuidelines(tools, extraGuidelines) {
82
+ function buildGuidelines(tools, extraGuidelines, skillCatalog) {
71
83
  const guidelines = [];
72
84
  const add = (item) => {
73
85
  if (!guidelines.includes(item)) {
74
86
  guidelines.push(item);
75
87
  }
76
88
  };
77
- if (tools.includes("glob")) {
78
- add("Use glob for file discovery and project structure inspection; do not use bash ls/find for this unless glob cannot answer");
89
+ if (tools.includes("ls")) {
90
+ add("Use ls to list directory contents, including files and subdirectories");
79
91
  }
80
- if (tools.includes("bash") && tools.includes("grep")) {
81
- add("Use grep for content search; do not run grep, rg, or ripgrep through bash");
92
+ if (tools.includes("grep")) {
93
+ add("Use grep to search file contents");
94
+ }
95
+ if (tools.includes("bash")) {
96
+ add("Use bash for file operations like ls, find, and rg when appropriate");
82
97
  }
83
98
  if (tools.includes("question")) {
84
99
  add("When the user is explicitly discussing, brainstorming, or shaping an approach instead of asking for immediate execution, use the question tool for targeted clarification or preference choices when it would materially improve the discussion; do not use it for generic permission-to-proceed questions");
85
100
  }
86
- if (tools.includes("skill_search") && tools.includes("skill")) {
101
+ // With a catalog the Skills section itself says how to load; search remains
102
+ // the only route when the host gave no catalog or turned it off.
103
+ if (!skillCatalog?.listed && tools.includes("skill_search") && tools.includes("skill")) {
87
104
  add("Skills may provide specialized workflows. When a task appears to match a specialized workflow, call skill_search to find relevant skills, then call skill with the exact name to load the selected skill before applying it");
88
105
  }
89
106
  for (const item of extraGuidelines) {
@@ -4,7 +4,7 @@ export const defaultToolSnippets = {
4
4
  bash: "Execute a bash command",
5
5
  edit: "Apply targeted string replacements to a file",
6
6
  write: "Write a new file or overwrite an existing one",
7
- glob: "Find files by glob pattern without using bash",
7
+ ls: "List directory contents, including hidden files and subdirectories",
8
8
  grep: "Search file contents using regex",
9
9
  web_search: "Search the public web for current information",
10
10
  web_fetch: "Fetch and extract the contents of a specific webpage",
@@ -17,7 +17,7 @@ export const defaultToolSnippets = {
17
17
  };
18
18
  export const defaultToolNames = [
19
19
  "read",
20
- "glob",
20
+ "ls",
21
21
  "bash",
22
22
  "edit",
23
23
  "write",
@@ -29,7 +29,7 @@ const PLAN_MODE_ENTER = `
29
29
  Plan mode is now ACTIVE.
30
30
 
31
31
  Rules while in plan mode:
32
- - Only read-only tools are allowed, including read, glob, grep, web_search, web_fetch, spawn_agent, wait_agent, send_input, skill_search, skill, tool_search, question, and exit_plan_mode.
32
+ - Only read-only tools are allowed, including read, ls, grep, web_search, web_fetch, spawn_agent, wait_agent, send_input, skill_search, skill, tool_search, question, and exit_plan_mode.
33
33
  - Writes, edits, and shell commands WILL be rejected by the harness; do not try them.
34
34
  - Do not edit files or claim implementation is complete while plan mode is active.
35
35
  - Investigate the codebase, then use the question tool to clarify important ambiguities, tradeoffs, requirements, or preference choices that would materially change the plan.
@@ -8,7 +8,7 @@ const defaultGuidelines = [
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
9
  "Runtime meta instructions are private control state. Use them only to adjust behavior; do not quote, mention, or paraphrase them in user-facing text.",
10
10
  "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 targeted changes and write for intentional full-file replacement of an existing file. Never delete and recreate a file just to overwrite it.",
11
- "Prefer structured tools (glob, grep, 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
+ "Do not repeat a near-identical search or re-read the same file unless new evidence changes the question. When output is truncated, narrow the scope or adjust the limit before treating it as a complete inventory.",
12
12
  "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.",
13
13
  "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.",
14
14
  "Before declaring a task complete, re-read the user's original request and verify that every explicit requirement in it — about both the result and the process — has been fulfilled, not only that the implementation works.",
@@ -22,7 +22,11 @@ interface AnthropicRequest {
22
22
  stream?: boolean;
23
23
  temperature?: number;
24
24
  thinking?: {
25
- type: "adaptive";
25
+ type: "adaptive" | "enabled" | "disabled";
26
+ display?: "summarized";
27
+ block_binding?: {
28
+ prefix_mismatch_behavior: "drop_block";
29
+ };
26
30
  };
27
31
  output_config?: {
28
32
  effort: AnthropicEffort;
@@ -113,7 +117,7 @@ export declare function buildAnthropicRequest(options: AnthropicProviderOptions,
113
117
  export declare function placeMessageCacheBreakpoints(messages: AnthropicMessage[], budget: number): number;
114
118
  export declare function resolveAnthropicMaxTokens(options: AnthropicProviderOptions, model: string): number;
115
119
  export declare function supportsAnthropicPromptCache(options: AnthropicProviderOptions, model: string): boolean;
116
- export declare function toAnthropicMessages(messages: ProviderMessage[], echoThinking?: boolean): {
120
+ export declare function toAnthropicMessages(messages: ProviderMessage[], echoThinking?: boolean | "all", allowUnsignedThinking?: boolean): {
117
121
  system: string;
118
122
  messages: AnthropicMessage[];
119
123
  };
@@ -1,8 +1,11 @@
1
1
  import { getAvailableThinkingLevels, normalizeThinkingLevel } from "./provider-transform.js";
2
+ import { getModelDefaultReasoningLevel } from "./model-catalog.js";
3
+ import { isClaudeWithBoundThinking } from "./anthropic-thinking.js";
2
4
  import { RateLimitError } from "./network/errors.js";
3
5
  import { isProviderTransportError, normalizeProviderNetworkError, providerFetch } from "./network/provider-transport.js";
4
6
  import { computeRetryDelayMs, getProviderMaxRetries, isRetryableHttpStatus, ProviderStreamInterruptedError, retryAfterMsFromResponse, sleepBeforeRetry, } from "./network/retry.js";
5
7
  const ANTHROPIC_VERSION = "2023-06-01";
8
+ const THINKING_BINDING_BETA = "thinking-binding-controls-2026-08-01";
6
9
  const DEFAULT_MAX_TOKENS = 8192;
7
10
  const ANTHROPIC_OPUS_LONG_OUTPUT_MAX_TOKENS = 128000;
8
11
  const ANTHROPIC_LONG_OUTPUT_MAX_TOKENS = 64000;
@@ -11,6 +14,8 @@ const ANTHROPIC_LONG_OUTPUT_MAX_TOKENS = 64000;
11
14
  // MiniMax's docs recommend M3 128K, M2.x 64K output.
12
15
  const MINIMAX_M3_MAX_TOKENS = 128000;
13
16
  const MINIMAX_M2_MAX_TOKENS = 64000;
17
+ // MiMo counts thinking and visible text together; match its documented 128K cap.
18
+ const MIMO_MAX_TOKENS = 131072;
14
19
  const ANTHROPIC_PROMPT_CACHE_CONTROL = { type: "ephemeral" };
15
20
  /** Hard API limit: a 5th cache_control breakpoint is a 400, not a warning. */
16
21
  const ANTHROPIC_MAX_CACHE_BREAKPOINTS = 4;
@@ -55,6 +60,7 @@ export function createAnthropicMessagesProvider(options) {
55
60
  });
56
61
  const events = streamAnthropicEventsWithRetry(options, {
57
62
  url: resolveAnthropicMessagesUrl(options.baseURL),
63
+ model: body.model,
58
64
  stream: true,
59
65
  method: "POST",
60
66
  body: JSON.stringify(body),
@@ -73,20 +79,25 @@ export function createAnthropicMessagesProvider(options) {
73
79
  });
74
80
  const response = await fetchAnthropicResponseWithRetry(options, {
75
81
  url: resolveAnthropicMessagesUrl(options.baseURL),
82
+ model: body.model,
76
83
  stream: false,
77
84
  method: "POST",
78
85
  body: JSON.stringify(body),
79
86
  signal: chatOptions?.abortSignal,
80
87
  });
81
88
  const data = await response.json();
89
+ reportThinkingPrefixDrops(data.input_transformations);
82
90
  return extractAnthropicText(data.content).join("");
83
91
  }
84
92
  return { streamChat, complete };
85
93
  }
86
94
  export function buildAnthropicRequest(options, messages, chatOptions) {
87
- const { system, messages: anthropicMessages } = toAnthropicMessages(messages, shouldEchoThinking(options.providerId));
95
+ const mimo = isMiMoProvider(options);
96
+ const modernClaude = isClaudeWithBoundThinking(chatOptions.model);
97
+ const { system, messages: anthropicMessages } = toAnthropicMessages(messages, modernClaude ? "all" : shouldEchoThinking(options), !modernClaude);
88
98
  const enablePromptCache = supportsAnthropicPromptCache(options, chatOptions.model);
89
- const tools = chatOptions.tools?.map((tool) => ({
99
+ // MiMo only supports tool_choice=auto. Removing tools enforces "none".
100
+ const tools = (mimo && chatOptions.toolChoice === "none" ? undefined : chatOptions.tools)?.map((tool) => ({
90
101
  name: tool.name,
91
102
  description: tool.description,
92
103
  input_schema: tool.parameters,
@@ -116,22 +127,35 @@ export function buildAnthropicRequest(options, messages, chatOptions) {
116
127
  && !messageCacheKillSwitchEnabled()) {
117
128
  placeMessageCacheBreakpoints(anthropicMessages, ANTHROPIC_MAX_CACHE_BREAKPOINTS - staticBreakpoints);
118
129
  }
119
- const effectiveThinkingLevel = normalizeThinkingLevel(chatOptions.thinkingLevel ?? options.thinkingLevel ?? "off", getAvailableThinkingLevels(options.providerId || "", chatOptions.model));
130
+ const effectiveThinkingLevel = normalizeThinkingLevel(chatOptions.thinkingLevel ?? options.thinkingLevel
131
+ ?? (mimo ? "medium" : modernClaude || isClaude5WithOptionalThinking(chatOptions.model)
132
+ ? getModelDefaultReasoningLevel(options.providerId || "", chatOptions.model) ?? "off" : "off"), mimo ? ["off", "medium"] : getAvailableThinkingLevels(options.providerId || "", chatOptions.model));
120
133
  const body = {
121
134
  model: chatOptions.model,
122
135
  max_tokens: resolveAnthropicMaxTokens(options, chatOptions.model),
123
136
  system: buildAnthropicSystem(system, enablePromptCache),
124
137
  messages: anthropicMessages,
125
138
  tools: tools && tools.length > 0 ? tools : undefined,
126
- tool_choice: tools && tools.length > 0 ? { type: chatOptions.toolChoice ?? "auto" } : undefined,
139
+ tool_choice: tools && tools.length > 0 ? { type: mimo ? "auto" : chatOptions.toolChoice ?? "auto" } : undefined,
127
140
  stream: chatOptions.stream || undefined,
128
141
  };
129
142
  if (typeof chatOptions.temperature === "number"
130
143
  && shouldSendTemperature(options, chatOptions.model, effectiveThinkingLevel)) {
131
144
  body.temperature = chatOptions.temperature;
132
145
  }
133
- if (effectiveThinkingLevel !== "off") {
134
- body.thinking = { type: "adaptive" };
146
+ if (mimo) {
147
+ // MiMo defaults to thinking on and rejects Claude's adaptive mode/effort.
148
+ body.thinking = { type: effectiveThinkingLevel === "off" ? "disabled" : "enabled" };
149
+ }
150
+ else if (effectiveThinkingLevel !== "off") {
151
+ body.thinking = modernClaude ? { type: "adaptive", display: "summarized" } : { type: "adaptive" };
152
+ if (modernClaude && isOfficialAnthropicBaseUrl(options.baseURL)) {
153
+ // Checkpoints proactively reset thinking. Runtime reminders, restored
154
+ // host context and tool changes can also change a prefix; let the API
155
+ // drop invalid blocks instead of failing the turn. Applied on every
156
+ // request so this behavior persists after restarts, per official guidance.
157
+ body.thinking.block_binding = { prefix_mismatch_behavior: "drop_block" };
158
+ }
135
159
  // Apply the selected reasoning depth via output_config.effort. Without this
136
160
  // every thinking request silently ran at Anthropic's default (high),
137
161
  // ignoring the chosen level. effort is an official-API feature, so only
@@ -145,6 +169,10 @@ export function buildAnthropicRequest(options, messages, chatOptions) {
145
169
  }
146
170
  }
147
171
  }
172
+ // Opus 5 and Sonnet 5 default to thinking on; omission cannot express Off.
173
+ if (!mimo && effectiveThinkingLevel === "off" && isClaude5WithOptionalThinking(chatOptions.model)) {
174
+ body.thinking = { type: "disabled" };
175
+ }
148
176
  return body;
149
177
  }
150
178
  /**
@@ -237,6 +265,8 @@ function messageCacheKillSwitchEnabled() {
237
265
  return raw === "0" || raw === "false";
238
266
  }
239
267
  export function resolveAnthropicMaxTokens(options, model) {
268
+ if (isMiMoProvider(options))
269
+ return MIMO_MAX_TOKENS;
240
270
  // MiniMax needs a large output budget so thinking doesn't starve the answer.
241
271
  if (isMiniMaxProvider(options)) {
242
272
  return /m3/i.test(model) ? MINIMAX_M3_MAX_TOKENS : MINIMAX_M2_MAX_TOKENS;
@@ -244,7 +274,8 @@ export function resolveAnthropicMaxTokens(options, model) {
244
274
  if (!isOfficialAnthropicBaseUrl(options.baseURL)) {
245
275
  return DEFAULT_MAX_TOKENS;
246
276
  }
247
- if (isFableModelWith128kOutput(model) || isOpusModelWith128kOutput(model)) {
277
+ if (isFableModelWith128kOutput(model) || isOpusModelWith128kOutput(model)
278
+ || isClaudeFamilyVersionAtLeast(model, "sonnet", 5, 0)) {
248
279
  return ANTHROPIC_OPUS_LONG_OUTPUT_MAX_TOKENS;
249
280
  }
250
281
  if (isSonnetOrHaikuModelWith64kOutput(model)) {
@@ -269,7 +300,7 @@ export function supportsAnthropicPromptCache(options, model) {
269
300
  }
270
301
  return MINIMAX_PROMPT_CACHE_MODELS.has(model.toLowerCase());
271
302
  }
272
- export function toAnthropicMessages(messages, echoThinking = false) {
303
+ export function toAnthropicMessages(messages, echoThinking = false, allowUnsignedThinking = true) {
273
304
  const system = [];
274
305
  const out = [];
275
306
  const thinkingReplayIndexes = getThinkingReplayIndexes(messages, echoThinking);
@@ -292,7 +323,7 @@ export function toAnthropicMessages(messages, echoThinking = false) {
292
323
  continue;
293
324
  }
294
325
  if (message.role === "assistant") {
295
- const content = buildAssistantAnthropicBlocks(message, thinkingReplayIndexes.has(index));
326
+ const content = buildAssistantAnthropicBlocks(message, thinkingReplayIndexes.has(index), allowUnsignedThinking);
296
327
  if (content.length > 0) {
297
328
  pushAnthropicMessage(out, { role: "assistant", content });
298
329
  }
@@ -307,7 +338,7 @@ export function toAnthropicMessages(messages, echoThinking = false) {
307
338
  }
308
339
  return { system: system.join("\n\n"), messages: out };
309
340
  }
310
- function buildAssistantAnthropicBlocks(message, includeThinking) {
341
+ function buildAssistantAnthropicBlocks(message, includeThinking, allowUnsignedThinking) {
311
342
  const rawBlocks = message.providerMetadata?.anthropic?.contentBlocks;
312
343
  if (rawBlocks && rawBlocks.length > 0) {
313
344
  const blocks = rawBlocks
@@ -319,7 +350,7 @@ function buildAssistantAnthropicBlocks(message, includeThinking) {
319
350
  }
320
351
  }
321
352
  const content = [];
322
- if (includeThinking && message.reasoning?.trim()) {
353
+ if (includeThinking && allowUnsignedThinking && message.reasoning?.trim()) {
323
354
  content.push({ type: "thinking", thinking: message.reasoning });
324
355
  }
325
356
  if (message.content.trim()) {
@@ -339,6 +370,15 @@ function getThinkingReplayIndexes(messages, echoThinking) {
339
370
  const indexes = new Set();
340
371
  if (!echoThinking)
341
372
  return indexes;
373
+ // MiMo and bound Claude thinking need intact historical blocks across user
374
+ // turns. Removing a middle block breaks the later Claude signature chain.
375
+ if (echoThinking === "all") {
376
+ messages.forEach((message, index) => {
377
+ if (message.role === "assistant")
378
+ indexes.add(index);
379
+ });
380
+ return indexes;
381
+ }
342
382
  let lastUserIndex = -1;
343
383
  for (let index = 0; index < messages.length; index++) {
344
384
  if (messages[index].role === "user") {
@@ -384,6 +424,7 @@ export async function* translateAnthropicStream(events) {
384
424
  for await (const event of events) {
385
425
  const type = typeof event.type === "string" ? event.type : "";
386
426
  if (type === "message_start") {
427
+ reportThinkingPrefixDrops(event.message?.input_transformations);
387
428
  usage = mergeAnthropicUsage(usage, event.message?.usage);
388
429
  if (usage)
389
430
  yield { type: "usage", usage };
@@ -560,7 +601,7 @@ async function fetchAnthropicResponseWithRetry(options, request) {
560
601
  try {
561
602
  response = await providerFetch(request.url, {
562
603
  method: request.method,
563
- headers: buildAnthropicHeaders(options, request.stream),
604
+ headers: buildAnthropicHeaders(options, request.stream, request.model),
564
605
  body: request.body,
565
606
  signal: request.signal,
566
607
  keepalive: false,
@@ -616,7 +657,7 @@ function resolveAnthropicMessagesUrl(baseURL) {
616
657
  return `${normalized}/messages`;
617
658
  return `${normalized || "https://api.anthropic.com"}/v1/messages`;
618
659
  }
619
- function buildAnthropicHeaders(options, stream) {
660
+ function buildAnthropicHeaders(options, stream, model) {
620
661
  const headers = {
621
662
  "content-type": "application/json",
622
663
  "x-api-key": options.apiKey,
@@ -629,7 +670,25 @@ function buildAnthropicHeaders(options, stream) {
629
670
  headers.accept = "text/event-stream";
630
671
  // User-configured headers win last: anthropic-protocol coding plans gate on
631
672
  // client identity (e.g. a specific User-Agent), configured per provider.
632
- return { ...headers, ...(options.headers ?? {}) };
673
+ const merged = { ...headers, ...(options.headers ?? {}) };
674
+ if (isOfficialAnthropicBaseUrl(options.baseURL) && isClaudeWithBoundThinking(model)) {
675
+ // Merge case-insensitively without losing user-configured beta features.
676
+ const betaKeys = Object.keys(merged).filter(key => key.toLowerCase() === "anthropic-beta");
677
+ const betas = betaKeys.flatMap(key => merged[key].split(",").map(value => value.trim()).filter(Boolean));
678
+ for (const key of betaKeys)
679
+ delete merged[key];
680
+ merged["anthropic-beta"] = [...new Set([...betas, THINKING_BINDING_BETA])].join(",");
681
+ }
682
+ return merged;
683
+ }
684
+ function reportThinkingPrefixDrops(transformations) {
685
+ if (!Array.isArray(transformations))
686
+ return;
687
+ const count = transformations.filter(item => item?.type === "thinking_dropped" && item?.reason === "prefix_binding_mismatch").length;
688
+ if (count > 0) {
689
+ // Count only; raw transformations, signatures and prompt text are private.
690
+ console.warn(`[anthropic-thinking] API discarded ${count} thinking block(s) after a conversation prefix change.`);
691
+ }
633
692
  }
634
693
  async function readAnthropicErrorDetail(response) {
635
694
  try {
@@ -788,18 +847,28 @@ function mergeAnthropicUsage(current, raw) {
788
847
  totalTokens: promptTokens + outputTokens,
789
848
  };
790
849
  }
791
- function shouldEchoThinking(providerId) {
792
- return providerId?.startsWith("minimax") ?? false;
850
+ function shouldEchoThinking(options) {
851
+ if (isMiMoProvider(options))
852
+ return "all";
853
+ return options.providerId?.startsWith("minimax") ?? false;
793
854
  }
794
855
  function shouldSendBearerAuth(options) {
795
856
  return !isOfficialAnthropicBaseUrl(options.baseURL) || options.providerId?.startsWith("minimax") === true;
796
857
  }
797
858
  function shouldSendTemperature(options, model, thinkingLevel) {
859
+ if (isMiMoProvider(options) && thinkingLevel !== "off")
860
+ return false;
798
861
  if (!isOfficialAnthropicBaseUrl(options.baseURL))
799
862
  return true;
800
863
  if (thinkingLevel !== "off")
801
864
  return false;
802
- return !isOpusModelWithoutSamplingControls(model);
865
+ return !isOpusModelWithoutSamplingControls(model)
866
+ && !isFableModelWith128kOutput(model)
867
+ && !isClaudeFamilyVersionAtLeast(model, "sonnet", 5, 0);
868
+ }
869
+ function isClaude5WithOptionalThinking(model) {
870
+ return isClaudeFamilyVersionAtLeast(model, "sonnet", 5, 0)
871
+ || (isClaudeFamilyVersionAtLeast(model, "opus", 5, 0) && !isClaudeWithBoundThinking(model));
803
872
  }
804
873
  function isOpusModelWith128kOutput(model) {
805
874
  return isClaudeFamilyVersionAtLeast(model, "opus", 4, 6);
@@ -828,7 +897,7 @@ function isClaudeFamilyVersionAtLeast(model, family, minMajor, minMinor) {
828
897
  if (major < minMajor)
829
898
  return false;
830
899
  if (!minorSegment || minorSegment.length > 2)
831
- return false;
900
+ return minMinor === 0;
832
901
  const minor = Number(minorSegment);
833
902
  return Number.isFinite(minor) && minor >= minMinor;
834
903
  }
@@ -858,3 +927,14 @@ function isMiniMaxProvider(options) {
858
927
  return (options.providerId || "").toLowerCase().startsWith("minimax")
859
928
  || options.baseURL.toLowerCase().includes("minimaxi");
860
929
  }
930
+ function isMiMoProvider(options) {
931
+ if (options.providerId === "mimo-token-plan")
932
+ return true;
933
+ try {
934
+ return ["api.xiaomimimo.com", "token-plan-cn.xiaomimimo.com", "token-plan-sgp.xiaomimimo.com", "token-plan-ams.xiaomimimo.com"]
935
+ .includes(new URL(options.baseURL).hostname);
936
+ }
937
+ catch {
938
+ return false;
939
+ }
940
+ }
@@ -14,6 +14,11 @@ export interface SanitizedProviderError {
14
14
  bubbleVersion: string;
15
15
  pid: number;
16
16
  runtimeStartedAt: number;
17
+ retry?: {
18
+ attempt: number;
19
+ maxAttempts: number;
20
+ };
21
+ requestShape?: Record<string, number>;
17
22
  }
18
23
  export interface ProviderErrorContext {
19
24
  providerId: string;
@@ -22,6 +27,10 @@ export interface ProviderErrorContext {
22
27
  thinkingLevel: ThinkingLevel;
23
28
  messageCount: number;
24
29
  toolCount: number;
30
+ retry?: {
31
+ attempt: number;
32
+ maxAttempts: number;
33
+ };
25
34
  }
26
35
  /**
27
36
  * Build the allowlisted diagnostic record written to a session log.