@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
@@ -16,6 +16,7 @@ import {
16
16
  getUserById,
17
17
  getWorkflow,
18
18
  getWorkflowRun,
19
+ type UpdateScheduledTaskData,
19
20
  updateScheduledTask,
20
21
  } from "@/be/db";
21
22
  import {
@@ -38,11 +39,12 @@ import {
38
39
  reconcileDeferredTaskWaits,
39
40
  stopDeferredTaskWaits,
40
41
  } from "./deferred-task-waits";
41
- import { createStandaloneScheduleTask } from "./schedule-task";
42
+ import { createStandaloneScheduleTask, prepareStandaloneScheduleTask } from "./schedule-task";
42
43
 
43
44
  export { createStandaloneScheduleTask } from "./schedule-task";
44
45
 
45
46
  let schedulerInterval: ReturnType<typeof setInterval> | null = null;
47
+ let schedulerRun: object | null = null;
46
48
  let isProcessing = false;
47
49
  let executorRegistry: ExecutorRegistry | null = null;
48
50
 
@@ -201,8 +203,10 @@ export async function dispatchScheduleTarget(
201
203
  }
202
204
  }
203
205
  if (!triggeredWorkflows) {
206
+ // Extension hooks must run before the transaction opens (see schedule-task.ts).
207
+ const prepared = await prepareStandaloneScheduleTask(schedule, extraTags);
204
208
  const task = await getDbClient().transaction(async () =>
205
- createStandaloneScheduleTask(schedule, extraTags),
209
+ createStandaloneScheduleTask(schedule, extraTags, prepared),
206
210
  );
207
211
  return { triggeredWorkflows, task };
208
212
  }
@@ -220,96 +224,75 @@ async function throwIfWorkflowNeedsSetup(scheduleId: string, runIds: string[]):
220
224
  }
221
225
  }
222
226
 
223
- async function advanceScheduleAfterNeedsSetup(
224
- schedule: ScheduledTask,
225
- now: Date = new Date(),
226
- ): Promise<void> {
227
- schedule = renderScheduledTaskParams(schedule);
228
- const timestamp = now.toISOString();
229
- if (schedule.scheduleType === "one_time") {
230
- await updateScheduledTask(schedule.id, {
231
- nextRunAt: null,
232
- lastUpdatedAt: timestamp,
233
- });
234
- return;
235
- }
236
- await updateScheduledTask(schedule.id, {
237
- nextRunAt: calculateNextRun(
238
- schedule.timezone?.includes("{{") ? { ...schedule, timezone: "UTC" } : schedule,
239
- now,
240
- ),
241
- lastUpdatedAt: timestamp,
227
+ /**
228
+ * Claim the snapshot's occurrence under BEGIN IMMEDIATE. A competing poller,
229
+ * recovery pass, or schedule edit invalidates it before any target is dispatched.
230
+ */
231
+ async function claimScheduleOccurrence(snapshot: ScheduledTask, extraTags: string[]) {
232
+ return getDbClient().transaction(async () => {
233
+ const current = await getScheduledTaskById(snapshot.id);
234
+ if (!current?.enabled || !current.nextRunAt || current.nextRunAt !== snapshot.nextRunAt) {
235
+ return null;
236
+ }
237
+ const schedule = renderScheduledTaskParams(current);
238
+
239
+ // Event-driven waits already atomically create their child and consume the
240
+ // ceiling. Keep that transaction (and its afterCommit emits) intact: disabling
241
+ // a one-time schedule first would prevent its wake-up task from being created.
242
+ const deferred = await dispatchDeferredTaskWait(schedule.id, "ceiling", extraTags);
243
+ if (deferred) {
244
+ return {
245
+ schedule,
246
+ claimed: (await getScheduledTaskById(schedule.id))!,
247
+ dispatched: { triggeredWorkflows: false, ...deferred },
248
+ };
249
+ }
250
+
251
+ const now = new Date();
252
+ const nextRunAt =
253
+ schedule.scheduleType === "one_time"
254
+ ? null
255
+ : calculateNextRun(
256
+ schedule.timezone?.includes("{{") ? { ...schedule, timezone: "UTC" } : schedule,
257
+ new Date(Math.max(now.getTime(), new Date(current.nextRunAt).getTime())),
258
+ );
259
+ const claimed = (await updateScheduledTask(schedule.id, {
260
+ lastRunAt: now.toISOString(),
261
+ nextRunAt,
262
+ ...(schedule.scheduleType === "one_time" ? { enabled: false } : {}),
263
+ }))!;
264
+ return { schedule, claimed, dispatched: undefined };
242
265
  });
243
266
  }
244
267
 
245
- /**
246
- * Recover missed scheduled task runs from downtime.
247
- * Fires ONE catch-up run per schedule (not N missed runs).
248
- * Tags the task with "recovered" so it's distinguishable.
249
- */
268
+ /** A slow target must not overwrite a newer occurrence or a schedule edit. */
269
+ async function updateClaimedSchedule(claimed: ScheduledTask, updates: UpdateScheduledTaskData) {
270
+ await getDbClient().transaction(async () => {
271
+ const current = await getScheduledTaskById(claimed.id);
272
+ if (
273
+ current?.nextRunAt !== claimed.nextRunAt ||
274
+ current?.lastRunAt !== claimed.lastRunAt ||
275
+ current?.enabled !== claimed.enabled ||
276
+ current?.lastUpdatedAt !== claimed.lastUpdatedAt
277
+ )
278
+ return;
279
+ await updateScheduledTask(claimed.id, updates);
280
+ });
281
+ }
282
+
283
+ /** Recover ONE catch-up occurrence per schedule, using the poller's same claim. */
250
284
  async function recoverMissedSchedules(): Promise<void> {
251
- const now = new Date();
285
+ const now = Date.now();
252
286
  const dueSchedules = await getDueScheduledTasks();
253
-
254
- for (const storedSchedule of dueSchedules) {
255
- const schedule = renderScheduledTaskParams(storedSchedule);
287
+ for (const schedule of dueSchedules) {
256
288
  if (!schedule.nextRunAt) continue;
257
- const missedBy = now.getTime() - new Date(schedule.nextRunAt).getTime();
289
+ const missedBy = now - new Date(schedule.nextRunAt).getTime();
258
290
  if (missedBy < 15000) continue; // Less than 15s — normal timing jitter
259
-
260
291
  console.log(
261
292
  `[Scheduler] Recovering missed schedule "${schedule.name}" ` +
262
293
  `(was due ${Math.round(missedBy / 1000)}s ago)`,
263
294
  );
264
-
265
- let triggeredWorkflows = false;
266
- try {
267
- ({ triggeredWorkflows } = await dispatchScheduleTarget(schedule, ["recovered"]));
268
-
269
- // Update schedule state regardless of workflow/task path
270
- if (schedule.scheduleType === "one_time") {
271
- await updateScheduledTask(schedule.id, {
272
- lastRunAt: now.toISOString(),
273
- nextRunAt: null,
274
- enabled: false,
275
- lastUpdatedAt: now.toISOString(),
276
- });
277
- } else {
278
- const nextRun = calculateNextRun(schedule, now);
279
- await updateScheduledTask(schedule.id, {
280
- lastRunAt: now.toISOString(),
281
- nextRunAt: nextRun,
282
- lastUpdatedAt: now.toISOString(),
283
- });
284
- }
285
-
286
- if (schedule.scheduleType === "one_time") {
287
- console.log(`[Scheduler] One-time schedule "${schedule.name}" recovered and auto-disabled`);
288
- }
289
- telemetry.schedule("executed", {
290
- scheduleType: schedule.scheduleType,
291
- triggeredWorkflows,
292
- wasRecovered: true,
293
- });
294
- } catch (err) {
295
- if (err instanceof AutomationNeedsSetupError) {
296
- await advanceScheduleAfterNeedsSetup(schedule, now);
297
- telemetry.schedule("error", {
298
- scheduleType: schedule.scheduleType,
299
- triggeredWorkflows,
300
- wasRecovered: true,
301
- consecutiveErrors: schedule.consecutiveErrors ?? 0,
302
- });
303
- continue;
304
- }
305
- telemetry.schedule("error", {
306
- scheduleType: schedule.scheduleType,
307
- triggeredWorkflows,
308
- wasRecovered: true,
309
- consecutiveErrors: schedule.consecutiveErrors ?? 0,
310
- });
311
- console.error(`[Scheduler] Error recovering "${schedule.name}":`, err);
312
- }
295
+ await executeSchedule(schedule, ["recovered"]);
313
296
  }
314
297
  }
315
298
 
@@ -360,49 +343,43 @@ function getBackoffMs(consecutiveErrors: number): number {
360
343
  * Execute a single scheduled task by creating an agent task.
361
344
  * Tracks consecutive errors and applies exponential backoff on failure.
362
345
  */
363
- export async function executeSchedule(schedule: ScheduledTask): Promise<void> {
364
- schedule = renderScheduledTaskParams(schedule);
346
+ export async function executeSchedule(
347
+ schedule: ScheduledTask,
348
+ extraTags: string[] = [],
349
+ ): Promise<void> {
350
+ let claimed = schedule;
365
351
  let triggeredWorkflows = false;
352
+ const wasRecovered = extraTags.includes("recovered");
366
353
  try {
367
- ({ triggeredWorkflows } = await dispatchScheduleTarget(schedule));
368
-
369
- // Update schedule state regardless of workflow/task path
370
- const now = new Date().toISOString();
371
- if (schedule.scheduleType === "one_time") {
372
- await updateScheduledTask(schedule.id, {
373
- lastRunAt: now,
374
- nextRunAt: null,
375
- enabled: false,
376
- lastUpdatedAt: now,
377
- consecutiveErrors: 0,
378
- lastErrorAt: null,
379
- lastErrorMessage: null,
380
- });
381
- console.log(`[Scheduler] Executed one-time schedule "${schedule.name}", auto-disabled`);
382
- } else {
383
- const nextRun = calculateNextRun(schedule, new Date());
384
- await updateScheduledTask(schedule.id, {
385
- lastRunAt: now,
386
- nextRunAt: nextRun,
387
- lastUpdatedAt: now,
388
- consecutiveErrors: 0,
389
- lastErrorAt: null,
390
- lastErrorMessage: null,
391
- });
392
- console.log(`[Scheduler] Executed schedule "${schedule.name}", next run: ${nextRun}`);
393
- }
354
+ const occurrence = await claimScheduleOccurrence(schedule, extraTags);
355
+ if (!occurrence) return;
356
+ ({ schedule, claimed } = occurrence);
357
+ // The claim has committed before external workflow/script dispatch begins.
358
+ // Never re-arm it: a target may have produced effects before reporting failure.
359
+ ({ triggeredWorkflows } =
360
+ occurrence.dispatched ?? (await dispatchScheduleTarget(schedule, extraTags)));
361
+
362
+ await updateClaimedSchedule(claimed, {
363
+ consecutiveErrors: 0,
364
+ lastErrorAt: null,
365
+ lastErrorMessage: null,
366
+ });
367
+ console.log(
368
+ `[Scheduler] Executed schedule "${schedule.name}", next run: ${claimed.nextRunAt ?? "disabled"}`,
369
+ );
394
370
  telemetry.schedule("executed", {
395
371
  scheduleType: schedule.scheduleType,
396
372
  triggeredWorkflows,
397
- wasRecovered: false,
373
+ wasRecovered,
398
374
  });
399
375
  } catch (err) {
400
376
  if (err instanceof AutomationNeedsSetupError) {
401
- await advanceScheduleAfterNeedsSetup(schedule);
377
+ // Preflight records its own diagnostic. The claim already advanced the
378
+ // cadence, so no retry/backoff or second schedule advancement is needed.
402
379
  telemetry.schedule("error", {
403
380
  scheduleType: schedule.scheduleType,
404
381
  triggeredWorkflows,
405
- wasRecovered: false,
382
+ wasRecovered,
406
383
  consecutiveErrors: schedule.consecutiveErrors ?? 0,
407
384
  });
408
385
  return;
@@ -446,11 +423,11 @@ export async function executeSchedule(schedule: ScheduledTask): Promise<void> {
446
423
  console.log(`[Scheduler] Backing off "${schedule.name}" for ${backoff / 1000}s`);
447
424
  }
448
425
 
449
- await updateScheduledTask(schedule.id, updates);
426
+ await updateClaimedSchedule(claimed, updates);
450
427
  telemetry.schedule("error", {
451
428
  scheduleType: schedule.scheduleType,
452
429
  triggeredWorkflows,
453
- wasRecovered: false,
430
+ wasRecovered,
454
431
  consecutiveErrors: errorCount,
455
432
  });
456
433
  }
@@ -466,23 +443,29 @@ export function startScheduler(
466
443
  intervalMs = 10000,
467
444
  opts?: { runId?: string },
468
445
  ): void {
469
- if (schedulerInterval) {
446
+ if (schedulerRun) {
470
447
  console.log("[Scheduler] Already running");
471
448
  return;
472
449
  }
473
450
 
451
+ const run = {};
452
+ schedulerRun = run;
474
453
  executorRegistry = registry;
475
454
  console.log(`[Scheduler] Starting with ${intervalMs}ms polling interval`);
476
455
 
477
- // Recover missed schedules from downtime, then run normal processing
456
+ // Both success and failure resume polling, but only after recovery settles.
457
+ // The identity prevents stop/restart during startup from installing an old timer.
478
458
  void initDeferredTaskWaits()
479
- .then(() => recoverMissedSchedules())
480
- .then(() => processSchedules())
481
- .catch((err) => console.error("[Scheduler] Recovery failed:", err));
482
-
483
- schedulerInterval = setInterval(async () => {
484
- await processSchedules();
485
- }, intervalMs);
459
+ .then(() => (schedulerRun === run ? recoverMissedSchedules() : undefined))
460
+ .catch((err) => console.error("[Scheduler] Recovery failed:", err))
461
+ .then(async () => {
462
+ if (schedulerRun !== run) return;
463
+ schedulerInterval = setInterval(() => {
464
+ void processSchedules().catch((err) => console.error("[Scheduler] Poll failed:", err));
465
+ }, intervalMs);
466
+ await processSchedules();
467
+ })
468
+ .catch((err) => console.error("[Scheduler] Poll failed:", err));
486
469
 
487
470
  ensure({
488
471
  id: "scheduler_started",
@@ -531,11 +514,11 @@ async function processSchedules(): Promise<void> {
531
514
  * Stop the scheduler polling loop.
532
515
  */
533
516
  export function stopScheduler(): void {
517
+ schedulerRun = null;
534
518
  stopDeferredTaskWaits();
535
519
  if (schedulerInterval) {
536
520
  clearInterval(schedulerInterval);
537
521
  schedulerInterval = null;
538
- isProcessing = false;
539
522
  console.log("[Scheduler] Stopped");
540
523
  }
541
524
  }
@@ -92,7 +92,11 @@ async function readRuntimeError(
92
92
  }
93
93
  }
94
94
 
95
- async function writeBareImportShim(tmpdir: string, name: string, targetUrl: URL): Promise<void> {
95
+ export async function writeBareImportShim(
96
+ tmpdir: string,
97
+ name: string,
98
+ targetUrl: URL,
99
+ ): Promise<void> {
96
100
  const dir = `${tmpdir}/node_modules/${name}`;
97
101
  await Bun.$`mkdir -p ${dir}`;
98
102
  await Bun.write(`${dir}/package.json`, JSON.stringify({ type: "module", main: "index.ts" }));
@@ -15,6 +15,11 @@ export type SwarmConfigPayload = {
15
15
  * context like the agent identity — scripts never supply or override it.
16
16
  */
17
17
  runtimeInstanceId?: { value: string; isSecret: false };
18
+ /**
19
+ * Per-process secret set only by the in-process extension `ctx.swarm` SDK. The
20
+ * bridge requires it to grant the `extension` call origin. Never sent to scripts.
21
+ */
22
+ extensionToken?: { value: string; isSecret: true };
18
23
  };
19
24
  user: Record<string, { value: string; isSecret: boolean }>;
20
25
  egressSecrets?: EgressSecretEntry[];
@@ -1,9 +1,14 @@
1
1
  import ts from "typescript";
2
2
 
3
- const ALLOWED_BARE_IMPORTS = new Set(["swarm-sdk", "stdlib", "zod"]);
3
+ const SCRIPT_ALLOWED_BARE_IMPORTS = ["swarm-sdk", "stdlib", "zod"] as const;
4
4
  const FORBIDDEN_HINTS = new Set(["node:", "bun:", "fs", "child_process", "crypto", "bun:sqlite"]);
5
- const ALLOWLIST_REMEDY =
6
- 'Allowed imports are "swarm-sdk", "stdlib", "zod", and relative paths ("./" or "../").';
5
+
6
+ export type ImportAllowlistOptions = {
7
+ allowedBare: readonly string[];
8
+ allowRelative: boolean;
9
+ // Extensions need strict dynamic imports because they run in-process without a sandbox.
10
+ strictDynamic?: boolean;
11
+ };
7
12
 
8
13
  export type ImportAllowlistResult =
9
14
  | { ok: true }
@@ -13,11 +18,18 @@ function isRelative(specifier: string): boolean {
13
18
  return specifier.startsWith("./") || specifier.startsWith("../");
14
19
  }
15
20
 
16
- function isAllowed(specifier: string): boolean {
17
- return isRelative(specifier) || ALLOWED_BARE_IMPORTS.has(specifier);
21
+ function isAllowed(specifier: string, options: ImportAllowlistOptions): boolean {
22
+ return (
23
+ (options.allowRelative && isRelative(specifier)) || options.allowedBare.includes(specifier)
24
+ );
18
25
  }
19
26
 
20
- function collectImportSpecifiers(source: string): string[] {
27
+ type CollectedImports = {
28
+ imports: string[];
29
+ unsupportedSyntax: "import()" | "require()" | "import-equals" | null;
30
+ };
31
+
32
+ function collectImportSpecifiers(source: string, strictDynamic = false): CollectedImports {
21
33
  const sourceFile = ts.createSourceFile(
22
34
  "user-script.ts",
23
35
  source,
@@ -26,6 +38,7 @@ function collectImportSpecifiers(source: string): string[] {
26
38
  ts.ScriptKind.TS,
27
39
  );
28
40
  const imports: string[] = [];
41
+ let unsupportedSyntax: CollectedImports["unsupportedSyntax"] = null;
29
42
 
30
43
  const visit = (node: ts.Node) => {
31
44
  if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) {
@@ -34,19 +47,42 @@ function collectImportSpecifiers(source: string): string[] {
34
47
  imports.push(moduleSpecifier.text);
35
48
  }
36
49
 
50
+ if (
51
+ strictDynamic &&
52
+ ts.isImportEqualsDeclaration(node) &&
53
+ ts.isExternalModuleReference(node.moduleReference)
54
+ ) {
55
+ const specifier = node.moduleReference.expression;
56
+ if (specifier && ts.isStringLiteralLike(specifier)) imports.push(specifier.text);
57
+ else unsupportedSyntax ??= "import-equals";
58
+ }
59
+
37
60
  if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword) {
38
61
  const [arg] = node.arguments;
39
- if (arg && ts.isStringLiteral(arg)) imports.push(arg.text);
62
+ if (arg && (ts.isStringLiteral(arg) || (strictDynamic && ts.isStringLiteralLike(arg))))
63
+ imports.push(arg.text);
64
+ else if (strictDynamic) unsupportedSyntax ??= "import()";
65
+ }
66
+
67
+ if (
68
+ strictDynamic &&
69
+ ts.isCallExpression(node) &&
70
+ ts.isIdentifier(node.expression) &&
71
+ node.expression.text === "require"
72
+ ) {
73
+ const [arg] = node.arguments;
74
+ if (arg && ts.isStringLiteralLike(arg)) imports.push(arg.text);
75
+ else unsupportedSyntax ??= "require()";
40
76
  }
41
77
 
42
78
  ts.forEachChild(node, visit);
43
79
  };
44
80
 
45
81
  visit(sourceFile);
46
- return imports;
82
+ return { imports, unsupportedSyntax };
47
83
  }
48
84
 
49
- function findForbiddenDynamicCode(source: string): string | null {
85
+ function findForbiddenDynamicCode(source: string, subject: string): string | null {
50
86
  const sourceFile = ts.createSourceFile(
51
87
  "user-script.ts",
52
88
  source,
@@ -64,7 +100,7 @@ function findForbiddenDynamicCode(source: string): string | null {
64
100
  node.expression.kind === ts.SyntaxKind.Identifier &&
65
101
  node.expression.getText(sourceFile) === "eval"
66
102
  ) {
67
- diagnostic = "eval is not allowed in swarm scripts";
103
+ diagnostic = `eval is not allowed in ${subject}`;
68
104
  return;
69
105
  }
70
106
 
@@ -73,7 +109,7 @@ function findForbiddenDynamicCode(source: string): string | null {
73
109
  node.expression.kind === ts.SyntaxKind.Identifier &&
74
110
  node.expression.getText(sourceFile) === "Function"
75
111
  ) {
76
- diagnostic = "Function constructor is not allowed in swarm scripts";
112
+ diagnostic = `Function constructor is not allowed in ${subject}`;
77
113
  return;
78
114
  }
79
115
 
@@ -82,7 +118,7 @@ function findForbiddenDynamicCode(source: string): string | null {
82
118
  node.expression.kind === ts.SyntaxKind.Identifier &&
83
119
  node.expression.getText(sourceFile) === "Function"
84
120
  ) {
85
- diagnostic = "Function constructor is not allowed in swarm scripts";
121
+ diagnostic = `Function constructor is not allowed in ${subject}`;
86
122
  return;
87
123
  }
88
124
 
@@ -93,23 +129,47 @@ function findForbiddenDynamicCode(source: string): string | null {
93
129
  return diagnostic;
94
130
  }
95
131
 
96
- export function validateScriptImports(source: string): ImportAllowlistResult {
97
- const dynamicDiagnostic = findForbiddenDynamicCode(source);
132
+ export function checkImportAllowlist(
133
+ source: string,
134
+ options: ImportAllowlistOptions,
135
+ ): ImportAllowlistResult {
136
+ const isExtension = options.allowedBare.includes("swarm-extension");
137
+ const subject = isExtension ? "swarm extensions" : "swarm scripts";
138
+ const dynamicDiagnostic = findForbiddenDynamicCode(source, subject);
98
139
  if (dynamicDiagnostic) return { ok: false, diagnostic: dynamicDiagnostic, imports: [] };
99
140
 
100
- const imports = collectImportSpecifiers(source);
101
- const rejected = imports.filter((specifier) => !isAllowed(specifier));
141
+ const collected = collectImportSpecifiers(source, options.strictDynamic);
142
+ if (collected.unsupportedSyntax) {
143
+ return {
144
+ ok: false,
145
+ diagnostic: `Computed ${collected.unsupportedSyntax} is not allowed in ${subject}. Use a string literal from the import allowlist.`,
146
+ imports: collected.imports,
147
+ };
148
+ }
149
+ const imports = collected.imports;
150
+ const rejected = imports.filter((specifier) => !isAllowed(specifier, options));
102
151
  if (rejected.length === 0) return { ok: true };
103
152
 
104
153
  const hint = rejected.find(
105
154
  (specifier) => FORBIDDEN_HINTS.has(specifier) || specifier.startsWith("node:"),
106
155
  );
107
156
  const reason = hint
108
- ? `Import '${hint}' is not allowed in swarm scripts`
109
- : `Import '${rejected[0]}' is not on the swarm script allowlist`;
157
+ ? `Import '${hint}' is not allowed in ${subject}`
158
+ : `Import '${rejected[0]}' is not on the ${subject} allowlist`;
159
+ const listed = options.allowedBare.map((specifier) => `"${specifier}"`).join(", ");
160
+ const allowlistRemedy = options.allowRelative
161
+ ? `Allowed imports are ${listed}, and relative paths ("./" or "../").`
162
+ : `Allowed imports are ${listed}. Relative imports are not allowed.`;
110
163
  const remedy =
111
164
  hint === "crypto" || hint === "node:crypto"
112
165
  ? "The global crypto object already provides randomUUID, getRandomValues, and subtle.digest; delete the import and use crypto directly."
113
- : ALLOWLIST_REMEDY;
166
+ : allowlistRemedy;
114
167
  return { ok: false, diagnostic: `${reason}. ${remedy}`, imports: rejected };
115
168
  }
169
+
170
+ export function validateScriptImports(source: string): ImportAllowlistResult {
171
+ return checkImportAllowlist(source, {
172
+ allowedBare: SCRIPT_ALLOWED_BARE_IMPORTS,
173
+ allowRelative: true,
174
+ });
175
+ }
@@ -56,6 +56,10 @@ export const SDK_TOOL_NAME_MAP = {
56
56
  script_getRun: "get-script-run",
57
57
  script_listRuns: "list-script-runs",
58
58
 
59
+ // ── extensions ──
60
+ extension_install: "extension-install",
61
+ extension_list: "extension-list",
62
+
59
63
  // ── swarm / agent ──
60
64
  swarm_get: "get-swarm",
61
65
  agent_info: "my-agent-info",
@@ -7,6 +7,8 @@ export class SwarmConfig {
7
7
  readonly mcpBaseUrl: RedactedValue<string>;
8
8
  /** Per-boot runtime identity of the invoking worker; system context, never script input. */
9
9
  readonly runtimeInstanceId: RedactedValue<string> | undefined;
10
+ /** Per-process extension bridge secret; present only for in-process extension SDKs. */
11
+ readonly extensionToken: RedactedValue<string> | undefined;
10
12
 
11
13
  private readonly userValues: Map<string, RedactedValue<string>>;
12
14
 
@@ -29,6 +31,12 @@ export class SwarmConfig {
29
31
  isSecret: payload.system.runtimeInstanceId.isSecret,
30
32
  })
31
33
  : undefined;
34
+ this.extensionToken = payload.system.extensionToken
35
+ ? Redacted.make(payload.system.extensionToken.value, {
36
+ type: "system",
37
+ isSecret: payload.system.extensionToken.isSecret,
38
+ })
39
+ : undefined;
32
40
  this.userValues = new Map(
33
41
  Object.entries(payload.user ?? {}).map(([key, value]) => [
34
42
  key,
@@ -20,6 +20,9 @@ function headers(config: SwarmConfig): Record<string, string> {
20
20
  ...(config.runtimeInstanceId
21
21
  ? { "X-Runtime-Instance-ID": Redacted.value(config.runtimeInstanceId) }
22
22
  : {}),
23
+ ...(config.extensionToken
24
+ ? { "X-Extension-Token": Redacted.value(config.extensionToken) }
25
+ : {}),
23
26
  "Content-Type": "application/json",
24
27
  };
25
28
  }
@@ -36,10 +36,14 @@ declare module "swarm-sdk" {
36
36
  | "reroute_fault";
37
37
  export type TaskSendArgs = Record<string, unknown> & {
38
38
  task: string;
39
- routingNote?: string;
40
39
  } & (
41
- | { agentId: string; routingReason: RoutingReason }
42
- | { agentId?: never; routingReason?: never }
40
+ | {
41
+ agentId: string;
42
+ routingReason: RoutingReason;
43
+ /** At least 10 characters after trim; maximum 200. */
44
+ routingNote: string;
45
+ }
46
+ | { agentId?: never; routingReason?: never; routingNote?: string }
43
47
  );
44
48
  export type AgentTaskStepConfig = {
45
49
  template?: string;
@@ -513,6 +517,15 @@ declare module "swarm-sdk" {
513
517
  offset?: number;
514
518
  }): Promise<unknown>;
515
519
 
520
+ // --- write: extensions ---
521
+ extension_install(args: {
522
+ manifest: Record<string, unknown>;
523
+ files: Record<string, string>;
524
+ priority?: number;
525
+ config?: Record<string, unknown>;
526
+ }): Promise<unknown>;
527
+ extension_list(args?: { enabledOnly?: boolean }): Promise<unknown>;
528
+
516
529
  // --- write: repos ---
517
530
  repo_update(args: Record<string, unknown>): Promise<unknown>;
518
531