@desplega.ai/agent-swarm 1.149.1 → 1.151.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 (538) hide show
  1. package/README.md +18 -3
  2. package/dist/{acp-adapter-vhkzfpjg.js → acp-adapter-ex8hzjfc.js} +5 -5
  3. package/dist/{actions-9d2694yk.js → actions-2bgpyhww.js} +39 -11
  4. package/dist/{app-fq2ggynt.js → app-1706zhr1.js} +7 -7
  5. package/dist/{artifact-gj9arwrr.js → artifact-td4q4dch.js} +1 -1
  6. package/dist/{assistant-edb6n9nn.js → assistant-bavke5p7.js} +44 -20
  7. package/dist/{azure-openai-responses-cqar64rc.js → azure-openai-responses-f38x2avq.js} +13 -13
  8. package/dist/{boot-reembed-msweabbp.js → boot-reembed-3cmrrmpa.js} +9 -9
  9. package/dist/{boot-reembed-q5wd3tha.js → boot-reembed-nafcmmmc.js} +14 -16
  10. package/dist/{boot-scrub-logs-3g1az933.js → boot-scrub-logs-cd6a2sq0.js} +5 -5
  11. package/dist/{claude-adapter-v3rnzsdc.js → claude-adapter-wfp2gzvz.js} +15 -16
  12. package/dist/{claude-managed-adapter-qvsmh6mv.js → claude-managed-adapter-dsmkcrjy.js} +3 -3
  13. package/dist/{claude-sdk-session-qxrpsna1.js → claude-sdk-session-bht9jjak.js} +15 -16
  14. package/dist/{cli-5m0qmvdp.js → cli-0qb1z6s2.js} +1 -1
  15. package/dist/{cli-9d8ntetj.js → cli-2658f6at.js} +1 -1
  16. package/dist/{cli-7evafqvy.js → cli-2tbvf1z1.js} +85 -69
  17. package/dist/{cli-k351hv5k.js → cli-3v9xhjcn.js} +24 -12
  18. package/dist/{cli-1ebpar1q.js → cli-4wppfkcm.js} +350 -20
  19. package/dist/{cli-4260r1tg.js → cli-5jd2z7av.js} +97 -84
  20. package/dist/{cli-cv7s5zdq.js → cli-5tpgq60f.js} +9865 -715
  21. package/dist/{cli-6skz3rk8.js → cli-7tgcstn2.js} +9 -4
  22. package/dist/{cli-01g3x1ca.js → cli-9004vfdf.js} +108 -56
  23. package/dist/{cli-1c94wvcw.js → cli-a1z3h107.js} +124 -99
  24. package/dist/{cli-9br8ypfv.js → cli-czjtgfys.js} +2 -2
  25. package/dist/{cli-5my3bjsd.js → cli-d3p82erb.js} +7 -7
  26. package/dist/{cli-e29gmdmw.js → cli-dv6cdjs2.js} +2 -1
  27. package/dist/{cli-wby1xnnb.js → cli-f1wky6zp.js} +1 -1
  28. package/dist/{cli-zvz62chv.js → cli-fm4ytsdk.js} +4 -4
  29. package/dist/{cli-tg8qj8ee.js → cli-frcta2w4.js} +6 -6
  30. package/dist/{cli-7xx10h0a.js → cli-g69fdc9g.js} +2 -2
  31. package/dist/{cli-cmp8g3sw.js → cli-gbfwcd3k.js} +44 -9
  32. package/dist/{handlers-nsg0bcye.js → cli-gjyvhmmh.js} +97 -45
  33. package/dist/{cli-08b07b4r.js → cli-ht5rs63n.js} +1 -1
  34. package/dist/{cli-jecc01cb.js → cli-j4nv2wqy.js} +2 -4
  35. package/dist/{cli-cd60fv6j.js → cli-j9xczhaq.js} +1 -1
  36. package/dist/cli-pf9q3696.js +172 -0
  37. package/dist/{cli-xm87hzbw.js → cli-pgj4a5fb.js} +1 -1
  38. package/dist/{cli-jezx9b9c.js → cli-ppm4975n.js} +9 -3
  39. package/dist/{cli-3z634pqe.js → cli-q88st9aj.js} +2 -2
  40. package/dist/{cli-3sysyjve.js → cli-r2q6pevn.js} +142 -49
  41. package/dist/{cli-07jd68a1.js → cli-re8z5p8p.js} +1 -1
  42. package/dist/{cli-qgd4sqz5.js → cli-s14agm5s.js} +121 -14
  43. package/dist/{cli-svbrpf5q.js → cli-sjaae6ff.js} +9872 -424
  44. package/dist/{cli-73gq0zxn.js → cli-vwm416m6.js} +13 -13
  45. package/dist/{cli-4wtq5jjv.js → cli-wbyk465z.js} +1 -1
  46. package/dist/{cli-3apbzgyk.js → cli-z61ykvam.js} +19 -14
  47. package/dist/{cli-e06a8a7y.js → cli-zkxymas5.js} +4 -3
  48. package/dist/cli.js +26 -25
  49. package/dist/{codex-adapter-jyw7a91z.js → codex-adapter-jjjk8w4m.js} +8 -9
  50. package/dist/{codex-hook-hfrt9shr.js → codex-hook-8jexc34x.js} +3 -3
  51. package/dist/{codex-session-runner-bmngj0s5.js → codex-session-runner-nd6ayxkx.js} +8 -9
  52. package/dist/{commands-n755g391.js → commands-9v4psyq1.js} +5 -5
  53. package/dist/{db-s1cgh5gv.js → db-wnt58tpe.js} +5 -5
  54. package/dist/dispatcher-05jh1aqk.js +74 -0
  55. package/dist/{e2b-p04vmtx0.js → e2b-413b8t4v.js} +1 -1
  56. package/dist/{google-generative-ai-tsskx7te.js → google-generative-ai-ndjmj5fa.js} +7 -7
  57. package/dist/{google-vertex-ppw8ydpa.js → google-vertex-fr665tqs.js} +10 -10
  58. package/dist/handlers-tw90pkh5.js +65 -0
  59. package/dist/{hook-jf0xe8zh.js → hook-123e2scb.js} +11 -12
  60. package/dist/{hook-h9cqrm8q.js → hook-sxe3r349.js} +11 -12
  61. package/dist/{http-nc6pydw5.js → http-ecsar8ez.js} +1442 -581
  62. package/dist/{index-ezxkds2n.js → index-16js521z.js} +335 -107
  63. package/dist/{index-769nc012.js → index-hw26vj40.js} +25 -19
  64. package/dist/{index-0wdy6gwc.js → index-nnrbpcjp.js} +25 -17
  65. package/dist/index-ymn7em0p.js +25 -0
  66. package/dist/{index-x1zcj09z.js → index-zj518gf9.js} +26 -20
  67. package/dist/{keepalive-nesm3xb6.js → keepalive-asqtcm4y.js} +7 -7
  68. package/dist/{lead-bwy6t2za.js → lead-xbfr7fvt.js} +40 -48
  69. package/dist/{maintenance-8w393zf0.js → maintenance-qma55qvw.js} +9 -9
  70. package/dist/{oauth-refresh-sweep-sh8xjy08.js → oauth-refresh-sweep-3s04c6qw.js} +6 -6
  71. package/dist/{onboard-25ps29h1.js → onboard-drskd386.js} +13 -4
  72. package/dist/{openai-codex-responses-g7w48y30.js → openai-codex-responses-wy6a43wd.js} +13 -13
  73. package/dist/{openai-responses-nmeyptke.js → openai-responses-6xwf7bzg.js} +19 -19
  74. package/dist/{opencode-adapter-dce4wskd.js → opencode-adapter-28axqa09.js} +4 -4
  75. package/dist/{otel-impl-b93gxnz5.js → otel-impl-943c96er.js} +1 -1
  76. package/dist/{pi-mono-adapter-zvshk5cj.js → pi-mono-adapter-jcqt71cg.js} +17 -19
  77. package/dist/{pricing-refresh-5v56x7gb.js → pricing-refresh-mx51az05.js} +7 -7
  78. package/dist/{rbac-roles-x55cnezn.js → rbac-roles-78baecqd.js} +13 -9
  79. package/dist/{rbac-roles-0p0s6pbj.js → rbac-roles-wcmrg6sv.js} +7 -7
  80. package/dist/{render-v2-hyd45w8b.js → render-v2-8zjntgf5.js} +7 -7
  81. package/dist/{seed-pricing-49gbgn96.js → seed-pricing-jqjs6cws.js} +6 -6
  82. package/dist/{setup-qvq64vzq.js → setup-4pks0vfk.js} +2 -2
  83. package/dist/work-objects-wangt792.js +165 -0
  84. package/dist/{worker-scj60cxg.js → worker-p2mr422x.js} +40 -48
  85. package/openapi.json +1365 -268
  86. package/package.json +4 -3
  87. package/src/agentmail/handlers.ts +95 -66
  88. package/src/be/budget-refusal-notify.ts +1 -0
  89. package/src/be/db/tasks/read.ts +3 -0
  90. package/src/be/db/tasks/write.ts +13 -1
  91. package/src/be/db-client.ts +5 -0
  92. package/src/be/db.ts +15 -5
  93. package/src/be/extensions/db.ts +461 -0
  94. package/src/be/extensions/validate.ts +107 -0
  95. package/src/be/memory/boot-reembed.ts +17 -12
  96. package/src/be/memory/index.ts +12 -0
  97. package/src/be/memory/providers/openai-embedding.ts +4 -0
  98. package/src/be/memory/types.ts +2 -0
  99. package/src/be/migrations/153_deferred_task_wait_members.sql +25 -0
  100. package/src/be/migrations/154_routing_source.sql +3 -0
  101. package/src/be/migrations/155_extensions.sql +66 -0
  102. package/src/be/migrations/156_inbox_item_state_read_at.sql +7 -0
  103. package/src/be/rbac-roles.ts +3 -2
  104. package/src/be/scripts/typecheck.ts +59 -41
  105. package/src/be/seed/schedules-seeder.ts +25 -2
  106. package/src/be/seed/workflows-seeder.ts +17 -0
  107. package/src/be/seed-scripts/catalog/delegate.ts +3 -1
  108. package/src/be/seed-skills/bundled-files.generated.json +1 -1
  109. package/src/be/seed-skills/index.ts +7 -0
  110. package/src/be/steering.ts +1 -0
  111. package/src/be/swarm-config-guard.ts +3 -1
  112. package/src/commands/credential-refresh.ts +70 -0
  113. package/src/commands/onboard/compose-generator.ts +14 -2
  114. package/src/commands/onboard/env-generator.ts +7 -0
  115. package/src/commands/provider-credentials.ts +2 -25
  116. package/src/commands/runner.ts +24 -57
  117. package/src/extensions/apply-task-create.ts +135 -0
  118. package/src/extensions/bundle-path.ts +10 -0
  119. package/src/extensions/contract-runtime.ts +7 -0
  120. package/src/extensions/contract-types.generated.ts +4 -0
  121. package/src/extensions/contract.ts +270 -0
  122. package/src/extensions/ctx.ts +186 -0
  123. package/src/extensions/dispatcher.ts +479 -0
  124. package/src/extensions/identity.ts +44 -0
  125. package/src/extensions/lifecycle.ts +281 -0
  126. package/src/extensions/loader.ts +207 -0
  127. package/src/extensions/post-bridge.ts +161 -0
  128. package/src/github/handlers.ts +185 -140
  129. package/src/gitlab/handlers.ts +97 -77
  130. package/src/heartbeat/heartbeat.ts +155 -83
  131. package/src/http/agents.ts +1 -1
  132. package/src/http/all-routes.ts +1 -0
  133. package/src/http/approval-requests.ts +1 -0
  134. package/src/http/apps.ts +14 -9
  135. package/src/http/config.ts +4 -1
  136. package/src/http/core.ts +38 -2
  137. package/src/http/extensions.ts +547 -0
  138. package/src/http/inbox-state.ts +2 -0
  139. package/src/http/index.ts +41 -3
  140. package/src/http/mcp-bridge.ts +96 -62
  141. package/src/http/mcp.ts +1 -1
  142. package/src/http/script-runs.ts +5 -0
  143. package/src/http/status.ts +6 -4
  144. package/src/http/tasks.ts +51 -30
  145. package/src/integrations/kapso/inbound.ts +15 -10
  146. package/src/jira/sync.ts +26 -16
  147. package/src/linear/sync.ts +26 -16
  148. package/src/prompts/session-templates.ts +18 -13
  149. package/src/providers/claude-adapter.ts +21 -8
  150. package/src/rbac/admission.ts +1 -1
  151. package/src/rbac/elevated-agents.ts +24 -0
  152. package/src/rbac/legacy-policy.ts +35 -6
  153. package/src/rbac/permissions.ts +8 -0
  154. package/src/scheduler/deferred-task-waits.ts +138 -27
  155. package/src/scheduler/schedule-task.ts +54 -24
  156. package/src/scheduler/scheduler.ts +108 -125
  157. package/src/scripts-runtime/executors/native.ts +5 -1
  158. package/src/scripts-runtime/executors/types.ts +5 -0
  159. package/src/scripts-runtime/import-allowlist.ts +79 -19
  160. package/src/scripts-runtime/sdk-allowlist.ts +4 -0
  161. package/src/scripts-runtime/swarm-config.ts +8 -0
  162. package/src/scripts-runtime/swarm-sdk.ts +3 -0
  163. package/src/scripts-runtime/types/stdlib.d.ts +16 -3
  164. package/src/scripts-runtime/types/swarm-extension.d.ts +562 -0
  165. package/src/scripts-runtime/types/swarm-sdk.d.ts +16 -3
  166. package/src/server.ts +6 -0
  167. package/src/slack/HEURISTICS.md +3 -1
  168. package/src/slack/actions.ts +21 -16
  169. package/src/slack/app.ts +2 -0
  170. package/src/slack/assistant.ts +4 -12
  171. package/src/slack/blocks.ts +1 -1
  172. package/src/slack/handlers.ts +107 -19
  173. package/src/slack/inbound-files.ts +9 -5
  174. package/src/slack/render-v2.ts +188 -38
  175. package/src/slack/router.ts +13 -3
  176. package/src/slack/steering.ts +3 -3
  177. package/src/slack/thread-buffer.ts +66 -23
  178. package/src/slack/work-objects.test.ts +252 -0
  179. package/src/slack/work-objects.ts +118 -0
  180. package/src/tasks/errors.ts +9 -0
  181. package/src/tasks/sibling-awareness.ts +35 -1
  182. package/src/tasks/worker-follow-up.ts +67 -11
  183. package/src/tests/acp-adapter.test.ts +10 -10
  184. package/src/tests/agent-fs-live-url.test.ts +40 -0
  185. package/src/tests/agent-fs-provision-seeder.test.ts +12 -12
  186. package/src/tests/api-key-tracking.test.ts +18 -18
  187. package/src/tests/api-key.test.ts +4 -2
  188. package/src/tests/apps-rbac.test.ts +1 -1
  189. package/src/tests/apps-spike5.test.ts +1 -1
  190. package/src/tests/apps-sync-engine.test.ts +8 -8
  191. package/src/tests/apps-user-config.test.ts +1 -1
  192. package/src/tests/artifact-sdk.test.ts +8 -8
  193. package/src/tests/asset-key-mcp.test.ts +2 -0
  194. package/src/tests/automation-preflight-alert.test.ts +1 -1
  195. package/src/tests/base-prompt.test.ts +7 -6
  196. package/src/tests/budget-user-scope.test.ts +1 -1
  197. package/src/tests/budgets-routes.test.ts +1 -1
  198. package/src/tests/claude-adapter-binary.test.ts +22 -40
  199. package/src/tests/claude-adapter-otel.test.ts +13 -7
  200. package/src/tests/claude-adapter.test.ts +8 -6
  201. package/src/tests/claude-hook-credentials.test.ts +3 -3
  202. package/src/tests/claude-managed-adapter.test.ts +4 -4
  203. package/src/tests/claude-managed-setup.test.ts +3 -3
  204. package/src/tests/claude-sdk-lifecycle.test.ts +1 -1
  205. package/src/tests/claude-sdk-transport.test.ts +6 -6
  206. package/src/tests/claude-stop-hook.test.ts +8 -8
  207. package/src/tests/claude-worker-parity.test.ts +3 -3
  208. package/src/tests/codex-adapter-pool-auth-revalidate.test.ts +5 -5
  209. package/src/tests/codex-adapter.test.ts +7 -5
  210. package/src/tests/codex-app-server.test.ts +2 -2
  211. package/src/tests/codex-login.test.ts +15 -15
  212. package/src/tests/codex-oauth-credential-info.test.ts +4 -4
  213. package/src/tests/codex-oauth-keep-warm.test.ts +4 -4
  214. package/src/tests/codex-oauth-refresh-lock.test.ts +1 -1
  215. package/src/tests/codex-oauth-storage.test.ts +7 -7
  216. package/src/tests/codex-pool.test.ts +1 -1
  217. package/src/tests/codex-steering-hook.test.ts +1 -1
  218. package/src/tests/codex-swarm-events.test.ts +1 -1
  219. package/src/tests/config-api-only-keys.test.ts +1 -1
  220. package/src/tests/config-session-auth.test.ts +2 -2
  221. package/src/tests/connection-embedded-auth.test.ts +20 -18
  222. package/src/tests/core-auth.test.ts +1 -1
  223. package/src/tests/create-page-tool.test.ts +3 -3
  224. package/src/tests/credential-refresh.test.ts +115 -0
  225. package/src/tests/credential-wait.test.ts +2 -2
  226. package/src/tests/credentials.test.ts +6 -4
  227. package/src/tests/db-queries-oauth.test.ts +40 -40
  228. package/src/tests/defer-task.test.ts +73 -8
  229. package/src/tests/deferred-task-wait-migration.test.ts +64 -0
  230. package/src/tests/deferred-task-wake.test.ts +349 -8
  231. package/src/tests/devin-adapter.test.ts +1 -1
  232. package/src/tests/devin-api.test.ts +1 -1
  233. package/src/tests/docs-index-footprint.test.js +3 -3
  234. package/src/tests/e2b-dispatch.test.ts +21 -19
  235. package/src/tests/e2e-claude-transport.test.ts +3 -3
  236. package/src/tests/e2e-redact.test.ts +6 -6
  237. package/src/tests/ensure-token.test.ts +68 -68
  238. package/src/tests/entrypoint-api-readiness.test.ts +1 -1
  239. package/src/tests/entrypoint-codex-oauth-seed.test.ts +13 -13
  240. package/src/tests/env-flag.test.ts +1 -0
  241. package/src/tests/events-http.test.ts +1 -1
  242. package/src/tests/extensions-db.test.ts +216 -0
  243. package/src/tests/extensions-dispatcher.test.ts +275 -0
  244. package/src/tests/extensions-example-heartbeat-remediate.test.ts +69 -0
  245. package/src/tests/extensions-example-mcp-install.test.ts +63 -0
  246. package/src/tests/extensions-example-no-exclamation-marks.test.ts +72 -0
  247. package/src/tests/extensions-example-rewrite-and-suppress.test.ts +70 -0
  248. package/src/tests/extensions-example-route-slack-channel.test.ts +217 -0
  249. package/src/tests/extensions-heartbeat.test.ts +291 -0
  250. package/src/tests/extensions-http.test.ts +431 -0
  251. package/src/tests/extensions-lead-equivalence.test.ts +49 -0
  252. package/src/tests/extensions-lifecycle.test.ts +209 -0
  253. package/src/tests/extensions-loader.test.ts +95 -0
  254. package/src/tests/extensions-mcp-tools.test.ts +245 -0
  255. package/src/tests/extensions-pre-task.test.ts +487 -0
  256. package/src/tests/extensions-run-context.test.ts +41 -0
  257. package/src/tests/extensions-tool-call.test.ts +288 -0
  258. package/src/tests/extensions-validate.test.ts +196 -0
  259. package/src/tests/fixtures/extensions/bad-import/hooks.ts +8 -0
  260. package/src/tests/fixtures/extensions/bad-import/manifest.json +9 -0
  261. package/src/tests/fixtures/extensions/bad-return-shape/hooks.ts +10 -0
  262. package/src/tests/fixtures/extensions/bad-return-shape/manifest.json +9 -0
  263. package/src/tests/fixtures/extensions/block-tasks-from-source/hooks.ts +22 -0
  264. package/src/tests/fixtures/extensions/block-tasks-from-source/manifest.json +9 -0
  265. package/src/tests/fixtures/extensions/ignore-channel/hooks.ts +22 -0
  266. package/src/tests/fixtures/extensions/ignore-channel/manifest.json +9 -0
  267. package/src/tests/fixtures/extensions/load.ts +10 -0
  268. package/src/tests/fixtures/extensions/minimal/hooks.ts +7 -0
  269. package/src/tests/fixtures/extensions/minimal/manifest.json +9 -0
  270. package/src/tests/fixtures/extensions/never-fail-long-tasks/hooks.ts +11 -0
  271. package/src/tests/fixtures/extensions/never-fail-long-tasks/manifest.json +9 -0
  272. package/src/tests/fixtures/extensions/no-exclamation-marks/hooks.ts +23 -0
  273. package/src/tests/fixtures/extensions/no-exclamation-marks/manifest.json +9 -0
  274. package/src/tests/fixtures/extensions/post-logger/hooks.ts +9 -0
  275. package/src/tests/fixtures/extensions/post-logger/manifest.json +9 -0
  276. package/src/tests/fixtures/extensions/priority-a/hooks.ts +7 -0
  277. package/src/tests/fixtures/extensions/priority-a/manifest.json +9 -0
  278. package/src/tests/fixtures/extensions/priority-b/hooks.ts +7 -0
  279. package/src/tests/fixtures/extensions/priority-b/manifest.json +9 -0
  280. package/src/tests/fixtures/extensions/record-only-on-tag/hooks.ts +16 -0
  281. package/src/tests/fixtures/extensions/record-only-on-tag/manifest.json +9 -0
  282. package/src/tests/fixtures/extensions/reserved-assets/hooks.ts +5 -0
  283. package/src/tests/fixtures/extensions/reserved-assets/manifest.json +10 -0
  284. package/src/tests/fixtures/extensions/rewrite-progress-prefix/hooks.ts +22 -0
  285. package/src/tests/fixtures/extensions/rewrite-progress-prefix/manifest.json +9 -0
  286. package/src/tests/fixtures/extensions/rewrite-task-priority/hooks.ts +7 -0
  287. package/src/tests/fixtures/extensions/rewrite-task-priority/manifest.json +9 -0
  288. package/src/tests/fixtures/extensions/route-channel-to-agent/hooks.ts +26 -0
  289. package/src/tests/fixtures/extensions/route-channel-to-agent/manifest.json +9 -0
  290. package/src/tests/fixtures/extensions/slow/hooks.ts +10 -0
  291. package/src/tests/fixtures/extensions/slow/manifest.json +9 -0
  292. package/src/tests/fixtures/extensions/suppress-lead-follow-up/hooks.ts +12 -0
  293. package/src/tests/fixtures/extensions/suppress-lead-follow-up/manifest.json +9 -0
  294. package/src/tests/fixtures/extensions/throws/hooks.ts +12 -0
  295. package/src/tests/fixtures/extensions/throws/manifest.json +9 -0
  296. package/src/tests/fixtures/extensions/worker-runtime/hooks.ts +5 -0
  297. package/src/tests/fixtures/extensions/worker-runtime/manifest.json +9 -0
  298. package/src/tests/fs-routes.test.ts +1 -1
  299. package/src/tests/gitlab-auth.test.ts +10 -10
  300. package/src/tests/harness-provider-resolution.test.ts +8 -5
  301. package/src/tests/heartbeat-reroute-decision.test.ts +1 -0
  302. package/src/tests/helper-credentials.test.ts +137 -0
  303. package/src/tests/http/context-routes.test.ts +2 -2
  304. package/src/tests/http-api-integration.test.ts +5 -2
  305. package/src/tests/http-users.test.ts +1 -1
  306. package/src/tests/identity-refresh.test.ts +2 -2
  307. package/src/tests/internal-ai/complete-structured.test.ts +2 -2
  308. package/src/tests/internal-ai/credentials.test.ts +19 -17
  309. package/src/tests/jira-metadata.test.ts +1 -1
  310. package/src/tests/jira-oauth.test.ts +5 -5
  311. package/src/tests/jira-sync.test.ts +1 -1
  312. package/src/tests/jira-webhook-lifecycle.test.ts +3 -3
  313. package/src/tests/jira-webhook.test.ts +2 -2
  314. package/src/tests/kapso-inbound.test.ts +2 -2
  315. package/src/tests/kv-http.test.ts +1 -1
  316. package/src/tests/kv-namespace-resolution.test.ts +1 -1
  317. package/src/tests/kv-page-proxy.test.ts +2 -2
  318. package/src/tests/launch-password-rejection.test.ts +2 -2
  319. package/src/tests/linear-webhook.test.ts +1 -1
  320. package/src/tests/mcp-oauth-dcr-reuse.test.ts +6 -6
  321. package/src/tests/mcp-oauth-ensure-token.test.ts +28 -22
  322. package/src/tests/mcp-oauth-manual-client.test.ts +16 -12
  323. package/src/tests/mcp-oauth-queries.test.ts +35 -32
  324. package/src/tests/mcp-oauth-resolve-secrets.test.ts +8 -8
  325. package/src/tests/mcp-oauth-wrapper.test.ts +20 -20
  326. package/src/tests/mcp-user-route.test.ts +1 -1
  327. package/src/tests/memory-edges.test.ts +1 -1
  328. package/src/tests/memory-edit-authorization.test.ts +32 -0
  329. package/src/tests/memory-rate-endpoint.test.ts +1 -1
  330. package/src/tests/memory-rate-tool.test.ts +2 -2
  331. package/src/tests/memory-rater-e2e.test.ts +1 -1
  332. package/src/tests/memory-rater-llm-summarizer.test.ts +3 -3
  333. package/src/tests/memory-rater-llm.test.ts +1 -1
  334. package/src/tests/model-control.test.ts +3 -0
  335. package/src/tests/multi-runtime-enabled-stats.test.ts +2 -2
  336. package/src/tests/multi-runtime-registration.test.ts +111 -17
  337. package/src/tests/oauth-access-token-tool.test.ts +18 -18
  338. package/src/tests/oauth-callback-flow.test.ts +8 -8
  339. package/src/tests/oauth-credential-bindings.test.ts +29 -27
  340. package/src/tests/oauth-keepalive.test.ts +10 -10
  341. package/src/tests/oauth-presets.test.ts +13 -5
  342. package/src/tests/oauth-refresh-failure.test.ts +9 -9
  343. package/src/tests/oauth-refresh-sweep.test.ts +20 -13
  344. package/src/tests/oauth-wrapper.test.ts +1 -1
  345. package/src/tests/onboard-compose.test.ts +47 -3
  346. package/src/tests/onboard-env.test.ts +26 -26
  347. package/src/tests/onboard-non-interactive.test.ts +4 -4
  348. package/src/tests/opencode-adapter.test.ts +1 -1
  349. package/src/tests/opencode-plugin.test.ts +2 -2
  350. package/src/tests/otel-session-cost-metrics.test.ts +1 -1
  351. package/src/tests/page-proxy-authed.test.ts +2 -2
  352. package/src/tests/page-proxy.test.ts +2 -2
  353. package/src/tests/page-session.test.ts +7 -7
  354. package/src/tests/page-viewer-auth.test.ts +2 -2
  355. package/src/tests/pages-authed-mode.test.ts +1 -1
  356. package/src/tests/pages-http.test.ts +1 -1
  357. package/src/tests/pages-password-mode.test.ts +1 -1
  358. package/src/tests/pages-public-authed-401.test.ts +1 -1
  359. package/src/tests/pages-public-html.test.ts +2 -2
  360. package/src/tests/pages-view-count.test.ts +1 -1
  361. package/src/tests/pi-mono-adapter.test.ts +10 -10
  362. package/src/tests/pi-mono-extension.test.ts +2 -2
  363. package/src/tests/preload.ts +5 -0
  364. package/src/tests/pricing-routes.test.ts +1 -1
  365. package/src/tests/profile-sync-warning.test.ts +8 -8
  366. package/src/tests/profile-sync.test.ts +8 -8
  367. package/src/tests/prompt-template-session.test.ts +36 -5
  368. package/src/tests/provider-process-group.test.ts +95 -4
  369. package/src/tests/providers/pi-cost.test.ts +1 -1
  370. package/src/tests/queue-stall-alarm.test.ts +1 -1
  371. package/src/tests/rbac-admission.test.ts +19 -4
  372. package/src/tests/rbac-charact-http.test.ts +1 -1
  373. package/src/tests/rbac-e2e-helpers.ts +1 -1
  374. package/src/tests/rbac-engine.test.ts +39 -0
  375. package/src/tests/realtime-room-auth.test.ts +1 -1
  376. package/src/tests/realtime-room-surfaces.test.ts +1 -1
  377. package/src/tests/reload-config.test.ts +45 -1
  378. package/src/tests/routing-affinity-inventory.test.ts +1 -1
  379. package/src/tests/routing-reason-contract.test.ts +76 -3
  380. package/src/tests/routing-reason-inventory.test.ts +11 -6
  381. package/src/tests/routing-source.test.ts +53 -0
  382. package/src/tests/runner-agent-fs-provision-retry.test.ts +1 -1
  383. package/src/tests/runner-context-preamble.test.ts +1 -1
  384. package/src/tests/runner-deployment-resume.test.ts +1 -1
  385. package/src/tests/runner-fallback-output.test.ts +1 -1
  386. package/src/tests/runner-identity-refresh.test.ts +1 -1
  387. package/src/tests/runner-skills-refresh.test.ts +1 -1
  388. package/src/tests/schedule-target-type.test.ts +3 -3
  389. package/src/tests/scheduled-tasks.test.ts +14 -5
  390. package/src/tests/scheduler-occurrence.test.ts +347 -0
  391. package/src/tests/script-apis-mcp.test.ts +1 -1
  392. package/src/tests/script-connections-http.test.ts +61 -58
  393. package/src/tests/script-connections-mcp.test.ts +1 -1
  394. package/src/tests/script-connections.test.ts +3 -1
  395. package/src/tests/script-runs-http.test.ts +15 -7
  396. package/src/tests/script-workflows-runtime-e2e.test.ts +1 -1
  397. package/src/tests/scripts-boot-reembed.test.ts +4 -0
  398. package/src/tests/scripts-embeddings.test.ts +4 -0
  399. package/src/tests/scripts-external-api.test.ts +1 -1
  400. package/src/tests/scripts-http.test.ts +1 -1
  401. package/src/tests/scripts-import-allowlist.test.ts +44 -1
  402. package/src/tests/scripts-mcp-e2e.test.ts +81 -1
  403. package/src/tests/scripts-only-gating.test.ts +4 -2
  404. package/src/tests/scripts-retention.test.ts +1 -1
  405. package/src/tests/scripts-runtime-identity.test.ts +2 -2
  406. package/src/tests/scripts-runtime-secret-egress.test.ts +1 -1
  407. package/src/tests/scripts-runtime.test.ts +6 -4
  408. package/src/tests/scripts-typecheck.test.ts +21 -0
  409. package/src/tests/sdk-allowlist.test.ts +1 -1
  410. package/src/tests/secret-scrubber.test.ts +38 -31
  411. package/src/tests/seed-registry.test.ts +46 -8
  412. package/src/tests/seed-schedules.test.ts +44 -9
  413. package/src/tests/seed-workflows.test.ts +21 -9
  414. package/src/tests/send-task-output-schema.test.ts +9 -1
  415. package/src/tests/send-task-requested-by.test.ts +1 -0
  416. package/src/tests/send-task-slack-routing-guard.test.ts +2 -0
  417. package/src/tests/session-costs-codex-recompute.test.ts +1 -1
  418. package/src/tests/session-costs-golden.test.ts +2 -2
  419. package/src/tests/session-costs-model-key-normalize.test.ts +2 -2
  420. package/src/tests/session-costs-recompute-all-providers.test.ts +1 -1
  421. package/src/tests/session-tokens.test.ts +1 -1
  422. package/src/tests/shutdown-error-scrubbing.test.ts +1 -1
  423. package/src/tests/slack-blocks.test.ts +12 -1
  424. package/src/tests/slack-file-tools-attachments.test.ts +1 -1
  425. package/src/tests/slack-inbound-files.test.ts +222 -86
  426. package/src/tests/slack-reaction-config.test.ts +5 -4
  427. package/src/tests/slack-read-boundaries.test.ts +1 -1
  428. package/src/tests/slack-render-v2.test.ts +196 -4
  429. package/src/tests/slack-router.test.ts +17 -2
  430. package/src/tests/slack-steering.test.ts +19 -0
  431. package/src/tests/status.test.ts +19 -7
  432. package/src/tests/steering-core.test.ts +1 -1
  433. package/src/tests/steering-enabled.test.ts +6 -8
  434. package/src/tests/steering-transport.test.ts +4 -4
  435. package/src/tests/store-progress-output-guidance.test.ts +34 -0
  436. package/src/tests/swarm-tool-result-gate.test.ts +11 -11
  437. package/src/tests/swarm-x-tool.test.ts +7 -5
  438. package/src/tests/sync-ai-toolbox-skills.test.ts +19 -0
  439. package/src/tests/task-supersede-resume.test.ts +1 -1
  440. package/src/tests/task-tool-preload-mcp.test.ts +4 -1
  441. package/src/tests/task-tools-ctx.test.ts +2 -0
  442. package/src/tests/tasks-requested-by-filter.test.ts +2 -2
  443. package/src/tests/telemetry-init.test.ts +11 -3
  444. package/src/tests/tool-annotations.test.ts +1 -1
  445. package/src/tests/tool-output-agent-id.test.ts +1 -0
  446. package/src/tests/tool-registrar-no-input.test.ts +42 -1
  447. package/src/tests/tracker-fold.test.ts +7 -7
  448. package/src/tests/tracker-tools.test.ts +3 -3
  449. package/src/tests/ui-followup-lead-delegation.test.ts +1 -0
  450. package/src/tests/unified-oauth-migration.test.ts +5 -5
  451. package/src/tests/user-token-rest-auth.test.ts +1 -1
  452. package/src/tests/user-token-routes.test.ts +1 -1
  453. package/src/tests/whoami-route.test.ts +1 -1
  454. package/src/tests/workflow-agent-task.test.ts +15 -13
  455. package/src/tests/workflow-ctx.test.ts +1 -1
  456. package/src/tests/workflow-e2e.test.ts +1 -1
  457. package/src/tests/workflow-engine-v2.test.ts +3 -1
  458. package/src/tests/workflow-executors.test.ts +2 -2
  459. package/src/tests/workflow-http-v2.test.ts +8 -6
  460. package/src/tests/workflow-input-redaction.test.ts +7 -5
  461. package/src/tests/workflow-integration-io.test.ts +2 -2
  462. package/src/tests/workflow-llm.test.ts +5 -5
  463. package/src/tests/workflow-registry.test.ts +1 -1
  464. package/src/tests/workflow-swarm-script.test.ts +1 -1
  465. package/src/tests/workflow-triggers-v2.test.ts +23 -21
  466. package/src/tests/x-composio.test.ts +9 -7
  467. package/src/tests/x402-client.test.ts +1 -1
  468. package/src/tests/x402-config.test.ts +28 -41
  469. package/src/tools/defer-task.ts +156 -24
  470. package/src/tools/extension-common.ts +84 -0
  471. package/src/tools/extension-install.ts +65 -0
  472. package/src/tools/extension-list.ts +66 -0
  473. package/src/tools/script-common.ts +43 -18
  474. package/src/tools/send-task.ts +87 -105
  475. package/src/tools/skills/skill-publish.ts +1 -0
  476. package/src/tools/store-progress.ts +1 -1
  477. package/src/tools/task-action.ts +43 -22
  478. package/src/tools/task-tool-ctx.ts +2 -0
  479. package/src/tools/templates.ts +2 -1
  480. package/src/tools/tool-config.ts +3 -1
  481. package/src/tools/utils.ts +140 -16
  482. package/src/types.ts +144 -0
  483. package/src/utils/constants.ts +7 -1
  484. package/src/utils/credentials.test.ts +16 -16
  485. package/src/utils/multi-runtime.ts +2 -2
  486. package/src/utils/steering-enabled.ts +4 -4
  487. package/src/workflows/executors/agent-task.ts +18 -3
  488. package/templates/ai-toolbox.manifest.json +7 -1
  489. package/templates/extensions/README.md +17 -0
  490. package/templates/extensions/notify-on-complete/README.md +11 -0
  491. package/templates/extensions/notify-on-complete/hooks.ts +61 -0
  492. package/templates/extensions/notify-on-complete/manifest.json +7 -0
  493. package/templates/extensions/require-ticket-ref/README.md +11 -0
  494. package/templates/extensions/require-ticket-ref/hooks.ts +30 -0
  495. package/templates/extensions/require-ticket-ref/manifest.json +7 -0
  496. package/templates/extensions/require-verification-note/README.md +11 -0
  497. package/templates/extensions/require-verification-note/hooks.ts +32 -0
  498. package/templates/extensions/require-verification-note/manifest.json +7 -0
  499. package/templates/schedules/daily-hn-briefing/config.json +1 -1
  500. package/templates/schedules/gtm-weekly-review/config.json +1 -1
  501. package/templates/schedules/weekly-code-health-reports/config.json +1 -1
  502. package/templates/schedules/weekly-dependabot-triage/config.json +1 -1
  503. package/templates/schedules/weekly-dora-metrics/config.json +1 -1
  504. package/templates/schedules/weekly-harness-upgrade-check/config.json +1 -1
  505. package/templates/skills/delegate-work/files/scripts/codex-exec.sh +1 -1
  506. package/templates/skills/scheduling/content.md +2 -0
  507. package/templates/skills/serply-search/SKILL.md +95 -0
  508. package/templates/skills/serply-search/config.json +13 -0
  509. package/templates/skills/serply-search/content.md +90 -0
  510. package/templates/skills/swarm-extensions/config.json +14 -0
  511. package/templates/skills/swarm-extensions/content.md +120 -0
  512. package/templates/skills/swarm-scripts/SKILL.md +2 -2
  513. package/templates/skills/swarm-scripts/content.md +2 -2
  514. package/templates/workflows/claude-code-changelog-watch/config.json +1 -1
  515. package/templates/workflows/gsc-topic-miner/config.json +1 -1
  516. package/templates/workflows/linear-drain-loop/config.json +1 -1
  517. package/templates/workflows/llm-safe-release-context/config.json +1 -1
  518. package/templates/workflows/ralph-loop/config.json +1 -1
  519. package/dist/cli-7fehefdf.js +0 -562
  520. package/dist/cli-ayvy5kde.js +0 -164
  521. package/dist/cli-ddfw1mc4.js +0 -4322
  522. package/dist/cli-hzqmgcm2.js +0 -3066
  523. package/dist/cli-j4d4f6hv.js +0 -338
  524. package/dist/cli-qgm2pfpq.js +0 -6377
  525. package/dist/cli-sdzq01t9.js +0 -823
  526. package/dist/cli-xvkmedgn.js +0 -332
  527. package/dist/cli-y3pq4v9g.js +0 -664
  528. package/dist/{anthropic-messages-vs2rabzp.js → anthropic-messages-2y8nktar.js} +19 -19
  529. package/dist/{claude-managed-setup-f5pmsyrp.js → claude-managed-setup-48dwg9bv.js} +3 -3
  530. package/dist/{cli-81r683bv.js → cli-ch38q5z4.js} +0 -0
  531. package/dist/{cli-6664w7y4.js → cli-kj76p1ez.js} +8 -8
  532. package/dist/{cli-c71mg51n.js → cli-r2fpe7cx.js} +5 -5
  533. package/dist/{cli-4xz3pmrq.js → cli-y6x0gqtg.js} +16 -16
  534. package/dist/{index-aa3sa6s8.js → index-2wsdsnmm.js} +1 -1
  535. package/dist/{mistral-conversations-4ryk7h83.js → mistral-conversations-qhr84ph3.js} +9 -9
  536. package/dist/{openai-completions-0bqmsajw.js → openai-completions-32rdwmze.js} +19 -19
  537. package/dist/{openrouter-images-qx18fyp8.js → openrouter-images-9f9ek4rw.js} +3 -3
  538. package/dist/{pi-messages-79jvzz1d.js → pi-messages-b1mbr6gp.js} +4 -4
@@ -0,0 +1,186 @@
1
+ import type { SwarmSdk } from "swarm-sdk";
2
+ import { insertExtensionRun } from "../be/extensions/db";
3
+ import { invokeToolInProcess } from "../http/mcp-bridge";
4
+ import { SDK_TOOL_NAME_MAP } from "../scripts-runtime/sdk-allowlist";
5
+ import { SwarmConfig } from "../scripts-runtime/swarm-config";
6
+ import { createSwarmSdk } from "../scripts-runtime/swarm-sdk";
7
+ import { getApiKey } from "../utils/api-key";
8
+ import { scrubSecrets } from "../utils/secret-scrubber";
9
+ import type { ApiCtx, ExtensionLogger, ExtensionState, SwarmEventMap } from "./contract";
10
+ import { ExtensionAbortedError, getExtensionBridgeToken, isRegistered } from "./dispatcher";
11
+ import { getExtensionLoopbackBaseUrl } from "./lifecycle";
12
+ import type { LoadedExtension } from "./loader";
13
+
14
+ type WireToolResult = {
15
+ structuredContent?: Record<string, unknown>;
16
+ isError?: boolean;
17
+ };
18
+
19
+ function wireResult(value: unknown): WireToolResult {
20
+ if (!value || typeof value !== "object") return {};
21
+ return value as WireToolResult;
22
+ }
23
+
24
+ async function callExtensionTool(
25
+ loaded: LoadedExtension,
26
+ toolName: string,
27
+ args?: unknown,
28
+ ): Promise<WireToolResult> {
29
+ if (!loaded.record.agentId) throw new Error("Extension agent identity is missing");
30
+ return wireResult(
31
+ await invokeToolInProcess({
32
+ toolName,
33
+ args,
34
+ agentId: loaded.record.agentId,
35
+ callOrigin: "extension",
36
+ }),
37
+ );
38
+ }
39
+
40
+ function prefixedStateKey(loaded: LoadedExtension, key: string): string {
41
+ return `ext:${loaded.record.name}:${key}`;
42
+ }
43
+
44
+ function createExtensionState(loaded: LoadedExtension): ExtensionState {
45
+ const invoke = async (toolName: string, args: Record<string, unknown>) => {
46
+ const result = await callExtensionTool(loaded, toolName, args);
47
+ const data = result.structuredContent ?? {};
48
+ if (result.isError) throw new Error(String(data.message ?? `${toolName} failed`));
49
+ return data;
50
+ };
51
+
52
+ return {
53
+ async get<T>(key: string): Promise<T | null> {
54
+ const data = await invoke(SDK_TOOL_NAME_MAP.kv_get, { key: prefixedStateKey(loaded, key) });
55
+ const entry = data.entry as { value?: T } | null | undefined;
56
+ return entry?.value ?? null;
57
+ },
58
+ async set<T>(key: string, value: T): Promise<void> {
59
+ await invoke(SDK_TOOL_NAME_MAP.kv_set, { key: prefixedStateKey(loaded, key), value });
60
+ },
61
+ async incr(key: string, by = 1): Promise<number> {
62
+ const data = await invoke(SDK_TOOL_NAME_MAP.kv_incr, {
63
+ key: prefixedStateKey(loaded, key),
64
+ by,
65
+ });
66
+ const entry = data.entry as { value?: unknown } | undefined;
67
+ if (typeof entry?.value !== "number") throw new Error("KV increment returned no number");
68
+ return entry.value;
69
+ },
70
+ async del(key: string): Promise<void> {
71
+ await invoke(SDK_TOOL_NAME_MAP.kv_delete, { key: prefixedStateKey(loaded, key) });
72
+ },
73
+ };
74
+ }
75
+
76
+ function logText(message: string, data?: unknown): string {
77
+ let suffix = "";
78
+ if (data !== undefined) {
79
+ try {
80
+ suffix = ` ${typeof data === "string" ? data : JSON.stringify(data)}`;
81
+ } catch {
82
+ suffix = " [unserializable]";
83
+ }
84
+ }
85
+ return scrubSecrets(`${message}${suffix}`);
86
+ }
87
+
88
+ function createExtensionLogger(
89
+ loaded: LoadedExtension,
90
+ eventName: keyof SwarmEventMap,
91
+ ): ExtensionLogger {
92
+ const prefix = `[extension:${loaded.record.name}]`;
93
+ return {
94
+ debug(message, data) {
95
+ console.debug(prefix, logText(message, data));
96
+ },
97
+ info(message, data) {
98
+ console.info(prefix, logText(message, data));
99
+ },
100
+ warn(message, data) {
101
+ console.warn(prefix, logText(message, data));
102
+ },
103
+ error(message, data) {
104
+ const text = logText(message, data);
105
+ console.error(prefix, text);
106
+ void insertExtensionRun({
107
+ extensionId: loaded.record.id,
108
+ version: loaded.record.activeVersion,
109
+ event: eventName,
110
+ action: "error",
111
+ message: text,
112
+ }).catch((error) => {
113
+ console.error(
114
+ "[extensions] Failed to store extension log:",
115
+ scrubSecrets(error instanceof Error ? error.message : String(error)),
116
+ );
117
+ });
118
+ },
119
+ };
120
+ }
121
+
122
+ export function buildCtx(
123
+ loaded: LoadedExtension,
124
+ eventName: keyof SwarmEventMap,
125
+ signal: AbortSignal,
126
+ ): ApiCtx {
127
+ const assertActive = () => {
128
+ if (signal.aborted || !isRegistered(loaded.record.id)) throw new ExtensionAbortedError();
129
+ };
130
+ const guard = <T extends object>(target: T): T =>
131
+ new Proxy(target, {
132
+ get(object, property, receiver) {
133
+ const value = Reflect.get(object, property, receiver);
134
+ if (typeof value !== "function") return value;
135
+ return (...args: unknown[]) => {
136
+ assertActive();
137
+ return Reflect.apply(value, object, args);
138
+ };
139
+ },
140
+ });
141
+
142
+ // Defer SDK construction until a call, because boot precedes the HTTP listener.
143
+ let sdk: ReturnType<typeof createSwarmSdk> | undefined;
144
+ const getSdk = () => {
145
+ if (!loaded.record.agentId) throw new Error("Extension agent identity is missing");
146
+ sdk ??= createSwarmSdk(
147
+ new SwarmConfig({
148
+ system: {
149
+ apiKey: { value: getApiKey(), isSecret: true },
150
+ agentId: { value: loaded.record.agentId, isSecret: false },
151
+ mcpBaseUrl: { value: getExtensionLoopbackBaseUrl(), isSecret: false },
152
+ extensionToken: { value: getExtensionBridgeToken(), isSecret: true },
153
+ },
154
+ user: {},
155
+ }),
156
+ );
157
+ return sdk;
158
+ };
159
+ const swarmProxy = (room = false): object =>
160
+ new Proxy(
161
+ {},
162
+ {
163
+ get(_target, property) {
164
+ if (!room && property === "room") return swarmProxy(true);
165
+ if (typeof property !== "string") return undefined;
166
+ return async (...args: unknown[]) => {
167
+ assertActive();
168
+ const target = room ? getSdk().room : getSdk();
169
+ return await Reflect.apply(Reflect.get(target, property), target, args);
170
+ };
171
+ },
172
+ },
173
+ );
174
+ return {
175
+ swarm: swarmProxy() as SwarmSdk,
176
+ state: guard(createExtensionState(loaded)),
177
+ config: loaded.config,
178
+ log: guard(createExtensionLogger(loaded, eventName)),
179
+ signal,
180
+ event: {
181
+ name: eventName,
182
+ at: new Date().toISOString(),
183
+ extension: { id: loaded.record.id, version: loaded.record.activeVersion },
184
+ },
185
+ };
186
+ }
@@ -0,0 +1,479 @@
1
+ import { isInTransaction } from "../be/db-client";
2
+ import { insertExtensionRun, recordExtensionFailure, setExtensionState } from "../be/extensions/db";
3
+ import { grantLeadEquivalence, revokeLeadEquivalence } from "../rbac/elevated-agents";
4
+ import { scrubSecrets } from "../utils/secret-scrubber";
5
+ import type { SwarmEventMap } from "./contract";
6
+ import { buildCtx } from "./ctx";
7
+ import type { ExtensionHandler, LoadedExtension } from "./loader";
8
+
9
+ type PreEventName = Extract<keyof SwarmEventMap, `pre.${string}`>;
10
+ type PostEventName = Extract<keyof SwarmEventMap, `post.${string}`>;
11
+ type DispatchOptions = { skipExtensionId?: string };
12
+ type PreDispatchOptions<E extends PreEventName> = DispatchOptions & {
13
+ /** Boundary-specific reshaping of a modify result (runs before validateModify). */
14
+ transformModify?: (args: {
15
+ data: Record<string, unknown>;
16
+ currentPayload: Record<string, unknown>;
17
+ extension: { id: string; name: string; version: number };
18
+ }) => Record<string, unknown>;
19
+ /** Boundary-specific schema check of a modify result; a failure is attributed to the extension. */
20
+ validateModify?: (
21
+ data: SwarmEventMap[E]["modify"],
22
+ ) =>
23
+ | { success: true; data: SwarmEventMap[E]["modify"] }
24
+ | { success: false; error: unknown }
25
+ | Promise<
26
+ { success: true; data: SwarmEventMap[E]["modify"] } | { success: false; error: unknown }
27
+ >;
28
+ };
29
+
30
+ export type PreDispatchResult<E extends PreEventName> =
31
+ | { action: "continue" }
32
+ | { action: "modify"; data: SwarmEventMap[E]["modify"] }
33
+ | {
34
+ action: "block";
35
+ reason: string;
36
+ extension: { id: string; name: string };
37
+ };
38
+
39
+ const registry = new Map<string, LoadedExtension>();
40
+ const extensionAgentIds = new Set<string>();
41
+ const extensionByAgent = new Map<string, string>();
42
+
43
+ export class ExtensionAbortedError extends Error {
44
+ constructor() {
45
+ super("Extension handler is aborted or unregistered");
46
+ this.name = "ExtensionAbortedError";
47
+ }
48
+ }
49
+
50
+ export function isRegistered(id: string): boolean {
51
+ return registry.has(id);
52
+ }
53
+
54
+ export function isExtensionAgentId(agentId: string | null | undefined): boolean {
55
+ return !!agentId && extensionAgentIds.has(agentId);
56
+ }
57
+
58
+ /**
59
+ * Per-process secret that authenticates extension-originated loopback calls. Only the
60
+ * in-process `ctx.swarm` SDK holds it; the bridge grants `callOrigin: "extension"` solely
61
+ * when the request presents it, so an agent header alone can never claim that origin.
62
+ */
63
+ const extensionBridgeToken = crypto.randomUUID();
64
+
65
+ export function getExtensionBridgeToken(): string {
66
+ return extensionBridgeToken;
67
+ }
68
+
69
+ /** Resolve the bridge call origin from the agent identity plus the per-process token. */
70
+ export function resolveBridgeCallOrigin(
71
+ agentId: string | undefined,
72
+ token: string | undefined,
73
+ ): "extension" | "script-sdk" {
74
+ return isExtensionAgentId(agentId) && token === extensionBridgeToken ? "extension" : "script-sdk";
75
+ }
76
+
77
+ export function extensionIdForAgent(agentId: string | null | undefined): string | undefined {
78
+ return agentId && isExtensionAgentId(agentId) ? extensionByAgent.get(agentId) : undefined;
79
+ }
80
+
81
+ class ExtensionTimeoutError extends Error {
82
+ constructor() {
83
+ super("Extension handler timed out");
84
+ this.name = "ExtensionTimeoutError";
85
+ }
86
+ }
87
+
88
+ function positiveEnv(name: string, fallback: number): number {
89
+ const value = Number(process.env[name]);
90
+ return Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
91
+ }
92
+
93
+ function handlersFor(event: keyof SwarmEventMap, opts?: DispatchOptions) {
94
+ return [...registry.values()]
95
+ .filter((loaded) => loaded.record.id !== opts?.skipExtensionId)
96
+ .flatMap((loaded) =>
97
+ loaded.handlers
98
+ .filter((handler) => handler.event === event)
99
+ .map((handler) => ({ loaded, handler })),
100
+ )
101
+ .sort(
102
+ (a, b) =>
103
+ a.handler.priority - b.handler.priority ||
104
+ a.loaded.record.name.localeCompare(b.loaded.record.name),
105
+ );
106
+ }
107
+
108
+ type RunContext = { agentId: string | null; subject: string | null };
109
+
110
+ function snippet(text: unknown, max = 80): string {
111
+ const flat = String(text ?? "")
112
+ .replace(/\s+/g, " ")
113
+ .trim();
114
+ return flat.length > max ? `${flat.slice(0, max - 1)}…` : flat;
115
+ }
116
+
117
+ /**
118
+ * Who and what a run row refers to, so the run log can say which agent was
119
+ * blocked and on what. Never stores the full payload: subjects are short and
120
+ * secret-scrubbed.
121
+ */
122
+ function field(source: unknown, ...path: string[]): unknown {
123
+ let current: unknown = source;
124
+ for (const key of path) {
125
+ if (typeof current !== "object" || current === null) return undefined;
126
+ current = (current as Record<string, unknown>)[key];
127
+ }
128
+ return current;
129
+ }
130
+
131
+ function stringField(source: unknown, ...path: string[]): string | null {
132
+ const value = field(source, ...path);
133
+ return typeof value === "string" ? value : null;
134
+ }
135
+
136
+ export function runContext(event: keyof SwarmEventMap, payload: unknown): RunContext {
137
+ const requestAgent = stringField(payload, "requestInfo", "agentId");
138
+ let agentId: string | null = null;
139
+ let subject: string | null = null;
140
+ switch (event) {
141
+ case "pre.tool.call":
142
+ case "post.tool.call":
143
+ agentId = requestAgent;
144
+ subject = `tool ${stringField(payload, "tool") ?? "?"}`;
145
+ break;
146
+ case "pre.task.create":
147
+ agentId = requestAgent ?? stringField(payload, "options", "agentId");
148
+ subject = `${stringField(payload, "origin") ?? "?"}: ${snippet(field(payload, "description"))}`;
149
+ break;
150
+ case "pre.task.followUp":
151
+ agentId = stringField(payload, "workerAgentId");
152
+ subject =
153
+ `task ${stringField(payload, "completedTask", "id") ?? "?"} ${stringField(payload, "status") ?? ""}`.trim();
154
+ break;
155
+ case "pre.slack.route":
156
+ case "post.slack.message":
157
+ subject = `channel ${stringField(payload, "channelId") ?? "?"}: ${snippet(field(payload, "text"), 60)}`;
158
+ break;
159
+ case "pre.heartbeat.remediate":
160
+ agentId = stringField(payload, "task", "agentId");
161
+ subject = `task ${stringField(payload, "task", "id") ?? "?"} ${stringField(payload, "classification") ?? ""} -> ${stringField(payload, "proposedAction") ?? ""}`;
162
+ break;
163
+ default: {
164
+ agentId = stringField(payload, "task", "agentId");
165
+ const taskId = stringField(payload, "task", "id");
166
+ subject = taskId ? `task ${taskId}: ${snippet(field(payload, "task", "task"))}` : null;
167
+ }
168
+ }
169
+ return { agentId, subject: subject ? scrubSecrets(subject) : null };
170
+ }
171
+
172
+ async function writeRun(
173
+ loaded: LoadedExtension,
174
+ event: keyof SwarmEventMap,
175
+ action: "continue" | "modify" | "block" | "error" | "timeout",
176
+ durationMs: number,
177
+ message?: string,
178
+ context?: RunContext,
179
+ ): Promise<void> {
180
+ try {
181
+ await insertExtensionRun({
182
+ extensionId: loaded.record.id,
183
+ version: loaded.record.activeVersion,
184
+ event,
185
+ action,
186
+ durationMs,
187
+ message: message ?? null,
188
+ agentId: context?.agentId ?? null,
189
+ subject: context?.subject ?? null,
190
+ });
191
+ } catch (error) {
192
+ console.error(
193
+ "[extensions] Failed to store handler run:",
194
+ scrubSecrets(error instanceof Error ? error.message : String(error)),
195
+ );
196
+ }
197
+ }
198
+
199
+ async function markSuccess(loaded: LoadedExtension): Promise<void> {
200
+ if (loaded.record.consecutiveFailures === 0 && loaded.record.lastError === null) return;
201
+ try {
202
+ const next = await setExtensionState(loaded.record.id, {
203
+ consecutiveFailures: 0,
204
+ lastError: null,
205
+ });
206
+ if (next) loaded.record = next;
207
+ } catch (error) {
208
+ console.error(
209
+ "[extensions] Failed to reset failure count:",
210
+ scrubSecrets(error instanceof Error ? error.message : String(error)),
211
+ );
212
+ }
213
+ }
214
+
215
+ async function markFailure(
216
+ loaded: LoadedExtension,
217
+ event: keyof SwarmEventMap,
218
+ error: unknown,
219
+ durationMs: number,
220
+ payload?: unknown,
221
+ ): Promise<void> {
222
+ const timedOut = error instanceof ExtensionTimeoutError;
223
+ const message = scrubSecrets(error instanceof Error ? error.message : String(error));
224
+ await writeRun(
225
+ loaded,
226
+ event,
227
+ timedOut ? "timeout" : "error",
228
+ durationMs,
229
+ message,
230
+ runContext(event, payload),
231
+ );
232
+ try {
233
+ const next = await recordExtensionFailure(
234
+ loaded.record.id,
235
+ message,
236
+ positiveEnv("EXTENSION_MAX_CONSECUTIVE_FAILURES", 5),
237
+ );
238
+ if (!next) return;
239
+ loaded.record = next;
240
+ if (next.status === "auto-disabled") {
241
+ unregister(next.id);
242
+ await loaded.dispose();
243
+ }
244
+ } catch (persistError) {
245
+ console.error(
246
+ "[extensions] Failed to update failure count:",
247
+ scrubSecrets(persistError instanceof Error ? persistError.message : String(persistError)),
248
+ );
249
+ }
250
+ }
251
+
252
+ async function runHandler(
253
+ loaded: LoadedExtension,
254
+ handler: ExtensionHandler,
255
+ event: keyof SwarmEventMap,
256
+ payload: unknown,
257
+ ): Promise<{ ok: true; result: unknown; durationMs: number } | { ok: false }> {
258
+ const controller = new AbortController();
259
+ const startedAt = Date.now();
260
+ let timeout: ReturnType<typeof setTimeout> | undefined;
261
+ try {
262
+ const timeoutPromise = new Promise<never>((_, reject) => {
263
+ timeout = setTimeout(
264
+ () => {
265
+ controller.abort();
266
+ reject(new ExtensionTimeoutError());
267
+ },
268
+ positiveEnv("EXTENSION_HANDLER_TIMEOUT_MS", 5_000),
269
+ );
270
+ timeout.unref?.();
271
+ });
272
+ const result = await Promise.race([
273
+ Promise.resolve().then(() =>
274
+ handler.handler(payload as never, buildCtx(loaded, event, controller.signal)),
275
+ ),
276
+ timeoutPromise,
277
+ ]);
278
+ const durationMs = Date.now() - startedAt;
279
+ return { ok: true, result, durationMs };
280
+ } catch (error) {
281
+ await markFailure(loaded, event, error, Date.now() - startedAt, payload);
282
+ return { ok: false };
283
+ } finally {
284
+ if (timeout) clearTimeout(timeout);
285
+ }
286
+ }
287
+
288
+ function definedObject(value: unknown): Record<string, unknown> {
289
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
290
+ throw new Error("Extension modify result data must be an object");
291
+ }
292
+ return Object.fromEntries(
293
+ Object.entries(value as Record<string, unknown>).filter(([, child]) => child !== undefined),
294
+ );
295
+ }
296
+
297
+ function mergePayload(
298
+ event: PreEventName,
299
+ payload: Record<string, unknown>,
300
+ data: Record<string, unknown>,
301
+ ): Record<string, unknown> {
302
+ if (event === "pre.task.create") {
303
+ const { description, ...optionChanges } = data;
304
+ return {
305
+ ...payload,
306
+ ...(description === undefined ? {} : { description }),
307
+ options: {
308
+ ...((payload.options as Record<string, unknown> | undefined) ?? {}),
309
+ ...optionChanges,
310
+ },
311
+ };
312
+ }
313
+ if (event === "pre.task.followUp" && data.description !== undefined) {
314
+ const { description, ...changes } = data;
315
+ return { ...payload, ...changes, summary: description };
316
+ }
317
+ return { ...payload, ...data };
318
+ }
319
+
320
+ export function registerLoaded(loaded: LoadedExtension): void {
321
+ unregister(loaded.record.id);
322
+ registry.set(loaded.record.id, loaded);
323
+ if (loaded.record.agentId) {
324
+ extensionAgentIds.add(loaded.record.agentId);
325
+ extensionByAgent.set(loaded.record.agentId, loaded.record.id);
326
+ grantLeadEquivalence(loaded.record.agentId);
327
+ }
328
+ }
329
+
330
+ export function unregister(extensionId: string): LoadedExtension | undefined {
331
+ const loaded = registry.get(extensionId);
332
+ registry.delete(extensionId);
333
+ if (loaded?.record.agentId) {
334
+ extensionAgentIds.delete(loaded.record.agentId);
335
+ extensionByAgent.delete(loaded.record.agentId);
336
+ revokeLeadEquivalence(loaded.record.agentId);
337
+ }
338
+ return loaded;
339
+ }
340
+
341
+ export function listRegistered(): LoadedExtension[] {
342
+ return [...registry.values()];
343
+ }
344
+
345
+ export async function dispatchPre<E extends PreEventName>(
346
+ event: E,
347
+ payload: SwarmEventMap[E]["event"],
348
+ opts?: PreDispatchOptions<E>,
349
+ ): Promise<PreDispatchResult<E>> {
350
+ if (isInTransaction()) {
351
+ console.error("[extensions] pre dispatch inside transaction:", scrubSecrets(String(event)));
352
+ return { action: "continue" };
353
+ }
354
+
355
+ let currentPayload = payload as unknown as Record<string, unknown>;
356
+ let mergedData: Record<string, unknown> = {};
357
+ let modified = false;
358
+ for (const { loaded, handler } of handlersFor(event, opts)) {
359
+ if (!isRegistered(loaded.record.id)) continue;
360
+ const run = await runHandler(loaded, handler, event, currentPayload);
361
+ if (!run.ok) continue;
362
+ const result = run.result;
363
+ if (
364
+ result === undefined ||
365
+ (typeof result === "object" &&
366
+ result !== null &&
367
+ (result as { action?: unknown }).action === "continue")
368
+ ) {
369
+ await markSuccess(loaded);
370
+ await writeRun(
371
+ loaded,
372
+ event,
373
+ "continue",
374
+ run.durationMs,
375
+ undefined,
376
+ runContext(event, currentPayload),
377
+ );
378
+ continue;
379
+ }
380
+ if (typeof result !== "object" || result === null) {
381
+ await markFailure(
382
+ loaded,
383
+ event,
384
+ new Error("Extension pre handler returned an invalid result"),
385
+ run.durationMs,
386
+ );
387
+ continue;
388
+ }
389
+ const preResult = result as { action?: unknown; data?: unknown; reason?: unknown };
390
+ if (preResult.action === "block" && typeof preResult.reason === "string") {
391
+ const reason = scrubSecrets(preResult.reason);
392
+ await markSuccess(loaded);
393
+ await writeRun(
394
+ loaded,
395
+ event,
396
+ "block",
397
+ run.durationMs,
398
+ reason,
399
+ runContext(event, currentPayload),
400
+ );
401
+ return {
402
+ action: "block",
403
+ reason,
404
+ extension: { id: loaded.record.id, name: loaded.record.name },
405
+ };
406
+ }
407
+ if (preResult.action === "modify") {
408
+ try {
409
+ let data = definedObject(preResult.data);
410
+ if (opts?.transformModify) {
411
+ data = opts.transformModify({
412
+ data,
413
+ currentPayload,
414
+ extension: {
415
+ id: loaded.record.id,
416
+ name: loaded.record.name,
417
+ version: loaded.record.activeVersion,
418
+ },
419
+ });
420
+ }
421
+ if (opts?.validateModify) {
422
+ const validation = await opts.validateModify(data as SwarmEventMap[E]["modify"]);
423
+ if (!validation.success) throw validation.error;
424
+ data = definedObject(validation.data);
425
+ }
426
+ mergedData = { ...mergedData, ...data };
427
+ currentPayload = mergePayload(event, currentPayload, data);
428
+ modified = true;
429
+ await markSuccess(loaded);
430
+ await writeRun(
431
+ loaded,
432
+ event,
433
+ "modify",
434
+ run.durationMs,
435
+ undefined,
436
+ runContext(event, currentPayload),
437
+ );
438
+ } catch (error) {
439
+ await markFailure(loaded, event, error, run.durationMs, currentPayload);
440
+ }
441
+ continue;
442
+ }
443
+ await markFailure(
444
+ loaded,
445
+ event,
446
+ new Error("Extension pre handler returned an invalid result"),
447
+ run.durationMs,
448
+ currentPayload,
449
+ );
450
+ }
451
+
452
+ return modified
453
+ ? { action: "modify", data: mergedData as SwarmEventMap[E]["modify"] }
454
+ : { action: "continue" };
455
+ }
456
+
457
+ // Bus events emit afterCommit, so post handlers observe committed state only.
458
+ // Post dispatch therefore needs no isInTransaction guard.
459
+ export async function dispatchPost<E extends PostEventName>(
460
+ event: E,
461
+ payload: SwarmEventMap[E]["event"],
462
+ opts?: DispatchOptions,
463
+ ): Promise<void> {
464
+ for (const { loaded, handler } of handlersFor(event, opts)) {
465
+ if (!isRegistered(loaded.record.id)) continue;
466
+ const run = await runHandler(loaded, handler, event, payload);
467
+ if (run.ok) {
468
+ await markSuccess(loaded);
469
+ await writeRun(
470
+ loaded,
471
+ event,
472
+ "continue",
473
+ run.durationMs,
474
+ undefined,
475
+ runContext(event, payload),
476
+ );
477
+ }
478
+ }
479
+ }
@@ -0,0 +1,44 @@
1
+ import {
2
+ createAgent,
3
+ getAllAgents,
4
+ getDbClient,
5
+ updateAgentMaxTasks,
6
+ updateAgentProfile,
7
+ updateAgentStatus,
8
+ } from "../be/db";
9
+
10
+ function extensionAgentName(name: string): string {
11
+ return `ext:${name}`;
12
+ }
13
+
14
+ export async function ensureExtensionAgent(name: string): Promise<string> {
15
+ const agentName = extensionAgentName(name);
16
+ return await getDbClient().transaction(async () => {
17
+ let agent = (await getAllAgents()).find((candidate) => candidate.name === agentName);
18
+ if (!agent) {
19
+ agent = await createAgent({
20
+ name: agentName,
21
+ isLead: false,
22
+ status: "offline",
23
+ maxTasks: 0,
24
+ capabilities: [],
25
+ });
26
+ }
27
+
28
+ if (agent.status !== "offline") await updateAgentStatus(agent.id, "offline");
29
+ if (agent.maxTasks !== 0) await updateAgentMaxTasks(agent.id, 0);
30
+ await updateAgentProfile(agent.id, {
31
+ description: `System agent for extension ${name}`,
32
+ role: "extension",
33
+ capabilities: [],
34
+ });
35
+ return agent.id;
36
+ });
37
+ }
38
+
39
+ export async function deactivateExtensionAgent(name: string): Promise<void> {
40
+ const agent = (await getAllAgents()).find(
41
+ (candidate) => candidate.name === extensionAgentName(name),
42
+ );
43
+ if (agent && agent.status !== "offline") await updateAgentStatus(agent.id, "offline");
44
+ }