@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,24 @@
1
+ /**
2
+ * Agents that act with lead privileges without carrying the `isLead` flag.
3
+ *
4
+ * Extension system agents (`ext:<name>`) run operator-enabled code inside the
5
+ * API process. Their `ctx.swarm` calls must pass lead-only checks such as
6
+ * posting to Slack, but the agent row keeps `isLead: false` so lead selection
7
+ * (Slack routing, follow-ups, heartbeat triage) never picks them.
8
+ *
9
+ * The registry is process-local and mirrors the loaded-extension set: the
10
+ * extension dispatcher grants on load and revokes on dispose.
11
+ */
12
+ const leadEquivalentAgentIds = new Set<string>();
13
+
14
+ export function grantLeadEquivalence(agentId: string): void {
15
+ leadEquivalentAgentIds.add(agentId);
16
+ }
17
+
18
+ export function revokeLeadEquivalence(agentId: string): void {
19
+ leadEquivalentAgentIds.delete(agentId);
20
+ }
21
+
22
+ export function hasLeadEquivalence(agentId: string | null | undefined): boolean {
23
+ return !!agentId && leadEquivalentAgentIds.has(agentId);
24
+ }
@@ -10,6 +10,8 @@
10
10
  * Compile-time exhaustiveness: `LEGACY_POLICY` must cover every
11
11
  * `PermissionVerb` (enforced via `satisfies`).
12
12
  */
13
+
14
+ import { hasLeadEquivalence } from "./elevated-agents";
13
15
  import type { PermissionVerb } from "./permissions";
14
16
  import type { RbacPrincipal, RbacResource } from "./types";
15
17
 
@@ -24,10 +26,33 @@ export type LegacyRule = {
24
26
 
25
27
  // ── Named rules (research §3 Rule column) ────────────────────────────────────
26
28
 
29
+ /** Lead agents, plus extension system agents registered as lead-equivalent. */
30
+ function actsAsLead(principal: RbacPrincipal): boolean {
31
+ return principal.kind === "agent" && (principal.isLead || hasLeadEquivalence(principal.agentId));
32
+ }
33
+
27
34
  const leadOnly: LegacyRule = {
28
35
  name: "lead-only",
29
36
  denyReason: "requires lead agent",
30
- evaluate: (principal) => principal.kind === "agent" && principal.isLead,
37
+ evaluate: (principal) => actsAsLead(principal),
38
+ };
39
+
40
+ /**
41
+ * Human principals: the shared API key (operator) and dashboard session users.
42
+ * Dashboard users carry no admin flag today, so they are treated like the
43
+ * operator, matching the config routes (`src/http/config.ts`).
44
+ */
45
+ const operatorOrUser: LegacyRule = {
46
+ name: "operator-or-user",
47
+ denyReason: "requires operator or user authentication",
48
+ evaluate: (principal) => principal.kind === "operator" || principal.kind === "user",
49
+ };
50
+
51
+ const leadOrOperatorOrUser: LegacyRule = {
52
+ name: "lead-or-operator-or-user",
53
+ denyReason: "requires lead agent, operator, or user authentication",
54
+ evaluate: (principal) =>
55
+ principal.kind === "operator" || principal.kind === "user" || actsAsLead(principal),
31
56
  };
32
57
 
33
58
  const leadOrTaskCreator: LegacyRule = {
@@ -35,7 +60,7 @@ const leadOrTaskCreator: LegacyRule = {
35
60
  denyReason: "requires lead agent or task creator",
36
61
  evaluate: (principal, resource) => {
37
62
  if (principal.kind !== "agent") return false;
38
- if (principal.isLead) return true;
63
+ if (actsAsLead(principal)) return true;
39
64
  return (
40
65
  resource?.kind === "task" &&
41
66
  resource.creatorAgentId != null &&
@@ -49,7 +74,7 @@ const leadOrResourceOwner: LegacyRule = {
49
74
  denyReason: "requires lead agent or resource owner",
50
75
  evaluate: (principal, resource) => {
51
76
  if (principal.kind !== "agent") return false;
52
- if (principal.isLead) return true;
77
+ if (actsAsLead(principal)) return true;
53
78
  return (
54
79
  resource?.kind === "owned" &&
55
80
  resource.ownerAgentId != null &&
@@ -63,7 +88,7 @@ const leadOrOwnNamespace: LegacyRule = {
63
88
  denyReason: "requires lead agent or your own task:agent: namespace",
64
89
  evaluate: (principal, resource) => {
65
90
  if (principal.kind !== "agent") return false;
66
- if (principal.isLead) return true;
91
+ if (actsAsLead(principal)) return true;
67
92
  // A blank agent id can never own a namespace — the pre-migration guards
68
93
  // used truthiness (`if (info.agentId && ...)`), so `X-Agent-ID: ""` plus
69
94
  // the literal namespace `task:agent:` must stay denied.
@@ -105,7 +130,7 @@ const memoryOwnerOrLeadSwarm: LegacyRule = {
105
130
  evaluate: (principal, resource) => {
106
131
  if (principal.kind !== "agent" || resource?.kind !== "owned") return false;
107
132
  if (resource.ownerAgentId != null && resource.ownerAgentId === principal.agentId) return true;
108
- return principal.isLead && resource.scope === "swarm";
133
+ return actsAsLead(principal) && resource.scope === "swarm";
109
134
  },
110
135
  };
111
136
 
@@ -121,7 +146,7 @@ const taskFsMutate: LegacyRule = {
121
146
  denyReason: "requires operator, user, lead agent, task assignee, or task creator",
122
147
  evaluate: (principal, resource) => {
123
148
  if (principal.kind === "operator" || principal.kind === "user") return true;
124
- if (principal.isLead) return true;
149
+ if (actsAsLead(principal)) return true;
125
150
  if (resource?.kind !== "task") return false;
126
151
  return (
127
152
  (resource.agentId != null && resource.agentId === principal.agentId) ||
@@ -132,6 +157,8 @@ const taskFsMutate: LegacyRule = {
132
157
 
133
158
  /** All named (non-composite) rule kinds, keyed by identifier. */
134
159
  export const LEGACY_RULES = {
160
+ "operator-or-user": operatorOrUser,
161
+ "lead-or-operator-or-user": leadOrOperatorOrUser,
135
162
  "lead-only": leadOnly,
136
163
  "lead-or-task-creator": leadOrTaskCreator,
137
164
  "lead-or-resource-owner": leadOrResourceOwner,
@@ -203,4 +230,6 @@ export const LEGACY_POLICY = {
203
230
  "script.api.update": leadOnly,
204
231
  "script.api.rotate": leadOnly,
205
232
  "script.api.delete": leadOnly,
233
+ "extension.write": leadOrOperatorOrUser,
234
+ "extension.activate": operatorOrUser,
206
235
  } as const satisfies Record<PermissionVerb, LegacyRule>;
@@ -257,6 +257,14 @@ export const PERMISSIONS = {
257
257
  description: "Delete an external script API endpoint.",
258
258
  namespace: "script",
259
259
  },
260
+ "extension.write": {
261
+ description: "Install, update, or uninstall a swarm extension.",
262
+ namespace: "extension",
263
+ },
264
+ "extension.activate": {
265
+ description: "Enable, disable, or activate a version of a swarm extension.",
266
+ namespace: "extension",
267
+ },
260
268
  } as const satisfies Record<string, { description: string; namespace: string }>;
261
269
 
262
270
  export type PermissionVerb = keyof typeof PERMISSIONS;
@@ -1,21 +1,94 @@
1
1
  import { getDbClient, getScheduledTaskById, getUserById, updateScheduledTask } from "@/be/db";
2
- import type { AgentTask } from "@/types";
2
+ import type { PreparedTaskCreate } from "@/tasks/sibling-awareness";
3
+ import type { AgentTask, ScheduledTask } from "@/types";
3
4
  import { workflowEventBus } from "@/workflows/event-bus";
4
- import { createStandaloneScheduleTask } from "./schedule-task";
5
+ import { createStandaloneScheduleTask, prepareStandaloneScheduleTask } from "./schedule-task";
5
6
 
6
7
  export type TaskWakeEvent = "task.completed" | "task.failed" | "settled";
7
8
 
8
9
  type TaskWait = {
9
10
  scheduleId: string;
10
- taskId: string;
11
11
  eventName: TaskWakeEvent;
12
+ mode: "all" | "any";
12
13
  status: "pending" | "fired";
13
14
  };
14
15
 
16
+ type WaitMember = { taskId: string; taskStatus: string | null; deferred: number };
17
+
18
+ type WakeReason = "ceiling" | "task.completed" | "task.failed" | "task.cancelled";
19
+
20
+ /**
21
+ * Read the wait row and its member set and decide whether the wait is ready.
22
+ * Returns the wake-up cause text, or undefined when the wait is absent, already
23
+ * fired, not addressed by `reason`, or its member set has not matched yet.
24
+ * Pure reads: callers run it once outside the transaction (to build the task
25
+ * through the extension pre-hooks) and once more under the write lock, where
26
+ * the answer is authoritative. An event only identifies a candidate wait; it
27
+ * never proves the set is ready.
28
+ */
29
+ async function resolveWaitCause(
30
+ scheduleId: string,
31
+ reason: WakeReason,
32
+ ): Promise<{ wait: TaskWait; cause: string } | undefined> {
33
+ const client = getDbClient();
34
+ const wait = await client.get<TaskWait>(
35
+ "SELECT * FROM deferred_task_waits WHERE scheduleId = ?",
36
+ [scheduleId],
37
+ );
38
+ if (!wait || wait.status !== "pending") return undefined;
39
+ if (reason !== "ceiling" && wait.eventName !== "settled" && wait.eventName !== reason)
40
+ return undefined;
41
+ if (reason === "ceiling") return { wait, cause: "ceiling expired" };
42
+ const members = await client.query<WaitMember>(
43
+ `SELECT m.taskId, t.status AS taskStatus, EXISTS (
44
+ SELECT 1 FROM scheduled_tasks d
45
+ WHERE d.parentTaskId = t.id AND d.taskType = 'deferred' AND d.enabled = 1
46
+ ) AS deferred
47
+ FROM deferred_task_wait_members m LEFT JOIN agent_tasks t ON t.id = m.taskId
48
+ WHERE m.scheduleId = ?`,
49
+ [scheduleId],
50
+ );
51
+ const matched = members.filter(
52
+ (member) =>
53
+ !member.deferred &&
54
+ (member.taskStatus === "completed" ||
55
+ member.taskStatus === "failed" ||
56
+ member.taskStatus === "cancelled") &&
57
+ (wait.eventName === "settled" || wait.eventName === `task.${member.taskStatus}`),
58
+ );
59
+ if (!matched.length || (wait.mode === "all" && matched.length !== members.length))
60
+ return undefined;
61
+ const cause =
62
+ members.length === 1
63
+ ? `task.${matched[0]!.taskStatus} for task ${matched[0]!.taskId}`
64
+ : `${wait.mode} tasks matched ${wait.eventName}: ${matched.map((member) => `${member.taskId} (${member.taskStatus})`).join(", ")}`;
65
+ return { wait, cause };
66
+ }
67
+
68
+ function wakeSchedule(schedule: ScheduledTask, cause: string): ScheduledTask {
69
+ return { ...schedule, taskTemplate: `${schedule.taskTemplate}\n\nWake-up cause: ${cause}.` };
70
+ }
71
+
72
+ async function prepareDeferredWakeTask(
73
+ scheduleId: string,
74
+ reason: WakeReason,
75
+ extraTags: string[],
76
+ ): Promise<PreparedTaskCreate | undefined> {
77
+ const resolved = await resolveWaitCause(scheduleId, reason);
78
+ if (!resolved) return undefined;
79
+ let schedule = await getScheduledTaskById(scheduleId);
80
+ if (!schedule?.enabled) return undefined;
81
+ if (schedule.createdBy && !(await getUserById(schedule.createdBy))) {
82
+ schedule = { ...schedule, createdBy: undefined };
83
+ }
84
+ if (!schedule.taskTemplate) throw new Error(`Schedule "${schedule.name}" has no taskTemplate`);
85
+ return await prepareStandaloneScheduleTask(wakeSchedule(schedule, resolved.cause), extraTags);
86
+ }
87
+
15
88
  /** Undefined means a normal schedule; an empty result means another caller won. */
16
89
  export async function dispatchDeferredTaskWait(
17
90
  scheduleId: string,
18
- reason: "ceiling" | "task.completed" | "task.failed",
91
+ reason: WakeReason,
19
92
  extraTags: string[] = [],
20
93
  ): Promise<{ task?: AgentTask } | undefined> {
21
94
  const client = getDbClient();
@@ -26,14 +99,15 @@ export async function dispatchDeferredTaskWait(
26
99
  ]))
27
100
  )
28
101
  return undefined;
102
+ // Extension pre-hooks must run outside the transaction, so build the wake-up task
103
+ // first; the transaction below re-reads the wait and its member set before claiming.
104
+ // The prepared description carries the cause seen here; for an `any` wait a member
105
+ // that settles between this read and the claim is missing from that text only.
106
+ const preparedForWait = await prepareDeferredWakeTask(scheduleId, reason, extraTags);
107
+ if (!preparedForWait) return {};
29
108
  return client.transaction(async () => {
30
- const wait = await client.get<TaskWait>(
31
- "SELECT * FROM deferred_task_waits WHERE scheduleId = ?",
32
- [scheduleId],
33
- );
34
- if (!wait || wait.status !== "pending") return {};
35
- if (reason !== "ceiling" && wait.eventName !== "settled" && wait.eventName !== reason)
36
- return {};
109
+ const resolved = await resolveWaitCause(scheduleId, reason);
110
+ if (!resolved) return {};
37
111
  let schedule = await getScheduledTaskById(scheduleId);
38
112
  if (!schedule?.enabled) return {};
39
113
  const now = new Date().toISOString();
@@ -46,13 +120,10 @@ export async function dispatchDeferredTaskWait(
46
120
  schedule = { ...schedule, createdBy: undefined };
47
121
  }
48
122
  if (!schedule.taskTemplate) throw new Error(`Schedule "${schedule.name}" has no taskTemplate`);
49
- const cause = reason === "ceiling" ? "ceiling expired" : `${reason} for task ${wait.taskId}`;
50
123
  const task = await createStandaloneScheduleTask(
51
- {
52
- ...schedule,
53
- taskTemplate: `${schedule.taskTemplate}\n\nWake-up cause: ${cause}.`,
54
- },
124
+ wakeSchedule(schedule, resolved.cause),
55
125
  extraTags,
126
+ preparedForWait,
56
127
  );
57
128
  await client.run("UPDATE deferred_task_waits SET childTaskId = ? WHERE scheduleId = ?", [
58
129
  task.id,
@@ -73,17 +144,50 @@ export async function dispatchDeferredTaskWait(
73
144
 
74
145
  /** Reconcile durable producer state, including terminal transitions missed by this process. */
75
146
  export async function reconcileDeferredTaskWaits(taskId?: string): Promise<void> {
76
- const rows = await getDbClient().query<TaskWait & { taskStatus: "completed" | "failed" }>(
77
- `SELECT w.*, t.status AS taskStatus FROM deferred_task_waits w
78
- JOIN agent_tasks t ON t.id = w.taskId
147
+ const client = getDbClient();
148
+ const replacements = await client.query<{ taskId: string; resumeTaskId: string }>(
149
+ `SELECT DISTINCT m.taskId, (
150
+ SELECT r.id FROM agent_tasks r
151
+ WHERE r.parentTaskId = t.id AND r.taskType = 'resume'
152
+ ORDER BY r.createdAt, r.id LIMIT 1
153
+ ) AS resumeTaskId
154
+ FROM deferred_task_wait_members m
155
+ JOIN deferred_task_waits w ON w.scheduleId = m.scheduleId
156
+ JOIN agent_tasks t ON t.id = m.taskId
157
+ WHERE w.status = 'pending' AND t.status = 'superseded'
158
+ AND resumeTaskId IS NOT NULL`,
159
+ );
160
+ if (replacements.length) {
161
+ await client.transaction(async () => {
162
+ for (const replacement of replacements) {
163
+ // Collapse members that now watch the same child. Fired waits and their
164
+ // original ceilings stay untouched. No resume child (including skipped
165
+ // recovery) means hold to the ceiling, never wake on supersession.
166
+ await client.run(
167
+ `UPDATE OR REPLACE deferred_task_wait_members SET taskId = ?, updated_at = ?
168
+ WHERE taskId = ? AND scheduleId IN (
169
+ SELECT scheduleId FROM deferred_task_waits WHERE status = 'pending'
170
+ )`,
171
+ [replacement.resumeTaskId, new Date().toISOString(), replacement.taskId],
172
+ );
173
+ }
174
+ });
175
+ // Catch up through a chain missed while stopped, and check the replacements
176
+ // even when the triggering event named their superseded parent.
177
+ return reconcileDeferredTaskWaits();
178
+ }
179
+ const rows = await client.query<TaskWait & { taskStatus: "completed" | "failed" | "cancelled" }>(
180
+ `SELECT DISTINCT w.*, t.status AS taskStatus FROM deferred_task_waits w
181
+ JOIN deferred_task_wait_members m ON m.scheduleId = w.scheduleId
182
+ JOIN agent_tasks t ON t.id = m.taskId
79
183
  JOIN scheduled_tasks s ON s.id = w.scheduleId
80
- WHERE w.status = 'pending' AND s.enabled = 1 AND t.status IN ('completed', 'failed')
184
+ WHERE w.status = 'pending' AND s.enabled = 1 AND t.status IN ('completed', 'failed', 'cancelled')
81
185
  AND NOT EXISTS (
82
186
  SELECT 1 FROM scheduled_tasks d
83
187
  WHERE d.parentTaskId = t.id AND d.taskType = 'deferred' AND d.enabled = 1
84
188
  )
85
189
  AND (w.eventName = 'settled' OR w.eventName = 'task.' || t.status)
86
- ${taskId ? "AND w.taskId = ?" : ""}`,
190
+ ${taskId ? "AND m.taskId = ?" : ""}`,
87
191
  taskId ? [taskId] : [],
88
192
  );
89
193
  for (const row of rows) {
@@ -96,7 +200,7 @@ export async function reconcileDeferredTaskWaits(taskId?: string): Promise<void>
96
200
  }
97
201
  }
98
202
 
99
- function onTaskTerminal(payload: unknown): void {
203
+ function onTaskEvent(payload: unknown): void {
100
204
  if (
101
205
  !payload ||
102
206
  typeof payload !== "object" ||
@@ -109,15 +213,22 @@ function onTaskTerminal(payload: unknown): void {
109
213
  });
110
214
  }
111
215
 
112
- /** Two fixed listeners read durable waits, so boot needs no per-wait in-memory registry. */
216
+ /** Fixed listeners read durable waits, so boot needs no per-wait in-memory registry. */
113
217
  export async function initDeferredTaskWaits(): Promise<void> {
114
218
  stopDeferredTaskWaits();
115
- workflowEventBus.on("task.completed", onTaskTerminal);
116
- workflowEventBus.on("task.failed", onTaskTerminal);
219
+ workflowEventBus.on("task.completed", onTaskEvent);
220
+ workflowEventBus.on("task.failed", onTaskEvent);
221
+ workflowEventBus.on("task.cancelled", onTaskEvent);
222
+ workflowEventBus.on("task.superseded", onTaskEvent);
223
+ // The resume may be committed after its parent's supersede event.
224
+ workflowEventBus.on("task.created", onTaskEvent);
117
225
  await reconcileDeferredTaskWaits();
118
226
  }
119
227
 
120
228
  export function stopDeferredTaskWaits(): void {
121
- workflowEventBus.off("task.completed", onTaskTerminal);
122
- workflowEventBus.off("task.failed", onTaskTerminal);
229
+ workflowEventBus.off("task.completed", onTaskEvent);
230
+ workflowEventBus.off("task.failed", onTaskEvent);
231
+ workflowEventBus.off("task.cancelled", onTaskEvent);
232
+ workflowEventBus.off("task.superseded", onTaskEvent);
233
+ workflowEventBus.off("task.created", onTaskEvent);
123
234
  }
@@ -1,40 +1,70 @@
1
- import { getDbClient } from "@/be/db";
1
+ import { createTaskExtended, getDbClient } from "@/be/db";
2
2
  import { scheduleContextKey } from "@/tasks/context-key";
3
- import { createTaskWithSiblingAwareness } from "@/tasks/sibling-awareness";
3
+ import {
4
+ type PreparedTaskCreate,
5
+ prepareTaskWithSiblingAwareness,
6
+ } from "@/tasks/sibling-awareness";
4
7
  import type { AgentTask, ScheduledTask } from "@/types";
5
8
 
6
- export async function createStandaloneScheduleTask(
9
+ /**
10
+ * Run the `pre.task.create` extension hooks for a schedule firing. Call this OUTSIDE
11
+ * the transaction that creates the task: the dispatcher skips hooks while a
12
+ * transaction is open. Throws `TaskCreationBlockedError` when a hook blocks.
13
+ */
14
+ export async function prepareStandaloneScheduleTask(
7
15
  schedule: ScheduledTask,
8
16
  extraTags: string[] = [],
9
- ): Promise<AgentTask> {
17
+ ): Promise<PreparedTaskCreate> {
10
18
  if (!schedule.taskTemplate) {
11
19
  throw new Error(`Schedule "${schedule.name}" has no taskTemplate (targetType=agent-task)`);
12
20
  }
13
- const task = await createTaskWithSiblingAwareness(schedule.taskTemplate, {
14
- key: schedule.key,
15
- creatorAgentId: schedule.createdByAgentId,
16
- taskType: schedule.taskType,
17
- tags: [...schedule.tags, "scheduled", `schedule:${schedule.name}`, ...extraTags],
18
- priority: schedule.priority,
19
- agentId: schedule.targetAgentId,
20
- routingReason: schedule.targetAgentId ? "human_pinned" : undefined,
21
- model: schedule.model,
22
- modelTier: schedule.modelTier,
23
- scheduleId: schedule.id,
24
- source: "schedule",
25
- requestedByUserId: schedule.createdBy,
26
- contextKey: scheduleContextKey({ scheduleId: schedule.id }),
27
- // Set only by `defer-task`. An explicit parent wins over the sibling-awareness
28
- // auto-wiring (see withSiblingAwareness in src/tasks/sibling-awareness.ts), so
29
- // the wake-up run continues the deferred task rather than a random sibling.
30
- parentTaskId: schedule.parentTaskId,
31
- });
21
+ return await prepareTaskWithSiblingAwareness(
22
+ schedule.taskTemplate,
23
+ {
24
+ key: schedule.key,
25
+ creatorAgentId: schedule.createdByAgentId,
26
+ taskType: schedule.taskType,
27
+ tags: [...schedule.tags, "scheduled", `schedule:${schedule.name}`, ...extraTags],
28
+ priority: schedule.priority,
29
+ agentId: schedule.targetAgentId,
30
+ routingReason: schedule.targetAgentId ? "human_pinned" : undefined,
31
+ routingSource: schedule.targetAgentId ? "engine_default" : undefined,
32
+ model: schedule.model,
33
+ modelTier: schedule.modelTier,
34
+ scheduleId: schedule.id,
35
+ source: "schedule",
36
+ requestedByUserId: schedule.createdBy,
37
+ contextKey: scheduleContextKey({ scheduleId: schedule.id }),
38
+ // Set only by `defer-task`. An explicit parent wins over the sibling-awareness
39
+ // auto-wiring (see withSiblingAwareness in src/tasks/sibling-awareness.ts), so
40
+ // the wake-up run continues the deferred task rather than a random sibling.
41
+ parentTaskId: schedule.parentTaskId,
42
+ },
43
+ { origin: "schedule" },
44
+ );
45
+ }
46
+
47
+ /**
48
+ * Insert the schedule's task. Pass the result of `prepareStandaloneScheduleTask`
49
+ * when calling from inside a transaction; without it the hooks run here.
50
+ */
51
+ export async function createStandaloneScheduleTask(
52
+ schedule: ScheduledTask,
53
+ extraTags: string[] = [],
54
+ prepared?: PreparedTaskCreate,
55
+ ): Promise<AgentTask> {
56
+ const { description, options } =
57
+ prepared ?? (await prepareStandaloneScheduleTask(schedule, extraTags));
58
+ const task = await createTaskExtended(description, options);
32
59
  // Both timer and event dispatch wrap this helper in a transaction. Transfer
33
60
  // pending watchers before the resume task becomes visible or its schedule is
34
61
  // disabled; their own schedules retain the original ceilings.
35
62
  if (schedule.taskType === "deferred" && schedule.parentTaskId) {
36
63
  await getDbClient().run(
37
- "UPDATE deferred_task_waits SET taskId = ?, updated_at = ? WHERE status = 'pending' AND taskId = ?",
64
+ `UPDATE deferred_task_wait_members SET taskId = ?, updated_at = ?
65
+ WHERE taskId = ? AND scheduleId IN (
66
+ SELECT scheduleId FROM deferred_task_waits WHERE status = 'pending'
67
+ )`,
38
68
  [task.id, new Date().toISOString(), schedule.parentTaskId],
39
69
  );
40
70
  }