@desplega.ai/agent-swarm 1.158.0 → 1.160.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 (378) hide show
  1. package/README.md +1 -1
  2. package/dist/{acp-adapter-xaq4n6pc.js → acp-adapter-2mjwcsfh.js} +124 -27
  3. package/dist/{actions-p9hz5yat.js → actions-e43rxskr.js} +35 -30
  4. package/dist/{anthropic-messages-51pz26qe.js → anthropic-messages-5qx80j9k.js} +106 -16
  5. package/dist/{app-g26wdcz6.js → app-mc7xfagn.js} +12 -10
  6. package/dist/{assistant-wydfqkb3.js → assistant-sedkzv3b.js} +42 -31
  7. package/dist/{azure-openai-responses-8gksqtmz.js → azure-openai-responses-nfb21z9a.js} +13 -14
  8. package/dist/{boot-reembed-se8zmdcp.js → boot-reembed-cqz9swn5.js} +13 -12
  9. package/dist/{boot-reembed-g5ckg9yb.js → boot-reembed-m3656j2n.js} +14 -13
  10. package/dist/{boot-scrub-logs-wfxqan61.js → boot-scrub-logs-xbxf5d7w.js} +11 -9
  11. package/dist/boot-seeds-gsetbs5y.js +28 -0
  12. package/dist/{claude-adapter-tv2x4f36.js → claude-adapter-dc6289mc.js} +28 -23
  13. package/dist/{claude-managed-adapter-41b2s7fd.js → claude-managed-adapter-naaw51me.js} +4 -3
  14. package/dist/{claude-managed-setup-60g8aazx.js → claude-managed-setup-eacvchx0.js} +12 -3
  15. package/dist/claude-sdk-session-bqp2xvym.js +29722 -0
  16. package/dist/{cli-987hg0aa.js → cli-0jwe9034.js} +2 -2
  17. package/dist/{cli-vj6h9kc2.js → cli-0m7wct0d.js} +59 -1
  18. package/dist/{cli-c07r9a2m.js → cli-0q3avq3s.js} +12 -11
  19. package/dist/{cli-19jenss3.js → cli-0wgxmwf6.js} +31 -10
  20. package/dist/cli-0x01kwrd.js +491 -0
  21. package/dist/cli-0xqde53b.js +176 -0
  22. package/dist/cli-10nhj6xq.js +8355 -0
  23. package/dist/{cli-tt9k8r35.js → cli-12tszpk9.js} +479 -910
  24. package/dist/{cli-5b6b62tz.js → cli-13hw4z91.js} +1 -1
  25. package/dist/{cli-cqt19wg6.js → cli-1a2j50m0.js} +11 -2
  26. package/dist/{cli-xberxa1r.js → cli-25fcq4b4.js} +2 -2
  27. package/dist/{cli-npssxgaz.js → cli-2pdaysab.js} +52 -2
  28. package/dist/cli-3qhyh31j.js +166 -0
  29. package/dist/cli-447dt1h3.js +3828 -0
  30. package/dist/cli-4derpgyk.js +115 -0
  31. package/dist/{cli-00w2c66p.js → cli-523679eh.js} +13 -2
  32. package/dist/{cli-y95wp3tg.js → cli-58e8yr0d.js} +1 -1
  33. package/dist/{cli-24k806sc.js → cli-5nbmpy8w.js} +3 -3
  34. package/dist/{cli-53sz8gkz.js → cli-6cs6wzxt.js} +1 -1
  35. package/dist/cli-741yb0xx.js +1027 -0
  36. package/dist/cli-74h9hscv.js +69 -0
  37. package/dist/cli-7cxw8b60.js +14910 -0
  38. package/dist/{cli-xhaeg2nc.js → cli-7e398j1h.js} +1 -1
  39. package/dist/cli-7kyk9jr3.js +22 -0
  40. package/dist/cli-89s4ngw3.js +183 -0
  41. package/dist/{cli-9b13ggd1.js → cli-89v6kyq7.js} +1 -0
  42. package/dist/{cli-ftfvggzx.js → cli-9hsgwz4w.js} +1045 -581
  43. package/dist/{cli-rqz8qbw7.js → cli-a8dvhzrt.js} +59 -20
  44. package/dist/{cli-j57qkrpq.js → cli-azhgzetg.js} +395 -56
  45. package/dist/cli-br0qdrnp.js +2150 -0
  46. package/dist/{cli-rj19hc34.js → cli-btfv8y88.js} +1 -1
  47. package/dist/{cli-8qmme6vd.js → cli-c8a36vcc.js} +2 -2
  48. package/dist/cli-cpvsaqsw.js +333 -0
  49. package/dist/cli-d5czme6m.js +17805 -0
  50. package/dist/{cli-h38x0qhg.js → cli-et8m9xsv.js} +4 -3
  51. package/dist/{cli-x24pshtq.js → cli-evvtk35y.js} +6 -6
  52. package/dist/{cli-7s3syk22.js → cli-f8gy6r2r.js} +5 -5
  53. package/dist/{cli-zt1227k1.js → cli-fkqhbg8g.js} +5 -3
  54. package/dist/{cli-9httdpyq.js → cli-g2whhdrf.js} +1 -1
  55. package/dist/cli-gt4hxb8p.js +99 -0
  56. package/dist/{cli-s8eet254.js → cli-jajnsm8q.js} +37184 -53838
  57. package/dist/{cli-7gtvvpbh.js → cli-jayjpjmb.js} +21 -12
  58. package/dist/{cli-68w09sjw.js → cli-jyk0ynw2.js} +27 -89
  59. package/dist/{cli-aehm1rbq.js → cli-jz4w45qt.js} +1152 -61
  60. package/dist/cli-kmcpfwn2.js +333856 -0
  61. package/dist/{cli-4198qc9v.js → cli-ktmyrtpn.js} +19 -3
  62. package/dist/{cli-j55n64b6.js → cli-mfe5btp9.js} +505 -189
  63. package/dist/{cli-q67y4wzm.js → cli-phsaj4xq.js} +24 -2
  64. package/dist/{cli-1hkrch3k.js → cli-pm1rst0h.js} +46 -7
  65. package/dist/{cli-pw9rj5t9.js → cli-pqetb83f.js} +1 -176
  66. package/dist/{cli-ggm92wcn.js → cli-ptr8h7kp.js} +20 -1
  67. package/dist/{cli-s8s499xy.js → cli-qbksc292.js} +3 -2
  68. package/dist/{cli-47m6dkd3.js → cli-qydf22qq.js} +597 -346
  69. package/dist/{cli-3gmcwfyx.js → cli-r1mxd1xh.js} +13 -6
  70. package/dist/cli-rb9m6rpe.js +4980 -0
  71. package/dist/cli-rykfmgwg.js +102 -0
  72. package/dist/{cli-5d10vd2m.js → cli-shkxm1vn.js} +6 -5
  73. package/dist/{cli-eetrvv91.js → cli-t3p77qk1.js} +1 -1
  74. package/dist/cli-tfky2838.js +756 -0
  75. package/dist/{cli-g8djp6qe.js → cli-tmqt7dpj.js} +40 -0
  76. package/dist/{cli-fgqxwrpv.js → cli-tpq83z89.js} +1631 -127
  77. package/dist/cli-trc24wk2.js +143 -0
  78. package/dist/{cli-aky8tctm.js → cli-tzx2ke1v.js} +3 -3
  79. package/dist/cli-v7fn5gms.js +1425 -0
  80. package/dist/{cli-wscdvcqz.js → cli-vctdqs5h.js} +1 -1
  81. package/dist/{cli-cw9zck8j.js → cli-xjcywfsf.js} +8 -4
  82. package/dist/{cli-k0g1sjzm.js → cli-ybgv4kw2.js} +18 -11
  83. package/dist/{cli-rjehvrts.js → cli-ydmcgve1.js} +8 -8
  84. package/dist/{cli-vhjwh6cz.js → cli-ygwm91nt.js} +1 -1
  85. package/dist/{cli-mcnhgnn1.js → cli-ykk5jaax.js} +184 -26
  86. package/dist/{cli-de1396j2.js → cli-ywybs5as.js} +7 -1
  87. package/dist/cli-zpep9q5e.js +491 -0
  88. package/dist/cli.js +41 -32
  89. package/dist/cloudflare-workers-ai-system-one-k6fbxw9d.js +46 -0
  90. package/dist/{codex-adapter-cnm8wk11.js → codex-adapter-nmxcatww.js} +26 -17
  91. package/dist/{codex-hook-9hptmdhj.js → codex-hook-20me6byw.js} +2 -2
  92. package/dist/{codex-login-x3jk4etn.js → codex-login-w6fz4xbz.js} +50 -5
  93. package/dist/{codex-session-runner-gfg547zj.js → codex-session-runner-90aa80ev.js} +24 -17
  94. package/dist/{commands-e3bn0p75.js → commands-8wcq1kn7.js} +11 -9
  95. package/dist/{db-03v9kzr2.js → db-j40kc4se.js} +25 -11
  96. package/dist/{devin-adapter-8vgkwh29.js → devin-adapter-dpsv6hd9.js} +1 -1
  97. package/dist/{dispatcher-nrmg8yht.js → dispatcher-4zqy768d.js} +34 -29
  98. package/dist/{dsh-adapter-k03nfqa6.js → dsh-adapter-mpnzgztj.js} +3 -3
  99. package/dist/{e2b-93x1k0j5.js → e2b-ez07znam.js} +4 -3
  100. package/dist/execute-6e7qeyk4.js +1017 -0
  101. package/dist/{google-generative-ai-bxmv50nc.js → google-generative-ai-k6n5nbpp.js} +6 -5
  102. package/dist/{google-vertex-809af514.js → google-vertex-44xxtcvp.js} +6 -5
  103. package/dist/{handlers-aqcedbyb.js → handlers-kjdce1w5.js} +37 -32
  104. package/dist/{hook-ybvmrmhq.js → hook-czx13n12.js} +19 -15
  105. package/dist/{hook-7vkskkre.js → hook-r2t4bqfg.js} +18 -14
  106. package/dist/{http-2eqa8t24.js → http-3bbb19be.js} +900 -145
  107. package/dist/{index-f6cryxwe.js → index-4a5njnm1.js} +2 -59
  108. package/dist/{index-x71bvq0p.js → index-668me3f0.js} +35 -30
  109. package/dist/{index-c6zsv1bd.js → index-7aj368tw.js} +13 -12
  110. package/dist/{index-1yks3qcr.js → index-fg0dsp6c.js} +34 -29
  111. package/dist/{index-chgdkq36.js → index-kkv84jq9.js} +34 -29
  112. package/dist/{index-a3vn96n8.js → index-zg7vp46x.js} +198 -54
  113. package/dist/{keepalive-vtkf5n99.js → keepalive-yra8dy57.js} +13 -11
  114. package/dist/{lead-h3sp357x.js → lead-va0pad2c.js} +54 -49
  115. package/dist/llama-cpp-classify-tgj35k8d.js +335 -0
  116. package/dist/{maintenance-jzeqtk3q.js → maintenance-hqewtmyg.js} +14 -13
  117. package/dist/{mistral-conversations-zx0kn554.js → mistral-conversations-pc3ntnxt.js} +14 -17
  118. package/dist/{oauth-refresh-sweep-qp3dzmdp.js → oauth-refresh-sweep-7rdt1m9n.js} +12 -10
  119. package/dist/{onboard-tmsp0qbz.js → onboard-j6ms6d0x.js} +6 -5
  120. package/dist/{openai-codex-responses-4vjqzvjp.js → openai-codex-responses-bbancrr0.js} +21 -8
  121. package/dist/{openai-completions-9hjrqbw8.js → openai-completions-jpaj5g2b.js} +9 -12
  122. package/dist/{openai-responses-7b1pn0xt.js → openai-responses-200k1wx4.js} +23 -18
  123. package/dist/{opencode-adapter-x6x2da1z.js → opencode-adapter-89rabkw2.js} +8 -7
  124. package/dist/{openrouter-images-9f9ek4rw.js → openrouter-images-8yx3487g.js} +5 -7
  125. package/dist/{otel-impl-9mz73q65.js → otel-impl-a792rjz0.js} +1 -1
  126. package/dist/{pi-messages-txpjj39q.js → pi-messages-vxdt8v7j.js} +2 -1
  127. package/dist/{pi-mono-adapter-m6dvv0dz.js → pi-mono-adapter-g7qzvr3v.js} +295 -70
  128. package/dist/pricing-refresh-wx0tp7cb.js +39 -0
  129. package/dist/quickjs-r1pq2107.wasm +0 -0
  130. package/dist/{rbac-roles-c9wqz83x.js → rbac-roles-8tzawacv.js} +12 -10
  131. package/dist/{render-v2-gryrh9h8.js → render-v2-mm7zswb8.js} +12 -10
  132. package/dist/runtime-zyssgse0.js +24 -0
  133. package/dist/{setup-q9720bt6.js → setup-8y70ys6t.js} +6 -5
  134. package/dist/typesafe-system-one-2ef30pec.js +27 -0
  135. package/dist/{virtual-modules-3htpgn8x.js → virtual-modules-6nmk7h3n.js} +22 -13
  136. package/dist/{work-objects-b76x7deb.js → work-objects-fg6xkb0n.js} +37 -32
  137. package/dist/{worker-6vrxbv41.js → worker-wmwhxsph.js} +54 -49
  138. package/openapi.json +1314 -91
  139. package/package.json +12 -11
  140. package/src/be/db/agents.ts +20 -0
  141. package/src/be/db/api-keys.ts +70 -0
  142. package/src/be/db/tasks/human-free.ts +442 -0
  143. package/src/be/db/tasks/read.ts +56 -1
  144. package/src/be/db/tasks/write.ts +107 -11
  145. package/src/be/db.ts +274 -188
  146. package/src/be/harness-model-support.ts +182 -0
  147. package/src/be/human-free-drain.ts +67 -0
  148. package/src/be/memory/providers/sqlite-store.ts +51 -15
  149. package/src/be/migrations/182_task_human_free_flag.sql +63 -0
  150. package/src/be/migrations/183_human_free_reclassify_queue.sql +25 -0
  151. package/src/be/migrations/184_model_catalog.sql +120 -0
  152. package/src/be/migrations/185_api_key_auth_failures.sql +6 -0
  153. package/src/be/migrations/186_api_key_seat_mismatch.sql +8 -0
  154. package/src/be/model-catalog-store.ts +644 -0
  155. package/src/be/model-tier-keys.ts +46 -0
  156. package/src/be/model-tier-resolution.ts +534 -0
  157. package/src/be/model-validation.ts +188 -0
  158. package/src/be/models-catalog.ts +61 -7
  159. package/src/be/modelsdev-cache.json +82 -0
  160. package/src/be/modelsdev-cache.ts +2 -0
  161. package/src/be/pricing-refresh.ts +154 -12
  162. package/src/be/scripts/typecheck.ts +15 -0
  163. package/src/be/seed/runner.ts +18 -12
  164. package/src/be/seed/types.ts +1 -0
  165. package/src/be/seed-scripts/catalog/task-failure-audit.ts +61 -9
  166. package/src/be/swarm-config-guard.ts +8 -0
  167. package/src/cli.tsx +10 -0
  168. package/src/commands/claude-managed-setup.ts +3 -1
  169. package/src/commands/codex-login.ts +67 -0
  170. package/src/commands/credential-outcome-report.ts +191 -0
  171. package/src/commands/rate-limit-outcome.ts +34 -4
  172. package/src/commands/runner.ts +357 -186
  173. package/src/commands/seat-mismatch-report.ts +46 -0
  174. package/src/extensions/apply-task-create.ts +27 -1
  175. package/src/extensions/contract-types.generated.ts +1 -1
  176. package/src/heartbeat/heartbeat.ts +79 -17
  177. package/src/heartbeat/templates.ts +2 -2
  178. package/src/http/agents.ts +62 -1
  179. package/src/http/api-keys.ts +140 -2
  180. package/src/http/codex-oauth-keep-warm.ts +3 -5
  181. package/src/http/index.ts +8 -0
  182. package/src/http/memory.ts +12 -5
  183. package/src/http/models-catalog.ts +357 -8
  184. package/src/http/poll.ts +54 -3
  185. package/src/http/schedules.ts +51 -6
  186. package/src/http/script-runs.ts +12 -0
  187. package/src/http/session-data.ts +2 -5
  188. package/src/http/stats.ts +6 -8
  189. package/src/http/status.ts +15 -1
  190. package/src/http/tasks.ts +90 -7
  191. package/src/http/usage-cache.ts +56 -15
  192. package/src/http/workflows.ts +46 -8
  193. package/src/mcp-client/http-client.ts +5 -0
  194. package/src/memory/recall-query.ts +74 -0
  195. package/src/prompts/base-prompt.ts +15 -4
  196. package/src/prompts/session-templates.ts +23 -1
  197. package/src/providers/acp-adapter.ts +1 -0
  198. package/src/providers/claude-adapter.ts +14 -5
  199. package/src/providers/claude-managed-adapter.ts +1 -0
  200. package/src/providers/claude-managed-models.ts +47 -161
  201. package/src/providers/claude-sdk-session.ts +3 -1
  202. package/src/providers/codex-adapter.ts +14 -14
  203. package/src/providers/codex-models.ts +65 -198
  204. package/src/providers/devin-adapter.ts +1 -0
  205. package/src/providers/dsh-adapter.ts +2 -1
  206. package/src/providers/harness-version.ts +83 -7
  207. package/src/providers/opencode-adapter.ts +53 -1
  208. package/src/providers/pi-binary-root/src/extensions/codemode/worker.ts +11 -0
  209. package/src/providers/pi-codemode-runtime.ts +17 -0
  210. package/src/providers/pi-mono-adapter.ts +361 -83
  211. package/src/providers/pi-mono-extension.ts +16 -1
  212. package/src/providers/reasoning-effort.ts +44 -140
  213. package/src/providers/types.ts +8 -0
  214. package/src/rbac/legacy-policy.ts +43 -0
  215. package/src/rbac/permissions.ts +15 -0
  216. package/src/scripts-runtime/sdk-allowlist.ts +4 -0
  217. package/src/scripts-runtime/types/stdlib.d.ts +15 -0
  218. package/src/scripts-runtime/types/swarm-extension.d.ts +3 -0
  219. package/src/scripts-runtime/types/swarm-sdk.d.ts +15 -0
  220. package/src/server.ts +6 -0
  221. package/src/slack/assistant.ts +13 -0
  222. package/src/slack/message-text.ts +56 -2
  223. package/src/slack/render-v2.ts +87 -37
  224. package/src/slack/session-status.ts +541 -0
  225. package/src/slack/thread-buffer.ts +10 -2
  226. package/src/tasks/sibling-awareness.ts +3 -2
  227. package/src/telemetry.ts +5 -4
  228. package/src/templates.d.ts +6 -0
  229. package/src/tests/agents-list-model-display.test.ts +4 -6
  230. package/src/tests/api-key-auth-failure-route.test.ts +142 -0
  231. package/src/tests/api-key-tracking.test.ts +368 -1
  232. package/src/tests/base-prompt.test.ts +43 -2
  233. package/src/tests/claude-adapter.test.ts +68 -0
  234. package/src/tests/claude-managed-adapter.test.ts +12 -18
  235. package/src/tests/codex-adapter.test.ts +2 -1
  236. package/src/tests/codex-auth-failure.test.ts +52 -0
  237. package/src/tests/codex-gpt6-release.test.ts +66 -5
  238. package/src/tests/codex-login.test.ts +116 -0
  239. package/src/tests/context-window.test.ts +7 -5
  240. package/src/tests/credential-exhaustion-policy.test.ts +118 -3
  241. package/src/tests/credential-outcome-report.test.ts +171 -0
  242. package/src/tests/credential-status-api.test.ts +103 -2
  243. package/src/tests/db-extraction-contract.test.ts +1 -0
  244. package/src/tests/db-task-read-contract.test.ts +10 -1
  245. package/src/tests/db-task-write-contract.test.ts +535 -2
  246. package/src/tests/docs-index-footprint.test.js +3 -3
  247. package/src/tests/dokploy-deploy.test.ts +315 -0
  248. package/src/tests/dsh-adapter.test.ts +1 -1
  249. package/src/tests/explicit-model-validation.test.ts +583 -0
  250. package/src/tests/extensions-pre-task.test.ts +73 -0
  251. package/src/tests/fetch-resolved-env.test.ts +46 -0
  252. package/src/tests/hang-watchdog-worker.ts +47 -0
  253. package/src/tests/hang-watchdog.test.ts +45 -0
  254. package/src/tests/hang-watchdog.ts +30 -0
  255. package/src/tests/harness-model-guard.test.ts +123 -0
  256. package/src/tests/harness-model-support.test.ts +374 -0
  257. package/src/tests/harness-version.test.ts +70 -3
  258. package/src/tests/heartbeat-checklist-config.test.ts +71 -0
  259. package/src/tests/heartbeat-checklist.test.ts +4 -2
  260. package/src/tests/heartbeat-orphan-resume.test.ts +48 -0
  261. package/src/tests/http-task-write-ownership.test.ts +172 -0
  262. package/src/tests/human-free-flag.test.ts +279 -0
  263. package/src/tests/human-free-oracle.ts +57 -0
  264. package/src/tests/human-free-reclassify.test.ts +669 -0
  265. package/src/tests/memory-fts-populate.test.ts +139 -0
  266. package/src/tests/memory-http-recall-gating.test.ts +125 -1
  267. package/src/tests/migration-046-budgets.test.ts +3 -3
  268. package/src/tests/model-catalog-migration.test.ts +134 -0
  269. package/src/tests/model-catalog-table.test.ts +249 -0
  270. package/src/tests/model-catalog-write-access.test.ts +387 -0
  271. package/src/tests/model-rate-limit-windows.test.ts +19 -0
  272. package/src/tests/model-tier-resolution.test.ts +611 -0
  273. package/src/tests/models-catalog.test.ts +1 -1
  274. package/src/tests/multi-runtime-enabled-stats.test.ts +65 -1
  275. package/src/tests/opencode-adapter.test.ts +157 -2
  276. package/src/tests/package-publish.test.ts +137 -36
  277. package/src/tests/pi-codemode.test.ts +246 -0
  278. package/src/tests/pi-mono-adapter.test.ts +3 -0
  279. package/src/tests/pi-native-mcp.test.ts +193 -0
  280. package/src/tests/pi-tool-exposure.test.ts +374 -0
  281. package/src/tests/preload.ts +3 -0
  282. package/src/tests/pricing-routes.test.ts +2 -2
  283. package/src/tests/prompt-template-session.test.ts +5 -3
  284. package/src/tests/rate-limit-event.test.ts +65 -0
  285. package/src/tests/rate-limit-outcome.test.ts +90 -0
  286. package/src/tests/rbac-engine.test.ts +77 -3
  287. package/src/tests/reasoning-effort.test.ts +109 -0
  288. package/src/tests/refresh-modelsdev-carryforward.test.ts +5 -5
  289. package/src/tests/runner-key-completion-outcome.test.ts +112 -0
  290. package/src/tests/runner-key-outcome-late-reset.test.ts +224 -0
  291. package/src/tests/runner-registration-retry.test.ts +149 -0
  292. package/src/tests/runtime-model-catalog.test.ts +86 -0
  293. package/src/tests/script-connections.test.ts +9 -0
  294. package/src/tests/seat-mismatch-report.test.ts +85 -0
  295. package/src/tests/seed-scripts.test.ts +138 -0
  296. package/src/tests/seed.test.ts +25 -0
  297. package/src/tests/session-costs.test.ts +14 -11
  298. package/src/tests/slack-message-text.test.ts +101 -0
  299. package/src/tests/slack-render-v2.test.ts +96 -0
  300. package/src/tests/slack-session-status.test.ts +626 -0
  301. package/src/tests/slack-thread-buffer-context.test.ts +93 -0
  302. package/src/tests/status.test.ts +29 -0
  303. package/src/tests/steering-promotion.test.ts +51 -0
  304. package/src/tests/store-progress-ownership.test.ts +136 -0
  305. package/src/tests/subscription-plans.test.ts +28 -0
  306. package/src/tests/task-cascade-fail.test.ts +148 -2
  307. package/src/tests/task-citations.test.ts +3 -2
  308. package/src/tests/tool-annotations.test.ts +1 -1
  309. package/src/tests/usage-cache.test.ts +166 -0
  310. package/src/tests/workflow-agent-task.test.ts +30 -0
  311. package/src/tests/workflow-executors.test.ts +5 -2
  312. package/src/tests/workflow-system-one-decision-review.test.ts +1021 -0
  313. package/src/tests/workflow-system-one-decision-save-warnings.test.ts +365 -0
  314. package/src/tests/workflow-system-one-decision.test.ts +2063 -0
  315. package/src/tests/workflow-triggers-v2.test.ts +320 -0
  316. package/src/tools/model-catalog/index.ts +137 -0
  317. package/src/tools/schedules/create-schedule.ts +17 -2
  318. package/src/tools/schedules/patch-schedule.ts +31 -1
  319. package/src/tools/schedules/update-schedule.ts +31 -1
  320. package/src/tools/send-task.ts +18 -1
  321. package/src/tools/store-progress.ts +49 -4
  322. package/src/tools/task-action.ts +14 -1
  323. package/src/tools/tool-config.ts +3 -1
  324. package/src/tools/workflows/create-workflow.ts +17 -4
  325. package/src/tools/workflows/patch-workflow-node.ts +17 -7
  326. package/src/tools/workflows/patch-workflow.ts +9 -2
  327. package/src/tools/workflows/update-workflow.ts +17 -4
  328. package/src/types.ts +41 -1
  329. package/src/utils/codex-auth-failure.ts +34 -0
  330. package/src/utils/context-window.ts +47 -53
  331. package/src/utils/credentials.ts +72 -30
  332. package/src/utils/error-tracker.ts +80 -0
  333. package/src/utils/harness-cli-version.ts +115 -0
  334. package/src/utils/harness-model-error.ts +33 -0
  335. package/src/utils/model-rate-limit-windows.ts +12 -0
  336. package/src/utils/runtime-model-catalog.ts +152 -0
  337. package/src/utils/spawn-model-guard.ts +43 -0
  338. package/src/utils/subscription-plans.ts +22 -0
  339. package/src/workflows/approval-resolution.ts +39 -0
  340. package/src/workflows/definition.ts +7 -0
  341. package/src/workflows/engine.ts +69 -1
  342. package/src/workflows/executors/agent-task.ts +16 -6
  343. package/src/workflows/executors/base.ts +53 -0
  344. package/src/workflows/executors/human-in-the-loop.ts +8 -8
  345. package/src/workflows/executors/index.ts +1 -0
  346. package/src/workflows/executors/registry.ts +2 -0
  347. package/src/workflows/executors/system-one-decision.ts +1232 -0
  348. package/src/workflows/executors/system-one-providers.ts +301 -0
  349. package/src/workflows/executors/workflow-llm.ts +13 -1
  350. package/src/workflows/model-validation.ts +45 -0
  351. package/src/workflows/patch-definition.ts +5 -0
  352. package/src/workflows/readiness.ts +86 -0
  353. package/src/workflows/recovery.ts +12 -12
  354. package/src/workflows/resume.ts +23 -9
  355. package/src/workflows/triggers.ts +85 -0
  356. package/templates/schedules/daily-blocker-digest/content.md +4 -5
  357. package/templates/skills/agent-browser/SKILL.md +6 -5
  358. package/templates/skills/agent-browser/content.md +6 -5
  359. package/templates/skills/heartbeat-runbook/config.json +1 -1
  360. package/templates/skills/heartbeat-runbook/content.md +2 -2
  361. package/templates/skills/swarm-scripts/SKILL.md +1 -1
  362. package/templates/skills/swarm-scripts/content.md +1 -1
  363. package/templates/skills/workflow-iterate/content.md +82 -1
  364. package/templates/workflows/claude-code-changelog-watch/content.md +2 -0
  365. package/templates/workflows/pr-review-status-sweep/content.md +2 -0
  366. package/dist/boot-seeds-s3q01xj3.js +0 -26
  367. package/dist/claude-sdk-session-t442qz6f.js +0 -39108
  368. package/dist/cli-0xjkj1j7.js +0 -61
  369. package/dist/cli-20cj37vt.js +0 -335561
  370. package/dist/cli-2djtr51b.js +0 -48
  371. package/dist/cli-65hjmev5.js +0 -1141
  372. package/dist/cli-aw1jwp1r.js +0 -14075
  373. package/dist/cli-h967nb6r.js +0 -6603
  374. package/dist/cli-vapyk8sw.js +0 -20
  375. package/dist/cli-ws6rqycm.js +0 -247
  376. package/dist/pricing-refresh-v1cbr9te.js +0 -27
  377. package/src/providers/modelsdev-reasoning.json +0 -6453
  378. package/dist/{index-f24115ge.js → index-2wsdsnmm.js} +3 -3
@@ -0,0 +1,93 @@
1
+ import { afterAll, beforeAll, describe, expect, mock, test } from "bun:test";
2
+ import { unlinkSync } from "node:fs";
3
+ import { closeDb, createAgent, getLatestActiveTaskInThread, initDb } from "../be/db";
4
+
5
+ process.env.SLACK_RENDER_V2 = "false";
6
+ process.env.STEERING_ENABLED = "false";
7
+
8
+ const TEST_DB_PATH = "./test-slack-thread-buffer-context.sqlite";
9
+
10
+ // Replies Slack returns for the thread: the root, one earlier human reply, and
11
+ // the buffered follow-up itself (conversations.replies includes it by the time
12
+ // the buffer flushes).
13
+ const threadMessages = [
14
+ { ts: "7000.0001", user: "U0ROOT", text: "original request" },
15
+ { ts: "7000.0005", user: "U0EARLIER", text: "earlier reply that is only context" },
16
+ {
17
+ ts: "7000.0010",
18
+ user: "U0FOLLOWUP",
19
+ text: "please also fix the retry\nand add a test for it",
20
+ blocks: [
21
+ {
22
+ type: "rich_text",
23
+ elements: [
24
+ {
25
+ type: "rich_text_section",
26
+ elements: [{ type: "text", text: "please also fix the retry\nand add a test for it" }],
27
+ },
28
+ ],
29
+ },
30
+ ],
31
+ },
32
+ ];
33
+
34
+ const realSlackApp = await import("../slack/app");
35
+ mock.module("../slack/app", () => ({
36
+ ...realSlackApp,
37
+ getSlackApp: () => ({
38
+ client: {
39
+ conversations: { replies: async () => ({ ok: true, messages: threadMessages }) },
40
+ users: { info: async () => ({ ok: true, user: { profile: {} } }) },
41
+ chat: { postMessage: async () => ({ ok: true, ts: "7000.0099" }) },
42
+ },
43
+ }),
44
+ }));
45
+
46
+ const { instantFlush, bufferThreadMessage } = await import("../slack/thread-buffer");
47
+
48
+ beforeAll(async () => {
49
+ initDb(TEST_DB_PATH);
50
+ await createAgent({ name: "lead-agent", isLead: true, status: "idle", capabilities: [] });
51
+ });
52
+
53
+ afterAll(() => {
54
+ closeDb();
55
+ for (const suffix of ["", "-wal", "-shm"]) {
56
+ try {
57
+ unlinkSync(`${TEST_DB_PATH}${suffix}`);
58
+ } catch {}
59
+ }
60
+ });
61
+
62
+ describe("buffered follow-up task description", () => {
63
+ test("carries each buffered message once, outside <thread_context>", async () => {
64
+ const channelId = "C700";
65
+ const threadTs = "7000.0001";
66
+ bufferThreadMessage(
67
+ channelId,
68
+ threadTs,
69
+ "please also fix the retry\nand add a test for it",
70
+ "U0FOLLOWUP",
71
+ "7000.0010",
72
+ );
73
+
74
+ await instantFlush(`${channelId}:${threadTs}`);
75
+
76
+ const task = await getLatestActiveTaskInThread(channelId, threadTs);
77
+ expect(task).not.toBeNull();
78
+ const description = task!.task;
79
+ const context = description.slice(
80
+ description.indexOf("<thread_context>"),
81
+ description.indexOf("</thread_context>"),
82
+ );
83
+
84
+ // The follow-up appears exactly once, in the buffered section.
85
+ expect(description.split("please also fix the retry").length - 1).toBe(1);
86
+ expect(context).not.toContain("please also fix the retry");
87
+ expect(description).toContain("1 message(s) buffered");
88
+
89
+ // Negative control: messages that are not buffered stay in the context.
90
+ expect(context).toContain("original request");
91
+ expect(context).toContain("earlier reply that is only context");
92
+ });
93
+ });
@@ -116,6 +116,7 @@ const ENV_KEYS_TO_RESET = [
116
116
  "AGENTMAIL_API_KEY",
117
117
  "AGENTMAIL_DISABLE",
118
118
  "SWARM_VERIFY_TTL_MS",
119
+ "ANONYMIZED_TELEMETRY",
119
120
  ];
120
121
 
121
122
  const savedEnv = new Map<string, string | undefined>();
@@ -222,6 +223,34 @@ describe("buildStatusPayload — identity", () => {
222
223
  });
223
224
  });
224
225
 
226
+ // ─── Telemetry opt-out state ─────────────────────────────────────────────────
227
+
228
+ describe("buildStatusPayload — telemetry", () => {
229
+ test("reports enabled by default (opt-out model)", async () => {
230
+ const payload = await buildStatusPayload();
231
+ expect(payload.telemetry).toEqual({ enabled: true });
232
+ });
233
+
234
+ test.each(["false", "0"])("reports disabled when ANONYMIZED_TELEMETRY=%s", async (value) => {
235
+ process.env.ANONYMIZED_TELEMETRY = value;
236
+ const payload = await buildStatusPayload();
237
+ expect(payload.telemetry).toEqual({ enabled: false });
238
+ });
239
+
240
+ test("reports enabled when ANONYMIZED_TELEMETRY=true", async () => {
241
+ process.env.ANONYMIZED_TELEMETRY = "true";
242
+ const payload = await buildStatusPayload();
243
+ expect(payload.telemetry).toEqual({ enabled: true });
244
+ });
245
+
246
+ test("follows the env at call time, matching the flag track() reads", async () => {
247
+ process.env.ANONYMIZED_TELEMETRY = "false";
248
+ expect((await buildStatusPayload()).telemetry?.enabled).toBe(false);
249
+ process.env.ANONYMIZED_TELEMETRY = "true";
250
+ expect((await buildStatusPayload()).telemetry?.enabled).toBe(true);
251
+ });
252
+ });
253
+
225
254
  // ─── Setup state machine ─────────────────────────────────────────────────────
226
255
 
227
256
  function getMilestone(payload: Awaited<ReturnType<typeof buildStatusPayload>>, id: string) {
@@ -18,6 +18,7 @@ import {
18
18
  } from "../be/db";
19
19
  import { buildResumeContextPreamble } from "../commands/context-preamble";
20
20
  import { codeLevelTriage } from "../heartbeat/heartbeat";
21
+ import { handlePoll } from "../http/poll";
21
22
 
22
23
  const TEST_DB_PATH = `./test-steering-promotion-${process.pid}.sqlite`;
23
24
 
@@ -94,6 +95,56 @@ describe("steering promotion on terminal tasks", () => {
94
95
  });
95
96
  });
96
97
 
98
+ test("a promoted steer on a Codex agent carries no model or tier, so the claim records none and the adapter default applies", async () => {
99
+ const codexId = (
100
+ await createAgent({
101
+ name: "steering codex",
102
+ isLead: false,
103
+ status: "idle",
104
+ maxTasks: 2,
105
+ harnessProvider: "codex",
106
+ })
107
+ ).id;
108
+ const task = await createTaskExtended("codex parent", {
109
+ agentId: codexId,
110
+ model: "gpt-6-luna",
111
+ });
112
+ expect((await startTask(task.id))?.status).toBe("in_progress");
113
+ await createSteeringMessage({
114
+ taskId: task.id,
115
+ body: "steer after reboot",
116
+ mode: "queue",
117
+ source: "api",
118
+ createdByKind: "system",
119
+ });
120
+ expect((await failTask(task.id, "reboot sweep"))?.status).toBe("failed");
121
+ const [promoted] = await getSteeringMessagesForTask(task.id);
122
+ const followUp = await getTaskById(promoted!.promotedTaskId!);
123
+ expect(followUp?.agentId).toBe(codexId);
124
+ expect(followUp?.model ?? null).toBeNull();
125
+ expect(followUp?.modelTier ?? null).toBeNull();
126
+
127
+ let body = "";
128
+ const req = {
129
+ method: "GET",
130
+ url: "/api/poll",
131
+ headers: { "x-agent-id": codexId },
132
+ } as unknown as Parameters<typeof handlePoll>[0];
133
+ const res = {
134
+ setHeader() {},
135
+ writeHead() {},
136
+ end(chunk?: string) {
137
+ body = chunk ?? "";
138
+ },
139
+ } as unknown as Parameters<typeof handlePoll>[1];
140
+ await handlePoll(req, res, ["api", "poll"], new URLSearchParams(), codexId);
141
+ const trigger = (JSON.parse(body) as { trigger: Record<string, any> | null }).trigger;
142
+ expect(trigger?.type).toBe("task_assigned");
143
+ expect(trigger?.taskId).toBe(followUp!.id);
144
+ expect(trigger?.task.resolvedModel).toBeUndefined();
145
+ expect(trigger?.task.modelUnsupported).toBeUndefined();
146
+ });
147
+
97
148
  test("promotion bypasses disabled follow-up configuration", async () => {
98
149
  const { task } = await taskWithPendingSteer("must still be followed", {
99
150
  followUpConfig: { disabled: true },
@@ -0,0 +1,136 @@
1
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import type { ZodType } from "zod";
4
+ import {
5
+ closeDb,
6
+ createAgent,
7
+ createTaskExtended,
8
+ getTaskAttachments,
9
+ getTaskById,
10
+ initDb,
11
+ startTask,
12
+ } from "../be/db";
13
+ import { registerStoreProgressTool } from "../tools/store-progress";
14
+ import type { SwarmToolResult } from "../tools/utils";
15
+
16
+ // Regression: a workflow foreach child saw a sibling's task id in its prompt
17
+ // and completed that sibling's task with its own report. Only the assignee
18
+ // (or a lead) may write a task's progress, status, or attachments.
19
+ const ownerId = "aaaa0000-0000-4000-8000-000000000a01";
20
+ const siblingId = "bbbb0000-0000-4000-8000-000000000a02";
21
+ const leadId = "cccc0000-0000-4000-8000-000000000a03";
22
+
23
+ const server = new McpServer({ name: "store-progress-ownership", version: "1.0.0" });
24
+ registerStoreProgressTool(server);
25
+ const tool = (
26
+ server as unknown as {
27
+ _registeredTools: Record<
28
+ string,
29
+ {
30
+ inputSchema: ZodType;
31
+ handler: (args: unknown, meta: unknown) => Promise<SwarmToolResult>;
32
+ }
33
+ >;
34
+ }
35
+ )._registeredTools["store-progress"]!;
36
+
37
+ async function call(args: unknown, callerId: string, sourceTaskId?: string) {
38
+ return tool.handler(tool.inputSchema.parse(args), {
39
+ sessionId: "store-progress-ownership-session",
40
+ requestInfo: {
41
+ headers: {
42
+ "x-agent-id": callerId,
43
+ ...(sourceTaskId ? { "x-source-task-id": sourceTaskId } : {}),
44
+ },
45
+ },
46
+ });
47
+ }
48
+
49
+ async function task(owner: string | null) {
50
+ const row = await createTaskExtended("ownership regression", {
51
+ ...(owner ? { agentId: owner } : {}),
52
+ source: "system",
53
+ followUpConfig: { disabled: true },
54
+ });
55
+ if (owner) await startTask(row.id);
56
+ return row;
57
+ }
58
+
59
+ const originalEmbeddingKey = process.env.EMBEDDING_API_KEY;
60
+ const originalOpenAiKey = process.env.OPENAI_API_KEY;
61
+ beforeAll(async () => {
62
+ process.env.EMBEDDING_API_KEY = "";
63
+ process.env.OPENAI_API_KEY = "";
64
+ initDb(":memory:");
65
+ await createAgent({ id: ownerId, name: "Owner worker", isLead: false, status: "idle" });
66
+ await createAgent({ id: siblingId, name: "Sibling worker", isLead: false, status: "idle" });
67
+ await createAgent({ id: leadId, name: "Lead", role: "lead", isLead: true, status: "idle" });
68
+ });
69
+ afterAll(() => {
70
+ closeDb();
71
+ if (originalEmbeddingKey === undefined) delete process.env.EMBEDDING_API_KEY;
72
+ else process.env.EMBEDDING_API_KEY = originalEmbeddingKey;
73
+ if (originalOpenAiKey === undefined) delete process.env.OPENAI_API_KEY;
74
+ else process.env.OPENAI_API_KEY = originalOpenAiKey;
75
+ });
76
+
77
+ describe("store-progress task ownership", () => {
78
+ test("a non-lead non-owner cannot complete another agent's task", async () => {
79
+ const target = await task(ownerId);
80
+ const own = await task(siblingId);
81
+ const result = await call(
82
+ {
83
+ taskId: target.id,
84
+ status: "completed",
85
+ output: "sibling report",
86
+ attachments: [{ kind: "url", name: "report", url: "https://example.com/r" }],
87
+ },
88
+ siblingId,
89
+ own.id,
90
+ );
91
+ expect(result.structuredContent?.success).toBe(false);
92
+ expect(result.structuredContent?.message).toContain("assigned to another agent");
93
+ // The refusal names the caller's own task so it can retry correctly.
94
+ expect(result.structuredContent?.message).toContain(own.id);
95
+ const after = await getTaskById(target.id);
96
+ expect(after?.status).toBe("in_progress");
97
+ expect(after?.output).toBeUndefined();
98
+ expect(await getTaskAttachments(target.id)).toHaveLength(0);
99
+ });
100
+
101
+ test("a non-lead non-owner cannot write progress, and the retry hint falls back to its in-progress task", async () => {
102
+ const target = await task(ownerId);
103
+ const own = await task(siblingId);
104
+ const result = await call({ taskId: target.id, progress: "not mine" }, siblingId);
105
+ expect(result.structuredContent?.success).toBe(false);
106
+ expect(result.structuredContent?.message).toContain(own.id);
107
+ expect((await getTaskById(target.id))?.progress).toBeUndefined();
108
+ });
109
+
110
+ test("the owner can write progress and complete its task", async () => {
111
+ const own = await task(ownerId);
112
+ expect(
113
+ (await call({ taskId: own.id, progress: "halfway" }, ownerId)).structuredContent?.success,
114
+ ).toBe(true);
115
+ expect((await getTaskById(own.id))?.progress).toBe("halfway");
116
+ const result = await call({ taskId: own.id, status: "completed", output: "done" }, ownerId);
117
+ expect(result.structuredContent?.success).toBe(true);
118
+ expect((await getTaskById(own.id))?.status).toBe("completed");
119
+ });
120
+
121
+ test("a lead can still complete a worker's task", async () => {
122
+ const target = await task(ownerId);
123
+ const result = await call(
124
+ { taskId: target.id, status: "completed", output: "closed by lead" },
125
+ leadId,
126
+ );
127
+ expect(result.structuredContent?.success).toBe(true);
128
+ expect((await getTaskById(target.id))?.status).toBe("completed");
129
+ });
130
+
131
+ test("an unassigned task stays writable, matching POST /api/tasks/:id/finish", async () => {
132
+ const target = await task(null);
133
+ const result = await call({ taskId: target.id, progress: "pool note" }, siblingId);
134
+ expect(result.structuredContent?.success).toBe(true);
135
+ });
136
+ });
@@ -0,0 +1,28 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { planAllowsModelFamily } from "../utils/subscription-plans";
3
+
4
+ describe("planAllowsModelFamily", () => {
5
+ test("claude_team_standard cannot run fable", () => {
6
+ expect(planAllowsModelFamily("claude_team_standard", "fable")).toBe(false);
7
+ });
8
+
9
+ test("claude_team_standard can run opus", () => {
10
+ expect(planAllowsModelFamily("claude_team_standard", "opus")).toBe(true);
11
+ });
12
+
13
+ test("claude_team_premium can run fable", () => {
14
+ expect(planAllowsModelFamily("claude_team_premium", "fable")).toBe(true);
15
+ });
16
+
17
+ test("a null plan fails open", () => {
18
+ expect(planAllowsModelFamily(null, "fable")).toBe(true);
19
+ });
20
+
21
+ test("an unknown plan fails open", () => {
22
+ expect(planAllowsModelFamily("unknown_plan", "fable")).toBe(true);
23
+ });
24
+
25
+ test("an undefined family is always allowed", () => {
26
+ expect(planAllowsModelFamily("claude_team_standard", undefined)).toBe(true);
27
+ });
28
+ });
@@ -1,6 +1,7 @@
1
1
  import { afterAll, beforeAll, describe, expect, test } from "bun:test";
2
2
  import { unlinkSync } from "node:fs";
3
3
  import {
4
+ backfillSupersedeTaskResumeTaskId,
4
5
  cancelTask,
5
6
  cascadeFailDependents,
6
7
  closeDb,
@@ -230,7 +231,7 @@ describe("cascadeFailDependents", () => {
230
231
  expect(childAfter!.failureReason).toContain("was cancelled");
231
232
  });
232
233
 
233
- test("supersedeTask cascades to dependents", async () => {
234
+ test("supersedeTask with a resume id re-points never-started dependents", async () => {
234
235
  const agent = await createAgent({
235
236
  name: "cascade-worker-6",
236
237
  isLead: false,
@@ -239,19 +240,164 @@ describe("cascadeFailDependents", () => {
239
240
  });
240
241
 
241
242
  const parent = await createTaskExtended("Parent supersede", { agentId: agent.id });
243
+ const other = await createTaskExtended("Other dependency", { agentId: agent.id });
242
244
  const child = await createTaskExtended("Child of superseded", {
245
+ agentId: agent.id,
246
+ dependsOn: [parent.id, other.id],
247
+ });
248
+ const resume = await createTaskExtended("Resume of parent", { agentId: agent.id });
249
+
250
+ await startTask(parent.id);
251
+ await supersedeTask(parent.id, { reason: "context limit", resumeTaskId: resume.id });
252
+
253
+ const childAfter = await getTaskById(child.id);
254
+ expect(childAfter!.status).toBe("pending");
255
+ expect(childAfter!.dependsOn).toEqual([resume.id, other.id]);
256
+
257
+ const log = await getDbClient().get<{ oldValue: string; newValue: string; metadata: string }>(
258
+ "SELECT oldValue, newValue, metadata FROM agent_log WHERE taskId = ? AND eventType = 'task_dependency_repointed'",
259
+ [child.id],
260
+ );
261
+ expect(log?.oldValue).toBe(parent.id);
262
+ expect(log?.newValue).toBe(resume.id);
263
+ expect(JSON.parse(log!.metadata).reason).toBe("supersede_resume");
264
+ });
265
+
266
+ test("supersedeTask without a resume id defers dependents until the backfill", async () => {
267
+ const agent = await createAgent({
268
+ name: "cascade-worker-6b",
269
+ isLead: false,
270
+ status: "idle",
271
+ capabilities: [],
272
+ });
273
+
274
+ const parent = await createTaskExtended("Parent supersede late resume", { agentId: agent.id });
275
+ const child = await createTaskExtended("Child of late-resumed", {
276
+ agentId: agent.id,
277
+ dependsOn: [parent.id],
278
+ });
279
+
280
+ await startTask(parent.id);
281
+ await supersedeTask(parent.id, { reason: "graceful_shutdown", resumeTaskId: null });
282
+
283
+ // The window between supersede and resume creation must not fail it.
284
+ expect((await getTaskById(child.id))!.status).toBe("pending");
285
+
286
+ const resume = await createTaskExtended("Late resume", { agentId: agent.id });
287
+ await backfillSupersedeTaskResumeTaskId(parent.id, resume.id);
288
+
289
+ const childAfter = await getTaskById(child.id);
290
+ expect(childAfter!.status).toBe("pending");
291
+ expect(childAfter!.dependsOn).toEqual([resume.id]);
292
+ });
293
+
294
+ test("supersede settle does not rewrite a dependent that already started", async () => {
295
+ const agent = await createAgent({
296
+ name: "cascade-worker-6c",
297
+ isLead: false,
298
+ status: "idle",
299
+ capabilities: [],
300
+ });
301
+
302
+ const parent = await createTaskExtended("Parent supersede started dep", {
303
+ agentId: agent.id,
304
+ });
305
+ const child = await createTaskExtended("Started child", {
243
306
  agentId: agent.id,
244
307
  dependsOn: [parent.id],
245
308
  });
309
+ const resume = await createTaskExtended("Resume for started dep", { agentId: agent.id });
246
310
 
247
311
  await startTask(parent.id);
248
- await supersedeTask(parent.id, { reason: "context limit", resumeTaskId: null });
312
+ await startTask(child.id);
313
+ await supersedeTask(parent.id, { reason: "context limit", resumeTaskId: resume.id });
249
314
 
250
315
  const childAfter = await getTaskById(child.id);
316
+ expect(childAfter!.dependsOn).toEqual([parent.id]);
251
317
  expect(childAfter!.status).toBe("failed");
252
318
  expect(childAfter!.failureReason).toContain("was superseded");
253
319
  });
254
320
 
321
+ test("concurrent settlements of a shared dependent keep both replacements", async () => {
322
+ const agent = await createAgent({
323
+ name: "cascade-worker-6d",
324
+ isLead: false,
325
+ status: "idle",
326
+ capabilities: [],
327
+ });
328
+
329
+ const a = await createTaskExtended("Shared parent A", { agentId: agent.id });
330
+ const b = await createTaskExtended("Shared parent B", { agentId: agent.id });
331
+ const child = await createTaskExtended("Depends on A and B", {
332
+ agentId: agent.id,
333
+ dependsOn: [a.id, b.id],
334
+ });
335
+ const ra = await createTaskExtended("Resume of A", { agentId: agent.id });
336
+ const rb = await createTaskExtended("Resume of B", { agentId: agent.id });
337
+
338
+ for (const parent of [a, b]) {
339
+ await startTask(parent.id);
340
+ await supersedeTask(parent.id, { reason: "crash_recovery", resumeTaskId: null });
341
+ }
342
+ await Promise.all([
343
+ backfillSupersedeTaskResumeTaskId(a.id, ra.id),
344
+ backfillSupersedeTaskResumeTaskId(b.id, rb.id),
345
+ ]);
346
+
347
+ const childAfter = await getTaskById(child.id);
348
+ expect(childAfter!.status).toBe("pending");
349
+ expect(childAfter!.dependsOn).toEqual([ra.id, rb.id]);
350
+ });
351
+
352
+ // A dependent's status changes right after any read the settlement makes.
353
+ // An accept out of `offered` is still never-started and must be re-pointed;
354
+ // the claim-into-in_progress guard is covered by the started-dependent test.
355
+ for (const moveTo of ["reviewing", "pending"] as const) {
356
+ test(`settlement racing offered -> ${moveTo} still re-points the dependent`, async () => {
357
+ const agent = await createAgent({
358
+ name: `cascade-worker-6e-${moveTo}`,
359
+ isLead: false,
360
+ status: "idle",
361
+ capabilities: [],
362
+ });
363
+
364
+ const parent = await createTaskExtended("Parent of offered dep", { agentId: agent.id });
365
+ const child = await createTaskExtended("Offered child", {
366
+ agentId: agent.id,
367
+ dependsOn: [parent.id],
368
+ });
369
+ const resume = await createTaskExtended("Resume for offered dep", { agentId: agent.id });
370
+ const client = getDbClient();
371
+ await client.run("UPDATE agent_tasks SET status = 'offered' WHERE id = ?", [child.id]);
372
+ await startTask(parent.id);
373
+ await supersedeTask(parent.id, { reason: "crash_recovery", resumeTaskId: null });
374
+
375
+ const query = client.query.bind(client);
376
+ let raced = false;
377
+ client.query = (async (sql: string, params?: unknown[]) => {
378
+ const rows = (await query(sql, params as never)) as { id?: string; status?: string }[];
379
+ if (!raced && rows.some((r) => r.id === child.id && r.status === "offered")) {
380
+ raced = true;
381
+ await client.run("UPDATE agent_tasks SET status = ? WHERE id = ?", [moveTo, child.id]);
382
+ }
383
+ return rows;
384
+ }) as typeof client.query;
385
+ try {
386
+ await backfillSupersedeTaskResumeTaskId(parent.id, resume.id);
387
+ } finally {
388
+ client.query = query;
389
+ }
390
+ // No read to race (single-statement settlement): the accept lands after.
391
+ if (!raced) {
392
+ await client.run("UPDATE agent_tasks SET status = ? WHERE id = ?", [moveTo, child.id]);
393
+ }
394
+
395
+ const childAfter = await getTaskById(child.id);
396
+ expect(childAfter!.status).toBe(moveTo);
397
+ expect(childAfter!.dependsOn).toEqual([resume.id]);
398
+ });
399
+ }
400
+
255
401
  test("wide fan-out: multiple dependents all cascade-failed", async () => {
256
402
  const agent = await createAgent({
257
403
  name: "cascade-worker-7",
@@ -249,10 +249,11 @@ describe("citation persistence and completion warnings", () => {
249
249
  "failed",
250
250
  "cancelled",
251
251
  ] as const)("only the assigned agent can insert or replace citations on a %s task", async (taskStatus) => {
252
+ // A lead: non-lead non-owners are refused outright (store-progress-ownership).
252
253
  const otherAgent = await createAgent({
253
254
  name: `Other citation agent ${taskStatus}`,
254
- role: "worker",
255
- isLead: false,
255
+ role: "lead",
256
+ isLead: true,
256
257
  status: "idle",
257
258
  capabilities: [],
258
259
  });
@@ -378,7 +378,7 @@ describe("Tool Annotations & Classification", () => {
378
378
  // Includes 11 skill tools, 7 MCP server tools, reusable script tools, and the
379
379
  // native Kapso/WhatsApp tools (register/unregister number + send/reply message).
380
380
  expect(count).toBeGreaterThanOrEqual(45);
381
- expect(count).toBeLessThanOrEqual(149);
381
+ expect(count).toBeLessThanOrEqual(151);
382
382
  });
383
383
 
384
384
  test("core tools are fewer than deferred tools", () => {