@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
@@ -0,0 +1,216 @@
1
+ import { getDb } from "./be/db";
2
+ import { getSlackApp } from "./slack/app";
3
+ import { scrubSecrets } from "./utils/secret-scrubber";
4
+
5
+ export const QUEUE_STALL_THRESHOLD_MS = 30 * 60 * 1000;
6
+ export const QUEUE_STALL_CHECK_INTERVAL_MS = 5 * 60 * 1000;
7
+ const QUEUE_STALL_STARTUP_DELAY_MS = 10_000;
8
+
9
+ export interface QueueStallSnapshot {
10
+ claimableCount: number;
11
+ oldestTaskId: string | null;
12
+ oldestClaimableAt: string | null;
13
+ oldestAgeMs: number;
14
+ recentPickupCount: number;
15
+ }
16
+
17
+ type QueueStallNotifier = (message: string) => Promise<void>;
18
+
19
+ let alarmInterval: ReturnType<typeof setInterval> | null = null;
20
+ let startupTimeout: ReturnType<typeof setTimeout> | null = null;
21
+ let checkInFlight = false;
22
+ let alarmActive = false;
23
+
24
+ /**
25
+ * Read the queue denominator and oldest age in one snapshot. Tasks with unmet
26
+ * dependencies are excluded because a worker cannot start them yet.
27
+ */
28
+ export function getQueueStallSnapshot(now = new Date()): QueueStallSnapshot {
29
+ const oldest = getDb()
30
+ .prepare<
31
+ { claimableCount: number; oldestTaskId: string | null; oldestClaimableAt: string | null },
32
+ []
33
+ >(
34
+ `WITH queued AS (
35
+ SELECT
36
+ t.id,
37
+ t.dependsOn,
38
+ COALESCE(
39
+ (
40
+ SELECT MAX(entry.createdAt)
41
+ FROM agent_log entry
42
+ WHERE entry.taskId = t.id
43
+ AND entry.newValue = t.status
44
+ AND entry.eventType IN (
45
+ 'task_created',
46
+ 'task_status_change',
47
+ 'task_released',
48
+ 'task_accepted',
49
+ 'task_rejected'
50
+ )
51
+ ),
52
+ t.createdAt
53
+ ) AS queueEnteredAt
54
+ FROM agent_tasks t
55
+ WHERE t.status IN ('pending', 'unassigned')
56
+ ),
57
+ claimable AS (
58
+ SELECT
59
+ queued.id,
60
+ MAX(
61
+ queued.queueEnteredAt,
62
+ COALESCE(
63
+ (
64
+ SELECT MAX(COALESCE(prerequisite.finishedAt, prerequisite.createdAt))
65
+ FROM json_each(COALESCE(queued.dependsOn, '[]')) dep
66
+ JOIN agent_tasks prerequisite ON prerequisite.id = dep.value
67
+ ),
68
+ queued.queueEnteredAt
69
+ )
70
+ ) AS claimableAt
71
+ FROM queued
72
+ WHERE NOT EXISTS (
73
+ SELECT 1
74
+ FROM json_each(COALESCE(queued.dependsOn, '[]')) dep
75
+ LEFT JOIN agent_tasks prerequisite ON prerequisite.id = dep.value
76
+ WHERE prerequisite.id IS NULL OR prerequisite.status != 'completed'
77
+ )
78
+ ),
79
+ oldest AS (
80
+ SELECT id, claimableAt FROM claimable ORDER BY claimableAt ASC LIMIT 1
81
+ )
82
+ SELECT
83
+ (SELECT COUNT(*) FROM claimable) AS claimableCount,
84
+ (SELECT id FROM oldest) AS oldestTaskId,
85
+ (SELECT claimableAt FROM oldest) AS oldestClaimableAt`,
86
+ )
87
+ .get();
88
+
89
+ const pickupCutoff = new Date(now.getTime() - QUEUE_STALL_THRESHOLD_MS).toISOString();
90
+ const recentPickupCount =
91
+ getDb()
92
+ .prepare<{ count: number }, [string]>(
93
+ `SELECT COUNT(*) AS count
94
+ FROM agent_log
95
+ WHERE eventType IN ('task_status_change', 'task_claimed')
96
+ AND oldValue IN ('pending', 'unassigned')
97
+ AND newValue = 'in_progress'
98
+ AND createdAt >= ?`,
99
+ )
100
+ .get(pickupCutoff)?.count ?? 0;
101
+
102
+ const oldestClaimableAt = oldest?.oldestClaimableAt ?? null;
103
+ return {
104
+ claimableCount: oldest?.claimableCount ?? 0,
105
+ oldestTaskId: oldest?.oldestTaskId ?? null,
106
+ oldestClaimableAt,
107
+ oldestAgeMs: oldestClaimableAt
108
+ ? Math.max(0, now.getTime() - new Date(oldestClaimableAt).getTime())
109
+ : 0,
110
+ recentPickupCount,
111
+ };
112
+ }
113
+
114
+ export function isQueueStalled(snapshot: QueueStallSnapshot): boolean {
115
+ return snapshot.claimableCount > 0 && snapshot.oldestAgeMs >= QUEUE_STALL_THRESHOLD_MS;
116
+ }
117
+
118
+ function formatAge(ageMs: number): string {
119
+ return `${Math.floor(ageMs / 60_000)}m`;
120
+ }
121
+
122
+ function formatCheckFailure(error: unknown): string {
123
+ const message = error instanceof Error ? error.message : String(error);
124
+ return `[Queue Stall Alarm] Check or notification failed: ${scrubSecrets(message)}`;
125
+ }
126
+
127
+ async function notifySlack(message: string): Promise<void> {
128
+ const channel = process.env.SLACK_ALERTS_CHANNEL?.trim();
129
+ if (!channel) throw new Error("SLACK_ALERTS_CHANNEL is not configured");
130
+
131
+ const app = getSlackApp();
132
+ if (!app) throw new Error("Slack is not connected");
133
+
134
+ await app.client.chat.postMessage({
135
+ channel,
136
+ text: message,
137
+ unfurl_links: false,
138
+ unfurl_media: false,
139
+ });
140
+ }
141
+
142
+ /** Run one detector tick. Exported so tests and operators can exercise it directly. */
143
+ export async function checkQueueStall(
144
+ now = new Date(),
145
+ notify: QueueStallNotifier = notifySlack,
146
+ ): Promise<QueueStallSnapshot> {
147
+ const snapshot = getQueueStallSnapshot(now);
148
+ const stalled = isQueueStalled(snapshot);
149
+
150
+ if (stalled && !alarmActive) {
151
+ await notify(
152
+ `🚨 *Agent Swarm queue pickup stalled*\n` +
153
+ `Claimable tasks: *${snapshot.claimableCount}*\n` +
154
+ `Oldest wait: *${formatAge(snapshot.oldestAgeMs)}* (task \`${snapshot.oldestTaskId}\`)\n` +
155
+ `Pickups in the last 30m: *${snapshot.recentPickupCount}*\n\n` +
156
+ "This alarm runs in the API process and does not require an agent to claim a task.",
157
+ );
158
+ alarmActive = true;
159
+ console.error(
160
+ `[Queue Stall Alarm] Fired: claimable=${snapshot.claimableCount}, oldest=${snapshot.oldestTaskId}, age=${formatAge(snapshot.oldestAgeMs)}, recent_pickups=${snapshot.recentPickupCount}`,
161
+ );
162
+ } else if (!stalled && alarmActive) {
163
+ await notify(
164
+ `✅ *Agent Swarm queue pickup recovered*\nClaimable tasks: *${snapshot.claimableCount}*\nPickups in the last 30m: *${snapshot.recentPickupCount}*`,
165
+ );
166
+ alarmActive = false;
167
+ console.log("[Queue Stall Alarm] Recovery notification sent");
168
+ }
169
+
170
+ return snapshot;
171
+ }
172
+
173
+ function scheduleCheck(): void {
174
+ if (checkInFlight) return;
175
+ checkInFlight = true;
176
+ checkQueueStall()
177
+ .catch((error) => {
178
+ console.error(formatCheckFailure(error));
179
+ })
180
+ .finally(() => {
181
+ checkInFlight = false;
182
+ });
183
+ }
184
+
185
+ export function startQueueStallAlarm(): void {
186
+ if (alarmInterval || startupTimeout) return;
187
+
188
+ console.log(
189
+ `[Queue Stall Alarm] Starting (threshold ${formatAge(QUEUE_STALL_THRESHOLD_MS)}, interval ${formatAge(QUEUE_STALL_CHECK_INTERVAL_MS)})`,
190
+ );
191
+ startupTimeout = setTimeout(() => {
192
+ startupTimeout = null;
193
+ scheduleCheck();
194
+ }, QUEUE_STALL_STARTUP_DELAY_MS);
195
+ alarmInterval = setInterval(scheduleCheck, QUEUE_STALL_CHECK_INTERVAL_MS);
196
+ }
197
+
198
+ export function stopQueueStallAlarm(): void {
199
+ if (startupTimeout) {
200
+ clearTimeout(startupTimeout);
201
+ startupTimeout = null;
202
+ }
203
+ if (alarmInterval) {
204
+ clearInterval(alarmInterval);
205
+ alarmInterval = null;
206
+ }
207
+ checkInFlight = false;
208
+ }
209
+
210
+ export const _test = {
211
+ formatCheckFailure,
212
+ resetState(): void {
213
+ stopQueueStallAlarm();
214
+ alarmActive = false;
215
+ },
216
+ };
@@ -162,6 +162,9 @@ export const LEGACY_POLICY = {
162
162
  "integration.slack.post": leadOnly,
163
163
  "integration.slack.read": leadOnly,
164
164
  "integration.slack.thread.start": leadOnly,
165
+ "integration.slack.channel.create": leadOnly,
166
+ "integration.slack.channel.invite": leadOnly,
167
+ "integration.slack.channel.archive": leadOnly,
165
168
  "integration.slack.upload": leadOnly,
166
169
  "integration.slack.delete": leadOnly,
167
170
  "integration.slack.update": leadOnly,
@@ -93,6 +93,18 @@ export const PERMISSIONS = {
93
93
  description: "Start a thread in a direct Slack channel.",
94
94
  namespace: "integration",
95
95
  },
96
+ "integration.slack.channel.create": {
97
+ description: "Create a Slack channel.",
98
+ namespace: "integration",
99
+ },
100
+ "integration.slack.channel.invite": {
101
+ description: "Invite users to a Slack channel.",
102
+ namespace: "integration",
103
+ },
104
+ "integration.slack.channel.archive": {
105
+ description: "Archive a Slack channel.",
106
+ namespace: "integration",
107
+ },
96
108
  "integration.slack.upload": {
97
109
  description: "Upload a file to a direct Slack channel.",
98
110
  namespace: "integration",
@@ -4,6 +4,7 @@ import {
4
4
  getDb,
5
5
  getDueScheduledTasks,
6
6
  getScheduledTaskById,
7
+ getUserById,
7
8
  getWorkflow,
8
9
  updateScheduledTask,
9
10
  } from "@/be/db";
@@ -117,10 +118,16 @@ export interface DispatchScheduleResult {
117
118
  task?: AgentTask;
118
119
  }
119
120
 
121
+ function withoutDeletedRequester(schedule: ScheduledTask): ScheduledTask {
122
+ if (!schedule.createdBy || getUserById(schedule.createdBy)) return schedule;
123
+ return { ...schedule, createdBy: undefined };
124
+ }
125
+
120
126
  export async function dispatchScheduleTarget(
121
127
  schedule: ScheduledTask,
122
128
  extraTags: string[] = [],
123
129
  ): Promise<DispatchScheduleResult> {
130
+ schedule = withoutDeletedRequester(schedule);
124
131
  switch (schedule.targetType) {
125
132
  case "workflow": {
126
133
  if (!schedule.workflowId) {
@@ -1,6 +1,7 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import { mkdir, rm } from "node:fs/promises";
3
3
  import { resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
4
5
  import type { ScriptRun } from "../types";
5
6
  import {
6
7
  buildSandboxedCommand,
@@ -40,7 +41,7 @@ export interface ScriptExecutor {
40
41
 
41
42
  export function getScriptWorkflowHarnessPath(): string {
42
43
  const runtimeDir = process.env.SCRIPT_WORKFLOW_RUNTIME_DIR;
43
- if (!runtimeDir) return new URL("./harness.ts", import.meta.url).pathname;
44
+ if (!runtimeDir) return fileURLToPath(new URL("./harness.ts", import.meta.url));
44
45
 
45
46
  const bundledHarness = `${resolve(runtimeDir)}/harness.bundle.js`;
46
47
  if (!existsSync(bundledHarness)) {
@@ -1,3 +1,4 @@
1
+ import { fileURLToPath } from "node:url";
1
2
  import {
2
3
  buildSandboxedCommand,
3
4
  readStreamCapped,
@@ -120,7 +121,7 @@ export class NativeScriptExecutor implements ScriptExecutor {
120
121
  // subprocesses cannot access. Use the pre-built bundle from real filesystem instead.
121
122
  const harnessPath = process.env.SCRIPT_RUNTIME_DIR
122
123
  ? `${process.env.SCRIPT_RUNTIME_DIR}/eval-harness.bundle.js`
123
- : new URL("../eval-harness.ts", import.meta.url).pathname;
124
+ : fileURLToPath(new URL("../eval-harness.ts", import.meta.url));
124
125
  const controller = new AbortController();
125
126
  let timedOut = false;
126
127
  let killed = input.signal?.aborted ?? false;
@@ -10,6 +10,11 @@ export type SwarmConfigPayload = {
10
10
  apiKey: { value: string; isSecret: true };
11
11
  agentId: { value: string; isSecret: false };
12
12
  mcpBaseUrl: { value: string; isSecret: false };
13
+ /**
14
+ * Per-boot runtime identity of the invoking worker process. System
15
+ * context like the agent identity — scripts never supply or override it.
16
+ */
17
+ runtimeInstanceId?: { value: string; isSecret: false };
13
18
  };
14
19
  user: Record<string, { value: string; isSecret: boolean }>;
15
20
  egressSecrets?: EgressSecretEntry[];
@@ -19,6 +19,8 @@ export type RunScriptInput = {
19
19
  args?: unknown;
20
20
  fsMode?: ScriptFsMode;
21
21
  agentId: string;
22
+ /** Per-boot runtime identity of the invoking worker; system context, never script input. */
23
+ runtimeInstanceId?: string;
22
24
  signal?: AbortSignal;
23
25
  timeoutMs?: number;
24
26
  mcpBaseUrl?: string;
@@ -50,6 +52,9 @@ async function buildConfigPayload(input: RunScriptInput): Promise<SwarmConfigPay
50
52
  value: input.mcpBaseUrl ?? process.env.MCP_BASE_URL ?? "http://localhost:3013",
51
53
  isSecret: false,
52
54
  },
55
+ ...(input.runtimeInstanceId
56
+ ? { runtimeInstanceId: { value: input.runtimeInstanceId, isSecret: false as const } }
57
+ : {}),
53
58
  },
54
59
  user: input.userConfig ?? {},
55
60
  egressSecrets: input.egressSecrets ?? (await buildEgressSecrets()),
@@ -69,6 +69,9 @@ export const SDK_TOOL_NAME_MAP = {
69
69
  slack_post: "slack-post", // external: sends to Slack
70
70
  slack_reply: "slack-reply", // external: sends to Slack
71
71
  slack_startThread: "slack-start-thread", // external: sends to Slack
72
+ slack_createChannel: "slack-create-channel", // external: mutates Slack
73
+ slack_inviteToChannel: "slack-invite-to-channel", // external: mutates Slack
74
+ slack_archiveChannel: "slack-archive-channel", // external: mutates Slack
72
75
  slack_uploadFile: "slack-upload-file", // external: sends to Slack
73
76
  slack_downloadFile: "slack-download-file",
74
77
  slack_delete: "slack-delete", // external: mutates Slack, destructive
@@ -5,6 +5,8 @@ export class SwarmConfig {
5
5
  readonly apiKey: RedactedValue<string>;
6
6
  readonly agentId: RedactedValue<string>;
7
7
  readonly mcpBaseUrl: RedactedValue<string>;
8
+ /** Per-boot runtime identity of the invoking worker; system context, never script input. */
9
+ readonly runtimeInstanceId: RedactedValue<string> | undefined;
8
10
 
9
11
  private readonly userValues: Map<string, RedactedValue<string>>;
10
12
 
@@ -21,6 +23,12 @@ export class SwarmConfig {
21
23
  type: "system",
22
24
  isSecret: payload.system.mcpBaseUrl.isSecret,
23
25
  });
26
+ this.runtimeInstanceId = payload.system.runtimeInstanceId
27
+ ? Redacted.make(payload.system.runtimeInstanceId.value, {
28
+ type: "system",
29
+ isSecret: payload.system.runtimeInstanceId.isSecret,
30
+ })
31
+ : undefined;
24
32
  this.userValues = new Map(
25
33
  Object.entries(payload.user ?? {}).map(([key, value]) => [
26
34
  key,
@@ -14,6 +14,12 @@ function headers(config: SwarmConfig): Record<string, string> {
14
14
  return {
15
15
  Authorization: `Bearer ${Redacted.value(config.apiKey)}`,
16
16
  "X-Agent-ID": Redacted.value(config.agentId),
17
+ // Per-boot identity of the invoking worker process (system context, not
18
+ // script input) — work-acquisition endpoints gate on it in multi-runtime
19
+ // mode.
20
+ ...(config.runtimeInstanceId
21
+ ? { "X-Runtime-Instance-ID": Redacted.value(config.runtimeInstanceId) }
22
+ : {}),
17
23
  "Content-Type": "application/json",
18
24
  };
19
25
  }
@@ -168,6 +174,7 @@ function bridgeRequestFor(name: string, args: unknown): BridgeRequest | null {
168
174
  hideCompleted: body.hideCompleted,
169
175
  consecutiveErrorsMin: body.consecutiveErrorsMin,
170
176
  lastRunStatus: body.lastRunStatus,
177
+ fields: body.includeFull ? "full" : undefined,
171
178
  }),
172
179
  };
173
180
  case "schedule_create":
@@ -158,6 +158,7 @@ declare module "swarm-sdk" {
158
158
  hideCompleted?: boolean;
159
159
  consecutiveErrorsMin?: number;
160
160
  lastRunStatus?: "failed" | "succeeded";
161
+ includeFull?: boolean;
161
162
  }): Promise<unknown>;
162
163
  // --- scripts ---
163
164
  script_search(args: { query?: string; scope?: ScriptScope; limit?: number }): Promise<unknown>;
@@ -325,6 +326,9 @@ declare module "swarm-sdk" {
325
326
  message: string;
326
327
  blocks?: unknown;
327
328
  }): Promise<unknown>;
329
+ slack_createChannel(args: { name: string; isPrivate?: boolean }): Promise<unknown>;
330
+ slack_inviteToChannel(args: { channelId: string; userIds: string[] }): Promise<unknown>;
331
+ slack_archiveChannel(args: { channelId: string }): Promise<unknown>;
328
332
  slack_uploadFile(args: Record<string, unknown>): Promise<unknown>;
329
333
  slack_downloadFile(args: { url: string }): Promise<unknown>;
330
334
  slack_delete(args: { channelId: string; messageTs: string }): Promise<unknown>;
@@ -140,6 +140,7 @@ declare module "swarm-sdk" {
140
140
  hideCompleted?: boolean;
141
141
  consecutiveErrorsMin?: number;
142
142
  lastRunStatus?: "failed" | "succeeded";
143
+ includeFull?: boolean;
143
144
  }): Promise<unknown>;
144
145
  // --- scripts ---
145
146
  script_search(args: { query?: string; scope?: ScriptScope; limit?: number }): Promise<unknown>;
@@ -307,6 +308,9 @@ declare module "swarm-sdk" {
307
308
  message: string;
308
309
  blocks?: unknown;
309
310
  }): Promise<unknown>;
311
+ slack_createChannel(args: { name: string; isPrivate?: boolean }): Promise<unknown>;
312
+ slack_inviteToChannel(args: { channelId: string; userIds: string[] }): Promise<unknown>;
313
+ slack_archiveChannel(args: { channelId: string }): Promise<unknown>;
310
314
  slack_uploadFile(args: Record<string, unknown>): Promise<unknown>;
311
315
  slack_downloadFile(args: { url: string }): Promise<unknown>;
312
316
  slack_delete(args: { channelId: string; messageTs: string }): Promise<unknown>;
package/src/server.ts CHANGED
@@ -119,8 +119,11 @@ import {
119
119
  registerSkillUninstallTool,
120
120
  registerSkillUpdateTool,
121
121
  } from "./tools/skills";
122
+ import { registerSlackArchiveChannelTool } from "./tools/slack-archive-channel";
123
+ import { registerSlackCreateChannelTool } from "./tools/slack-create-channel";
122
124
  import { registerSlackDeleteTool } from "./tools/slack-delete";
123
125
  import { registerSlackDownloadFileTool } from "./tools/slack-download-file";
126
+ import { registerSlackInviteToChannelTool } from "./tools/slack-invite-to-channel";
124
127
  import { registerSlackListChannelsTool } from "./tools/slack-list-channels";
125
128
  import { registerSlackPostTool } from "./tools/slack-post";
126
129
  import { registerSlackReadTool } from "./tools/slack-read";
@@ -504,6 +507,9 @@ export function createServer(opts: { scriptsOnly?: boolean; fullSurface?: boolea
504
507
  registerSlackReadTool(server);
505
508
  registerSlackPostTool(server);
506
509
  registerSlackStartThreadTool(server);
510
+ registerSlackCreateChannelTool(server);
511
+ registerSlackInviteToChannelTool(server);
512
+ registerSlackArchiveChannelTool(server);
507
513
  registerSlackListChannelsTool(server);
508
514
  registerSlackUploadFileTool(server);
509
515
  registerSlackDownloadFileTool(server);
@@ -2,14 +2,33 @@ import type { WebClient } from "@slack/web-api";
2
2
 
3
3
  const logger = console;
4
4
 
5
+ type SlackErrorData = {
6
+ error?: unknown;
7
+ needed?: unknown;
8
+ };
9
+
5
10
  // @slack/web-api platform errors set message to "An API error occurred: <code>"
6
- // and store the raw Slack API code at error.data.error.
7
- function slackCode(error: unknown): string | undefined {
11
+ // and store Slack's full response body on error.data.
12
+ function slackErrorData(error: unknown): SlackErrorData | undefined {
8
13
  if (!(error instanceof Error)) return undefined;
9
- const d = (error as { data?: { error?: unknown } }).data;
14
+ return (error as { data?: SlackErrorData }).data;
15
+ }
16
+
17
+ export function slackCode(error: unknown): string | undefined {
18
+ const d = slackErrorData(error);
10
19
  return typeof d?.error === "string" ? d.error : undefined;
11
20
  }
12
21
 
22
+ export function slackMissingScopeMessage(error: unknown): string | undefined {
23
+ const data = slackErrorData(error);
24
+ if (data?.error !== "missing_scope" || typeof data.needed !== "string") return undefined;
25
+
26
+ const needed = data.needed.trim();
27
+ if (!needed) return undefined;
28
+
29
+ return `Slack requires the \`${needed}\` scope. Add it to slack-manifest.json, apply the updated manifest to the Slack app, then reinstall the app to the workspace for the change to take effect.`;
30
+ }
31
+
13
32
  /**
14
33
  * Returns true if the channel has any external (non-host-org) members.
15
34
  * Uses Slack's documented flags: is_ext_shared (accepted Connect) and
@@ -0,0 +1,114 @@
1
+ import type { WebClient } from "@slack/web-api";
2
+ import { slackCode } from "@/slack/channel-join";
3
+
4
+ export type CreateChannelResult = {
5
+ channelId: string;
6
+ name: string;
7
+ };
8
+
9
+ export type InviteToChannelResult = {
10
+ alreadyInChannel: boolean;
11
+ };
12
+
13
+ export type ArchiveChannelResult = {
14
+ alreadyArchived: boolean;
15
+ };
16
+
17
+ export function normalizeChannelName(name: string): string {
18
+ const normalized = name
19
+ .trim()
20
+ .toLowerCase()
21
+ .replace(/[^a-z0-9_-]+/g, "-")
22
+ .replace(/^[-_]+|[-_]+$/g, "")
23
+ .slice(0, 80)
24
+ .replace(/[-_]+$/g, "");
25
+
26
+ if (!normalized) {
27
+ throw new Error("Slack channel name must contain at least one letter or number.");
28
+ }
29
+
30
+ return normalized;
31
+ }
32
+
33
+ export async function createChannel(
34
+ client: WebClient,
35
+ { name, isPrivate = false }: { name: string; isPrivate?: boolean },
36
+ ): Promise<CreateChannelResult> {
37
+ const normalizedName = normalizeChannelName(name);
38
+
39
+ try {
40
+ const result = await client.conversations.create({
41
+ name: normalizedName,
42
+ is_private: isPrivate,
43
+ });
44
+ const channelId = result.channel?.id;
45
+ if (!channelId) {
46
+ throw new Error("Slack created the channel but did not return its ID.");
47
+ }
48
+
49
+ return { channelId, name: normalizedName };
50
+ } catch (error) {
51
+ if (slackCode(error) === "name_taken") {
52
+ throw new Error(`Slack channel name "${normalizedName}" is already taken.`);
53
+ }
54
+ throw error;
55
+ }
56
+ }
57
+
58
+ export async function inviteToChannel(
59
+ client: WebClient,
60
+ channelId: string,
61
+ userIds: string[],
62
+ ): Promise<InviteToChannelResult> {
63
+ if (userIds.length === 0) {
64
+ throw new Error("At least one Slack user ID is required.");
65
+ }
66
+
67
+ try {
68
+ await client.conversations.invite({
69
+ channel: channelId,
70
+ users: userIds.join(","),
71
+ });
72
+ return { alreadyInChannel: false };
73
+ } catch (error) {
74
+ if (slackCode(error) !== "already_in_channel") {
75
+ throw error;
76
+ }
77
+
78
+ let allAlreadyInChannel = true;
79
+ for (const userId of userIds) {
80
+ try {
81
+ await client.conversations.invite({ channel: channelId, users: userId });
82
+ allAlreadyInChannel = false;
83
+ } catch (userError) {
84
+ if (slackCode(userError) !== "already_in_channel") {
85
+ throw userError;
86
+ }
87
+ }
88
+ }
89
+
90
+ return { alreadyInChannel: allAlreadyInChannel };
91
+ }
92
+ }
93
+
94
+ export async function archiveChannel(
95
+ client: WebClient,
96
+ channelId: string,
97
+ ): Promise<ArchiveChannelResult> {
98
+ try {
99
+ await client.conversations.archive({ channel: channelId });
100
+ return { alreadyArchived: false };
101
+ } catch (error) {
102
+ const code = slackCode(error);
103
+ if (code === "already_archived") {
104
+ return { alreadyArchived: true };
105
+ }
106
+ if (code === "cant_archive_general") {
107
+ throw new Error("Slack's general channel cannot be archived.");
108
+ }
109
+ if (code === "cant_archive_required") {
110
+ throw new Error("This required Slack channel cannot be archived.");
111
+ }
112
+ throw error;
113
+ }
114
+ }
@@ -128,6 +128,7 @@ describe("Phase 3 — /api/poll budget admission gate", () => {
128
128
  if ("error" in body) throw new Error("unexpected error response");
129
129
  expect(body.trigger?.type).toBe("task_assigned");
130
130
  expect((body.trigger as { taskId: string }).taskId).toBe(task.id);
131
+ expect(getAgentById(worker.id)?.status).toBe("busy");
131
132
  });
132
133
 
133
134
  test("pending task trigger includes requester role and notes", async () => {
@@ -272,6 +273,25 @@ describe("Phase 3 — /api/poll budget admission gate", () => {
272
273
  expect(claimedTriggers).toHaveLength(1);
273
274
  expect(nullTriggers).toHaveLength(1);
274
275
  });
276
+
277
+ test("pool claim marks the agent busy before the poll transaction returns", async () => {
278
+ const worker = createAgent({
279
+ name: "claim-status",
280
+ isLead: false,
281
+ status: "idle",
282
+ maxTasks: 1,
283
+ });
284
+ const pooled = createTaskExtended("claim status task", {});
285
+
286
+ expect(getAgentById(worker.id)?.status).toBe("idle");
287
+
288
+ const { body } = await callPoll(worker.id);
289
+
290
+ if ("error" in body) throw new Error("unexpected error response");
291
+ expect(body.trigger?.type).toBe("task_assigned");
292
+ expect((body.trigger as { taskId: string }).taskId).toBe(pooled.id);
293
+ expect(getAgentById(worker.id)?.status).toBe("busy");
294
+ });
275
295
  });
276
296
 
277
297
  // ─── MCP task-action accept tests ───────────────────────────────────────────
@@ -687,6 +687,38 @@ describe("mergeMcpConfig (issue #369)", () => {
687
687
  expect((entry.headers as Record<string, string>)["X-Source-Task-Id"]).toBe(TASK_ID);
688
688
  });
689
689
 
690
+ test("agent-swarm server gets X-Runtime-Instance-ID when a runtime identity is given", () => {
691
+ const base = {
692
+ mcpServers: {
693
+ "agent-swarm": {
694
+ type: "http",
695
+ url: "http://localhost:3013/mcp",
696
+ headers: { Authorization: "Bearer KEY", "X-Agent-ID": "a1" },
697
+ },
698
+ },
699
+ };
700
+ const merged = mergeMcpConfig(base, null, TASK_ID, undefined, "runtime-boot-1");
701
+ const agentSwarm = merged.mcpServers["agent-swarm"] as Record<string, unknown>;
702
+ expect((agentSwarm.headers as Record<string, string>)["X-Runtime-Instance-ID"]).toBe(
703
+ "runtime-boot-1",
704
+ );
705
+ });
706
+
707
+ test("no runtime identity → no X-Runtime-Instance-ID header", () => {
708
+ const base = {
709
+ mcpServers: {
710
+ "agent-swarm": {
711
+ type: "http",
712
+ url: "http://localhost:3013/mcp",
713
+ headers: { Authorization: "Bearer KEY", "X-Agent-ID": "a1" },
714
+ },
715
+ },
716
+ };
717
+ const merged = mergeMcpConfig(base, null, TASK_ID);
718
+ const agentSwarm = merged.mcpServers["agent-swarm"] as Record<string, unknown>;
719
+ expect((agentSwarm.headers as Record<string, string>)["X-Runtime-Instance-ID"]).toBeUndefined();
720
+ });
721
+
690
722
  test("does not mutate the input baseConfig", () => {
691
723
  const base = {
692
724
  mcpServers: {