@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,252 @@
1
+ import { afterEach, beforeEach, describe, expect, mock, spyOn, test } from "bun:test";
2
+ import type { SlackEventMiddlewareArgs } from "@slack/bolt";
3
+ import type { EntityPresentDetailsArguments } from "@slack/web-api";
4
+ import * as db from "../be/db";
5
+ import { AgentTaskSchema } from "../types";
6
+ import * as handlers from "./handlers";
7
+ import { registerWorkObjectHandlers } from "./work-objects";
8
+
9
+ const taskId = "11111111-2222-4333-8444-555555555555";
10
+ const task = AgentTaskSchema.parse({
11
+ id: taskId,
12
+ key: `shared/task:${taskId}/`,
13
+ agentId: null,
14
+ task: "Implement Work Object details",
15
+ title: "Slack flexpane support",
16
+ status: "completed",
17
+ output: "Implemented and tested.",
18
+ createdAt: "2026-09-15T09:00:00.000Z",
19
+ lastUpdatedAt: "2026-09-15T10:00:00.000Z",
20
+ slackChannelId: "C_TASK",
21
+ });
22
+
23
+ type DetailsEvent = SlackEventMiddlewareArgs<"entity_details_requested">["event"];
24
+ const event: DetailsEvent = {
25
+ type: "entity_details_requested",
26
+ user: "U_ALLOWED",
27
+ trigger_id: "trigger-123",
28
+ external_ref: { type: "task", id: taskId },
29
+ entity_url: `https://swarm.example/tasks/${taskId}`,
30
+ link: { url: `https://swarm.example/tasks/${taskId}`, domain: "swarm.example" },
31
+ user_locale: "en-US",
32
+ event_ts: "1757923200.000001",
33
+ channel: "C_TASK",
34
+ };
35
+
36
+ describe("Work Object details handler", () => {
37
+ const originalFlag = process.env.SLACK_WORK_OBJECTS_ENABLED;
38
+ const originalAppUrl = process.env.APP_URL;
39
+ const presentDetails = mock(async (_args: EntityPresentDetailsArguments) => ({ ok: true }));
40
+ const client = { entity: { presentDetails } };
41
+ const logger = { error: mock(() => {}) };
42
+ let listener: (args: {
43
+ event: DetailsEvent;
44
+ client: typeof client;
45
+ logger: typeof logger;
46
+ }) => Promise<void>;
47
+ let getTask: ReturnType<typeof spyOn<typeof db, "getTaskById">>;
48
+ let getAgent: ReturnType<typeof spyOn<typeof db, "getAgentById">>;
49
+ let userAllowed: ReturnType<typeof spyOn<typeof handlers, "isUserAllowed">>;
50
+
51
+ beforeEach(() => {
52
+ process.env.SLACK_WORK_OBJECTS_ENABLED = "true";
53
+ process.env.APP_URL = "https://swarm.example";
54
+ getTask = spyOn(db, "getTaskById").mockResolvedValue(task);
55
+ getAgent = spyOn(db, "getAgentById").mockResolvedValue(null);
56
+ userAllowed = spyOn(handlers, "isUserAllowed").mockResolvedValue(true);
57
+ presentDetails.mockReset();
58
+ presentDetails.mockResolvedValue({ ok: true });
59
+ logger.error.mockClear();
60
+ registerWorkObjectHandlers({
61
+ event: (name: string, callback: typeof listener) => {
62
+ expect(name).toBe("entity_details_requested");
63
+ listener = callback;
64
+ },
65
+ } as never);
66
+ });
67
+
68
+ afterEach(() => {
69
+ getTask.mockRestore();
70
+ getAgent.mockRestore();
71
+ userAllowed.mockRestore();
72
+ if (originalFlag === undefined) delete process.env.SLACK_WORK_OBJECTS_ENABLED;
73
+ else process.env.SLACK_WORK_OBJECTS_ENABLED = originalFlag;
74
+ if (originalAppUrl === undefined) delete process.env.APP_URL;
75
+ else process.env.APP_URL = originalAppUrl;
76
+ });
77
+
78
+ async function request(overrides: Partial<DetailsEvent> = {}) {
79
+ await listener({ event: { ...event, ...overrides }, client, logger });
80
+ }
81
+
82
+ for (const flag of [undefined, "false", "0", "typo"]) {
83
+ test(`does no work when flag is ${flag}`, async () => {
84
+ if (flag === undefined) delete process.env.SLACK_WORK_OBJECTS_ENABLED;
85
+ else process.env.SLACK_WORK_OBJECTS_ENABLED = flag;
86
+ await request();
87
+ expect(getTask).not.toHaveBeenCalled();
88
+ expect(userAllowed).not.toHaveBeenCalled();
89
+ expect(presentDetails).not.toHaveBeenCalled();
90
+ });
91
+ }
92
+
93
+ test("responds with current task data and the requesting trigger", async () => {
94
+ await request();
95
+ expect(presentDetails).toHaveBeenCalledTimes(1);
96
+ const response = presentDetails.mock.calls[0]?.[0];
97
+ expect(response?.trigger_id).toBe(event.trigger_id);
98
+ expect(response?.metadata).toMatchObject({
99
+ entity_type: "slack#/entities/task",
100
+ external_ref: event.external_ref,
101
+ url: event.entity_url,
102
+ entity_payload: {
103
+ attributes: { title: { text: task.title }, product_name: "Agent Swarm" },
104
+ fields: {
105
+ description: { value: task.task },
106
+ status: { value: "completed" },
107
+ assignee: { value: "Unassigned" },
108
+ },
109
+ custom_fields: [{ key: "result", label: "Result", value: task.output }],
110
+ },
111
+ });
112
+ expect(response?.metadata).not.toHaveProperty("entities");
113
+ expect(response?.metadata).not.toHaveProperty("app_unfurl_url");
114
+ expect(response).not.toHaveProperty("error");
115
+ expect(userAllowed).toHaveBeenCalledWith(client, event.user);
116
+ expect(getTask).toHaveBeenCalledWith(taskId);
117
+ });
118
+
119
+ test("reloads the task for each refresh and accepts 1 as the flag", async () => {
120
+ process.env.SLACK_WORK_OBJECTS_ENABLED = "1";
121
+ await request();
122
+ getTask.mockResolvedValue({
123
+ ...task,
124
+ status: "in_progress",
125
+ output: undefined,
126
+ progress: "Testing",
127
+ });
128
+ await request({ trigger_id: "refresh-trigger", app_unfurl_url: event.entity_url });
129
+ expect(getTask).toHaveBeenCalledTimes(2);
130
+ expect(presentDetails.mock.calls[1]?.[0]).toMatchObject({
131
+ trigger_id: "refresh-trigger",
132
+ metadata: {
133
+ entity_payload: {
134
+ fields: { status: { value: "in_progress" } },
135
+ custom_fields: [{ label: "Progress", value: "Testing" }],
136
+ },
137
+ },
138
+ });
139
+ });
140
+
141
+ test("shows failures and bounds long task text", async () => {
142
+ getTask.mockResolvedValue({
143
+ ...task,
144
+ title: undefined,
145
+ task: "x".repeat(10000),
146
+ status: "failed",
147
+ output: undefined,
148
+ failureReason: "y".repeat(10000),
149
+ });
150
+ await request();
151
+ const payload = presentDetails.mock.calls[0]?.[0].metadata?.entity_payload;
152
+ expect(payload?.attributes.title.text.length).toBe(200);
153
+ expect(payload?.fields).toMatchObject({ description: { value: `${"x".repeat(2799)}…` } });
154
+ expect(payload?.custom_fields?.[0]).toMatchObject({
155
+ label: "Failure",
156
+ value: `${"y".repeat(2799)}…`,
157
+ });
158
+ });
159
+
160
+ for (const type of ["file", "item", "content_item", "unknown", undefined]) {
161
+ test(`presents an error for unsupported reference type ${type}`, async () => {
162
+ await request({ external_ref: { id: taskId, type } });
163
+ expect(presentDetails).toHaveBeenCalledWith({
164
+ trigger_id: event.trigger_id,
165
+ error: {
166
+ status: "custom",
167
+ custom_title: "Work Object unavailable",
168
+ custom_message: "Agent Swarm does not support this Work Object reference.",
169
+ },
170
+ });
171
+ expect(getTask).not.toHaveBeenCalled();
172
+ });
173
+ }
174
+
175
+ test("presents an error when external_ref is absent or empty", async () => {
176
+ await request({ external_ref: undefined });
177
+ await request({ external_ref: { type: "task", id: "" } });
178
+ expect(presentDetails).toHaveBeenCalledTimes(2);
179
+ expect(getTask).not.toHaveBeenCalled();
180
+ for (const [response] of presentDetails.mock.calls)
181
+ expect(response.error?.status).toBe("custom");
182
+ });
183
+
184
+ test("presents not_found when the task was deleted", async () => {
185
+ getTask.mockResolvedValue(null);
186
+ await request();
187
+ expect(presentDetails).toHaveBeenCalledWith({
188
+ trigger_id: event.trigger_id,
189
+ error: { status: "not_found" },
190
+ });
191
+ });
192
+
193
+ test("denies filtered users before reading the task", async () => {
194
+ userAllowed.mockResolvedValue(false);
195
+ await request();
196
+ expect(getTask).not.toHaveBeenCalled();
197
+ expect(presentDetails).toHaveBeenCalledWith({
198
+ trigger_id: event.trigger_id,
199
+ error: { status: "restricted" },
200
+ });
201
+ });
202
+
203
+ for (const channel of [undefined, "C_OTHER"]) {
204
+ test(`does not disclose task details to channel ${channel}`, async () => {
205
+ await request({ channel });
206
+ expect(presentDetails).toHaveBeenCalledWith({
207
+ trigger_id: event.trigger_id,
208
+ error: { status: "restricted" },
209
+ });
210
+ });
211
+ }
212
+
213
+ test("does not disclose tasks without a Slack channel", async () => {
214
+ getTask.mockResolvedValue({ ...task, slackChannelId: undefined });
215
+ await request();
216
+ expect(presentDetails.mock.calls[0]?.[0].error?.status).toBe("restricted");
217
+ });
218
+
219
+ test("returns an error view when a task lookup fails", async () => {
220
+ getTask.mockRejectedValue(new Error("database unavailable"));
221
+ await request();
222
+ expect(presentDetails).toHaveBeenCalledWith({
223
+ trigger_id: event.trigger_id,
224
+ error: { status: "internal_error" },
225
+ });
226
+ expect(logger.error).toHaveBeenCalledTimes(1);
227
+ });
228
+
229
+ test("falls back to an error view when Slack rejects the metadata", async () => {
230
+ presentDetails.mockRejectedValueOnce(new Error("invalid_metadata"));
231
+ await request();
232
+ expect(presentDetails).toHaveBeenCalledTimes(2);
233
+ expect(presentDetails.mock.calls[1]?.[0]).toEqual({
234
+ trigger_id: event.trigger_id,
235
+ error: { status: "internal_error" },
236
+ });
237
+ });
238
+
239
+ test("logs a failed fallback without an unhandled rejection or retry loop", async () => {
240
+ presentDetails.mockRejectedValue(new Error("Slack unavailable"));
241
+ await request();
242
+ expect(presentDetails).toHaveBeenCalledTimes(2);
243
+ expect(logger.error).toHaveBeenCalledTimes(2);
244
+ });
245
+
246
+ test("logs rejection of an unknown object's error response", async () => {
247
+ presentDetails.mockRejectedValue(new Error("Slack unavailable"));
248
+ await request({ external_ref: undefined });
249
+ expect(presentDetails).toHaveBeenCalledTimes(1);
250
+ expect(logger.error).toHaveBeenCalledTimes(1);
251
+ });
252
+ });
@@ -0,0 +1,118 @@
1
+ import type { App, SlackEventMiddlewareArgs } from "@slack/bolt";
2
+ import type { EntityPresentDetailsArguments, WebClient } from "@slack/web-api";
3
+ import { getAgentById, getTaskById } from "../be/db";
4
+ import { isEnvFlagEnabled } from "../utils/env-flag";
5
+ import { getTaskUrl } from "./blocks";
6
+ import { isUserAllowed } from "./handlers";
7
+
8
+ type DetailsEvent = SlackEventMiddlewareArgs<"entity_details_requested">["event"];
9
+ type Details = Omit<EntityPresentDetailsArguments, "trigger_id" | "token">;
10
+
11
+ function truncate(text: string, limit: number): string {
12
+ return text.length > limit ? `${text.slice(0, limit - 1)}…` : text;
13
+ }
14
+
15
+ async function getDetails(event: DetailsEvent, client: WebClient): Promise<Details> {
16
+ const ref = event.external_ref;
17
+ // external_ref.type is our resource type, not Slack's entity_type. Never fetch event URLs.
18
+ if (ref?.type !== "task" || !ref.id) {
19
+ return {
20
+ error: {
21
+ status: "custom",
22
+ custom_title: "Work Object unavailable",
23
+ custom_message: "Agent Swarm does not support this Work Object reference.",
24
+ },
25
+ };
26
+ }
27
+
28
+ if (!event.user || !event.channel || !(await isUserAllowed(client, event.user))) {
29
+ return { error: { status: "restricted" } };
30
+ }
31
+
32
+ const task = await getTaskById(ref.id);
33
+ if (!task) return { error: { status: "not_found" } };
34
+
35
+ // A forwarded card must not expose a private task to another conversation.
36
+ if (task.slackChannelId !== event.channel) return { error: { status: "restricted" } };
37
+
38
+ const agent = task.agentId ? await getAgentById(task.agentId) : null;
39
+ const result = task.output || task.failureReason || task.progress;
40
+ return {
41
+ metadata: {
42
+ entity_type: "slack#/entities/task",
43
+ external_ref: ref,
44
+ url: getTaskUrl(task.id),
45
+ entity_payload: {
46
+ attributes: {
47
+ title: { text: truncate(task.title?.trim() || task.task, 200) },
48
+ display_id: task.id.slice(0, 8),
49
+ display_type: "Task",
50
+ product_name: "Agent Swarm",
51
+ metadata_last_modified: Math.floor(Date.parse(task.lastUpdatedAt) / 1000),
52
+ },
53
+ fields: {
54
+ description: { value: truncate(task.task, 2800), format: "markdown" },
55
+ status: { value: task.status },
56
+ assignee: { type: "string", value: agent?.name || task.agentId || "Unassigned" },
57
+ priority: { value: String(task.priority) },
58
+ date_created: { value: Math.floor(Date.parse(task.createdAt) / 1000) },
59
+ date_updated: { value: Math.floor(Date.parse(task.lastUpdatedAt) / 1000) },
60
+ },
61
+ ...(result
62
+ ? {
63
+ custom_fields: [
64
+ {
65
+ key: "result",
66
+ label: task.output ? "Result" : task.failureReason ? "Failure" : "Progress",
67
+ type: "string",
68
+ value: truncate(result, 2800),
69
+ format: "markdown",
70
+ },
71
+ ],
72
+ }
73
+ : {}),
74
+ display_order: [
75
+ "status",
76
+ "assignee",
77
+ "priority",
78
+ "description",
79
+ ...(result ? ["result"] : []),
80
+ "date_created",
81
+ "date_updated",
82
+ ],
83
+ },
84
+ },
85
+ };
86
+ }
87
+
88
+ export function registerWorkObjectHandlers(app: App): void {
89
+ // Bolt acknowledges Events API deliveries; the flexpane needs a separate API response.
90
+ app.event("entity_details_requested", async ({ event, client, logger }) => {
91
+ if (!isEnvFlagEnabled("SLACK_WORK_OBJECTS_ENABLED", false)) return;
92
+
93
+ let details: Details;
94
+ try {
95
+ details = await getDetails(event, client);
96
+ } catch (error) {
97
+ logger.error("[Slack] Failed to load Work Object details", error);
98
+ details = { error: { status: "internal_error" } };
99
+ }
100
+
101
+ try {
102
+ await client.entity.presentDetails({ trigger_id: event.trigger_id, ...details });
103
+ } catch (error) {
104
+ logger.error("[Slack] Failed to present Work Object details", error);
105
+ // If Slack rejects the metadata, still try to replace the spinner with an error view.
106
+ if (details.metadata) {
107
+ try {
108
+ await client.entity.presentDetails({
109
+ trigger_id: event.trigger_id,
110
+ error: { status: "internal_error" },
111
+ });
112
+ } catch (fallbackError) {
113
+ logger.error("[Slack] Failed to present Work Object error", fallbackError);
114
+ }
115
+ }
116
+ }
117
+ });
118
+ }
@@ -0,0 +1,9 @@
1
+ export class TaskCreationBlockedError extends Error {
2
+ constructor(
3
+ readonly reason: string,
4
+ readonly extension: { id: string; name: string },
5
+ ) {
6
+ super(reason);
7
+ this.name = "TaskCreationBlockedError";
8
+ }
9
+ }
@@ -17,7 +17,10 @@ import {
17
17
  getAgentById,
18
18
  getInProgressTasksByContextKey,
19
19
  } from "../be/db";
20
+ import { applyPreTaskCreate } from "../extensions/apply-task-create";
21
+ import type { TaskCreateOrigin } from "../extensions/contract";
20
22
  import type { AgentTask } from "../types";
23
+ import { TaskCreationBlockedError } from "./errors";
21
24
  import {
22
25
  pickResumeParent,
23
26
  prependSiblingBlock,
@@ -136,10 +139,41 @@ export async function withSiblingAwareness(
136
139
  * first. Use this from every ingress that has a `contextKey` so cross-ingress
137
140
  * sibling coordination is uniform without duplicating the wrapper boilerplate.
138
141
  */
142
+ /** Final description and options after extension pre-hooks and sibling wiring. */
143
+ export type PreparedTaskCreate = { description: string; options: CreateTaskOptions };
144
+
145
+ /**
146
+ * Run the `pre.task.create` extension hooks and sibling-awareness wiring without
147
+ * inserting the task. Callers that must create the task inside a transaction call
148
+ * this first, outside the transaction, so extension hooks are not skipped by the
149
+ * dispatcher's `isInTransaction()` guard.
150
+ */
151
+ export async function prepareTaskWithSiblingAwareness(
152
+ description: string,
153
+ options: CreateTaskOptions,
154
+ args: { origin?: TaskCreateOrigin } = {},
155
+ ): Promise<PreparedTaskCreate> {
156
+ const preCreate = await applyPreTaskCreate({
157
+ description,
158
+ options,
159
+ // Preserve the plan's REST default for callers without an explicit ingress origin.
160
+ origin: args.origin ?? "rest",
161
+ });
162
+ if (preCreate.kind === "blocked") {
163
+ throw new TaskCreationBlockedError(preCreate.reason, preCreate.extension);
164
+ }
165
+ return await withSiblingAwareness(preCreate.description, preCreate.options);
166
+ }
167
+
139
168
  export async function createTaskWithSiblingAwareness(
140
169
  description: string,
141
170
  options: CreateTaskOptions,
171
+ args: { origin?: TaskCreateOrigin } = {},
142
172
  ): Promise<AgentTask> {
143
- const { description: d, options: o } = await withSiblingAwareness(description, options);
173
+ const { description: d, options: o } = await prepareTaskWithSiblingAwareness(
174
+ description,
175
+ options,
176
+ args,
177
+ );
144
178
  return await createTaskExtended(d, o);
145
179
  }
@@ -1,3 +1,4 @@
1
+ import { z } from "zod";
1
2
  import {
2
3
  buildRoutingAffinityFromAgent,
3
4
  createLogEntry,
@@ -12,13 +13,32 @@ import {
12
13
  isAgentEligibleForTask,
13
14
  } from "../be/db";
14
15
  import { repointTrackerSyncBySwarmId } from "../be/db-queries/tracker";
16
+ import { dispatchPre } from "../extensions/dispatcher";
15
17
  import { resolveTemplate } from "../prompts/resolver";
16
- import type { Agent, AgentTask, ResumeReason, RoutingAffinity, TaskAttachment } from "../types";
18
+ import {
19
+ type Agent,
20
+ type AgentTask,
21
+ CreateTaskOptionsSchema,
22
+ type ResumeReason,
23
+ type RoutingAffinity,
24
+ type TaskAttachment,
25
+ } from "../types";
17
26
  import { isEnvFlagEnabled } from "../utils/env-flag";
18
27
  import { taskAttachmentDisplayUrl } from "../utils/task-attachment-links";
28
+ import { createTaskWithSiblingAwareness } from "./sibling-awareness";
19
29
  // Side-effect import: registers task lifecycle templates in the in-memory registry.
20
30
  import "../tools/templates";
21
31
 
32
+ /** Runtime check for `pre.task.followUp` modifications (mirrors `TaskFollowUpModify`). */
33
+ const TaskFollowUpModifySchema = CreateTaskOptionsSchema.pick({
34
+ agentId: true,
35
+ priority: true,
36
+ followUpConfig: true,
37
+ })
38
+ .partial()
39
+ .extend({ description: z.string().trim().min(1).optional() })
40
+ .strict();
41
+
22
42
  /**
23
43
  * Liveness window (seconds) for considering a worker "online" enough to
24
44
  * pre-assign a resume task. Defaults to 30s; override via env. The worker
@@ -217,16 +237,49 @@ export async function createWorkerTaskFollowUp(args: {
217
237
  }
218
238
  }
219
239
 
220
- return await createTaskExtended(followUpDescription, {
221
- agentId: leadAgent.id,
222
- routingReason: "skill",
223
- source: "system",
224
- taskType: "follow-up",
225
- parentTaskId: task.id,
226
- slackChannelId: task.slackChannelId,
227
- slackThreadTs: task.slackThreadTs,
228
- slackUserId: task.slackUserId,
229
- });
240
+ const preFollowUp = await dispatchPre(
241
+ "pre.task.followUp",
242
+ {
243
+ completedTask: task,
244
+ status,
245
+ output,
246
+ failureReason,
247
+ workerAgentId: taskAgent.id,
248
+ leadAgentId: leadAgent.id,
249
+ summary: followUpDescription,
250
+ },
251
+ {
252
+ // A modification that violates the task schema (for example `priority: -1`)
253
+ // is attributed to the extension and ignored, so the follow-up is still created.
254
+ validateModify: (data) => {
255
+ const parsed = TaskFollowUpModifySchema.safeParse(data);
256
+ return parsed.success
257
+ ? { success: true, data: parsed.data }
258
+ : { success: false, error: new Error(parsed.error.message) };
259
+ },
260
+ },
261
+ );
262
+ if (preFollowUp.action === "block") return null;
263
+
264
+ const changes = preFollowUp.action === "modify" ? preFollowUp.data : {};
265
+ const followUpAgentId = changes.agentId === undefined ? leadAgent.id : changes.agentId;
266
+ return await createTaskWithSiblingAwareness(
267
+ changes.description ?? followUpDescription,
268
+ {
269
+ agentId: followUpAgentId,
270
+ routingReason: followUpAgentId ? "skill" : undefined,
271
+ routingSource: followUpAgentId ? "engine_default" : undefined,
272
+ source: "system",
273
+ taskType: "follow-up",
274
+ priority: changes.priority,
275
+ parentTaskId: task.id,
276
+ slackChannelId: task.slackChannelId,
277
+ slackThreadTs: task.slackThreadTs,
278
+ slackUserId: task.slackUserId,
279
+ followUpConfig: changes.followUpConfig,
280
+ },
281
+ { origin: "followUp" },
282
+ );
230
283
  }
231
284
 
232
285
  /** Result of `createResumeFollowUp`. */
@@ -431,6 +484,7 @@ export async function createResumeFollowUp(args: {
431
484
  : preferredAgentId === parent.agentId
432
485
  ? "continuity"
433
486
  : "reroute_fault",
487
+ routingSource: preferredAgentId === undefined ? undefined : "engine_default",
434
488
  creatorAgentId: parent.creatorAgentId,
435
489
  source: "system",
436
490
  taskType: "resume",
@@ -550,6 +604,7 @@ export async function createRerouteDecisionTask(args: {
550
604
  const created = await createTaskExtended(decision.text, {
551
605
  agentId: leadAgent.id,
552
606
  routingReason: "reroute_fault",
607
+ routingSource: "engine_default",
553
608
  creatorAgentId: original.creatorAgentId,
554
609
  source: "system",
555
610
  taskType: "reroute-decision",
@@ -630,6 +685,7 @@ export async function createPoolStarvationDecisionTask(args: {
630
685
  const created = await createTaskExtended(decision.text, {
631
686
  agentId: leadAgent.id,
632
687
  routingReason: "overflow",
688
+ routingSource: "engine_default",
633
689
  creatorAgentId: original.creatorAgentId,
634
690
  source: "system",
635
691
  taskType: "reroute-decision",
@@ -264,7 +264,7 @@ class FakeAgent {
264
264
  },
265
265
  },
266
266
  },
267
- diagnosticToken: "ghp_abcdefghijklmnopqrstuvwxyz0123456789",
267
+ diagnosticToken: "example-ghp_abcdefghijklmnopqrstuvwxyz0123456789",
268
268
  chunks: Array.from({ length: 20 }, () => "y".repeat(2_000)),
269
269
  },
270
270
  },
@@ -283,7 +283,7 @@ class FakeAgent {
283
283
  stopReason: "end_turn",
284
284
  usage,
285
285
  _meta: {
286
- debug: "ghp_abcdefghijklmnopqrstuvwxyz0123456789",
286
+ debug: "example-ghp_abcdefghijklmnopqrstuvwxyz0123456789",
287
287
  authorization: "opaque-response-credential",
288
288
  note: "response metadata",
289
289
  },
@@ -475,7 +475,7 @@ new AgentSideConnection((connection) => new FakeAgent(connection), stream);
475
475
  }
476
476
  expect(persistedJson).toContain("X-Debug");
477
477
  expect(persistedJson).toContain("X-Debug-Map");
478
- expect(persistedJson).not.toContain("ghp_abcdefghijklmnopqrstuvwxyz0123456789");
478
+ expect(persistedJson).not.toContain("example-ghp_abcdefghijklmnopqrstuvwxyz0123456789");
479
479
  expect(persistedJson).toContain("[REDACTED:github_token]");
480
480
  expect(persistedJson).toContain("… [truncated]");
481
481
  expect(persistedJson).not.toContain("x".repeat(30_001));
@@ -510,7 +510,7 @@ new AgentSideConnection((connection) => new FakeAgent(connection), stream);
510
510
  PATH: "/bin",
511
511
  HOME: "/home/test",
512
512
  ACP_TARGET: "opencode",
513
- OPENAI_API_KEY: "test-key",
513
+ OPENAI_API_KEY: "example-test-key",
514
514
  OPENCODE_CONFIG_CONTENT: JSON.stringify({ theme: "dark" }),
515
515
  },
516
516
  }),
@@ -525,13 +525,13 @@ new AgentSideConnection((connection) => new FakeAgent(connection), stream);
525
525
  PATH: "/bin",
526
526
  HOME: "/home/test",
527
527
  ACP_TARGET: "opencode",
528
- OPENAI_API_KEY: "test-key",
528
+ OPENAI_API_KEY: "example-test-key",
529
529
  OPENCODE_CONFIG_CONTENT: JSON.stringify({ theme: "dark" }),
530
530
  },
531
531
  }),
532
532
  ),
533
533
  ).toMatchObject({
534
- OPENAI_API_KEY: "test-key",
534
+ OPENAI_API_KEY: "example-test-key",
535
535
  OPENCODE_CONFIG_CONTENT: JSON.stringify({ theme: "dark", model: "opencode/model" }),
536
536
  });
537
537
  });
@@ -596,8 +596,8 @@ new AgentSideConnection((connection) => new FakeAgent(connection), stream);
596
596
  ]),
597
597
  ).toEqual([{ type: "boolean", id: "flag", name: "Flag", currentValue: true }]);
598
598
 
599
- const secret = "ghp_abcdefghijklmnopqrstuvwxyz1234567890";
600
- const redacted = "[REDACTED:github_token]";
599
+ const secret = "example-ghp_abcdefghijklmnopqrstuvwxyz1234567890";
600
+ const redacted = "example-[REDACTED:github_token]";
601
601
  const sanitized = sanitizeAcpConfigOptions([
602
602
  {
603
603
  type: "select",
@@ -729,7 +729,7 @@ new AgentSideConnection((connection) => new FakeAgent(connection), stream);
729
729
  );
730
730
 
731
731
  // Minimal swarm API stub: tracks calls to the session token endpoints.
732
- const FAKE_TOKEN = "aseph_ephemtesttoken12345678901234";
732
+ const FAKE_TOKEN = "example-aseph_ephemtesttoken12345678901234";
733
733
  const FAKE_TOKEN_ID = "fake-ephem-token-id";
734
734
  let mintCalled = false;
735
735
  let revokeCalled = false;
@@ -781,7 +781,7 @@ new AgentSideConnection((connection) => new FakeAgent(connection), stream);
781
781
  baseConfig({
782
782
  cwd,
783
783
  apiUrl: `http://127.0.0.1:${swarmPort}`,
784
- apiKey: "real-operator-key",
784
+ apiKey: "example-real-operator-key",
785
785
  env: {
786
786
  PATH: process.env.PATH ?? "",
787
787
  HOME: process.env.HOME ?? "",
@@ -0,0 +1,40 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
+ import { buildAgentFsLiveUrl } from "../utils/constants";
3
+
4
+ const originalHost = process.env.AGENT_FS_LIVE_URL;
5
+
6
+ beforeEach(() => {
7
+ process.env.AGENT_FS_LIVE_URL = "https://files.example.test/";
8
+ });
9
+
10
+ afterEach(() => {
11
+ if (originalHost === undefined) delete process.env.AGENT_FS_LIVE_URL;
12
+ else process.env.AGENT_FS_LIVE_URL = originalHost;
13
+ });
14
+
15
+ describe("buildAgentFsLiveUrl path encoding", () => {
16
+ test.each([
17
+ ["plain ASCII positive control", "/reports/report-2026_v1.txt", "reports/report-2026_v1.txt"],
18
+ [
19
+ "spaces in every segment",
20
+ "/shared reports/final report.md",
21
+ "shared%20reports/final%20report.md",
22
+ ],
23
+ ["Unicode and URL syntax", "/reports/café #1?.md", "reports/caf%C3%A9%20%231%3F.md"],
24
+ [
25
+ "already encoded positive control",
26
+ "/reports/final%20report%2f%2520.md",
27
+ "reports/final%20report%2f%2520.md",
28
+ ],
29
+ [
30
+ "mixed raw and encoded text",
31
+ "/shared%20reports/final%20report 100%.md",
32
+ "shared%20reports/final%20report%20100%25.md",
33
+ ],
34
+ ["malformed percent escapes", "/reports/100% %2 %GG.md", "reports/100%25%20%252%20%25GG.md"],
35
+ ])("%s", (_label, path, expectedPath) => {
36
+ expect(buildAgentFsLiveUrl({ path, orgId: "org-1", driveId: "drive-1" })).toBe(
37
+ `https://files.example.test/file/~/org-1/drive-1/${expectedPath}`,
38
+ );
39
+ });
40
+ });