@desplega.ai/agent-swarm 1.150.0 → 1.152.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 (311) hide show
  1. package/README.md +11 -0
  2. package/dist/{acp-adapter-d5mzggyk.js → acp-adapter-d05sg0q4.js} +5 -5
  3. package/dist/{actions-wn6ew0p7.js → actions-4sc0v342.js} +38 -11
  4. package/dist/{anthropic-messages-ec35ktvx.js → anthropic-messages-2y8nktar.js} +16 -16
  5. package/dist/{app-dty7p9hg.js → app-2d0v6kt6.js} +7 -7
  6. package/dist/{assistant-q4vhgjtx.js → assistant-zs6wdnrf.js} +41 -16
  7. package/dist/{azure-openai-responses-g85jv7m8.js → azure-openai-responses-f38x2avq.js} +13 -13
  8. package/dist/{boot-reembed-n15q6s9x.js → boot-reembed-3dwdnnsr.js} +13 -15
  9. package/dist/{boot-reembed-92292pgf.js → boot-reembed-vhh33520.js} +8 -8
  10. package/dist/{boot-scrub-logs-44pc955m.js → boot-scrub-logs-e1fnabcg.js} +5 -5
  11. package/dist/{claude-adapter-cjegjbje.js → claude-adapter-h58xqzzd.js} +12 -13
  12. package/dist/{claude-managed-adapter-p3ts5m9m.js → claude-managed-adapter-dsmkcrjy.js} +3 -3
  13. package/dist/{claude-sdk-session-7ywbn12v.js → claude-sdk-session-pbaczbgt.js} +12 -13
  14. package/dist/{cli-9gqddmxs.js → cli-4fqn2vpg.js} +8 -8
  15. package/dist/{cli-mzb0n642.js → cli-4wppfkcm.js} +350 -20
  16. package/dist/{cli-h505yhex.js → cli-5bqt4gwa.js} +2 -2
  17. package/dist/{cli-hbagregr.js → cli-5p007m2q.js} +88 -13
  18. package/dist/{cli-3ckzxdt1.js → cli-6k2athpf.js} +94 -81
  19. package/dist/{cli-3mh119qv.js → cli-72vksk1n.js} +9855 -693
  20. package/dist/{cli-taym3dmg.js → cli-87hdfyde.js} +13 -13
  21. package/dist/{cli-zthatyrx.js → cli-8fy9dmpv.js} +5 -5
  22. package/dist/{cli-asvrh7kj.js → cli-9xvdm3bx.js} +1 -1
  23. package/dist/{cli-72xdszyx.js → cli-awb59f08.js} +2 -2
  24. package/dist/{cli-vr6qj104.js → cli-cs9z1hw3.js} +1 -1
  25. package/dist/{cli-kt7wevqh.js → cli-cwvey7n1.js} +164 -56
  26. package/dist/{cli-5my3bjsd.js → cli-d3p82erb.js} +7 -7
  27. package/dist/{cli-7kwmt3r5.js → cli-evgy5w3y.js} +9 -3
  28. package/dist/{cli-111ey3ra.js → cli-f1f9n77e.js} +5 -5
  29. package/dist/{cli-ttje5a4z.js → cli-fhdssm2v.js} +105 -56
  30. package/dist/{cli-a45d5v9b.js → cli-fm4ytsdk.js} +2 -2
  31. package/dist/{cli-qp69dd5d.js → cli-gacq5hg8.js} +116 -14
  32. package/dist/{cli-hmyynq9b.js → cli-ht5rs63n.js} +1 -1
  33. package/dist/{cli-dyx0237s.js → cli-j4nv2wqy.js} +2 -4
  34. package/dist/{cli-gm0fhz13.js → cli-jh9txj4n.js} +2 -1
  35. package/dist/{cli-8chp8xab.js → cli-nd5s3m4y.js} +2 -2
  36. package/dist/{cli-zd21jrrc.js → cli-p2hx1pxm.js} +9872 -424
  37. package/dist/cli-pf9q3696.js +172 -0
  38. package/dist/{cli-7fesa1ky.js → cli-pgj4a5fb.js} +1 -1
  39. package/dist/{cli-07jd68a1.js → cli-re8z5p8p.js} +1 -1
  40. package/dist/{cli-ryz8q8p1.js → cli-t1mykkng.js} +40 -40
  41. package/dist/{cli-ck1vqzdj.js → cli-t6axtdcg.js} +9 -4
  42. package/dist/{cli-1mx65de9.js → cli-wbyk465z.js} +1 -1
  43. package/dist/{cli-kyysm7m3.js → cli-wp7cejrg.js} +1 -1
  44. package/dist/{cli-n75jt793.js → cli-z61ykvam.js} +6 -5
  45. package/dist/{cli-ceb5y160.js → cli-zdcdh02m.js} +1 -1
  46. package/dist/{cli-cmp8g3sw.js → cli-zfzznn7c.js} +45 -9
  47. package/dist/cli.js +22 -21
  48. package/dist/{codex-adapter-rn3aq7jm.js → codex-adapter-v3cz462q.js} +8 -9
  49. package/dist/{codex-hook-gthcmm0g.js → codex-hook-8jexc34x.js} +2 -2
  50. package/dist/{codex-session-runner-bge0cbsw.js → codex-session-runner-de1a5g5z.js} +8 -9
  51. package/dist/{commands-26phw0pc.js → commands-wfqqpn95.js} +5 -5
  52. package/dist/{db-jxjx6s1b.js → db-9zzv5y05.js} +5 -5
  53. package/dist/dispatcher-hfm1jbgx.js +74 -0
  54. package/dist/{google-generative-ai-tsskx7te.js → google-generative-ai-ndjmj5fa.js} +7 -7
  55. package/dist/{google-vertex-pkhpkbq9.js → google-vertex-fr665tqs.js} +10 -10
  56. package/dist/handlers-wrrhsack.js +65 -0
  57. package/dist/{hook-jw0ztn96.js → hook-9skye8zd.js} +9 -10
  58. package/dist/{hook-t6fqsggw.js → hook-fgjv62k5.js} +9 -10
  59. package/dist/{http-hyc7s57s.js → http-7grttjfq.js} +1434 -579
  60. package/dist/{index-bcyx5djw.js → index-29gxx9ar.js} +25 -17
  61. package/dist/{index-wtwpthe1.js → index-30pz2h9w.js} +25 -19
  62. package/dist/{index-1d9r6a74.js → index-4ek1df87.js} +26 -20
  63. package/dist/{index-sfhaq59d.js → index-myfwqqyq.js} +120 -102
  64. package/dist/index-pfhzrh1d.js +25 -0
  65. package/dist/{keepalive-fbsak4ya.js → keepalive-j75m2m7k.js} +7 -7
  66. package/dist/{lead-rdzdm627.js → lead-a1hpjt0a.js} +41 -49
  67. package/dist/{maintenance-25z1c1vf.js → maintenance-tngjzqdq.js} +8 -8
  68. package/dist/{node-209sxmg3.js → node-x1g4v0pq.js} +9 -9
  69. package/dist/{oauth-refresh-sweep-j3d7gtse.js → oauth-refresh-sweep-em1fn044.js} +6 -6
  70. package/dist/{onboard-dkcwrc75.js → onboard-fv7ezvwx.js} +13 -4
  71. package/dist/{openai-codex-responses-pm5gnyfa.js → openai-codex-responses-wy6a43wd.js} +11 -11
  72. package/dist/{openai-responses-vwvk1taj.js → openai-responses-6xwf7bzg.js} +13 -13
  73. package/dist/{opencode-adapter-dce4wskd.js → opencode-adapter-28axqa09.js} +4 -4
  74. package/dist/{otel-impl-8rgyy3j8.js → otel-impl-6vbb568y.js} +1 -1
  75. package/dist/{pi-mono-adapter-4s2bbkgq.js → pi-mono-adapter-jcqt71cg.js} +14 -16
  76. package/dist/{pricing-refresh-9cek71nq.js → pricing-refresh-nmm949an.js} +7 -7
  77. package/dist/{rbac-roles-vk8mqp9v.js → rbac-roles-0fz5qryx.js} +7 -7
  78. package/dist/{rbac-roles-z98s2ypy.js → rbac-roles-xtp05syf.js} +13 -9
  79. package/dist/{render-v2-3j26g0qh.js → render-v2-fpb8whme.js} +7 -7
  80. package/dist/{seed-pricing-7rxhcnzm.js → seed-pricing-zwa8bs3t.js} +6 -6
  81. package/dist/{setup-e2t7vz0c.js → setup-3ybwbdpd.js} +2 -2
  82. package/dist/{work-objects-8d3nd3x2.js → work-objects-rmayxcx7.js} +41 -16
  83. package/dist/{worker-0x9fn7nq.js → worker-rng4jhnh.js} +41 -49
  84. package/openapi.json +1351 -265
  85. package/package.json +2 -1
  86. package/src/agentmail/handlers.ts +95 -71
  87. package/src/be/db-client.ts +5 -0
  88. package/src/be/db.ts +12 -4
  89. package/src/be/extensions/db.ts +461 -0
  90. package/src/be/extensions/validate.ts +107 -0
  91. package/src/be/memory/boot-reembed.ts +17 -12
  92. package/src/be/memory/index.ts +12 -0
  93. package/src/be/memory/providers/openai-embedding.ts +4 -0
  94. package/src/be/memory/types.ts +2 -0
  95. package/src/be/migrations/155_extensions.sql +66 -0
  96. package/src/be/migrations/156_inbox_item_state_read_at.sql +7 -0
  97. package/src/be/rbac-roles.ts +3 -2
  98. package/src/be/scripts/typecheck.ts +56 -38
  99. package/src/be/seed-skills/index.ts +7 -0
  100. package/src/be/swarm-config-guard.ts +10 -2
  101. package/src/commands/onboard/compose-generator.ts +14 -2
  102. package/src/commands/onboard/env-generator.ts +7 -0
  103. package/src/commands/runner.ts +1 -1
  104. package/src/extensions/apply-task-create.ts +135 -0
  105. package/src/extensions/bundle-path.ts +10 -0
  106. package/src/extensions/contract-runtime.ts +7 -0
  107. package/src/extensions/contract-types.generated.ts +4 -0
  108. package/src/extensions/contract.ts +270 -0
  109. package/src/extensions/ctx.ts +186 -0
  110. package/src/extensions/dispatcher.ts +479 -0
  111. package/src/extensions/identity.ts +44 -0
  112. package/src/extensions/lifecycle.ts +303 -0
  113. package/src/extensions/loader.ts +207 -0
  114. package/src/extensions/post-bridge.ts +161 -0
  115. package/src/github/handlers.ts +185 -149
  116. package/src/gitlab/handlers.ts +97 -81
  117. package/src/heartbeat/heartbeat.ts +152 -83
  118. package/src/http/agents.ts +1 -1
  119. package/src/http/all-routes.ts +1 -0
  120. package/src/http/apps.ts +14 -10
  121. package/src/http/core.ts +34 -0
  122. package/src/http/extensions.ts +552 -0
  123. package/src/http/inbox-state.ts +2 -0
  124. package/src/http/index.ts +25 -0
  125. package/src/http/mcp-bridge.ts +96 -62
  126. package/src/http/mcp.ts +1 -1
  127. package/src/http/status.ts +6 -4
  128. package/src/http/tasks.ts +51 -35
  129. package/src/integrations/kapso/inbound.ts +15 -11
  130. package/src/jira/sync.ts +26 -18
  131. package/src/linear/sync.ts +26 -18
  132. package/src/prompts/session-templates.ts +5 -4
  133. package/src/rbac/admission.ts +1 -1
  134. package/src/rbac/elevated-agents.ts +24 -0
  135. package/src/rbac/legacy-policy.ts +47 -6
  136. package/src/rbac/permissions.ts +8 -0
  137. package/src/scheduler/deferred-task-waits.ts +133 -51
  138. package/src/scheduler/schedule-task.ts +50 -24
  139. package/src/scheduler/scheduler.ts +4 -2
  140. package/src/scripts-runtime/executors/native.ts +5 -1
  141. package/src/scripts-runtime/executors/types.ts +5 -0
  142. package/src/scripts-runtime/import-allowlist.ts +79 -19
  143. package/src/scripts-runtime/sdk-allowlist.ts +8 -0
  144. package/src/scripts-runtime/swarm-config.ts +8 -0
  145. package/src/scripts-runtime/swarm-sdk.ts +3 -0
  146. package/src/scripts-runtime/types/stdlib.d.ts +13 -0
  147. package/src/scripts-runtime/types/swarm-extension.d.ts +562 -0
  148. package/src/scripts-runtime/types/swarm-sdk.d.ts +13 -0
  149. package/src/server.ts +18 -0
  150. package/src/slack/HEURISTICS.md +1 -1
  151. package/src/slack/actions.ts +21 -17
  152. package/src/slack/blocks.ts +1 -1
  153. package/src/slack/handlers.ts +98 -5
  154. package/src/slack/inbound-files.ts +6 -2
  155. package/src/slack/render-v2.ts +181 -39
  156. package/src/slack/responses.ts +7 -4
  157. package/src/slack/router.ts +13 -3
  158. package/src/slack/steering.ts +3 -3
  159. package/src/slack/task-output.ts +21 -0
  160. package/src/slack/watcher.ts +3 -2
  161. package/src/slack/work-objects.ts +2 -1
  162. package/src/tasks/errors.ts +9 -0
  163. package/src/tasks/sibling-awareness.ts +35 -1
  164. package/src/tasks/worker-follow-up.ts +64 -12
  165. package/src/tests/base-prompt.test.ts +1 -0
  166. package/src/tests/deferred-task-wake.test.ts +161 -9
  167. package/src/tests/docs-index-footprint.test.js +3 -3
  168. package/src/tests/env-flag.test.ts +1 -0
  169. package/src/tests/extensions-db.test.ts +216 -0
  170. package/src/tests/extensions-dispatcher.test.ts +275 -0
  171. package/src/tests/extensions-example-heartbeat-remediate.test.ts +69 -0
  172. package/src/tests/extensions-example-mcp-install.test.ts +63 -0
  173. package/src/tests/extensions-example-no-exclamation-marks.test.ts +72 -0
  174. package/src/tests/extensions-example-rewrite-and-suppress.test.ts +70 -0
  175. package/src/tests/extensions-example-route-slack-channel.test.ts +217 -0
  176. package/src/tests/extensions-heartbeat.test.ts +291 -0
  177. package/src/tests/extensions-http.test.ts +512 -0
  178. package/src/tests/extensions-lead-equivalence.test.ts +70 -0
  179. package/src/tests/extensions-lifecycle.test.ts +209 -0
  180. package/src/tests/extensions-loader.test.ts +95 -0
  181. package/src/tests/extensions-mcp-tools.test.ts +353 -0
  182. package/src/tests/extensions-pre-task.test.ts +487 -0
  183. package/src/tests/extensions-run-context.test.ts +41 -0
  184. package/src/tests/extensions-tool-call.test.ts +288 -0
  185. package/src/tests/extensions-validate.test.ts +196 -0
  186. package/src/tests/fixtures/extensions/bad-import/hooks.ts +8 -0
  187. package/src/tests/fixtures/extensions/bad-import/manifest.json +9 -0
  188. package/src/tests/fixtures/extensions/bad-return-shape/hooks.ts +10 -0
  189. package/src/tests/fixtures/extensions/bad-return-shape/manifest.json +9 -0
  190. package/src/tests/fixtures/extensions/block-tasks-from-source/hooks.ts +22 -0
  191. package/src/tests/fixtures/extensions/block-tasks-from-source/manifest.json +9 -0
  192. package/src/tests/fixtures/extensions/ignore-channel/hooks.ts +22 -0
  193. package/src/tests/fixtures/extensions/ignore-channel/manifest.json +9 -0
  194. package/src/tests/fixtures/extensions/load.ts +10 -0
  195. package/src/tests/fixtures/extensions/minimal/hooks.ts +7 -0
  196. package/src/tests/fixtures/extensions/minimal/manifest.json +9 -0
  197. package/src/tests/fixtures/extensions/never-fail-long-tasks/hooks.ts +11 -0
  198. package/src/tests/fixtures/extensions/never-fail-long-tasks/manifest.json +9 -0
  199. package/src/tests/fixtures/extensions/no-exclamation-marks/hooks.ts +23 -0
  200. package/src/tests/fixtures/extensions/no-exclamation-marks/manifest.json +9 -0
  201. package/src/tests/fixtures/extensions/post-logger/hooks.ts +9 -0
  202. package/src/tests/fixtures/extensions/post-logger/manifest.json +9 -0
  203. package/src/tests/fixtures/extensions/priority-a/hooks.ts +7 -0
  204. package/src/tests/fixtures/extensions/priority-a/manifest.json +9 -0
  205. package/src/tests/fixtures/extensions/priority-b/hooks.ts +7 -0
  206. package/src/tests/fixtures/extensions/priority-b/manifest.json +9 -0
  207. package/src/tests/fixtures/extensions/record-only-on-tag/hooks.ts +16 -0
  208. package/src/tests/fixtures/extensions/record-only-on-tag/manifest.json +9 -0
  209. package/src/tests/fixtures/extensions/reserved-assets/hooks.ts +5 -0
  210. package/src/tests/fixtures/extensions/reserved-assets/manifest.json +10 -0
  211. package/src/tests/fixtures/extensions/rewrite-progress-prefix/hooks.ts +22 -0
  212. package/src/tests/fixtures/extensions/rewrite-progress-prefix/manifest.json +9 -0
  213. package/src/tests/fixtures/extensions/rewrite-task-priority/hooks.ts +7 -0
  214. package/src/tests/fixtures/extensions/rewrite-task-priority/manifest.json +9 -0
  215. package/src/tests/fixtures/extensions/route-channel-to-agent/hooks.ts +26 -0
  216. package/src/tests/fixtures/extensions/route-channel-to-agent/manifest.json +9 -0
  217. package/src/tests/fixtures/extensions/slow/hooks.ts +10 -0
  218. package/src/tests/fixtures/extensions/slow/manifest.json +9 -0
  219. package/src/tests/fixtures/extensions/suppress-lead-follow-up/hooks.ts +12 -0
  220. package/src/tests/fixtures/extensions/suppress-lead-follow-up/manifest.json +9 -0
  221. package/src/tests/fixtures/extensions/throws/hooks.ts +12 -0
  222. package/src/tests/fixtures/extensions/throws/manifest.json +9 -0
  223. package/src/tests/fixtures/extensions/worker-runtime/hooks.ts +5 -0
  224. package/src/tests/fixtures/extensions/worker-runtime/manifest.json +9 -0
  225. package/src/tests/fixtures/store-progress-malformed-output.json +4 -0
  226. package/src/tests/memory-edit-authorization.test.ts +32 -0
  227. package/src/tests/multi-runtime-enabled-stats.test.ts +2 -2
  228. package/src/tests/multi-runtime-registration.test.ts +14 -14
  229. package/src/tests/onboard-compose.test.ts +44 -0
  230. package/src/tests/preload.ts +5 -0
  231. package/src/tests/rbac-admission-e2e.test.ts +35 -4
  232. package/src/tests/rbac-admission.test.ts +18 -3
  233. package/src/tests/rbac-e2e-helpers.ts +18 -0
  234. package/src/tests/rbac-engine.test.ts +39 -0
  235. package/src/tests/rbac-mcp-admission-e2e.test.ts +22 -4
  236. package/src/tests/realtime-room-auth.test.ts +1 -1
  237. package/src/tests/reload-config.test.ts +61 -0
  238. package/src/tests/routing-affinity-inventory.test.ts +1 -1
  239. package/src/tests/script-connections.test.ts +2 -0
  240. package/src/tests/scripts-boot-reembed.test.ts +4 -0
  241. package/src/tests/scripts-embeddings.test.ts +4 -0
  242. package/src/tests/scripts-import-allowlist.test.ts +44 -1
  243. package/src/tests/scripts-mcp-e2e.test.ts +80 -0
  244. package/src/tests/scripts-only-gating.test.ts +6 -0
  245. package/src/tests/scripts-runtime-identity.test.ts +1 -1
  246. package/src/tests/slack-blocks.test.ts +12 -1
  247. package/src/tests/slack-render-v2.test.ts +100 -5
  248. package/src/tests/slack-router.test.ts +17 -2
  249. package/src/tests/slack-steering.test.ts +19 -0
  250. package/src/tests/slack-task-output.test.ts +36 -0
  251. package/src/tests/status.test.ts +12 -0
  252. package/src/tests/steering-enabled.test.ts +6 -8
  253. package/src/tests/store-progress-attachments-handler.test.ts +100 -1
  254. package/src/tests/swarm-config-reserved-keys.test.ts +6 -1
  255. package/src/tests/task-tool-preload-mcp.test.ts +4 -1
  256. package/src/tests/tool-annotations.test.ts +1 -1
  257. package/src/tests/tool-registrar-no-input.test.ts +42 -1
  258. package/src/tools/defer-task.ts +1 -1
  259. package/src/tools/extension-activate-version.ts +38 -0
  260. package/src/tools/extension-common.ts +85 -0
  261. package/src/tools/extension-delete.ts +29 -0
  262. package/src/tools/extension-disable.ts +36 -0
  263. package/src/tools/extension-enable.ts +36 -0
  264. package/src/tools/extension-install.ts +65 -0
  265. package/src/tools/extension-list.ts +66 -0
  266. package/src/tools/script-common.ts +43 -18
  267. package/src/tools/send-task.ts +69 -107
  268. package/src/tools/store-progress.ts +30 -0
  269. package/src/tools/task-action.ts +43 -22
  270. package/src/tools/task-tool-ctx.ts +2 -0
  271. package/src/tools/tool-config.ts +7 -1
  272. package/src/tools/utils.ts +140 -16
  273. package/src/types.ts +133 -0
  274. package/src/utils/multi-runtime.ts +2 -2
  275. package/src/utils/steering-enabled.ts +4 -4
  276. package/src/workflows/executors/agent-task.ts +13 -3
  277. package/templates/extensions/README.md +17 -0
  278. package/templates/extensions/notify-on-complete/README.md +11 -0
  279. package/templates/extensions/notify-on-complete/hooks.ts +61 -0
  280. package/templates/extensions/notify-on-complete/manifest.json +7 -0
  281. package/templates/extensions/require-ticket-ref/README.md +11 -0
  282. package/templates/extensions/require-ticket-ref/hooks.ts +30 -0
  283. package/templates/extensions/require-ticket-ref/manifest.json +7 -0
  284. package/templates/extensions/require-verification-note/README.md +11 -0
  285. package/templates/extensions/require-verification-note/hooks.ts +32 -0
  286. package/templates/extensions/require-verification-note/manifest.json +7 -0
  287. package/templates/skills/agent-browser/SKILL.md +84 -0
  288. package/templates/skills/agent-browser/content.md +21 -0
  289. package/templates/skills/scheduling/content.md +2 -0
  290. package/templates/skills/swarm-extensions/config.json +14 -0
  291. package/templates/skills/swarm-extensions/content.md +120 -0
  292. package/dist/cli-b8x19vbh.js +0 -823
  293. package/dist/cli-n1yj5fd5.js +0 -164
  294. package/dist/cli-q04q68be.js +0 -3066
  295. package/dist/cli-qgm2pfpq.js +0 -6377
  296. package/dist/cli-rnsr8asm.js +0 -4323
  297. package/dist/cli-rwp3ksm7.js +0 -545
  298. package/dist/cli-xjb9wghj.js +0 -668
  299. package/dist/cli-xvkmedgn.js +0 -332
  300. package/dist/cli-ydk0wjj8.js +0 -338
  301. package/dist/handlers-vr1djg4n.js +0 -41
  302. package/dist/{claude-managed-setup-f5pmsyrp.js → claude-managed-setup-48dwg9bv.js} +3 -3
  303. package/dist/{cli-81r683bv.js → cli-ch38q5z4.js} +0 -0
  304. package/dist/{cli-wk0gjrn3.js → cli-kj76p1ez.js} +6 -6
  305. package/dist/{cli-c71mg51n.js → cli-r2fpe7cx.js} +5 -5
  306. package/dist/{cli-8qx7jnrk.js → cli-y6x0gqtg.js} +16 -16
  307. package/dist/{index-aa3sa6s8.js → index-2wsdsnmm.js} +1 -1
  308. package/dist/{mistral-conversations-4ryk7h83.js → mistral-conversations-qhr84ph3.js} +9 -9
  309. package/dist/{openai-completions-qdcmkfnr.js → openai-completions-32rdwmze.js} +17 -17
  310. package/dist/{openrouter-images-qx18fyp8.js → openrouter-images-9f9ek4rw.js} +3 -3
  311. package/dist/{pi-messages-79jvzz1d.js → pi-messages-b1mbr6gp.js} +4 -4
@@ -0,0 +1,66 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import * as z from "zod";
3
+ import { createToolRegistrar, toolOk } from "@/tools/utils";
4
+ import {
5
+ coerceExtensionSummary,
6
+ extensionToolOutputSchema,
7
+ proxyExtensionsApi,
8
+ } from "./extension-common";
9
+
10
+ export const EXTENSION_LIST_DESCRIPTION =
11
+ "List installed extensions and their activation state. To write a new one, fetch GET /api/extensions/type-defs for the hook contract and install it with extension-install.";
12
+
13
+ type ExtensionListItem = ReturnType<typeof coerceExtensionSummary>;
14
+
15
+ function extensionListItems(data: unknown): ExtensionListItem[] {
16
+ if (
17
+ typeof data !== "object" ||
18
+ data === null ||
19
+ !Array.isArray((data as { extensions?: unknown }).extensions)
20
+ ) {
21
+ return [];
22
+ }
23
+ return (data as { extensions: unknown[] }).extensions.map(coerceExtensionSummary);
24
+ }
25
+
26
+ function renderExtensionsTable(extensions: ExtensionListItem[]): string {
27
+ const header = "| Name | Version | Active | Enabled | Status | Priority | Failures |";
28
+ const divider = "| --- | ---: | ---: | --- | --- | ---: | ---: |";
29
+ const rows = extensions.map(
30
+ (extension) =>
31
+ `| ${extension.name ?? "?"} | ${extension.version ?? "?"} | ${extension.activeVersion ?? "?"} | ${extension.enabled ?? false} | ${extension.status ?? "unknown"} | ${extension.priority ?? "?"} | ${extension.consecutiveFailures ?? 0} |`,
32
+ );
33
+ return [header, divider, ...rows].join("\n");
34
+ }
35
+
36
+ export const registerExtensionListTool = (server: McpServer) => {
37
+ createToolRegistrar(server)(
38
+ "extension-list",
39
+ {
40
+ title: "Extension List",
41
+ description: EXTENSION_LIST_DESCRIPTION,
42
+ annotations: { readOnlyHint: true, openWorldHint: false },
43
+ inputSchema: z
44
+ .object({
45
+ enabledOnly: z.boolean().optional().describe("Return only enabled extensions."),
46
+ })
47
+ .strict(),
48
+ outputSchema: extensionToolOutputSchema,
49
+ },
50
+ async ({ enabledOnly }, requestInfo) =>
51
+ proxyExtensionsApi({
52
+ method: "GET",
53
+ path: "/api/extensions",
54
+ requestInfo,
55
+ success: (data) => {
56
+ const extensions = extensionListItems(data).filter(
57
+ (extension) => !enabledOnly || extension.enabled,
58
+ );
59
+ return toolOk(`Found ${extensions.length} extension(s).`, {
60
+ details: renderExtensionsTable(extensions),
61
+ data: { extensions },
62
+ });
63
+ },
64
+ }),
65
+ );
66
+ };
@@ -3,6 +3,7 @@ import { getApiKey } from "@/utils/api-key";
3
3
  import { getMcpBaseUrl } from "@/utils/constants";
4
4
  import {
5
5
  type RequestInfo,
6
+ type SwarmToolData,
6
7
  type SwarmToolResult,
7
8
  swarmToolOutputSchema,
8
9
  toolErr,
@@ -161,7 +162,37 @@ export async function proxyScriptsApi(args: {
161
162
  */
162
163
  uncappedDetails?: boolean;
163
164
  }): Promise<SwarmToolResult> {
164
- if (!args.requestInfo.agentId) return toolErr(SCRIPT_TRANSPORT_ERROR);
165
+ return proxySwarmApi({
166
+ ...args,
167
+ transportError: SCRIPT_TRANSPORT_ERROR,
168
+ respond: (data, res) => {
169
+ const toolData = { status: res.status, data };
170
+ const failure = describeScriptFailure(res.ok, res.status, data);
171
+ if (failure) {
172
+ return toolErr(failure.message, {
173
+ details: failure.details ?? capDetails(args.failureDetails?.(data)),
174
+ data: toolData,
175
+ });
176
+ }
177
+
178
+ const successDetails = args.successDetails?.(data);
179
+ return toolOk(args.successMessage(data), {
180
+ details: args.uncappedDetails ? successDetails : capDetails(successDetails),
181
+ data: toolData,
182
+ });
183
+ },
184
+ });
185
+ }
186
+
187
+ export async function proxySwarmApi<T extends Record<string, unknown>>(args: {
188
+ method: "GET" | "POST" | "PATCH" | "DELETE";
189
+ path: string;
190
+ body?: unknown;
191
+ requestInfo: RequestInfo;
192
+ transportError: string;
193
+ respond: (data: unknown, response: { ok: boolean; status: number }) => SwarmToolResult<T>;
194
+ }): Promise<SwarmToolResult<T>> {
195
+ if (!args.requestInfo.agentId) return toolErr(args.transportError);
165
196
 
166
197
  const apiKey = getApiKey();
167
198
  const headers: Record<string, string> = {
@@ -172,8 +203,7 @@ export async function proxyScriptsApi(args: {
172
203
  if (args.requestInfo.sourceTaskId) {
173
204
  headers["X-Source-Task-Id"] = args.requestInfo.sourceTaskId;
174
205
  }
175
- // Runtime identity is process context from the worker's MCP session — it
176
- // rides the proxy like the agent identity, never as tool or script input.
206
+ // Forward runtime identity from the worker's MCP session, like agent identity.
177
207
  if (args.requestInfo.runtimeInstanceId) {
178
208
  headers["X-Runtime-Instance-ID"] = args.requestInfo.runtimeInstanceId;
179
209
  }
@@ -189,22 +219,17 @@ export async function proxyScriptsApi(args: {
189
219
  try {
190
220
  data = JSON.parse(text);
191
221
  } catch {
192
- data = { error: text };
222
+ const contentType = res.headers.get("content-type") ?? "unknown";
223
+ // Transport-level failure: only `{ status }` is known here, and that shape
224
+ // is not statically related to the caller's `T`. Tool output schemas are
225
+ // loose with every data field optional, so the wire result stays valid.
226
+ const transportData: SwarmToolData = { status: res.status };
227
+ return toolErr<T>(
228
+ `Swarm API returned non-JSON response (HTTP ${res.status}, Content-Type: ${contentType}). Check API/proxy routing.`,
229
+ { details: capDetails(text), data: transportData as T },
230
+ );
193
231
  }
194
232
  }
195
233
 
196
- const toolData = { status: res.status, data };
197
- const failure = describeScriptFailure(res.ok, res.status, data);
198
- if (failure) {
199
- return toolErr(failure.message, {
200
- details: failure.details ?? capDetails(args.failureDetails?.(data)),
201
- data: toolData,
202
- });
203
- }
204
-
205
- const successDetails = args.successDetails?.(data);
206
- return toolOk(args.successMessage(data), {
207
- details: args.uncappedDetails ? successDetails : capDetails(successDetails),
208
- data: toolData,
209
- });
234
+ return args.respond(data, res);
210
235
  }
@@ -15,6 +15,7 @@ import {
15
15
  hasCapacity,
16
16
  } from "@/be/db";
17
17
  import { repointTrackerSyncBySwarmId } from "@/be/db-queries/tracker";
18
+ import { applyPreTaskCreate } from "@/extensions/apply-task-create";
18
19
  import { checkSlackRoutingCoherence } from "@/tasks/slack-routing";
19
20
  import { findDuplicateTask } from "@/tools/task-dedup";
20
21
  import { ownerCtx, type ToolCtx } from "@/tools/task-tool-ctx";
@@ -28,6 +29,7 @@ import {
28
29
  import {
29
30
  type AgentTask,
30
31
  AssetKeySchema,
32
+ type CreateTaskOptions,
31
33
  FollowUpConfigSchema,
32
34
  ModelTierSchema,
33
35
  ReasoningEffortSchema,
@@ -382,6 +384,53 @@ export async function sendTaskHandler(
382
384
  const effectiveRoutingSource =
383
385
  agentId !== undefined ? "declared" : effectiveAgentId ? "engine_default" : undefined;
384
386
 
387
+ const requestedTaskOptions: CreateTaskOptions = {
388
+ key: assetKey,
389
+ agentId: offerMode ? undefined : effectiveAgentId,
390
+ offeredTo: offerMode ? effectiveAgentId : undefined,
391
+ creatorAgentId,
392
+ requestedByUserId,
393
+ source: "mcp",
394
+ sourceTaskId,
395
+ taskType,
396
+ tags,
397
+ priority,
398
+ dependsOn,
399
+ dir,
400
+ parentTaskId: effectiveParentTaskId,
401
+ vcsRepo: effectiveVcsRepo,
402
+ model: normalizedModel.model,
403
+ modelTier: normalizedModel.modelTier,
404
+ effort,
405
+ slackChannelId,
406
+ slackThreadTs,
407
+ slackUserId,
408
+ overrideSlackContext,
409
+ followUpConfig,
410
+ outputSchema,
411
+ routingReason: effectiveRoutingReason,
412
+ routingSource: effectiveRoutingSource,
413
+ routingNote: effectiveRoutingNote,
414
+ routingAffinity:
415
+ effectiveLeadOnly || requiredCapabilities?.length
416
+ ? { leadOnly: effectiveLeadOnly, capabilities: requiredCapabilities ?? [] }
417
+ : undefined,
418
+ };
419
+ const preCreate = await applyPreTaskCreate({
420
+ description: task,
421
+ options: requestedTaskOptions,
422
+ origin: "mcp",
423
+ requestInfo: ctx.kind === "owner" ? ctx.requestInfo : undefined,
424
+ });
425
+ if (preCreate.kind === "blocked") {
426
+ return toolErr(preCreate.reason, {
427
+ data: { yourAgentId: creatorAgentId },
428
+ details: JSON.stringify({ extension: preCreate.extension }),
429
+ });
430
+ }
431
+ const taskDescription = preCreate.description;
432
+ const taskOptions = preCreate.options;
433
+
385
434
  // The three dedup guards are pure reads, so they run twice: once here as a
386
435
  // fast path (keeping this tool's existing early-exit responses), and once
387
436
  // inside the write transaction below, where the check is authoritative.
@@ -405,9 +454,9 @@ export async function sendTaskHandler(
405
454
  // Dedup guard: check for similar recent tasks
406
455
  if (!allowDuplicate && creatorAgentId) {
407
456
  const duplicate = await findDuplicateTask({
408
- taskDescription: task,
409
- creatorAgentId,
410
- targetAgentId: effectiveAgentId ?? undefined,
457
+ taskDescription,
458
+ creatorAgentId: taskOptions.creatorAgentId ?? creatorAgentId,
459
+ targetAgentId: taskOptions.agentId ?? taskOptions.offeredTo,
411
460
  });
412
461
  if (duplicate) {
413
462
  return {
@@ -487,42 +536,13 @@ export async function sendTaskHandler(
487
536
  const raced = await evaluateDedupGuards();
488
537
  if (raced) return { success: raced.ok, message: raced.message, task: raced.task };
489
538
 
490
- const finalTags = tags;
491
-
492
539
  // If no agentId (and no auto-routed agentId), create an unassigned task for the pool
493
- if (!effectiveAgentId) {
494
- const newTask = await createTaskExtended(task, {
495
- key: assetKey,
496
- creatorAgentId,
497
- requestedByUserId,
498
- sourceTaskId,
499
- taskType,
500
- tags: finalTags,
501
- priority,
502
- dependsOn,
503
- dir,
504
- parentTaskId: effectiveParentTaskId,
505
- vcsRepo: effectiveVcsRepo,
506
- model: normalizedModel.model,
507
- modelTier: normalizedModel.modelTier,
508
- effort,
509
- slackChannelId,
510
- slackThreadTs,
511
- slackUserId,
512
- overrideSlackContext,
513
- followUpConfig,
514
- outputSchema,
515
- routingReason: effectiveRoutingReason,
516
- routingSource: effectiveRoutingSource,
517
- routingNote: effectiveRoutingNote,
518
- routingAffinity:
519
- effectiveLeadOnly || requiredCapabilities?.length
520
- ? { leadOnly: effectiveLeadOnly, capabilities: requiredCapabilities ?? [] }
521
- : undefined,
522
- });
540
+ const targetAgentId = taskOptions.offeredTo ?? taskOptions.agentId ?? undefined;
541
+ if (!targetAgentId) {
542
+ const newTask = await createTaskExtended(taskDescription, taskOptions);
523
543
  await transferTrackerSyncToResumeChild({
524
- parentTaskId: effectiveParentTaskId,
525
- taskType,
544
+ parentTaskId: taskOptions.parentTaskId,
545
+ taskType: taskOptions.taskType,
526
546
  child: newTask,
527
547
  });
528
548
 
@@ -533,16 +553,16 @@ export async function sendTaskHandler(
533
553
  };
534
554
  }
535
555
 
536
- const agent = await getAgentById(effectiveAgentId);
556
+ const agent = await getAgentById(targetAgentId);
537
557
 
538
558
  if (!agent) {
539
559
  return {
540
560
  success: false,
541
- message: `Agent with ID "${effectiveAgentId}" not found.`,
561
+ message: `Agent with ID "${targetAgentId}" not found.`,
542
562
  };
543
563
  }
544
564
 
545
- if (effectiveLeadOnly && !agent.isLead) {
565
+ if (taskOptions.routingAffinity?.leadOnly && !agent.isLead) {
546
566
  return {
547
567
  success: false,
548
568
  message: `Lead-only task requires a Lead agent; "${agent.name}" is not a Lead.`,
@@ -550,49 +570,20 @@ export async function sendTaskHandler(
550
570
  }
551
571
 
552
572
  // For direct assignment (not offer), check if agent has capacity
553
- if (!offerMode && !(await hasCapacity(effectiveAgentId))) {
554
- const activeCount = await getActiveTaskCount(effectiveAgentId);
573
+ if (!taskOptions.offeredTo && !(await hasCapacity(targetAgentId))) {
574
+ const activeCount = await getActiveTaskCount(targetAgentId);
555
575
  return {
556
576
  success: false,
557
577
  message: `Agent "${agent.name}" is at capacity (${activeCount}/${agent.maxTasks ?? 1} tasks). Use offerMode: true to offer the task instead, or wait for a task to complete.`,
558
578
  };
559
579
  }
560
580
 
561
- if (offerMode) {
581
+ if (taskOptions.offeredTo) {
562
582
  // Offer the task to the agent (they must accept/reject)
563
- const newTask = await createTaskExtended(task, {
564
- key: assetKey,
565
- offeredTo: effectiveAgentId,
566
- creatorAgentId,
567
- requestedByUserId,
568
- sourceTaskId,
569
- taskType,
570
- tags: finalTags,
571
- priority,
572
- dependsOn,
573
- dir,
574
- parentTaskId: effectiveParentTaskId,
575
- vcsRepo: effectiveVcsRepo,
576
- model: normalizedModel.model,
577
- modelTier: normalizedModel.modelTier,
578
- effort,
579
- slackChannelId,
580
- slackThreadTs,
581
- slackUserId,
582
- overrideSlackContext,
583
- followUpConfig,
584
- outputSchema,
585
- routingReason: effectiveRoutingReason,
586
- routingSource: effectiveRoutingSource,
587
- routingNote: effectiveRoutingNote,
588
- routingAffinity:
589
- effectiveLeadOnly || requiredCapabilities?.length
590
- ? { leadOnly: effectiveLeadOnly, capabilities: requiredCapabilities ?? [] }
591
- : undefined,
592
- });
583
+ const newTask = await createTaskExtended(taskDescription, taskOptions);
593
584
  await transferTrackerSyncToResumeChild({
594
- parentTaskId: effectiveParentTaskId,
595
- taskType,
585
+ parentTaskId: taskOptions.parentTaskId,
586
+ taskType: taskOptions.taskType,
596
587
  child: newTask,
597
588
  });
598
589
 
@@ -604,39 +595,10 @@ export async function sendTaskHandler(
604
595
  }
605
596
 
606
597
  // Direct assignment
607
- const newTask = await createTaskExtended(task, {
608
- key: assetKey,
609
- agentId: effectiveAgentId,
610
- creatorAgentId,
611
- requestedByUserId,
612
- sourceTaskId,
613
- taskType,
614
- tags: finalTags,
615
- priority,
616
- dependsOn,
617
- dir,
618
- parentTaskId: effectiveParentTaskId,
619
- vcsRepo: effectiveVcsRepo,
620
- model: normalizedModel.model,
621
- modelTier: normalizedModel.modelTier,
622
- effort,
623
- slackChannelId,
624
- slackThreadTs,
625
- slackUserId,
626
- overrideSlackContext,
627
- followUpConfig,
628
- outputSchema,
629
- routingReason: effectiveRoutingReason,
630
- routingSource: effectiveRoutingSource,
631
- routingNote: effectiveRoutingNote,
632
- routingAffinity:
633
- effectiveLeadOnly || requiredCapabilities?.length
634
- ? { leadOnly: effectiveLeadOnly, capabilities: requiredCapabilities ?? [] }
635
- : undefined,
636
- });
598
+ const newTask = await createTaskExtended(taskDescription, taskOptions);
637
599
  await transferTrackerSyncToResumeChild({
638
- parentTaskId: effectiveParentTaskId,
639
- taskType,
600
+ parentTaskId: taskOptions.parentTaskId,
601
+ taskType: taskOptions.taskType,
640
602
  child: newTask,
641
603
  });
642
604
 
@@ -167,6 +167,36 @@ export const registerStoreProgressTool = (server: McpServer) => {
167
167
  ? requestedStatus
168
168
  : undefined;
169
169
 
170
+ // Stock Claude occasionally puts the next tool parameter inside output.
171
+ // Strip the leaked tail before validation/persistence; recovered pointers
172
+ // must still pass the ordinary attachment schema and verification below.
173
+ const leakedParameter = output?.match(
174
+ /(?:<\/(?:output|parameter)>\s*)?<parameter name="([a-zA-Z_]+)">\s*([\s\S]*)$/,
175
+ );
176
+ if (leakedParameter) {
177
+ output = output?.slice(0, leakedParameter.index);
178
+ let recovered = false;
179
+ if (leakedParameter[1] === "attachments") {
180
+ try {
181
+ const parsed = z
182
+ .array(AttachmentInputSchema)
183
+ .max(20)
184
+ .safeParse(JSON.parse(leakedParameter[2]!.replace(/\s*<\/parameter>\s*$/, "")));
185
+ if (parsed.success && (attachments?.length ?? 0) + parsed.data.length <= 20) {
186
+ attachments = [...(attachments ?? []), ...parsed.data];
187
+ recovered = true;
188
+ }
189
+ } catch {
190
+ // Malformed JSON must not prevent saving the cleaned task result.
191
+ }
192
+ }
193
+ if (!recovered) {
194
+ console.warn(
195
+ `[store-progress] Stripped malformed output tail for task ${taskId}; parameter ${leakedParameter[1]} was not recovered.`,
196
+ );
197
+ }
198
+ }
199
+
170
200
  // Verify agent-fs pointers before opening the write transaction. The
171
201
  // registering agent's resolved config selects both credentials and the
172
202
  // exact org/drive; never let the provider fall back to a personal drive.
@@ -28,6 +28,7 @@ import {
28
28
  updateTaskClaudeSessionId,
29
29
  } from "@/be/db";
30
30
  import { touchRuntimeInstance } from "@/be/multi-runtime";
31
+ import { applyPreTaskCreate } from "@/extensions/apply-task-create";
31
32
  import { assertOwnsTask, ownerCtx, type ToolCtx } from "@/tools/task-tool-ctx";
32
33
  import {
33
34
  createToolRegistrar,
@@ -39,6 +40,7 @@ import {
39
40
  import {
40
41
  AssetKeySchema,
41
42
  BudgetRefusalCauseSchema,
43
+ type CreateTaskOptions,
42
44
  ModelTierSchema,
43
45
  ReasoningEffortSchema,
44
46
  splitLegacyModelAlias,
@@ -265,7 +267,14 @@ export async function taskActionHandler(
265
267
 
266
268
  const agentId = ctx.agentId;
267
269
  let assetKey: string | undefined;
270
+ let preparedCreate: { description: string; options: CreateTaskOptions } | undefined;
268
271
  if (action === "create") {
272
+ if (!task) {
273
+ return taskActionResult(
274
+ { success: false, message: "Task description is required for 'create' action." },
275
+ agentId,
276
+ );
277
+ }
269
278
  try {
270
279
  assetKey = key
271
280
  ? await authorizeAssetKeyWrite(key, await resolveTaskAuditUserId(ctx.sourceTaskId, agentId))
@@ -279,33 +288,45 @@ export async function taskActionHandler(
279
288
  : String(error);
280
289
  return taskActionResult({ success: false, message }, agentId);
281
290
  }
291
+
292
+ const preCreate = await applyPreTaskCreate({
293
+ description: task,
294
+ options: {
295
+ key: assetKey,
296
+ creatorAgentId: agentId,
297
+ source: "mcp",
298
+ taskType,
299
+ tags,
300
+ priority,
301
+ dependsOn,
302
+ dir,
303
+ model: normalizedModel.model,
304
+ modelTier: normalizedModel.modelTier,
305
+ effort: input.effort,
306
+ routingAffinity:
307
+ leadOnly || requiredCapabilities?.length
308
+ ? { leadOnly, capabilities: requiredCapabilities ?? [] }
309
+ : undefined,
310
+ },
311
+ origin: "mcp",
312
+ requestInfo: ctx.requestInfo,
313
+ });
314
+ if (preCreate.kind === "blocked") {
315
+ return taskActionResult({ success: false, message: preCreate.reason }, agentId);
316
+ }
317
+ preparedCreate = {
318
+ description: preCreate.description,
319
+ options: preCreate.options,
320
+ };
282
321
  }
283
322
 
284
323
  const result = await getDbClient().transaction(async (): Promise<TaskActionResult> => {
285
324
  switch (action) {
286
325
  case "create": {
287
- if (!task) {
288
- return {
289
- success: false,
290
- message: "Task description is required for 'create' action.",
291
- };
292
- }
293
- const newTask = await createTaskExtended(task, {
294
- key: assetKey,
295
- creatorAgentId: agentId,
296
- taskType,
297
- tags,
298
- priority,
299
- dependsOn,
300
- dir,
301
- model: normalizedModel.model,
302
- modelTier: normalizedModel.modelTier,
303
- effort: input.effort,
304
- routingAffinity:
305
- leadOnly || requiredCapabilities?.length
306
- ? { leadOnly, capabilities: requiredCapabilities ?? [] }
307
- : undefined,
308
- });
326
+ const newTask = await createTaskExtended(
327
+ preparedCreate!.description,
328
+ preparedCreate!.options,
329
+ );
309
330
  return {
310
331
  success: true,
311
332
  message: `Created unassigned task "${newTask.id}".`,
@@ -9,6 +9,7 @@ export type ToolCtx =
9
9
  runtimeInstanceId?: string;
10
10
  sourceTaskId?: string;
11
11
  sessionId?: string;
12
+ requestInfo?: RequestInfo;
12
13
  }
13
14
  | { kind: "user"; userId: string; user: User; sessionId?: string };
14
15
 
@@ -19,6 +20,7 @@ export function ownerCtx(info: RequestInfo): ToolCtx {
19
20
  runtimeInstanceId: info.runtimeInstanceId,
20
21
  sourceTaskId: info.sourceTaskId,
21
22
  sessionId: info.sessionId,
23
+ requestInfo: info,
22
24
  };
23
25
  }
24
26
 
@@ -197,13 +197,19 @@ export const DEFERRED_TOOLS = new Set([
197
197
  "room-reset",
198
198
  "room-decode",
199
199
 
200
- // Reusable scripts (6)
200
+ // Reusable scripts and extensions (12)
201
201
  "script-upsert",
202
202
  "script-delete",
203
203
  "script-apis",
204
204
  "launch-script-run",
205
205
  "get-script-run",
206
206
  "list-script-runs",
207
+ "extension-delete",
208
+ "extension-enable",
209
+ "extension-disable",
210
+ "extension-activate-version",
211
+ "extension-install",
212
+ "extension-list",
207
213
 
208
214
  // External command routes (1)
209
215
  "swarm_x",