@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
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Harness CLI model support (model-catalog phase 4).
3
+ *
4
+ * A catalog model can exist before the pinned `claude` / `codex` CLI accepts
5
+ * it. Workers report their CLI version on register and record each model's
6
+ * first-run outcome here; claim-time resolution reads it:
7
+ *
8
+ * - `unknown` (no row) and `ok` are allowed at claim.
9
+ * - `unsupported` + an alias/tier resolution → fall back to the newest
10
+ * `ok`/`unknown` model in the same family (modelSource
11
+ * `fallback:cli-unsupported`), with one notice per (harness, CLI, model).
12
+ * - `unsupported` + an explicit task `model` → fail fast at claim.
13
+ *
14
+ * See runbooks/model-catalog.md.
15
+ */
16
+ import {
17
+ claudeCatalogModelId,
18
+ harnessCatalogSection,
19
+ harnessModelIds,
20
+ modelFamilyKey,
21
+ } from "@desplega/model-catalog";
22
+ import { scrubSecrets } from "../utils/secret-scrubber";
23
+ import { getDbClient } from "./db";
24
+
25
+ export type HarnessModelSupportStatus = "ok" | "unsupported" | "unknown";
26
+
27
+ export interface HarnessModelSupportRow {
28
+ harness: string;
29
+ cliVersion: string;
30
+ modelId: string;
31
+ status: HarnessModelSupportStatus;
32
+ checkedAt: number;
33
+ error: string | null;
34
+ }
35
+
36
+ /** Harnesses whose CLI pins the set of accepted model ids. */
37
+ export const CLI_PINNED_HARNESSES = new Set(["claude", "codex"]);
38
+
39
+ export async function getHarnessModelSupport(
40
+ harness: string,
41
+ cliVersion: string,
42
+ modelId: string,
43
+ ): Promise<HarnessModelSupportStatus> {
44
+ const row = await getDbClient().get<{ status: HarnessModelSupportStatus }>(
45
+ "SELECT status FROM harness_model_support WHERE harness = ? AND cliVersion = ? AND modelId = ?",
46
+ [harness, cliVersion, modelId],
47
+ );
48
+ return row?.status ?? "unknown";
49
+ }
50
+
51
+ export async function listHarnessModelSupport(filter?: {
52
+ harness?: string;
53
+ cliVersion?: string;
54
+ }): Promise<HarnessModelSupportRow[]> {
55
+ const where: string[] = [];
56
+ const params: string[] = [];
57
+ if (filter?.harness) {
58
+ where.push("harness = ?");
59
+ params.push(filter.harness);
60
+ }
61
+ if (filter?.cliVersion) {
62
+ where.push("cliVersion = ?");
63
+ params.push(filter.cliVersion);
64
+ }
65
+ return await getDbClient().query<HarnessModelSupportRow>(
66
+ `SELECT harness, cliVersion, modelId, status, checkedAt, error FROM harness_model_support${
67
+ where.length > 0 ? ` WHERE ${where.join(" AND ")}` : ""
68
+ } ORDER BY checkedAt DESC`,
69
+ params,
70
+ );
71
+ }
72
+
73
+ /** Upsert one outcome. An `ok` never downgrades to `unknown`. */
74
+ export async function recordHarnessModelSupport(input: {
75
+ harness: string;
76
+ cliVersion: string;
77
+ modelId: string;
78
+ status: HarnessModelSupportStatus;
79
+ error?: string | null;
80
+ now?: number;
81
+ }): Promise<HarnessModelSupportRow> {
82
+ const checkedAt = input.now ?? Date.now();
83
+ const error = input.error ? scrubSecrets(input.error).slice(0, 2000) : null;
84
+ await getDbClient().run(
85
+ `INSERT INTO harness_model_support (harness, cliVersion, modelId, status, checkedAt, error)
86
+ VALUES (?, ?, ?, ?, ?, ?)
87
+ ON CONFLICT (harness, cliVersion, modelId) DO UPDATE SET
88
+ status = CASE WHEN excluded.status = 'unknown' THEN harness_model_support.status ELSE excluded.status END,
89
+ checkedAt = excluded.checkedAt,
90
+ error = excluded.error`,
91
+ [input.harness, input.cliVersion, input.modelId, input.status, checkedAt, error],
92
+ );
93
+ const row = await getDbClient().get<HarnessModelSupportRow>(
94
+ "SELECT harness, cliVersion, modelId, status, checkedAt, error FROM harness_model_support WHERE harness = ? AND cliVersion = ? AND modelId = ?",
95
+ [input.harness, input.cliVersion, input.modelId],
96
+ );
97
+ if (!row) throw new Error("harness_model_support upsert returned no row");
98
+ return row;
99
+ }
100
+
101
+ /**
102
+ * True when `agentId` was assigned a task that ran `modelId`: the server's claim-time resolution,
103
+ * or the task's own model when nothing was resolved. Binds an `unsupported` report to a model the
104
+ * worker actually ran, so it cannot mark an arbitrary model unsupported for its peers.
105
+ */
106
+ export async function agentRanModel(agentId: string, modelId: string): Promise<boolean> {
107
+ const row = await getDbClient().get<{ id: string }>(
108
+ `SELECT id FROM agent_tasks
109
+ WHERE agentId = ? AND (resolvedModel = ? OR (resolvedModel IS NULL AND model = ?))
110
+ LIMIT 1`,
111
+ [agentId, modelId, modelId],
112
+ );
113
+ return row !== null && row !== undefined;
114
+ }
115
+
116
+ export async function getAgentHarnessCliVersion(agentId: string): Promise<string | null> {
117
+ const row = await getDbClient().get<{ harnessCliVersion: string | null }>(
118
+ "SELECT harnessCliVersion FROM agents WHERE id = ?",
119
+ [agentId],
120
+ );
121
+ return row?.harnessCliVersion ?? null;
122
+ }
123
+
124
+ /** Store the worker's CLI version; no write when unchanged. */
125
+ export async function setAgentHarnessCliVersion(
126
+ agentId: string,
127
+ cliVersion: string | null,
128
+ ): Promise<void> {
129
+ await getDbClient().run(
130
+ "UPDATE agents SET harnessCliVersion = ? WHERE id = ? AND harnessCliVersion IS NOT ?",
131
+ [cliVersion, agentId, cliVersion],
132
+ );
133
+ }
134
+
135
+ export function unsupportedModelMessage(harness: string, cliVersion: string, model: string) {
136
+ return `Model "${model}" is not supported by the ${harness} CLI ${cliVersion} on this worker. A CLI bump is needed before tasks can pin it.`;
137
+ }
138
+
139
+ /**
140
+ * Newest model in the same family as `model` that this harness CLI has not
141
+ * rejected. Null when the family has nothing else. A Claude CLI shortname (the
142
+ * tier default `opus`) counts as the family of the model it stands for, so a
143
+ * rejected `opus` falls back to the newest usable `claude-opus-*`.
144
+ */
145
+ export async function fallbackForUnsupportedModel(
146
+ harness: string,
147
+ cliVersion: string,
148
+ model: string,
149
+ catalogSection: Record<string, { release_date?: string; status?: string; reasoning?: boolean }>,
150
+ ): Promise<string | null> {
151
+ if (!harnessCatalogSection(harness)) return null;
152
+ const family = modelFamilyKey(
153
+ harness === "claude" ? claudeCatalogModelId(model, catalogSection) : model,
154
+ );
155
+ const rejected = new Set(
156
+ (await listHarnessModelSupport({ harness, cliVersion }))
157
+ .filter((r) => r.status === "unsupported")
158
+ .map((r) => r.modelId),
159
+ );
160
+ for (const id of harnessModelIds(harness, catalogSection)) {
161
+ if (id === model || rejected.has(id)) continue;
162
+ if (modelFamilyKey(id) === family) return id;
163
+ }
164
+ return null;
165
+ }
166
+
167
+ const noticed = new Set<string>();
168
+
169
+ /** One console notice per (harness, CLI, model) per process. */
170
+ export function noticeCliUnsupported(
171
+ harness: string,
172
+ cliVersion: string,
173
+ model: string,
174
+ fallback: string,
175
+ ): void {
176
+ const key = `${harness}|${cliVersion}|${model}`;
177
+ if (noticed.has(key)) return;
178
+ noticed.add(key);
179
+ console.warn(
180
+ `[model-tiers] ${model} is unsupported on ${harness} CLI ${cliVersion}; tier/alias tasks fall back to ${fallback} until the CLI is bumped`,
181
+ );
182
+ }
@@ -0,0 +1,67 @@
1
+ import { drainHumanFreeReclassifyQueue, setHumanFreeQueueListener } from "./db/tasks/human-free";
2
+
3
+ /**
4
+ * Background drain for `human_free_reclassify_queue`: the part of a large
5
+ * subtree's human-free reclassification that did not fit in the request that
6
+ * changed it. It runs one batch per pass and yields between passes, so no
7
+ * pass holds the write lock or the event loop for longer than one batch.
8
+ *
9
+ * It wakes when a mutation queues work, and once at boot and on a slow timer to
10
+ * pick up rows that survived a restart or a failed pass.
11
+ */
12
+
13
+ const YIELD_MS = 25;
14
+ const IDLE_INTERVAL_MS = 60_000;
15
+
16
+ let running = false;
17
+ let idleTimer: ReturnType<typeof setTimeout> | null = null;
18
+ let passTimer: ReturnType<typeof setTimeout> | null = null;
19
+
20
+ function scheduleIdle(): void {
21
+ if (!running) return;
22
+ if (idleTimer) clearTimeout(idleTimer);
23
+ idleTimer = setTimeout(() => schedulePass(0), IDLE_INTERVAL_MS);
24
+ idleTimer.unref?.();
25
+ }
26
+
27
+ function schedulePass(delayMs: number): void {
28
+ if (!running || passTimer) return;
29
+ passTimer = setTimeout(() => {
30
+ passTimer = null;
31
+ void runPass();
32
+ }, delayMs);
33
+ passTimer.unref?.();
34
+ }
35
+
36
+ async function runPass(): Promise<void> {
37
+ if (!running) return;
38
+ try {
39
+ const { batches, remaining } = await drainHumanFreeReclassifyQueue({ maxBatches: 1 });
40
+ if (batches > 0 && remaining > 0) {
41
+ schedulePass(YIELD_MS);
42
+ return;
43
+ }
44
+ } catch (err) {
45
+ console.error(
46
+ "[human-free-drain] batch failed, retrying later:",
47
+ err instanceof Error ? err.message : String(err),
48
+ );
49
+ }
50
+ scheduleIdle();
51
+ }
52
+
53
+ export function startHumanFreeDrain(): void {
54
+ if (running) return;
55
+ running = true;
56
+ setHumanFreeQueueListener(() => schedulePass(0));
57
+ schedulePass(0);
58
+ }
59
+
60
+ export function stopHumanFreeDrain(): void {
61
+ running = false;
62
+ setHumanFreeQueueListener(undefined);
63
+ if (idleTimer) clearTimeout(idleTimer);
64
+ if (passTimer) clearTimeout(passTimer);
65
+ idleTimer = null;
66
+ passTimer = null;
67
+ }
@@ -0,0 +1,63 @@
1
+ -- Persist the structurally-human-free classification on agent_tasks.
2
+ --
3
+ -- The usage reports (GET /api/session-costs/summary, /api/attribution/by-person)
4
+ -- rebuilt a recursive CTE over EVERY task on each call to decide which sessions
5
+ -- belong to the swarm maintaining itself. That CTE is seeded from all tasks and
6
+ -- date filters do not prune it, so a 30-day summary paid for the whole history.
7
+ -- The classification is fixed when a task is created, so it is stored once
8
+ -- (src/be/db/tasks/human-free.ts owns the rule) and the reports read a column.
9
+ --
10
+ -- Rule (mirrors HUMAN_FREE_BASE_SQL): heartbeat / boot-triage tasks, scheduled
11
+ -- runs with no human creator (including workflow roots launched by one), and
12
+ -- `source='system'` follow-ups whose parent has no requester. It propagates to
13
+ -- children that stay unattributed (no requester, or one copied from the parent).
14
+ ALTER TABLE agent_tasks
15
+ ADD COLUMN isHumanFree INTEGER NOT NULL DEFAULT 0
16
+ CHECK (isHumanFree IN (0, 1));
17
+
18
+ WITH RECURSIVE human_free_tasks(id) AS (
19
+ SELECT task.id
20
+ FROM agent_tasks task
21
+ LEFT JOIN agent_tasks parent ON parent.id = task.parentTaskId
22
+ WHERE COALESCE(task.taskType, '') IN ('heartbeat', 'heartbeat-checklist', 'boot-triage')
23
+ OR COALESCE(task.tags, '[]') LIKE '%"heartbeat"%'
24
+ OR (COALESCE(task.source, '') = 'schedule' AND task.requestedByUserId IS NULL)
25
+ OR (
26
+ task.parentTaskId IS NULL
27
+ AND COALESCE(task.source, '') = 'workflow'
28
+ AND task.requestedByUserId IS NULL
29
+ AND EXISTS (
30
+ SELECT 1
31
+ FROM workflow_runs run
32
+ WHERE run.id = task.workflowRunId
33
+ AND run.triggerType = 'schedule'
34
+ AND run.created_by IS NULL
35
+ )
36
+ )
37
+ OR (
38
+ COALESCE(task.source, '') = 'system'
39
+ AND parent.id IS NOT NULL
40
+ AND parent.requestedByUserId IS NULL
41
+ )
42
+
43
+ UNION
44
+
45
+ SELECT child.id
46
+ FROM agent_tasks child
47
+ JOIN human_free_tasks parent ON child.parentTaskId = parent.id
48
+ WHERE child.requestedByUserId IS NULL
49
+ OR child.requestedByUserIdInherited = 1
50
+ )
51
+ UPDATE agent_tasks
52
+ SET isHumanFree = 1
53
+ WHERE id IN (SELECT id FROM human_free_tasks);
54
+
55
+ -- Migration 164's covering index carried the columns the recursive CTE read
56
+ -- (parentTaskId, taskType, source, tags, workflowRunId). Nothing reads them
57
+ -- through it now. The session join needs only the requester, the flag and the
58
+ -- credential, so keep the index to those and let it stay index-only.
59
+ DROP INDEX IF EXISTS idx_agent_tasks_usage_cover;
60
+ CREATE INDEX idx_agent_tasks_usage_cover
61
+ ON agent_tasks(
62
+ id, requestedByUserId, isHumanFree, credentialKeyType, credentialKeySuffix
63
+ );
@@ -0,0 +1,25 @@
1
+ -- Continuation queue for the human-free reclassification (migration 182).
2
+ --
3
+ -- Reclassifying a subtree used to walk every descendant inside the request that
4
+ -- changed a classifying input, so a very large task tree could hold the write
5
+ -- lock and the event loop for the whole walk. The walk now handles a bounded
6
+ -- batch inline and parks the rest here; a background drain
7
+ -- (src/be/human-free-drain.ts) works the queue off in bounded batches, each in
8
+ -- its own transaction. Rows are written in the same transaction as the mutation
9
+ -- that needs them, so a crash can neither lose nor half-apply the remainder.
10
+ --
11
+ -- scope = 'self' re-evaluate `taskId` and everything below it
12
+ -- scope = 'children' re-evaluate the children of `taskId` whose rowid is
13
+ -- above `afterRowid`, and everything below them
14
+ --
15
+ -- One row per (taskId, scope): a repeat enqueue restarts that row from the top.
16
+ -- `hops` counts how many batches deep a continuation is, so a parent cycle that
17
+ -- the product never writes cannot re-queue itself forever.
18
+ CREATE TABLE human_free_reclassify_queue (
19
+ seq INTEGER PRIMARY KEY AUTOINCREMENT,
20
+ taskId TEXT NOT NULL,
21
+ scope TEXT NOT NULL CHECK (scope IN ('self', 'children')),
22
+ afterRowid INTEGER NOT NULL DEFAULT 0,
23
+ hops INTEGER NOT NULL DEFAULT 0,
24
+ UNIQUE (taskId, scope)
25
+ );
@@ -0,0 +1,120 @@
1
+ -- Model catalog, claim-time model resolution and harness CLI model support.
2
+ --
3
+ -- One migration for the whole model-catalog stack (persistent catalog, tier
4
+ -- resolution, harness support). Three parts, in dependency order.
5
+
6
+ -- ---------------------------------------------------------------------------
7
+ -- Part 1: persistent model catalog (models.dev projection) + operator overlay.
8
+ --
9
+ -- model_catalog: one row per (provider, modelId) for the picker-reachable
10
+ -- providers (CATALOG_PROVIDER_IDS). Rewritten by the catalog refresh; system
11
+ -- written, so it carries no user-attribution columns (.non-audit-tables).
12
+ -- model_catalog_overlay: hand-verified facts for models models.dev does not
13
+ -- list yet (or lists wrong). Overlay wins per non-null field. Rows with
14
+ -- expiresWhenUpstreamMatches=1 are deleted once upstream agrees.
15
+ -- model_catalog_meta: single-row fetch state (ETag / Last-Modified / times).
16
+ --
17
+ -- JSON columns: reasoningOptions = [{type, values?}], pricing =
18
+ -- {input?, output?, cache_read?, cache_write?} (USD per million tokens).
19
+ -- ---------------------------------------------------------------------------
20
+
21
+ CREATE TABLE IF NOT EXISTS model_catalog (
22
+ provider TEXT NOT NULL,
23
+ modelId TEXT NOT NULL,
24
+ name TEXT,
25
+ family TEXT,
26
+ releaseDate TEXT,
27
+ contextWindow INTEGER,
28
+ maxOutput INTEGER,
29
+ reasoning INTEGER,
30
+ reasoningOptions TEXT,
31
+ pricing TEXT,
32
+ status TEXT,
33
+ providerName TEXT,
34
+ checkedAt INTEGER NOT NULL,
35
+ PRIMARY KEY (provider, modelId)
36
+ );
37
+
38
+ CREATE TABLE IF NOT EXISTS model_catalog_overlay (
39
+ provider TEXT NOT NULL,
40
+ modelId TEXT NOT NULL,
41
+ name TEXT,
42
+ family TEXT,
43
+ releaseDate TEXT,
44
+ contextWindow INTEGER,
45
+ maxOutput INTEGER,
46
+ reasoning INTEGER,
47
+ reasoningOptions TEXT,
48
+ pricing TEXT,
49
+ status TEXT,
50
+ reason TEXT NOT NULL,
51
+ verifiedBy TEXT,
52
+ expiresWhenUpstreamMatches INTEGER NOT NULL DEFAULT 1,
53
+ createdAt INTEGER NOT NULL DEFAULT (CAST(unixepoch('subsec') * 1000 AS INTEGER)),
54
+ updatedAt INTEGER NOT NULL DEFAULT (CAST(unixepoch('subsec') * 1000 AS INTEGER)),
55
+ created_by TEXT REFERENCES users(id),
56
+ updated_by TEXT REFERENCES users(id),
57
+ PRIMARY KEY (provider, modelId)
58
+ );
59
+
60
+ CREATE TABLE IF NOT EXISTS model_catalog_meta (
61
+ id INTEGER PRIMARY KEY CHECK (id = 1),
62
+ etag TEXT,
63
+ lastModified TEXT,
64
+ lastFetchAt INTEGER,
65
+ lastCheckedAt INTEGER
66
+ );
67
+
68
+ -- ---------------------------------------------------------------------------
69
+ -- Part 2: claim-time model resolution.
70
+ --
71
+ -- agent_tasks.resolvedModel / modelSource / modelAlias: what the server
72
+ -- resolved when the task was claimed. modelSource is one of
73
+ -- model | worker-env | tier-config | tier-default | fallback:cli-unsupported.
74
+ -- modelAlias is the `latest:` alias, when any.
75
+ -- agents.modelTierOverrides: the worker's parsed MODEL_TIER_* env overrides,
76
+ -- JSON {provider: {tier: value}}. Values only, never secrets. Sent on
77
+ -- register and on every poll.
78
+ -- model_alias_resolutions: one row each time a `latest:` alias resolves to a
79
+ -- different concrete model than its previous resolution. System written.
80
+ -- ---------------------------------------------------------------------------
81
+
82
+ ALTER TABLE agent_tasks ADD COLUMN resolvedModel TEXT;
83
+ ALTER TABLE agent_tasks ADD COLUMN modelSource TEXT;
84
+ ALTER TABLE agent_tasks ADD COLUMN modelAlias TEXT;
85
+
86
+ ALTER TABLE agents ADD COLUMN modelTierOverrides TEXT;
87
+
88
+ CREATE TABLE IF NOT EXISTS model_alias_resolutions (
89
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
90
+ alias TEXT NOT NULL,
91
+ previousModel TEXT,
92
+ newModel TEXT NOT NULL,
93
+ changedAt INTEGER NOT NULL
94
+ );
95
+
96
+ CREATE INDEX IF NOT EXISTS idx_model_alias_resolutions_alias
97
+ ON model_alias_resolutions (alias, id);
98
+
99
+ -- ---------------------------------------------------------------------------
100
+ -- Part 3: harness CLI model support.
101
+ --
102
+ -- harness_model_support: whether a catalog model runs on a given harness CLI
103
+ -- version. Workers write it after a model's first run on their CLI: `ok` on
104
+ -- success, `unsupported` when the CLI rejects the model id. No row means
105
+ -- `unknown`, which is allowed at claim. System written.
106
+ -- agents.harnessCliVersion: the worker's `claude --version` / `codex --version`,
107
+ -- reported on register. Keys claim-time support lookups.
108
+ -- ---------------------------------------------------------------------------
109
+
110
+ CREATE TABLE IF NOT EXISTS harness_model_support (
111
+ harness TEXT NOT NULL,
112
+ cliVersion TEXT NOT NULL,
113
+ modelId TEXT NOT NULL,
114
+ status TEXT NOT NULL CHECK (status IN ('ok', 'unsupported', 'unknown')),
115
+ checkedAt INTEGER NOT NULL,
116
+ error TEXT,
117
+ PRIMARY KEY (harness, cliVersion, modelId)
118
+ );
119
+
120
+ ALTER TABLE agents ADD COLUMN harnessCliVersion TEXT;