@desplega.ai/agent-swarm 1.158.0 → 1.159.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 (272) hide show
  1. package/dist/{acp-adapter-xaq4n6pc.js → acp-adapter-qhch0n2v.js} +1 -1
  2. package/dist/{actions-p9hz5yat.js → actions-dtsh1mfb.js} +33 -29
  3. package/dist/{anthropic-messages-51pz26qe.js → anthropic-messages-f2h06k64.js} +8 -3
  4. package/dist/{app-g26wdcz6.js → app-y1b9srva.js} +10 -9
  5. package/dist/{assistant-wydfqkb3.js → assistant-31hwy5xj.js} +34 -30
  6. package/dist/{azure-openai-responses-8gksqtmz.js → azure-openai-responses-0k1mfax5.js} +12 -13
  7. package/dist/{boot-reembed-se8zmdcp.js → boot-reembed-48d2qpkt.js} +11 -11
  8. package/dist/{boot-reembed-g5ckg9yb.js → boot-reembed-w0bryhkr.js} +12 -12
  9. package/dist/{boot-scrub-logs-wfxqan61.js → boot-scrub-logs-t65wbrn7.js} +9 -8
  10. package/dist/{boot-seeds-s3q01xj3.js → boot-seeds-h7c32hvm.js} +12 -11
  11. package/dist/{claude-adapter-tv2x4f36.js → claude-adapter-1t6611ne.js} +26 -21
  12. package/dist/{claude-managed-adapter-41b2s7fd.js → claude-managed-adapter-werp659j.js} +4 -2
  13. package/dist/{claude-managed-setup-60g8aazx.js → claude-managed-setup-y6rdgbc1.js} +11 -2
  14. package/dist/claude-sdk-session-zhkyk8yw.js +29319 -0
  15. package/dist/{cli-vj6h9kc2.js → cli-0m7wct0d.js} +59 -1
  16. package/dist/{cli-wscdvcqz.js → cli-0n2e4x49.js} +1 -1
  17. package/dist/cli-0xqde53b.js +176 -0
  18. package/dist/{cli-xberxa1r.js → cli-10ym38qc.js} +2 -2
  19. package/dist/{cli-5b6b62tz.js → cli-13hw4z91.js} +1 -1
  20. package/dist/{cli-rqz8qbw7.js → cli-20035trh.js} +50 -18
  21. package/dist/{cli-k0g1sjzm.js → cli-31nkw3g3.js} +3 -3
  22. package/dist/cli-3gvrr8vj.js +1422 -0
  23. package/dist/{cli-c07r9a2m.js → cli-48527fpw.js} +9 -8
  24. package/dist/{cli-24k806sc.js → cli-4aj25szq.js} +2 -2
  25. package/dist/{cli-7gtvvpbh.js → cli-4tvgz99m.js} +7 -7
  26. package/dist/cli-503ya656.js +455 -0
  27. package/dist/{cli-cw9zck8j.js → cli-5f2hq128.js} +8 -4
  28. package/dist/{cli-s8eet254.js → cli-68bmme5p.js} +11078 -27833
  29. package/dist/{cli-ftfvggzx.js → cli-79aq9cgb.js} +794 -563
  30. package/dist/cli-7cxw8b60.js +14910 -0
  31. package/dist/cli-7kyk9jr3.js +22 -0
  32. package/dist/cli-7zttzetj.js +324 -0
  33. package/dist/{cli-y95wp3tg.js → cli-96qd5psa.js} +1 -1
  34. package/dist/{cli-g8djp6qe.js → cli-9yg65c1j.js} +22 -0
  35. package/dist/{cli-3gmcwfyx.js → cli-ardpzhjm.js} +5 -5
  36. package/dist/{cli-7s3syk22.js → cli-b6thtzac.js} +5 -5
  37. package/dist/{cli-00w2c66p.js → cli-bgv70kym.js} +13 -2
  38. package/dist/{cli-j55n64b6.js → cli-bv0a38v9.js} +163 -44
  39. package/dist/cli-ca2w7zv7.js +115 -0
  40. package/dist/cli-cpvsaqsw.js +333 -0
  41. package/dist/cli-cygyqj3b.js +754 -0
  42. package/dist/{cli-x24pshtq.js → cli-dvjpkkjd.js} +6 -6
  43. package/dist/{cli-5d10vd2m.js → cli-e0ndxhtk.js} +4 -4
  44. package/dist/{cli-4198qc9v.js → cli-e4xnc0qp.js} +15 -3
  45. package/dist/{cli-rjehvrts.js → cli-eg7cdg68.js} +8 -8
  46. package/dist/cli-ejpygnqz.js +3815 -0
  47. package/dist/{cli-fgqxwrpv.js → cli-fjtp3463.js} +1631 -127
  48. package/dist/{cli-zt1227k1.js → cli-fkqhbg8g.js} +5 -3
  49. package/dist/{cli-47m6dkd3.js → cli-g9df2m37.js} +409 -168
  50. package/dist/cli-gzyh0j0g.js +8455 -0
  51. package/dist/cli-hgqfttfc.js +183 -0
  52. package/dist/cli-hj65wsts.js +17755 -0
  53. package/dist/cli-j9z8mfx3.js +476 -0
  54. package/dist/{cli-aehm1rbq.js → cli-kc3dp84d.js} +1135 -61
  55. package/dist/{cli-h38x0qhg.js → cli-kdnehe0t.js} +4 -3
  56. package/dist/{cli-20cj37vt.js → cli-m4excttz.js} +136 -2216
  57. package/dist/{cli-8qmme6vd.js → cli-mg24hkt4.js} +2 -2
  58. package/dist/{cli-xhaeg2nc.js → cli-mzkqpxee.js} +1 -1
  59. package/dist/{cli-q67y4wzm.js → cli-phsaj4xq.js} +24 -2
  60. package/dist/{cli-pw9rj5t9.js → cli-pqetb83f.js} +1 -176
  61. package/dist/{cli-rj19hc34.js → cli-qjxy52w7.js} +1 -1
  62. package/dist/{cli-53sz8gkz.js → cli-qv4jv8bx.js} +1 -1
  63. package/dist/cli-qvnnx4ct.js +1051 -0
  64. package/dist/cli-rb9m6rpe.js +4980 -0
  65. package/dist/{cli-tt9k8r35.js → cli-rfezr056.js} +476 -909
  66. package/dist/{cli-ggm92wcn.js → cli-rgjprct3.js} +1 -0
  67. package/dist/{cli-9httdpyq.js → cli-rrqmqr1s.js} +1 -1
  68. package/dist/cli-rykfmgwg.js +102 -0
  69. package/dist/{cli-eetrvv91.js → cli-t3p77qk1.js} +1 -1
  70. package/dist/{cli-68w09sjw.js → cli-vn0qv9cj.js} +25 -88
  71. package/dist/{cli-987hg0aa.js → cli-vwbbaecp.js} +2 -2
  72. package/dist/cli-wd9hz40d.js +69 -0
  73. package/dist/{cli-j57qkrpq.js → cli-wgjx0q33.js} +13 -13
  74. package/dist/cli-xgpf00sk.js +2149 -0
  75. package/dist/{cli-mcnhgnn1.js → cli-ykk5jaax.js} +184 -26
  76. package/dist/{cli-aky8tctm.js → cli-z9x0sh1t.js} +3 -3
  77. package/dist/{cli-1hkrch3k.js → cli-zw65mab6.js} +5 -5
  78. package/dist/cli.js +38 -29
  79. package/dist/cloudflare-workers-ai-system-one-1sg586qv.js +46 -0
  80. package/dist/{codex-adapter-cnm8wk11.js → codex-adapter-jqfqqa21.js} +21 -14
  81. package/dist/{codex-hook-9hptmdhj.js → codex-hook-5eys3dya.js} +2 -2
  82. package/dist/{codex-session-runner-gfg547zj.js → codex-session-runner-zd341dsm.js} +21 -14
  83. package/dist/{commands-e3bn0p75.js → commands-vfsysz4s.js} +9 -8
  84. package/dist/{db-03v9kzr2.js → db-s3znhvbn.js} +15 -10
  85. package/dist/{dispatcher-nrmg8yht.js → dispatcher-5xn0c7rf.js} +32 -28
  86. package/dist/{dsh-adapter-k03nfqa6.js → dsh-adapter-894mgfvs.js} +3 -3
  87. package/dist/{e2b-93x1k0j5.js → e2b-ez07znam.js} +4 -3
  88. package/dist/execute-71ysgv35.js +965 -0
  89. package/dist/{google-generative-ai-bxmv50nc.js → google-generative-ai-1nfcdphp.js} +5 -4
  90. package/dist/{google-vertex-809af514.js → google-vertex-1tqcd7b1.js} +5 -4
  91. package/dist/{handlers-aqcedbyb.js → handlers-5w4jwycr.js} +35 -31
  92. package/dist/{hook-7vkskkre.js → hook-853bxrr6.js} +16 -13
  93. package/dist/{hook-ybvmrmhq.js → hook-z0hbxj85.js} +16 -13
  94. package/dist/{http-2eqa8t24.js → http-akc3wy3m.js} +600 -123
  95. package/dist/{index-f6cryxwe.js → index-4a5njnm1.js} +2 -59
  96. package/dist/{index-a3vn96n8.js → index-5sz16ksh.js} +187 -47
  97. package/dist/{index-c6zsv1bd.js → index-bjmjh9h4.js} +11 -11
  98. package/dist/{index-chgdkq36.js → index-cd151qkq.js} +32 -28
  99. package/dist/{index-x71bvq0p.js → index-k661dzzr.js} +33 -29
  100. package/dist/{index-1yks3qcr.js → index-ttpbhnjg.js} +32 -28
  101. package/dist/{keepalive-vtkf5n99.js → keepalive-8p9rpvz5.js} +11 -10
  102. package/dist/{lead-h3sp357x.js → lead-jp69z5h3.js} +49 -45
  103. package/dist/llama-cpp-classify-t2tccams.js +335 -0
  104. package/dist/{maintenance-jzeqtk3q.js → maintenance-fwx4ev0k.js} +12 -12
  105. package/dist/{mistral-conversations-zx0kn554.js → mistral-conversations-bhvwqcnk.js} +14 -17
  106. package/dist/{oauth-refresh-sweep-qp3dzmdp.js → oauth-refresh-sweep-cwcq1ctx.js} +10 -9
  107. package/dist/{onboard-tmsp0qbz.js → onboard-2b4q0t84.js} +6 -5
  108. package/dist/{openai-codex-responses-4vjqzvjp.js → openai-codex-responses-nekthgks.js} +21 -8
  109. package/dist/{openai-completions-9hjrqbw8.js → openai-completions-hxtzwj0s.js} +8 -11
  110. package/dist/{openai-responses-7b1pn0xt.js → openai-responses-w4r118w7.js} +22 -17
  111. package/dist/{opencode-adapter-x6x2da1z.js → opencode-adapter-zz7xvjj6.js} +6 -4
  112. package/dist/{openrouter-images-9f9ek4rw.js → openrouter-images-b6sqqjgn.js} +4 -6
  113. package/dist/{otel-impl-9mz73q65.js → otel-impl-g8rs4vwr.js} +1 -1
  114. package/dist/{pi-messages-txpjj39q.js → pi-messages-vxdt8v7j.js} +2 -1
  115. package/dist/{pi-mono-adapter-m6dvv0dz.js → pi-mono-adapter-n0me28wt.js} +29 -17
  116. package/dist/pricing-refresh-0v77385v.js +39 -0
  117. package/dist/{rbac-roles-c9wqz83x.js → rbac-roles-krjyazbj.js} +10 -9
  118. package/dist/{render-v2-gryrh9h8.js → render-v2-3wjsp9aw.js} +10 -9
  119. package/dist/runtime-ktjysacx.js +24 -0
  120. package/dist/{setup-q9720bt6.js → setup-yeqryke6.js} +6 -5
  121. package/dist/typesafe-system-one-96960jxp.js +27 -0
  122. package/dist/{virtual-modules-3htpgn8x.js → virtual-modules-dzdeq1sf.js} +21 -13
  123. package/dist/{work-objects-b76x7deb.js → work-objects-3pakpkw0.js} +35 -31
  124. package/dist/{worker-6vrxbv41.js → worker-85testgj.js} +49 -45
  125. package/openapi.json +863 -14
  126. package/package.json +9 -8
  127. package/src/be/db/agents.ts +20 -0
  128. package/src/be/db/tasks/human-free.ts +442 -0
  129. package/src/be/db/tasks/read.ts +56 -1
  130. package/src/be/db/tasks/write.ts +107 -11
  131. package/src/be/db.ts +89 -165
  132. package/src/be/harness-model-support.ts +182 -0
  133. package/src/be/human-free-drain.ts +67 -0
  134. package/src/be/migrations/182_task_human_free_flag.sql +63 -0
  135. package/src/be/migrations/183_human_free_reclassify_queue.sql +25 -0
  136. package/src/be/migrations/184_model_catalog.sql +120 -0
  137. package/src/be/model-catalog-store.ts +644 -0
  138. package/src/be/model-tier-keys.ts +31 -0
  139. package/src/be/model-tier-resolution.ts +489 -0
  140. package/src/be/model-validation.ts +112 -0
  141. package/src/be/models-catalog.ts +61 -7
  142. package/src/be/modelsdev-cache.json +82 -0
  143. package/src/be/modelsdev-cache.ts +2 -0
  144. package/src/be/pricing-refresh.ts +154 -12
  145. package/src/be/scripts/typecheck.ts +15 -0
  146. package/src/be/seed-scripts/catalog/task-failure-audit.ts +61 -9
  147. package/src/be/swarm-config-guard.ts +6 -0
  148. package/src/cli.tsx +10 -0
  149. package/src/commands/claude-managed-setup.ts +3 -1
  150. package/src/commands/runner.ts +76 -2
  151. package/src/extensions/contract-types.generated.ts +1 -1
  152. package/src/heartbeat/heartbeat.ts +64 -15
  153. package/src/heartbeat/templates.ts +2 -2
  154. package/src/http/agents.ts +49 -1
  155. package/src/http/index.ts +8 -0
  156. package/src/http/models-catalog.ts +357 -8
  157. package/src/http/poll.ts +45 -2
  158. package/src/http/schedules.ts +36 -6
  159. package/src/http/session-data.ts +2 -5
  160. package/src/http/stats.ts +6 -8
  161. package/src/http/tasks.ts +26 -1
  162. package/src/http/usage-cache.ts +56 -15
  163. package/src/http/workflows.ts +35 -8
  164. package/src/prompts/session-templates.ts +1 -0
  165. package/src/providers/claude-managed-models.ts +47 -161
  166. package/src/providers/codex-adapter.ts +4 -4
  167. package/src/providers/codex-models.ts +65 -198
  168. package/src/providers/reasoning-effort.ts +44 -140
  169. package/src/rbac/legacy-policy.ts +25 -0
  170. package/src/rbac/permissions.ts +10 -0
  171. package/src/scripts-runtime/sdk-allowlist.ts +4 -0
  172. package/src/scripts-runtime/types/stdlib.d.ts +15 -0
  173. package/src/scripts-runtime/types/swarm-extension.d.ts +3 -0
  174. package/src/scripts-runtime/types/swarm-sdk.d.ts +15 -0
  175. package/src/server.ts +6 -0
  176. package/src/slack/message-text.ts +56 -2
  177. package/src/slack/thread-buffer.ts +10 -2
  178. package/src/tests/agents-list-model-display.test.ts +4 -6
  179. package/src/tests/base-prompt.test.ts +3 -0
  180. package/src/tests/claude-adapter.test.ts +10 -0
  181. package/src/tests/claude-managed-adapter.test.ts +12 -18
  182. package/src/tests/codex-adapter.test.ts +2 -1
  183. package/src/tests/codex-gpt6-release.test.ts +66 -5
  184. package/src/tests/context-window.test.ts +7 -5
  185. package/src/tests/db-extraction-contract.test.ts +1 -0
  186. package/src/tests/db-task-read-contract.test.ts +5 -1
  187. package/src/tests/db-task-write-contract.test.ts +4 -1
  188. package/src/tests/docs-index-footprint.test.js +3 -3
  189. package/src/tests/explicit-model-validation.test.ts +332 -0
  190. package/src/tests/harness-model-support.test.ts +374 -0
  191. package/src/tests/heartbeat-checklist-config.test.ts +71 -0
  192. package/src/tests/heartbeat-checklist.test.ts +4 -2
  193. package/src/tests/heartbeat-orphan-resume.test.ts +48 -0
  194. package/src/tests/human-free-flag.test.ts +279 -0
  195. package/src/tests/human-free-oracle.ts +57 -0
  196. package/src/tests/human-free-reclassify.test.ts +669 -0
  197. package/src/tests/migration-046-budgets.test.ts +3 -3
  198. package/src/tests/model-catalog-migration.test.ts +134 -0
  199. package/src/tests/model-catalog-table.test.ts +249 -0
  200. package/src/tests/model-catalog-write-access.test.ts +387 -0
  201. package/src/tests/model-tier-resolution.test.ts +519 -0
  202. package/src/tests/models-catalog.test.ts +1 -1
  203. package/src/tests/multi-runtime-enabled-stats.test.ts +65 -1
  204. package/src/tests/package-publish.test.ts +137 -36
  205. package/src/tests/pricing-routes.test.ts +2 -2
  206. package/src/tests/rbac-engine.test.ts +42 -2
  207. package/src/tests/reasoning-effort.test.ts +109 -0
  208. package/src/tests/refresh-modelsdev-carryforward.test.ts +5 -5
  209. package/src/tests/runtime-model-catalog.test.ts +86 -0
  210. package/src/tests/script-connections.test.ts +5 -0
  211. package/src/tests/seed-scripts.test.ts +89 -0
  212. package/src/tests/session-costs.test.ts +14 -11
  213. package/src/tests/slack-message-text.test.ts +101 -0
  214. package/src/tests/slack-thread-buffer-context.test.ts +93 -0
  215. package/src/tests/task-cascade-fail.test.ts +148 -2
  216. package/src/tests/tool-annotations.test.ts +1 -1
  217. package/src/tests/usage-cache.test.ts +166 -0
  218. package/src/tests/workflow-executors.test.ts +5 -2
  219. package/src/tests/workflow-system-one-decision-review.test.ts +1021 -0
  220. package/src/tests/workflow-system-one-decision-save-warnings.test.ts +365 -0
  221. package/src/tests/workflow-system-one-decision.test.ts +2063 -0
  222. package/src/tools/model-catalog/index.ts +137 -0
  223. package/src/tools/schedules/create-schedule.ts +16 -1
  224. package/src/tools/schedules/patch-schedule.ts +17 -0
  225. package/src/tools/schedules/update-schedule.ts +17 -0
  226. package/src/tools/send-task.ts +14 -0
  227. package/src/tools/task-action.ts +12 -0
  228. package/src/tools/tool-config.ts +3 -1
  229. package/src/tools/workflows/create-workflow.ts +9 -2
  230. package/src/tools/workflows/patch-workflow-node.ts +17 -7
  231. package/src/tools/workflows/patch-workflow.ts +9 -2
  232. package/src/tools/workflows/update-workflow.ts +9 -2
  233. package/src/types.ts +30 -1
  234. package/src/utils/context-window.ts +47 -53
  235. package/src/utils/harness-cli-version.ts +115 -0
  236. package/src/utils/harness-model-error.ts +33 -0
  237. package/src/utils/runtime-model-catalog.ts +137 -0
  238. package/src/workflows/approval-resolution.ts +39 -0
  239. package/src/workflows/definition.ts +7 -0
  240. package/src/workflows/engine.ts +69 -1
  241. package/src/workflows/executors/agent-task.ts +9 -0
  242. package/src/workflows/executors/base.ts +53 -0
  243. package/src/workflows/executors/human-in-the-loop.ts +8 -8
  244. package/src/workflows/executors/index.ts +1 -0
  245. package/src/workflows/executors/registry.ts +2 -0
  246. package/src/workflows/executors/system-one-decision.ts +1232 -0
  247. package/src/workflows/executors/system-one-providers.ts +301 -0
  248. package/src/workflows/executors/workflow-llm.ts +13 -1
  249. package/src/workflows/readiness.ts +86 -0
  250. package/src/workflows/recovery.ts +12 -12
  251. package/src/workflows/resume.ts +23 -9
  252. package/templates/schedules/daily-blocker-digest/content.md +4 -5
  253. package/templates/skills/agent-browser/SKILL.md +6 -5
  254. package/templates/skills/agent-browser/content.md +6 -5
  255. package/templates/skills/heartbeat-runbook/config.json +1 -1
  256. package/templates/skills/heartbeat-runbook/content.md +2 -2
  257. package/templates/skills/swarm-scripts/SKILL.md +1 -1
  258. package/templates/skills/swarm-scripts/content.md +1 -1
  259. package/templates/skills/workflow-iterate/content.md +81 -1
  260. package/templates/workflows/claude-code-changelog-watch/content.md +2 -0
  261. package/templates/workflows/pr-review-status-sweep/content.md +2 -0
  262. package/dist/claude-sdk-session-t442qz6f.js +0 -39108
  263. package/dist/cli-0xjkj1j7.js +0 -61
  264. package/dist/cli-65hjmev5.js +0 -1141
  265. package/dist/cli-aw1jwp1r.js +0 -14075
  266. package/dist/cli-h967nb6r.js +0 -6603
  267. package/dist/cli-vapyk8sw.js +0 -20
  268. package/dist/cli-ws6rqycm.js +0 -247
  269. package/dist/pricing-refresh-v1cbr9te.js +0 -27
  270. package/src/providers/modelsdev-reasoning.json +0 -6453
  271. package/dist/{codex-login-x3jk4etn.js → codex-login-he9vrn90.js} +3 -3
  272. package/dist/{index-f24115ge.js → index-2wsdsnmm.js} +3 -3
@@ -60,8 +60,12 @@ export interface CatalogReasoningOption {
60
60
  export interface CatalogModel {
61
61
  id: string;
62
62
  name?: string;
63
- cost?: { input?: number; output?: number };
63
+ cost?: { input?: number; output?: number; cache_read?: number; cache_write?: number };
64
64
  limit?: { context?: number };
65
+ /** models.dev `release_date` (ISO date). Drives newest-first ordering in harness lists. */
66
+ release_date?: string;
67
+ /** models.dev `status` (e.g. "deprecated"). Deprecated models drop out of harness lists. */
68
+ status?: string;
65
69
  reasoning?: boolean;
66
70
  reasoning_options?: CatalogReasoningOption[];
67
71
  }
@@ -94,9 +98,20 @@ function slimModel(modelKey: string, model: ModelsDevModel): CatalogModel {
94
98
  id: model.id ?? modelKey,
95
99
  ...(model.name !== undefined ? { name: model.name } : {}),
96
100
  ...(model.cost !== undefined
97
- ? { cost: { input: model.cost.input, output: model.cost.output } }
101
+ ? {
102
+ cost: {
103
+ input: model.cost.input,
104
+ output: model.cost.output,
105
+ ...(model.cost.cache_read !== undefined ? { cache_read: model.cost.cache_read } : {}),
106
+ ...(model.cost.cache_write !== undefined
107
+ ? { cache_write: model.cost.cache_write }
108
+ : {}),
109
+ },
110
+ }
98
111
  : {}),
99
112
  ...(model.limit?.context !== undefined ? { limit: { context: model.limit.context } } : {}),
113
+ ...(typeof model.release_date === "string" ? { release_date: model.release_date } : {}),
114
+ ...(typeof model.status === "string" ? { status: model.status } : {}),
100
115
  ...(model.reasoning !== undefined ? { reasoning: model.reasoning } : {}),
101
116
  ...(reasoningOptions.length > 0 ? { reasoning_options: reasoningOptions } : {}),
102
117
  };
@@ -148,22 +163,61 @@ function mergePinnedEntries(catalog: ModelsCatalog): void {
148
163
  }
149
164
  }
150
165
 
151
- /** Called by the pricing-refresh loop after every successful full models.dev fetch. */
152
- export function updateLiveModelsCatalog(cache: ModelsDevCache, now = Date.now()): void {
153
- const catalog = buildModelsCatalog(cache);
166
+ /**
167
+ * Install a freshly built live catalog (already merged with the persistent
168
+ * table + overlay by `src/be/model-catalog-store.ts`). Pinned snapshot entries
169
+ * are re-merged so they never drop out of the picker.
170
+ */
171
+ export function setLiveModelsCatalog(catalog: ModelsCatalog, updatedAt: number | null): void {
154
172
  mergePinnedEntries(catalog);
155
173
  liveCatalog = catalog;
156
- liveUpdatedAt = now;
174
+ liveUpdatedAt = updatedAt;
175
+ }
176
+
177
+ /**
178
+ * Legacy in-memory update from a raw models.dev payload. The refresh path now
179
+ * persists to `model_catalog` and reloads via `reloadModelsCatalog()`; this
180
+ * stays for callers/tests that only have a payload in hand.
181
+ */
182
+ export function updateLiveModelsCatalog(cache: ModelsDevCache, now = Date.now()): void {
183
+ setLiveModelsCatalog(buildModelsCatalog(cache), now);
184
+ }
185
+
186
+ /** Drop the in-memory catalog so the next async read rebuilds it from the DB. */
187
+ export function invalidateModelsCatalog(): void {
188
+ liveCatalog = null;
189
+ liveUpdatedAt = null;
190
+ snapshotOverride = null;
191
+ }
192
+
193
+ export function isModelsCatalogLoaded(): boolean {
194
+ return liveCatalog !== null;
195
+ }
196
+
197
+ /** Deep copy of the slimmed vendored snapshot (safe to mutate). */
198
+ export function snapshotModelsCatalog(): ModelsCatalog {
199
+ return structuredClone(bundledSnapshotCatalog());
200
+ }
201
+
202
+ /** Snapshot with overlay rows layered on; served while the table is empty. */
203
+ let snapshotOverride: ModelsCatalog | null = null;
204
+ export function setSnapshotOverlayCatalog(catalog: ModelsCatalog | null): void {
205
+ snapshotOverride = catalog;
157
206
  }
158
207
 
159
208
  export function getModelsCatalog(): ModelsCatalogResult {
160
209
  if (liveCatalog) {
161
210
  return { source: "live", updatedAt: liveUpdatedAt, providers: liveCatalog };
162
211
  }
163
- return { source: "snapshot", updatedAt: null, providers: bundledSnapshotCatalog() };
212
+ return {
213
+ source: "snapshot",
214
+ updatedAt: null,
215
+ providers: snapshotOverride ?? bundledSnapshotCatalog(),
216
+ };
164
217
  }
165
218
 
166
219
  export function resetModelsCatalogForTests(): void {
167
220
  liveCatalog = null;
168
221
  liveUpdatedAt = null;
222
+ snapshotOverride = null;
169
223
  }
@@ -287104,6 +287104,88 @@
287104
287104
  "cache_read": 2.5
287105
287105
  }
287106
287106
  },
287107
+ "gpt-6.1-sol": {
287108
+ "id": "gpt-6.1-sol",
287109
+ "name": "GPT-6.1 Sol",
287110
+ "description": "Near-Astra performance for complex work at a lower cost",
287111
+ "family": "gpt-sol",
287112
+ "attachment": true,
287113
+ "reasoning": true,
287114
+ "reasoning_options": [
287115
+ {
287116
+ "type": "effort",
287117
+ "values": [
287118
+ "low",
287119
+ "medium",
287120
+ "high",
287121
+ "xhigh",
287122
+ "max"
287123
+ ]
287124
+ }
287125
+ ],
287126
+ "tool_call": true,
287127
+ "structured_output": true,
287128
+ "temperature": false,
287129
+ "knowledge": "2026-04-30",
287130
+ "release_date": "2026-09-29",
287131
+ "last_updated": "2026-09-29",
287132
+ "modalities": {
287133
+ "input": [
287134
+ "text",
287135
+ "image"
287136
+ ],
287137
+ "output": [
287138
+ "text"
287139
+ ]
287140
+ },
287141
+ "open_weights": false,
287142
+ "limit": {
287143
+ "context": 1050000,
287144
+ "input": 922000,
287145
+ "output": 128000
287146
+ },
287147
+ "experimental": {
287148
+ "modes": {
287149
+ "fast": {
287150
+ "cost": {
287151
+ "input": 4,
287152
+ "output": 20,
287153
+ "cache_read": 0.2,
287154
+ "cache_write": 5
287155
+ },
287156
+ "provider": {
287157
+ "body": {
287158
+ "service_tier": "priority"
287159
+ }
287160
+ }
287161
+ }
287162
+ }
287163
+ },
287164
+ "cost": {
287165
+ "input": 2,
287166
+ "output": 10,
287167
+ "cache_read": 0.1,
287168
+ "cache_write": 2.5,
287169
+ "tiers": [
287170
+ {
287171
+ "input": 4,
287172
+ "output": 15,
287173
+ "cache_read": 0.2,
287174
+ "cache_write": 5,
287175
+ "tier": {
287176
+ "type": "context",
287177
+ "size": 272000
287178
+ }
287179
+ }
287180
+ ],
287181
+ "context_over_200k": {
287182
+ "input": 4,
287183
+ "output": 15,
287184
+ "cache_read": 0.2,
287185
+ "cache_write": 5
287186
+ }
287187
+ }
287188
+ },
287107
287189
  "gpt-6-sol": {
287108
287190
  "id": "gpt-6-sol",
287109
287191
  "name": "GPT-6 Sol",
@@ -18,6 +18,8 @@ export interface ModelsDevModel {
18
18
  name?: string;
19
19
  cost?: ModelsDevCostBlock;
20
20
  limit?: { context?: number };
21
+ release_date?: string;
22
+ status?: string;
21
23
  reasoning?: boolean;
22
24
  reasoning_options?: ModelsDevReasoningOption[];
23
25
  }
@@ -6,14 +6,25 @@ import {
6
6
  type InsertPricingRowInput,
7
7
  insertPricingRow,
8
8
  } from "./db";
9
- import { updateLiveModelsCatalog } from "./models-catalog";
9
+ import {
10
+ applyOverlayPricingRows,
11
+ buildCatalogEntries,
12
+ countModelCatalog,
13
+ expireMatchedOverlays,
14
+ getModelCatalogMeta,
15
+ reloadModelsCatalog,
16
+ replaceModelCatalog,
17
+ touchModelCatalogCheckedAt,
18
+ updateModelCatalogMeta,
19
+ } from "./model-catalog-store";
10
20
  import type { ModelsDevCache } from "./modelsdev-cache";
11
21
  import { buildModelsDevSeedRows, type PricingSeedRow } from "./seed-pricing";
12
22
 
13
23
  const MODELSDEV_API_URL = "https://models.dev/api.json";
14
24
  export const PRICING_REFRESH_INTERVAL_MS = 12 * 60 * 60 * 1000;
25
+ /** Unforced `refreshModelCatalog()` calls skip the network when the last models.dev check (200 or 304) is younger than this. */
26
+ export const MODEL_CATALOG_FRESH_MS = 4 * 60 * 60 * 1000;
15
27
 
16
- let lastETag: string | null = null;
17
28
  let refreshLoopStarted = false;
18
29
 
19
30
  interface RefreshPricingOptions {
@@ -28,6 +39,11 @@ export interface PricingRefreshResult {
28
39
  unchanged: number;
29
40
  pruned: number;
30
41
  etag?: string;
42
+ /** Catalog keys ("provider/modelId") new since the previous fetch. */
43
+ added?: string[];
44
+ /** Overlay rows auto-expired because upstream now matches. */
45
+ expiredOverlays?: string[];
46
+ catalogModels?: number;
31
47
  }
32
48
 
33
49
  function logPricingRefresh(message: string): void {
@@ -129,17 +145,29 @@ export async function refreshPricingFromModelsDev(
129
145
  ): Promise<PricingRefreshResult> {
130
146
  const fetchImpl = opts.fetchImpl ?? fetch;
131
147
  const now = opts.now ?? Date.now();
132
- const headers: Record<string, string> = lastETag ? { "If-None-Match": lastETag } : {};
148
+ const meta = await getModelCatalogMeta();
149
+ // Only send validators when the table is populated — a 304 against an empty
150
+ // table (fresh DB, same ETag) would leave the catalog empty.
151
+ const catalogCount = await countModelCatalog();
152
+ const headers: Record<string, string> = {};
153
+ if (catalogCount > 0 && meta.etag) headers["If-None-Match"] = meta.etag;
154
+ if (catalogCount > 0 && meta.lastModified) headers["If-Modified-Since"] = meta.lastModified;
133
155
 
134
156
  const response = await fetchImpl(MODELSDEV_API_URL, { headers });
135
157
  if (response.status === 304) {
158
+ await touchModelCatalogCheckedAt(now);
159
+ await updateModelCatalogMeta({ lastCheckedAt: now });
160
+ await reloadModelsCatalog();
136
161
  const result: PricingRefreshResult = {
137
162
  status: "not_modified",
138
163
  candidateRows: 0,
139
164
  inserted: 0,
140
165
  unchanged: 0,
141
166
  pruned: 0,
142
- etag: lastETag ?? undefined,
167
+ etag: meta.etag ?? undefined,
168
+ added: [],
169
+ expiredOverlays: [],
170
+ catalogModels: catalogCount,
143
171
  };
144
172
  await auditPricingRefresh(result);
145
173
  logPricingRefresh("models.dev returned 304; pricing rows unchanged");
@@ -151,36 +179,150 @@ export async function refreshPricingFromModelsDev(
151
179
 
152
180
  const cache = (await response.json()) as ModelsDevCache;
153
181
  const etag = response.headers.get("etag");
154
- updateLiveModelsCatalog(cache, now);
182
+ const lastModified = response.headers.get("last-modified");
183
+ const entries = buildCatalogEntries(cache, now);
184
+ const added = await replaceModelCatalog(entries);
155
185
  const rows = buildModelsDevSeedRows(cache);
156
186
  const { inserted, unchanged } = await insertChangedPricingRows(rows, now);
187
+ const expiredOverlays = await expireMatchedOverlays();
188
+ // Overlay prices only fill (provider, model, tokenClass) gaps upstream left.
189
+ const overlayInserted = await applyOverlayPricingRows(now);
157
190
  const pruned = await prunePricingHistory(2);
158
- lastETag = etag;
191
+ await updateModelCatalogMeta({ etag, lastModified, lastFetchAt: now, lastCheckedAt: now });
192
+ await reloadModelsCatalog();
159
193
 
160
194
  const result: PricingRefreshResult = {
161
195
  status: "refreshed",
162
196
  candidateRows: rows.length,
163
- inserted,
197
+ inserted: inserted + overlayInserted,
164
198
  unchanged,
165
199
  pruned,
166
- etag: lastETag ?? undefined,
200
+ etag: etag ?? undefined,
201
+ added,
202
+ expiredOverlays,
203
+ catalogModels: entries.length,
167
204
  };
168
205
  await auditPricingRefresh(result);
169
206
  logPricingRefresh(
170
- `refreshed ${rows.length} candidate row(s); inserted=${inserted}; unchanged=${unchanged}; pruned=${pruned}`,
207
+ `refreshed ${rows.length} candidate row(s); inserted=${inserted}; unchanged=${unchanged}; pruned=${pruned}; catalog=${entries.length}; added=${added.length}; expiredOverlays=${expiredOverlays.length}`,
171
208
  );
172
209
  return result;
173
210
  }
174
211
 
175
- async function runPricingRefreshSafely(): Promise<void> {
212
+ export interface ModelCatalogRefreshResult {
213
+ status: "updated" | "not-modified" | "skipped-fresh" | "skipped-cooldown" | "error";
214
+ models: number;
215
+ added: string[];
216
+ checkedAt: number | null;
217
+ /** Set with `skipped-cooldown`: milliseconds until a forced refresh is accepted again. */
218
+ retryAfterMs?: number;
219
+ error?: string;
220
+ }
221
+
222
+ /** Minimum gap between forced refreshes accepted from callers (HTTP route, MCP tool). */
223
+ export const MODEL_CATALOG_FORCE_COOLDOWN_MS = 60 * 1000;
224
+
225
+ let refreshInFlight: Promise<ModelCatalogRefreshResult> | null = null;
226
+ let lastForcedRefreshAt: number | null = null;
227
+
228
+ /** Test-only: forget the in-flight refresh and the forced-refresh cooldown. */
229
+ export function resetModelCatalogRefreshGuardForTests(): void {
230
+ refreshInFlight = null;
231
+ lastForcedRefreshAt = null;
232
+ }
233
+
234
+ /**
235
+ * `pi update --models` for the swarm. Unforced calls skip the network when the
236
+ * last fetch is younger than MODEL_CATALOG_FRESH_MS; `force` always fetches
237
+ * (still conditional on the stored ETag). Never throws — failures come back as
238
+ * `status: "error"` and the catalog keeps serving the last good table (or the
239
+ * vendored snapshot).
240
+ */
241
+ export function refreshModelCatalog(
242
+ opts: { force?: boolean } & RefreshPricingOptions = {},
243
+ ): Promise<ModelCatalogRefreshResult> {
244
+ // Single flight: a refresh that is already running serves every concurrent caller, so a burst of
245
+ // requests (or the boot loop racing a forced call) costs one models.dev fetch.
246
+ if (refreshInFlight) return refreshInFlight;
247
+ const run = runModelCatalogRefresh(opts).finally(() => {
248
+ if (refreshInFlight === run) refreshInFlight = null;
249
+ });
250
+ refreshInFlight = run;
251
+ return run;
252
+ }
253
+
254
+ /**
255
+ * Caller-facing entry for the HTTP route and the MCP tool: `refreshModelCatalog` plus a cooldown
256
+ * on `force`. A forced call within MODEL_CATALOG_FORCE_COOLDOWN_MS of the previous one does not
257
+ * fetch; it reports `skipped-cooldown` and how long to wait. Unforced calls pass straight through
258
+ * (they already skip the network while the table is fresh).
259
+ */
260
+ export async function requestModelCatalogRefresh(
261
+ opts: { force?: boolean } & RefreshPricingOptions = {},
262
+ ): Promise<ModelCatalogRefreshResult> {
263
+ const now = opts.now ?? Date.now();
264
+ if (opts.force) {
265
+ const waitMs =
266
+ lastForcedRefreshAt === null
267
+ ? 0
268
+ : lastForcedRefreshAt + MODEL_CATALOG_FORCE_COOLDOWN_MS - now;
269
+ if (waitMs > 0) {
270
+ const meta = await getModelCatalogMeta().catch(() => null);
271
+ return {
272
+ status: "skipped-cooldown",
273
+ models: await countModelCatalog().catch(() => 0),
274
+ added: [],
275
+ checkedAt: meta?.lastCheckedAt ?? null,
276
+ retryAfterMs: waitMs,
277
+ };
278
+ }
279
+ lastForcedRefreshAt = now;
280
+ }
281
+ return refreshModelCatalog({ ...opts, now });
282
+ }
283
+
284
+ async function runModelCatalogRefresh(
285
+ opts: { force?: boolean } & RefreshPricingOptions = {},
286
+ ): Promise<ModelCatalogRefreshResult> {
287
+ const now = opts.now ?? Date.now();
176
288
  try {
177
- await refreshPricingFromModelsDev();
289
+ const meta = await getModelCatalogMeta();
290
+ const count = await countModelCatalog();
291
+ if (
292
+ !opts.force &&
293
+ count > 0 &&
294
+ meta.lastCheckedAt !== null &&
295
+ now - meta.lastCheckedAt < MODEL_CATALOG_FRESH_MS
296
+ ) {
297
+ await reloadModelsCatalog();
298
+ return { status: "skipped-fresh", models: count, added: [], checkedAt: meta.lastCheckedAt };
299
+ }
300
+ const result = await refreshPricingFromModelsDev({ fetchImpl: opts.fetchImpl, now });
301
+ return {
302
+ status: result.status === "refreshed" ? "updated" : "not-modified",
303
+ models: result.catalogModels ?? (await countModelCatalog()),
304
+ added: result.added ?? [],
305
+ checkedAt: now,
306
+ };
178
307
  } catch (err) {
179
- logPricingRefreshError("refresh failed", err);
308
+ logPricingRefreshError("model catalog refresh failed", err);
180
309
  await auditPricingRefreshFailure(err);
310
+ const meta = await getModelCatalogMeta().catch(() => null);
311
+ return {
312
+ status: "error",
313
+ models: await countModelCatalog().catch(() => 0),
314
+ added: [],
315
+ checkedAt: meta?.lastCheckedAt ?? null,
316
+ error: scrubSecrets(err instanceof Error ? err.message : String(err)),
317
+ };
181
318
  }
182
319
  }
183
320
 
321
+ async function runPricingRefreshSafely(): Promise<void> {
322
+ // refreshModelCatalog never throws; errors are logged + audited inside.
323
+ await refreshModelCatalog();
324
+ }
325
+
184
326
  export function startPricingRefreshLoop(): void {
185
327
  if (refreshLoopStarted) return;
186
328
  refreshLoopStarted = true;
@@ -262,6 +262,21 @@ export interface SwarmSdk {
262
262
  config_set(args: { key: string; value: unknown; scope?: "global" | "agent" | "repo"; scopeId?: string; isSecret?: boolean }): Promise<unknown>;
263
263
  config_delete(args: { id: string }): Promise<unknown>;
264
264
 
265
+ // --- write: model catalog ---
266
+ modelCatalog_refresh(args?: { force?: boolean }): Promise<unknown>;
267
+ modelCatalog_overlayUpsert(args: {
268
+ provider: string;
269
+ modelId: string;
270
+ name?: string;
271
+ releaseDate?: string;
272
+ contextWindow?: number;
273
+ maxOutput?: number;
274
+ reasoningOptions?: Array<{ type: string; values?: string[] }>;
275
+ pricing?: { input?: number; output?: number; cache_read?: number; cache_write?: number };
276
+ reason: string;
277
+ verifiedBy?: string;
278
+ }): Promise<unknown>;
279
+
265
280
  // --- write: slack ---
266
281
  slack_post(args: { channelId: string; message: string; blocks?: unknown }): Promise<unknown>;
267
282
  slack_reply(args: { channelId?: string; threadTs?: string; message: string; taskId?: string; blocks?: unknown }): Promise<unknown>;
@@ -1,6 +1,8 @@
1
1
  import { z } from "zod";
2
2
  import { publishCatalogReportPage } from "./catalog-report";
3
3
 
4
+ const MAX_TASK_SCAN_LIMIT = 500;
5
+
4
6
  export const argsSchema = z.object({
5
7
  days: z
6
8
  .number()
@@ -16,8 +18,9 @@ export const argsSchema = z.object({
16
18
  .number()
17
19
  .int()
18
20
  .positive()
21
+ .max(MAX_TASK_SCAN_LIMIT)
19
22
  .optional()
20
- .describe("Max failed tasks to scan (default 500)"),
23
+ .describe("Max failed tasks to scan (default 500, max 500)"),
21
24
  publishPage: z.boolean().optional().describe("Publish an authed HTML page (default true)"),
22
25
  });
23
26
 
@@ -25,20 +28,37 @@ const REASON_PATTERNS: any[] = [
25
28
  { key: "sigterm/killed", re: /sigterm|sigkill|killed|143|137/i },
26
29
  { key: "timeout", re: /time?d?\s*out|timeout|deadline/i },
27
30
  { key: "context-window", re: /context (window|limit|saturat)|peakcontext|compact/i },
31
+ { key: "reboot-sweep", re: /reboot sweep/i },
28
32
  { key: "not-found", re: /not found|404|missing|no such/i },
29
33
  { key: "auth/credentials", re: /unauthorized|401|403|credential|token|forbidden/i },
30
- { key: "ci/checks-failed", re: /ci|check.?s? fail|lint|tsc|test.?s? fail/i },
34
+ { key: "ci/checks-failed", re: /\bci\b|check.?s? fail|lint|tsc|test.?s? fail/i },
31
35
  { key: "network", re: /network|econn|fetch failed|socket|dns|502|503|504/i },
32
36
  { key: "cancelled", re: /cancel|aborted/i },
33
37
  ];
34
38
 
39
+ function sanitizeUntrustedReasonSample(reason: unknown): string {
40
+ return String(reason)
41
+ .replace(/[\u0000-\u001f\u007f-\u009f]/g, " ")
42
+ .replace(/\s+/g, " ")
43
+ .trim()
44
+ .slice(0, 200);
45
+ }
46
+
47
+ function rowsToObjects(result: any): any[] {
48
+ const payload = result?.data ?? result;
49
+ const columns: string[] = payload?.columns ?? [];
50
+ return (payload?.rows ?? []).map((row: any) =>
51
+ Array.isArray(row) ? Object.fromEntries(columns.map((column, i) => [column, row[i]])) : row,
52
+ );
53
+ }
54
+
35
55
  function reasonCluster(reason: string): string {
36
56
  const r = (reason || "").trim();
37
57
  if (!r) return "(no reason given)";
38
58
  for (const p of REASON_PATTERNS) {
39
59
  if (p.re.test(r)) return p.key;
40
60
  }
41
- return r.toLowerCase().slice(0, 48);
61
+ return "other";
42
62
  }
43
63
 
44
64
  /** Cluster recently failed swarm tasks by reason, agent, or schedule. */
@@ -47,7 +67,7 @@ export default async function taskFailureAudit(args: any, ctx: any) {
47
67
  if (!parsed.success) return { error: "invalid args: " + parsed.error.message };
48
68
  const days = parsed.data.days || 7;
49
69
  const groupBy = parsed.data.groupBy || "reason";
50
- const limit = parsed.data.limit || 500;
70
+ const limit = Math.min(parsed.data.limit ?? MAX_TASK_SCAN_LIMIT, MAX_TASK_SCAN_LIMIT);
51
71
  const publishPage = parsed.data.publishPage !== false;
52
72
 
53
73
  const since = new Date(Date.now() - days * 86400000).toISOString();
@@ -62,17 +82,49 @@ export default async function taskFailureAudit(args: any, ctx: any) {
62
82
  const payload: any = res && res.data ? res.data : res;
63
83
  const tasks: any = payload && Array.isArray(payload.tasks) ? payload.tasks : [];
64
84
 
85
+ const failureReasons = new Map<string, string>();
86
+ if (groupBy === "reason") {
87
+ const tasksNeedingReason = tasks.filter((task: any) => !task.failureReason && task.id);
88
+ const taskIds = tasksNeedingReason.map((task: any) => task.id as string);
89
+ if (taskIds.length > 0) {
90
+ const placeholders = taskIds.map(() => "?").join(", ");
91
+ const reasonResult: any = await ctx.swarm.db_query({
92
+ sql: `SELECT id, failureReason FROM agent_tasks WHERE id IN (${placeholders})`,
93
+ params: taskIds,
94
+ });
95
+ const reasonPayload: any = reasonResult?.data ?? reasonResult;
96
+ if (
97
+ reasonResult?.success === false ||
98
+ reasonPayload?.success === false ||
99
+ reasonPayload?.error
100
+ ) {
101
+ return { error: "failure reason projection failed with status " + reasonResult?.status };
102
+ }
103
+ if (reasonPayload?.truncated) {
104
+ return {
105
+ error: `failure reason projection truncated (${reasonPayload.rows?.length ?? 0} of ${reasonPayload.total ?? "unknown"} rows)`,
106
+ };
107
+ }
108
+ for (const row of rowsToObjects(reasonResult)) {
109
+ if (typeof row?.id === "string" && typeof row.failureReason === "string") {
110
+ failureReasons.set(row.id, row.failureReason);
111
+ }
112
+ }
113
+ }
114
+ }
115
+
65
116
  const groups: any = {};
66
117
  for (const t of tasks) {
67
118
  let key: string;
68
119
  if (groupBy === "agent") key = t.agentId || "(unassigned)";
69
120
  else if (groupBy === "schedule") key = t.scheduleId || "(not scheduled)";
70
- else key = reasonCluster(t.failureReason || "");
71
- if (!groups[key]) groups[key] = { key, count: 0, taskIds: [], sampleReason: "" };
121
+ else key = reasonCluster(failureReasons.get(t.id) || t.failureReason || "");
122
+ if (!groups[key]) groups[key] = { key, count: 0, taskIds: [], untrustedWorkerTextSample: "" };
72
123
  groups[key].count++;
73
124
  if (groups[key].taskIds.length < 5) groups[key].taskIds.push(t.id);
74
- if (!groups[key].sampleReason && t.failureReason) {
75
- groups[key].sampleReason = String(t.failureReason).slice(0, 200);
125
+ const failureReason = failureReasons.get(t.id) || t.failureReason;
126
+ if (!groups[key].untrustedWorkerTextSample && failureReason) {
127
+ groups[key].untrustedWorkerTextSample = sanitizeUntrustedReasonSample(failureReason);
76
128
  }
77
129
  }
78
130
 
@@ -118,7 +170,7 @@ export default async function taskFailureAudit(args: any, ctx: any) {
118
170
  key: group.key,
119
171
  count: group.count,
120
172
  taskIds: group.taskIds,
121
- sampleReason: group.sampleReason,
173
+ untrustedWorkerTextSample: group.untrustedWorkerTextSample,
122
174
  },
123
175
  ],
124
176
  })),
@@ -1,6 +1,7 @@
1
1
  import { normalizeSlackReactionShortcode } from "../slack/reaction-shortcode";
2
2
  import { ProviderNameSchema } from "../types";
3
3
  import { parseTaskToolManifest } from "../utils/task-tool-manifest";
4
+ import { isTierConfigKey, validateTierConfigValue } from "./model-tier-keys";
4
5
 
5
6
  /**
6
7
  * Guards against storing reserved keys in the swarm_config table.
@@ -274,6 +275,7 @@ const VALIDATED_KEYS: Record<string, ConfigValidator> = {
274
275
  return null;
275
276
  },
276
277
  ...booleanValidators([
278
+ "SWARM_DEV_MODE",
277
279
  "MULTI_RUNTIME_ENABLED",
278
280
  "STEERING_ENABLED",
279
281
  "MEMORY_HYBRID_SEARCH",
@@ -304,6 +306,7 @@ const VALIDATED_KEYS: Record<string, ConfigValidator> = {
304
306
  "SWARM_HIDE_CLOUD_PROMO",
305
307
  "DB_QUERY_BOUNDED_ENABLED",
306
308
  "DB_RETENTION_DRY_RUN",
309
+ "MODEL_AUTO_UPGRADE",
307
310
  ]),
308
311
  ...enumValidator("SLACK_MODE", ["socket", "http"]),
309
312
  ...enumValidator("SLACK_THREAD_STEERING", ["off", "lead", "all"]),
@@ -359,6 +362,8 @@ const VALIDATED_KEYS: Record<string, ConfigValidator> = {
359
362
  ),
360
363
  // 0 is meaningful here: "auto-assign nothing this sweep".
361
364
  ...integerValidators(["HEARTBEAT_MAX_AUTO_ASSIGN"], 0),
365
+ // 0 disables the `latest:...@stable` soak window.
366
+ ...integerValidators(["MODEL_LATEST_SOAK_DAYS"], 0),
362
367
  // 0 turns approval auto-cancellation off.
363
368
  ...integerValidators(["APPROVAL_REQUEST_AUTO_CANCELLATION_DAYS"], 0),
364
369
  // Below ~100 tokens the preamble can't fit a useful summary; above 20000
@@ -378,6 +383,7 @@ const VALIDATED_KEYS: Record<string, ConfigValidator> = {
378
383
  };
379
384
 
380
385
  export function validateConfigValue(key: string, value: unknown): string | null {
386
+ if (isTierConfigKey(key)) return validateTierConfigValue(key.toUpperCase(), value);
381
387
  const validator = VALIDATED_KEYS[key.toUpperCase()];
382
388
  return validator ? validator(value) : null;
383
389
  }
package/src/cli.tsx CHANGED
@@ -717,6 +717,16 @@ if (args.showHelp || args.command === "help" || args.command === undefined) {
717
717
  } else if (args.command === "version") {
718
718
  console.log(`${binName} v${pkg.version}`);
719
719
  process.exit(0);
720
+ } else if (args.command === "onboard" && !process.versions.bun) {
721
+ // The published bin uses Node, but the onboarding wizard requires Bun APIs.
722
+ const { spawnSync } = await import("node:child_process");
723
+ const result = spawnSync("bun", process.argv.slice(1), { stdio: "inherit" });
724
+ if (result.error) {
725
+ console.error(
726
+ "Onboarding requires Bun. Install Bun from https://bun.sh, then retry this command.",
727
+ );
728
+ }
729
+ process.exit(result.status ?? 1);
720
730
  } else if (args.command === "docs") {
721
731
  const docsUrl = "https://docs.agent-swarm.dev";
722
732
  console.log(`\n${binName} docs — v${pkg.version}\n`);
@@ -34,6 +34,7 @@ import type { SkillCreateResponse } from "@anthropic-ai/sdk/resources/beta/skill
34
34
  import { toFile } from "@anthropic-ai/sdk/uploads";
35
35
 
36
36
  import { buildSkillContent, type SkillTemplateConfig } from "../be/seed-skills/render";
37
+ import { DEFAULT_MODEL_TIER_MAP } from "../types";
37
38
  import { getApiKey } from "../utils/api-key";
38
39
  import { promptHiddenInput } from "./codex-login.js";
39
40
 
@@ -65,7 +66,8 @@ export type ClaudeManagedSetupResult = {
65
66
 
66
67
  // ─── Constants ───────────────────────────────────────────────────────────────
67
68
 
68
- const DEFAULT_AGENT_MODEL = "claude-sonnet-5";
69
+ /** The claude-managed `regular` tier default — no per-model constant here. */
70
+ const DEFAULT_AGENT_MODEL = DEFAULT_MODEL_TIER_MAP["claude-managed"].regular;
69
71
  const SKILLS_DIR_RELATIVE = "plugin/commands";
70
72
  const SEEDED_SKILLS_DIR_RELATIVE = "templates/skills";
71
73
  const SKILLS_BETA_HEADER = "skills-2025-10-02";