@wrongstack/core 0.287.0 → 0.291.0

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 (339) hide show
  1. package/dist/agent-status-tracker.d.ts +13 -0
  2. package/dist/agent-status-tracker.d.ts.map +1 -1
  3. package/dist/boot.d.ts.map +1 -1
  4. package/dist/chronicle/context.d.ts +13 -0
  5. package/dist/chronicle/context.d.ts.map +1 -0
  6. package/dist/chronicle/decision-adapter.d.ts +13 -0
  7. package/dist/chronicle/decision-adapter.d.ts.map +1 -0
  8. package/dist/chronicle/domain-adapter.d.ts +13 -0
  9. package/dist/chronicle/domain-adapter.d.ts.map +1 -0
  10. package/dist/chronicle/file-observer.d.ts +20 -0
  11. package/dist/chronicle/file-observer.d.ts.map +1 -0
  12. package/dist/chronicle/health-monitor.d.ts +11 -0
  13. package/dist/chronicle/health-monitor.d.ts.map +1 -0
  14. package/dist/chronicle/identity.d.ts +18 -0
  15. package/dist/chronicle/identity.d.ts.map +1 -0
  16. package/dist/chronicle/index.d.ts +16 -0
  17. package/dist/chronicle/index.d.ts.map +1 -0
  18. package/dist/chronicle/index.js +2555 -0
  19. package/dist/chronicle/index.js.map +7 -0
  20. package/dist/chronicle/journal.d.ts +81 -0
  21. package/dist/chronicle/journal.d.ts.map +1 -0
  22. package/dist/chronicle/process-adapter.d.ts +14 -0
  23. package/dist/chronicle/process-adapter.d.ts.map +1 -0
  24. package/dist/chronicle/prompt-manifest.d.ts +34 -0
  25. package/dist/chronicle/prompt-manifest.d.ts.map +1 -0
  26. package/dist/chronicle/provider-adapter.d.ts +13 -0
  27. package/dist/chronicle/provider-adapter.d.ts.map +1 -0
  28. package/dist/chronicle/query.d.ts +108 -0
  29. package/dist/chronicle/query.d.ts.map +1 -0
  30. package/dist/chronicle/rollup-adapter.d.ts +14 -0
  31. package/dist/chronicle/rollup-adapter.d.ts.map +1 -0
  32. package/dist/chronicle/stream-adapter.d.ts +13 -0
  33. package/dist/chronicle/stream-adapter.d.ts.map +1 -0
  34. package/dist/chronicle/tool-adapter.d.ts +15 -0
  35. package/dist/chronicle/tool-adapter.d.ts.map +1 -0
  36. package/dist/chronicle/types.d.ts +82 -0
  37. package/dist/chronicle/types.d.ts.map +1 -0
  38. package/dist/coordination/agent-monitor.d.ts +40 -0
  39. package/dist/coordination/agent-monitor.d.ts.map +1 -1
  40. package/dist/coordination/agent-subagent-runner.d.ts +7 -1
  41. package/dist/coordination/agent-subagent-runner.d.ts.map +1 -1
  42. package/dist/coordination/agents/phase6-domain.d.ts.map +1 -1
  43. package/dist/coordination/autonomous-coordinator.d.ts +2 -0
  44. package/dist/coordination/autonomous-coordinator.d.ts.map +1 -1
  45. package/dist/coordination/brain-monitor.d.ts +1 -1
  46. package/dist/coordination/brain.d.ts +3 -2
  47. package/dist/coordination/brain.d.ts.map +1 -1
  48. package/dist/coordination/collab-debug.d.ts +0 -9
  49. package/dist/coordination/collab-debug.d.ts.map +1 -1
  50. package/dist/coordination/coordinator/error-classifier.d.ts.map +1 -1
  51. package/dist/coordination/dep-watcher.d.ts +2 -1
  52. package/dist/coordination/dep-watcher.d.ts.map +1 -1
  53. package/dist/coordination/director/director-collab.d.ts +1 -1
  54. package/dist/coordination/director/director-collab.d.ts.map +1 -1
  55. package/dist/coordination/director-tools.d.ts.map +1 -1
  56. package/dist/coordination/director.d.ts +10 -1
  57. package/dist/coordination/director.d.ts.map +1 -1
  58. package/dist/coordination/file-author-tracker.d.ts.map +1 -1
  59. package/dist/coordination/fleet-bus.d.ts.map +1 -1
  60. package/dist/coordination/fleet-event-validation.d.ts +0 -7
  61. package/dist/coordination/fleet-event-validation.d.ts.map +1 -1
  62. package/dist/coordination/fleet-spawn.d.ts.map +1 -1
  63. package/dist/coordination/fleet-supervisor.d.ts +1 -1
  64. package/dist/coordination/fleet-supervisor.d.ts.map +1 -1
  65. package/dist/coordination/fleet.d.ts +0 -44
  66. package/dist/coordination/fleet.d.ts.map +1 -1
  67. package/dist/coordination/global-mailbox.d.ts +7 -1
  68. package/dist/coordination/global-mailbox.d.ts.map +1 -1
  69. package/dist/coordination/index.d.ts +5 -3
  70. package/dist/coordination/index.d.ts.map +1 -1
  71. package/dist/coordination/index.js +2426 -299
  72. package/dist/coordination/index.js.map +4 -4
  73. package/dist/coordination/knowledge-graph.d.ts +2 -1
  74. package/dist/coordination/knowledge-graph.d.ts.map +1 -1
  75. package/dist/coordination/large-answer-store.d.ts +0 -6
  76. package/dist/coordination/large-answer-store.d.ts.map +1 -1
  77. package/dist/coordination/mail-tools.d.ts +2 -2
  78. package/dist/coordination/mail-tools.d.ts.map +1 -1
  79. package/dist/coordination/mailbox-hooks.d.ts +2 -0
  80. package/dist/coordination/mailbox-hooks.d.ts.map +1 -1
  81. package/dist/coordination/mailbox-http-router.d.ts +43 -0
  82. package/dist/coordination/mailbox-http-router.d.ts.map +1 -0
  83. package/dist/coordination/mailbox-message-codec.d.ts +35 -0
  84. package/dist/coordination/mailbox-message-codec.d.ts.map +1 -1
  85. package/dist/coordination/mailbox-tool.d.ts +1 -1
  86. package/dist/coordination/mailbox-tool.d.ts.map +1 -1
  87. package/dist/coordination/mailbox-types.d.ts +170 -10
  88. package/dist/coordination/mailbox-types.d.ts.map +1 -1
  89. package/dist/coordination/mailbox.d.ts +1 -1
  90. package/dist/coordination/mailbox.d.ts.map +1 -1
  91. package/dist/coordination/package-author-tracker.d.ts.map +1 -1
  92. package/dist/coordination/provider-status-tracker.d.ts +236 -0
  93. package/dist/coordination/provider-status-tracker.d.ts.map +1 -0
  94. package/dist/coordination/subagent-budget.d.ts +2 -1
  95. package/dist/coordination/subagent-budget.d.ts.map +1 -1
  96. package/dist/core/agent-loop.d.ts +8 -3
  97. package/dist/core/agent-loop.d.ts.map +1 -1
  98. package/dist/core/agent-response.d.ts +5 -4
  99. package/dist/core/agent-response.d.ts.map +1 -1
  100. package/dist/core/agent-tools.d.ts +1 -1
  101. package/dist/core/agent-tools.d.ts.map +1 -1
  102. package/dist/core/agent.d.ts +4 -2
  103. package/dist/core/agent.d.ts.map +1 -1
  104. package/dist/core/context.d.ts +55 -1
  105. package/dist/core/context.d.ts.map +1 -1
  106. package/dist/core/fallback-model.d.ts +14 -8
  107. package/dist/core/fallback-model.d.ts.map +1 -1
  108. package/dist/core/fallback-profile-manager.d.ts +118 -0
  109. package/dist/core/fallback-profile-manager.d.ts.map +1 -0
  110. package/dist/core/fleet-pulse.d.ts +2 -1
  111. package/dist/core/fleet-pulse.d.ts.map +1 -1
  112. package/dist/core/iteration-limit.d.ts +2 -1
  113. package/dist/core/iteration-limit.d.ts.map +1 -1
  114. package/dist/core/mailbox-loop.d.ts +16 -1
  115. package/dist/core/mailbox-loop.d.ts.map +1 -1
  116. package/dist/core/model-availability-calendar.d.ts +27 -0
  117. package/dist/core/model-availability-calendar.d.ts.map +1 -0
  118. package/dist/core/provider-runner.d.ts.map +1 -1
  119. package/dist/core/system-prompt-builder.d.ts +2 -1
  120. package/dist/core/system-prompt-builder.d.ts.map +1 -1
  121. package/dist/defaults/index.js +1920 -598
  122. package/dist/defaults/index.js.map +4 -4
  123. package/dist/execution/auto-compaction-middleware.d.ts +3 -2
  124. package/dist/execution/auto-compaction-middleware.d.ts.map +1 -1
  125. package/dist/execution/compaction-core.d.ts.map +1 -1
  126. package/dist/execution/council-brain.d.ts +28 -49
  127. package/dist/execution/council-brain.d.ts.map +1 -1
  128. package/dist/execution/council-orchestrator.d.ts +30 -0
  129. package/dist/execution/council-orchestrator.d.ts.map +1 -1
  130. package/dist/execution/enhance-recovery.d.ts +8 -0
  131. package/dist/execution/enhance-recovery.d.ts.map +1 -1
  132. package/dist/execution/error-handler.d.ts +2 -2
  133. package/dist/execution/error-handler.d.ts.map +1 -1
  134. package/dist/execution/eternal-autonomy.d.ts.map +1 -1
  135. package/dist/execution/index.js +2600 -1903
  136. package/dist/execution/index.js.map +4 -4
  137. package/dist/execution/intelligent-compactor.d.ts +0 -16
  138. package/dist/execution/intelligent-compactor.d.ts.map +1 -1
  139. package/dist/execution/model-runtime.d.ts.map +1 -1
  140. package/dist/execution/one-shot-llm.d.ts +9 -1
  141. package/dist/execution/one-shot-llm.d.ts.map +1 -1
  142. package/dist/execution/prompt-enhancer.d.ts +36 -0
  143. package/dist/execution/prompt-enhancer.d.ts.map +1 -1
  144. package/dist/execution/prompt-enhancer.js +158 -10
  145. package/dist/execution/prompt-enhancer.js.map +2 -2
  146. package/dist/execution/retry-policy.d.ts +0 -13
  147. package/dist/execution/retry-policy.d.ts.map +1 -1
  148. package/dist/execution/skill-loader.d.ts.map +1 -1
  149. package/dist/execution/tool-executor.d.ts.map +1 -1
  150. package/dist/goal/checkpoint.d.ts.map +1 -0
  151. package/dist/{autophase/auto-phase-planner.d.ts → goal/goal-planner.d.ts} +8 -8
  152. package/dist/goal/goal-planner.d.ts.map +1 -0
  153. package/dist/{autophase/auto-phase-runner.d.ts → goal/goal-runner.d.ts} +10 -10
  154. package/dist/goal/goal-runner.d.ts.map +1 -0
  155. package/dist/{autophase → goal}/index.d.ts +3 -3
  156. package/dist/goal/index.d.ts.map +1 -0
  157. package/dist/goal/phase-graph-builder.d.ts.map +1 -0
  158. package/dist/{autophase → goal}/phase-orchestrator.d.ts +2 -2
  159. package/dist/goal/phase-orchestrator.d.ts.map +1 -0
  160. package/dist/goal/phase-store.d.ts.map +1 -0
  161. package/dist/{autophase → goal}/types.d.ts +2 -2
  162. package/dist/goal/types.d.ts.map +1 -0
  163. package/dist/hq/auth-store.d.ts.map +1 -1
  164. package/dist/hq/commands.d.ts +2 -1
  165. package/dist/hq/commands.d.ts.map +1 -1
  166. package/dist/hq/exposure.d.ts +66 -0
  167. package/dist/hq/exposure.d.ts.map +1 -0
  168. package/dist/hq/index.d.ts +1 -0
  169. package/dist/hq/index.d.ts.map +1 -1
  170. package/dist/hq/index.js +190 -21
  171. package/dist/hq/index.js.map +4 -4
  172. package/dist/hq/mailbox-mapper.d.ts +13 -1
  173. package/dist/hq/mailbox-mapper.d.ts.map +1 -1
  174. package/dist/hq/protocol/browser.d.ts +9 -1
  175. package/dist/hq/protocol/browser.d.ts.map +1 -1
  176. package/dist/hq/protocol/client.d.ts +2 -2
  177. package/dist/hq/protocol/client.d.ts.map +1 -1
  178. package/dist/hq/protocol/core.d.ts.map +1 -1
  179. package/dist/hq/protocol/mailbox.d.ts +6 -0
  180. package/dist/hq/protocol/mailbox.d.ts.map +1 -1
  181. package/dist/hq/protocol/session.d.ts +4 -0
  182. package/dist/hq/protocol/session.d.ts.map +1 -1
  183. package/dist/hq/publisher.d.ts.map +1 -1
  184. package/dist/index.d.ts +34 -25
  185. package/dist/index.d.ts.map +1 -1
  186. package/dist/index.js +30287 -22214
  187. package/dist/index.js.map +4 -4
  188. package/dist/infrastructure/index.js +60 -5
  189. package/dist/infrastructure/index.js.map +2 -2
  190. package/dist/kernel/events/agent-events.d.ts +33 -1
  191. package/dist/kernel/events/agent-events.d.ts.map +1 -1
  192. package/dist/kernel/events/file-events.d.ts +65 -0
  193. package/dist/kernel/events/file-events.d.ts.map +1 -0
  194. package/dist/kernel/events/memory-events.d.ts +36 -0
  195. package/dist/kernel/events/memory-events.d.ts.map +1 -1
  196. package/dist/kernel/events/network-events.d.ts +60 -0
  197. package/dist/kernel/events/network-events.d.ts.map +1 -0
  198. package/dist/kernel/events/process-events.d.ts +44 -0
  199. package/dist/kernel/events/process-events.d.ts.map +1 -0
  200. package/dist/kernel/events/provider-events.d.ts +89 -0
  201. package/dist/kernel/events/provider-events.d.ts.map +1 -1
  202. package/dist/kernel/events/tool-events.d.ts +10 -0
  203. package/dist/kernel/events/tool-events.d.ts.map +1 -1
  204. package/dist/kernel/events/worktree-events.d.ts +1 -1
  205. package/dist/kernel/events.d.ts +14 -1
  206. package/dist/kernel/events.d.ts.map +1 -1
  207. package/dist/kernel/index.js +2 -1
  208. package/dist/kernel/index.js.map +2 -2
  209. package/dist/kernel/tokens.d.ts +3 -1
  210. package/dist/kernel/tokens.d.ts.map +1 -1
  211. package/dist/mailbox-attach.d.ts.map +1 -1
  212. package/dist/models/index.js.map +2 -2
  213. package/dist/observability/index.d.ts +3 -0
  214. package/dist/observability/index.d.ts.map +1 -1
  215. package/dist/observability/index.js +278 -0
  216. package/dist/observability/index.js.map +4 -4
  217. package/dist/observability/network-telemetry.d.ts +15 -0
  218. package/dist/observability/network-telemetry.d.ts.map +1 -0
  219. package/dist/observability/process-telemetry.d.ts +18 -0
  220. package/dist/observability/process-telemetry.d.ts.map +1 -0
  221. package/dist/observability/redact-command.d.ts +20 -0
  222. package/dist/observability/redact-command.d.ts.map +1 -0
  223. package/dist/plugins/auto-review-plugin.d.ts +56 -3
  224. package/dist/plugins/auto-review-plugin.d.ts.map +1 -1
  225. package/dist/plugins/chimera-plugin.d.ts +164 -5
  226. package/dist/plugins/chimera-plugin.d.ts.map +1 -1
  227. package/dist/plugins/review-context-builder.d.ts +52 -0
  228. package/dist/plugins/review-context-builder.d.ts.map +1 -0
  229. package/dist/security/index.d.ts +1 -0
  230. package/dist/security/index.d.ts.map +1 -1
  231. package/dist/security/index.js +186 -9
  232. package/dist/security/index.js.map +4 -4
  233. package/dist/security/kanban-boundary.d.ts +10 -0
  234. package/dist/security/kanban-boundary.d.ts.map +1 -0
  235. package/dist/security/permission-policy.d.ts +1 -0
  236. package/dist/security/permission-policy.d.ts.map +1 -1
  237. package/dist/session-registry.d.ts +80 -0
  238. package/dist/session-registry.d.ts.map +1 -1
  239. package/dist/skills/index.js.map +2 -2
  240. package/dist/storage/config-loader.d.ts +7 -0
  241. package/dist/storage/config-loader.d.ts.map +1 -1
  242. package/dist/storage/file-session-writer.d.ts +20 -7
  243. package/dist/storage/file-session-writer.d.ts.map +1 -1
  244. package/dist/storage/goal-coordination.d.ts +40 -0
  245. package/dist/storage/goal-coordination.d.ts.map +1 -0
  246. package/dist/storage/goal-store.d.ts +9 -5
  247. package/dist/storage/goal-store.d.ts.map +1 -1
  248. package/dist/storage/index.d.ts +4 -2
  249. package/dist/storage/index.d.ts.map +1 -1
  250. package/dist/storage/index.js +1066 -226
  251. package/dist/storage/index.js.map +4 -4
  252. package/dist/storage/memory-consolidator.d.ts +49 -4
  253. package/dist/storage/memory-consolidator.d.ts.map +1 -1
  254. package/dist/storage/memory-graph-backend.d.ts +11 -0
  255. package/dist/storage/memory-graph-backend.d.ts.map +1 -1
  256. package/dist/storage/provider-config-watcher.d.ts +19 -0
  257. package/dist/storage/provider-config-watcher.d.ts.map +1 -1
  258. package/dist/storage/session-event-bridge.d.ts +2 -2
  259. package/dist/storage/session-event-bridge.d.ts.map +1 -1
  260. package/dist/storage/session-resume-validation.d.ts +10 -0
  261. package/dist/storage/session-resume-validation.d.ts.map +1 -1
  262. package/dist/storage/session-rewind-apply.d.ts +53 -0
  263. package/dist/storage/session-rewind-apply.d.ts.map +1 -0
  264. package/dist/storage/session-rewinder.d.ts.map +1 -1
  265. package/dist/storage/session-store.d.ts.map +1 -1
  266. package/dist/tasking/index.js.map +1 -1
  267. package/dist/tasking/task-store.d.ts +1 -1
  268. package/dist/tools/council-tool.d.ts +3 -0
  269. package/dist/tools/council-tool.d.ts.map +1 -1
  270. package/dist/tools/fallback-manage-tools.d.ts +82 -0
  271. package/dist/tools/fallback-manage-tools.d.ts.map +1 -0
  272. package/dist/tools/index.d.ts +1 -0
  273. package/dist/tools/index.d.ts.map +1 -1
  274. package/dist/tools/index.js +3423 -192
  275. package/dist/tools/index.js.map +4 -4
  276. package/dist/tools/one-shot-llm-tool.d.ts +2 -0
  277. package/dist/tools/one-shot-llm-tool.d.ts.map +1 -1
  278. package/dist/types/config.d.ts +76 -7
  279. package/dist/types/config.d.ts.map +1 -1
  280. package/dist/types/file-event-record.d.ts +68 -0
  281. package/dist/types/file-event-record.d.ts.map +1 -0
  282. package/dist/types/index.d.ts +1 -0
  283. package/dist/types/index.d.ts.map +1 -1
  284. package/dist/types/index.js +13 -8
  285. package/dist/types/index.js.map +3 -3
  286. package/dist/types/memory.d.ts +12 -0
  287. package/dist/types/memory.d.ts.map +1 -1
  288. package/dist/types/multi-agent.d.ts +12 -1
  289. package/dist/types/multi-agent.d.ts.map +1 -1
  290. package/dist/types/one-shot-llm.d.ts +16 -7
  291. package/dist/types/one-shot-llm.d.ts.map +1 -1
  292. package/dist/types/provider.d.ts +1 -1
  293. package/dist/types/provider.d.ts.map +1 -1
  294. package/dist/types/session.d.ts +68 -2
  295. package/dist/types/session.d.ts.map +1 -1
  296. package/dist/types/tool-executor.d.ts +2 -0
  297. package/dist/types/tool-executor.d.ts.map +1 -1
  298. package/dist/types/tool.d.ts +5 -0
  299. package/dist/types/tool.d.ts.map +1 -1
  300. package/dist/utils/atomic-write.d.ts.map +1 -1
  301. package/dist/utils/index.d.ts +10 -10
  302. package/dist/utils/index.d.ts.map +1 -1
  303. package/dist/utils/index.js +497 -254
  304. package/dist/utils/index.js.map +4 -4
  305. package/dist/utils/message-invariants.d.ts +9 -0
  306. package/dist/utils/message-invariants.d.ts.map +1 -1
  307. package/dist/utils/safe-json.d.ts +1 -1
  308. package/dist/utils/safe-json.d.ts.map +1 -1
  309. package/dist/utils/term.d.ts +279 -0
  310. package/dist/utils/term.d.ts.map +1 -1
  311. package/dist/utils/tool-wire-compact.d.ts +9 -0
  312. package/dist/utils/tool-wire-compact.d.ts.map +1 -1
  313. package/dist/utils/wstack-paths.d.ts +12 -2
  314. package/dist/utils/wstack-paths.d.ts.map +1 -1
  315. package/dist/worktree/worktree-manager.d.ts +1 -1
  316. package/dist/worktree/worktree-manager.d.ts.map +1 -1
  317. package/instructions/agents/ios.md +113 -0
  318. package/instructions/agents/search.md +1 -1
  319. package/instructions/llm/chimera-review.md +66 -9
  320. package/instructions/llm/memory-consolidator.md +4 -12
  321. package/instructions/llm/prompt-enhancer.md +3 -1
  322. package/instructions/sections/tool/common-patterns.md +3 -3
  323. package/instructions/system.md +40 -5
  324. package/package.json +7 -3
  325. package/skills/auto-review/SKILL.md +176 -0
  326. package/skills/mnemosyne/SKILL.md +156 -0
  327. package/skills/mnemosyne/instructions/agent-prompt.md +200 -0
  328. package/dist/autophase/auto-phase-planner.d.ts.map +0 -1
  329. package/dist/autophase/auto-phase-runner.d.ts.map +0 -1
  330. package/dist/autophase/checkpoint.d.ts.map +0 -1
  331. package/dist/autophase/index.d.ts.map +0 -1
  332. package/dist/autophase/phase-graph-builder.d.ts.map +0 -1
  333. package/dist/autophase/phase-orchestrator.d.ts.map +0 -1
  334. package/dist/autophase/phase-store.d.ts.map +0 -1
  335. package/dist/autophase/types.d.ts.map +0 -1
  336. /package/dist/{autophase → goal}/checkpoint.d.ts +0 -0
  337. /package/dist/{autophase → goal}/phase-graph-builder.d.ts +0 -0
  338. /package/dist/{autophase → goal}/phase-store.d.ts +0 -0
  339. /package/instructions/{autophase → goal}/phase-planner.md +0 -0
@@ -412,7 +412,7 @@ async function renameWithRetry(from, to) {
412
412
  if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
413
413
  throw err;
414
414
  }
415
- await new Promise((resolve2) => setTimeout(resolve2, delays[i]));
415
+ await new Promise((resolve3) => setTimeout(resolve3, delays[i]));
416
416
  }
417
417
  }
418
418
  throw lastErr;
@@ -437,13 +437,13 @@ async function updateJsonObjectFile(filePath, mutator) {
437
437
  await writeJsonObjectFile(filePath, next);
438
438
  return next;
439
439
  }
440
- function setJsonPath(root, path3, value) {
441
- if (path3.length === 0) {
440
+ function setJsonPath(root, path4, value) {
441
+ if (path4.length === 0) {
442
442
  if (!isJsonObject(value)) throw new Error("Root config value must be an object");
443
443
  return value;
444
444
  }
445
- const parent = ensureJsonParent(root, path3);
446
- const leaf = lastPathSegment(path3);
445
+ const parent = ensureJsonParent(root, path4);
446
+ const leaf = lastPathSegment(path4);
447
447
  if (typeof leaf === "number") {
448
448
  if (!Array.isArray(parent)) throw new Error(`Cannot set numeric segment ${leaf} on non-array parent`);
449
449
  parent[leaf] = value;
@@ -456,16 +456,16 @@ function setJsonPath(root, path3, value) {
456
456
  function isJsonObject(value) {
457
457
  return typeof value === "object" && value !== null && !Array.isArray(value);
458
458
  }
459
- function lastPathSegment(path3) {
460
- const segment = path3[path3.length - 1];
459
+ function lastPathSegment(path4) {
460
+ const segment = path4[path4.length - 1];
461
461
  if (segment === void 0) throw new Error("Invalid empty JSON path");
462
462
  return segment;
463
463
  }
464
- function ensureJsonParent(root, path3) {
464
+ function ensureJsonParent(root, path4) {
465
465
  let current = root;
466
- for (let i = 0; i < path3.length - 1; i += 1) {
467
- const segment = path3[i];
468
- const nextSegment = path3[i + 1];
466
+ for (let i = 0; i < path4.length - 1; i += 1) {
467
+ const segment = path4[i];
468
+ const nextSegment = path4[i + 1];
469
469
  if (segment === void 0) throw new Error("Invalid empty JSON path segment");
470
470
  const nextContainer = typeof nextSegment === "number" ? [] : {};
471
471
  if (typeof segment === "number") {
@@ -1219,9 +1219,9 @@ function normalizeOptions(options) {
1219
1219
  };
1220
1220
  });
1221
1221
  }
1222
- function requiredInstruction(path3) {
1223
- const text = readBundledInstructionText(path3);
1224
- if (!text) throw new Error(`Council instruction file is unavailable: ${path3}`);
1222
+ function requiredInstruction(path4) {
1223
+ const text = readBundledInstructionText(path4);
1224
+ if (!text) throw new Error(`Council instruction file is unavailable: ${path4}`);
1225
1225
  return text;
1226
1226
  }
1227
1227
 
@@ -1332,6 +1332,9 @@ var CouncilOrchestrator = class {
1332
1332
  defaultProfile;
1333
1333
  maxConcurrency;
1334
1334
  refusalOptionId;
1335
+ fallbackProfileManager;
1336
+ seatCaller;
1337
+ judgeCaller;
1335
1338
  constructor(opts) {
1336
1339
  this.caller = opts.caller;
1337
1340
  this.personas = opts.personas ?? DEFAULT_COUNCIL_PERSONA_REGISTRY;
@@ -1341,6 +1344,9 @@ var CouncilOrchestrator = class {
1341
1344
  opts.maxConcurrency ?? DEFAULT_COUNCIL_MAX_CONCURRENCY
1342
1345
  );
1343
1346
  this.refusalOptionId = opts.refusalOptionId?.trim() || COUNCIL_REFUSAL_OPTION_ID;
1347
+ this.fallbackProfileManager = opts.fallbackProfileManager;
1348
+ this.seatCaller = opts.seatCaller;
1349
+ this.judgeCaller = opts.judgeCaller;
1344
1350
  }
1345
1351
  async ask(question) {
1346
1352
  const startedAt = Date.now();
@@ -1361,9 +1367,9 @@ var CouncilOrchestrator = class {
1361
1367
  const votes = await mapConcurrent(
1362
1368
  profile.seats,
1363
1369
  Math.min(this.maxConcurrency, profile.seats.length),
1364
- async (seat) => {
1370
+ async (seat, i) => {
1365
1371
  try {
1366
- return await this.callSeat(question, profile, seat, signal, usage);
1372
+ return await this.callSeat(question, profile, seat, i, signal, usage);
1367
1373
  } catch (error) {
1368
1374
  return {
1369
1375
  seatId: seat.id,
@@ -1425,7 +1431,7 @@ var CouncilOrchestrator = class {
1425
1431
  errors
1426
1432
  );
1427
1433
  }
1428
- async callSeat(question, profile, seat, signal, usage) {
1434
+ async callSeat(question, profile, seat, seatIndex, signal, usage) {
1429
1435
  if (signal.aborted) return cancelledVote(seat);
1430
1436
  let persona;
1431
1437
  try {
@@ -1447,7 +1453,8 @@ var CouncilOrchestrator = class {
1447
1453
  maxTokens: profile.voterMaxTokens,
1448
1454
  timeoutMs: profile.perCallTimeoutMs,
1449
1455
  signal,
1450
- usage
1456
+ usage,
1457
+ seatIndex
1451
1458
  });
1452
1459
  const metadata = callMetadata(result);
1453
1460
  if (result.error) {
@@ -1698,19 +1705,20 @@ var CouncilOrchestrator = class {
1698
1705
  return parseJudge(result.text, question, this.refusalOptionId);
1699
1706
  }
1700
1707
  async safeCall(input) {
1708
+ const effectiveCaller = input.seatIndex !== void 0 && this.seatCaller ? this.seatCaller(input.seatIndex) : this.judgeCaller ?? (this.seatCaller ? this.seatCaller(0) : this.caller);
1709
+ const resolvedTarget = this.resolveCouncilTarget(input.target);
1701
1710
  try {
1702
- const result = await this.caller.call({
1711
+ const result = await effectiveCaller.call({
1703
1712
  system: input.system,
1704
1713
  userPrompt: input.userPrompt,
1705
1714
  responseFormat: { type: "json_object" },
1706
1715
  maxTokens: input.maxTokens,
1707
1716
  timeoutMs: input.timeoutMs,
1708
1717
  signal: input.signal,
1709
- ...input.target?.providerId ? { providerId: input.target.providerId } : {},
1710
- ...input.target?.model ? { model: input.target.model } : {},
1711
- ...input.target?.role ? { role: input.target.role } : {},
1712
- ...input.target?.fallbackProfile ? { fallbackProfile: input.target.fallbackProfile } : {},
1713
- ...input.target?.fallbackModels ? { fallbackModels: [...input.target.fallbackModels] } : {}
1718
+ ...resolvedTarget?.providerId ? { providerId: resolvedTarget.providerId } : {},
1719
+ ...resolvedTarget?.model ? { model: resolvedTarget.model } : {},
1720
+ ...resolvedTarget?.role ? { role: resolvedTarget.role } : {},
1721
+ ...resolvedTarget?.fallbackModels && resolvedTarget.fallbackModels.length > 0 ? { fallbackModels: [...resolvedTarget.fallbackModels] } : {}
1714
1722
  });
1715
1723
  addUsage(input.usage, result);
1716
1724
  return result;
@@ -1719,9 +1727,42 @@ var CouncilOrchestrator = class {
1719
1727
  return emptyCallResult(errorMessage(error));
1720
1728
  }
1721
1729
  }
1730
+ /**
1731
+ * Resolve a CouncilModelTarget: pre-resolve fallbackProfile to fallbackModels
1732
+ * so the downstream caller only sees the resolved chain.
1733
+ */
1734
+ resolveCouncilTarget(target) {
1735
+ if (!target) return void 0;
1736
+ if (!target.fallbackProfile) return target;
1737
+ const mgr = this.fallbackProfileManager;
1738
+ if (!mgr) return target;
1739
+ const chain = mgr.resolve(target.fallbackProfile);
1740
+ if (chain.length === 0) return target;
1741
+ const combined = [
1742
+ ...chain.map((e) => `${e.providerId}/${e.model}`),
1743
+ ...target.fallbackModels ?? []
1744
+ ];
1745
+ const seen = /* @__PURE__ */ new Set();
1746
+ const deduped = combined.filter((ref) => {
1747
+ if (seen.has(ref)) return false;
1748
+ seen.add(ref);
1749
+ return true;
1750
+ });
1751
+ return {
1752
+ ...target.providerId ? { providerId: target.providerId } : {},
1753
+ ...target.model ? { model: target.model } : {},
1754
+ ...target.role ? { role: target.role } : {},
1755
+ fallbackModels: deduped
1756
+ };
1757
+ }
1722
1758
  };
1723
1759
  function parseVote(text, question, refusalOptionId) {
1724
1760
  const parsed = parseObject(text);
1761
+ if (!parsed.ok && (!question.options || question.options.length === 0)) {
1762
+ const fallback = text.trim();
1763
+ if (fallback) return { ok: true, vote: { stance: fallback } };
1764
+ return { ok: false, error: "Voter returned an empty response." };
1765
+ }
1725
1766
  if (!parsed.ok) return parsed;
1726
1767
  const rationale = optionalString(parsed.value["rationale"]);
1727
1768
  if (question.options && question.options.length > 0) {
@@ -1738,6 +1779,11 @@ function parseVote(text, question, refusalOptionId) {
1738
1779
  }
1739
1780
  function parseJudge(text, question, refusalOptionId) {
1740
1781
  const parsed = parseObject(text);
1782
+ if (!parsed.ok && (!question.options || question.options.length === 0)) {
1783
+ const fallback = text.trim();
1784
+ if (fallback) return { ok: true, value: { answer: fallback } };
1785
+ return { ok: false, error: "Judge returned an empty response." };
1786
+ }
1741
1787
  if (!parsed.ok) return parsed;
1742
1788
  const rationale = optionalString(parsed.value["rationale"]);
1743
1789
  if (question.options && question.options.length > 0) {
@@ -1913,7 +1959,10 @@ var INPUT_SCHEMA = {
1913
1959
  additionalProperties: false
1914
1960
  };
1915
1961
  function createCouncilTool(opts) {
1916
- const orchestrator = new CouncilOrchestrator(opts);
1962
+ const orchestrator = new CouncilOrchestrator({
1963
+ ...opts,
1964
+ fallbackProfileManager: opts.fallbackProfileManager
1965
+ });
1917
1966
  return {
1918
1967
  name: COUNCIL_TOOL_NAME,
1919
1968
  description: "Ask an independent, multi-persona Council to evaluate a decision or synthesize an answer. Uses bounded parallel voters, quorum/veto/weighted resolution, optional judging, model routing, fallback chains, and cancellation.",
@@ -1962,6 +2011,84 @@ function validateCouncilToolInput(input) {
1962
2011
  return errors;
1963
2012
  }
1964
2013
 
2014
+ // src/core/model-availability-calendar.ts
2015
+ function logicalCalendarTarget(providerId, model) {
2016
+ if (providerId !== "omniroute") return { providerId, model };
2017
+ const slash = model.indexOf("/");
2018
+ return slash > 0 && slash < model.length - 1 ? { providerId: model.slice(0, slash), model: model.slice(slash + 1) } : { providerId, model };
2019
+ }
2020
+ var WEEKDAYS = {
2021
+ Sun: 0,
2022
+ Mon: 1,
2023
+ Tue: 2,
2024
+ Wed: 3,
2025
+ Thu: 4,
2026
+ Fri: 5,
2027
+ Sat: 6
2028
+ };
2029
+ function minuteOfDay(value) {
2030
+ const match = /^(\d{2}):(\d{2})$/.exec(value);
2031
+ if (!match) return void 0;
2032
+ const hour = Number(match[1]);
2033
+ const minute = Number(match[2]);
2034
+ if (hour > 23 || minute > 59) return void 0;
2035
+ return hour * 60 + minute;
2036
+ }
2037
+ function clockAt(date, timezone) {
2038
+ try {
2039
+ const parts = new Intl.DateTimeFormat("en-US", {
2040
+ timeZone: timezone,
2041
+ weekday: "short",
2042
+ hour: "2-digit",
2043
+ minute: "2-digit",
2044
+ hourCycle: "h23"
2045
+ }).formatToParts(date);
2046
+ const weekday = parts.find((part) => part.type === "weekday")?.value;
2047
+ const hour = Number(parts.find((part) => part.type === "hour")?.value);
2048
+ const minute = Number(parts.find((part) => part.type === "minute")?.value);
2049
+ if (!weekday || WEEKDAYS[weekday] === void 0 || !Number.isFinite(hour + minute))
2050
+ return void 0;
2051
+ return { day: WEEKDAYS[weekday], minute: hour * 60 + minute };
2052
+ } catch {
2053
+ return void 0;
2054
+ }
2055
+ }
2056
+ function targetMatches(rule, providerId, model) {
2057
+ if (rule.provider && rule.provider !== providerId) return false;
2058
+ if (rule.model && rule.model !== model) return false;
2059
+ return Boolean(rule.provider || rule.model);
2060
+ }
2061
+ function timeMatches(rule, day, minute) {
2062
+ const start = minuteOfDay(rule.start);
2063
+ const end = minuteOfDay(rule.end);
2064
+ if (start === void 0 || end === void 0) return false;
2065
+ const days = rule.days?.length ? new Set(rule.days) : void 0;
2066
+ if (start === end) return !days || days.has(day);
2067
+ if (start < end) return (!days || days.has(day)) && minute >= start && minute < end;
2068
+ if (minute >= start) return !days || days.has(day);
2069
+ const previousDay = (day + 6) % 7;
2070
+ return minute < end && (!days || days.has(previousDay));
2071
+ }
2072
+ function evaluateModelCalendar(rules, providerId, model, at = /* @__PURE__ */ new Date()) {
2073
+ ({ providerId, model } = logicalCalendarTarget(providerId, model));
2074
+ const allowRules = [];
2075
+ let allowMatched = false;
2076
+ for (const rule of rules ?? []) {
2077
+ if (rule.enabled === false || !targetMatches(rule, providerId, model)) continue;
2078
+ const clock = clockAt(at, rule.timezone);
2079
+ if (!clock || minuteOfDay(rule.start) === void 0 || minuteOfDay(rule.end) === void 0)
2080
+ continue;
2081
+ if (rule.mode === "allow_only") {
2082
+ allowRules.push(rule);
2083
+ if (timeMatches(rule, clock.day, clock.minute)) allowMatched = true;
2084
+ } else if (timeMatches(rule, clock.day, clock.minute)) {
2085
+ return { allowed: false, rule };
2086
+ }
2087
+ }
2088
+ if (allowRules.length > 0 && !allowMatched) return { allowed: false, rule: allowRules[0] };
2089
+ return { allowed: true };
2090
+ }
2091
+
1965
2092
  // src/types/blocks.ts
1966
2093
  function isTextBlock(b) {
1967
2094
  return b.type === "text";
@@ -1970,18 +2097,20 @@ function isTextBlock(b) {
1970
2097
  // src/types/provider.ts
1971
2098
  var CONTEXT_OVERFLOW_RE = /context.length|context.window|max.*tokens?.*exceeded|prompt is too long|too long|exceeds the context|\btokens\b.*exceed|context_length_exceeded/i;
1972
2099
  var CONTENT_FILTER_RE = /content.(filter|policy|moderation)|safety (system|filter)/i;
2100
+ var QUOTA_EXHAUSTED_RE = /(?:insufficient|exhausted|depleted|exceeded|no|not enough)[-_\s]*(?:quota|credit|balance)|(?:quota|credit|balance)[-_\s]*(?:exhausted|depleted|exceeded|insufficient)|billing[_\s-]*(?:hard[_\s-]*)?limit|payment required|spending limit|plan limit/i;
1973
2101
  function classifyProviderError(status, body, message) {
1974
2102
  const type = body?.type;
2103
+ const text = [message, body?.message, type, body?.raw].filter(Boolean).join("\n");
1975
2104
  if (status === 0) return "network";
1976
2105
  if (status === 408) return "timeout";
1977
2106
  if (status === 599) return "stream_hang";
2107
+ if (status === 402 || QUOTA_EXHAUSTED_RE.test(text)) return "quota_exhausted";
1978
2108
  if (type === "rate_limit_error" || status === 429) return "rate_limit";
1979
2109
  if (type === "overloaded_error" || status === 529) return "overloaded";
1980
2110
  if (status >= 500) return "server";
1981
2111
  if (type === "authentication_error" || type === "permission_error" || status === 401 || status === 403) {
1982
2112
  return "auth";
1983
2113
  }
1984
- const text = [message, body?.message, type, body?.raw].filter(Boolean).join("\n");
1985
2114
  if (type === "content_filter" || CONTENT_FILTER_RE.test(text)) return "content_filter";
1986
2115
  if (status === 413 || status >= 400 && CONTEXT_OVERFLOW_RE.test(text)) {
1987
2116
  return "context_overflow";
@@ -1994,6 +2123,7 @@ function isFallbackWorthy(kind) {
1994
2123
  }
1995
2124
  var FALLBACK_WORTHY_BY_KIND = {
1996
2125
  rate_limit: true,
2126
+ quota_exhausted: true,
1997
2127
  overloaded: true,
1998
2128
  server: true,
1999
2129
  timeout: true,
@@ -2072,6 +2202,7 @@ var KIND_TO_CODE = {
2072
2202
  network: ERROR_CODES.PROVIDER_NETWORK_ERROR,
2073
2203
  timeout: ERROR_CODES.PROVIDER_NETWORK_ERROR,
2074
2204
  rate_limit: ERROR_CODES.PROVIDER_RATE_LIMITED,
2205
+ quota_exhausted: ERROR_CODES.PROVIDER_RATE_LIMITED,
2075
2206
  auth: ERROR_CODES.PROVIDER_AUTH_FAILED,
2076
2207
  overloaded: ERROR_CODES.PROVIDER_OVERLOADED,
2077
2208
  context_overflow: ERROR_CODES.PROVIDER_CONTEXT_OVERFLOW,
@@ -2085,98 +2216,6 @@ function kindToCode(kind) {
2085
2216
  return KIND_TO_CODE[kind];
2086
2217
  }
2087
2218
 
2088
- // src/core/fallback-model.ts
2089
- function visibleProviderModels(config, providerId, providerModels) {
2090
- const entry = config.providers?.[providerId];
2091
- return entry?.models !== void 0 ? [...entry.models] : providerModels;
2092
- }
2093
- function parseModelRef(ref) {
2094
- const trimmed = ref.trim();
2095
- const slash = trimmed.indexOf("/");
2096
- if (slash !== -1) {
2097
- return {
2098
- provider: trimmed.slice(0, slash) || void 0,
2099
- model: trimmed.slice(slash + 1).trim()
2100
- };
2101
- }
2102
- const parts = trimmed.split(/\s+/);
2103
- if (parts.length >= 2) {
2104
- return { provider: parts[0], model: parts.slice(1).join(" ") };
2105
- }
2106
- return { model: trimmed };
2107
- }
2108
- function formatModelRef(ref, defaultProvider) {
2109
- const provider = ref.provider ?? defaultProvider;
2110
- return provider ? `${provider}/${ref.model}` : ref.model;
2111
- }
2112
- function normalizeModelRef(ref, defaultProvider) {
2113
- const parsed = parseModelRef(ref);
2114
- return formatModelRef(parsed, defaultProvider);
2115
- }
2116
- function fallbackProfileChain(config, profileName) {
2117
- if (!profileName) return [];
2118
- const chain = config.fallbackProfiles?.[profileName];
2119
- return Array.isArray(chain) ? chain.filter((ref) => parseModelRef(ref).model) : [];
2120
- }
2121
- function providerHasKey(entry) {
2122
- if (!entry) return false;
2123
- if (typeof entry.apiKey === "string" && entry.apiKey.length > 0) return true;
2124
- if (Array.isArray(entry.apiKeys) && entry.apiKeys.some((k) => k?.apiKey)) return true;
2125
- if (Array.isArray(entry.envVars) && entry.envVars.some((v) => !!process.env[v])) return true;
2126
- return false;
2127
- }
2128
- var SMART_DEFAULT_MAX = 4;
2129
- function smartDefaultFallbackChain(config) {
2130
- const leaderProvider = config.provider;
2131
- const leaderModel = config.model;
2132
- const providers = config.providers ?? {};
2133
- const favoriteSet = new Set(
2134
- (config.favoriteModels ?? []).map((ref) => normalizeModelRef(ref, leaderProvider))
2135
- );
2136
- const hasFavorites = favoriteSet.size > 0;
2137
- const favoritesOnly = config.favoriteModelsOnly === true;
2138
- const seen = /* @__PURE__ */ new Set();
2139
- const favoriteRefs = [];
2140
- const sameProvider = [];
2141
- const crossProvider = [];
2142
- const ids = Object.keys(providers).sort(
2143
- (a, b) => a === leaderProvider ? -1 : b === leaderProvider ? 1 : a.localeCompare(b)
2144
- );
2145
- for (const id of ids) {
2146
- const entry = providers[id];
2147
- if (!providerHasKey(entry)) continue;
2148
- const models = visibleProviderModels(config, id, entry?.models ?? []);
2149
- for (const model of models) {
2150
- if (id === leaderProvider && model === leaderModel) continue;
2151
- const ref = `${id}/${model}`;
2152
- if (seen.has(ref)) continue;
2153
- seen.add(ref);
2154
- if (favoriteSet.has(ref)) {
2155
- favoriteRefs.push(ref);
2156
- continue;
2157
- }
2158
- if (favoritesOnly && hasFavorites) continue;
2159
- (id === leaderProvider ? sameProvider : crossProvider).push(ref);
2160
- }
2161
- }
2162
- return [...favoriteRefs, ...sameProvider, ...crossProvider].slice(0, SMART_DEFAULT_MAX);
2163
- }
2164
- function effectiveFallbackChain(config) {
2165
- const explicit = config.fallbackModels ?? [];
2166
- const filteredExplicit = explicit.filter((ref) => {
2167
- const parsed = parseModelRef(ref);
2168
- if (!parsed.model) return false;
2169
- const providerId = parsed.provider ?? config.provider;
2170
- const entry = config.providers?.[providerId];
2171
- if (!entry?.models) return true;
2172
- return entry.models.includes(parsed.model);
2173
- });
2174
- if (filteredExplicit.length > 0) return filteredExplicit;
2175
- if (config.fallbackAuto === false) return [];
2176
- return smartDefaultFallbackChain(config);
2177
- }
2178
- var DEFAULT_PRIMARY_COOLDOWN_MAX_MS = 10 * 6e4;
2179
-
2180
2219
  // src/execution/one-shot-llm.ts
2181
2220
  var DEFAULT_TIMEOUT_MS = 3e4;
2182
2221
  var DEFAULT_MAX_TOKENS = 1024;
@@ -2223,71 +2262,74 @@ var OneShotOrchestrator = class {
2223
2262
  const request = this.buildRequest(input, target.model);
2224
2263
  const signal = this.resolveSignal(input);
2225
2264
  const chain = this.resolveFallbackChain(input, config, target);
2265
+ const tracker = this.opts.statusTracker;
2226
2266
  let servingProviderId = provider.id;
2227
2267
  let servingModel = target.model;
2228
2268
  let fromFallback = false;
2229
- const primaryAttempt = await this.tryCall(provider, request, signal);
2230
- let result = primaryAttempt.response;
2231
- let lastError = primaryAttempt.error;
2232
- let fallbackEligible = primaryAttempt.fallbackEligible;
2233
- if (!result && fallbackEligible && chain.length > 0) {
2234
- for (const ref of chain) {
2235
- const parsed = parseModelRef(ref);
2236
- if (!parsed.model) continue;
2237
- const fbProviderId = parsed.provider ?? config.provider;
2238
- if (fbProviderId === provider.id && parsed.model === target.model) continue;
2239
- servingProviderId = fbProviderId;
2240
- servingModel = parsed.model;
2241
- let fbProvider;
2242
- try {
2243
- fbProvider = await this.opts.buildProvider(fbProviderId, parsed.model);
2244
- } catch (err) {
2245
- lastError = err;
2246
- continue;
2247
- }
2248
- servingProviderId = fbProvider.id;
2249
- const attempt = await this.tryCall(
2250
- fbProvider,
2251
- this.buildRequest(input, parsed.model),
2252
- signal
2253
- );
2254
- if (attempt.response) {
2255
- result = attempt.response;
2256
- fromFallback = true;
2257
- break;
2258
- }
2259
- lastError = attempt.error;
2260
- fallbackEligible = attempt.fallbackEligible;
2261
- if (!fallbackEligible) break;
2269
+ let lastError;
2270
+ let fallbackEligible = false;
2271
+ if (tracker && !tracker.isAvailable(target.providerId, target.model) || !evaluateModelCalendar(config.modelAvailabilitySchedule, target.providerId, target.model).allowed) {
2272
+ this.opts.logger?.debug(
2273
+ `one-shot: primary "${target.providerId}/${target.model}" is blocked \u2014 trying fallback`
2274
+ );
2275
+ } else {
2276
+ const primaryAttempt = await this.tryCall(
2277
+ provider,
2278
+ request,
2279
+ signal,
2280
+ target.providerId,
2281
+ target.model
2282
+ );
2283
+ const result = primaryAttempt.response;
2284
+ lastError = primaryAttempt.error;
2285
+ fallbackEligible = primaryAttempt.fallbackEligible;
2286
+ if (result) {
2287
+ tracker?.recordSuccess(target.providerId, target.model);
2288
+ servingProviderId = provider.id;
2289
+ servingModel = target.model;
2290
+ return this.buildResult(result, servingProviderId, servingModel, false, startedAt);
2291
+ }
2292
+ if (!fallbackEligible || chain.length === 0) {
2293
+ return this.buildErrorResult(lastError, target.providerId, target.model, false, startedAt);
2262
2294
  }
2263
2295
  }
2264
- const elapsed = Math.round(performance.now() - startedAt);
2265
- if (result) {
2266
- const textBlocks = result.content.filter(isTextBlock);
2267
- const text = textBlocks.map((b) => b.text).join("\n").trim();
2268
- return {
2269
- text: text || "(empty response)",
2270
- model: result.model ?? servingModel,
2271
- provider: servingProviderId,
2272
- tokens: {
2273
- input: result.usage?.input ?? 0,
2274
- output: result.usage?.output ?? 0,
2275
- total: (result.usage?.input ?? 0) + (result.usage?.output ?? 0)
2276
- },
2277
- durationMs: elapsed,
2278
- fromFallback,
2279
- stopReason: result.stopReason
2280
- };
2296
+ const usableChain = tracker ? chain.filter((e) => tracker.isAvailable(e.providerId, e.model)) : chain;
2297
+ for (const entry of usableChain) {
2298
+ if (!evaluateModelCalendar(config.modelAvailabilitySchedule, entry.providerId, entry.model).allowed)
2299
+ continue;
2300
+ if (entry.providerId === provider.id && entry.model === target.model) continue;
2301
+ let fbProvider;
2302
+ try {
2303
+ fbProvider = await this.opts.buildProvider(entry.providerId, entry.model);
2304
+ } catch (err) {
2305
+ lastError = err;
2306
+ continue;
2307
+ }
2308
+ servingProviderId = fbProvider.id;
2309
+ servingModel = entry.model;
2310
+ const attempt = await this.tryCall(
2311
+ fbProvider,
2312
+ this.buildRequest(input, entry.model),
2313
+ signal,
2314
+ entry.providerId,
2315
+ entry.model
2316
+ );
2317
+ if (attempt.response) {
2318
+ tracker?.recordSuccess(entry.providerId, entry.model);
2319
+ fromFallback = true;
2320
+ return this.buildResult(attempt.response, servingProviderId, servingModel, true, startedAt);
2321
+ }
2322
+ lastError = attempt.error;
2323
+ fallbackEligible = attempt.fallbackEligible;
2324
+ if (!fallbackEligible) break;
2281
2325
  }
2282
- return {
2283
- text: "",
2284
- model: servingModel,
2285
- provider: servingProviderId,
2286
- tokens: { input: 0, output: 0, total: 0 },
2287
- durationMs: elapsed,
2326
+ return this.buildErrorResult(
2327
+ lastError,
2328
+ servingProviderId,
2329
+ servingModel,
2288
2330
  fromFallback,
2289
- error: lastError instanceof Error ? lastError.message : String(lastError ?? "Unknown error")
2290
- };
2331
+ startedAt
2332
+ );
2291
2333
  }
2292
2334
  // ── Private helpers ─────────────────────────────────────────────
2293
2335
  /**
@@ -2343,38 +2385,81 @@ var OneShotOrchestrator = class {
2343
2385
  }
2344
2386
  /**
2345
2387
  * Build the fallback model chain from input + config + current target.
2388
+ * The injected {@link OneShotOrchestratorOptions.fallbackProfileManager}
2389
+ * is the only allowed manager — OneShot never owns a private snapshot
2390
+ * so a live `ConfigStore` change reaches every call without rebuilding
2391
+ * the manager.
2346
2392
  */
2347
2393
  resolveFallbackChain(input, config, target) {
2394
+ const mgr = this.opts.fallbackProfileManager;
2348
2395
  if (input.fallbackModels && input.fallbackModels.length > 0) {
2349
- return input.fallbackModels;
2396
+ return mgr.resolveRefs(input.fallbackModels, target);
2350
2397
  }
2351
- if (input.fallbackProfile) {
2352
- const profile = fallbackProfileChain(config, input.fallbackProfile);
2353
- if (profile.length > 0) return profile;
2398
+ if (config.fallbackModels && config.fallbackModels.length > 0) {
2399
+ return mgr.resolveRefs(config.fallbackModels, target);
2354
2400
  }
2355
2401
  if (config.fallbackAuto !== false) {
2356
- return effectiveFallbackChain(config).filter((ref) => {
2357
- const parsed = parseModelRef(ref);
2358
- const fbProvider = parsed.provider ?? config.provider;
2359
- return !(fbProvider === target.providerId && parsed.model === target.model);
2402
+ return mgr.resolveEffective({
2403
+ fallbackAuto: true,
2404
+ exclude: target
2360
2405
  });
2361
2406
  }
2362
- return [];
2407
+ return Object.freeze([]);
2363
2408
  }
2364
2409
  /** Attempt a provider call while preserving the actual failure for callers. */
2365
- async tryCall(provider, request, signal) {
2410
+ async tryCall(provider, request, signal, providerId, model) {
2366
2411
  try {
2367
2412
  return {
2368
2413
  response: await provider.complete(request, { signal }),
2369
2414
  fallbackEligible: false
2370
2415
  };
2371
2416
  } catch (err) {
2417
+ if (err instanceof ProviderError && providerId && model) {
2418
+ this.opts.statusTracker?.recordFailure(
2419
+ providerId,
2420
+ model,
2421
+ err.kind,
2422
+ err.status,
2423
+ err.describe(),
2424
+ { retryAfterMs: err.body?.retryAfterMs }
2425
+ );
2426
+ }
2372
2427
  return {
2373
2428
  error: err,
2374
2429
  fallbackEligible: !signal.aborted && (!(err instanceof ProviderError) || isFallbackWorthy(err.kind))
2375
2430
  };
2376
2431
  }
2377
2432
  }
2433
+ /** Build a success result from a provider Response. */
2434
+ buildResult(response, servingProviderId, servingModel, fromFallback, startedAt) {
2435
+ const textBlocks = response.content.filter(isTextBlock);
2436
+ const text = textBlocks.map((b) => b.text).join("\n").trim();
2437
+ return {
2438
+ text: text || "(empty response)",
2439
+ model: response.model ?? servingModel,
2440
+ provider: servingProviderId,
2441
+ tokens: {
2442
+ input: response.usage?.input ?? 0,
2443
+ output: response.usage?.output ?? 0,
2444
+ total: (response.usage?.input ?? 0) + (response.usage?.output ?? 0)
2445
+ },
2446
+ durationMs: Math.round(performance.now() - startedAt),
2447
+ fromFallback,
2448
+ stopReason: response.stopReason
2449
+ };
2450
+ }
2451
+ /** Build a total-failure error result. */
2452
+ buildErrorResult(error, servingProviderId, servingModel, fromFallback, startedAt) {
2453
+ return {
2454
+ text: "",
2455
+ model: servingModel,
2456
+ provider: servingProviderId,
2457
+ tokens: { input: 0, output: 0, total: 0 },
2458
+ durationMs: Math.round(performance.now() - startedAt),
2459
+ fromFallback,
2460
+ error: error instanceof Error ? error.message : String(error ?? "Unknown error")
2461
+ };
2462
+ }
2378
2463
  };
2379
2464
  function asTextBlocks(system) {
2380
2465
  if (!system) return [];
@@ -2427,14 +2512,10 @@ var INPUT_SCHEMA2 = {
2427
2512
  type: "string",
2428
2513
  description: "Roster role for model-matrix routing. Overrides model/providerId."
2429
2514
  },
2430
- fallbackProfile: {
2431
- type: "string",
2432
- description: "Named fallback profile from config.fallbackProfiles."
2433
- },
2434
2515
  fallbackModels: {
2435
2516
  type: "array",
2436
2517
  items: { type: "string" },
2437
- description: 'Explicit fallback model chain (e.g. ["anthropic/claude-haiku", "openai/gpt-4o-mini"]).'
2518
+ description: 'Explicit fallback model chain (e.g. ["anthropic/claude-haiku", "openai/gpt-4o-mini"]). Resolved named profiles from FallbackProfileManager are passed here.'
2438
2519
  },
2439
2520
  maxTokens: {
2440
2521
  type: "number",
@@ -2472,13 +2553,14 @@ function createOneShotLLMTool(opts) {
2472
2553
  const orchestrator = new OneShotOrchestrator({
2473
2554
  buildProvider: opts.buildProvider,
2474
2555
  getConfig: opts.getConfig,
2556
+ fallbackProfileManager: opts.fallbackProfileManager,
2475
2557
  modelRouter: opts.modelRouter,
2476
2558
  logger: opts.logger
2477
2559
  });
2478
2560
  return {
2479
2561
  name: ONE_SHOT_LLM_TOOL_NAME,
2480
2562
  description: "Make a one-shot LLM call with a system prompt and user input. Supports provider selection, model routing by role, fallback chains, and timeout. Returns the response text, model info, token usage, and whether a fallback was used. Use this for summarization, classification, extraction, and any single-turn LLM task.",
2481
- usageHint: "Provide `system` for the instruction and `userPrompt` for the input. Either set `model`+`providerId`, or have defaults configured on the tool. Set `fallbackProfile` or `fallbackModels` for resilience. Check `error` on the result for failure details.",
2563
+ usageHint: "Provide `system` for the instruction and `userPrompt` for the input. Either set `model`+`providerId`, or have defaults configured on the tool. Set `fallbackModels` for resilience. Check `error` on the result for failure details.",
2482
2564
  inputSchema: INPUT_SCHEMA2,
2483
2565
  permission: "auto",
2484
2566
  mutating: false,
@@ -2504,13 +2586,3162 @@ function createOneShotLLMTool(opts) {
2504
2586
  }
2505
2587
  };
2506
2588
  }
2589
+
2590
+ // src/coordination/agents/types.ts
2591
+ var HOUR = 60 * 60 * 1e3;
2592
+ var LIGHT_BUDGET = {
2593
+ timeoutMs: 3 * HOUR,
2594
+ maxIterations: 3e3,
2595
+ maxToolCalls: 8e3
2596
+ };
2597
+ var MEDIUM_BUDGET = {
2598
+ timeoutMs: 5 * HOUR,
2599
+ maxIterations: 5e3,
2600
+ maxToolCalls: 14e3
2601
+ };
2602
+ var HEAVY_BUDGET = {
2603
+ timeoutMs: 10 * HOUR,
2604
+ maxIterations: 8e3,
2605
+ maxToolCalls: 2e4
2606
+ };
2607
+ var TOOLS = {
2608
+ /** Pure read/inspect — safe for analysis and review agents. */
2609
+ read: ["read", "grep", "glob", "search", "tree", "mailbox"],
2610
+ /** Read + structured inspection (logs, diffs, json, dependency audit). */
2611
+ inspect: ["read", "grep", "glob", "search", "tree", "json", "diff", "logs", "audit", "mailbox"],
2612
+ /** Read + edit (no shell). For agents that write code/docs but don't run it. */
2613
+ write: ["read", "grep", "glob", "search", "tree", "write", "edit", "replace", "patch", "mailbox"],
2614
+ /** Full build loop: edit + run (lint/format/typecheck/test/bash). */
2615
+ build: [
2616
+ "read",
2617
+ "grep",
2618
+ "glob",
2619
+ "search",
2620
+ "tree",
2621
+ "write",
2622
+ "edit",
2623
+ "replace",
2624
+ "patch",
2625
+ "bash",
2626
+ "exec",
2627
+ "lint",
2628
+ "format",
2629
+ "typecheck",
2630
+ "test",
2631
+ "mailbox"
2632
+ ],
2633
+ /** Version control. */
2634
+ vcs: ["read", "grep", "glob", "git", "diff"],
2635
+ /** Dependency management + CVE audit. */
2636
+ deps: ["read", "grep", "glob", "install", "outdated", "audit", "json", "mailbox"],
2637
+ /** Documentation authoring. */
2638
+ docs: ["read", "grep", "glob", "search", "tree", "write", "edit", "document", "mailbox"],
2639
+ /** Web research. */
2640
+ research: ["read", "grep", "glob", "search", "fetch", "mailbox"]
2641
+ };
2642
+
2643
+ // src/coordination/agents/agent-prompts.ts
2644
+ import { readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
2645
+ import * as os from "node:os";
2646
+ import * as path3 from "node:path";
2647
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
2648
+ var promptCache = /* @__PURE__ */ new Map();
2649
+ var candidateCache = /* @__PURE__ */ new Map();
2650
+ function agentPrompt(id) {
2651
+ const envDir = process.env["WRONGSTACK_AGENT_INSTRUCTIONS_DIR"] ?? "";
2652
+ const cacheKey = `${envDir}\0${id}`;
2653
+ const cached = promptCache.get(cacheKey);
2654
+ if (cached !== void 0) return cached;
2655
+ const fileName = `${id}.md`;
2656
+ let resolved = "";
2657
+ for (const dir of agentPromptDirCandidates(envDir)) {
2658
+ try {
2659
+ resolved = readFileSync2(path3.join(dir, fileName), "utf8").trimEnd();
2660
+ break;
2661
+ } catch {
2662
+ }
2663
+ }
2664
+ promptCache.set(cacheKey, resolved);
2665
+ return resolved;
2666
+ }
2667
+ function agentPromptDirCandidates(envDir) {
2668
+ const globalRoot = process.env["WRONGSTACK_HOME"] || path3.join(os.homedir(), ".wrongstack");
2669
+ const candKey = `${envDir}\0${globalRoot}`;
2670
+ const cached = candidateCache.get(candKey);
2671
+ if (cached !== void 0) return cached;
2672
+ const here = path3.dirname(fileURLToPath2(import.meta.url));
2673
+ const explicitDir = envDir || void 0;
2674
+ const candidates = [
2675
+ ...explicitDir ? [path3.resolve(explicitDir)] : [],
2676
+ path3.join(globalRoot, "instructions", "agents"),
2677
+ path3.resolve(here, "../../../../instructions/agents"),
2678
+ path3.resolve(here, "../../../instructions/agents"),
2679
+ path3.resolve(here, "../../instructions/agents"),
2680
+ path3.resolve(here, "../instructions/agents"),
2681
+ path3.resolve(here, "instructions/agents")
2682
+ ];
2683
+ const ordered = candidates.sort((a, b) => Number(!isDirectory2(a)) - Number(!isDirectory2(b)));
2684
+ candidateCache.set(candKey, ordered);
2685
+ return ordered;
2686
+ }
2687
+ function isDirectory2(candidate) {
2688
+ try {
2689
+ return statSync2(candidate).isDirectory();
2690
+ } catch {
2691
+ return false;
2692
+ }
2693
+ }
2694
+
2695
+ // src/coordination/agents/phase1-discovery.ts
2696
+ var DISCOVERY_AGENTS = [
2697
+ {
2698
+ config: {
2699
+ id: "explore",
2700
+ name: "Explore",
2701
+ role: "explore",
2702
+ tools: [...TOOLS.read],
2703
+ prompt: agentPrompt("explore")
2704
+ },
2705
+ budget: MEDIUM_BUDGET,
2706
+ capability: {
2707
+ phase: "discovery",
2708
+ summary: "Maps unfamiliar codebases: entry points, structure, architecture, feature flow (read-only).",
2709
+ keywords: [
2710
+ "explore",
2711
+ "map",
2712
+ "understand",
2713
+ "where is",
2714
+ "how does",
2715
+ "codebase",
2716
+ "architecture",
2717
+ "structure",
2718
+ "overview",
2719
+ "find file",
2720
+ "entry point",
2721
+ "orient"
2722
+ ]
2723
+ }
2724
+ },
2725
+ {
2726
+ config: {
2727
+ id: "search",
2728
+ name: "Search",
2729
+ role: "search",
2730
+ tools: [...TOOLS.read],
2731
+ prompt: agentPrompt("search")
2732
+ },
2733
+ budget: MEDIUM_BUDGET,
2734
+ capability: {
2735
+ phase: "discovery",
2736
+ summary: "Semantic + lexical code search across repos; finds definitions, references, duplicates, ranks by relevance.",
2737
+ keywords: [
2738
+ "search",
2739
+ "find all",
2740
+ "references",
2741
+ "usages",
2742
+ "call sites",
2743
+ "grep",
2744
+ "locate symbol",
2745
+ "duplicate",
2746
+ "where used",
2747
+ "occurrences",
2748
+ "cross-repo"
2749
+ ]
2750
+ }
2751
+ },
2752
+ {
2753
+ config: {
2754
+ id: "research",
2755
+ name: "Research",
2756
+ role: "research",
2757
+ tools: [...TOOLS.research],
2758
+ prompt: agentPrompt("research")
2759
+ },
2760
+ budget: LIGHT_BUDGET,
2761
+ capability: {
2762
+ phase: "discovery",
2763
+ summary: "Technical research and feasibility: compares libraries/approaches, recommends a path with evidence and tradeoffs.",
2764
+ keywords: [
2765
+ "research",
2766
+ "feasibility",
2767
+ "compare libraries",
2768
+ "which library",
2769
+ "best practice",
2770
+ "tradeoff",
2771
+ "investigate",
2772
+ "evaluate approach",
2773
+ "should we use",
2774
+ "pros and cons"
2775
+ ]
2776
+ }
2777
+ }
2778
+ ];
2779
+
2780
+ // src/coordination/agents/phase2-planning.ts
2781
+ var PLAN_TOOLS = [...TOOLS.read, "plan", "todo"];
2782
+ var PLANNING_AGENTS = [
2783
+ {
2784
+ config: {
2785
+ id: "analyst",
2786
+ name: "Analyst",
2787
+ role: "analyst",
2788
+ tools: [...PLAN_TOOLS],
2789
+ prompt: agentPrompt("analyst")
2790
+ },
2791
+ budget: LIGHT_BUDGET,
2792
+ capability: {
2793
+ phase: "planning",
2794
+ summary: "Requirement analysis: turns vague requests into testable specs with acceptance criteria and open questions.",
2795
+ keywords: [
2796
+ "requirements",
2797
+ "analyze requirement",
2798
+ "acceptance criteria",
2799
+ "spec",
2800
+ "specification",
2801
+ "clarify",
2802
+ "scope",
2803
+ "user story",
2804
+ "what should it do"
2805
+ ]
2806
+ }
2807
+ },
2808
+ {
2809
+ config: {
2810
+ id: "planner",
2811
+ name: "Planner",
2812
+ role: "planner",
2813
+ tools: [...PLAN_TOOLS],
2814
+ prompt: agentPrompt("planner")
2815
+ },
2816
+ budget: LIGHT_BUDGET,
2817
+ capability: {
2818
+ phase: "planning",
2819
+ summary: "Execution planning: decomposes a goal into ordered, dependency-aware, parallelizable steps with checkpoints.",
2820
+ keywords: [
2821
+ "plan",
2822
+ "execution plan",
2823
+ "break down",
2824
+ "decompose",
2825
+ "steps",
2826
+ "sequence",
2827
+ "roadmap",
2828
+ "task breakdown",
2829
+ "order of work",
2830
+ "milestones"
2831
+ ]
2832
+ }
2833
+ },
2834
+ {
2835
+ config: {
2836
+ id: "architect",
2837
+ name: "Architect",
2838
+ role: "architect",
2839
+ tools: [...PLAN_TOOLS],
2840
+ prompt: agentPrompt("architect")
2841
+ },
2842
+ budget: LIGHT_BUDGET,
2843
+ capability: {
2844
+ phase: "planning",
2845
+ summary: "System architecture: designs module boundaries, interfaces, data flow, and records key decisions.",
2846
+ keywords: [
2847
+ "architecture",
2848
+ "design system",
2849
+ "module boundaries",
2850
+ "interfaces",
2851
+ "data flow",
2852
+ "component design",
2853
+ "system design",
2854
+ "decision record",
2855
+ "adr",
2856
+ "structure the"
2857
+ ]
2858
+ }
2859
+ },
2860
+ {
2861
+ config: {
2862
+ id: "critic",
2863
+ name: "Critic",
2864
+ role: "critic",
2865
+ tools: [...TOOLS.read],
2866
+ prompt: agentPrompt("critic")
2867
+ },
2868
+ budget: LIGHT_BUDGET,
2869
+ capability: {
2870
+ phase: "planning",
2871
+ summary: "Adversarial review of plans/designs: finds gaps, risks, and unstated assumptions with ranked fixes.",
2872
+ keywords: [
2873
+ "critique",
2874
+ "review plan",
2875
+ "review design",
2876
+ "red team",
2877
+ "poke holes",
2878
+ "risks",
2879
+ "what could go wrong",
2880
+ "second opinion",
2881
+ "challenge",
2882
+ "flaws"
2883
+ ]
2884
+ }
2885
+ },
2886
+ {
2887
+ config: {
2888
+ id: "refactor-planner",
2889
+ name: "Refactor Planner",
2890
+ role: "refactor-planner",
2891
+ tools: [...PLAN_TOOLS, "diff"],
2892
+ prompt: agentPrompt("refactor-planner")
2893
+ },
2894
+ budget: HEAVY_BUDGET,
2895
+ capability: {
2896
+ phase: "planning",
2897
+ summary: "Refactoring planner: analyzes code structure, maps dependencies, produces risk-scored phased plans with rollback strategy.",
2898
+ keywords: [
2899
+ "refactor",
2900
+ "refactoring",
2901
+ "restructure",
2902
+ "debt",
2903
+ "technical debt",
2904
+ "clean up",
2905
+ "modularize",
2906
+ "decouple",
2907
+ "dependency graph",
2908
+ "code structure"
2909
+ ]
2910
+ }
2911
+ }
2912
+ ];
2913
+
2914
+ // src/coordination/agents/phase3-build.ts
2915
+ var BUILD_AGENTS = [
2916
+ {
2917
+ config: {
2918
+ id: "executor",
2919
+ name: "Executor",
2920
+ role: "executor",
2921
+ tools: [...TOOLS.build],
2922
+ prompt: agentPrompt("executor")
2923
+ },
2924
+ budget: HEAVY_BUDGET,
2925
+ capability: {
2926
+ phase: "build",
2927
+ summary: "Implements well-specified tasks: writes code, runs checks, leaves the tree green.",
2928
+ keywords: [
2929
+ "implement",
2930
+ "build",
2931
+ "write code",
2932
+ "add feature",
2933
+ "create",
2934
+ "code up",
2935
+ "develop",
2936
+ "apply change",
2937
+ "make it work"
2938
+ ]
2939
+ }
2940
+ },
2941
+ {
2942
+ config: {
2943
+ id: "refactor",
2944
+ name: "Refactor",
2945
+ role: "refactor",
2946
+ tools: [...TOOLS.build],
2947
+ prompt: agentPrompt("refactor")
2948
+ },
2949
+ budget: HEAVY_BUDGET,
2950
+ capability: {
2951
+ phase: "build",
2952
+ summary: "Structural refactoring: extract/split/move/rename/decouple without changing observable behavior.",
2953
+ keywords: [
2954
+ "refactor",
2955
+ "restructure",
2956
+ "extract",
2957
+ "split module",
2958
+ "decouple",
2959
+ "rename",
2960
+ "move code",
2961
+ "break dependency",
2962
+ "reorganize"
2963
+ ]
2964
+ }
2965
+ },
2966
+ {
2967
+ config: {
2968
+ id: "simplifier",
2969
+ name: "Simplifier",
2970
+ role: "simplifier",
2971
+ tools: [...TOOLS.build],
2972
+ prompt: agentPrompt("simplifier")
2973
+ },
2974
+ budget: MEDIUM_BUDGET,
2975
+ capability: {
2976
+ phase: "build",
2977
+ summary: "Reduces complexity: deletes dead code, collapses needless abstractions, shortens and clarifies code.",
2978
+ keywords: [
2979
+ "simplify",
2980
+ "dead code",
2981
+ "remove unused",
2982
+ "reduce complexity",
2983
+ "clean up",
2984
+ "denest",
2985
+ "shorten",
2986
+ "over-engineered",
2987
+ "too complex"
2988
+ ]
2989
+ }
2990
+ },
2991
+ {
2992
+ config: {
2993
+ id: "migration",
2994
+ name: "Migration",
2995
+ role: "migration",
2996
+ tools: [...TOOLS.build, "install", "outdated"],
2997
+ prompt: agentPrompt("migration")
2998
+ },
2999
+ budget: HEAVY_BUDGET,
3000
+ capability: {
3001
+ phase: "build",
3002
+ summary: "Framework/language/version upgrades: applies codemods across call sites, staged and verified.",
3003
+ keywords: [
3004
+ "migrate",
3005
+ "upgrade",
3006
+ "codemod",
3007
+ "breaking change",
3008
+ "major version",
3009
+ "port to",
3010
+ "convert to",
3011
+ "esm",
3012
+ "modernize"
3013
+ ]
3014
+ }
3015
+ },
3016
+ {
3017
+ config: {
3018
+ id: "vision",
3019
+ name: "Vision",
3020
+ role: "vision",
3021
+ tools: [...TOOLS.write, "fetch"],
3022
+ prompt: agentPrompt("vision")
3023
+ },
3024
+ budget: MEDIUM_BUDGET,
3025
+ capability: {
3026
+ phase: "build",
3027
+ summary: "Screenshot/mockup \u2192 UI code: infers component tree and generates matching, accessible markup.",
3028
+ keywords: [
3029
+ "screenshot",
3030
+ "mockup",
3031
+ "design to code",
3032
+ "image to ui",
3033
+ "figma",
3034
+ "replicate this ui",
3035
+ "from this picture",
3036
+ "vision",
3037
+ "clone ui"
3038
+ ]
3039
+ }
3040
+ },
3041
+ {
3042
+ config: {
3043
+ id: "debugger",
3044
+ name: "Debugger",
3045
+ role: "debugger",
3046
+ tools: [...TOOLS.build, "logs"],
3047
+ prompt: agentPrompt("debugger")
3048
+ },
3049
+ budget: HEAVY_BUDGET,
3050
+ capability: {
3051
+ phase: "build",
3052
+ summary: "Root-cause bug fixing: reproduces, bisects to the true cause, applies a minimal fix with a regression test.",
3053
+ keywords: [
3054
+ "bug",
3055
+ "fix",
3056
+ "debug",
3057
+ "broken",
3058
+ "error",
3059
+ "crash",
3060
+ "root cause",
3061
+ "not working",
3062
+ "failing",
3063
+ "reproduce",
3064
+ "why does"
3065
+ ]
3066
+ }
3067
+ },
3068
+ {
3069
+ config: {
3070
+ id: "tracer",
3071
+ name: "Tracer",
3072
+ role: "tracer",
3073
+ tools: [...TOOLS.build, "logs"],
3074
+ prompt: agentPrompt("tracer")
3075
+ },
3076
+ budget: MEDIUM_BUDGET,
3077
+ capability: {
3078
+ phase: "build",
3079
+ summary: "Runtime tracing: instruments and runs code to observe call order, values, and timing, then cleans up.",
3080
+ keywords: [
3081
+ "trace",
3082
+ "runtime",
3083
+ "instrument",
3084
+ "execution path",
3085
+ "what happens at runtime",
3086
+ "call order",
3087
+ "profile execution",
3088
+ "observe behavior",
3089
+ "stack trace"
3090
+ ]
3091
+ }
3092
+ }
3093
+ ];
3094
+
3095
+ // src/coordination/agents/phase4-verify.ts
3096
+ var VERIFY_AGENTS = [
3097
+ {
3098
+ config: {
3099
+ id: "verifier",
3100
+ name: "Verifier",
3101
+ role: "verifier",
3102
+ tools: [...TOOLS.inspect, "bash", "exec", "lint", "typecheck", "test", "git"],
3103
+ prompt: agentPrompt("verifier")
3104
+ },
3105
+ budget: HEAVY_BUDGET,
3106
+ capability: {
3107
+ phase: "verify",
3108
+ summary: "Independent verification gate: runs the relevant checks until they pass or returns exact failures that need another implementation pass.",
3109
+ keywords: [
3110
+ "verify",
3111
+ "verification",
3112
+ "quality gate",
3113
+ "prove it works",
3114
+ "run checks",
3115
+ "run tests until pass",
3116
+ "test pass",
3117
+ "green build",
3118
+ "typecheck",
3119
+ "lint",
3120
+ "regression check",
3121
+ "acceptance gate"
3122
+ ]
3123
+ }
3124
+ },
3125
+ {
3126
+ config: {
3127
+ id: "test",
3128
+ name: "Test",
3129
+ role: "test",
3130
+ tools: [...TOOLS.build],
3131
+ prompt: agentPrompt("test")
3132
+ },
3133
+ budget: HEAVY_BUDGET,
3134
+ capability: {
3135
+ phase: "verify",
3136
+ summary: "Unit + integration testing: writes meaningful tests covering golden path and edge cases, runs the suite.",
3137
+ keywords: [
3138
+ "test",
3139
+ "unit test",
3140
+ "integration test",
3141
+ "write tests",
3142
+ "coverage",
3143
+ "test suite",
3144
+ "vitest",
3145
+ "jest",
3146
+ "add tests",
3147
+ "spec"
3148
+ ]
3149
+ }
3150
+ },
3151
+ {
3152
+ config: {
3153
+ id: "e2e",
3154
+ name: "E2E",
3155
+ role: "e2e",
3156
+ tools: [
3157
+ ...TOOLS.build,
3158
+ "fetch",
3159
+ "playwright_navigate",
3160
+ "playwright_screenshot",
3161
+ "playwright_click",
3162
+ "playwright_type",
3163
+ "playwright_evaluate",
3164
+ "playwright_select_option",
3165
+ "playwright_hover",
3166
+ "playwright_fill_form",
3167
+ "playwright_wait_for",
3168
+ "playwright_press_key",
3169
+ "playwright_drag"
3170
+ ],
3171
+ prompt: agentPrompt("e2e")
3172
+ },
3173
+ budget: HEAVY_BUDGET,
3174
+ capability: {
3175
+ phase: "verify",
3176
+ summary: "End-to-end testing: drives full user journeys across UI/CLI/API boundaries with reproducible failures.",
3177
+ keywords: [
3178
+ "e2e",
3179
+ "end to end",
3180
+ "end-to-end",
3181
+ "user journey",
3182
+ "smoke test",
3183
+ "playwright",
3184
+ "browser",
3185
+ "screenshot",
3186
+ "web ui",
3187
+ "headless",
3188
+ "cypress",
3189
+ "full flow",
3190
+ "browser test",
3191
+ "acceptance test",
3192
+ "navigate",
3193
+ "click",
3194
+ "form fill",
3195
+ "dom",
3196
+ "page load"
3197
+ ]
3198
+ }
3199
+ },
3200
+ {
3201
+ config: {
3202
+ id: "browser",
3203
+ name: "Browser",
3204
+ role: "browser",
3205
+ tools: [
3206
+ ...TOOLS.read,
3207
+ "fetch",
3208
+ "playwright_navigate",
3209
+ "playwright_screenshot",
3210
+ "playwright_click",
3211
+ "playwright_type",
3212
+ "playwright_evaluate",
3213
+ "playwright_select_option",
3214
+ "playwright_hover",
3215
+ "playwright_fill_form",
3216
+ "playwright_wait_for",
3217
+ "playwright_press_key",
3218
+ "playwright_drag"
3219
+ ],
3220
+ prompt: agentPrompt("browser")
3221
+ },
3222
+ budget: MEDIUM_BUDGET,
3223
+ capability: {
3224
+ phase: "verify",
3225
+ summary: "Browser automation: opens pages, clicks, types, screenshots, extracts data via Playwright headless Chromium.",
3226
+ keywords: [
3227
+ "browser",
3228
+ "screenshot",
3229
+ "navigate",
3230
+ "web page",
3231
+ "scrape",
3232
+ "crawl",
3233
+ "headless",
3234
+ "chrome",
3235
+ "open url",
3236
+ "capture",
3237
+ "page title",
3238
+ "extract data",
3239
+ "fill form",
3240
+ "click button",
3241
+ "take screenshot"
3242
+ ]
3243
+ }
3244
+ },
3245
+ {
3246
+ config: {
3247
+ id: "performance",
3248
+ name: "Performance",
3249
+ role: "performance",
3250
+ tools: [...TOOLS.build, "logs"],
3251
+ prompt: agentPrompt("performance")
3252
+ },
3253
+ budget: MEDIUM_BUDGET,
3254
+ capability: {
3255
+ phase: "verify",
3256
+ summary: "Performance analysis: benchmarks/profiles to find the real bottleneck, optimizes, proves speedup with numbers.",
3257
+ keywords: [
3258
+ "performance",
3259
+ "slow",
3260
+ "optimize",
3261
+ "bottleneck",
3262
+ "profile",
3263
+ "benchmark",
3264
+ "latency",
3265
+ "throughput",
3266
+ "memory",
3267
+ "speed up",
3268
+ "too slow"
3269
+ ]
3270
+ }
3271
+ },
3272
+ {
3273
+ config: {
3274
+ id: "chaos",
3275
+ name: "Chaos",
3276
+ role: "chaos",
3277
+ tools: [...TOOLS.build, "logs"],
3278
+ prompt: agentPrompt("chaos")
3279
+ },
3280
+ budget: MEDIUM_BUDGET,
3281
+ capability: {
3282
+ phase: "verify",
3283
+ summary: "Resilience testing via fault injection: breaks network/disk/timing to find ungraceful failures and recovery gaps.",
3284
+ keywords: [
3285
+ "chaos",
3286
+ "resilience",
3287
+ "fault injection",
3288
+ "failure mode",
3289
+ "fail safe",
3290
+ "retry",
3291
+ "circuit breaker",
3292
+ "graceful degradation",
3293
+ "inject failure",
3294
+ "robustness"
3295
+ ]
3296
+ }
3297
+ },
3298
+ {
3299
+ config: {
3300
+ id: "security-scanner",
3301
+ name: "Security Scanner",
3302
+ role: "security-scanner",
3303
+ tools: [...TOOLS.inspect],
3304
+ prompt: agentPrompt("security-scanner")
3305
+ },
3306
+ budget: HEAVY_BUDGET,
3307
+ capability: {
3308
+ phase: "verify",
3309
+ summary: "Security scanner: detects hardcoded secrets, injection vectors, insecure patterns, and supply-chain risks with remediation.",
3310
+ keywords: [
3311
+ "security",
3312
+ "scan",
3313
+ "vulnerability",
3314
+ "secret",
3315
+ "api key",
3316
+ "hardcoded",
3317
+ "injection",
3318
+ "cve",
3319
+ "audit dependencies",
3320
+ "supply chain",
3321
+ "xss",
3322
+ "sqli",
3323
+ "shell injection",
3324
+ "sensitive data",
3325
+ "credential"
3326
+ ]
3327
+ }
3328
+ },
3329
+ {
3330
+ config: {
3331
+ id: "bug-hunter",
3332
+ name: "Bug Hunter",
3333
+ role: "bug-hunter",
3334
+ tools: [...TOOLS.inspect],
3335
+ prompt: agentPrompt("bug-hunter")
3336
+ },
3337
+ budget: HEAVY_BUDGET,
3338
+ capability: {
3339
+ phase: "verify",
3340
+ summary: "Bug hunter: scans source code for bugs, anti-patterns, and code smells, producing a file:line-ranked hit list with fixes.",
3341
+ keywords: [
3342
+ "bug",
3343
+ "hunt",
3344
+ "scan",
3345
+ "code smell",
3346
+ "anti-pattern",
3347
+ "race condition",
3348
+ "memory leak",
3349
+ "null deref",
3350
+ "type safety",
3351
+ "unhandled error",
3352
+ "find bugs",
3353
+ "audit code",
3354
+ "code quality"
3355
+ ]
3356
+ }
3357
+ },
3358
+ {
3359
+ config: {
3360
+ id: "audit-log",
3361
+ name: "Audit Log",
3362
+ role: "audit-log",
3363
+ tools: [...TOOLS.inspect],
3364
+ prompt: agentPrompt("audit-log")
3365
+ },
3366
+ budget: MEDIUM_BUDGET,
3367
+ capability: {
3368
+ phase: "verify",
3369
+ summary: "Audit log analyzer: parses session JSONL, detects failure patterns, tool anomalies, and cost trends with structured reports.",
3370
+ keywords: [
3371
+ "audit",
3372
+ "log",
3373
+ "logs",
3374
+ "session",
3375
+ "trace",
3376
+ "analyze logs",
3377
+ "error patterns",
3378
+ "cost analysis",
3379
+ "tool usage",
3380
+ "token usage",
3381
+ "post-mortem",
3382
+ "trend",
3383
+ "anomaly"
3384
+ ]
3385
+ }
3386
+ }
3387
+ ];
3388
+
3389
+ // src/coordination/agents/phase5-review.ts
3390
+ var REVIEW_AGENTS = [
3391
+ {
3392
+ config: {
3393
+ id: "reviewer",
3394
+ name: "Reviewer",
3395
+ role: "reviewer",
3396
+ tools: [...TOOLS.inspect, "git"],
3397
+ prompt: agentPrompt("reviewer")
3398
+ },
3399
+ budget: MEDIUM_BUDGET,
3400
+ capability: {
3401
+ phase: "review",
3402
+ summary: "Independent AI reviewer: audits another agent's output, flags uncertainty, correlated-failure risk, and must-fix defects.",
3403
+ keywords: [
3404
+ "reviewer",
3405
+ "independent review",
3406
+ "ai review",
3407
+ "review agent",
3408
+ "quality review",
3409
+ "second opinion",
3410
+ "check another agent",
3411
+ "cross check",
3412
+ "uncertainty",
3413
+ "correlated error",
3414
+ "verify output",
3415
+ "quality control"
3416
+ ]
3417
+ }
3418
+ },
3419
+ {
3420
+ config: {
3421
+ id: "code-reviewer",
3422
+ name: "Code Reviewer",
3423
+ role: "code-reviewer",
3424
+ tools: [...TOOLS.inspect, "git"],
3425
+ prompt: agentPrompt("code-reviewer")
3426
+ },
3427
+ budget: MEDIUM_BUDGET,
3428
+ capability: {
3429
+ phase: "review",
3430
+ summary: "Correctness-first code review of diffs/PRs: finds bugs, edge cases, and convention violations with fixes.",
3431
+ keywords: [
3432
+ "review",
3433
+ "code review",
3434
+ "review pr",
3435
+ "review diff",
3436
+ "look over",
3437
+ "feedback on code",
3438
+ "quality",
3439
+ "is this correct",
3440
+ "check my code"
3441
+ ]
3442
+ }
3443
+ },
3444
+ {
3445
+ config: {
3446
+ id: "security-reviewer",
3447
+ name: "Security Reviewer",
3448
+ role: "security-reviewer",
3449
+ tools: [...TOOLS.inspect, "git"],
3450
+ prompt: agentPrompt("security-reviewer")
3451
+ },
3452
+ budget: MEDIUM_BUDGET,
3453
+ capability: {
3454
+ phase: "review",
3455
+ summary: "Security review: finds injection/authz/secret/crypto issues mapped to OWASP severity with remediation.",
3456
+ keywords: [
3457
+ "security review",
3458
+ "security",
3459
+ "vulnerability",
3460
+ "vulnerabilities",
3461
+ "owasp",
3462
+ "injection",
3463
+ "sql injection",
3464
+ "xss",
3465
+ "ssrf",
3466
+ "authz",
3467
+ "secrets",
3468
+ "security audit",
3469
+ "threat",
3470
+ "unsafe"
3471
+ ]
3472
+ }
3473
+ },
3474
+ {
3475
+ config: {
3476
+ id: "accessibility",
3477
+ name: "Accessibility",
3478
+ role: "accessibility",
3479
+ tools: [...TOOLS.read],
3480
+ prompt: agentPrompt("accessibility")
3481
+ },
3482
+ budget: MEDIUM_BUDGET,
3483
+ capability: {
3484
+ phase: "review",
3485
+ summary: "WCAG/a11y review of UI: checks semantics, ARIA, keyboard, contrast; maps findings to success criteria.",
3486
+ keywords: [
3487
+ "accessibility",
3488
+ "a11y",
3489
+ "wcag",
3490
+ "aria",
3491
+ "screen reader",
3492
+ "keyboard navigation",
3493
+ "contrast",
3494
+ "disabled users",
3495
+ "accessible"
3496
+ ]
3497
+ }
3498
+ },
3499
+ {
3500
+ config: {
3501
+ id: "compliance",
3502
+ name: "Compliance",
3503
+ role: "compliance",
3504
+ tools: [...TOOLS.inspect],
3505
+ prompt: agentPrompt("compliance")
3506
+ },
3507
+ budget: MEDIUM_BUDGET,
3508
+ capability: {
3509
+ phase: "review",
3510
+ summary: "License/privacy/regulatory review: audits licenses, PII handling, and controls vs GDPR/SOC2.",
3511
+ keywords: [
3512
+ "compliance",
3513
+ "license",
3514
+ "gdpr",
3515
+ "soc2",
3516
+ "privacy",
3517
+ "pii",
3518
+ "data retention",
3519
+ "regulatory",
3520
+ "audit log",
3521
+ "legal review"
3522
+ ]
3523
+ }
3524
+ }
3525
+ ];
3526
+
3527
+ // src/coordination/agents/phase6-domain.ts
3528
+ var DOMAIN_AGENTS = [
3529
+ {
3530
+ config: {
3531
+ id: "database",
3532
+ name: "Database",
3533
+ role: "database",
3534
+ tools: [...TOOLS.build],
3535
+ prompt: agentPrompt("database")
3536
+ },
3537
+ budget: HEAVY_BUDGET,
3538
+ capability: {
3539
+ phase: "domain",
3540
+ summary: "Schema design, query optimization, and safe reversible migrations for SQL databases.",
3541
+ keywords: [
3542
+ "database",
3543
+ "schema",
3544
+ "sql",
3545
+ "migration",
3546
+ "query",
3547
+ "index",
3548
+ "postgres",
3549
+ "mysql",
3550
+ "table",
3551
+ "orm",
3552
+ "slow query"
3553
+ ]
3554
+ }
3555
+ },
3556
+ {
3557
+ config: {
3558
+ id: "api",
3559
+ name: "API",
3560
+ role: "api",
3561
+ tools: [...TOOLS.build, "fetch"],
3562
+ prompt: agentPrompt("api")
3563
+ },
3564
+ budget: HEAVY_BUDGET,
3565
+ capability: {
3566
+ phase: "domain",
3567
+ summary: "REST + GraphQL API design and implementation: contracts, HTTP/GraphQL semantics, versioning.",
3568
+ keywords: [
3569
+ "api",
3570
+ "rest",
3571
+ "graphql",
3572
+ "endpoint",
3573
+ "resolver",
3574
+ "http",
3575
+ "openapi",
3576
+ "swagger",
3577
+ "route",
3578
+ "contract",
3579
+ "webhook"
3580
+ ]
3581
+ }
3582
+ },
3583
+ {
3584
+ config: {
3585
+ id: "auth",
3586
+ name: "Auth",
3587
+ role: "auth",
3588
+ tools: [...TOOLS.build],
3589
+ prompt: agentPrompt("auth")
3590
+ },
3591
+ budget: HEAVY_BUDGET,
3592
+ capability: {
3593
+ phase: "domain",
3594
+ summary: "Authentication and authorization: identity, sessions/tokens, RBAC/ABAC, OAuth/OIDC, done securely.",
3595
+ keywords: [
3596
+ "auth",
3597
+ "authentication",
3598
+ "authorization",
3599
+ "login",
3600
+ "session",
3601
+ "jwt",
3602
+ "oauth",
3603
+ "oidc",
3604
+ "rbac",
3605
+ "permissions",
3606
+ "token",
3607
+ "sso"
3608
+ ]
3609
+ }
3610
+ },
3611
+ {
3612
+ config: {
3613
+ id: "data",
3614
+ name: "Data",
3615
+ role: "data",
3616
+ tools: [...TOOLS.build],
3617
+ prompt: agentPrompt("data")
3618
+ },
3619
+ budget: HEAVY_BUDGET,
3620
+ capability: {
3621
+ phase: "domain",
3622
+ summary: "Data engineering: ETL/ELT pipelines, data-quality validation, idempotent transforms, reconciliation.",
3623
+ keywords: [
3624
+ "etl",
3625
+ "elt",
3626
+ "pipeline",
3627
+ "data quality",
3628
+ "data engineering",
3629
+ "transform",
3630
+ "ingestion",
3631
+ "batch",
3632
+ "stream",
3633
+ "reconcile",
3634
+ "dataset"
3635
+ ]
3636
+ }
3637
+ },
3638
+ {
3639
+ config: {
3640
+ id: "frontend",
3641
+ name: "Frontend",
3642
+ role: "frontend",
3643
+ tools: [...TOOLS.build, "fetch"],
3644
+ prompt: agentPrompt("frontend")
3645
+ },
3646
+ budget: HEAVY_BUDGET,
3647
+ capability: {
3648
+ phase: "domain",
3649
+ summary: "UI implementation: components, client state, data fetching, responsive and accessible by default.",
3650
+ keywords: [
3651
+ "frontend",
3652
+ "component",
3653
+ "react",
3654
+ "vue",
3655
+ "svelte",
3656
+ "client state",
3657
+ "ui implementation",
3658
+ "css",
3659
+ "responsive",
3660
+ "hook",
3661
+ "render"
3662
+ ]
3663
+ }
3664
+ },
3665
+ {
3666
+ config: {
3667
+ id: "backend",
3668
+ name: "Backend",
3669
+ role: "backend",
3670
+ tools: [...TOOLS.build],
3671
+ prompt: agentPrompt("backend")
3672
+ },
3673
+ budget: HEAVY_BUDGET,
3674
+ capability: {
3675
+ phase: "domain",
3676
+ summary: "Server-side logic: services, business rules, persistence/queue wiring, concurrency and transactions.",
3677
+ keywords: [
3678
+ "backend",
3679
+ "server",
3680
+ "service",
3681
+ "business logic",
3682
+ "controller",
3683
+ "handler",
3684
+ "queue",
3685
+ "cache",
3686
+ "transaction",
3687
+ "microservice",
3688
+ "server-side"
3689
+ ]
3690
+ }
3691
+ },
3692
+ {
3693
+ config: {
3694
+ id: "designer",
3695
+ name: "Designer",
3696
+ role: "designer",
3697
+ tools: [...TOOLS.docs],
3698
+ prompt: agentPrompt("designer")
3699
+ },
3700
+ budget: MEDIUM_BUDGET,
3701
+ capability: {
3702
+ phase: "domain",
3703
+ summary: "UI/UX design: user flows, layout/wireframes, interaction states, and design-system decisions.",
3704
+ keywords: [
3705
+ "design",
3706
+ "ux",
3707
+ "ui design",
3708
+ "wireframe",
3709
+ "user flow",
3710
+ "layout",
3711
+ "design system",
3712
+ "interaction",
3713
+ "mockup design",
3714
+ "information architecture"
3715
+ ]
3716
+ }
3717
+ },
3718
+ {
3719
+ config: {
3720
+ id: "ios",
3721
+ name: "iOS",
3722
+ role: "ios",
3723
+ tools: [...TOOLS.build, "fetch"],
3724
+ prompt: agentPrompt("ios")
3725
+ },
3726
+ budget: HEAVY_BUDGET,
3727
+ capability: {
3728
+ phase: "domain",
3729
+ summary: "Apple-platform app development in Swift against the latest Xcode/iOS-SDK line: SwiftUI, UIKit, SwiftData, concurrency, accessibility, and App Store submission.",
3730
+ keywords: [
3731
+ "ios",
3732
+ "iphone",
3733
+ "ipad",
3734
+ "ipados",
3735
+ "watchos",
3736
+ "tvos",
3737
+ "visionos",
3738
+ "macos",
3739
+ "swift",
3740
+ "swiftui",
3741
+ "uikit",
3742
+ "xcode",
3743
+ "swiftdata",
3744
+ "app store",
3745
+ "app intents",
3746
+ "swift package manager",
3747
+ "cocoapods",
3748
+ "foundation models"
3749
+ ]
3750
+ }
3751
+ }
3752
+ ];
3753
+
3754
+ // src/coordination/agents/phase7-knowledge.ts
3755
+ var KNOWLEDGE_AGENTS = [
3756
+ {
3757
+ config: {
3758
+ id: "document",
3759
+ name: "Document",
3760
+ role: "document",
3761
+ tools: [...TOOLS.docs],
3762
+ prompt: agentPrompt("document")
3763
+ },
3764
+ budget: MEDIUM_BUDGET,
3765
+ capability: {
3766
+ phase: "knowledge",
3767
+ summary: "Technical documentation: READMEs, API/reference docs, guides, and verified examples grounded in code.",
3768
+ keywords: [
3769
+ "document",
3770
+ "documentation",
3771
+ "readme",
3772
+ "docs",
3773
+ "write up",
3774
+ "guide",
3775
+ "api docs",
3776
+ "explain in writing",
3777
+ "reference",
3778
+ "changelog notes"
3779
+ ]
3780
+ }
3781
+ },
3782
+ {
3783
+ config: {
3784
+ id: "uml",
3785
+ name: "UML",
3786
+ role: "uml",
3787
+ tools: [...TOOLS.read, "write", "edit"],
3788
+ prompt: agentPrompt("uml")
3789
+ },
3790
+ budget: LIGHT_BUDGET,
3791
+ capability: {
3792
+ phase: "knowledge",
3793
+ summary: "Diagram generation from code: class/sequence/component/ER diagrams as Mermaid/PlantUML.",
3794
+ keywords: [
3795
+ "uml",
3796
+ "diagram",
3797
+ "mermaid",
3798
+ "plantuml",
3799
+ "sequence diagram",
3800
+ "class diagram",
3801
+ "er diagram",
3802
+ "visualize",
3803
+ "flowchart",
3804
+ "architecture diagram"
3805
+ ]
3806
+ }
3807
+ },
3808
+ {
3809
+ config: {
3810
+ id: "i18n",
3811
+ name: "I18n",
3812
+ role: "i18n",
3813
+ tools: [...TOOLS.write],
3814
+ prompt: agentPrompt("i18n")
3815
+ },
3816
+ budget: MEDIUM_BUDGET,
3817
+ capability: {
3818
+ phase: "knowledge",
3819
+ summary: "Internationalization/localization: string extraction, catalog management, plurals/RTL/format handling.",
3820
+ keywords: [
3821
+ "i18n",
3822
+ "internationalization",
3823
+ "localization",
3824
+ "l10n",
3825
+ "translation",
3826
+ "translate ui",
3827
+ "locale",
3828
+ "rtl",
3829
+ "message catalog",
3830
+ "multilingual"
3831
+ ]
3832
+ }
3833
+ },
3834
+ {
3835
+ config: {
3836
+ id: "prompt",
3837
+ name: "Prompt",
3838
+ role: "prompt",
3839
+ tools: [...TOOLS.write],
3840
+ prompt: agentPrompt("prompt")
3841
+ },
3842
+ budget: LIGHT_BUDGET,
3843
+ capability: {
3844
+ phase: "knowledge",
3845
+ summary: "Prompt engineering: designs/refines/evaluates LLM system prompts and agent instructions.",
3846
+ keywords: [
3847
+ "prompt",
3848
+ "prompt engineering",
3849
+ "system prompt",
3850
+ "llm instructions",
3851
+ "few-shot",
3852
+ "refine prompt",
3853
+ "agent instructions",
3854
+ "prompt template"
3855
+ ]
3856
+ }
3857
+ }
3858
+ ];
3859
+
3860
+ // src/coordination/agents/phase8-delivery.ts
3861
+ var DELIVERY_AGENTS = [
3862
+ {
3863
+ config: {
3864
+ id: "git",
3865
+ name: "Git",
3866
+ role: "git",
3867
+ tools: [...TOOLS.vcs, "bash"],
3868
+ prompt: agentPrompt("git")
3869
+ },
3870
+ budget: MEDIUM_BUDGET,
3871
+ capability: {
3872
+ phase: "delivery",
3873
+ summary: "Git automation: focused commits, branch/rebase/conflict handling, PR prep, history investigation.",
3874
+ keywords: [
3875
+ "git",
3876
+ "commit",
3877
+ "branch",
3878
+ "rebase",
3879
+ "merge",
3880
+ "pull request",
3881
+ "pr",
3882
+ "conflict",
3883
+ "blame",
3884
+ "bisect",
3885
+ "cherry-pick",
3886
+ "stash"
3887
+ ]
3888
+ }
3889
+ },
3890
+ {
3891
+ config: {
3892
+ id: "release",
3893
+ name: "Release",
3894
+ role: "release",
3895
+ tools: [...TOOLS.vcs, "bash", "json"],
3896
+ prompt: agentPrompt("release")
3897
+ },
3898
+ budget: MEDIUM_BUDGET,
3899
+ capability: {
3900
+ phase: "delivery",
3901
+ summary: "Release management: semver bumps, changelogs, and release notes derived from real history.",
3902
+ keywords: [
3903
+ "release",
3904
+ "version",
3905
+ "semver",
3906
+ "changelog",
3907
+ "release notes",
3908
+ "tag",
3909
+ "bump version",
3910
+ "publish",
3911
+ "versioning"
3912
+ ]
3913
+ }
3914
+ },
3915
+ {
3916
+ config: {
3917
+ id: "devops",
3918
+ name: "DevOps",
3919
+ role: "devops",
3920
+ tools: [
3921
+ ...TOOLS.build,
3922
+ "mcp__ssh__ssh_list_servers",
3923
+ "mcp__ssh__ssh_connection_status",
3924
+ "mcp__ssh__ssh_execute",
3925
+ "mcp__ssh__ssh_execute_sudo",
3926
+ "mcp__ssh__ssh_upload",
3927
+ "mcp__ssh__ssh_download",
3928
+ "mcp__ssh__ssh_sync",
3929
+ "mcp__ssh__ssh_deploy",
3930
+ "mcp__ssh__ssh_health_check",
3931
+ "mcp__ssh__ssh_service_status",
3932
+ "mcp__ssh__ssh_process_manager",
3933
+ "mcp__ssh__ssh_tunnel",
3934
+ "mcp__ssh__ssh_backup_create",
3935
+ "mcp__ssh__ssh_backup_list",
3936
+ "mcp__ssh__ssh_backup_restore",
3937
+ "mcp__ssh__ssh_db_list",
3938
+ "mcp__ssh__ssh_db_query",
3939
+ "mcp__ssh__ssh_profile"
3940
+ ],
3941
+ prompt: agentPrompt("devops")
3942
+ },
3943
+ budget: MEDIUM_BUDGET,
3944
+ capability: {
3945
+ phase: "delivery",
3946
+ summary: "CI/CD, containerization, and deployment config: reproducible builds and safe deploys with rollback.",
3947
+ keywords: [
3948
+ "devops",
3949
+ "ci",
3950
+ "cd",
3951
+ "ci/cd",
3952
+ "pipeline",
3953
+ "docker",
3954
+ "dockerfile",
3955
+ "kubernetes",
3956
+ "k8s",
3957
+ "deploy",
3958
+ "ssh",
3959
+ "remote ssh",
3960
+ "remote server",
3961
+ "sftp",
3962
+ "tunnel",
3963
+ "bastion",
3964
+ "jump host",
3965
+ "github actions",
3966
+ "container"
3967
+ ]
3968
+ }
3969
+ },
3970
+ {
3971
+ config: {
3972
+ id: "observability",
3973
+ name: "Observability",
3974
+ role: "observability",
3975
+ tools: [...TOOLS.build, "logs"],
3976
+ prompt: agentPrompt("observability")
3977
+ },
3978
+ budget: MEDIUM_BUDGET,
3979
+ capability: {
3980
+ phase: "delivery",
3981
+ summary: "Observability: structured logging, metrics, distributed tracing, and alerts/dashboards.",
3982
+ keywords: [
3983
+ "observability",
3984
+ "logging",
3985
+ "metrics",
3986
+ "tracing",
3987
+ "telemetry",
3988
+ "opentelemetry",
3989
+ "otel",
3990
+ "prometheus",
3991
+ "monitoring",
3992
+ "alert",
3993
+ "dashboard",
3994
+ "instrument"
3995
+ ]
3996
+ }
3997
+ },
3998
+ {
3999
+ config: {
4000
+ id: "dependency",
4001
+ name: "Dependency",
4002
+ role: "dependency",
4003
+ tools: [...TOOLS.deps, "bash"],
4004
+ prompt: agentPrompt("dependency")
4005
+ },
4006
+ budget: MEDIUM_BUDGET,
4007
+ capability: {
4008
+ phase: "delivery",
4009
+ summary: "Package management + supply-chain safety: CVE audit, safe upgrades, pruning, install-script review.",
4010
+ keywords: [
4011
+ "dependency",
4012
+ "dependencies",
4013
+ "package",
4014
+ "npm",
4015
+ "pnpm",
4016
+ "cve",
4017
+ "vulnerability scan",
4018
+ "upgrade deps",
4019
+ "audit",
4020
+ "supply chain",
4021
+ "outdated",
4022
+ "lockfile"
4023
+ ]
4024
+ }
4025
+ }
4026
+ ];
4027
+
4028
+ // src/coordination/agents/phase9-meta.ts
4029
+ var META_AGENTS = [
4030
+ {
4031
+ config: {
4032
+ id: "skill-manage",
4033
+ name: "Skill Manager",
4034
+ role: "skill-manage",
4035
+ tools: [...TOOLS.write],
4036
+ prompt: agentPrompt("skill-manage")
4037
+ },
4038
+ budget: LIGHT_BUDGET,
4039
+ capability: {
4040
+ phase: "meta",
4041
+ summary: "Skill curation: audits, refines descriptions/triggers, scaffolds, and retires skills.",
4042
+ keywords: [
4043
+ "skill",
4044
+ "skills",
4045
+ "curate skill",
4046
+ "skill description",
4047
+ "create skill",
4048
+ "skill library",
4049
+ "skill trigger",
4050
+ "manage skills"
4051
+ ]
4052
+ }
4053
+ },
4054
+ {
4055
+ config: {
4056
+ id: "self-improving",
4057
+ name: "Self-Improving",
4058
+ role: "self-improving",
4059
+ tools: [...TOOLS.inspect],
4060
+ prompt: agentPrompt("self-improving")
4061
+ },
4062
+ budget: MEDIUM_BUDGET,
4063
+ capability: {
4064
+ phase: "meta",
4065
+ summary: "Learns from execution logs: mines recurring failures/inefficiencies and proposes evidence-based improvements.",
4066
+ keywords: [
4067
+ "self-improving",
4068
+ "learn from",
4069
+ "session logs",
4070
+ "execution analysis",
4071
+ "recurring failure",
4072
+ "improve agents",
4073
+ "post-mortem",
4074
+ "retrospective",
4075
+ "meta-analysis"
4076
+ ]
4077
+ }
4078
+ },
4079
+ {
4080
+ config: {
4081
+ id: "context",
4082
+ name: "Context",
4083
+ role: "context",
4084
+ tools: [...TOOLS.inspect, "remember", "forget"],
4085
+ prompt: agentPrompt("context")
4086
+ },
4087
+ budget: LIGHT_BUDGET,
4088
+ capability: {
4089
+ phase: "meta",
4090
+ summary: "Memory + context-window management: compaction, recall, and curation within a token budget.",
4091
+ keywords: [
4092
+ "context",
4093
+ "context window",
4094
+ "memory",
4095
+ "compact",
4096
+ "summarize history",
4097
+ "recall",
4098
+ "token budget",
4099
+ "prune context",
4100
+ "remember",
4101
+ "dfmt"
4102
+ ]
4103
+ }
4104
+ },
4105
+ {
4106
+ config: {
4107
+ id: "cost",
4108
+ name: "Cost",
4109
+ role: "cost",
4110
+ tools: [...TOOLS.inspect],
4111
+ prompt: agentPrompt("cost")
4112
+ },
4113
+ budget: LIGHT_BUDGET,
4114
+ capability: {
4115
+ phase: "meta",
4116
+ summary: "Token/cloud cost optimization: finds spend waste, recommends model routing and trimming with $ estimates.",
4117
+ keywords: [
4118
+ "cost",
4119
+ "token cost",
4120
+ "optimize cost",
4121
+ "spend",
4122
+ "cheaper",
4123
+ "model routing",
4124
+ "budget",
4125
+ "expensive",
4126
+ "reduce tokens",
4127
+ "pricing",
4128
+ "cloud cost"
4129
+ ]
4130
+ }
4131
+ },
4132
+ {
4133
+ config: {
4134
+ id: "tech-stack",
4135
+ name: "Tech Stack Validator",
4136
+ role: "tech-stack",
4137
+ tools: ["search", "fetch", "read", "grep", "glob", "outdated", "audit", "json", "mailbox"],
4138
+ prompt: agentPrompt("tech-stack")
4139
+ },
4140
+ budget: {
4141
+ timeoutMs: 12e4,
4142
+ maxIterations: 10,
4143
+ maxToolCalls: 40,
4144
+ maxTokens: 6e4,
4145
+ maxCostUsd: 0.25
4146
+ },
4147
+ capability: {
4148
+ phase: "meta",
4149
+ summary: "Single-shot tech stack validator: checks npm for latest versions, rejects dead/obsolete packages, enforces modern alternatives.",
4150
+ keywords: [
4151
+ "tech stack",
4152
+ "version",
4153
+ "package",
4154
+ "library",
4155
+ "framework",
4156
+ "dependency",
4157
+ "install",
4158
+ "upgrade",
4159
+ "latest",
4160
+ "npm",
4161
+ "pnpm add",
4162
+ "outdated",
4163
+ "obsolete",
4164
+ "deprecated",
4165
+ "what version",
4166
+ "which package",
4167
+ "check version",
4168
+ "verify version",
4169
+ "is this current"
4170
+ ]
4171
+ }
4172
+ }
4173
+ ];
4174
+
4175
+ // src/coordination/agents/index.ts
4176
+ var ALL_AGENT_DEFINITIONS = [
4177
+ ...DISCOVERY_AGENTS,
4178
+ ...PLANNING_AGENTS,
4179
+ ...BUILD_AGENTS,
4180
+ ...VERIFY_AGENTS,
4181
+ ...REVIEW_AGENTS,
4182
+ ...DOMAIN_AGENTS,
4183
+ ...KNOWLEDGE_AGENTS,
4184
+ ...DELIVERY_AGENTS,
4185
+ ...META_AGENTS
4186
+ ];
4187
+ var AGENTS_BY_PHASE = {
4188
+ discovery: DISCOVERY_AGENTS,
4189
+ planning: PLANNING_AGENTS,
4190
+ build: BUILD_AGENTS,
4191
+ verify: VERIFY_AGENTS,
4192
+ review: REVIEW_AGENTS,
4193
+ domain: DOMAIN_AGENTS,
4194
+ knowledge: KNOWLEDGE_AGENTS,
4195
+ delivery: DELIVERY_AGENTS,
4196
+ meta: META_AGENTS
4197
+ };
4198
+ var AGENT_CATALOG = (() => {
4199
+ const map = {};
4200
+ for (const def of ALL_AGENT_DEFINITIONS) {
4201
+ const role = def.config.role;
4202
+ if (!role) {
4203
+ throw new Error(`Agent "${def.config.name}" is missing a role`);
4204
+ }
4205
+ if (map[role]) {
4206
+ throw new Error(`Duplicate agent role in catalog: "${role}"`);
4207
+ }
4208
+ map[role] = def;
4209
+ }
4210
+ return map;
4211
+ })();
4212
+
4213
+ // src/core/fallback-profile-manager.ts
4214
+ function hasText(value) {
4215
+ return typeof value === "string" && value.trim().length > 0;
4216
+ }
4217
+ function providerHasKey(entry) {
4218
+ if (!entry) return false;
4219
+ if (hasText(entry.apiKey)) return true;
4220
+ if (Array.isArray(entry.apiKeys) && entry.apiKeys.some((k) => hasText(k?.apiKey))) return true;
4221
+ if (Array.isArray(entry.envVars) && entry.envVars.some((v) => hasText(process.env[v])))
4222
+ return true;
4223
+ return false;
4224
+ }
4225
+ function visibleProviderModels(config, providerId, providerModels) {
4226
+ const entry = config.providers?.[providerId];
4227
+ return entry?.models !== void 0 ? [...entry.models] : providerModels;
4228
+ }
4229
+ function buildProfiles(config) {
4230
+ const entries = /* @__PURE__ */ new Map();
4231
+ for (const [name, chain] of Object.entries(config.fallbackProfiles ?? {})) {
4232
+ if (Array.isArray(chain) && chain.length > 0) {
4233
+ entries.set(name, Object.freeze([...chain]));
4234
+ }
4235
+ }
4236
+ return entries;
4237
+ }
4238
+ var FallbackProfileManager = class {
4239
+ /** Immutable snapshot of config.fallbackProfiles for the active config. */
4240
+ profiles;
4241
+ /** Active frozen config snapshot for provider lookups. */
4242
+ config;
4243
+ /** Optional shared runtime status tracker. */
4244
+ statusTracker;
4245
+ constructor(config, opts) {
4246
+ this.config = config;
4247
+ this.profiles = buildProfiles(config);
4248
+ this.statusTracker = opts?.statusTracker;
4249
+ }
4250
+ /**
4251
+ * Bind (or replace) the shared runtime status tracker.
4252
+ * Called by the boot path after the tracker is created.
4253
+ */
4254
+ setStatusTracker(tracker) {
4255
+ this.statusTracker = tracker;
4256
+ }
4257
+ // ── Profile existence ──────────────────────────────────────────────────
4258
+ hasProfile(name) {
4259
+ return this.profiles.has(name);
4260
+ }
4261
+ listProfiles() {
4262
+ return Object.freeze([...this.profiles.keys()]);
4263
+ }
4264
+ // ── Resolution ─────────────────────────────────────────────────────────
4265
+ /**
4266
+ * Resolve a named fallback profile to a validated, provider-filtered chain.
4267
+ *
4268
+ * Returns an empty chain when:
4269
+ * - The profile doesn't exist.
4270
+ * - Every entry's provider is missing, has no key, or has no matching model.
4271
+ *
4272
+ * @param name - Profile name from config.fallbackProfiles.
4273
+ * @param defaultProvider - Used when an entry has no explicit provider.
4274
+ * @param exclude - Optional { providerId, model } to skip (avoid self-fallback).
4275
+ */
4276
+ resolve(name, opts = {}) {
4277
+ const defaultProvider = opts.defaultProvider ?? this.config.provider;
4278
+ const chain = this.profiles.get(name);
4279
+ if (!chain) return FREEZER_EMPTY;
4280
+ const excludeKey = opts.exclude ? `${opts.exclude.providerId}/${opts.exclude.model}` : void 0;
4281
+ const resolved = [];
4282
+ const seen = /* @__PURE__ */ new Set();
4283
+ for (const ref of chain) {
4284
+ const parsed = parseModelRef(ref);
4285
+ if (!parsed.model) continue;
4286
+ const providerId = parsed.provider ?? defaultProvider;
4287
+ const key = `${providerId}/${parsed.model}`;
4288
+ if (seen.has(key)) continue;
4289
+ seen.add(key);
4290
+ if (excludeKey && key === excludeKey) continue;
4291
+ const health = this.checkProvider(providerId);
4292
+ if (!health.usable) continue;
4293
+ if (this.statusTracker && !this.statusTracker.isAvailable(providerId, parsed.model)) continue;
4294
+ if (!evaluateModelCalendar(this.config.modelAvailabilitySchedule, providerId, parsed.model).allowed)
4295
+ continue;
4296
+ const allowedModels = this.config.providers?.[providerId]?.models;
4297
+ if (allowedModels && !allowedModels.includes(parsed.model)) continue;
4298
+ resolved.push({
4299
+ providerId,
4300
+ model: parsed.model,
4301
+ providerSwitched: providerId !== (opts.exclude?.providerId ?? this.config.provider)
4302
+ });
4303
+ }
4304
+ return Object.freeze(resolved);
4305
+ }
4306
+ /**
4307
+ * Resolve the effective fallback chain for a session: explicit fallbackModels
4308
+ * first, then named profile, then smart default (unless disabled).
4309
+ *
4310
+ * Mirrors the previous `effectiveFallbackChain()` logic but centralized.
4311
+ */
4312
+ resolveEffective(opts = {}) {
4313
+ if (opts.fallbackModels && opts.fallbackModels.length > 0) {
4314
+ const resolved = this.resolveRefs(opts.fallbackModels, opts.exclude);
4315
+ if (resolved.length > 0) return resolved;
4316
+ }
4317
+ if (opts.fallbackProfile) {
4318
+ const resolved = this.resolve(opts.fallbackProfile, { exclude: opts.exclude });
4319
+ if (resolved.length > 0) return resolved;
4320
+ }
4321
+ if (opts.fallbackAuto !== false) {
4322
+ return this.smartDefault(opts.exclude);
4323
+ }
4324
+ return FREEZER_EMPTY;
4325
+ }
4326
+ // ── Provider availability (read-only) ──────────────────────────────────
4327
+ checkProvider(providerId) {
4328
+ const entry = this.config.providers?.[providerId];
4329
+ const isPrimary = providerId === this.config.provider;
4330
+ const hasKey = providerHasKey(entry) || isPrimary && hasText(this.config.apiKey);
4331
+ const hasEndpoint = hasText(entry?.baseUrl) || isPrimary && hasText(this.config.baseUrl);
4332
+ const hasModels = Array.isArray(entry?.models) && entry.models.length > 0 || isPrimary && hasText(this.config.model);
4333
+ return Object.freeze({
4334
+ providerId,
4335
+ hasKey,
4336
+ hasEndpoint,
4337
+ hasModels,
4338
+ usable: hasKey || hasEndpoint
4339
+ });
4340
+ }
4341
+ // ── Rebuild on config change ───────────────────────────────────────────
4342
+ /**
4343
+ * Atomically replace the active immutable snapshot while preserving this
4344
+ * service identity for every injected consumer.
4345
+ */
4346
+ reload(newConfig) {
4347
+ this.config = newConfig;
4348
+ this.profiles = buildProfiles(newConfig);
4349
+ }
4350
+ // ── Internal helpers ───────────────────────────────────────────────────
4351
+ /**
4352
+ * Resolve an array of model ref strings (from explicit fallbackModels config).
4353
+ * Public so consumers like one-shot-llm can use it directly.
4354
+ */
4355
+ resolveRefs(refs, exclude) {
4356
+ const excludeKey = exclude ? `${exclude.providerId}/${exclude.model}` : void 0;
4357
+ const resolved = [];
4358
+ const seen = /* @__PURE__ */ new Set();
4359
+ for (const ref of refs) {
4360
+ const parsed = parseModelRef(ref);
4361
+ if (!parsed.model) continue;
4362
+ const providerId = parsed.provider ?? this.config.provider;
4363
+ const key = `${providerId}/${parsed.model}`;
4364
+ if (seen.has(key)) continue;
4365
+ seen.add(key);
4366
+ if (excludeKey && key === excludeKey) continue;
4367
+ if (this.statusTracker && !this.statusTracker.isAvailable(providerId, parsed.model)) continue;
4368
+ if (!evaluateModelCalendar(this.config.modelAvailabilitySchedule, providerId, parsed.model).allowed)
4369
+ continue;
4370
+ resolved.push({
4371
+ providerId,
4372
+ model: parsed.model,
4373
+ providerSwitched: providerId !== (exclude?.providerId ?? this.config.provider)
4374
+ });
4375
+ }
4376
+ return Object.freeze(resolved);
4377
+ }
4378
+ /**
4379
+ * Derive a smart default chain from configured providers when nothing
4380
+ * explicit is set. Same-provider alternatives first, then cross-provider.
4381
+ * Limited to 4 entries to avoid burning through models on a transient blip.
4382
+ */
4383
+ smartDefault(exclude) {
4384
+ const leaderProvider = this.config.provider;
4385
+ const leaderModel = this.config.model;
4386
+ const providers = this.config.providers ?? {};
4387
+ const favoriteSet = new Set(
4388
+ (this.config.favoriteModels ?? []).map((ref) => {
4389
+ const p = parseModelRef(ref);
4390
+ return `${p.provider ?? leaderProvider}/${p.model}`;
4391
+ })
4392
+ );
4393
+ const hasFavorites = favoriteSet.size > 0;
4394
+ const favoritesOnly = this.config.favoriteModelsOnly === true;
4395
+ const seen = /* @__PURE__ */ new Set();
4396
+ const favorites = [];
4397
+ const sameProvider = [];
4398
+ const crossProvider = [];
4399
+ const excludeKey = exclude ? `${exclude.providerId}/${exclude.model}` : void 0;
4400
+ const ids = Object.keys(providers).sort(
4401
+ (a, b) => a === leaderProvider ? -1 : b === leaderProvider ? 1 : a.localeCompare(b)
4402
+ );
4403
+ for (const id of ids) {
4404
+ const entry = providers[id];
4405
+ if (!this.checkProvider(id).usable) continue;
4406
+ const models = visibleProviderModels(this.config, id, entry?.models ?? []);
4407
+ for (const model of models) {
4408
+ if (id === leaderProvider && model === leaderModel) continue;
4409
+ const ref = `${id}/${model}`;
4410
+ if (seen.has(ref)) continue;
4411
+ seen.add(ref);
4412
+ if (excludeKey && ref === excludeKey) continue;
4413
+ if (this.statusTracker && !this.statusTracker.isAvailable(id, model)) continue;
4414
+ if (!evaluateModelCalendar(this.config.modelAvailabilitySchedule, id, model).allowed)
4415
+ continue;
4416
+ if (favoriteSet.has(ref)) {
4417
+ favorites.push(ref);
4418
+ continue;
4419
+ }
4420
+ if (favoritesOnly && hasFavorites) continue;
4421
+ (id === leaderProvider ? sameProvider : crossProvider).push(ref);
4422
+ }
4423
+ }
4424
+ const MAX = 4;
4425
+ const all = [...favorites, ...sameProvider, ...crossProvider].slice(0, MAX);
4426
+ return Object.freeze(
4427
+ all.map((ref) => {
4428
+ const p = parseModelRef(ref);
4429
+ return {
4430
+ providerId: p.provider ?? leaderProvider,
4431
+ model: p.model,
4432
+ providerSwitched: (p.provider ?? leaderProvider) !== (exclude?.providerId ?? leaderProvider)
4433
+ };
4434
+ })
4435
+ );
4436
+ }
4437
+ };
4438
+ var FREEZER_EMPTY = Object.freeze([]);
4439
+
4440
+ // src/core/fallback-model.ts
4441
+ function parseModelRef(ref) {
4442
+ const trimmed = ref.trim();
4443
+ const slash = trimmed.indexOf("/");
4444
+ if (slash !== -1) {
4445
+ return {
4446
+ provider: trimmed.slice(0, slash) || void 0,
4447
+ model: trimmed.slice(slash + 1).trim()
4448
+ };
4449
+ }
4450
+ const parts = trimmed.split(/\s+/);
4451
+ if (parts.length >= 2) {
4452
+ return { provider: parts[0], model: parts.slice(1).join(" ") };
4453
+ }
4454
+ return { model: trimmed };
4455
+ }
4456
+ function formatModelRef(ref, defaultProvider) {
4457
+ const provider = ref.provider ?? defaultProvider;
4458
+ return provider ? `${provider}/${ref.model}` : ref.model;
4459
+ }
4460
+ function normalizeModelRef(ref, defaultProvider) {
4461
+ const parsed = parseModelRef(ref);
4462
+ return formatModelRef(parsed, defaultProvider);
4463
+ }
4464
+ function fallbackProfileChain(config, profileName) {
4465
+ if (!profileName) return [];
4466
+ const mgr = new FallbackProfileManager(config);
4467
+ return mgr.resolve(profileName).map((e) => `${e.providerId}/${e.model}`);
4468
+ }
4469
+ var DEFAULT_PRIMARY_COOLDOWN_MAX_MS = 10 * 6e4;
4470
+
4471
+ // src/coordination/model-matrix.ts
4472
+ var MATRIX_PHASE_KEYS = Object.keys(AGENTS_BY_PHASE);
4473
+ var ROLE_TO_PHASE = (() => {
4474
+ const map = {};
4475
+ for (const [phase, defs] of Object.entries(AGENTS_BY_PHASE)) {
4476
+ for (const def of defs) {
4477
+ const role = def.config.role;
4478
+ if (role) map[role] = phase;
4479
+ }
4480
+ }
4481
+ return map;
4482
+ })();
4483
+ function phaseForRole(role) {
4484
+ return role ? ROLE_TO_PHASE[role] : void 0;
4485
+ }
4486
+ function resolveModelMatrixResolution(matrix, role) {
4487
+ if (!matrix) return void 0;
4488
+ if (role && matrix[role]) return { entry: matrix[role], source: "role", key: role };
4489
+ const phase = phaseForRole(role);
4490
+ if (phase && matrix[phase]) return { entry: matrix[phase], source: "phase", key: phase };
4491
+ if (matrix["*"]) return { entry: matrix["*"], source: "default", key: "*" };
4492
+ return void 0;
4493
+ }
4494
+ function resolveModelMatrix(matrix, role) {
4495
+ return resolveModelMatrixResolution(matrix, role)?.entry;
4496
+ }
4497
+ function resolveModelTargetFromEntry(config, entry) {
4498
+ if (!entry) return void 0;
4499
+ if (entry.model) {
4500
+ return {
4501
+ provider: entry.provider,
4502
+ model: entry.model,
4503
+ modelRuntime: entry.modelRuntime,
4504
+ fallbackProfile: entry.fallbackProfile,
4505
+ fallbackModels: fallbackProfileChain(config, entry.fallbackProfile)
4506
+ };
4507
+ }
4508
+ const chain = fallbackProfileChain(config, entry.fallbackProfile);
4509
+ const first = chain[0];
4510
+ if (!first) {
4511
+ return entry.modelRuntime ? { modelRuntime: entry.modelRuntime } : void 0;
4512
+ }
4513
+ const parsed = parseModelRef(first);
4514
+ return {
4515
+ provider: parsed.provider,
4516
+ model: parsed.model,
4517
+ modelRuntime: entry.modelRuntime,
4518
+ fallbackProfile: entry.fallbackProfile,
4519
+ fallbackModels: chain.slice(1)
4520
+ };
4521
+ }
4522
+ function roleNeedsIndependentReviewModel(role) {
4523
+ if (!role) return false;
4524
+ return role === "reviewer" || phaseForRole(role) === "review";
4525
+ }
4526
+ function resolveSubagentModelTarget(config, role, opts = {}) {
4527
+ const resolution = resolveModelMatrixResolution(config.modelMatrix, role);
4528
+ const matrixTarget = resolveModelTargetFromEntry(config, resolution?.entry);
4529
+ const implementationTarget = opts.implementationTarget ?? resolveImplementationModelTarget(config);
4530
+ if (!roleNeedsIndependentReviewModel(role)) {
4531
+ if (!matrixTarget) return void 0;
4532
+ return {
4533
+ ...matrixTarget,
4534
+ source: "matrix",
4535
+ matrixSource: resolution?.source
4536
+ };
4537
+ }
4538
+ const matrixRef = materializeTarget(config, matrixTarget);
4539
+ if (resolution?.source === "role" || resolution?.source === "phase") {
4540
+ return matrixTarget ? { ...matrixTarget, source: "matrix", matrixSource: resolution.source } : void 0;
4541
+ }
4542
+ if (matrixRef && !sameModelReference(matrixRef, implementationTarget)) {
4543
+ return {
4544
+ ...matrixTarget ?? {},
4545
+ source: "matrix",
4546
+ matrixSource: resolution?.source
4547
+ };
4548
+ }
4549
+ const diverse = chooseDiverseModelTarget(config, implementationTarget);
4550
+ if (diverse) {
4551
+ return {
4552
+ provider: diverse.provider,
4553
+ model: diverse.model,
4554
+ modelRuntime: matrixTarget?.modelRuntime,
4555
+ fallbackModels: matrixTarget?.fallbackModels,
4556
+ fallbackProfile: matrixTarget?.fallbackProfile,
4557
+ source: "diversity",
4558
+ matrixSource: resolution?.source,
4559
+ diversified: true
4560
+ };
4561
+ }
4562
+ if (!matrixTarget) return void 0;
4563
+ return {
4564
+ ...matrixTarget,
4565
+ source: "matrix",
4566
+ matrixSource: resolution?.source
4567
+ };
4568
+ }
4569
+ function resolveImplementationModelTarget(config) {
4570
+ const target = resolveModelTargetFromEntry(
4571
+ config,
4572
+ resolveModelMatrix(config.modelMatrix, "executor")
4573
+ );
4574
+ return materializeTarget(config, target) ?? {
4575
+ provider: config.provider,
4576
+ model: config.model
4577
+ };
4578
+ }
4579
+ function sameModelReference(a, b) {
4580
+ if (!a?.model || !b?.model) return false;
4581
+ const providerA = a.provider ?? "";
4582
+ const providerB = b.provider ?? "";
4583
+ return providerA === providerB && a.model === b.model;
4584
+ }
4585
+ function materializeTarget(config, target) {
4586
+ if (!target?.model) return void 0;
4587
+ return {
4588
+ provider: target.provider ?? config.provider,
4589
+ model: target.model
4590
+ };
4591
+ }
4592
+ function chooseDiverseModelTarget(config, avoid) {
4593
+ const candidates = collectConfiguredModelTargets(config).filter(
4594
+ (candidate) => !sameModelReference(candidate, avoid)
4595
+ );
4596
+ candidates.sort((a, b) => modelDiversityScore(b, avoid) - modelDiversityScore(a, avoid));
4597
+ return candidates[0];
4598
+ }
4599
+ function collectConfiguredModelTargets(config) {
4600
+ const seen = /* @__PURE__ */ new Set();
4601
+ const out = [];
4602
+ const add = (provider, model) => {
4603
+ if (!provider || !model) return;
4604
+ const key = `${provider}\0${model}`;
4605
+ if (seen.has(key)) return;
4606
+ seen.add(key);
4607
+ out.push({ provider, model });
4608
+ };
4609
+ add(config.provider, config.model);
4610
+ for (const [providerId, provider] of Object.entries(config.providers ?? {})) {
4611
+ if (!isProviderAvailable(providerId, provider, config.provider)) continue;
4612
+ for (const model of provider.models ?? []) add(providerId, model);
4613
+ for (const model of Object.keys(provider.customModels ?? {})) add(providerId, model);
4614
+ }
4615
+ for (const model of Object.keys(config.models ?? {})) add(config.provider, model);
4616
+ return out;
4617
+ }
4618
+ function isProviderAvailable(providerId, provider, leaderProvider) {
4619
+ if (providerId === leaderProvider) return true;
4620
+ if (typeof provider.apiKey === "string" && provider.apiKey.length > 0) return true;
4621
+ if (Array.isArray(provider.apiKeys) && provider.apiKeys.some((key) => key?.apiKey)) return true;
4622
+ if (typeof provider.baseUrl === "string" && provider.baseUrl.length > 0) return true;
4623
+ return false;
4624
+ }
4625
+ function modelDiversityScore(candidate, avoid) {
4626
+ let score = 0;
4627
+ if (candidate.provider !== avoid.provider) score += 100;
4628
+ if (candidate.model !== avoid.model) score += 20;
4629
+ if (/opus|gpt-5|o3|o4|gemini.*pro|deepseek-r1/i.test(candidate.model)) score += 10;
4630
+ if (/mini|haiku|flash/i.test(candidate.model)) score -= 5;
4631
+ return score;
4632
+ }
4633
+ function matrixKeyKind(key) {
4634
+ if (key === "*") return "default";
4635
+ if (key in AGENT_CATALOG) return "role";
4636
+ if (MATRIX_PHASE_KEYS.includes(key)) return "phase";
4637
+ return "unknown";
4638
+ }
4639
+ function isValidMatrixKey(key) {
4640
+ return matrixKeyKind(key) !== "unknown";
4641
+ }
4642
+
4643
+ // src/tools/fallback-manage-tools.ts
4644
+ var FAVORITE_MANAGE_TOOL_NAME = "favorite_manage";
4645
+ var FALLBACK_CHAIN_MANAGE_TOOL_NAME = "fallback_chain_manage";
4646
+ var FALLBACK_PROFILE_MANAGE_TOOL_NAME = "fallback_profile_manage";
4647
+ var AGENT_MODEL_ASSIGN_TOOL_NAME = "agent_model_assign";
4648
+ function normalizeRef(ref) {
4649
+ return ref.trim().replace(/\s*\/\s*/g, "/").replace(/\s+/g, " ");
4650
+ }
4651
+ function isFavoriteRef(ref, config) {
4652
+ const favorites = config.favoriteModels ?? [];
4653
+ if (favorites.length === 0) {
4654
+ return true;
4655
+ }
4656
+ const canonical = normalizeModelRef(ref, config.provider);
4657
+ return favorites.some((f) => normalizeModelRef(f, config.provider) === canonical);
4658
+ }
4659
+ function notFavoriteError(ref, config) {
4660
+ const favorites = config.favoriteModels ?? [];
4661
+ return `"${ref}" is not in your favorites list. ` + (favorites.length === 0 ? 'Add some favorites first with favorite_manage({ action: "add", model: "<provider/model>" }).' : `Current favorites: ${favorites.join(", ") || "(none)"}. Use favorite_manage to add this model first.`);
4662
+ }
4663
+ function modelList(config) {
4664
+ return config.favoriteModels ?? [];
4665
+ }
4666
+ function profileList(config) {
4667
+ return config.fallbackProfiles ?? {};
4668
+ }
4669
+ function chainList(config) {
4670
+ return config.fallbackModels ?? [];
4671
+ }
4672
+ var FAVORITE_MANAGE_SCHEMA = {
4673
+ type: "object",
4674
+ properties: {
4675
+ action: {
4676
+ type: "string",
4677
+ enum: ["list", "add", "remove"],
4678
+ description: "Operation to perform: list (show all), add (add a favorite), remove (remove by index or ref)."
4679
+ },
4680
+ model: {
4681
+ type: "string",
4682
+ description: 'Model reference to add/remove (e.g. "anthropic/claude-haiku-3", "openai/gpt-4o-mini"). Required for "add" and "remove" (when removing by ref).'
4683
+ },
4684
+ index: {
4685
+ type: "number",
4686
+ description: '1-based index for removal. Alternative to `model`. Used when action="remove".'
4687
+ }
4688
+ },
4689
+ required: ["action"],
4690
+ additionalProperties: false
4691
+ };
4692
+ function createFavoriteManageTool(opts) {
4693
+ return {
4694
+ name: FAVORITE_MANAGE_TOOL_NAME,
4695
+ description: "Manage your favorite provider/model list. Favorites are the only models that can be added to fallback chains and profiles. The LLM uses this tool to curate which models are available for fallback and role assignment.",
4696
+ usageHint: 'Start with "list" to see current favorites. Use "add <provider/model>" to add. Use "remove <index|ref>" to remove.',
4697
+ category: "Config",
4698
+ inputSchema: FAVORITE_MANAGE_SCHEMA,
4699
+ permission: "auto",
4700
+ mutating: true,
4701
+ riskTier: "standard",
4702
+ icon: "settings",
4703
+ async execute(input) {
4704
+ const config = opts.getConfig();
4705
+ const favorites = [...modelList(config)];
4706
+ if (input.action === "list") {
4707
+ const msg = favorites.length === 0 ? 'No favorites set. Add one with favorite_manage({ action: "add", model: "<provider/model>" }).' : `Favorites (${favorites.length}):
4708
+ ` + favorites.map((f, i) => ` ${i + 1}. ${f}`).join("\n");
4709
+ return { status: "ok", message: msg, favorites: [...favorites] };
4710
+ }
4711
+ if (input.action === "add") {
4712
+ if (!input.model) {
4713
+ return { status: "error", message: 'Provide "model" (e.g. "anthropic/claude-haiku-3") to add a favorite.' };
4714
+ }
4715
+ const ref = normalizeRef(input.model);
4716
+ const canonical = normalizeModelRef(ref, config.provider);
4717
+ if (favorites.some((f) => normalizeModelRef(f, config.provider) === canonical)) {
4718
+ return { status: "error", message: `"${ref}" is already a favorite.` };
4719
+ }
4720
+ favorites.push(ref);
4721
+ await opts.updateConfig((cfg) => {
4722
+ cfg.favoriteModels = favorites;
4723
+ });
4724
+ return {
4725
+ status: "ok",
4726
+ message: `\u2713 Added favorite: ${ref} (${favorites.length} total)`,
4727
+ favorites: [...favorites]
4728
+ };
4729
+ }
4730
+ if (input.action === "remove") {
4731
+ if (input.index !== void 0) {
4732
+ const idx = input.index - 1;
4733
+ if (idx < 0 || idx >= favorites.length) {
4734
+ return { status: "error", message: `Index ${input.index} is out of range (1\u2013${favorites.length}).` };
4735
+ }
4736
+ const [removed] = favorites.splice(idx, 1);
4737
+ await opts.updateConfig((cfg) => {
4738
+ cfg.favoriteModels = favorites;
4739
+ });
4740
+ return { status: "ok", message: `\u2713 Removed favorite: ${removed}`, favorites: [...favorites] };
4741
+ }
4742
+ if (input.model) {
4743
+ const ref = normalizeRef(input.model);
4744
+ const canonical = normalizeModelRef(ref, config.provider);
4745
+ const idx = favorites.findIndex((f) => normalizeModelRef(f, config.provider) === canonical);
4746
+ if (idx === -1) {
4747
+ return { status: "error", message: `Favorite "${ref}" not found. Use "list" to see all favorites.` };
4748
+ }
4749
+ const [removed] = favorites.splice(idx, 1);
4750
+ await opts.updateConfig((cfg) => {
4751
+ cfg.favoriteModels = favorites;
4752
+ });
4753
+ return { status: "ok", message: `\u2713 Removed favorite: ${removed}`, favorites: [...favorites] };
4754
+ }
4755
+ return { status: "error", message: 'Provide either "model" or "index" to remove a favorite.' };
4756
+ }
4757
+ return { status: "error", message: `Unknown action: "${input.action}". Use "list", "add", or "remove".` };
4758
+ }
4759
+ };
4760
+ }
4761
+ var FALLBACK_CHAIN_SCHEMA = {
4762
+ type: "object",
4763
+ properties: {
4764
+ action: {
4765
+ type: "string",
4766
+ enum: ["list", "add", "insert", "remove", "clear"],
4767
+ description: "Operation: list (show chain), add (append), insert (insert at position), remove (by index or ref), clear (empty the chain)."
4768
+ },
4769
+ model: {
4770
+ type: "string",
4771
+ description: 'Model reference for add/insert/remove (e.g. "anthropic/claude-haiku-3"). Must be in your favorites list for add/insert. Required for add, insert, and remove (when removing by ref).'
4772
+ },
4773
+ index: {
4774
+ type: "number",
4775
+ description: '1-based insertion position (for action="insert") or removal index (for action="remove"). For insert: the new entry is placed before this position. Omit to append. For remove: alternative to model. Omit to remove by model ref.'
4776
+ }
4777
+ },
4778
+ required: ["action"],
4779
+ additionalProperties: false
4780
+ };
4781
+ function createFallbackChainManageTool(opts) {
4782
+ return {
4783
+ name: FALLBACK_CHAIN_MANAGE_TOOL_NAME,
4784
+ description: "View or change the active rate-limit fallback chain. When the primary model is overloaded (429/5xx), the agent rotates through this chain in order. Every new entry must be a FAVORITE model \u2014 add it via favorite_manage first. Use insert to place a fallback at a specific position; use remove to delete an entry.",
4785
+ usageHint: '"list" to see the current chain. "add" with a favorite model to append. "insert" with an index (1-based) to place before that position. "remove" with index or model ref. "clear" to empty the chain (auto fallback takes over).',
4786
+ category: "Config",
4787
+ inputSchema: FALLBACK_CHAIN_SCHEMA,
4788
+ permission: "auto",
4789
+ mutating: true,
4790
+ riskTier: "standard",
4791
+ icon: "settings",
4792
+ async execute(input) {
4793
+ const config = opts.getConfig();
4794
+ const chain = [...chainList(config)];
4795
+ if (input.action === "list") {
4796
+ if (chain.length === 0) {
4797
+ return {
4798
+ status: "ok",
4799
+ message: 'Fallback chain is empty. Add entries with "add" or enable auto fallback.',
4800
+ chain: []
4801
+ };
4802
+ }
4803
+ const msg = chain.map((ref, i) => ` ${i + 1}. ${ref}`).join("\n");
4804
+ return { status: "ok", message: `Fallback chain (${chain.length}):
4805
+ ${msg}`, chain: [...chain] };
4806
+ }
4807
+ if (input.action === "add") {
4808
+ if (!input.model) {
4809
+ return { status: "error", message: 'Provide "model" (e.g. "anthropic/claude-haiku-3") to add to the chain.' };
4810
+ }
4811
+ const ref = normalizeRef(input.model);
4812
+ if (!isFavoriteRef(ref, config)) {
4813
+ return { status: "error", message: notFavoriteError(ref, config) };
4814
+ }
4815
+ if (chain.some((e) => normalizeRef(e) === ref)) {
4816
+ return { status: "error", message: `"${ref}" is already in the chain.` };
4817
+ }
4818
+ chain.push(ref);
4819
+ await opts.updateConfig((cfg) => {
4820
+ cfg.fallbackModels = chain;
4821
+ });
4822
+ return {
4823
+ status: "ok",
4824
+ message: `\u2713 Added to chain: ${ref} (position ${chain.length})`,
4825
+ chain: [...chain]
4826
+ };
4827
+ }
4828
+ if (input.action === "insert") {
4829
+ if (!input.model) {
4830
+ return { status: "error", message: 'Provide "model" to insert into the chain.' };
4831
+ }
4832
+ const ref = normalizeRef(input.model);
4833
+ if (!isFavoriteRef(ref, config)) {
4834
+ return { status: "error", message: notFavoriteError(ref, config) };
4835
+ }
4836
+ if (chain.some((e) => normalizeRef(e) === ref)) {
4837
+ return { status: "error", message: `"${ref}" is already in the chain.` };
4838
+ }
4839
+ let pos = chain.length;
4840
+ if (input.index !== void 0) {
4841
+ pos = Math.max(0, Math.min(chain.length, input.index - 1));
4842
+ }
4843
+ chain.splice(pos, 0, ref);
4844
+ await opts.updateConfig((cfg) => {
4845
+ cfg.fallbackModels = chain;
4846
+ });
4847
+ return {
4848
+ status: "ok",
4849
+ message: `\u2713 Inserted at position ${pos + 1}: ${ref}`,
4850
+ chain: [...chain]
4851
+ };
4852
+ }
4853
+ if (input.action === "remove") {
4854
+ if (chain.length === 0) {
4855
+ return { status: "error", message: "Chain is empty \u2014 nothing to remove." };
4856
+ }
4857
+ if (input.index !== void 0) {
4858
+ const idx = input.index - 1;
4859
+ if (idx < 0 || idx >= chain.length) {
4860
+ return { status: "error", message: `Index ${input.index} is out of range (1\u2013${chain.length}).` };
4861
+ }
4862
+ const [removed] = chain.splice(idx, 1);
4863
+ await opts.updateConfig((cfg) => {
4864
+ cfg.fallbackModels = chain;
4865
+ });
4866
+ return { status: "ok", message: `\u2713 Removed: ${removed}`, chain: [...chain] };
4867
+ }
4868
+ if (input.model) {
4869
+ const ref = normalizeRef(input.model);
4870
+ const idx = chain.findIndex((e) => normalizeRef(e) === ref);
4871
+ if (idx === -1) {
4872
+ return { status: "error", message: `"${ref}" not found in chain.` };
4873
+ }
4874
+ const [removed] = chain.splice(idx, 1);
4875
+ await opts.updateConfig((cfg) => {
4876
+ cfg.fallbackModels = chain;
4877
+ });
4878
+ return { status: "ok", message: `\u2713 Removed: ${removed}`, chain: [...chain] };
4879
+ }
4880
+ return { status: "error", message: 'Provide "index" or "model" to remove from the chain.' };
4881
+ }
4882
+ if (input.action === "clear") {
4883
+ if (chain.length === 0) {
4884
+ return { status: "ok", message: "Chain is already empty." };
4885
+ }
4886
+ await opts.updateConfig((cfg) => {
4887
+ cfg.fallbackModels = [];
4888
+ });
4889
+ return { status: "ok", message: "\u2713 Cleared the fallback chain. Auto fallback will take over when enabled." };
4890
+ }
4891
+ return { status: "error", message: `Unknown action: "${input.action}".` };
4892
+ }
4893
+ };
4894
+ }
4895
+ var FALLBACK_PROFILE_SCHEMA = {
4896
+ type: "object",
4897
+ properties: {
4898
+ action: {
4899
+ type: "string",
4900
+ enum: ["list", "set", "delete"],
4901
+ description: "Operation: list (show all profiles), set (create/update), delete (remove a profile)."
4902
+ },
4903
+ name: {
4904
+ type: "string",
4905
+ description: 'Profile name (e.g. "fast", "economy", "reliable"). Required for "set" and "delete".'
4906
+ },
4907
+ chain: {
4908
+ type: "array",
4909
+ items: { type: "string" },
4910
+ description: 'Ordered list of model references for the profile. Each entry must be a favorite model. Required for "set". Example: ["anthropic/claude-haiku-3", "openai/gpt-4o-mini"].'
4911
+ }
4912
+ },
4913
+ required: ["action"],
4914
+ additionalProperties: false
4915
+ };
4916
+ function createFallbackProfileManageTool(opts) {
4917
+ return {
4918
+ name: FALLBACK_PROFILE_MANAGE_TOOL_NAME,
4919
+ description: "Manage named fallback profiles. A profile is a reusable, ordered list of model references that can be assigned to agent roles. Every entry in a profile must be a FAVORITE model \u2014 add it via favorite_manage first. Use /setmodel or agent_model_assign to assign a profile to a role.",
4920
+ usageHint: '"list" to see all profiles. "set" with name and chain (array of model refs) to create or replace a profile. "delete" with name to remove a profile.',
4921
+ category: "Config",
4922
+ inputSchema: FALLBACK_PROFILE_SCHEMA,
4923
+ permission: "auto",
4924
+ mutating: true,
4925
+ riskTier: "standard",
4926
+ icon: "settings",
4927
+ async execute(input) {
4928
+ const config = opts.getConfig();
4929
+ const profiles = { ...profileList(config) };
4930
+ if (input.action === "list") {
4931
+ const names = Object.keys(profiles);
4932
+ if (names.length === 0) {
4933
+ return {
4934
+ status: "ok",
4935
+ message: 'No fallback profiles. Create one with "set".',
4936
+ profiles: {}
4937
+ };
4938
+ }
4939
+ const msg = names.sort().map((name) => ` ${name} \u2192 ${profiles[name]?.join(" \u2192 ") || "(empty)"}`).join("\n");
4940
+ return { status: "ok", message: `Fallback profiles:
4941
+ ${msg}`, profiles: { ...profiles } };
4942
+ }
4943
+ if (input.action === "set") {
4944
+ if (!input.name) {
4945
+ return { status: "error", message: 'Provide "name" for the profile (e.g. "fast").' };
4946
+ }
4947
+ if (!input.chain || input.chain.length === 0) {
4948
+ return { status: "error", message: 'Provide "chain" \u2014 a non-empty array of model references.' };
4949
+ }
4950
+ const invalid = [];
4951
+ for (const ref of input.chain) {
4952
+ if (!isFavoriteRef(ref, config)) {
4953
+ invalid.push(ref);
4954
+ }
4955
+ }
4956
+ if (invalid.length > 0) {
4957
+ return {
4958
+ status: "error",
4959
+ message: `The following entries are not in your favorites list:
4960
+ ${invalid.join("\n ")}
4961
+
4962
+ Add them first with favorite_manage({ action: "add", model: "<ref>" }).`
4963
+ };
4964
+ }
4965
+ profiles[input.name] = [...input.chain];
4966
+ await opts.updateConfig((cfg) => {
4967
+ cfg.fallbackProfiles = profiles;
4968
+ });
4969
+ return {
4970
+ status: "ok",
4971
+ message: `\u2713 Profile "${input.name}" \u2192 ${input.chain.join(" \u2192 ")}`,
4972
+ profiles: { ...profiles }
4973
+ };
4974
+ }
4975
+ if (input.action === "delete") {
4976
+ if (!input.name) {
4977
+ return { status: "error", message: 'Provide "name" of the profile to delete.' };
4978
+ }
4979
+ if (!(input.name in profiles)) {
4980
+ return { status: "error", message: `Profile "${input.name}" not found.` };
4981
+ }
4982
+ delete profiles[input.name];
4983
+ await opts.updateConfig((cfg) => {
4984
+ cfg.fallbackProfiles = profiles;
4985
+ });
4986
+ return {
4987
+ status: "ok",
4988
+ message: `\u2713 Deleted profile: ${input.name}`,
4989
+ profiles: { ...profiles }
4990
+ };
4991
+ }
4992
+ return { status: "error", message: `Unknown action: "${input.action}".` };
4993
+ }
4994
+ };
4995
+ }
4996
+ var AGENT_MODEL_ASSIGN_SCHEMA = {
4997
+ type: "object",
4998
+ properties: {
4999
+ role: {
5000
+ type: "string",
5001
+ description: 'Matrix key: a catalog role (e.g. "security-scanner", "bug-hunter"), a phase name (e.g. "review", "implementation"), or "*" for the fleet-wide default.'
5002
+ },
5003
+ provider: {
5004
+ type: "string",
5005
+ description: 'Provider id (e.g. "anthropic", "openai"). When omitted, the leader provider is used. The provider.model combination must be in your favorites list.'
5006
+ },
5007
+ model: {
5008
+ type: "string",
5009
+ description: 'Model id (e.g. "claude-haiku-3", "gpt-4o-mini"). When omitted together with provider, the role falls back to the leader model. Must be in your favorites list.'
5010
+ },
5011
+ profile: {
5012
+ type: "string",
5013
+ description: 'Named fallback profile to assign (e.g. "fast", "economy"). Alternative to provider+model. When set, the first profile entry becomes the primary model and the rest are the fallback chain.'
5014
+ },
5015
+ clear: {
5016
+ type: "boolean",
5017
+ description: "Set to true to remove the matrix entry for this role (it will fall through to phase/*/leader)."
5018
+ }
5019
+ },
5020
+ // Flattened from a top-level oneOf — Anthropic-family endpoints reject
5021
+ // top-level combinators (omniroute 400: "input_schema does not support
5022
+ // oneOf, allOf, or anyOf at the top level"). Combination rules are
5023
+ // enforced in the handler instead.
5024
+ required: ["role"],
5025
+ additionalProperties: false
5026
+ };
5027
+ function createAgentModelAssignTool(opts) {
5028
+ return {
5029
+ name: AGENT_MODEL_ASSIGN_TOOL_NAME,
5030
+ description: "Assign a provider/model or a fallback profile to a specific agent role, phase, or the fleet-wide default. This is the LLM-accessible equivalent of /setmodel set. The provider+model combination must be in your favorites list (unless only clearing). Resolution precedence: exact role \u2192 phase \u2192 * \u2192 leader model.",
5031
+ usageHint: 'Use "list" as role to see current assignments. Set with role + model, or role + provider + model, or role + profile. Set role + clear=true to remove a matrix entry. The provider/model must be a favorite.',
5032
+ category: "Config",
5033
+ inputSchema: AGENT_MODEL_ASSIGN_SCHEMA,
5034
+ permission: "auto",
5035
+ mutating: true,
5036
+ riskTier: "standard",
5037
+ icon: "settings",
5038
+ async execute(input) {
5039
+ const config = opts.getConfig();
5040
+ const modes = [input.clear ? "clear" : null, input.profile ? "profile" : null, input.model ? "model" : null].filter(Boolean);
5041
+ if (modes.length > 1) {
5042
+ return {
5043
+ status: "error",
5044
+ message: `Conflicting assignment modes: ${modes.join(" + ")}. Use exactly one: clear=true, profile="name", or model="name" (optionally with provider).`
5045
+ };
5046
+ }
5047
+ if (input.role === "list") {
5048
+ const matrix = config.modelMatrix ?? {};
5049
+ const keys = Object.keys(matrix);
5050
+ if (keys.length === 0) {
5051
+ return { status: "ok", message: "No matrix assignments. All roles use the leader model." };
5052
+ }
5053
+ const msg = keys.sort().map((k) => ` ${k} \u2192 ${JSON.stringify(matrix[k])}`).join("\n");
5054
+ return { status: "ok", message: `Model matrix (${keys.length} entries):
5055
+ ${msg}` };
5056
+ }
5057
+ if (!isValidMatrixKey(input.role)) {
5058
+ return {
5059
+ status: "error",
5060
+ message: `"${input.role}" is not a valid matrix key. Use a catalog role (e.g. "security-scanner"), a phase (e.g. "review"), or "*" for the fleet-wide default.`
5061
+ };
5062
+ }
5063
+ if (input.clear) {
5064
+ const matrix = { ...config.modelMatrix ?? {} };
5065
+ if (!(input.role in matrix)) {
5066
+ return { status: "ok", message: `No matrix entry for "${input.role}" to clear.` };
5067
+ }
5068
+ delete matrix[input.role];
5069
+ await opts.updateConfig((cfg) => {
5070
+ cfg.modelMatrix = matrix;
5071
+ });
5072
+ return { status: "ok", message: `\u2713 Cleared matrix entry for "${input.role}".` };
5073
+ }
5074
+ if (!input.model && !input.profile && !input.provider) {
5075
+ const matrix = config.modelMatrix ?? {};
5076
+ const entry = matrix[input.role];
5077
+ if (!entry) {
5078
+ return { status: "ok", message: `No specific assignment for "${input.role}". It uses the leader model or phase/* fallback.` };
5079
+ }
5080
+ return { status: "ok", message: `"${input.role}" \u2192 ${JSON.stringify(entry)}` };
5081
+ }
5082
+ if (input.profile && !input.model) {
5083
+ const profiles = profileList(config);
5084
+ if (!profiles[input.profile]) {
5085
+ return { status: "error", message: `Profile "${input.profile}" not found. Create it with fallback_profile_manage first.` };
5086
+ }
5087
+ const matrix = { ...config.modelMatrix ?? {} };
5088
+ matrix[input.role] = { fallbackProfile: input.profile };
5089
+ await opts.updateConfig((cfg) => {
5090
+ cfg.modelMatrix = matrix;
5091
+ });
5092
+ return { status: "ok", message: `\u2713 "${input.role}" \u2192 profile: ${input.profile}` };
5093
+ }
5094
+ if (input.model) {
5095
+ const effectiveProvider = input.provider ?? config.provider;
5096
+ const ref = `${effectiveProvider}/${input.model}`;
5097
+ if (!isFavoriteRef(ref, config)) {
5098
+ return { status: "error", message: notFavoriteError(ref, config) };
5099
+ }
5100
+ const matrix = { ...config.modelMatrix ?? {} };
5101
+ const previousRuntime = matrix[input.role]?.modelRuntime;
5102
+ matrix[input.role] = input.provider ? { provider: input.provider, model: input.model, ...previousRuntime ? { modelRuntime: previousRuntime } : {} } : { model: input.model, ...previousRuntime ? { modelRuntime: previousRuntime } : {} };
5103
+ await opts.updateConfig((cfg) => {
5104
+ cfg.modelMatrix = matrix;
5105
+ });
5106
+ const display = input.provider ? `${input.provider}/${input.model}` : `${input.model} (leader provider)`;
5107
+ return { status: "ok", message: `\u2713 "${input.role}" \u2192 ${display}` };
5108
+ }
5109
+ return { status: "error", message: "Provide model, profile, or clear=true for the role assignment." };
5110
+ }
5111
+ };
5112
+ }
5113
+ var PROVIDER_MANAGE_TOOL_NAME = "provider_manage";
5114
+ var PROVIDER_MANAGE_SCHEMA = {
5115
+ type: "object",
5116
+ properties: {
5117
+ action: {
5118
+ type: "string",
5119
+ enum: ["list", "add", "configure", "remove"],
5120
+ description: "Operation: list (show all providers), add (add a new provider config), configure (update fields of an existing provider), remove (delete a provider config)."
5121
+ },
5122
+ provider: {
5123
+ type: "string",
5124
+ description: 'Provider id (e.g. "openai", "anthropic"). Required for all actions except list.'
5125
+ },
5126
+ type: {
5127
+ type: "string",
5128
+ description: 'Provider type (e.g. "openai", "anthropic"). Required for "add".'
5129
+ },
5130
+ models: {
5131
+ type: "array",
5132
+ items: { type: "string" },
5133
+ description: "Model list to restrict visibility for this provider. Optional for add/configure."
5134
+ },
5135
+ baseUrl: {
5136
+ type: "string",
5137
+ description: "Custom base URL (e.g. for self-hosted endpoints). Optional."
5138
+ },
5139
+ family: {
5140
+ type: "string",
5141
+ description: 'Wire-family override (e.g. "openai", "openai-compatible", "anthropic"). When set, the provider can be constructed without a catalog entry.'
5142
+ },
5143
+ envVars: {
5144
+ type: "array",
5145
+ items: { type: "string" },
5146
+ description: "Custom env var names to probe when apiKey is missing. Optional."
5147
+ },
5148
+ autoDiscoverModels: {
5149
+ type: "boolean",
5150
+ description: "Auto-fetch model list from {baseUrl}/models. Optional."
5151
+ },
5152
+ apiKey: {
5153
+ type: "string",
5154
+ description: "**NOT RECOMMENDED** \u2014 use provider_key_set instead. The LLM output may contain this value; use env var references for safety."
5155
+ }
5156
+ },
5157
+ required: ["action"],
5158
+ additionalProperties: false
5159
+ };
5160
+ function createProviderManageTool(opts) {
5161
+ return {
5162
+ name: PROVIDER_MANAGE_TOOL_NAME,
5163
+ description: "View or configure provider entries. List all configured providers with their type, model lists, base URL, and key status. Add new providers, update their settings, or remove unused ones. API keys should be set via provider_key_set instead of passing them here \u2014 they are visible in the LLM output.",
5164
+ usageHint: '"list" to see all providers. "add" with provider id and type to create. "configure" to update models, baseUrl, family, or envVars. "remove" to delete a provider. Use provider_key_set for API key management.',
5165
+ category: "Config",
5166
+ inputSchema: PROVIDER_MANAGE_SCHEMA,
5167
+ permission: "auto",
5168
+ mutating: true,
5169
+ riskTier: "standard",
5170
+ icon: "settings",
5171
+ async execute(input) {
5172
+ const config = opts.getConfig();
5173
+ const providers = {
5174
+ ...config.providers ?? {}
5175
+ };
5176
+ const leaderProvider = config.provider ?? "";
5177
+ if (input.action === "list") {
5178
+ const ids = Object.keys(providers);
5179
+ if (ids.length === 0) {
5180
+ return { status: "ok", message: "No providers configured.", providers: [] };
5181
+ }
5182
+ const msg = ids.sort().map((id) => {
5183
+ const entry = providers[id] ?? {};
5184
+ const type = entry.type ?? "(unknown)";
5185
+ const models = Array.isArray(entry.models) ? entry.models.join(", ") : "(all)";
5186
+ const hasKey = entry.apiKey ? "\u2713" : entry.apiKeys ? "\u2713" : "\u2717";
5187
+ const prefix = id === leaderProvider ? "\u2605 " : " ";
5188
+ const baseUrl = entry.baseUrl ? ` url:${entry.baseUrl}` : "";
5189
+ const family = entry.family ? ` family:${entry.family}` : "";
5190
+ return ` ${prefix}${id} (${type}) key:${hasKey} models:[${models}]${baseUrl}${family}`;
5191
+ }).join("\n");
5192
+ return {
5193
+ status: "ok",
5194
+ message: `Providers (leader: ${leaderProvider}):
5195
+ ${msg}`,
5196
+ providers: ids
5197
+ };
5198
+ }
5199
+ if (input.action === "add") {
5200
+ if (!input.provider || !input.type) {
5201
+ return { status: "error", message: 'Provide "provider" (id) and "type" to add a provider.' };
5202
+ }
5203
+ if (providers[input.provider]) {
5204
+ return { status: "error", message: `Provider "${input.provider}" already exists. Use "configure" to update.` };
5205
+ }
5206
+ const entry = { type: input.type };
5207
+ if (input.models) entry.models = input.models;
5208
+ if (input.baseUrl) entry.baseUrl = input.baseUrl;
5209
+ if (input.family) entry.family = input.family;
5210
+ if (input.envVars) entry.envVars = input.envVars;
5211
+ if (input.autoDiscoverModels !== void 0) entry.autoDiscoverModels = input.autoDiscoverModels;
5212
+ if (input.apiKey) entry.apiKey = input.apiKey;
5213
+ providers[input.provider] = entry;
5214
+ await opts.updateConfig((cfg) => {
5215
+ cfg.providers = providers;
5216
+ });
5217
+ return { status: "ok", message: `\u2713 Added provider: ${input.provider} (type: ${input.type})` };
5218
+ }
5219
+ if (input.action === "configure") {
5220
+ if (!input.provider) {
5221
+ return { status: "error", message: 'Provide "provider" id to configure.' };
5222
+ }
5223
+ if (!providers[input.provider]) {
5224
+ return { status: "error", message: `Provider "${input.provider}" not found. Use "add" first or check "list".` };
5225
+ }
5226
+ const entry = { ...providers[input.provider] };
5227
+ if (input.models !== void 0) entry.models = input.models;
5228
+ if (input.baseUrl !== void 0) entry.baseUrl = input.baseUrl || void 0;
5229
+ if (input.family !== void 0) entry.family = input.family || void 0;
5230
+ if (input.envVars !== void 0) entry.envVars = input.envVars;
5231
+ if (input.autoDiscoverModels !== void 0) entry.autoDiscoverModels = input.autoDiscoverModels;
5232
+ if (input.apiKey !== void 0) entry.apiKey = input.apiKey || void 0;
5233
+ providers[input.provider] = entry;
5234
+ await opts.updateConfig((cfg) => {
5235
+ cfg.providers = providers;
5236
+ });
5237
+ const updated = Object.keys({ ...entry }).filter((k) => k !== "apiKey").join(", ");
5238
+ return { status: "ok", message: `\u2713 Updated ${input.provider}: ${updated}` };
5239
+ }
5240
+ if (input.action === "remove") {
5241
+ if (!input.provider) {
5242
+ return { status: "error", message: 'Provide "provider" id to remove.' };
5243
+ }
5244
+ if (!providers[input.provider]) {
5245
+ return { status: "error", message: `Provider "${input.provider}" not found.` };
5246
+ }
5247
+ if (input.provider === leaderProvider) {
5248
+ return { status: "error", message: `Cannot remove the active leader provider "${input.provider}". Switch the leader first.` };
5249
+ }
5250
+ delete providers[input.provider];
5251
+ await opts.updateConfig((cfg) => {
5252
+ cfg.providers = providers;
5253
+ });
5254
+ return { status: "ok", message: `\u2713 Removed provider: ${input.provider}` };
5255
+ }
5256
+ return { status: "error", message: `Unknown action: "${input.action}".` };
5257
+ }
5258
+ };
5259
+ }
5260
+ var PROVIDER_KEY_SET_TOOL_NAME = "provider_key_set";
5261
+ var PROVIDER_KEY_SET_SCHEMA = {
5262
+ type: "object",
5263
+ properties: {
5264
+ provider: {
5265
+ type: "string",
5266
+ description: 'Provider id (e.g. "openai", "anthropic"). Required.'
5267
+ },
5268
+ key: {
5269
+ type: "string",
5270
+ description: "The API key value. When provided directly, it is stored as the provider's primary key. \u26A0\uFE0F This value is visible to the LLM \u2014 for secrets, omit this field and use envVar instead, or provide the key through the interactive prompt."
5271
+ },
5272
+ envVar: {
5273
+ type: "string",
5274
+ description: 'Environment variable name that contains the key (e.g. "OPENAI_API_KEY"). The key is read from the environment at tool execution time \u2014 the value is never visible to the LLM. Preferred over passing the key directly.'
5275
+ },
5276
+ label: {
5277
+ type: "string",
5278
+ description: 'Optional label for the key entry (e.g. "work", "personal"). Useful when managing multiple keys.'
5279
+ },
5280
+ setActive: {
5281
+ type: "boolean",
5282
+ description: "Whether to make this the active key. Default: true."
5283
+ }
5284
+ },
5285
+ // Flattened from a top-level oneOf — Anthropic-family endpoints reject
5286
+ // top-level combinators (omniroute 400). Combination rules are enforced
5287
+ // in the handler instead.
5288
+ required: ["provider"],
5289
+ additionalProperties: false
5290
+ };
5291
+ function createProviderKeySetTool(opts) {
5292
+ return {
5293
+ name: PROVIDER_KEY_SET_TOOL_NAME,
5294
+ description: "Set the API key for a provider. For security, prefer using envVar (reads from environment variable, value never visible to the LLM) over passing the key directly. When neither key nor envVar is provided, the tool returns a prompt for interactive key entry \u2014 the UI will present an input field and the key is stored without LLM visibility.\n\nAfter setting a key, the provider becomes usable for model assignments and fallback chains. Add its models to favorites with favorite_manage to unlock them for fallback/profile use.",
5295
+ usageHint: 'Preferred: provider_key_set({ provider: "openai", envVar: "OPENAI_API_KEY" }). For interactive input: provider_key_set({ provider: "openai" }) \u2014 the UI will prompt. Direct key: provider_key_set({ provider: "openai", key: "sk-..." }) \u2014 visible to LLM.',
5296
+ category: "Config",
5297
+ inputSchema: PROVIDER_KEY_SET_SCHEMA,
5298
+ permission: "auto",
5299
+ mutating: true,
5300
+ riskTier: "standard",
5301
+ icon: "settings",
5302
+ async execute(input) {
5303
+ const config = opts.getConfig();
5304
+ const providers = {
5305
+ ...config.providers ?? {}
5306
+ };
5307
+ if (input.key && input.envVar) {
5308
+ return {
5309
+ status: "error",
5310
+ message: "Provide either key (direct, visible to LLM) OR envVar (reads from environment, never visible to LLM), not both. Use envVar for security."
5311
+ };
5312
+ }
5313
+ if (!input.key && !input.envVar) {
5314
+ if (opts.requestInput) {
5315
+ try {
5316
+ const value = await opts.requestInput(
5317
+ `Enter API key for "${input.provider}" (will be stored securely, LLM will not see it):`
5318
+ );
5319
+ if (!value || value.trim().length === 0) {
5320
+ return { status: "error", message: "No key was entered. Operation cancelled." };
5321
+ }
5322
+ return storeKey(providers, input, value.trim(), opts);
5323
+ } catch (err) {
5324
+ return {
5325
+ status: "error",
5326
+ message: `Interactive input failed or was cancelled: ${err instanceof Error ? err.message : String(err)}`
5327
+ };
5328
+ }
5329
+ }
5330
+ return {
5331
+ status: "needs_key",
5332
+ message: `To set the API key for "${input.provider}", use provider_key_set with either:
5333
+ 1. envVar: "${input.provider.toUpperCase()}_API_KEY" (reads from env, LLM never sees it)
5334
+ 2. key: "sk-..." (pass directly, visible to LLM)
5335
+
5336
+ Interactive key entry is handled by the UI \u2014 enter your key through the prompt surface.`
5337
+ };
5338
+ }
5339
+ if (input.envVar) {
5340
+ const envValue = process.env[input.envVar];
5341
+ if (!envValue) {
5342
+ return {
5343
+ status: "error",
5344
+ message: `Environment variable "${input.envVar}" is not set or empty. Set it first or use a different envVar.`
5345
+ };
5346
+ }
5347
+ return storeKey(providers, input, envValue, opts);
5348
+ }
5349
+ if (input.key) {
5350
+ return storeKey(providers, input, input.key, opts);
5351
+ }
5352
+ return { status: "error", message: "Unexpected \u2014 no key source available." };
5353
+ }
5354
+ };
5355
+ }
5356
+ async function storeKey(providers, input, keyValue, opts) {
5357
+ const providerId = input.provider;
5358
+ if (!providers[providerId]) {
5359
+ providers[providerId] = { type: providerId };
5360
+ }
5361
+ const entry = providers[providerId];
5362
+ const existingKeys = Array.isArray(entry.apiKeys) ? [...entry.apiKeys] : [];
5363
+ const label = input.label ?? "default";
5364
+ existingKeys.push({
5365
+ label,
5366
+ apiKey: keyValue,
5367
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
5368
+ });
5369
+ entry.apiKeys = existingKeys;
5370
+ entry.apiKey = void 0;
5371
+ if (input.setActive !== false) {
5372
+ entry.activeKey = label;
5373
+ }
5374
+ providers[providerId] = entry;
5375
+ await opts.updateConfig((cfg) => {
5376
+ cfg.providers = providers;
5377
+ });
5378
+ const sourceName = input.envVar ? `env:${input.envVar}` : "direct key";
5379
+ return {
5380
+ status: "ok",
5381
+ message: `\u2713 API key stored for "${providerId}" from ${sourceName}. Now add models to favorites with favorite_manage to use them in fallback chains.`
5382
+ };
5383
+ }
5384
+ var LEADER_MODEL_SET_TOOL_NAME = "leader_model_set";
5385
+ var LEADER_MODEL_SET_SCHEMA = {
5386
+ type: "object",
5387
+ properties: {
5388
+ action: {
5389
+ type: "string",
5390
+ enum: ["show", "set", "profile", "toggle"],
5391
+ description: "Operation: show (current leader + toggles), set (change provider/model), profile (set from a fallback profile), toggle (change fallbackAuto/favoriteModelsOnly)."
5392
+ },
5393
+ provider: {
5394
+ type: "string",
5395
+ description: 'Provider id for the leader (e.g. "anthropic", "openai"). Required for "set".'
5396
+ },
5397
+ model: {
5398
+ type: "string",
5399
+ description: 'Model id for the leader (e.g. "claude-sonnet-4-20250514"). Required for "set".'
5400
+ },
5401
+ profile: {
5402
+ type: "string",
5403
+ description: 'Fallback profile name to derive the leader + chain from. Required for "profile".'
5404
+ },
5405
+ toggle: {
5406
+ type: "string",
5407
+ enum: ["fallbackAuto", "favoriteModelsOnly"],
5408
+ description: 'Which toggle to change. Required for "toggle".'
5409
+ },
5410
+ value: {
5411
+ type: "boolean",
5412
+ description: 'New value for the toggle. Required for "toggle".'
5413
+ }
5414
+ },
5415
+ additionalProperties: false
5416
+ };
5417
+ function createLeaderModelSetTool(opts) {
5418
+ return {
5419
+ name: LEADER_MODEL_SET_TOOL_NAME,
5420
+ description: 'View or change the leader provider/model and system toggles. The leader is the primary model used for the main agent interactions. "set" changes it directly. "profile" derives it from a named fallback profile (first entry becomes leader, rest become the fallback chain). "toggle" controls fallbackAuto (smart default fallback) and favoriteModelsOnly (restrict auto-fallback to favorites only).',
5421
+ usageHint: '"show" to see current state. "set" with provider+model to change. "profile" with name to derive from a profile. "toggle" with toggle name and value to change a boolean setting.',
5422
+ category: "Config",
5423
+ inputSchema: LEADER_MODEL_SET_SCHEMA,
5424
+ permission: "auto",
5425
+ mutating: true,
5426
+ riskTier: "standard",
5427
+ icon: "settings",
5428
+ async execute(input) {
5429
+ const config = opts.getConfig();
5430
+ if (input.action === "show") {
5431
+ const lines = [
5432
+ ` ${"leader"}: ${config.provider}/${config.model}`,
5433
+ ` ${"fallbackAuto"}: ${config.fallbackAuto !== false ? "on" : "off"}`,
5434
+ ` ${"favoriteModelsOnly"}: ${config.favoriteModelsOnly ? "on" : "off"}`,
5435
+ "",
5436
+ ` ${"fallback models"}: ${(config.fallbackModels ?? []).length > 0 ? (config.fallbackModels ?? []).join(" \u2192 ") : "empty (auto fallback)"}`,
5437
+ ` ${"favorites"}: ${(config.favoriteModels ?? []).length > 0 ? `${(config.favoriteModels ?? []).length} models` : "(none)"}`,
5438
+ ` ${"refiner"}: ${config.autonomy?.refinerProvider ? `${config.autonomy.refinerProvider}/${config.autonomy.refinerModel ?? "(default model)"}` : "(same as leader)"}`
5439
+ ];
5440
+ return { status: "ok", message: lines.join("\n") };
5441
+ }
5442
+ if (input.action === "set") {
5443
+ if (!input.provider || !input.model) {
5444
+ return { status: "error", message: 'Provide "provider" and "model" for the leader.' };
5445
+ }
5446
+ await opts.updateConfig((cfg) => {
5447
+ cfg.provider = input.provider;
5448
+ cfg.model = input.model;
5449
+ });
5450
+ return { status: "ok", message: `\u2713 Leader \u2192 ${input.provider}/${input.model}` };
5451
+ }
5452
+ if (input.action === "profile") {
5453
+ if (!input.profile) {
5454
+ return { status: "error", message: 'Provide "profile" name to derive the leader from.' };
5455
+ }
5456
+ const profiles = config.fallbackProfiles ?? {};
5457
+ const chain = profiles[input.profile];
5458
+ if (!chain || chain.length === 0) {
5459
+ return { status: "error", message: `Profile "${input.profile}" not found or empty.` };
5460
+ }
5461
+ const first = chain[0];
5462
+ const p = parseRefInternal(first);
5463
+ const provider = p.provider ?? config.provider;
5464
+ const model = p.model;
5465
+ if (!model) {
5466
+ return { status: "error", message: `Cannot parse "${first}" as a valid model reference.` };
5467
+ }
5468
+ const rest = chain.slice(1);
5469
+ await opts.updateConfig((cfg) => {
5470
+ cfg.provider = provider;
5471
+ cfg.model = model;
5472
+ cfg.fallbackModels = rest;
5473
+ });
5474
+ return {
5475
+ status: "ok",
5476
+ message: `\u2713 Leader \u2192 ${provider}/${model} (profile: ${input.profile})` + (rest.length > 0 ? `
5477
+ Fallback chain: ${rest.join(" \u2192 ")}` : "")
5478
+ };
5479
+ }
5480
+ if (input.action === "toggle") {
5481
+ if (!input.toggle || input.value === void 0) {
5482
+ return { status: "error", message: 'Provide "toggle" (fallbackAuto | favoriteModelsOnly) and "value" (boolean).' };
5483
+ }
5484
+ await opts.updateConfig((cfg) => {
5485
+ if (input.toggle === "fallbackAuto") {
5486
+ cfg.fallbackAuto = input.value;
5487
+ } else if (input.toggle === "favoriteModelsOnly") {
5488
+ cfg.favoriteModelsOnly = input.value;
5489
+ }
5490
+ });
5491
+ return {
5492
+ status: "ok",
5493
+ message: `\u2713 ${input.toggle} \u2192 ${input.value ? "on" : "off"}`
5494
+ };
5495
+ }
5496
+ return { status: "error", message: `Unknown action: "${input.action}".` };
5497
+ }
5498
+ };
5499
+ }
5500
+ function parseRefInternal(ref) {
5501
+ const trimmed = ref.trim();
5502
+ const slash = trimmed.indexOf("/");
5503
+ if (slash !== -1) {
5504
+ const p = trimmed.slice(0, slash);
5505
+ const m = trimmed.slice(slash + 1).trim();
5506
+ if (p) return { provider: p, model: m };
5507
+ return { model: m };
5508
+ }
5509
+ const parts = trimmed.split(/\s+/);
5510
+ if (parts.length >= 2) {
5511
+ return { provider: parts[0], model: parts.slice(1).join(" ") };
5512
+ }
5513
+ return { model: trimmed };
5514
+ }
5515
+ var SYSTEM_CONFIG_VIEW_TOOL_NAME = "system_config_view";
5516
+ var SYSTEM_CONFIG_VIEW_SCHEMA = {
5517
+ type: "object",
5518
+ properties: {
5519
+ section: {
5520
+ type: "string",
5521
+ enum: ["all", "providers", "models", "fallbacks", "matrix", "agents", "refiner", "doctor"],
5522
+ description: "Which section to show: all (everything), providers (configured providers + keys), models (favorites + leader), fallbacks (chain + profiles + toggles), matrix (per-role assignments), agents (every catalog agent with resolved model), refiner (goal refinement config), doctor (validate config and show issues/warnings). Default: all."
5523
+ }
5524
+ },
5525
+ additionalProperties: false
5526
+ };
5527
+ function createSystemConfigViewTool(opts) {
5528
+ return {
5529
+ name: SYSTEM_CONFIG_VIEW_TOOL_NAME,
5530
+ description: "Get a comprehensive view of all provider, model, fallback, and matrix configuration. Shows the complete state across all configurable areas so you can see what is available and make informed decisions when assigning models, creating fallback profiles, or managing providers. Use the section parameter to focus on specific areas.",
5531
+ usageHint: '"section: all" for everything. "section: providers" for configured providers and key status. "section: models" for leader model and favorites. "section: fallbacks" for chains, profiles, and toggles. "section: matrix" for per-role assignments. "section: refiner" for goal refinement config.',
5532
+ category: "Config",
5533
+ inputSchema: SYSTEM_CONFIG_VIEW_SCHEMA,
5534
+ permission: "auto",
5535
+ mutating: false,
5536
+ riskTier: "safe",
5537
+ icon: "settings",
5538
+ async execute(input) {
5539
+ const config = opts.getConfig();
5540
+ const section = input.section ?? "all";
5541
+ const sections = [];
5542
+ const addSection = (title, content) => {
5543
+ sections.push(`\u2500\u2500 ${title} \u2500\u2500
5544
+ ${content}`);
5545
+ };
5546
+ addSection(
5547
+ "Leader",
5548
+ ` ${config.provider}/${config.model}`
5549
+ );
5550
+ if (section === "all" || section === "providers") {
5551
+ const providers = config.providers ?? {};
5552
+ const ids = Object.keys(providers);
5553
+ if (ids.length === 0) {
5554
+ addSection("Providers", " (none configured)");
5555
+ } else {
5556
+ const lines = ids.sort().map((id) => {
5557
+ const e = providers[id] ?? {};
5558
+ const type = e.type ?? "?";
5559
+ const models = Array.isArray(e.models) ? `[${e.models.join(", ")}]` : "(all)";
5560
+ const hasKey = e.apiKey || Array.isArray(e.apiKeys) && e.apiKeys.length > 0 ? "\u2713" : "\u2717";
5561
+ const baseUrl = e.baseUrl ? ` url:${e.baseUrl}` : "";
5562
+ const family = e.family ? ` family:${e.family}` : "";
5563
+ const envVars = Array.isArray(e.envVars) ? ` env:[${e.envVars.join(", ")}]` : "";
5564
+ return ` ${id === config.provider ? "\u2605" : " "} ${id} (${type}) key:${hasKey} models:${models}${baseUrl}${family}${envVars}`;
5565
+ });
5566
+ addSection("Providers", lines.join("\n"));
5567
+ }
5568
+ }
5569
+ if (section === "all" || section === "models") {
5570
+ const favorites = config.favoriteModels ?? [];
5571
+ addSection(
5572
+ "Favorites",
5573
+ favorites.length > 0 ? favorites.map((f, i) => ` ${i + 1}. ${f}`).join("\n") : " (none \u2014 use favorite_manage to add)"
5574
+ );
5575
+ addSection(
5576
+ "Settings",
5577
+ ` fallbackAuto: ${config.fallbackAuto !== false ? "on" : "off"}
5578
+ favoriteModelsOnly: ${config.favoriteModelsOnly ? "on" : "off"}`
5579
+ );
5580
+ }
5581
+ if (section === "all" || section === "fallbacks") {
5582
+ const fallbackModels = config.fallbackModels ?? [];
5583
+ const profiles = config.fallbackProfiles ?? {};
5584
+ addSection(
5585
+ "Fallback Chain",
5586
+ fallbackModels.length > 0 ? fallbackModels.map((f, i) => ` ${i + 1}. ${f}`).join("\n") : " (empty \u2014 auto fallback applies when fallbackAuto is on)"
5587
+ );
5588
+ const profileNames = Object.keys(profiles);
5589
+ addSection(
5590
+ "Fallback Profiles",
5591
+ profileNames.length > 0 ? profileNames.sort().map((n) => ` ${n} \u2192 ${profiles[n]?.join(" \u2192 ") ?? "(empty)"}`).join("\n") : " (none)"
5592
+ );
5593
+ }
5594
+ if (section === "all" || section === "matrix") {
5595
+ const matrix = config.modelMatrix ?? {};
5596
+ const keys = Object.keys(matrix);
5597
+ addSection(
5598
+ "Model Matrix (role assignments)",
5599
+ keys.length > 0 ? keys.sort().map((k) => ` ${k} \u2192 ${JSON.stringify(matrix[k])}`).join("\n") : " (empty \u2014 all roles use the leader model)"
5600
+ );
5601
+ }
5602
+ if (section === "all" || section === "agents") {
5603
+ const roleNames = Object.keys(AGENT_CATALOG).sort();
5604
+ const lines = roleNames.map((role) => {
5605
+ const phase = phaseForRole(role) ?? "?";
5606
+ const target = resolveSubagentModelTarget(config, role);
5607
+ const model = target?.provider ? `${target.provider}/${target.model ?? "(default)"}` : `${config.provider}/${config.model ?? "(leader)"}`;
5608
+ const src = target?.diversified ? " (diversified)" : target?.source === "matrix" ? ` (matrix:${target.matrixSource ?? "?"})` : "";
5609
+ return ` ${role.padEnd(24)} ${phase.padEnd(14)} ${model}${src}`;
5610
+ });
5611
+ addSection(
5612
+ `Agent Models (${roleNames.length} roles)`,
5613
+ lines.length > 0 ? ` ${"ROLE".padEnd(24)} ${"PHASE".padEnd(14)} RESOLVED MODEL
5614
+ ` + lines.join("\n") : " (no agents in catalog)"
5615
+ );
5616
+ }
5617
+ if (section === "all" || section === "doctor") {
5618
+ const issues = [];
5619
+ const warnings = [];
5620
+ const ok = [];
5621
+ const providers = config.providers ?? {};
5622
+ const favorites = config.favoriteModels ?? [];
5623
+ const profiles = config.fallbackProfiles ?? {};
5624
+ const chain = config.fallbackModels ?? [];
5625
+ const matrix = config.modelMatrix ?? {};
5626
+ for (const fav of favorites) {
5627
+ const p = parseRefInternal(fav);
5628
+ const provId = p.provider ?? config.provider;
5629
+ const model = p.model;
5630
+ const prov = providers[provId];
5631
+ if (!prov) {
5632
+ warnings.push(`Favorite "${fav}" references unknown provider "${provId}"`);
5633
+ continue;
5634
+ }
5635
+ const provModels = prov.models;
5636
+ if (provModels && provModels.length > 0 && !provModels.includes(model)) {
5637
+ warnings.push(`Favorite "${fav}" \u2014 model "${model}" not in ${provId} model list (${provModels.join(", ")})`);
5638
+ } else {
5639
+ ok.push(`Favorite "${fav}" \u2014 provider ${provId} is configured`);
5640
+ }
5641
+ }
5642
+ for (const entry of chain) {
5643
+ const p = parseRefInternal(entry);
5644
+ const provId = p.provider ?? config.provider;
5645
+ if (!providers[provId] && provId !== config.provider) {
5646
+ issues.push(`Chain entry "${entry}" references unknown provider "${provId}"`);
5647
+ } else {
5648
+ ok.push(`Chain entry "${entry}" \u2014 provider OK`);
5649
+ }
5650
+ }
5651
+ for (const [pname, pchain] of Object.entries(profiles)) {
5652
+ if (!pchain || pchain.length === 0) {
5653
+ warnings.push(`Profile "${pname}" is empty`);
5654
+ continue;
5655
+ }
5656
+ for (const entry of pchain) {
5657
+ const p = parseRefInternal(entry);
5658
+ const provId = p.provider ?? config.provider;
5659
+ if (!providers[provId] && provId !== config.provider) {
5660
+ issues.push(`Profile "${pname}" entry "${entry}" references unknown provider "${provId}"`);
5661
+ }
5662
+ }
5663
+ }
5664
+ for (const [key, entry] of Object.entries(matrix)) {
5665
+ const eProvider = entry.provider ?? config.provider;
5666
+ const eModel = entry.model;
5667
+ if (eModel) {
5668
+ const provData = providers[eProvider];
5669
+ if (!provData && eProvider !== config.provider) {
5670
+ issues.push(`Matrix "${key}" references unknown provider "${eProvider}"`);
5671
+ }
5672
+ const provModels = provData?.models;
5673
+ if (provModels && provModels.length > 0 && !provModels.includes(eModel)) {
5674
+ warnings.push(`Matrix "${key}" \u2014 model "${eModel}" not in ${eProvider} model list`);
5675
+ }
5676
+ }
5677
+ const eProfile = entry.fallbackProfile;
5678
+ if (eProfile && !profiles[eProfile]) {
5679
+ issues.push(`Matrix "${key}" references unknown fallback profile "${eProfile}"`);
5680
+ }
5681
+ }
5682
+ if (!providers[config.provider] && Object.keys(providers).length > 0) {
5683
+ warnings.push(`Leader provider "${config.provider}" has no explicit config entry`);
5684
+ }
5685
+ const summary = ` \u2713 ${ok.length} checks passed
5686
+ \u26A0 ${warnings.length} warnings
5687
+ \u2717 ${issues.length} issues`;
5688
+ const lines = [summary, ""];
5689
+ if (warnings.length > 0) {
5690
+ lines.push("\u2500\u2500 Warnings \u2500\u2500");
5691
+ lines.push(...warnings.map((w) => ` \u26A0 ${w}`));
5692
+ lines.push("");
5693
+ }
5694
+ if (issues.length > 0) {
5695
+ lines.push("\u2500\u2500 Issues \u2500\u2500");
5696
+ lines.push(...issues.map((i) => ` \u2717 ${i}`));
5697
+ lines.push("");
5698
+ }
5699
+ if (warnings.length === 0 && issues.length === 0 && ok.length > 0) {
5700
+ lines.push(" All checks passed \u2014 configuration is healthy.");
5701
+ }
5702
+ addSection("Configuration Doctor", lines.join("\n"));
5703
+ }
5704
+ if (section === "all" || section === "refiner") {
5705
+ const ref = config.autonomy;
5706
+ addSection(
5707
+ "Goal Refinement",
5708
+ ` refinerProvider: ${ref?.refinerProvider ?? "(same as leader)"}
5709
+ refinerModel: ${ref?.refinerModel ?? "(default for provider)"}
5710
+ refinerFallbackProfile: ${ref?.refinerFallbackProfile ?? "(none)"}`
5711
+ );
5712
+ }
5713
+ return { status: "ok", message: sections.join("\n\n") };
5714
+ }
5715
+ };
5716
+ }
5717
+ function createFallbackManageTools(opts) {
5718
+ return [
5719
+ createFavoriteManageTool(opts),
5720
+ createFallbackChainManageTool(opts),
5721
+ createFallbackProfileManageTool(opts),
5722
+ createAgentModelAssignTool(opts),
5723
+ createProviderManageTool(opts),
5724
+ createProviderKeySetTool(opts),
5725
+ createLeaderModelSetTool(opts),
5726
+ createSystemConfigViewTool(opts)
5727
+ ];
5728
+ }
2507
5729
  export {
5730
+ AGENT_MODEL_ASSIGN_TOOL_NAME,
2508
5731
  COUNCIL_TOOL_NAME,
5732
+ FALLBACK_CHAIN_MANAGE_TOOL_NAME,
5733
+ FALLBACK_PROFILE_MANAGE_TOOL_NAME,
5734
+ FAVORITE_MANAGE_TOOL_NAME,
5735
+ LEADER_MODEL_SET_TOOL_NAME,
2509
5736
  MAX_COUNCIL_CONTEXT_CHARS,
2510
5737
  MAX_COUNCIL_QUESTION_CHARS,
2511
5738
  MAX_COUNCIL_TOOL_OPTIONS,
2512
5739
  ONE_SHOT_LLM_TOOL_NAME,
5740
+ PROVIDER_KEY_SET_TOOL_NAME,
5741
+ PROVIDER_MANAGE_TOOL_NAME,
5742
+ SYSTEM_CONFIG_VIEW_TOOL_NAME,
2513
5743
  createCouncilTool,
5744
+ createFallbackManageTools,
2514
5745
  createMcpControlTool,
2515
5746
  createOneShotLLMTool
2516
5747
  };