@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,281 @@
1
+ import {
2
+ activateExtensionVersionSnapshot,
3
+ getExtensionById,
4
+ getExtensionFiles,
5
+ getExtensionVersion,
6
+ listExtensions,
7
+ pruneExtensionRuns,
8
+ setExtensionState,
9
+ } from "../be/extensions/db";
10
+ import type { Extension } from "../types";
11
+ import { ExtensionManifestSchema } from "../types";
12
+ import { scrubSecrets } from "../utils/secret-scrubber";
13
+ import { listRegistered, registerLoaded, unregister } from "./dispatcher";
14
+ import { deactivateExtensionAgent, ensureExtensionAgent } from "./identity";
15
+ import {
16
+ cleanExtensionTmpRoot,
17
+ type LoadableExtension,
18
+ loadExtension,
19
+ removeExtensionTmpRoot,
20
+ } from "./loader";
21
+ import { initExtensionPostBridge, teardownExtensionPostBridge } from "./post-bridge";
22
+
23
+ const EXTENSION_RELOAD_INTERVAL_MS = 30_000;
24
+
25
+ let reloadTimer: ReturnType<typeof setTimeout> | undefined;
26
+ let pollInFlight: Promise<void> | undefined;
27
+ let polling = false;
28
+ let loopbackBaseUrl: string | undefined;
29
+ let lastObservedUpdate = "";
30
+ const attemptedFingerprints = new Map<string, string>();
31
+
32
+ export class ExtensionLifecycleError extends Error {
33
+ constructor(
34
+ message: string,
35
+ readonly status: 400 | 404 = 400,
36
+ ) {
37
+ super(message);
38
+ this.name = "ExtensionLifecycleError";
39
+ }
40
+ }
41
+
42
+ export function setExtensionLoopbackBaseUrl(url: string): void {
43
+ loopbackBaseUrl = url;
44
+ }
45
+
46
+ export function getExtensionLoopbackBaseUrl(): string {
47
+ if (!loopbackBaseUrl) throw new ExtensionLifecycleError("extension loopback not ready");
48
+ return loopbackBaseUrl;
49
+ }
50
+
51
+ function extensionFingerprint(extension: Extension): string {
52
+ return JSON.stringify({
53
+ activeVersion: extension.activeVersion,
54
+ configJson: extension.configJson,
55
+ enabled: extension.enabled,
56
+ priority: extension.priority,
57
+ });
58
+ }
59
+
60
+ function maxUpdatedAt(extensions: Extension[]): string {
61
+ return extensions.reduce(
62
+ (latest, extension) => (extension.updatedAt > latest ? extension.updatedAt : latest),
63
+ "",
64
+ );
65
+ }
66
+
67
+ async function loadableExtension(record: Extension): Promise<LoadableExtension> {
68
+ if (record.activeVersion !== record.version) {
69
+ const version = await getExtensionVersion(record.id, record.activeVersion);
70
+ if (!version) {
71
+ throw new ExtensionLifecycleError(
72
+ `Extension version ${record.activeVersion} was not found`,
73
+ 404,
74
+ );
75
+ }
76
+ return {
77
+ record: {
78
+ ...record,
79
+ manifestJson: version.manifestJson,
80
+ contentHash: version.contentHash,
81
+ },
82
+ manifest: ExtensionManifestSchema.parse(JSON.parse(version.manifestJson)),
83
+ files: JSON.parse(version.filesJson) as Record<string, string>,
84
+ };
85
+ }
86
+ return {
87
+ record,
88
+ manifest: ExtensionManifestSchema.parse(JSON.parse(record.manifestJson)),
89
+ files: await getExtensionFiles(record.id),
90
+ };
91
+ }
92
+
93
+ async function removeRegistered(id: string): Promise<void> {
94
+ const previous = unregister(id);
95
+ if (previous) await previous.dispose();
96
+ }
97
+
98
+ export async function enableExtension(
99
+ id: string,
100
+ opts: { by?: string | null } = {},
101
+ ): Promise<Extension> {
102
+ initExtensionPostBridge();
103
+ let record = await getExtensionById(id);
104
+ if (!record) throw new ExtensionLifecycleError("Extension not found", 404);
105
+
106
+ const agentId = await ensureExtensionAgent(record.name);
107
+ const identified = await setExtensionState(id, { agentId, updatedBy: opts.by });
108
+ if (!identified) throw new ExtensionLifecycleError("Extension not found", 404);
109
+ record = identified;
110
+
111
+ await removeRegistered(id);
112
+ attemptedFingerprints.set(id, extensionFingerprint(record));
113
+ const loaded = await loadExtension(await loadableExtension(record));
114
+ registerLoaded(loaded);
115
+ try {
116
+ const enabled = await setExtensionState(id, {
117
+ enabled: true,
118
+ status: "enabled",
119
+ consecutiveFailures: 0,
120
+ lastError: null,
121
+ agentId,
122
+ updatedBy: opts.by,
123
+ });
124
+ if (!enabled) throw new ExtensionLifecycleError("Extension not found", 404);
125
+ loaded.record = enabled;
126
+ attemptedFingerprints.set(id, extensionFingerprint(enabled));
127
+ return enabled;
128
+ } catch (error) {
129
+ await removeRegistered(id);
130
+ throw error;
131
+ }
132
+ }
133
+
134
+ export async function disableExtension(
135
+ id: string,
136
+ opts: { by?: string | null } = {},
137
+ ): Promise<Extension> {
138
+ const record = await getExtensionById(id);
139
+ if (!record) throw new ExtensionLifecycleError("Extension not found", 404);
140
+ await removeRegistered(id);
141
+ attemptedFingerprints.delete(id);
142
+ await deactivateExtensionAgent(record.name);
143
+ const disabled = await setExtensionState(id, {
144
+ enabled: false,
145
+ status: "disabled",
146
+ updatedBy: opts.by,
147
+ });
148
+ if (!disabled) throw new ExtensionLifecycleError("Extension not found", 404);
149
+ return disabled;
150
+ }
151
+
152
+ export async function activateVersion(
153
+ id: string,
154
+ version: number,
155
+ opts: { by?: string | null } = {},
156
+ ): Promise<Extension> {
157
+ const current = await getExtensionById(id);
158
+ if (!current) throw new ExtensionLifecycleError("Extension not found", 404);
159
+ if (!(await getExtensionVersion(id, version))) {
160
+ throw new ExtensionLifecycleError(`Extension version ${version} was not found`, 404);
161
+ }
162
+
163
+ const wasEnabled = current.enabled;
164
+ if (wasEnabled) await disableExtension(id, opts);
165
+ const activated = await activateExtensionVersionSnapshot(id, version, opts.by);
166
+ if (!activated)
167
+ throw new ExtensionLifecycleError(`Extension version ${version} was not found`, 404);
168
+ return wasEnabled ? await enableExtension(id, opts) : activated;
169
+ }
170
+
171
+ export async function reloadExtension(
172
+ id: string,
173
+ opts: { by?: string | null } = {},
174
+ ): Promise<Extension> {
175
+ const record = await getExtensionById(id);
176
+ if (!record) throw new ExtensionLifecycleError("Extension not found", 404);
177
+ if (!record.enabled) return record;
178
+ return await enableExtension(id, opts);
179
+ }
180
+
181
+ async function syncEnabledExtensions(extensions: Extension[]): Promise<void> {
182
+ const enabledIds = new Set(
183
+ extensions.filter((extension) => extension.enabled).map((extension) => extension.id),
184
+ );
185
+ for (const loaded of listRegistered()) {
186
+ if (!enabledIds.has(loaded.record.id)) {
187
+ await removeRegistered(loaded.record.id);
188
+ attemptedFingerprints.delete(loaded.record.id);
189
+ }
190
+ }
191
+
192
+ for (const extension of extensions) {
193
+ if (!extension.enabled) continue;
194
+ const fingerprint = extensionFingerprint(extension);
195
+ const registered = listRegistered().find((loaded) => loaded.record.id === extension.id);
196
+ if (registered && extensionFingerprint(registered.record) === fingerprint) continue;
197
+ if (!registered && attemptedFingerprints.get(extension.id) === fingerprint) continue;
198
+ attemptedFingerprints.set(extension.id, fingerprint);
199
+ try {
200
+ await enableExtension(extension.id);
201
+ } catch (error) {
202
+ console.error(
203
+ `[extensions] Reload failed for ${extension.name}:`,
204
+ scrubSecrets(error instanceof Error ? error.message : String(error)),
205
+ );
206
+ }
207
+ }
208
+ }
209
+
210
+ async function pollForExtensionChanges(): Promise<void> {
211
+ const extensions = await listExtensions();
212
+ const observedUpdate = maxUpdatedAt(extensions);
213
+ if (observedUpdate !== lastObservedUpdate) {
214
+ lastObservedUpdate = observedUpdate;
215
+ await syncEnabledExtensions(extensions);
216
+ }
217
+ for (const extension of extensions) await pruneExtensionRuns(extension.id);
218
+ }
219
+
220
+ function startReloadPoll(): void {
221
+ if (polling) return;
222
+ polling = true;
223
+ scheduleReloadPoll();
224
+ }
225
+
226
+ function scheduleReloadPoll(): void {
227
+ if (!polling) return;
228
+ reloadTimer = setTimeout(() => {
229
+ reloadTimer = undefined;
230
+ pollInFlight = pollForExtensionChanges()
231
+ .catch((error) => {
232
+ console.error(
233
+ "[extensions] Reload poll failed:",
234
+ scrubSecrets(error instanceof Error ? error.message : String(error)),
235
+ );
236
+ })
237
+ .finally(() => {
238
+ pollInFlight = undefined;
239
+ scheduleReloadPoll();
240
+ });
241
+ }, EXTENSION_RELOAD_INTERVAL_MS);
242
+ reloadTimer.unref?.();
243
+ }
244
+
245
+ export async function loadEnabledExtensions(): Promise<void> {
246
+ initExtensionPostBridge();
247
+ for (const loaded of listRegistered()) await removeRegistered(loaded.record.id);
248
+ await cleanExtensionTmpRoot();
249
+ attemptedFingerprints.clear();
250
+
251
+ const extensions = await listExtensions();
252
+ for (const extension of extensions) {
253
+ await pruneExtensionRuns(extension.id);
254
+ if (!extension.enabled) continue;
255
+ attemptedFingerprints.set(extension.id, extensionFingerprint(extension));
256
+ try {
257
+ await enableExtension(extension.id);
258
+ } catch (error) {
259
+ console.error(
260
+ `[extensions] Load failed for ${extension.name}:`,
261
+ scrubSecrets(error instanceof Error ? error.message : String(error)),
262
+ );
263
+ }
264
+ }
265
+ lastObservedUpdate = maxUpdatedAt(await listExtensions());
266
+ startReloadPoll();
267
+ }
268
+
269
+ export async function stopExtensionRuntime(): Promise<void> {
270
+ polling = false;
271
+ if (reloadTimer) {
272
+ clearTimeout(reloadTimer);
273
+ reloadTimer = undefined;
274
+ }
275
+ await pollInFlight;
276
+ loopbackBaseUrl = undefined;
277
+ teardownExtensionPostBridge();
278
+ for (const loaded of listRegistered()) await removeRegistered(loaded.record.id);
279
+ attemptedFingerprints.clear();
280
+ await removeExtensionTmpRoot();
281
+ }
@@ -0,0 +1,207 @@
1
+ import { tmpdir } from "node:os";
2
+ import { resolve } from "node:path";
3
+ import { pathToFileURL } from "node:url";
4
+ import type { z } from "zod";
5
+ import { insertExtensionRun, setExtensionState } from "../be/extensions/db";
6
+ import { writeBareImportShim } from "../scripts-runtime/executors/native";
7
+ import type { Extension, ExtensionManifest } from "../types";
8
+ import { ExtensionManifestSchema } from "../types";
9
+ import { scrubSecrets } from "../utils/secret-scrubber";
10
+ import { isSafeBundlePath } from "./bundle-path";
11
+ import type { ApiCtx, ExtensionApi, HooksModule, SwarmEventMap } from "./contract";
12
+
13
+ const EXTENSIONS_TMP_PARENT = `${tmpdir()}/swarm-extensions`;
14
+ export const EXTENSIONS_TMP_ROOT = `${EXTENSIONS_TMP_PARENT}/${process.pid}`;
15
+
16
+ export type ExtensionHandler = {
17
+ event: keyof SwarmEventMap;
18
+ handler: (event: never, ctx: ApiCtx) => unknown | Promise<unknown>;
19
+ priority: number;
20
+ };
21
+
22
+ export type LoadableExtension = {
23
+ record: Extension;
24
+ manifest: ExtensionManifest;
25
+ files: Record<string, string>;
26
+ };
27
+
28
+ export type LoadedExtension = LoadableExtension & {
29
+ handlers: ExtensionHandler[];
30
+ configSchema?: z.ZodTypeAny;
31
+ config: Record<string, unknown>;
32
+ sourcePath: string;
33
+ dispose: () => Promise<void>;
34
+ };
35
+
36
+ export class ExtensionConfigError extends Error {
37
+ constructor(message: string) {
38
+ super(message);
39
+ this.name = "ExtensionConfigError";
40
+ }
41
+ }
42
+
43
+ const successfulDirectoryByName = new Map<string, string>();
44
+
45
+ function isZodSchema(value: unknown): value is z.ZodTypeAny {
46
+ return (
47
+ typeof value === "object" &&
48
+ value !== null &&
49
+ "safeParseAsync" in value &&
50
+ typeof value.safeParseAsync === "function"
51
+ );
52
+ }
53
+
54
+ async function writeRuntimeShims(directory: string): Promise<void> {
55
+ const runtimeDir = process.env.SCRIPT_RUNTIME_DIR;
56
+ if (runtimeDir) {
57
+ await Promise.all([
58
+ writeBareImportShim(directory, "stdlib", pathToFileURL(`${runtimeDir}/stdlib.bundle.js`)),
59
+ writeBareImportShim(directory, "zod", pathToFileURL(`${runtimeDir}/zod.bundle.js`)),
60
+ writeBareImportShim(
61
+ directory,
62
+ "swarm-extension",
63
+ pathToFileURL(`${runtimeDir}/extensions-contract.bundle.js`),
64
+ ),
65
+ ]);
66
+ return;
67
+ }
68
+
69
+ const zodEntry = Bun.resolveSync("zod", import.meta.dir);
70
+ await Promise.all([
71
+ writeBareImportShim(
72
+ directory,
73
+ "stdlib",
74
+ new URL("../scripts-runtime/stdlib/index.ts", import.meta.url),
75
+ ),
76
+ writeBareImportShim(directory, "zod", pathToFileURL(zodEntry)),
77
+ writeBareImportShim(
78
+ directory,
79
+ "swarm-extension",
80
+ new URL("./contract-runtime.ts", import.meta.url),
81
+ ),
82
+ ]);
83
+ }
84
+
85
+ async function persistLoadError(record: Extension, error: unknown): Promise<void> {
86
+ const message = scrubSecrets(error instanceof Error ? error.message : String(error));
87
+ try {
88
+ await insertExtensionRun({
89
+ extensionId: record.id,
90
+ version: record.activeVersion,
91
+ event: "load",
92
+ action: "load-error",
93
+ message,
94
+ });
95
+ await setExtensionState(record.id, { status: "error", lastError: message });
96
+ } catch (persistError) {
97
+ console.error(
98
+ "[extensions] Failed to persist load error:",
99
+ scrubSecrets(persistError instanceof Error ? persistError.message : String(persistError)),
100
+ );
101
+ }
102
+ }
103
+
104
+ export async function cleanExtensionTmpRoot(): Promise<void> {
105
+ await removeExtensionTmpRoot();
106
+ const siblings = new Bun.Glob("*");
107
+ await Bun.$`mkdir -p ${EXTENSIONS_TMP_PARENT}`.quiet();
108
+ for await (const name of siblings.scan({ cwd: EXTENSIONS_TMP_PARENT, onlyFiles: false })) {
109
+ if (!/^[1-9]\d*$/.test(name)) continue;
110
+ try {
111
+ process.kill(Number(name), 0);
112
+ } catch {
113
+ await Bun.$`rm -rf ${EXTENSIONS_TMP_PARENT}/${name}`.quiet();
114
+ }
115
+ }
116
+ }
117
+
118
+ export async function removeExtensionTmpRoot(): Promise<void> {
119
+ await Bun.$`rm -rf ${EXTENSIONS_TMP_ROOT}`.quiet();
120
+ successfulDirectoryByName.clear();
121
+ }
122
+
123
+ export async function loadExtension(input: LoadableExtension): Promise<LoadedExtension> {
124
+ const { record } = input;
125
+ const directory = `${EXTENSIONS_TMP_ROOT}/${record.name}/${record.contentHash}`;
126
+
127
+ try {
128
+ const bundlePath = (path: string): string => {
129
+ const filePath = resolve(directory, path);
130
+ if (!isSafeBundlePath(path) || !filePath.startsWith(`${directory}/`)) {
131
+ throw new Error(`Unsafe bundle file path: ${JSON.stringify(path)}`);
132
+ }
133
+ return filePath;
134
+ };
135
+ const sourcePath = bundlePath(input.manifest.assets.hooks);
136
+ const files = Object.entries(input.files).map(
137
+ ([path, content]) => [bundlePath(path), content] as const,
138
+ );
139
+ await Bun.$`mkdir -p ${directory}`.quiet();
140
+ for (const [filePath, content] of files) {
141
+ const parent = filePath.slice(0, filePath.lastIndexOf("/"));
142
+ await Bun.$`mkdir -p ${parent}`.quiet();
143
+ await Bun.write(filePath, content);
144
+ }
145
+ await writeRuntimeShims(directory);
146
+
147
+ const mod = (await import(sourcePath)) as Partial<HooksModule>;
148
+ if (typeof mod.default !== "function") {
149
+ throw new Error("Extension hooks must export a default function");
150
+ }
151
+ if (mod.config !== undefined && !isZodSchema(mod.config)) {
152
+ throw new ExtensionConfigError("Extension config export must be a Zod schema");
153
+ }
154
+
155
+ let config: Record<string, unknown>;
156
+ try {
157
+ config = JSON.parse(record.configJson) as Record<string, unknown>;
158
+ } catch {
159
+ throw new ExtensionConfigError("Extension configJson must contain a JSON object");
160
+ }
161
+ if (mod.config) {
162
+ const parsed = await mod.config.safeParseAsync(config);
163
+ if (!parsed.success) {
164
+ throw new ExtensionConfigError(`Extension config is invalid: ${parsed.error.message}`);
165
+ }
166
+ config = parsed.data as Record<string, unknown>;
167
+ }
168
+
169
+ const handlers: ExtensionHandler[] = [];
170
+ const api: ExtensionApi = {
171
+ on(event, handler, opts) {
172
+ handlers.push({
173
+ event,
174
+ handler: handler as ExtensionHandler["handler"],
175
+ priority: opts?.priority ?? record.priority,
176
+ });
177
+ },
178
+ };
179
+ mod.default(api);
180
+
181
+ const previousDirectory = successfulDirectoryByName.get(record.name);
182
+ if (previousDirectory && previousDirectory !== directory) {
183
+ await Bun.$`rm -rf ${previousDirectory}`.quiet();
184
+ }
185
+ successfulDirectoryByName.set(record.name, directory);
186
+
187
+ return {
188
+ ...input,
189
+ handlers,
190
+ configSchema: mod.config,
191
+ config,
192
+ sourcePath,
193
+ // Bun retains the imported module in its registry and cannot unload it.
194
+ // Disposal removes only the source directory from disk.
195
+ dispose: async () => {
196
+ await Bun.$`rm -rf ${directory}`.quiet();
197
+ },
198
+ };
199
+ } catch (error) {
200
+ await persistLoadError(record, error);
201
+ throw error;
202
+ }
203
+ }
204
+
205
+ export function parseStoredManifest(record: Extension): ExtensionManifest {
206
+ return ExtensionManifestSchema.parse(JSON.parse(record.manifestJson));
207
+ }
@@ -0,0 +1,161 @@
1
+ import { getTaskById } from "../be/db";
2
+ import { scrubSecrets } from "../utils/secret-scrubber";
3
+ import { workflowEventBus } from "../workflows/event-bus";
4
+ import { dispatchPost, extensionIdForAgent } from "./dispatcher";
5
+
6
+ let subscribed = false;
7
+
8
+ function observed(
9
+ eventName: string,
10
+ handler: (data: unknown) => Promise<void>,
11
+ ): (data: unknown) => void {
12
+ return (data): void => {
13
+ handler(data).catch((error) => {
14
+ console.error(
15
+ `[extensions] ${eventName} bridge failed:`,
16
+ scrubSecrets(error instanceof Error ? error.message : String(error)),
17
+ );
18
+ });
19
+ };
20
+ }
21
+
22
+ async function taskFrom(data: unknown) {
23
+ const taskId = (data as { taskId?: unknown } | null)?.taskId;
24
+ if (typeof taskId !== "string") return null;
25
+ return await getTaskById(taskId);
26
+ }
27
+
28
+ const onTaskCreated = observed("task.created", async (data) => {
29
+ const task = await taskFrom(data);
30
+ if (task)
31
+ await dispatchPost(
32
+ "post.task.created",
33
+ { task },
34
+ { skipExtensionId: extensionIdForAgent(task.creatorAgentId) },
35
+ );
36
+ });
37
+
38
+ const onTaskCompleted = observed("task.completed", async (data) => {
39
+ const task = await taskFrom(data);
40
+ if (!task) return;
41
+ const output = (data as { output?: unknown }).output;
42
+ await dispatchPost(
43
+ "post.task.completed",
44
+ {
45
+ task,
46
+ output: typeof output === "string" ? output : (task.output ?? ""),
47
+ },
48
+ { skipExtensionId: extensionIdForAgent(task.creatorAgentId) },
49
+ );
50
+ });
51
+
52
+ const onTaskFailed = observed("task.failed", async (data) => {
53
+ const task = await taskFrom(data);
54
+ if (!task) return;
55
+ const failureReason = (data as { failureReason?: unknown }).failureReason;
56
+ await dispatchPost(
57
+ "post.task.failed",
58
+ {
59
+ task,
60
+ failureReason: typeof failureReason === "string" ? failureReason : (task.failureReason ?? ""),
61
+ },
62
+ { skipExtensionId: extensionIdForAgent(task.creatorAgentId) },
63
+ );
64
+ });
65
+
66
+ const onTaskCancelled = observed("task.cancelled", async (data) => {
67
+ const task = await taskFrom(data);
68
+ if (task)
69
+ await dispatchPost(
70
+ "post.task.cancelled",
71
+ { task },
72
+ { skipExtensionId: extensionIdForAgent(task.creatorAgentId) },
73
+ );
74
+ });
75
+
76
+ const onTaskSuperseded = observed("task.superseded", async (data) => {
77
+ const task = await taskFrom(data);
78
+ if (!task) return;
79
+ const source = data as { resumeTaskId?: unknown; supersededBy?: unknown };
80
+ const supersededBy =
81
+ typeof source.supersededBy === "string"
82
+ ? source.supersededBy
83
+ : typeof source.resumeTaskId === "string"
84
+ ? source.resumeTaskId
85
+ : "";
86
+ await dispatchPost(
87
+ "post.task.superseded",
88
+ { task, supersededBy },
89
+ { skipExtensionId: extensionIdForAgent(task.creatorAgentId) },
90
+ );
91
+ });
92
+
93
+ const onTaskProgress = observed("task.progress", async (data) => {
94
+ const task = await taskFrom(data);
95
+ if (!task) return;
96
+ const progress = (data as { progress?: unknown }).progress;
97
+ await dispatchPost(
98
+ "post.task.progress",
99
+ {
100
+ task,
101
+ progress: typeof progress === "string" ? progress : (task.progress ?? ""),
102
+ },
103
+ { skipExtensionId: extensionIdForAgent(task.creatorAgentId) },
104
+ );
105
+ });
106
+
107
+ const onSlackMessage = observed("slack.message", async (data) => {
108
+ const source = data as {
109
+ channel?: unknown;
110
+ channelId?: unknown;
111
+ user?: unknown;
112
+ userId?: unknown;
113
+ text?: unknown;
114
+ threadTs?: unknown;
115
+ taskId?: unknown;
116
+ };
117
+ const channelId =
118
+ typeof source.channelId === "string"
119
+ ? source.channelId
120
+ : typeof source.channel === "string"
121
+ ? source.channel
122
+ : undefined;
123
+ const userId =
124
+ typeof source.userId === "string"
125
+ ? source.userId
126
+ : typeof source.user === "string"
127
+ ? source.user
128
+ : undefined;
129
+ if (!channelId || !userId || typeof source.text !== "string") return;
130
+ await dispatchPost("post.slack.message", {
131
+ channelId,
132
+ userId,
133
+ text: source.text,
134
+ ...(typeof source.threadTs === "string" ? { threadTs: source.threadTs } : {}),
135
+ ...(typeof source.taskId === "string" ? { taskId: source.taskId } : {}),
136
+ });
137
+ });
138
+
139
+ export function initExtensionPostBridge(): void {
140
+ if (subscribed) return;
141
+ subscribed = true;
142
+ workflowEventBus.on("task.created", onTaskCreated);
143
+ workflowEventBus.on("task.completed", onTaskCompleted);
144
+ workflowEventBus.on("task.failed", onTaskFailed);
145
+ workflowEventBus.on("task.cancelled", onTaskCancelled);
146
+ workflowEventBus.on("task.superseded", onTaskSuperseded);
147
+ workflowEventBus.on("task.progress", onTaskProgress);
148
+ workflowEventBus.on("slack.message", onSlackMessage);
149
+ }
150
+
151
+ export function teardownExtensionPostBridge(): void {
152
+ if (!subscribed) return;
153
+ subscribed = false;
154
+ workflowEventBus.off("task.created", onTaskCreated);
155
+ workflowEventBus.off("task.completed", onTaskCompleted);
156
+ workflowEventBus.off("task.failed", onTaskFailed);
157
+ workflowEventBus.off("task.cancelled", onTaskCancelled);
158
+ workflowEventBus.off("task.superseded", onTaskSuperseded);
159
+ workflowEventBus.off("task.progress", onTaskProgress);
160
+ workflowEventBus.off("slack.message", onSlackMessage);
161
+ }