@desplega.ai/agent-swarm 1.131.1 → 1.134.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 (360) hide show
  1. package/README.md +3 -3
  2. package/dist/{actions-jgcy477k.js → actions-7kast243.js} +20 -18
  3. package/dist/{anthropic-messages-rjmd2e7n.js → anthropic-messages-706yad38.js} +33 -17
  4. package/dist/app-0g84t8wf.js +28 -0
  5. package/dist/{artifact-jysj3wrg.js → artifact-dje5ke4h.js} +37 -131
  6. package/dist/{assistant-f2vkk1hj.js → assistant-ac4htcky.js} +25 -22
  7. package/dist/{azure-openai-responses-c0yjmbfd.js → azure-openai-responses-be59fbsz.js} +13 -13
  8. package/dist/{boot-reembed-q7mp8hcr.js → boot-reembed-b1rcqn85.js} +13 -11
  9. package/dist/{boot-reembed-xmw8wcx6.js → boot-reembed-kw2541c4.js} +14 -12
  10. package/dist/{boot-scrub-logs-dsqrv79n.js → boot-scrub-logs-ja29t92g.js} +11 -9
  11. package/dist/{claude-adapter-q6x7yspx.js → claude-adapter-5cg1fv8s.js} +33 -29
  12. package/dist/{claude-managed-adapter-wsg37bt8.js → claude-managed-adapter-9z9sdytx.js} +9 -9
  13. package/dist/{claude-managed-setup-52z4mq9w.js → claude-managed-setup-3zqp51cr.js} +5 -5
  14. package/dist/{claude-1ex232fc.js → claude-wqn3dtca.js} +1 -1
  15. package/dist/{cli-4wgngmwv.js → cli-00w4hbam.js} +1 -1
  16. package/dist/{cli-er3a4bb0.js → cli-15429qdw.js} +6 -1
  17. package/dist/{cli-e5dabkx0.js → cli-1cwhyvta.js} +4 -4
  18. package/dist/{cli-zsspvvm2.js → cli-1ybr6vw2.js} +237 -237
  19. package/dist/{cli-xpxde7rm.js → cli-2wv4kj0e.js} +60 -31
  20. package/dist/cli-34w2584r.js +530 -0
  21. package/dist/{cli-se2p6rkb.js → cli-38taxznr.js} +6 -6
  22. package/dist/{cli-9sbhdpvb.js → cli-3cgfyfzm.js} +14 -14
  23. package/dist/{cli-5n6e5yfn.js → cli-3zmdzfjf.js} +79 -74
  24. package/dist/{cli-3ty0t082.js → cli-43x36mty.js} +197 -8
  25. package/dist/{cli-ycrnynpw.js → cli-480nehnh.js} +29 -22
  26. package/dist/{cli-0258203w.js → cli-492e7ap5.js} +140 -20
  27. package/dist/{cli-3fj60etf.js → cli-497b685t.js} +3 -3
  28. package/dist/{cli-pxfxkx5m.js → cli-4bap00k0.js} +1 -1
  29. package/dist/{cli-w2wxd56v.js → cli-4bc8vmhm.js} +848 -770
  30. package/dist/{cli-aca5pz9f.js → cli-589599p9.js} +1 -1
  31. package/dist/{cli-t930g0xh.js → cli-5tnr64pm.js} +1 -1
  32. package/dist/{cli-jxg3s9ey.js → cli-65d93ves.js} +7 -7
  33. package/dist/{cli-6dpytdr2.js → cli-6x363v1z.js} +2 -2
  34. package/dist/{cli-dxak0fqd.js → cli-787ph852.js} +11 -11
  35. package/dist/{cli-793jvbsh.js → cli-7bv8nckk.js} +16 -11
  36. package/dist/{cli-zyxs93y9.js → cli-7jcc9y1r.js} +3 -3
  37. package/dist/{cli-f1am9szv.js → cli-87t2gstf.js} +1 -1
  38. package/dist/{cli-9aryf9tv.js → cli-95m51ztz.js} +1 -1
  39. package/dist/{cli-4w4fmq9x.js → cli-9w49enmh.js} +2 -2
  40. package/dist/{cli-v69vk5h0.js → cli-9y5b02vr.js} +13 -13
  41. package/dist/{cli-x23me2mg.js → cli-a1rrkrb4.js} +1 -1
  42. package/dist/{cli-psrqtw2m.js → cli-a3pkfpqs.js} +2 -2
  43. package/dist/{cli-h0rgxd24.js → cli-a4ajmfvp.js} +6 -6
  44. package/dist/{cli-ygrbmjja.js → cli-aw8bb6sf.js} +6 -6
  45. package/dist/{cli-g74vpjp2.js → cli-brm3et47.js} +55 -22
  46. package/dist/{cli-czkae1fp.js → cli-c5ddbdfj.js} +9 -9
  47. package/dist/{cli-114fvtmj.js → cli-c79a9zrc.js} +1 -1
  48. package/dist/{cli-49n9f49x.js → cli-dx3dz0ms.js} +3 -3
  49. package/dist/{cli-6dp3ydxa.js → cli-e07exmsb.js} +18 -14
  50. package/dist/cli-et1bbwqf.js +13 -0
  51. package/dist/{cli-yazx6hap.js → cli-f3s9dry0.js} +14 -14
  52. package/dist/{cli-t8d349cy.js → cli-f4wj0s3m.js} +3 -3
  53. package/dist/{cli-d6n0thxw.js → cli-f64gdfxc.js} +1 -1
  54. package/dist/{cli-pwfsqc2x.js → cli-fyd58xgb.js} +55 -55
  55. package/dist/{cli-7dm2zqy2.js → cli-g0j14czv.js} +2 -2
  56. package/dist/{cli-b0p7rfnd.js → cli-g2p9bd1z.js} +14 -14
  57. package/dist/{cli-z905tvmm.js → cli-g814jbst.js} +2 -2
  58. package/dist/{cli-5dsbcjjw.js → cli-ghexzd2d.js} +970 -970
  59. package/dist/{cli-jvv0g8bh.js → cli-gpt9kf01.js} +15 -15
  60. package/dist/{cli-rgrfzg8z.js → cli-grt6qv91.js} +380 -380
  61. package/dist/{cli-wz9115x0.js → cli-gtafvdy0.js} +8 -8
  62. package/dist/{cli-q5dzzak0.js → cli-gy07xk4s.js} +2 -2
  63. package/dist/{cli-sy7jk3tx.js → cli-h0djenzm.js} +2 -2
  64. package/dist/{cli-nrkkknat.js → cli-h37gbjbf.js} +2 -281
  65. package/dist/{cli-jevgrb4w.js → cli-h39xxnq6.js} +1200 -491
  66. package/dist/{cli-gz9sbyk3.js → cli-jepmgn7r.js} +1 -1
  67. package/dist/{cli-ccf9vy7q.js → cli-jmvk6et8.js} +7 -7
  68. package/dist/{cli-2rk0cx7s.js → cli-jxj92gkt.js} +51 -41
  69. package/dist/{cli-bqw0mt1n.js → cli-k46t4664.js} +18 -3
  70. package/dist/{cli-50vbt00w.js → cli-m07313tr.js} +1153 -696
  71. package/dist/{cli-4j2mrzag.js → cli-mkq1rk7b.js} +1 -1
  72. package/dist/{cli-1z7048dy.js → cli-mnm7w46e.js} +1 -1
  73. package/dist/{cli-kd1ck4rx.js → cli-mp9sg4cg.js} +14 -8
  74. package/dist/{cli-jbcva0xj.js → cli-mrammd8c.js} +60 -35
  75. package/dist/cli-nanaxrz6.js +27 -0
  76. package/dist/{cli-p9swy5t3.js → cli-pq2ee5ja.js} +8 -6
  77. package/dist/{cli-2wadx7p2.js → cli-pt43pdp1.js} +2 -2
  78. package/dist/{cli-7sgrxg9a.js → cli-q6d0vvey.js} +4 -4
  79. package/dist/{cli-k9qkf1hw.js → cli-qf9kpsve.js} +9 -9
  80. package/dist/cli-qh4sqfws.js +15 -0
  81. package/dist/{cli-e15jhafb.js → cli-qntkxysp.js} +14 -14
  82. package/dist/{cli-sajgpkqg.js → cli-qs9hhsvb.js} +1723 -325
  83. package/dist/{cli-z2zcxes1.js → cli-qt5bt3vk.js} +1 -1
  84. package/dist/{cli-wx82tw1k.js → cli-qtekrty1.js} +3 -3
  85. package/dist/{cli-g1re8r05.js → cli-rh6f3s9n.js} +3 -3
  86. package/dist/{cli-n0krnc6d.js → cli-rzhcrw2p.js} +2 -2
  87. package/dist/{cli-gecy3z2s.js → cli-snw62ze5.js} +4 -4
  88. package/dist/{cli-n6h8qt8n.js → cli-snz3p4yf.js} +15 -10
  89. package/dist/{cli-r6kqqbj0.js → cli-tkf8n6cw.js} +11 -11
  90. package/dist/{cli-4rkfg1wx.js → cli-tkrr24mz.js} +14 -9
  91. package/dist/{cli-9dr5qzc0.js → cli-tv9f1kyb.js} +54 -43
  92. package/dist/{cli-gm9cbhbq.js → cli-vtz6yj3v.js} +28 -9
  93. package/dist/{cli-2f07m0xr.js → cli-wf86d5t7.js} +1 -1
  94. package/dist/{cli-7zd05xb4.js → cli-whrvct19.js} +45 -45
  95. package/dist/{cli-ap1czx8f.js → cli-wrrea8xm.js} +3 -3
  96. package/dist/{cli-57khth04.js → cli-xmyqv00z.js} +120 -4
  97. package/dist/{cli-cwy5hqbq.js → cli-yjmk7tzc.js} +1 -1
  98. package/dist/{cli-6pcz9h15.js → cli-zykjrwcx.js} +180 -394
  99. package/dist/cli.js +45 -45
  100. package/dist/{codex-adapter-htjkn22b.js → codex-adapter-hsq6mz3m.js} +21 -19
  101. package/dist/{codex-hook-hq54t9ft.js → codex-hook-8c500gq8.js} +8 -7
  102. package/dist/{codex-login-z6yhbkmk.js → codex-login-x7603481.js} +3 -3
  103. package/dist/{codex-session-runner-wjfc7smz.js → codex-session-runner-c1x3x143.js} +15 -13
  104. package/dist/{commands-xhrfwqsk.js → commands-rwtddtst.js} +11 -9
  105. package/dist/{db-anvwb22f.js → db-j2b355vz.js} +458 -444
  106. package/dist/{devin-adapter-v1sarqtw.js → devin-adapter-8qmhqpy3.js} +3 -3
  107. package/dist/{devtools-2y1r7yqk.js → devtools-j2rnj8w7.js} +4 -4
  108. package/dist/{e2b-jdkwemv2.js → e2b-znpx78eg.js} +16 -16
  109. package/dist/{es5-vjgtkgp6.js → es5-fmqrjzy3.js} +2 -2
  110. package/dist/{event-bus-axn823e9.js → event-bus-n8sb35pw.js} +4 -4
  111. package/dist/{getMachineId-bsd-6tc9wxdj.js → getMachineId-bsd-wpzbhkm6.js} +4 -4
  112. package/dist/{getMachineId-darwin-sjt9sgnc.js → getMachineId-darwin-45zabjy0.js} +4 -4
  113. package/dist/{getMachineId-linux-v5n18y9p.js → getMachineId-linux-warnp5pd.js} +3 -3
  114. package/dist/{getMachineId-unsupported-yrjyqw3d.js → getMachineId-unsupported-5spent2a.js} +3 -3
  115. package/dist/{getMachineId-win-w3r8063c.js → getMachineId-win-v4bvcw7a.js} +4 -4
  116. package/dist/{google-generative-ai-hysrd8qx.js → google-generative-ai-b0wzss55.js} +17 -14
  117. package/dist/{google-vertex-p5arrga0.js → google-vertex-e8q4dpya.js} +18 -15
  118. package/dist/{handlers-b9kbc621.js → handlers-hchgrxkh.js} +32 -29
  119. package/dist/{hook-xkvn6c5h.js → hook-p49egqc0.js} +66 -52
  120. package/dist/hook-spb00w76.js +34 -0
  121. package/dist/{http-9xh6ta4r.js → http-nh14kkms.js} +663 -181
  122. package/dist/index-1qthq7vc.js +6 -0
  123. package/dist/{index-0g68f0p6.js → index-20xqmn3v.js} +3 -3
  124. package/dist/{index-vdkr5z2w.js → index-3e03k6e4.js} +114 -114
  125. package/dist/{index-ywds4812.js → index-4cbyhq2g.js} +43 -41
  126. package/dist/index-67pzqfrb.js +36 -0
  127. package/dist/{index-bccrkdkn.js → index-7m8yxkw5.js} +16 -16
  128. package/dist/{index-eaxyqcmj.js → index-dvsk64sm.js} +163 -83
  129. package/dist/{index-dqkpnmqm.js → index-e19s2m8k.js} +18 -18
  130. package/dist/{index-j30k5zxn.js → index-emvxcy98.js} +16 -16
  131. package/dist/{index-7ndw6ecy.js → index-gqnmr60d.js} +9 -9
  132. package/dist/{index-9znhar3x.js → index-j02xvjtd.js} +184 -185
  133. package/dist/{index-1s2442yw.js → index-ns5kksc1.js} +7 -7
  134. package/dist/{index-dpy2hth8.js → index-pdxc8kfh.js} +71 -71
  135. package/dist/{index-z1m7pw0v.js → index-ptcqm8bc.js} +107 -107
  136. package/dist/{index-070v4eyb.js → index-pyp7rexm.js} +5 -6
  137. package/dist/{index-86znjz1p.js → index-shne41sd.js} +13 -13
  138. package/dist/{index-2qn2qndv.js → index-t36q2v4t.js} +183 -183
  139. package/dist/index-tcnvv8w6.js +7 -0
  140. package/dist/index-w91qbfv1.js +7 -0
  141. package/dist/{index-fzc0mwvj.js → index-yqweff5n.js} +32 -32
  142. package/dist/index-yrfphye1.js +39 -0
  143. package/dist/{keepalive-vc7s2aa6.js → keepalive-9k51nqhc.js} +21 -15
  144. package/dist/lead-2xq3dxbz.js +88 -0
  145. package/dist/maintenance-0c0g6nym.js +28 -0
  146. package/dist/mistral-conversations-2njwdwhj.js +717 -0
  147. package/dist/{multipart-parser-qaktc7p8.js → multipart-parser-6gftesw0.js} +2 -2
  148. package/dist/{oauth-refresh-sweep-yecgjpk4.js → oauth-refresh-sweep-273a64r2.js} +14 -12
  149. package/dist/{onboard-xj0f73f9.js → onboard-tsv9wwhz.js} +12 -12
  150. package/dist/{openai-codex-responses-5g27pd6f.js → openai-codex-responses-kt84eq86.js} +26 -26
  151. package/dist/{openai-completions-3vkmfz2a.js → openai-completions-mrypq7qp.js} +17 -16
  152. package/dist/{openai-responses-dg4t7j7a.js → openai-responses-qhrk21ez.js} +20 -17
  153. package/dist/{opencode-adapter-1gm38aba.js → opencode-adapter-rar9676j.js} +13 -11
  154. package/dist/{openrouter-images-g3gm8931.js → openrouter-images-684fysx9.js} +5 -5
  155. package/dist/{otel-impl-nxg0n0b3.js → otel-impl-wp1mfzd2.js} +752 -537
  156. package/dist/{photon_rs-ky7dj5qw.js → photon_rs-8dwt2pzy.js} +581 -581
  157. package/dist/{pi-messages-tqj8q6b9.js → pi-messages-txp9f97r.js} +7 -7
  158. package/dist/{pi-mono-adapter-4g5kby31.js → pi-mono-adapter-yz47kk8e.js} +1910 -1180
  159. package/dist/pricing-refresh-jw1yw2fn.js +22 -0
  160. package/dist/rbac-roles-dg964c8p.js +32 -0
  161. package/dist/{rbac-roles-1hcxdtg7.js → rbac-roles-p53cg5cv.js} +19 -17
  162. package/dist/{registry-02cy9z5z.js → registry-mh12y17v.js} +4 -4
  163. package/dist/render-v2-56vn2rk9.js +36 -0
  164. package/dist/seed-pricing-52xx6z63.js +19 -0
  165. package/dist/{setup-46xtgq3c.js → setup-e8gp79sy.js} +7 -7
  166. package/dist/token-util-zy908s1a.js +6 -0
  167. package/dist/{token-fxkn2zf2.js → token-xg5711x5.js} +4 -4
  168. package/dist/worker-s17f1g46.js +88 -0
  169. package/dist/{x-6mg4kfpe.js → x-xkfh0w0v.js} +5 -5
  170. package/openapi.json +566 -22
  171. package/package.json +6 -6
  172. package/src/be/budget-refusal-notify.ts +14 -7
  173. package/src/be/db-queries/task-artifacts.ts +182 -0
  174. package/src/be/db.ts +834 -171
  175. package/src/be/events.ts +5 -0
  176. package/src/be/migrations/131_session_logs_created_at_index.sql +2 -0
  177. package/src/be/migrations/132_multi_runtime_instances.sql +44 -0
  178. package/src/be/migrations/133_workflow_run_trigger_type.sql +12 -0
  179. package/src/be/migrations/135_backfill_task_pull_request_attachments.sql +228 -0
  180. package/src/be/migrations/136_task_requester_provenance.sql +37 -0
  181. package/src/be/migrations/137_memory_retrieval_composite_index.sql +1 -0
  182. package/src/be/migrations/runner.ts +17 -11
  183. package/src/be/multi-runtime.ts +430 -0
  184. package/src/be/scripts/extract-schema.ts +3 -1
  185. package/src/be/scripts/typecheck.ts +6 -2
  186. package/src/be/seed-prompt-templates.ts +25 -2
  187. package/src/be/seed-skills/index.ts +7 -0
  188. package/src/be/steering.ts +1 -1
  189. package/src/be/swarm-config-guard.ts +18 -0
  190. package/src/commands/credential-wait.ts +54 -0
  191. package/src/commands/onboard/steps/core-credentials.tsx +1 -1
  192. package/src/commands/onboard/steps/custom-templates.tsx +1 -1
  193. package/src/commands/onboard/steps/harness-credentials.tsx +1 -1
  194. package/src/commands/onboard/steps/health-check.tsx +1 -1
  195. package/src/commands/onboard/steps/start.tsx +1 -1
  196. package/src/commands/profile-sync-audit.ts +56 -0
  197. package/src/commands/profile-sync.ts +287 -15
  198. package/src/commands/provider-credentials.ts +46 -19
  199. package/src/commands/runner.ts +199 -33
  200. package/src/commands/templates.ts +22 -0
  201. package/src/github/handlers.ts +55 -9
  202. package/src/heartbeat/heartbeat.ts +109 -17
  203. package/src/hooks/hook.ts +49 -33
  204. package/src/http/active-sessions.ts +11 -1
  205. package/src/http/agents.ts +261 -30
  206. package/src/http/all-routes.ts +1 -0
  207. package/src/http/config.ts +13 -2
  208. package/src/http/core.ts +102 -4
  209. package/src/http/db-query-bounded.ts +313 -0
  210. package/src/http/db-query-shared.ts +249 -0
  211. package/src/http/db-query.ts +54 -42
  212. package/src/http/events.ts +2 -0
  213. package/src/http/index.ts +23 -2
  214. package/src/http/mcp-bridge.ts +37 -3
  215. package/src/http/mcp.ts +12 -0
  216. package/src/http/memory.ts +13 -2
  217. package/src/http/openapi.ts +1 -0
  218. package/src/http/poll.ts +70 -27
  219. package/src/http/prompt-templates.ts +36 -13
  220. package/src/http/route-def.ts +43 -9
  221. package/src/http/schedules.ts +2 -2
  222. package/src/http/scripts.ts +10 -1
  223. package/src/http/session-data.ts +44 -0
  224. package/src/http/tasks.ts +2 -2
  225. package/src/http/users.ts +74 -52
  226. package/src/http/webhooks.ts +5 -2
  227. package/src/http/workflows.ts +1 -4
  228. package/src/integrations/kapso/inbound.ts +1 -1
  229. package/src/jira/outbound.ts +39 -8
  230. package/src/jira/webhook-lifecycle.ts +13 -2
  231. package/src/linear/outbound.ts +43 -10
  232. package/src/oauth/keepalive.ts +13 -2
  233. package/src/prompts/base-prompt.ts +1 -1
  234. package/src/prompts/default-drift.ts +21 -0
  235. package/src/prompts/session-templates.ts +56 -7
  236. package/src/providers/claude-adapter.ts +6 -0
  237. package/src/providers/codex-adapter.ts +6 -0
  238. package/src/providers/opencode-adapter.ts +6 -0
  239. package/src/providers/pi-mono-adapter.ts +8 -0
  240. package/src/providers/pricing-sources.md +24 -0
  241. package/src/queue-stall-alarm.ts +216 -0
  242. package/src/rbac/legacy-policy.ts +3 -0
  243. package/src/rbac/permissions.ts +12 -0
  244. package/src/scheduler/scheduler.ts +7 -0
  245. package/src/script-workflows/executor.ts +2 -1
  246. package/src/scripts-runtime/executors/native.ts +2 -1
  247. package/src/scripts-runtime/executors/types.ts +5 -0
  248. package/src/scripts-runtime/loader.ts +5 -0
  249. package/src/scripts-runtime/sdk-allowlist.ts +3 -0
  250. package/src/scripts-runtime/swarm-config.ts +8 -0
  251. package/src/scripts-runtime/swarm-sdk.ts +7 -0
  252. package/src/scripts-runtime/types/stdlib.d.ts +4 -0
  253. package/src/scripts-runtime/types/swarm-sdk.d.ts +4 -0
  254. package/src/server.ts +6 -0
  255. package/src/slack/channel-join.ts +22 -3
  256. package/src/slack/channel-lifecycle.ts +114 -0
  257. package/src/tests/budget-claim-gate.test.ts +20 -0
  258. package/src/tests/claude-adapter.test.ts +32 -0
  259. package/src/tests/codex-adapter.test.ts +30 -0
  260. package/src/tests/create-task-validation.test.ts +128 -0
  261. package/src/tests/credential-wait.test.ts +67 -1
  262. package/src/tests/db-query.test.ts +711 -2
  263. package/src/tests/heartbeat-checklist.test.ts +28 -0
  264. package/src/tests/http-api-integration.test.ts +81 -0
  265. package/src/tests/http-users.test.ts +166 -1
  266. package/src/tests/identity-field-budget.test.ts +176 -0
  267. package/src/tests/mcp-tools-user.test.ts +34 -0
  268. package/src/tests/mcp-user-route.test.ts +17 -0
  269. package/src/tests/metrics-http.test.ts +44 -0
  270. package/src/tests/migration-failure.test.ts +86 -0
  271. package/src/tests/multi-runtime-registration.test.ts +2810 -0
  272. package/src/tests/openapi-response-contract.test.ts +24 -0
  273. package/src/tests/openapi-route-headers.test.ts +83 -0
  274. package/src/tests/opencode-adapter.test.ts +40 -0
  275. package/src/tests/outbound-listener-rejection.test.ts +138 -0
  276. package/src/tests/pi-mono-adapter.test.ts +98 -0
  277. package/src/tests/preload.ts +2 -0
  278. package/src/tests/profile-sync-warning.test.ts +277 -0
  279. package/src/tests/profile-sync.test.ts +232 -6
  280. package/src/tests/prompt-template-resolver.test.ts +177 -6
  281. package/src/tests/prompt-template-session.test.ts +39 -3
  282. package/src/tests/queue-stall-alarm.test.ts +226 -0
  283. package/src/tests/rbac-charact-slack.test.ts +29 -0
  284. package/src/tests/rbac-engine.test.ts +3 -0
  285. package/src/tests/requester-comms.test.ts +32 -0
  286. package/src/tests/runner-requester-profile.test.ts +38 -0
  287. package/src/tests/schedule-target-type.test.ts +38 -0
  288. package/src/tests/script-connections.test.ts +13 -1
  289. package/src/tests/scripts-runtime-identity.test.ts +375 -0
  290. package/src/tests/scripts-runtime.test.ts +4 -3
  291. package/src/tests/sdk-allowlist.test.ts +47 -0
  292. package/src/tests/send-task-requested-by.test.ts +9 -0
  293. package/src/tests/session-costs.test.ts +506 -1
  294. package/src/tests/shutdown-error-scrubbing.test.ts +92 -0
  295. package/src/tests/slack-channel-lifecycle.test.ts +241 -0
  296. package/src/tests/slack-render-v2.test.ts +43 -0
  297. package/src/tests/steering-core.test.ts +28 -0
  298. package/src/tests/swarm-config-reserved-keys.test.ts +22 -0
  299. package/src/tests/task-artifacts.test.ts +386 -0
  300. package/src/tests/task-attachments-schema.test.ts +17 -1
  301. package/src/tests/task-completion-idempotency.test.ts +57 -0
  302. package/src/tests/task-pull-request-backfill-migration.test.ts +321 -0
  303. package/src/tests/tool-annotations.test.ts +4 -0
  304. package/src/tests/update-profile-api.test.ts +67 -39
  305. package/src/tests/update-profile-auth.test.ts +48 -4
  306. package/src/tests/user-attribution-migration.test.ts +54 -0
  307. package/src/tests/user-identity.test.ts +72 -0
  308. package/src/tests/workflow-async-v2.test.ts +39 -1
  309. package/src/tests/workflow-http-v2.test.ts +3 -2
  310. package/src/tests/workflow-mcp-trigger-schema.test.ts +52 -3
  311. package/src/tests/workflow-retry-v2.test.ts +45 -1
  312. package/src/tools/db-query.ts +18 -8
  313. package/src/tools/get-task-details.ts +24 -3
  314. package/src/tools/manage-user.ts +36 -3
  315. package/src/tools/poll-task.ts +93 -14
  316. package/src/tools/script-common.ts +5 -0
  317. package/src/tools/send-task.ts +1 -4
  318. package/src/tools/slack-archive-channel.ts +70 -0
  319. package/src/tools/slack-create-channel.ts +74 -0
  320. package/src/tools/slack-invite-to-channel.ts +78 -0
  321. package/src/tools/store-progress.ts +13 -2
  322. package/src/tools/swarm-config/delete-config.ts +9 -0
  323. package/src/tools/swarm-config/set-config.ts +3 -2
  324. package/src/tools/task-action.ts +30 -0
  325. package/src/tools/task-tool-ctx.ts +8 -1
  326. package/src/tools/tool-config.ts +4 -1
  327. package/src/tools/update-profile.ts +16 -21
  328. package/src/tools/utils.ts +5 -0
  329. package/src/tools/workflows/trigger-workflow.ts +7 -1
  330. package/src/types.ts +165 -1
  331. package/src/utils/github-pull-request.ts +76 -0
  332. package/src/utils/identity-field-budget.ts +73 -0
  333. package/src/utils/multi-runtime.ts +20 -0
  334. package/src/utils/requester-comms.ts +24 -0
  335. package/src/workflows/engine.ts +22 -2
  336. package/src/workflows/resume.ts +19 -11
  337. package/src/workflows/retry-poller.ts +1 -0
  338. package/src/workflows/triggers.ts +4 -0
  339. package/templates/skills/db-query-guidance/config.json +14 -0
  340. package/templates/skills/db-query-guidance/content.md +57 -0
  341. package/dist/app-5da78bd4.js +0 -26
  342. package/dist/cli-3te4jdkm.js +0 -225
  343. package/dist/cli-bmze986d.js +0 -7
  344. package/dist/cli-kh3ba2zf.js +0 -607
  345. package/dist/hook-cgk4wxmx.js +0 -31
  346. package/dist/index-25wjsdv2.js +0 -34
  347. package/dist/index-76at2ymp.js +0 -7
  348. package/dist/index-hase6zbe.js +0 -6
  349. package/dist/index-vg4snay4.js +0 -36
  350. package/dist/index-vy8edt27.js +0 -7
  351. package/dist/lead-r2yqfmx7.js +0 -85
  352. package/dist/maintenance-mw70k468.js +0 -26
  353. package/dist/mistral-conversations-81fwav0j.js +0 -30578
  354. package/dist/otel-nqa12wf3.js +0 -3149
  355. package/dist/pricing-refresh-2cn6effr.js +0 -20
  356. package/dist/rbac-roles-vynday5g.js +0 -30
  357. package/dist/render-v2-tc0bh8kk.js +0 -34
  358. package/dist/seed-pricing-8kceb43f.js +0 -17
  359. package/dist/token-util-pqyc8kng.js +0 -6
  360. package/dist/worker-qrbz6hmv.js +0 -85
@@ -1,7 +1,12 @@
1
- import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
1
+ import { afterAll, beforeAll, beforeEach, describe, expect, spyOn, test } from "bun:test";
2
2
  import { unlink } from "node:fs/promises";
3
+ import type { IncomingMessage, ServerResponse } from "node:http";
4
+ import { Readable } from "node:stream";
3
5
  import { closeDb, getPromptTemplates, initDb, upsertPromptTemplate } from "../be/db";
4
6
  import { seedDefaultTemplates } from "../be/seed-prompt-templates";
7
+ import { handlePromptTemplates } from "../http/prompt-templates";
8
+ import { getPathSegments, parseQueryParams } from "../http/utils";
9
+ import { getPromptTemplateDefaultDrift } from "../prompts/default-drift";
5
10
  import {
6
11
  clearTemplateDefinitions,
7
12
  getAllTemplateDefinitions,
@@ -13,6 +18,37 @@ import { restoreAllTemplateDefinitions } from "./template-registry-helpers";
13
18
 
14
19
  const TEST_DB_PATH = "./test-prompt-resolver.sqlite";
15
20
 
21
+ async function dispatchPromptTemplates(path: string): Promise<unknown> {
22
+ const req = Readable.from([]) as IncomingMessage;
23
+ req.method = "GET";
24
+ req.url = path;
25
+
26
+ let body = "";
27
+ const res = {
28
+ headersSent: false,
29
+ writableEnded: false,
30
+ setHeader() {},
31
+ writeHead() {
32
+ this.headersSent = true;
33
+ return this;
34
+ },
35
+ end(chunk?: unknown) {
36
+ if (chunk !== undefined) body += String(chunk);
37
+ this.writableEnded = true;
38
+ return this;
39
+ },
40
+ } as unknown as ServerResponse;
41
+
42
+ const handled = await handlePromptTemplates(
43
+ req,
44
+ res,
45
+ getPathSegments(path),
46
+ parseQueryParams(path),
47
+ );
48
+ expect(handled).toBe(true);
49
+ return JSON.parse(body);
50
+ }
51
+
16
52
  describe("Prompt Template Resolver", () => {
17
53
  beforeAll(async () => {
18
54
  for (const suffix of ["", "-wal", "-shm"]) {
@@ -568,7 +604,8 @@ describe("Prompt Template Resolver", () => {
568
604
  expect(after[0].body).toBe("Updated default");
569
605
  });
570
606
 
571
- test("re-seeding does not touch user customizations (isDefault=false)", () => {
607
+ test("re-seeding does not touch user customizations (isDefault=false)", async () => {
608
+ const warn = spyOn(console, "warn").mockImplementation(() => {});
572
609
  registerTemplate({
573
610
  eventType: "seed.test.custom",
574
611
  header: "",
@@ -583,7 +620,7 @@ describe("Prompt Template Resolver", () => {
583
620
  upsertPromptTemplate({
584
621
  eventType: "seed.test.custom",
585
622
  scope: "global",
586
- body: "User custom body",
623
+ body: "Default body v1",
587
624
  changedBy: "user-123",
588
625
  });
589
626
 
@@ -594,14 +631,14 @@ describe("Prompt Template Resolver", () => {
594
631
  });
595
632
  expect(customized.length).toBe(1);
596
633
  expect(customized[0].isDefault).toBe(false);
597
- expect(customized[0].body).toBe("User custom body");
634
+ expect(customized[0].body).toBe("Default body v1");
598
635
 
599
636
  // Re-seed with updated code
600
637
  clearTemplateDefinitions();
601
638
  registerTemplate({
602
639
  eventType: "seed.test.custom",
603
640
  header: "",
604
- defaultBody: "Default body v2",
641
+ defaultBody: "Default body version 2",
605
642
  variables: [],
606
643
  category: "event",
607
644
  });
@@ -614,8 +651,142 @@ describe("Prompt Template Resolver", () => {
614
651
  scope: "global",
615
652
  });
616
653
  expect(afterReseed.length).toBe(1);
617
- expect(afterReseed[0].body).toBe("User custom body");
654
+ expect(afterReseed[0].body).toBe("Default body v1");
655
+ expect(afterReseed[0].isDefault).toBe(false);
656
+ expect(
657
+ getPromptTemplateDefaultDrift(afterReseed[0], "Default body version 2").defaultDrifted,
658
+ ).toBe(true);
659
+ const response = (await dispatchPromptTemplates(
660
+ "/api/prompt-templates?eventType=seed.test.custom",
661
+ )) as { templates: Array<{ defaultDrifted: boolean }> };
662
+ expect(response.templates).toHaveLength(1);
663
+ expect(response.templates[0]?.defaultDrifted).toBe(true);
664
+ expect(warn).toHaveBeenCalledTimes(1);
665
+ expect(warn.mock.calls[0]?.[0]).toContain('"seed.test.custom"');
666
+ expect(warn.mock.calls[0]?.[0]).toContain("frozen for 0 hours");
667
+ expect(warn.mock.calls[0]?.[0]).toContain(
668
+ "byte delta +7 (code default 22 bytes, customization 15 bytes)",
669
+ );
670
+ warn.mockRestore();
671
+ });
672
+
673
+ test("customized templates matching the code default self-heal as defaults", async () => {
674
+ const warn = spyOn(console, "warn").mockImplementation(() => {});
675
+ registerTemplate({
676
+ eventType: "seed.test.custom-matching",
677
+ header: "",
678
+ defaultBody: "Still current",
679
+ variables: [],
680
+ category: "event",
681
+ });
682
+
683
+ seedDefaultTemplates();
684
+ upsertPromptTemplate({
685
+ eventType: "seed.test.custom-matching",
686
+ scope: "global",
687
+ body: "Still current",
688
+ changedBy: "user-123",
689
+ });
690
+ seedDefaultTemplates();
691
+
692
+ const customized = getPromptTemplates({
693
+ eventType: "seed.test.custom-matching",
694
+ scope: "global",
695
+ });
696
+ expect(customized).toHaveLength(1);
697
+ expect(customized[0].isDefault).toBe(true);
698
+ expect(getPromptTemplateDefaultDrift(customized[0], "Still current").defaultDrifted).toBe(
699
+ false,
700
+ );
701
+ const response = (await dispatchPromptTemplates(
702
+ "/api/prompt-templates?eventType=seed.test.custom-matching",
703
+ )) as { templates: Array<{ defaultDrifted: boolean; isDefault: boolean }> };
704
+ expect(response.templates).toHaveLength(1);
705
+ expect(response.templates[0]?.defaultDrifted).toBe(false);
706
+ expect(response.templates[0]?.isDefault).toBe(true);
707
+
708
+ clearTemplateDefinitions();
709
+ registerTemplate({
710
+ eventType: "seed.test.custom-matching",
711
+ header: "",
712
+ defaultBody: "Next code default",
713
+ variables: [],
714
+ category: "event",
715
+ });
716
+ seedDefaultTemplates();
717
+ const reconciled = getPromptTemplates({
718
+ eventType: "seed.test.custom-matching",
719
+ scope: "global",
720
+ });
721
+ expect(reconciled).toHaveLength(1);
722
+ expect(reconciled[0].body).toBe("Next code default");
723
+ expect(reconciled[0].isDefault).toBe(true);
724
+ expect(warn).not.toHaveBeenCalled();
725
+ warn.mockRestore();
726
+ });
727
+
728
+ test("customized templates matching the code default preserve skip_event state", () => {
729
+ registerTemplate({
730
+ eventType: "seed.test.custom-matching-skipped",
731
+ header: "",
732
+ defaultBody: "Still current",
733
+ variables: [],
734
+ category: "event",
735
+ });
736
+
737
+ seedDefaultTemplates();
738
+ const skipped = upsertPromptTemplate({
739
+ eventType: "seed.test.custom-matching-skipped",
740
+ scope: "global",
741
+ state: "skip_event",
742
+ body: "Still current",
743
+ changedBy: "user-123",
744
+ });
745
+ expect(skipped.isDefault).toBe(false);
746
+ expect(skipped.state).toBe("skip_event");
747
+
748
+ seedDefaultTemplates();
749
+
750
+ const afterReseed = getPromptTemplates({
751
+ eventType: "seed.test.custom-matching-skipped",
752
+ scope: "global",
753
+ });
754
+ expect(afterReseed).toHaveLength(1);
755
+ expect(afterReseed[0].body).toBe("Still current");
756
+ expect(afterReseed[0].state).toBe("skip_event");
618
757
  expect(afterReseed[0].isDefault).toBe(false);
758
+ expect(afterReseed[0].version).toBe(skipped.version);
759
+ });
760
+
761
+ test("unchanged default templates remain defaults without warnings", () => {
762
+ const warn = spyOn(console, "warn").mockImplementation(() => {});
763
+ registerTemplate({
764
+ eventType: "seed.test.default-unchanged",
765
+ header: "",
766
+ defaultBody: "Current default",
767
+ variables: [],
768
+ category: "event",
769
+ });
770
+
771
+ seedDefaultTemplates();
772
+ const seeded = getPromptTemplates({
773
+ eventType: "seed.test.default-unchanged",
774
+ scope: "global",
775
+ });
776
+ expect(seeded).toHaveLength(1);
777
+ expect(seeded[0].body).toBe("Current default");
778
+ expect(seeded[0].isDefault).toBe(true);
779
+
780
+ seedDefaultTemplates();
781
+ const unchanged = getPromptTemplates({
782
+ eventType: "seed.test.default-unchanged",
783
+ scope: "global",
784
+ });
785
+ expect(unchanged).toHaveLength(1);
786
+ expect(unchanged[0].body).toBe("Current default");
787
+ expect(unchanged[0].isDefault).toBe(true);
788
+ expect(warn).not.toHaveBeenCalled();
789
+ warn.mockRestore();
619
790
  });
620
791
 
621
792
  test("seeding with no registered templates is a no-op", () => {
@@ -68,7 +68,7 @@ describe("Session templates — registration", () => {
68
68
  await ensureTemplatesRegistered();
69
69
  });
70
70
 
71
- test("all 18 system templates are registered", () => {
71
+ test("all 19 system templates are registered", () => {
72
72
  const systemTemplates = [
73
73
  "system.agent.role",
74
74
  "system.agent.register",
@@ -89,6 +89,7 @@ describe("Session templates — registration", () => {
89
89
  "system.agent.services",
90
90
  "system.agent.artifacts",
91
91
  "system.agent.share_urls",
92
+ "system.agent.communication_style",
92
93
  ];
93
94
 
94
95
  for (const eventType of systemTemplates) {
@@ -113,7 +114,7 @@ describe("Session templates — registration", () => {
113
114
  }
114
115
  });
115
116
 
116
- test("total of 31 session/system templates registered", () => {
117
+ test("total of 32 session/system templates registered", () => {
117
118
  const all = getAllTemplateDefinitions();
118
119
  const sessionSystem = all.filter((d) => d.category === "system" || d.category === "session");
119
120
  // 26 = the original 19 + `system.session.worker.pi` + `system.agent.seed_scripts`
@@ -124,7 +125,8 @@ describe("Session templates — registration", () => {
124
125
  // 30 = 29 + `system.agent.scheduling` (split out of context_mode so the
125
126
  // pi composites keep the scheduling rules without the ctx_* tool block).
126
127
  // 31 = 30 + `system.agent.apps`.
127
- expect(sessionSystem.length).toBe(31);
128
+ // 32 = 31 + `system.agent.communication_style`.
129
+ expect(sessionSystem.length).toBe(32);
128
130
  });
129
131
  });
130
132
 
@@ -221,8 +223,15 @@ describe("Session templates — individual resolution", () => {
221
223
 
222
224
  test("system.agent.worker contains worker tools", () => {
223
225
  const result = resolveTemplate("system.agent.worker", {});
226
+ expect(result.text).toContain("Skills (CHECK BEFORE STARTING WORK)");
227
+ expect(result.text).toContain("researching");
228
+ expect(result.text).toContain("planning");
229
+ expect(result.text).toContain("code-reviewing");
224
230
  expect(result.text).toContain("store-progress");
225
231
  expect(result.text).toContain("task-action");
232
+ expect(result.text).toContain("Credential Hygiene");
233
+ expect(result.text).not.toContain("Slack Thread Updates");
234
+ expect(result.text).not.toContain("On start");
226
235
  });
227
236
 
228
237
  test("system.agent.register contains join-swarm", () => {
@@ -332,6 +341,9 @@ describe("Session templates — composite resolution", () => {
332
341
  // Contains register section
333
342
  expect(result.text).toContain("join-swarm");
334
343
 
344
+ // Lead is hinted to persist learned requester comms preferences
345
+ expect(result.text).toContain("persist them with its `comms` field");
346
+
335
347
  // Contains lead-specific section (not worker)
336
348
  expect(result.text).toContain("CRITICAL: You are a coordinator");
337
349
  expect(result.text).toContain("coordinator");
@@ -351,6 +363,9 @@ describe("Session templates — composite resolution", () => {
351
363
 
352
364
  // Contains system
353
365
  expect(result.text).toContain("System packages available");
366
+
367
+ // Contains communication style
368
+ expect(result.text).toContain("### Communication Style");
354
369
  });
355
370
 
356
371
  test("system.session.worker resolves all template references", () => {
@@ -387,6 +402,27 @@ describe("Session templates — composite resolution", () => {
387
402
 
388
403
  // Contains system
389
404
  expect(result.text).toContain("System packages available");
405
+
406
+ // Contains communication style
407
+ expect(result.text).toContain("### Communication Style");
408
+ });
409
+
410
+ test("communication style block resolves in every session composite", () => {
411
+ const composites = [
412
+ "system.session.lead",
413
+ "system.session.worker",
414
+ "system.session.worker.pi",
415
+ "system.session.lead.pi",
416
+ "system.session.worker.remote",
417
+ ];
418
+
419
+ for (const eventType of composites) {
420
+ const result = resolveTemplate(eventType, { role: "worker", agentId: "agent-001" });
421
+ expect(result.skipped).toBe(false);
422
+ expect(result.text).toContain("### Communication Style");
423
+ expect(result.text).toContain("Mirror the requester");
424
+ expect(result.text).toContain("Never use em dashes");
425
+ }
390
426
  });
391
427
 
392
428
  test("composite does NOT include conditional sections (agent_fs, services, artifacts)", () => {
@@ -0,0 +1,226 @@
1
+ import { afterAll, beforeAll, beforeEach, describe, expect, mock, test } from "bun:test";
2
+ import { unlink } from "node:fs/promises";
3
+ import { closeDb, createAgent, createTaskExtended, getDb, initDb, updateTaskVcs } from "../be/db";
4
+ import {
5
+ _test,
6
+ checkQueueStall,
7
+ getQueueStallSnapshot,
8
+ isQueueStalled,
9
+ QUEUE_STALL_THRESHOLD_MS,
10
+ } from "../queue-stall-alarm";
11
+
12
+ const TEST_DB_PATH = "./test-queue-stall-alarm.sqlite";
13
+ const NOW = new Date("2026-08-17T17:00:00.000Z");
14
+
15
+ beforeAll(() => {
16
+ closeDb();
17
+ initDb(TEST_DB_PATH);
18
+ });
19
+
20
+ beforeEach(() => {
21
+ _test.resetState();
22
+ getDb().run("DELETE FROM agent_log");
23
+ getDb().run("DELETE FROM agent_tasks");
24
+ getDb().run("DELETE FROM agents");
25
+ });
26
+
27
+ afterAll(async () => {
28
+ _test.resetState();
29
+ closeDb();
30
+ await unlink(TEST_DB_PATH).catch(() => {});
31
+ await unlink(`${TEST_DB_PATH}-wal`).catch(() => {});
32
+ await unlink(`${TEST_DB_PATH}-shm`).catch(() => {});
33
+ });
34
+
35
+ function setCreatedAt(taskId: string, createdAt: string): void {
36
+ getDb().run("UPDATE agent_tasks SET createdAt = ?, lastUpdatedAt = ? WHERE id = ?", [
37
+ createdAt,
38
+ createdAt,
39
+ taskId,
40
+ ]);
41
+ getDb().run(
42
+ "UPDATE agent_log SET createdAt = ? WHERE taskId = ? AND eventType = 'task_created'",
43
+ [createdAt, taskId],
44
+ );
45
+ }
46
+
47
+ describe("queue stall alarm", () => {
48
+ test("positive control: alerts when a claimable task is older than 30 minutes", async () => {
49
+ const worker = createAgent({ name: "worker", isLead: false, status: "idle" });
50
+ const task = createTaskExtended("Old claimable task", { agentId: worker.id });
51
+ setCreatedAt(task.id, "2026-08-17T16:29:59.000Z");
52
+ const notify = mock(async (_message: string) => {});
53
+
54
+ const snapshot = await checkQueueStall(NOW, notify);
55
+
56
+ expect(isQueueStalled(snapshot)).toBe(true);
57
+ expect(snapshot.claimableCount).toBe(1);
58
+ expect(snapshot.oldestTaskId).toBe(task.id);
59
+ expect(snapshot.oldestAgeMs).toBeGreaterThan(QUEUE_STALL_THRESHOLD_MS);
60
+ expect(notify).toHaveBeenCalledTimes(1);
61
+ expect(notify.mock.calls[0]?.[0]).toContain("queue pickup stalled");
62
+ });
63
+
64
+ test("negative control: an empty queue never alerts", async () => {
65
+ const notify = mock(async (_message: string) => {});
66
+
67
+ const snapshot = await checkQueueStall(NOW, notify);
68
+
69
+ expect(snapshot.claimableCount).toBe(0);
70
+ expect(isQueueStalled(snapshot)).toBe(false);
71
+ expect(notify).not.toHaveBeenCalled();
72
+ });
73
+
74
+ test("does not alert for a non-empty but fresh queue", async () => {
75
+ const task = createTaskExtended("Fresh pool task");
76
+ setCreatedAt(task.id, "2026-08-17T16:45:00.000Z");
77
+ const notify = mock(async (_message: string) => {});
78
+
79
+ const snapshot = await checkQueueStall(NOW, notify);
80
+
81
+ expect(snapshot.claimableCount).toBe(1);
82
+ expect(isQueueStalled(snapshot)).toBe(false);
83
+ expect(notify).not.toHaveBeenCalled();
84
+ });
85
+
86
+ test("measures queue age from a backlog task becoming claimable", async () => {
87
+ const task = createTaskExtended("Released backlog task");
88
+ setCreatedAt(task.id, "2026-08-17T12:00:00.000Z");
89
+ getDb().run("UPDATE agent_tasks SET status = 'backlog' WHERE id = ?", [task.id]);
90
+ getDb().run("UPDATE agent_tasks SET status = 'unassigned', lastUpdatedAt = ? WHERE id = ?", [
91
+ "2026-08-17T16:59:00.000Z",
92
+ task.id,
93
+ ]);
94
+ getDb().run(
95
+ `INSERT INTO agent_log (id, eventType, taskId, oldValue, newValue, createdAt)
96
+ VALUES (?, 'task_status_change', ?, 'backlog', 'unassigned', ?)`,
97
+ [crypto.randomUUID(), task.id, "2026-08-17T16:59:00.000Z"],
98
+ );
99
+ const notify = mock(async (_message: string) => {});
100
+
101
+ const snapshot = await checkQueueStall(NOW, notify);
102
+
103
+ expect(snapshot.oldestAgeMs).toBe(60_000);
104
+ expect(isQueueStalled(snapshot)).toBe(false);
105
+ expect(notify).not.toHaveBeenCalled();
106
+ });
107
+
108
+ test("measures queue age from the final dependency completing", async () => {
109
+ const worker = createAgent({ name: "worker", isLead: false, status: "idle" });
110
+ const prerequisite = createTaskExtended("Prerequisite", { agentId: worker.id });
111
+ const blocked = createTaskExtended("Newly unblocked", {
112
+ agentId: worker.id,
113
+ dependsOn: [prerequisite.id],
114
+ });
115
+ setCreatedAt(blocked.id, "2026-08-17T12:00:00.000Z");
116
+ getDb().run(
117
+ "UPDATE agent_tasks SET status = 'completed', finishedAt = ?, lastUpdatedAt = ? WHERE id = ?",
118
+ ["2026-08-17T16:59:00.000Z", "2026-08-17T16:59:00.000Z", prerequisite.id],
119
+ );
120
+ const notify = mock(async (_message: string) => {});
121
+
122
+ const snapshot = await checkQueueStall(NOW, notify);
123
+
124
+ expect(snapshot.oldestTaskId).toBe(blocked.id);
125
+ expect(snapshot.oldestAgeMs).toBe(60_000);
126
+ expect(isQueueStalled(snapshot)).toBe(false);
127
+ expect(notify).not.toHaveBeenCalled();
128
+ });
129
+
130
+ test("does not reset queue age when VCS metadata updates a claimable task", () => {
131
+ const task = createTaskExtended("Old task with new VCS metadata");
132
+ setCreatedAt(task.id, "2026-08-17T16:00:00.000Z");
133
+
134
+ updateTaskVcs(task.id, {
135
+ vcsProvider: "github",
136
+ vcsRepo: "desplega-ai/agent-swarm",
137
+ vcsNumber: 1177,
138
+ vcsUrl: "https://github.com/desplega-ai/agent-swarm/pull/1177",
139
+ });
140
+
141
+ const snapshot = getQueueStallSnapshot(NOW);
142
+ expect(snapshot.oldestAgeMs).toBe(60 * 60 * 1000);
143
+ expect(isQueueStalled(snapshot)).toBe(true);
144
+ });
145
+
146
+ test("does not reset claimable age when VCS metadata updates a completed dependency", () => {
147
+ const worker = createAgent({ name: "worker", isLead: false, status: "idle" });
148
+ const prerequisite = createTaskExtended("Completed prerequisite", { agentId: worker.id });
149
+ const blocked = createTaskExtended("Waiting after prerequisite", {
150
+ agentId: worker.id,
151
+ dependsOn: [prerequisite.id],
152
+ });
153
+ setCreatedAt(blocked.id, "2026-08-17T12:00:00.000Z");
154
+ getDb().run(
155
+ "UPDATE agent_tasks SET status = 'completed', finishedAt = ?, lastUpdatedAt = ? WHERE id = ?",
156
+ ["2026-08-17T16:00:00.000Z", "2026-08-17T16:00:00.000Z", prerequisite.id],
157
+ );
158
+
159
+ updateTaskVcs(prerequisite.id, {
160
+ vcsProvider: "github",
161
+ vcsRepo: "desplega-ai/agent-swarm",
162
+ vcsNumber: 1177,
163
+ vcsUrl: "https://github.com/desplega-ai/agent-swarm/pull/1177",
164
+ });
165
+
166
+ const snapshot = getQueueStallSnapshot(NOW);
167
+ expect(snapshot.oldestTaskId).toBe(blocked.id);
168
+ expect(snapshot.oldestAgeMs).toBe(60 * 60 * 1000);
169
+ expect(isQueueStalled(snapshot)).toBe(true);
170
+ });
171
+
172
+ test("excludes pending tasks whose dependencies are not complete", () => {
173
+ const worker = createAgent({ name: "worker", isLead: false, status: "idle" });
174
+ const prerequisite = createTaskExtended("Prerequisite", { agentId: worker.id });
175
+ const blocked = createTaskExtended("Blocked", {
176
+ agentId: worker.id,
177
+ dependsOn: [prerequisite.id],
178
+ });
179
+ setCreatedAt(blocked.id, "2026-08-17T12:00:00.000Z");
180
+
181
+ const snapshot = getQueueStallSnapshot(NOW);
182
+
183
+ expect(snapshot.claimableCount).toBe(1);
184
+ expect(snapshot.oldestTaskId).toBe(prerequisite.id);
185
+ });
186
+
187
+ test("reports direct and pool pickup transitions as diagnostic context", () => {
188
+ const worker = createAgent({ name: "worker", isLead: false, status: "idle" });
189
+ const task = createTaskExtended("Queued", { agentId: worker.id });
190
+ getDb().run(
191
+ `INSERT INTO agent_log (id, eventType, taskId, agentId, oldValue, newValue, createdAt)
192
+ VALUES (?, 'task_status_change', ?, ?, 'pending', 'in_progress', ?)`,
193
+ [crypto.randomUUID(), task.id, worker.id, "2026-08-17T16:50:00.000Z"],
194
+ );
195
+ getDb().run(
196
+ `INSERT INTO agent_log (id, eventType, taskId, agentId, oldValue, newValue, createdAt)
197
+ VALUES (?, 'task_claimed', ?, ?, 'unassigned', 'in_progress', ?)`,
198
+ [crypto.randomUUID(), task.id, worker.id, "2026-08-17T16:55:00.000Z"],
199
+ );
200
+
201
+ expect(getQueueStallSnapshot(NOW).recentPickupCount).toBe(2);
202
+ });
203
+
204
+ test("scrubs secrets from notification failures before logging", () => {
205
+ const token = "ghp_1234567890abcdefABCDEF1234567890ABCD";
206
+
207
+ const output = _test.formatCheckFailure(new Error(`Slack rejected token ${token}`));
208
+
209
+ expect(output).not.toContain(token);
210
+ expect(output).toContain("[REDACTED:github_token]");
211
+ });
212
+
213
+ test("deduplicates an active alarm and sends recovery", async () => {
214
+ const task = createTaskExtended("Old pool task");
215
+ setCreatedAt(task.id, "2026-08-17T16:00:00.000Z");
216
+ const notify = mock(async (_message: string) => {});
217
+
218
+ await checkQueueStall(NOW, notify);
219
+ await checkQueueStall(NOW, notify);
220
+ getDb().run("UPDATE agent_tasks SET status = 'completed' WHERE id = ?", [task.id]);
221
+ await checkQueueStall(NOW, notify);
222
+
223
+ expect(notify).toHaveBeenCalledTimes(2);
224
+ expect(notify.mock.calls[1]?.[0]).toContain("queue pickup recovered");
225
+ });
226
+ });
@@ -28,6 +28,9 @@ import {
28
28
  registerRegisterKapsoNumberTool,
29
29
  registerUnregisterKapsoNumberTool,
30
30
  } from "../tools/register-kapso-number";
31
+ import { registerSlackArchiveChannelTool } from "../tools/slack-archive-channel";
32
+ import { registerSlackCreateChannelTool } from "../tools/slack-create-channel";
33
+ import { registerSlackInviteToChannelTool } from "../tools/slack-invite-to-channel";
31
34
  import { registerSlackPostTool } from "../tools/slack-post";
32
35
  import { registerSlackReadTool } from "../tools/slack-read";
33
36
  import { registerSlackStartThreadTool } from "../tools/slack-start-thread";
@@ -102,6 +105,9 @@ beforeAll(async () => {
102
105
  server = new McpServer({ name: "test-rbac-charact-slack", version: "1.0.0" });
103
106
  registerSlackPostTool(server);
104
107
  registerSlackStartThreadTool(server);
108
+ registerSlackCreateChannelTool(server);
109
+ registerSlackInviteToChannelTool(server);
110
+ registerSlackArchiveChannelTool(server);
105
111
  registerSlackReadTool(server);
106
112
  registerSlackUploadFileTool(server);
107
113
  registerDeleteChannelTool(server);
@@ -167,6 +173,29 @@ describe("slack tool gates (characterization)", () => {
167
173
  expect(result.structuredContent.message).not.toContain("requires lead privileges");
168
174
  });
169
175
 
176
+ test.each([
177
+ ["slack-create-channel", { name: "project-channel" }],
178
+ ["slack-invite-to-channel", { channelId: "C0CHARACT01", userIds: ["U0CHARACT01"] }],
179
+ ["slack-archive-channel", { channelId: "C0CHARACT01" }],
180
+ ])("worker cannot use %s", async (toolName, args) => {
181
+ const result = await callTool(toolName, WORKER_ID, args);
182
+
183
+ expect(result.structuredContent.success).toBe(false);
184
+ expect(result.structuredContent.message).toBe(
185
+ "Managing Slack channels requires lead privileges.",
186
+ );
187
+ });
188
+
189
+ test.each([
190
+ ["slack-create-channel", { name: "project-channel" }],
191
+ ["slack-invite-to-channel", { channelId: "C0CHARACT01", userIds: ["U0CHARACT01"] }],
192
+ ["slack-archive-channel", { channelId: "C0CHARACT01" }],
193
+ ])("lead %s is not blocked by the lead gate", async (toolName, args) => {
194
+ const result = await callTool(toolName, LEAD_ID, args);
195
+
196
+ expect(result.structuredContent.message).not.toContain("requires lead privileges");
197
+ });
198
+
170
199
  // slack-read.ts:146 — direct channel read requires lead
171
200
  test("worker cannot read a Slack channel directly", async () => {
172
201
  const result = await callTool("slack-read", WORKER_ID, {
@@ -50,6 +50,9 @@ const LEAD_ONLY_VERBS: PermissionVerb[] = [
50
50
  "integration.slack.post",
51
51
  "integration.slack.read",
52
52
  "integration.slack.thread.start",
53
+ "integration.slack.channel.create",
54
+ "integration.slack.channel.invite",
55
+ "integration.slack.channel.archive",
53
56
  "integration.slack.upload",
54
57
  "integration.slack.delete",
55
58
  "integration.slack.update",
@@ -0,0 +1,32 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { getUserCommsPrefs } from "../utils/requester-comms";
3
+
4
+ describe("getUserCommsPrefs", () => {
5
+ test("returns undefined for missing user or metadata", () => {
6
+ expect(getUserCommsPrefs(undefined)).toBeUndefined();
7
+ expect(getUserCommsPrefs({ metadata: undefined })).toBeUndefined();
8
+ expect(getUserCommsPrefs({ metadata: {} })).toBeUndefined();
9
+ });
10
+
11
+ test("returns undefined when comms is not an object", () => {
12
+ expect(getUserCommsPrefs({ metadata: { comms: "casual" } })).toBeUndefined();
13
+ expect(getUserCommsPrefs({ metadata: { comms: ["casual"] } })).toBeUndefined();
14
+ expect(getUserCommsPrefs({ metadata: { comms: null } })).toBeUndefined();
15
+ });
16
+
17
+ test("picks trimmed string fields and drops the rest", () => {
18
+ expect(
19
+ getUserCommsPrefs({
20
+ metadata: {
21
+ comms: { tone: " casual ", language: "uk", verbosity: 3, extra: "ignored" },
22
+ },
23
+ }),
24
+ ).toEqual({ tone: "casual", language: "uk", verbosity: undefined });
25
+ });
26
+
27
+ test("returns undefined when all comms fields are empty or non-string", () => {
28
+ expect(
29
+ getUserCommsPrefs({ metadata: { comms: { tone: " ", language: 7 } } }),
30
+ ).toBeUndefined();
31
+ });
32
+ });