@duckmind/dm-darwin-x64 0.43.4 → 0.48.5

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 (254) hide show
  1. package/dm +0 -0
  2. package/extensions/.dm-extensions.json +242 -50
  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 +51 -51
  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/research.mjs +59 -59
  132. package/extensions/greedysearch-dm/src/search/synthesis-runner.mjs +10 -10
  133. package/extensions/greedysearch-dm/src/tools/greedy-search-handler.js +20 -0
  134. package/extensions/greedysearch-dm/src/tools/shared.js +9 -0
  135. package/extensions/greedysearch-dm/src/types.js +0 -0
  136. package/extensions/greedysearch-dm/src/utils/helpers.js +1 -0
  137. package/package.json +1 -1
  138. package/extensions/dm-9router-ext/src/index.ts +0 -541
  139. package/extensions/dm-ask-user/index.ts +0 -857
  140. package/extensions/dm-context/src/context.ts +0 -158
  141. package/extensions/dm-context/src/index.ts +0 -439
  142. package/extensions/dm-context/src/utils.ts +0 -6
  143. package/extensions/dm-fff/src/index.ts +0 -1023
  144. package/extensions/dm-fff/src/query.ts +0 -87
  145. package/extensions/dm-goal/src/goal.ts +0 -1073
  146. package/extensions/dm-grill-me/index.ts +0 -1085
  147. package/extensions/dm-subagents/src/agents/agent-management.ts +0 -1052
  148. package/extensions/dm-subagents/src/agents/agent-memory.ts +0 -254
  149. package/extensions/dm-subagents/src/agents/agent-scope.ts +0 -6
  150. package/extensions/dm-subagents/src/agents/agent-selection.ts +0 -25
  151. package/extensions/dm-subagents/src/agents/agent-serializer.ts +0 -121
  152. package/extensions/dm-subagents/src/agents/agents.ts +0 -1553
  153. package/extensions/dm-subagents/src/agents/chain-serializer.ts +0 -277
  154. package/extensions/dm-subagents/src/agents/frontmatter.ts +0 -93
  155. package/extensions/dm-subagents/src/agents/identity.ts +0 -30
  156. package/extensions/dm-subagents/src/agents/proactive-skills.ts +0 -191
  157. package/extensions/dm-subagents/src/agents/skills.ts +0 -729
  158. package/extensions/dm-subagents/src/extension/config.ts +0 -39
  159. package/extensions/dm-subagents/src/extension/control-notices.ts +0 -92
  160. package/extensions/dm-subagents/src/extension/doctor.ts +0 -214
  161. package/extensions/dm-subagents/src/extension/fanout-child.ts +0 -172
  162. package/extensions/dm-subagents/src/extension/index.ts +0 -656
  163. package/extensions/dm-subagents/src/extension/rpc.ts +0 -369
  164. package/extensions/dm-subagents/src/extension/schemas.ts +0 -309
  165. package/extensions/dm-subagents/src/intercom/intercom-bridge.ts +0 -180
  166. package/extensions/dm-subagents/src/intercom/native-supervisor-channel.ts +0 -519
  167. package/extensions/dm-subagents/src/intercom/result-intercom.ts +0 -377
  168. package/extensions/dm-subagents/src/profiles/profiles.ts +0 -637
  169. package/extensions/dm-subagents/src/runs/background/async-execution.ts +0 -1065
  170. package/extensions/dm-subagents/src/runs/background/async-job-tracker.ts +0 -441
  171. package/extensions/dm-subagents/src/runs/background/async-resume.ts +0 -391
  172. package/extensions/dm-subagents/src/runs/background/async-status.ts +0 -395
  173. package/extensions/dm-subagents/src/runs/background/chain-append.ts +0 -282
  174. package/extensions/dm-subagents/src/runs/background/chain-root-attachment.ts +0 -191
  175. package/extensions/dm-subagents/src/runs/background/completion-batcher.ts +0 -166
  176. package/extensions/dm-subagents/src/runs/background/completion-dedupe.ts +0 -63
  177. package/extensions/dm-subagents/src/runs/background/control-channel.ts +0 -332
  178. package/extensions/dm-subagents/src/runs/background/fleet-view.ts +0 -515
  179. package/extensions/dm-subagents/src/runs/background/notify.ts +0 -225
  180. package/extensions/dm-subagents/src/runs/background/parallel-groups.ts +0 -45
  181. package/extensions/dm-subagents/src/runs/background/result-watcher.ts +0 -315
  182. package/extensions/dm-subagents/src/runs/background/run-id-resolver.ts +0 -84
  183. package/extensions/dm-subagents/src/runs/background/run-status.ts +0 -434
  184. package/extensions/dm-subagents/src/runs/background/scheduled-runs.ts +0 -514
  185. package/extensions/dm-subagents/src/runs/background/stale-run-reconciler.ts +0 -368
  186. package/extensions/dm-subagents/src/runs/background/subagent-runner.ts +0 -3171
  187. package/extensions/dm-subagents/src/runs/background/top-level-async.ts +0 -13
  188. package/extensions/dm-subagents/src/runs/background/wait.ts +0 -353
  189. package/extensions/dm-subagents/src/runs/foreground/chain-clarify.ts +0 -1333
  190. package/extensions/dm-subagents/src/runs/foreground/chain-execution.ts +0 -1313
  191. package/extensions/dm-subagents/src/runs/foreground/execution.ts +0 -1239
  192. package/extensions/dm-subagents/src/runs/foreground/subagent-executor.ts +0 -3613
  193. package/extensions/dm-subagents/src/runs/shared/acceptance.ts +0 -879
  194. package/extensions/dm-subagents/src/runs/shared/chain-outputs.ts +0 -116
  195. package/extensions/dm-subagents/src/runs/shared/completion-guard.ts +0 -147
  196. package/extensions/dm-subagents/src/runs/shared/dm-args.ts +0 -271
  197. package/extensions/dm-subagents/src/runs/shared/dm-spawn.ts +0 -147
  198. package/extensions/dm-subagents/src/runs/shared/dynamic-fanout.ts +0 -295
  199. package/extensions/dm-subagents/src/runs/shared/long-running-guard.ts +0 -175
  200. package/extensions/dm-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +0 -365
  201. package/extensions/dm-subagents/src/runs/shared/model-fallback.ts +0 -292
  202. package/extensions/dm-subagents/src/runs/shared/model-scope.ts +0 -128
  203. package/extensions/dm-subagents/src/runs/shared/nested-events.ts +0 -908
  204. package/extensions/dm-subagents/src/runs/shared/nested-path.ts +0 -52
  205. package/extensions/dm-subagents/src/runs/shared/nested-render.ts +0 -115
  206. package/extensions/dm-subagents/src/runs/shared/parallel-utils.ts +0 -198
  207. package/extensions/dm-subagents/src/runs/shared/run-history.ts +0 -60
  208. package/extensions/dm-subagents/src/runs/shared/single-output.ts +0 -180
  209. package/extensions/dm-subagents/src/runs/shared/structured-output.ts +0 -77
  210. package/extensions/dm-subagents/src/runs/shared/subagent-control.ts +0 -223
  211. package/extensions/dm-subagents/src/runs/shared/subagent-prompt-runtime.ts +0 -342
  212. package/extensions/dm-subagents/src/runs/shared/tool-budget.ts +0 -74
  213. package/extensions/dm-subagents/src/runs/shared/turn-budget.ts +0 -52
  214. package/extensions/dm-subagents/src/runs/shared/workflow-graph.ts +0 -206
  215. package/extensions/dm-subagents/src/runs/shared/worktree.ts +0 -600
  216. package/extensions/dm-subagents/src/shared/artifacts.ts +0 -113
  217. package/extensions/dm-subagents/src/shared/atomic-json.ts +0 -86
  218. package/extensions/dm-subagents/src/shared/child-transcript.ts +0 -212
  219. package/extensions/dm-subagents/src/shared/file-coalescer.ts +0 -40
  220. package/extensions/dm-subagents/src/shared/fork-context.ts +0 -194
  221. package/extensions/dm-subagents/src/shared/formatters.ts +0 -133
  222. package/extensions/dm-subagents/src/shared/jsonl-writer.ts +0 -81
  223. package/extensions/dm-subagents/src/shared/model-info.ts +0 -78
  224. package/extensions/dm-subagents/src/shared/post-exit-stdio-guard.ts +0 -85
  225. package/extensions/dm-subagents/src/shared/session-identity.ts +0 -10
  226. package/extensions/dm-subagents/src/shared/session-tokens.ts +0 -44
  227. package/extensions/dm-subagents/src/shared/settings.ts +0 -450
  228. package/extensions/dm-subagents/src/shared/status-format.ts +0 -49
  229. package/extensions/dm-subagents/src/shared/types.ts +0 -1257
  230. package/extensions/dm-subagents/src/shared/utils.ts +0 -554
  231. package/extensions/dm-subagents/src/slash/prompt-template-bridge.ts +0 -420
  232. package/extensions/dm-subagents/src/slash/prompt-workflows.ts +0 -330
  233. package/extensions/dm-subagents/src/slash/slash-bridge.ts +0 -176
  234. package/extensions/dm-subagents/src/slash/slash-commands.ts +0 -1296
  235. package/extensions/dm-subagents/src/slash/slash-live-state.ts +0 -292
  236. package/extensions/dm-subagents/src/tui/render-helpers.ts +0 -80
  237. package/extensions/dm-subagents/src/tui/render.ts +0 -1748
  238. package/extensions/dm-tasks/src/auto-clear.ts +0 -91
  239. package/extensions/dm-tasks/src/index.ts +0 -1145
  240. package/extensions/dm-tasks/src/process-tracker.ts +0 -140
  241. package/extensions/dm-tasks/src/task-store.ts +0 -305
  242. package/extensions/dm-tasks/src/tasks-config.ts +0 -23
  243. package/extensions/dm-tasks/src/types.ts +0 -40
  244. package/extensions/dm-tasks/src/ui/settings-menu.ts +0 -100
  245. package/extensions/dm-tasks/src/ui/task-widget.ts +0 -265
  246. package/extensions/dm-usage/index.ts +0 -1718
  247. package/extensions/greedysearch-dm/index.ts +0 -177
  248. package/extensions/greedysearch-dm/src/formatters/results.ts +0 -163
  249. package/extensions/greedysearch-dm/src/formatters/sources.ts +0 -116
  250. package/extensions/greedysearch-dm/src/formatters/synthesis.ts +0 -87
  251. package/extensions/greedysearch-dm/src/tools/greedy-search-handler.ts +0 -370
  252. package/extensions/greedysearch-dm/src/tools/shared.ts +0 -187
  253. package/extensions/greedysearch-dm/src/types.ts +0 -110
  254. 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
- }