@duckmind/dm-darwin-x64 0.43.2 → 0.43.8

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 (256) hide show
  1. package/dm +0 -0
  2. package/extensions/.dm-extensions.json +242 -49
  3. package/extensions/dm-9router-ext/package.json +2 -19
  4. package/extensions/dm-9router-ext/src/index.js +5 -0
  5. package/extensions/dm-ask-user/index.js +10 -0
  6. package/extensions/dm-ask-user/package.json +1 -21
  7. package/extensions/dm-cli-anything/package.json +0 -6
  8. package/extensions/dm-context/package.json +2 -12
  9. package/extensions/dm-context/src/context.js +1 -0
  10. package/extensions/dm-context/src/index.js +7 -0
  11. package/extensions/dm-context/src/utils.js +1 -0
  12. package/extensions/dm-cua/package.json +0 -7
  13. package/extensions/dm-fff/package.json +1 -28
  14. package/extensions/dm-fff/src/index.js +15 -0
  15. package/extensions/dm-fff/src/query.js +1 -0
  16. package/extensions/dm-goal/package.json +2 -31
  17. package/extensions/dm-goal/src/goal.js +43 -0
  18. package/extensions/dm-grill-me/index.js +174 -0
  19. package/extensions/dm-grill-me/package.json +1 -26
  20. package/extensions/dm-image2/package.json +0 -7
  21. package/extensions/dm-subagents/package.json +1 -32
  22. package/extensions/dm-subagents/src/agents/agent-management.js +37 -0
  23. package/extensions/dm-subagents/src/agents/agent-memory.js +6 -0
  24. package/extensions/dm-subagents/src/agents/agent-scope.js +1 -0
  25. package/extensions/dm-subagents/src/agents/agent-selection.js +1 -0
  26. package/extensions/dm-subagents/src/agents/agent-serializer.js +7 -0
  27. package/extensions/dm-subagents/src/agents/agents.js +11 -0
  28. package/extensions/dm-subagents/src/agents/chain-serializer.js +12 -0
  29. package/extensions/dm-subagents/src/agents/frontmatter.js +6 -0
  30. package/extensions/dm-subagents/src/agents/identity.js +1 -0
  31. package/extensions/dm-subagents/src/agents/proactive-skills.js +1 -0
  32. package/extensions/dm-subagents/src/agents/skills.js +6 -0
  33. package/extensions/dm-subagents/src/extension/config.js +2 -0
  34. package/extensions/dm-subagents/src/extension/control-notices.js +4 -0
  35. package/extensions/dm-subagents/src/extension/doctor.js +16 -0
  36. package/extensions/dm-subagents/src/extension/fanout-child.js +231 -0
  37. package/extensions/dm-subagents/src/extension/index.js +367 -0
  38. package/extensions/dm-subagents/src/extension/rpc.js +8 -0
  39. package/extensions/dm-subagents/src/extension/schemas.js +1 -0
  40. package/extensions/dm-subagents/src/extension/{tool-description.ts → tool-description.js} +6 -126
  41. package/extensions/dm-subagents/src/intercom/intercom-bridge.js +13 -0
  42. package/extensions/dm-subagents/src/intercom/native-supervisor-channel.js +5 -0
  43. package/extensions/dm-subagents/src/intercom/result-intercom.js +3 -0
  44. package/extensions/dm-subagents/src/profiles/profiles.js +3 -0
  45. package/extensions/dm-subagents/src/runs/background/async-execution.js +46 -0
  46. package/extensions/dm-subagents/src/runs/background/async-job-tracker.js +14 -0
  47. package/extensions/dm-subagents/src/runs/background/async-resume.js +8 -0
  48. package/extensions/dm-subagents/src/runs/background/async-status.js +12 -0
  49. package/extensions/dm-subagents/src/runs/background/chain-append.js +2 -0
  50. package/extensions/dm-subagents/src/runs/background/chain-root-attachment.js +1 -0
  51. package/extensions/dm-subagents/src/runs/background/completion-batcher.js +1 -0
  52. package/extensions/dm-subagents/src/runs/background/completion-dedupe.js +1 -0
  53. package/extensions/dm-subagents/src/runs/background/control-channel.js +1 -0
  54. package/extensions/dm-subagents/src/runs/background/fleet-view.js +17 -0
  55. package/extensions/dm-subagents/src/runs/background/notify.js +3 -0
  56. package/extensions/dm-subagents/src/runs/background/parallel-groups.js +1 -0
  57. package/extensions/dm-subagents/src/runs/background/result-watcher.js +8 -0
  58. package/extensions/dm-subagents/src/runs/background/run-id-resolver.js +4 -0
  59. package/extensions/dm-subagents/src/runs/background/run-status.js +23 -0
  60. package/extensions/dm-subagents/src/runs/background/scheduled-runs.js +4 -0
  61. package/extensions/dm-subagents/src/runs/background/stale-run-reconciler.js +11 -0
  62. package/extensions/dm-subagents/src/runs/background/subagent-runner.js +78 -0
  63. package/extensions/dm-subagents/src/runs/background/top-level-async.js +1 -0
  64. package/extensions/dm-subagents/src/runs/background/wait.js +11 -0
  65. package/extensions/dm-subagents/src/runs/foreground/chain-clarify.js +12 -0
  66. package/extensions/dm-subagents/src/runs/foreground/chain-execution.js +102 -0
  67. package/extensions/dm-subagents/src/runs/foreground/execution.js +51 -0
  68. package/extensions/dm-subagents/src/runs/foreground/subagent-executor.js +228 -0
  69. package/extensions/dm-subagents/src/runs/shared/acceptance.js +3 -0
  70. package/extensions/dm-subagents/src/runs/shared/chain-outputs.js +1 -0
  71. package/extensions/dm-subagents/src/runs/shared/completion-guard.js +3 -0
  72. package/extensions/dm-subagents/src/runs/shared/dm-args.js +1 -0
  73. package/extensions/dm-subagents/src/runs/shared/dm-spawn.js +1 -0
  74. package/extensions/dm-subagents/src/runs/shared/dynamic-fanout.js +1 -0
  75. package/extensions/dm-subagents/src/runs/shared/long-running-guard.js +1 -0
  76. package/extensions/dm-subagents/src/runs/shared/mcp-direct-tool-allowlist.js +1 -0
  77. package/extensions/dm-subagents/src/runs/shared/model-fallback.js +1 -0
  78. package/extensions/dm-subagents/src/runs/shared/model-scope.js +1 -0
  79. package/extensions/dm-subagents/src/runs/shared/nested-events.js +8 -0
  80. package/extensions/dm-subagents/src/runs/shared/nested-path.js +1 -0
  81. package/extensions/dm-subagents/src/runs/shared/nested-render.js +1 -0
  82. package/extensions/dm-subagents/src/runs/shared/parallel-utils.js +5 -0
  83. package/extensions/dm-subagents/src/runs/shared/run-history.js +5 -0
  84. package/extensions/dm-subagents/src/runs/shared/single-output.js +14 -0
  85. package/extensions/dm-subagents/src/runs/shared/structured-output.js +1 -0
  86. package/extensions/dm-subagents/src/runs/shared/subagent-control.js +5 -0
  87. package/extensions/dm-subagents/src/runs/shared/subagent-prompt-runtime.js +22 -0
  88. package/extensions/dm-subagents/src/runs/shared/tool-budget.js +1 -0
  89. package/extensions/dm-subagents/src/runs/shared/turn-budget.js +7 -0
  90. package/extensions/dm-subagents/src/runs/shared/workflow-graph.js +1 -0
  91. package/extensions/dm-subagents/src/runs/shared/worktree.js +3 -0
  92. package/extensions/dm-subagents/src/shared/artifacts.js +2 -0
  93. package/extensions/dm-subagents/src/shared/atomic-json.js +1 -0
  94. package/extensions/dm-subagents/src/shared/child-transcript.js +5 -0
  95. package/extensions/dm-subagents/src/shared/file-coalescer.js +1 -0
  96. package/extensions/dm-subagents/src/shared/fork-context.js +6 -0
  97. package/extensions/dm-subagents/src/shared/formatters.js +9 -0
  98. package/extensions/dm-subagents/src/shared/jsonl-writer.js +2 -0
  99. package/extensions/dm-subagents/src/shared/model-info.js +1 -0
  100. package/extensions/dm-subagents/src/shared/post-exit-stdio-guard.js +1 -0
  101. package/extensions/dm-subagents/src/shared/session-identity.js +1 -0
  102. package/extensions/dm-subagents/src/shared/session-tokens.js +2 -0
  103. package/extensions/dm-subagents/src/shared/settings.js +23 -0
  104. package/extensions/dm-subagents/src/shared/status-format.js +1 -0
  105. package/extensions/dm-subagents/src/shared/types.js +8 -0
  106. package/extensions/dm-subagents/src/shared/utils.js +2 -0
  107. package/extensions/dm-subagents/src/slash/prompt-template-bridge.js +1 -0
  108. package/extensions/dm-subagents/src/slash/prompt-workflows.js +7 -0
  109. package/extensions/dm-subagents/src/slash/slash-bridge.js +1 -0
  110. package/extensions/dm-subagents/src/slash/slash-commands.js +38 -0
  111. package/extensions/dm-subagents/src/slash/slash-live-state.js +6 -0
  112. package/extensions/dm-subagents/src/tui/render-helpers.js +1 -0
  113. package/extensions/dm-subagents/src/tui/render.js +4 -0
  114. package/extensions/dm-tasks/package.json +1 -30
  115. package/extensions/dm-tasks/src/auto-clear.js +1 -0
  116. package/extensions/dm-tasks/src/index.js +212 -0
  117. package/extensions/dm-tasks/src/process-tracker.js +1 -0
  118. package/extensions/dm-tasks/src/task-store.js +1 -0
  119. package/extensions/dm-tasks/src/tasks-config.js +1 -0
  120. package/extensions/dm-tasks/src/types.js +0 -0
  121. package/extensions/dm-tasks/src/ui/settings-menu.js +1 -0
  122. package/extensions/dm-tasks/src/ui/task-widget.js +1 -0
  123. package/extensions/dm-usage/index.js +9 -0
  124. package/extensions/dm-usage/package.json +1 -18
  125. package/extensions/greedysearch-dm/bin/search.mjs +135 -135
  126. package/extensions/greedysearch-dm/index.js +20 -0
  127. package/extensions/greedysearch-dm/package.json +1 -22
  128. package/extensions/greedysearch-dm/src/formatters/results.js +8 -0
  129. package/extensions/greedysearch-dm/src/formatters/sources.js +1 -0
  130. package/extensions/greedysearch-dm/src/formatters/synthesis.js +1 -0
  131. package/extensions/greedysearch-dm/src/search/engines.mjs +9 -9
  132. package/extensions/greedysearch-dm/src/search/paths.mjs +1 -0
  133. package/extensions/greedysearch-dm/src/search/research.mjs +100 -100
  134. package/extensions/greedysearch-dm/src/search/synthesis-runner.mjs +10 -10
  135. package/extensions/greedysearch-dm/src/tools/greedy-search-handler.js +20 -0
  136. package/extensions/greedysearch-dm/src/tools/shared.js +9 -0
  137. package/extensions/greedysearch-dm/src/types.js +0 -0
  138. package/extensions/greedysearch-dm/src/utils/helpers.js +1 -0
  139. package/package.json +1 -1
  140. package/extensions/dm-9router-ext/src/index.ts +0 -541
  141. package/extensions/dm-ask-user/index.ts +0 -857
  142. package/extensions/dm-context/src/context.ts +0 -158
  143. package/extensions/dm-context/src/index.ts +0 -439
  144. package/extensions/dm-context/src/utils.ts +0 -6
  145. package/extensions/dm-fff/src/index.ts +0 -1023
  146. package/extensions/dm-fff/src/query.ts +0 -87
  147. package/extensions/dm-goal/src/goal.ts +0 -1073
  148. package/extensions/dm-grill-me/index.ts +0 -1085
  149. package/extensions/dm-subagents/src/agents/agent-management.ts +0 -1052
  150. package/extensions/dm-subagents/src/agents/agent-memory.ts +0 -254
  151. package/extensions/dm-subagents/src/agents/agent-scope.ts +0 -6
  152. package/extensions/dm-subagents/src/agents/agent-selection.ts +0 -25
  153. package/extensions/dm-subagents/src/agents/agent-serializer.ts +0 -121
  154. package/extensions/dm-subagents/src/agents/agents.ts +0 -1553
  155. package/extensions/dm-subagents/src/agents/chain-serializer.ts +0 -277
  156. package/extensions/dm-subagents/src/agents/frontmatter.ts +0 -93
  157. package/extensions/dm-subagents/src/agents/identity.ts +0 -30
  158. package/extensions/dm-subagents/src/agents/proactive-skills.ts +0 -191
  159. package/extensions/dm-subagents/src/agents/skills.ts +0 -729
  160. package/extensions/dm-subagents/src/extension/config.ts +0 -39
  161. package/extensions/dm-subagents/src/extension/control-notices.ts +0 -92
  162. package/extensions/dm-subagents/src/extension/doctor.ts +0 -214
  163. package/extensions/dm-subagents/src/extension/fanout-child.ts +0 -172
  164. package/extensions/dm-subagents/src/extension/index.ts +0 -656
  165. package/extensions/dm-subagents/src/extension/rpc.ts +0 -369
  166. package/extensions/dm-subagents/src/extension/schemas.ts +0 -309
  167. package/extensions/dm-subagents/src/intercom/intercom-bridge.ts +0 -180
  168. package/extensions/dm-subagents/src/intercom/native-supervisor-channel.ts +0 -519
  169. package/extensions/dm-subagents/src/intercom/result-intercom.ts +0 -377
  170. package/extensions/dm-subagents/src/profiles/profiles.ts +0 -637
  171. package/extensions/dm-subagents/src/runs/background/async-execution.ts +0 -1065
  172. package/extensions/dm-subagents/src/runs/background/async-job-tracker.ts +0 -441
  173. package/extensions/dm-subagents/src/runs/background/async-resume.ts +0 -391
  174. package/extensions/dm-subagents/src/runs/background/async-status.ts +0 -395
  175. package/extensions/dm-subagents/src/runs/background/chain-append.ts +0 -282
  176. package/extensions/dm-subagents/src/runs/background/chain-root-attachment.ts +0 -191
  177. package/extensions/dm-subagents/src/runs/background/completion-batcher.ts +0 -166
  178. package/extensions/dm-subagents/src/runs/background/completion-dedupe.ts +0 -63
  179. package/extensions/dm-subagents/src/runs/background/control-channel.ts +0 -332
  180. package/extensions/dm-subagents/src/runs/background/fleet-view.ts +0 -515
  181. package/extensions/dm-subagents/src/runs/background/notify.ts +0 -225
  182. package/extensions/dm-subagents/src/runs/background/parallel-groups.ts +0 -45
  183. package/extensions/dm-subagents/src/runs/background/result-watcher.ts +0 -315
  184. package/extensions/dm-subagents/src/runs/background/run-id-resolver.ts +0 -84
  185. package/extensions/dm-subagents/src/runs/background/run-status.ts +0 -434
  186. package/extensions/dm-subagents/src/runs/background/scheduled-runs.ts +0 -514
  187. package/extensions/dm-subagents/src/runs/background/stale-run-reconciler.ts +0 -368
  188. package/extensions/dm-subagents/src/runs/background/subagent-runner.ts +0 -3171
  189. package/extensions/dm-subagents/src/runs/background/top-level-async.ts +0 -13
  190. package/extensions/dm-subagents/src/runs/background/wait.ts +0 -353
  191. package/extensions/dm-subagents/src/runs/foreground/chain-clarify.ts +0 -1333
  192. package/extensions/dm-subagents/src/runs/foreground/chain-execution.ts +0 -1313
  193. package/extensions/dm-subagents/src/runs/foreground/execution.ts +0 -1239
  194. package/extensions/dm-subagents/src/runs/foreground/subagent-executor.ts +0 -3613
  195. package/extensions/dm-subagents/src/runs/shared/acceptance.ts +0 -879
  196. package/extensions/dm-subagents/src/runs/shared/chain-outputs.ts +0 -116
  197. package/extensions/dm-subagents/src/runs/shared/completion-guard.ts +0 -147
  198. package/extensions/dm-subagents/src/runs/shared/dm-args.ts +0 -271
  199. package/extensions/dm-subagents/src/runs/shared/dm-spawn.ts +0 -147
  200. package/extensions/dm-subagents/src/runs/shared/dynamic-fanout.ts +0 -295
  201. package/extensions/dm-subagents/src/runs/shared/long-running-guard.ts +0 -175
  202. package/extensions/dm-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +0 -365
  203. package/extensions/dm-subagents/src/runs/shared/model-fallback.ts +0 -292
  204. package/extensions/dm-subagents/src/runs/shared/model-scope.ts +0 -128
  205. package/extensions/dm-subagents/src/runs/shared/nested-events.ts +0 -908
  206. package/extensions/dm-subagents/src/runs/shared/nested-path.ts +0 -52
  207. package/extensions/dm-subagents/src/runs/shared/nested-render.ts +0 -115
  208. package/extensions/dm-subagents/src/runs/shared/parallel-utils.ts +0 -198
  209. package/extensions/dm-subagents/src/runs/shared/run-history.ts +0 -60
  210. package/extensions/dm-subagents/src/runs/shared/single-output.ts +0 -180
  211. package/extensions/dm-subagents/src/runs/shared/structured-output.ts +0 -77
  212. package/extensions/dm-subagents/src/runs/shared/subagent-control.ts +0 -223
  213. package/extensions/dm-subagents/src/runs/shared/subagent-prompt-runtime.ts +0 -342
  214. package/extensions/dm-subagents/src/runs/shared/tool-budget.ts +0 -74
  215. package/extensions/dm-subagents/src/runs/shared/turn-budget.ts +0 -52
  216. package/extensions/dm-subagents/src/runs/shared/workflow-graph.ts +0 -206
  217. package/extensions/dm-subagents/src/runs/shared/worktree.ts +0 -600
  218. package/extensions/dm-subagents/src/shared/artifacts.ts +0 -113
  219. package/extensions/dm-subagents/src/shared/atomic-json.ts +0 -86
  220. package/extensions/dm-subagents/src/shared/child-transcript.ts +0 -212
  221. package/extensions/dm-subagents/src/shared/file-coalescer.ts +0 -40
  222. package/extensions/dm-subagents/src/shared/fork-context.ts +0 -194
  223. package/extensions/dm-subagents/src/shared/formatters.ts +0 -133
  224. package/extensions/dm-subagents/src/shared/jsonl-writer.ts +0 -81
  225. package/extensions/dm-subagents/src/shared/model-info.ts +0 -78
  226. package/extensions/dm-subagents/src/shared/post-exit-stdio-guard.ts +0 -85
  227. package/extensions/dm-subagents/src/shared/session-identity.ts +0 -10
  228. package/extensions/dm-subagents/src/shared/session-tokens.ts +0 -44
  229. package/extensions/dm-subagents/src/shared/settings.ts +0 -450
  230. package/extensions/dm-subagents/src/shared/status-format.ts +0 -49
  231. package/extensions/dm-subagents/src/shared/types.ts +0 -1257
  232. package/extensions/dm-subagents/src/shared/utils.ts +0 -554
  233. package/extensions/dm-subagents/src/slash/prompt-template-bridge.ts +0 -420
  234. package/extensions/dm-subagents/src/slash/prompt-workflows.ts +0 -330
  235. package/extensions/dm-subagents/src/slash/slash-bridge.ts +0 -176
  236. package/extensions/dm-subagents/src/slash/slash-commands.ts +0 -1296
  237. package/extensions/dm-subagents/src/slash/slash-live-state.ts +0 -292
  238. package/extensions/dm-subagents/src/tui/render-helpers.ts +0 -80
  239. package/extensions/dm-subagents/src/tui/render.ts +0 -1748
  240. package/extensions/dm-tasks/src/auto-clear.ts +0 -91
  241. package/extensions/dm-tasks/src/index.ts +0 -1145
  242. package/extensions/dm-tasks/src/process-tracker.ts +0 -140
  243. package/extensions/dm-tasks/src/task-store.ts +0 -305
  244. package/extensions/dm-tasks/src/tasks-config.ts +0 -23
  245. package/extensions/dm-tasks/src/types.ts +0 -40
  246. package/extensions/dm-tasks/src/ui/settings-menu.ts +0 -100
  247. package/extensions/dm-tasks/src/ui/task-widget.ts +0 -265
  248. package/extensions/dm-usage/index.ts +0 -1718
  249. package/extensions/greedysearch-dm/index.ts +0 -177
  250. package/extensions/greedysearch-dm/src/formatters/results.ts +0 -163
  251. package/extensions/greedysearch-dm/src/formatters/sources.ts +0 -116
  252. package/extensions/greedysearch-dm/src/formatters/synthesis.ts +0 -87
  253. package/extensions/greedysearch-dm/src/tools/greedy-search-handler.ts +0 -370
  254. package/extensions/greedysearch-dm/src/tools/shared.ts +0 -187
  255. package/extensions/greedysearch-dm/src/types.ts +0 -110
  256. package/extensions/greedysearch-dm/src/utils/helpers.ts +0 -40
@@ -1,365 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import * as fs from "node:fs";
3
- import * as os from "node:os";
4
- import * as path from "node:path";
5
- import { getAgentDir, getProjectConfigDir } from "../../shared/utils.ts";
6
-
7
- const CACHE_VERSION = 1;
8
- const CACHE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
9
- const BUILTIN_TOOL_NAMES = new Set(["read", "bash", "edit", "write", "grep", "find", "ls", "mcp"]);
10
- const GENERIC_GLOBAL_CONFIG_PATH = path.join(os.homedir(), ".config", "mcp", "mcp.json");
11
- const IMPORT_PATHS = {
12
- cursor: [path.join(os.homedir(), ".cursor", "mcp.json")],
13
- "claude-code": [
14
- path.join(os.homedir(), ".claude", "mcp.json"),
15
- path.join(os.homedir(), ".claude.json"),
16
- path.join(os.homedir(), ".claude", "claude_desktop_config.json"),
17
- ],
18
- "claude-desktop": [path.join(os.homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json")],
19
- codex: [path.join(os.homedir(), ".codex", "config.json")],
20
- windsurf: [path.join(os.homedir(), ".windsurf", "mcp.json")],
21
- vscode: [".vscode/mcp.json"],
22
- } as const;
23
-
24
- type ToolPrefix = "server" | "none" | "short";
25
- type ImportKind = keyof typeof IMPORT_PATHS;
26
-
27
- interface ServerEntry {
28
- command?: string;
29
- args?: string[];
30
- env?: Record<string, string>;
31
- cwd?: string;
32
- url?: string;
33
- headers?: Record<string, string>;
34
- auth?: "oauth" | "bearer" | false;
35
- bearerToken?: string;
36
- bearerTokenEnv?: string;
37
- exposeResources?: boolean;
38
- excludeTools?: string[];
39
- directTools?: boolean | string[];
40
- }
41
-
42
- interface McpConfig {
43
- mcpServers: Record<string, ServerEntry>;
44
- imports?: ImportKind[];
45
- settings?: {
46
- toolPrefix?: ToolPrefix;
47
- directTools?: boolean;
48
- };
49
- }
50
-
51
- interface CachedTool {
52
- name?: string;
53
- }
54
-
55
- interface CachedResource {
56
- uri?: string;
57
- name?: string;
58
- }
59
-
60
- interface ServerCacheEntry {
61
- configHash?: string;
62
- tools?: CachedTool[];
63
- resources?: CachedResource[];
64
- cachedAt?: number;
65
- }
66
-
67
- interface MetadataCache {
68
- version: number;
69
- servers: Record<string, ServerCacheEntry>;
70
- }
71
-
72
- export function resolveMcpDirectToolNames(mcpDirectTools: string[] | undefined, cwd = process.cwd()): string[] {
73
- if (!mcpDirectTools?.length) return [];
74
-
75
- try {
76
- const config = loadMcpConfig(cwd);
77
- const cache = loadMetadataCache();
78
- if (!cache) return [];
79
- return resolveDirectToolNames(config, cache, getToolPrefix(config.settings?.toolPrefix), mcpDirectTools);
80
- } catch {
81
- return [];
82
- }
83
- }
84
-
85
- function loadMetadataCache(): MetadataCache | null {
86
- const cachePath = path.join(getAgentDir(), "mcp-cache.json");
87
- let parsed: unknown;
88
- try {
89
- parsed = JSON.parse(fs.readFileSync(cachePath, "utf-8"));
90
- } catch {
91
- return null;
92
- }
93
-
94
- if (!parsed || typeof parsed !== "object") return null;
95
- const raw = parsed as Record<string, unknown>;
96
- if (raw.version !== CACHE_VERSION || !raw.servers || typeof raw.servers !== "object" || Array.isArray(raw.servers)) {
97
- return null;
98
- }
99
- return raw as unknown as MetadataCache;
100
- }
101
-
102
- function loadMcpConfig(cwd: string): McpConfig {
103
- let config: McpConfig = { mcpServers: {} };
104
- for (const sourcePath of getConfigPaths(cwd)) {
105
- const loaded = readConfig(sourcePath);
106
- if (!loaded) continue;
107
- config = mergeConfigs(config, expandImports(loaded, cwd));
108
- }
109
- return config;
110
- }
111
-
112
- function getConfigPaths(cwd: string): string[] {
113
- const piGlobalPath = path.join(getAgentDir(), "mcp.json");
114
- const projectPath = path.resolve(cwd, ".mcp.json");
115
- const projectPiPath = path.resolve(getProjectConfigDir(cwd), "mcp.json");
116
- const sources: string[] = [];
117
- if (GENERIC_GLOBAL_CONFIG_PATH !== piGlobalPath) sources.push(GENERIC_GLOBAL_CONFIG_PATH);
118
- sources.push(piGlobalPath);
119
- if (projectPath !== piGlobalPath) sources.push(projectPath);
120
- if (projectPiPath !== piGlobalPath && projectPiPath !== projectPath) sources.push(projectPiPath);
121
- return sources;
122
- }
123
-
124
- function readConfig(configPath: string): McpConfig | null {
125
- let parsed: unknown;
126
- try {
127
- parsed = JSON.parse(fs.readFileSync(configPath, "utf-8"));
128
- } catch {
129
- return null;
130
- }
131
- return validateConfig(parsed);
132
- }
133
-
134
- function validateConfig(raw: unknown): McpConfig {
135
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) return { mcpServers: {} };
136
- const obj = raw as Record<string, unknown>;
137
- const servers = obj.mcpServers ?? obj["mcp-servers"] ?? {};
138
- return {
139
- mcpServers: servers && typeof servers === "object" && !Array.isArray(servers) ? servers as Record<string, ServerEntry> : {},
140
- imports: Array.isArray(obj.imports) ? obj.imports.filter((value): value is ImportKind => isImportKind(value)) : undefined,
141
- settings: obj.settings && typeof obj.settings === "object" && !Array.isArray(obj.settings)
142
- ? obj.settings as McpConfig["settings"]
143
- : undefined,
144
- };
145
- }
146
-
147
- function mergeConfigs(base: McpConfig, next: McpConfig): McpConfig {
148
- const imports = [...(base.imports ?? []), ...(next.imports ?? [])];
149
- return {
150
- mcpServers: { ...base.mcpServers, ...next.mcpServers },
151
- imports: imports.length ? [...new Set(imports)] : undefined,
152
- settings: next.settings ? { ...base.settings, ...next.settings } : base.settings,
153
- };
154
- }
155
-
156
- function expandImports(config: McpConfig, cwd: string): McpConfig {
157
- if (!config.imports?.length) return config;
158
-
159
- const importedServers: Record<string, ServerEntry> = {};
160
- for (const importKind of config.imports) {
161
- const importPath = resolveImportPath(importKind, cwd);
162
- if (!importPath) continue;
163
- let imported: unknown;
164
- try {
165
- imported = JSON.parse(fs.readFileSync(importPath, "utf-8"));
166
- } catch {
167
- continue;
168
- }
169
- for (const [name, definition] of Object.entries(extractServers(imported, importKind))) {
170
- if (!importedServers[name]) importedServers[name] = definition;
171
- }
172
- }
173
-
174
- return {
175
- imports: config.imports,
176
- settings: config.settings,
177
- mcpServers: { ...importedServers, ...config.mcpServers },
178
- };
179
- }
180
-
181
- function resolveImportPath(importKind: ImportKind, cwd: string): string | null {
182
- for (const candidate of IMPORT_PATHS[importKind]) {
183
- const fullPath = candidate.startsWith(".") ? path.resolve(cwd, candidate) : candidate;
184
- if (fs.existsSync(fullPath)) return fullPath;
185
- }
186
- return null;
187
- }
188
-
189
- function extractServers(config: unknown, kind: ImportKind): Record<string, ServerEntry> {
190
- if (!config || typeof config !== "object" || Array.isArray(config)) return {};
191
- const obj = config as Record<string, unknown>;
192
- const servers = kind === "cursor" || kind === "windsurf" || kind === "vscode"
193
- ? obj.mcpServers ?? obj["mcp-servers"]
194
- : obj.mcpServers;
195
- return servers && typeof servers === "object" && !Array.isArray(servers) ? servers as Record<string, ServerEntry> : {};
196
- }
197
-
198
- function resolveDirectToolNames(config: McpConfig, cache: MetadataCache, prefix: ToolPrefix, envOverride: string[]): string[] {
199
- const names: string[] = [];
200
- const seenNames = new Set<string>();
201
- const { servers: selectedServers, tools: selectedTools } = parseSelections(envOverride);
202
-
203
- for (const [serverName, definition] of Object.entries(config.mcpServers)) {
204
- const serverCache = cache.servers[serverName];
205
- if (!isServerCacheValid(serverCache, definition)) continue;
206
-
207
- const toolFilter = selectedServers.has(serverName)
208
- ? true
209
- : selectedTools.get(serverName);
210
- if (!toolFilter) continue;
211
-
212
- for (const tool of Array.isArray(serverCache.tools) ? serverCache.tools : []) {
213
- if (typeof tool?.name !== "string" || !tool.name) continue;
214
- if (toolFilter !== true && !toolFilter.has(tool.name)) continue;
215
- if (isToolExcluded(tool.name, serverName, prefix, definition.excludeTools)) continue;
216
- const prefixedName = formatToolName(tool.name, serverName, prefix);
217
- if (BUILTIN_TOOL_NAMES.has(prefixedName) || seenNames.has(prefixedName)) continue;
218
- seenNames.add(prefixedName);
219
- names.push(prefixedName);
220
- }
221
-
222
- if (definition.exposeResources === false) continue;
223
- for (const resource of Array.isArray(serverCache.resources) ? serverCache.resources : []) {
224
- if (typeof resource?.name !== "string" || !resource.name || typeof resource.uri !== "string" || !resource.uri) continue;
225
- const baseName = `get_${resourceNameToToolName(resource.name)}`;
226
- if (toolFilter !== true && !toolFilter.has(baseName)) continue;
227
- if (isToolExcluded(baseName, serverName, prefix, definition.excludeTools)) continue;
228
- const prefixedName = formatToolName(baseName, serverName, prefix);
229
- if (BUILTIN_TOOL_NAMES.has(prefixedName) || seenNames.has(prefixedName)) continue;
230
- seenNames.add(prefixedName);
231
- names.push(prefixedName);
232
- }
233
- }
234
-
235
- return names;
236
- }
237
-
238
- function parseSelections(selections: string[]): { servers: Set<string>; tools: Map<string, Set<string>> } {
239
- const servers = new Set<string>();
240
- const tools = new Map<string, Set<string>>();
241
- for (let item of selections) {
242
- item = item.replace(/\/+$/, "");
243
- if (item.includes("/")) {
244
- const [server, tool] = item.split("/", 2);
245
- if (server && tool) {
246
- if (!tools.has(server)) tools.set(server, new Set());
247
- tools.get(server)!.add(tool);
248
- } else if (server) {
249
- servers.add(server);
250
- }
251
- } else if (item) {
252
- servers.add(item);
253
- }
254
- }
255
- return { servers, tools };
256
- }
257
-
258
- function isServerCacheValid(entry: ServerCacheEntry | undefined, definition: ServerEntry): entry is ServerCacheEntry {
259
- if (!entry || entry.configHash !== computeMcpServerHash(definition)) return false;
260
- if (!entry.cachedAt || typeof entry.cachedAt !== "number") return false;
261
- return Date.now() - entry.cachedAt <= CACHE_MAX_AGE_MS;
262
- }
263
-
264
- export function computeMcpServerHash(definition: ServerEntry): string {
265
- const identity: Record<string, unknown> = {
266
- command: definition.command,
267
- args: definition.args,
268
- env: interpolateEnvRecord(definition.env),
269
- cwd: resolveConfigPath(definition.cwd),
270
- url: definition.url,
271
- headers: interpolateEnvRecord(definition.headers),
272
- auth: definition.auth,
273
- bearerToken: resolveBearerToken(definition),
274
- bearerTokenEnv: definition.bearerTokenEnv,
275
- exposeResources: definition.exposeResources,
276
- excludeTools: definition.excludeTools,
277
- };
278
- return createHash("sha256").update(stableStringify(identity)).digest("hex");
279
- }
280
-
281
- function getToolPrefix(value: unknown): ToolPrefix {
282
- return value === "none" || value === "short" || value === "server" ? value : "server";
283
- }
284
-
285
- function isImportKind(value: unknown): value is ImportKind {
286
- return typeof value === "string" && Object.hasOwn(IMPORT_PATHS, value);
287
- }
288
-
289
- function getServerPrefix(serverName: string, mode: ToolPrefix): string {
290
- if (mode === "none") return "";
291
- if (mode === "short") {
292
- const short = serverName.replace(/-?mcp$/i, "").replace(/-/g, "_");
293
- return short || "mcp";
294
- }
295
- return serverName.replace(/-/g, "_");
296
- }
297
-
298
- function formatToolName(toolName: string, serverName: string, prefix: ToolPrefix): string {
299
- const serverPrefix = getServerPrefix(serverName, prefix);
300
- return serverPrefix ? `${serverPrefix}_${toolName}` : toolName;
301
- }
302
-
303
- function isToolExcluded(toolName: string, serverName: string, prefix: ToolPrefix, excludeTools: unknown): boolean {
304
- if (!Array.isArray(excludeTools) || excludeTools.length === 0) return false;
305
- const candidates = new Set([
306
- normalizeToolName(toolName),
307
- normalizeToolName(formatToolName(toolName, serverName, prefix)),
308
- normalizeToolName(formatToolName(toolName, serverName, "server")),
309
- normalizeToolName(formatToolName(toolName, serverName, "short")),
310
- ]);
311
- return excludeTools.some((excluded) => typeof excluded === "string" && candidates.has(normalizeToolName(excluded)));
312
- }
313
-
314
- function normalizeToolName(value: string): string {
315
- return value.replace(/-/g, "_");
316
- }
317
-
318
- function resourceNameToToolName(name: string): string {
319
- let result = name
320
- .replace(/[^a-zA-Z0-9]/g, "_")
321
- .replace(/_+/g, "_")
322
- .replace(/^_+/, "")
323
- .replace(/_+$/, "")
324
- .toLowerCase();
325
- if (!result || /^\d/.test(result)) result = `resource${result ? `_${result}` : ""}`;
326
- return result;
327
- }
328
-
329
- function interpolateEnvRecord(values: Record<string, string> | undefined): Record<string, string> | undefined {
330
- if (!values || typeof values !== "object" || Array.isArray(values)) return undefined;
331
- const resolved: Record<string, string> = {};
332
- for (const [key, value] of Object.entries(values)) {
333
- if (typeof value === "string") resolved[key] = interpolateEnvVars(value);
334
- }
335
- return resolved;
336
- }
337
-
338
- function interpolateEnvVars(value: string): string {
339
- return value
340
- .replace(/\$\{(\w+)\}/g, (_, name: string) => process.env[name] ?? "")
341
- .replace(/\$env:(\w+)/g, (_, name: string) => process.env[name] ?? "");
342
- }
343
-
344
- function resolveConfigPath(value: string | undefined): string | undefined {
345
- if (typeof value !== "string") return undefined;
346
- const resolved = interpolateEnvVars(value);
347
- if (resolved === "~") return os.homedir();
348
- if (resolved.startsWith("~/") || resolved.startsWith("~\\")) return path.join(os.homedir(), resolved.slice(2));
349
- return resolved;
350
- }
351
-
352
- function resolveBearerToken(definition: Pick<ServerEntry, "bearerToken" | "bearerTokenEnv">): string | undefined {
353
- if (typeof definition.bearerToken === "string") return interpolateEnvVars(definition.bearerToken);
354
- return typeof definition.bearerTokenEnv === "string" ? process.env[definition.bearerTokenEnv] : undefined;
355
- }
356
-
357
- function stableStringify(value: unknown): string {
358
- if (value === null || value === undefined || typeof value !== "object") {
359
- const serialized = JSON.stringify(value);
360
- return serialized === undefined ? "undefined" : serialized;
361
- }
362
- if (Array.isArray(value)) return `[${value.map((entry) => stableStringify(entry)).join(",")}]`;
363
- const obj = value as Record<string, unknown>;
364
- return `{${Object.keys(obj).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(obj[key])}`).join(",")}}`;
365
- }
@@ -1,292 +0,0 @@
1
- import type { ModelInfo as AvailableModelInfo } from "../../shared/model-info.ts";
2
- import type { Usage } from "../../shared/types.ts";
3
- import { checkModelScope, type ModelScopeConfig, type ModelScopeViolation, type ModelSource } from "./model-scope.ts";
4
-
5
- export type { AvailableModelInfo };
6
-
7
- interface ModelAttemptSummary {
8
- model: string;
9
- success: boolean;
10
- exitCode?: number | null;
11
- error?: string;
12
- usage?: Usage;
13
- }
14
-
15
- export function splitThinkingSuffix(model: string): { baseModel: string; thinkingSuffix: string } {
16
- const colonIdx = model.lastIndexOf(":");
17
- if (colonIdx === -1) return { baseModel: model, thinkingSuffix: "" };
18
- return {
19
- baseModel: model.substring(0, colonIdx),
20
- thinkingSuffix: model.substring(colonIdx),
21
- };
22
- }
23
-
24
- /** Sentinel model value requesting that a subagent inherit the parent session's model. */
25
- export const INHERIT_MODEL = "inherit";
26
-
27
- /** Minimal shape of the parent session's in-memory model (`ctx.model`). */
28
- export interface ParentModel {
29
- provider: string;
30
- id: string;
31
- }
32
-
33
- /**
34
- * Normalize a model id or provider segment for fuzzy comparison: case-fold,
35
- * treat dots/underscores as dashes (so `4.5` matches `4-5`), and collapse
36
- * repeated separators. Pure.
37
- */
38
- export function normalizeModelSegment(segment: string): string {
39
- return segment
40
- .toLowerCase()
41
- .replace(/[._]+/g, "-")
42
- .replace(/-+/g, "-")
43
- .replace(/^-|-$/g, "");
44
- }
45
-
46
- function isPlausibleDateStamp(year: string, month: string, day: string): boolean {
47
- const yyyy = Number(year);
48
- const mm = Number(month);
49
- const dd = Number(day);
50
- return yyyy >= 1900 && yyyy <= 2099 && mm >= 1 && mm <= 12 && dd >= 1 && dd <= 31;
51
- }
52
-
53
- /** Drop a trailing date stamp (`-20251001` or `-2025-10-01`) so dated and undated ids match. Pure. */
54
- function stripTrailingDateStamp(segment: string): string {
55
- const dashed = /^(.*)-(\d{4})-(\d{2})-(\d{2})$/.exec(segment);
56
- if (dashed && isPlausibleDateStamp(dashed[2]!, dashed[3]!, dashed[4]!)) return dashed[1]!;
57
- const compact = /^(.*)-(\d{4})(\d{2})(\d{2})$/.exec(segment);
58
- if (compact && isPlausibleDateStamp(compact[2]!, compact[3]!, compact[4]!)) return compact[1]!;
59
- return segment;
60
- }
61
-
62
- function resolveBaseModelCandidate(
63
- baseModel: string,
64
- availableModels: AvailableModelInfo[],
65
- preferredProvider?: string,
66
- ): string | undefined {
67
- if (baseModel.includes("/")) {
68
- const exact = availableModels.find((entry) => entry.fullId === baseModel);
69
- if (exact) return exact.fullId;
70
- } else {
71
- const exactMatches = availableModels.filter((entry) => entry.id === baseModel);
72
- if (preferredProvider) {
73
- const preferredMatch = exactMatches.find((entry) => entry.provider === preferredProvider);
74
- if (preferredMatch) return preferredMatch.fullId;
75
- }
76
- if (exactMatches.length === 1) return exactMatches[0]!.fullId;
77
- }
78
-
79
- return fuzzyResolveModel(baseModel, availableModels, preferredProvider);
80
- }
81
-
82
- /**
83
- * Fuzzy-resolve a base model id (thinking suffix already stripped) against the
84
- * registry, tolerating separator, case, and optional date-stamp differences so
85
- * users do not have to spell provider/model exactly. A qualified `provider/id`
86
- * query only matches within the named provider — this never silently switches
87
- * providers for security/cost-sensitive configs. Returns the matched `fullId`,
88
- * or `undefined` when there is no match or the match is ambiguous across
89
- * providers (and no `preferredProvider` disambiguates). Pure.
90
- */
91
- export function fuzzyResolveModel(
92
- baseModel: string,
93
- availableModels: AvailableModelInfo[],
94
- preferredProvider?: string,
95
- ): string | undefined {
96
- let queryProvider: string | undefined;
97
- let queryIdRaw = baseModel;
98
- const slashIdx = baseModel.indexOf("/");
99
- if (slashIdx !== -1) {
100
- queryProvider = normalizeModelSegment(baseModel.slice(0, slashIdx));
101
- queryIdRaw = baseModel.slice(slashIdx + 1);
102
- } else {
103
- const providerSeparators = [":", "."];
104
- for (const separator of providerSeparators) {
105
- const separatorIdx = baseModel.indexOf(separator);
106
- if (separatorIdx <= 0) continue;
107
- const providerPart = normalizeModelSegment(baseModel.slice(0, separatorIdx));
108
- if (!availableModels.some((entry) => normalizeModelSegment(entry.provider) === providerPart)) continue;
109
- queryProvider = providerPart;
110
- queryIdRaw = baseModel.slice(separatorIdx + 1);
111
- break;
112
- }
113
- }
114
- const queryId = normalizeModelSegment(queryIdRaw);
115
- const queryIdNoDate = stripTrailingDateStamp(queryId);
116
-
117
- const candidates = availableModels.filter((entry) => {
118
- const entryId = normalizeModelSegment(entry.id);
119
- if (entryId !== queryId && stripTrailingDateStamp(entryId) !== queryIdNoDate) return false;
120
- if (queryProvider !== undefined && normalizeModelSegment(entry.provider) !== queryProvider) return false;
121
- return true;
122
- });
123
- if (candidates.length === 0) return undefined;
124
- if (preferredProvider) {
125
- const preferredProviderNorm = normalizeModelSegment(preferredProvider);
126
- const preferred = candidates.find((entry) => normalizeModelSegment(entry.provider) === preferredProviderNorm);
127
- if (preferred) return preferred.fullId;
128
- }
129
- if (candidates.length === 1) return candidates[0]!.fullId;
130
- return undefined;
131
- }
132
-
133
- /**
134
- * Resolve a possibly-loose model id to a canonical `provider/id` (plus any
135
- * thinking suffix). Exact registry matches win; fuzzy normalization
136
- * (separator/case/date-stamp via {@link fuzzyResolveModel}) is a fallback so
137
- * spelling differences still resolve. Never switches providers for a qualified
138
- * query. Pure.
139
- */
140
- export function resolveModelCandidate(
141
- model: string | undefined,
142
- availableModels: AvailableModelInfo[] | undefined,
143
- preferredProvider?: string,
144
- ): string | undefined {
145
- if (!model) return undefined;
146
- if (!availableModels || availableModels.length === 0) return model;
147
-
148
- const resolvedWhole = resolveBaseModelCandidate(model, availableModels, preferredProvider);
149
- if (resolvedWhole) return resolvedWhole;
150
-
151
- const { baseModel, thinkingSuffix } = splitThinkingSuffix(model);
152
- if (!thinkingSuffix) return model;
153
- const resolvedBase = resolveBaseModelCandidate(baseModel, availableModels, preferredProvider);
154
- if (resolvedBase) return `${resolvedBase}${thinkingSuffix}`;
155
- return model;
156
- }
157
-
158
- export interface ResolveSubagentModelOverrideOptions {
159
- /** When set with `enforce: true`, out-of-scope models are rejected. */
160
- scope?: ModelScopeConfig;
161
- /** Origin of the requested model: explicit caller-supplied (hard error) vs inherited (warn). Defaults to `"inherited"`. */
162
- source?: ModelSource;
163
- /** Called for warn-severity violations instead of `console.warn`. */
164
- onWarn?: (violation: ModelScopeViolation) => void;
165
- }
166
-
167
- function defaultScopeWarn(violation: ModelScopeViolation): void {
168
- console.warn(`[dm-subagents] ${violation.message}`);
169
- }
170
-
171
- /**
172
- * Resolve the `--model` override passed to a spawned subagent.
173
- *
174
- * When no model is requested (`undefined`, `false`, empty, or the `"inherit"`
175
- * sentinel), the child must inherit the parent session's *in-memory* model
176
- * (`provider/id`) instead of being left to resolve its own model. Without an
177
- * explicit `provider/id`, the child falls back to the global
178
- * `~/.dm/agent/settings.json` default, which is shared across every open PI
179
- * session — so a different session that last changed its model in the TUI would
180
- * silently contaminate this session's subagents (see issue #266). Passing an
181
- * explicit `provider/id` keeps each session's children isolated to that
182
- * session's model.
183
- *
184
- * An explicitly requested model string is resolved via {@link resolveModelCandidate}.
185
- * When `options.scope.enforce` is on, an out-of-scope resolved model throws for
186
- * an explicit (`source: "explicit"`) request and warns for an inherited one.
187
- */
188
- export function resolveSubagentModelOverride(
189
- requestedModel: string | boolean | undefined,
190
- parentModel: ParentModel | undefined,
191
- availableModels: AvailableModelInfo[] | undefined,
192
- preferredProvider?: string,
193
- options?: ResolveSubagentModelOverrideOptions,
194
- ): string | undefined {
195
- const trimmed = typeof requestedModel === "string" ? requestedModel.trim() : "";
196
- const explicit = trimmed && trimmed !== INHERIT_MODEL ? trimmed : undefined;
197
- let resolved: string | undefined;
198
- if (explicit === undefined) {
199
- resolved = parentModel ? `${parentModel.provider}/${parentModel.id}` : undefined;
200
- } else {
201
- resolved = resolveModelCandidate(explicit, availableModels, preferredProvider);
202
- }
203
- if (resolved && options?.scope?.enforce) {
204
- const source: ModelSource = explicit === undefined ? "inherited" : (options.source ?? "inherited");
205
- const violation = checkModelScope(resolved, options.scope, source);
206
- if (violation) {
207
- if (violation.severity === "error") throw new Error(violation.message);
208
- (options.onWarn ?? defaultScopeWarn)(violation);
209
- }
210
- }
211
- return resolved;
212
- }
213
-
214
- export interface BuildModelCandidatesOptions {
215
- /** Fallback models are inherited agent config and warn, rather than error, when out of scope. */
216
- scope?: ModelScopeConfig;
217
- onWarn?: (violation: ModelScopeViolation) => void;
218
- }
219
-
220
- export function buildModelCandidates(
221
- primaryModel: string | undefined,
222
- fallbackModels: string[] | undefined,
223
- availableModels: AvailableModelInfo[] | undefined,
224
- preferredProvider?: string,
225
- options?: BuildModelCandidatesOptions,
226
- ): string[] {
227
- const seen = new Set<string>();
228
- const candidates: string[] = [];
229
- const rawCandidates = [primaryModel, ...(fallbackModels ?? [])];
230
- for (let index = 0; index < rawCandidates.length; index++) {
231
- const raw = rawCandidates[index];
232
- if (!raw) continue;
233
- const normalized = resolveModelCandidate(raw.trim(), availableModels, preferredProvider);
234
- if (!normalized || seen.has(normalized)) continue;
235
- if (index > 0 && options?.scope?.enforce) {
236
- const violation = checkModelScope(normalized, options.scope, "inherited");
237
- if (violation) (options.onWarn ?? defaultScopeWarn)(violation);
238
- }
239
- seen.add(normalized);
240
- candidates.push(normalized);
241
- }
242
- return candidates;
243
- }
244
-
245
- const RETRYABLE_MODEL_FAILURE_PATTERNS = [
246
- /rate\s*limit/i,
247
- /too many requests/i,
248
- /\b429\b/,
249
- /quota/i,
250
- /billing/i,
251
- /credit/i,
252
- /auth(?:entication)?/i,
253
- /unauthori[sz]ed/i,
254
- /forbidden/i,
255
- /api key/i,
256
- /token expired/i,
257
- /invalid key/i,
258
- /provider.*unavailable/i,
259
- /model.*unavailable/i,
260
- /model.*disabled/i,
261
- /model.*not found/i,
262
- /unknown model/i,
263
- /overloaded/i,
264
- /service unavailable/i,
265
- /temporar(?:ily)? unavailable/i,
266
- /connection refused/i,
267
- /fetch failed/i,
268
- /network error/i,
269
- /socket hang up/i,
270
- /upstream/i,
271
- /timed? out/i,
272
- /timeout/i,
273
- /\b502\b/,
274
- /\b503\b/,
275
- /\b504\b/,
276
- /cold.?start/i,
277
- /empty response/i,
278
- /no output/i,
279
- /model.*(?:load|fail|error)/i,
280
- ];
281
-
282
- export function isRetryableModelFailure(error: string | undefined): boolean {
283
- if (!error) return false;
284
- return RETRYABLE_MODEL_FAILURE_PATTERNS.some((pattern) => pattern.test(error));
285
- }
286
-
287
- export function formatModelAttemptNote(attempt: ModelAttemptSummary, nextModel?: string): string {
288
- const failure = attempt.error?.trim() || `exit ${attempt.exitCode ?? 1}`;
289
- return nextModel
290
- ? `[fallback] ${attempt.model} failed: ${failure}. Retrying with ${nextModel}.`
291
- : `[fallback] ${attempt.model} failed: ${failure}.`;
292
- }