@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
@@ -9,6 +9,7 @@ import {
9
9
  getAgentById,
10
10
  getDbClient,
11
11
  getTaskById,
12
+ getUserById,
12
13
  updateAgentStatusFromCapacity,
13
14
  } from "@/be/db";
14
15
  import { reconcileDeferredTaskWaits } from "@/scheduler/deferred-task-waits";
@@ -17,6 +18,7 @@ import { getTaskOutputValidationError } from "@/tasks/terminal-result-guard";
17
18
  import { assertOwnsTask, ownerCtx } from "@/tools/task-tool-ctx";
18
19
  import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
19
20
  import { isTerminalTaskStatus } from "@/types";
21
+ import { getAppUrl } from "@/utils/constants";
20
22
 
21
23
  /** Thrown inside the transaction to abort and roll back the schedule INSERT. */
22
24
  class DeferAbortedError extends Error {}
@@ -27,6 +29,109 @@ function renderChecks(checks?: string[]): string {
27
29
  return `\n\nChecks:\n${checks.map((c) => `- ${c}`).join("\n")}`;
28
30
  }
29
31
 
32
+ /** Date/time parts of `date` as rendered in IANA zone `tz`. */
33
+ function partsInTz(date: Date, tz: string) {
34
+ const fmt = new Intl.DateTimeFormat("en-US", {
35
+ timeZone: tz,
36
+ year: "numeric",
37
+ month: "2-digit",
38
+ day: "2-digit",
39
+ hour: "2-digit",
40
+ minute: "2-digit",
41
+ second: "2-digit",
42
+ hour12: false,
43
+ });
44
+ const parts = Object.fromEntries(fmt.formatToParts(date).map((p) => [p.type, p.value]));
45
+ // Some locales render midnight as "24" under hour12: false.
46
+ const hour = parts.hour === "24" ? "00" : parts.hour;
47
+ return {
48
+ year: parts.year!,
49
+ month: parts.month!,
50
+ day: parts.day!,
51
+ hour: hour!,
52
+ minute: parts.minute!,
53
+ second: parts.second!,
54
+ };
55
+ }
56
+
57
+ /**
58
+ * "today"/"tomorrow" or "MM-dd" plus "HH:MM:ss", both computed in `tz`.
59
+ * Falls back to UTC (parts still computed correctly) if `tz` is not a valid
60
+ * IANA zone — the caller decides whether to label the fallback.
61
+ */
62
+ function formatDeferralDateTime(
63
+ target: Date,
64
+ now: Date,
65
+ tz: string,
66
+ ): { date: string; time: string } {
67
+ let zone = tz;
68
+ try {
69
+ Intl.DateTimeFormat("en-US", { timeZone: zone });
70
+ } catch {
71
+ zone = "UTC";
72
+ }
73
+ const t = partsInTz(target, zone);
74
+ const time = `${t.hour}:${t.minute}:${t.second}`;
75
+ const n = partsInTz(now, zone);
76
+ if (t.year === n.year && t.month === n.month && t.day === n.day) return { date: "today", time };
77
+ const tm = partsInTz(new Date(now.getTime() + 86_400_000), zone);
78
+ if (t.year === tm.year && t.month === tm.month && t.day === tm.day) {
79
+ return { date: "tomorrow", time };
80
+ }
81
+ return { date: `${t.month}-${t.day}`, time };
82
+ }
83
+
84
+ /**
85
+ * Resolve the requesting human's IANA timezone from `users.timezone` via
86
+ * `task.requestedByUserId`. We do not currently capture a Slack profile `tz`
87
+ * anywhere in the ingest path (checked `src/slack/enrich.ts`, the only place
88
+ * that reads `users.info`) — a user only has a timezone here if someone set
89
+ * it explicitly via `manage-user`. Falls back to UTC, flagged as such.
90
+ */
91
+ async function resolveRequesterTimezone(
92
+ requestedByUserId: string | undefined,
93
+ ): Promise<{ tz: string; isFallback: boolean }> {
94
+ if (requestedByUserId) {
95
+ const user = await getUserById(requestedByUserId);
96
+ if (user?.timezone) return { tz: user.timezone, isFallback: false };
97
+ }
98
+ return { tz: "UTC", isFallback: true };
99
+ }
100
+
101
+ /** First line of `note`, `Pending:` prefix stripped, capped to ~100 chars. */
102
+ function renderShortDesc(note: string, max = 100): string {
103
+ const firstLine = (note.split("\n")[0] ?? "").trim().replace(/^pending:\s*/i, "");
104
+ if (firstLine.length <= max) return firstLine;
105
+ return `${firstLine.slice(0, max - 1).trimEnd()}…`;
106
+ }
107
+
108
+ /**
109
+ * Human-facing deferral text for tasks without an outputSchema — this is what
110
+ * lands verbatim in a human's Slack thread as the task's terminal output.
111
+ * One line: `Deferred until {date} {time} ([shortId](url)) -> {short desc}`.
112
+ * No ISO timestamp, no bare UUID, no checks list, no `Pending:` prefix —
113
+ * those stay in the task log. The `[text](url)` link is plain GFM markdown:
114
+ * `markdownToSlack` (src/slack/blocks.ts) already down-converts it to a
115
+ * readable `text (url)` fallback for Slack, and it reads fine verbatim in
116
+ * the non-Slack task-output row / UI too.
117
+ */
118
+ function renderHumanFacingDeferral(
119
+ note: string,
120
+ nextRunAt: string,
121
+ scheduleId: string,
122
+ tz: string,
123
+ isFallbackTz: boolean,
124
+ ): string {
125
+ const now = new Date();
126
+ const target = new Date(nextRunAt);
127
+ const { date, time } = formatDeferralDateTime(target, now, tz);
128
+ const timeLabel = isFallbackTz ? `${time} UTC` : time;
129
+ const shortId = scheduleId.slice(0, 8);
130
+ const link = `[${shortId}](${getAppUrl()}/schedules/${scheduleId})`;
131
+ const shortDesc = renderShortDesc(note);
132
+ return `Deferred until ${date} ${timeLabel} (${link}) -> ${shortDesc}`;
133
+ }
134
+
30
135
  export const registerDeferTaskTool = (server: McpServer) => {
31
136
  createToolRegistrar(server)(
32
137
  "defer-task",
@@ -34,7 +139,7 @@ export const registerDeferTaskTool = (server: McpServer) => {
34
139
  title: "Defer Task",
35
140
  annotations: { destructiveHint: false, idempotentHint: false },
36
141
  description:
37
- "Completes this task now with status `completed` and books a wake-up for you. Use when the result needs time: a build, a deploy, a reply. The task reaches its final state on this call; the lead sees your summary as its output unless the task has an outputSchema. For a task with an outputSchema, provide output as a JSON string matching that schema; it is stored verbatim as terminal output, while deferral details remain visible in the task log. A one-off schedule wakes you up later with a child task that carries this task as its parent. Optionally provide wakeOn to wake early when another task completes or fails; delayMs or runAt remains required as the ceiling. Provide delayMs or runAt, a summary of what you did, and a note that says what is pending and what to check.",
142
+ "Completes this task now with status `completed` and books a wake-up for you. Use when the result needs time: a build, a deploy, a reply. The task reaches its final state on this call; the lead sees your summary as its output unless the task has an outputSchema. For a task with an outputSchema, provide output as a JSON string matching that schema; it is stored verbatim as terminal output, while deferral details remain visible in the task log. A one-off schedule wakes you up later with a child task that carries this task as its parent. Optionally provide wakeOn with taskId or taskIds to wake early on task outcomes; mode defaults to all, or use any for the first match. delayMs or runAt remains required as the ceiling for the whole set. Provide delayMs or runAt, a summary of what you did, and a note that says what is pending and what to check.",
38
143
  inputSchema: z.object({
39
144
  taskId: z.string().describe("The ID of the task you are working on."),
40
145
  delayMs: z
@@ -51,12 +156,20 @@ export const registerDeferTaskTool = (server: McpServer) => {
51
156
  wakeOn: z
52
157
  .object({
53
158
  event: z.enum(["task.completed", "task.failed", "settled"]),
54
- taskId: z.string().min(1),
159
+ taskId: z.string().min(1).optional(),
160
+ taskIds: z.array(z.string().min(1)).min(1).optional(),
161
+ mode: z.enum(["all", "any"]).optional(),
55
162
  })
56
163
  .strict()
164
+ .refine((wake) => (wake.taskId !== undefined) !== (wake.taskIds !== undefined), {
165
+ message: "Provide exactly one of wakeOn.taskId or wakeOn.taskIds.",
166
+ })
167
+ .refine((wake) => !wake.taskIds || new Set(wake.taskIds).size === wake.taskIds.length, {
168
+ message: "wakeOn.taskIds must not contain duplicates.",
169
+ })
57
170
  .optional()
58
171
  .describe(
59
- "Wake early on this task event. settled covers completed or failed. Already-terminal tasks are rejected; a delayMs/runAt ceiling is still required.",
172
+ "Wake early on a task event. Provide taskId or nonempty, unique taskIds. mode defaults to all (every member must match); any wakes on the first match. settled covers completed, failed, or cancelled. Deferred and superseded members follow their continuations; a superseded member without a resume child holds to the ceiling. Any already-terminal member rejects the request; one delayMs/runAt ceiling is still required for the whole set.",
60
173
  ),
61
174
  summary: z
62
175
  .string()
@@ -144,8 +257,10 @@ export const registerDeferTaskTool = (server: McpServer) => {
144
257
  }
145
258
  const nextRunAt = delayMs ? new Date(Date.now() + delayMs).toISOString() : runAt!;
146
259
 
260
+ const watchedTaskIds = wakeOn?.taskIds ?? (wakeOn?.taskId ? [wakeOn.taskId] : []);
261
+ const wakeMode = wakeOn?.mode ?? "all";
147
262
  const wakeDescription = wakeOn
148
- ? `on ${wakeOn.event} for task ${wakeOn.taskId}, or by ${nextRunAt}`
263
+ ? `on ${wakeOn.event} for ${wakeMode} of tasks ${watchedTaskIds.join(", ")}, or by ${nextRunAt}`
149
264
  : `at ${nextRunAt}`;
150
265
  const checksBlock = renderChecks(checks);
151
266
  const taskTemplate = `Resume task ${taskId}: ${note}${checksBlock}`;
@@ -163,16 +278,20 @@ export const registerDeferTaskTool = (server: McpServer) => {
163
278
  if (outputValidationError) return toolErr(outputValidationError);
164
279
  }
165
280
 
281
+ const { tz: requesterTz, isFallback: isFallbackTz } = await resolveRequesterTimezone(
282
+ task.requestedByUserId,
283
+ );
284
+
166
285
  try {
167
286
  const committed = await getDbClient().transaction(async () => {
168
- if (wakeOn) {
169
- if (wakeOn.taskId === taskId)
287
+ for (const watchedId of watchedTaskIds) {
288
+ if (watchedId === taskId)
170
289
  throw new DeferAbortedError("Cannot wake on the task being deferred.");
171
- const watched = await getTaskById(wakeOn.taskId);
172
- if (!watched) throw new DeferAbortedError(`Watched task ${wakeOn.taskId} not found.`);
290
+ const watched = await getTaskById(watchedId);
291
+ if (!watched) throw new DeferAbortedError(`Watched task ${watchedId} not found.`);
173
292
  if (isTerminalTaskStatus(watched.status))
174
293
  throw new DeferAbortedError(
175
- `Watched task ${wakeOn.taskId} is already ${watched.status}; read its result instead of deferring.`,
294
+ `Watched task ${watchedId} is already ${watched.status}; read its result instead of deferring.`,
176
295
  );
177
296
  }
178
297
  const schedule = await createScheduledTask({
@@ -200,20 +319,35 @@ export const registerDeferTaskTool = (server: McpServer) => {
200
319
 
201
320
  if (wakeOn) {
202
321
  await getDbClient().run(
203
- "INSERT INTO deferred_task_waits (scheduleId, taskId, eventName, created_by, updated_by) VALUES (?, ?, ?, ?, ?)",
204
- [schedule.id, wakeOn.taskId, wakeOn.event, createdBy ?? null, createdBy ?? null],
322
+ "INSERT INTO deferred_task_waits (scheduleId, eventName, mode, created_by, updated_by) VALUES (?, ?, ?, ?, ?)",
323
+ [schedule.id, wakeOn.event, wakeMode, createdBy ?? null, createdBy ?? null],
205
324
  );
325
+ for (const watchedId of watchedTaskIds) {
326
+ await getDbClient().run(
327
+ "INSERT INTO deferred_task_wait_members (scheduleId, taskId, created_by, updated_by) VALUES (?, ?, ?, ?)",
328
+ [schedule.id, watchedId, createdBy ?? null, createdBy ?? null],
329
+ );
330
+ }
206
331
  getDbClient().afterCommit(() => {
207
- void reconcileDeferredTaskWaits(wakeOn.taskId).catch((err) => {
208
- console.error("[defer-task] Event wake reconciliation failed:", err);
209
- });
332
+ void Promise.all(watchedTaskIds.map((id) => reconcileDeferredTaskWaits(id))).catch(
333
+ (err) => {
334
+ console.error("[defer-task] Event wake reconciliation failed:", err);
335
+ },
336
+ );
210
337
  });
211
338
  }
212
339
 
340
+ // Full detail for the task log — ISO timestamp, schedule id, checks.
341
+ // Never shown to a human directly; the wake-up task gets note+checks
342
+ // through taskTemplate instead.
213
343
  const deferralDetails = `${summary}\n\nDeferred until ${nextRunAt} (schedule ${schedule.id}). Pending: ${note}${checksBlock}`;
214
344
 
215
- const terminalOutput = task.outputSchema ? output! : deferralDetails;
216
- const completed = await completeTask(taskId, terminalOutput);
345
+ const terminalOutput = task.outputSchema
346
+ ? output!
347
+ : renderHumanFacingDeferral(note, nextRunAt, schedule.id, requesterTz, isFallbackTz);
348
+ const completed = await completeTask(taskId, terminalOutput, {
349
+ addTags: ["deferred"],
350
+ });
217
351
  if (!completed) {
218
352
  // Another writer terminally completed/failed/cancelled this task
219
353
  // between our early check and this transaction's write. Abort:
@@ -225,14 +359,12 @@ export const registerDeferTaskTool = (server: McpServer) => {
225
359
  );
226
360
  }
227
361
 
228
- if (task.outputSchema) {
229
- await createLogEntry({
230
- eventType: "task_progress",
231
- taskId,
232
- agentId: requestInfo.agentId,
233
- newValue: deferralDetails,
234
- });
235
- }
362
+ await createLogEntry({
363
+ eventType: "task_progress",
364
+ taskId,
365
+ agentId: requestInfo.agentId,
366
+ newValue: deferralDetails,
367
+ });
236
368
 
237
369
  // afterCommit: the transaction can still roll back; business-use must
238
370
  // not be told the task completed for a write that never landed.
@@ -0,0 +1,84 @@
1
+ import * as z from "zod";
2
+ import { proxySwarmApi } from "./script-common";
3
+ import { type RequestInfo, type SwarmToolResult, swarmToolOutputSchema, toolErr } from "./utils";
4
+
5
+ export const EXTENSION_TRANSPORT_ERROR =
6
+ "extension_* tools require HTTP MCP transport because agent identity is unavailable over stdio. Use MCP_BASE_URL=http://... or invoke the HTTP API directly.";
7
+
8
+ const extensionListItemSchema = z.looseObject({
9
+ id: z.string().optional(),
10
+ name: z.string().optional(),
11
+ version: z.number().optional(),
12
+ activeVersion: z.number().optional(),
13
+ enabled: z.boolean().optional(),
14
+ status: z.string().optional(),
15
+ priority: z.number().optional(),
16
+ consecutiveFailures: z.number().optional(),
17
+ });
18
+
19
+ export const extensionToolOutputSchema = swarmToolOutputSchema({
20
+ id: z.string().optional(),
21
+ name: z.string().optional(),
22
+ version: z.number().optional(),
23
+ activeVersion: z.number().optional(),
24
+ enabled: z.boolean().optional(),
25
+ status: z.string().optional(),
26
+ priority: z.number().optional(),
27
+ consecutiveFailures: z.number().optional(),
28
+ contentDeduped: z.boolean().optional(),
29
+ extensions: z.array(extensionListItemSchema).optional(),
30
+ });
31
+
32
+ function isRecord(value: unknown): value is Record<string, unknown> {
33
+ return typeof value === "object" && value !== null && !Array.isArray(value);
34
+ }
35
+
36
+ export function coerceExtensionSummary(entry: unknown) {
37
+ const extension = isRecord(entry) ? entry : {};
38
+ return {
39
+ id: typeof extension.id === "string" ? extension.id : undefined,
40
+ name: typeof extension.name === "string" ? extension.name : undefined,
41
+ version: typeof extension.version === "number" ? extension.version : undefined,
42
+ activeVersion:
43
+ typeof extension.activeVersion === "number" ? extension.activeVersion : undefined,
44
+ enabled: typeof extension.enabled === "boolean" ? extension.enabled : undefined,
45
+ status: typeof extension.status === "string" ? extension.status : undefined,
46
+ priority: typeof extension.priority === "number" ? extension.priority : undefined,
47
+ consecutiveFailures:
48
+ typeof extension.consecutiveFailures === "number" ? extension.consecutiveFailures : undefined,
49
+ };
50
+ }
51
+
52
+ function errorMessage(data: unknown, status: number): string {
53
+ if (isRecord(data) && typeof data.error === "string" && data.error) return data.error;
54
+ return `Extensions API request failed with ${status}`;
55
+ }
56
+
57
+ function renderDiagnostics(data: unknown): string | undefined {
58
+ if (!isRecord(data) || !Array.isArray(data.diagnostics)) return undefined;
59
+ const diagnostics = data.diagnostics.map(String).filter(Boolean);
60
+ return diagnostics.length > 0 ? diagnostics.join("\n") : undefined;
61
+ }
62
+
63
+ export async function proxyExtensionsApi<T>(args: {
64
+ method: "GET" | "POST";
65
+ path: string;
66
+ body?: unknown;
67
+ requestInfo: RequestInfo;
68
+ success: (data: unknown) => SwarmToolResult<T & Record<string, unknown>>;
69
+ }): Promise<SwarmToolResult<T & Record<string, unknown>>> {
70
+ return proxySwarmApi({
71
+ ...args,
72
+ transportError: EXTENSION_TRANSPORT_ERROR,
73
+ respond: (data, response) => {
74
+ if (!response.ok) {
75
+ const diagnostics = renderDiagnostics(data);
76
+ if (response.status === 400 && diagnostics) {
77
+ return toolErr("Extension rejected by typecheck.", { details: diagnostics });
78
+ }
79
+ return toolErr(errorMessage(data, response.status), { details: diagnostics });
80
+ }
81
+ return args.success(data);
82
+ },
83
+ });
84
+ }
@@ -0,0 +1,65 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import * as z from "zod";
3
+ import { createToolRegistrar, toolOk } from "@/tools/utils";
4
+ import { ExtensionManifestSchema } from "@/types";
5
+ import {
6
+ coerceExtensionSummary,
7
+ extensionToolOutputSchema,
8
+ proxyExtensionsApi,
9
+ } from "./extension-common";
10
+
11
+ export const EXTENSION_INSTALL_DESCRIPTION =
12
+ "Validate and store a TypeScript extension bundle (manifest plus a files map with one hooks.ts). Fetch the hook contract first: GET /api/extensions/type-defs returns swarm-extension.d.ts with every event and modify shape. The extension remains disabled until an operator enables it.";
13
+
14
+ function extensionInstallResult(data: unknown, fallbackName: string) {
15
+ const body = data as {
16
+ extension?: unknown;
17
+ contentDeduped?: unknown;
18
+ };
19
+ const extension = coerceExtensionSummary(body.extension);
20
+ const name = extension.name ?? fallbackName;
21
+ const version = typeof extension.version === "number" ? ` v${extension.version}` : "";
22
+ const deduped = body.contentDeduped === true ? " Content was unchanged." : "";
23
+ const enableInstruction =
24
+ "An operator must enable it from the dashboard or `POST /api/extensions/{id}/enable`.";
25
+
26
+ return toolOk(`Extension \`${name}\`${version} installed.${deduped} ${enableInstruction}`, {
27
+ data: {
28
+ ...extension,
29
+ name,
30
+ contentDeduped: body.contentDeduped === true,
31
+ },
32
+ });
33
+ }
34
+
35
+ export const registerExtensionInstallTool = (server: McpServer) => {
36
+ createToolRegistrar(server)(
37
+ "extension-install",
38
+ {
39
+ title: "Extension Install",
40
+ description: EXTENSION_INSTALL_DESCRIPTION,
41
+ annotations: { openWorldHint: false },
42
+ inputSchema: z
43
+ .object({
44
+ manifest: ExtensionManifestSchema.describe("Extension bundle manifest."),
45
+ files: z.record(z.string(), z.string()).describe("Bundle files keyed by relative path."),
46
+ priority: z
47
+ .number()
48
+ .int()
49
+ .optional()
50
+ .describe("Handler priority. Lower values run first."),
51
+ config: z.record(z.string(), z.unknown()).optional().describe("Extension configuration."),
52
+ })
53
+ .strict(),
54
+ outputSchema: extensionToolOutputSchema,
55
+ },
56
+ async (args, requestInfo) =>
57
+ proxyExtensionsApi({
58
+ method: "POST",
59
+ path: "/api/extensions/install",
60
+ body: args,
61
+ requestInfo,
62
+ success: (data) => extensionInstallResult(data, args.manifest.name),
63
+ }),
64
+ );
65
+ };
@@ -0,0 +1,66 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import * as z from "zod";
3
+ import { createToolRegistrar, toolOk } from "@/tools/utils";
4
+ import {
5
+ coerceExtensionSummary,
6
+ extensionToolOutputSchema,
7
+ proxyExtensionsApi,
8
+ } from "./extension-common";
9
+
10
+ export const EXTENSION_LIST_DESCRIPTION =
11
+ "List installed extensions and their activation state. To write a new one, fetch GET /api/extensions/type-defs for the hook contract and install it with extension-install.";
12
+
13
+ type ExtensionListItem = ReturnType<typeof coerceExtensionSummary>;
14
+
15
+ function extensionListItems(data: unknown): ExtensionListItem[] {
16
+ if (
17
+ typeof data !== "object" ||
18
+ data === null ||
19
+ !Array.isArray((data as { extensions?: unknown }).extensions)
20
+ ) {
21
+ return [];
22
+ }
23
+ return (data as { extensions: unknown[] }).extensions.map(coerceExtensionSummary);
24
+ }
25
+
26
+ function renderExtensionsTable(extensions: ExtensionListItem[]): string {
27
+ const header = "| Name | Version | Active | Enabled | Status | Priority | Failures |";
28
+ const divider = "| --- | ---: | ---: | --- | --- | ---: | ---: |";
29
+ const rows = extensions.map(
30
+ (extension) =>
31
+ `| ${extension.name ?? "?"} | ${extension.version ?? "?"} | ${extension.activeVersion ?? "?"} | ${extension.enabled ?? false} | ${extension.status ?? "unknown"} | ${extension.priority ?? "?"} | ${extension.consecutiveFailures ?? 0} |`,
32
+ );
33
+ return [header, divider, ...rows].join("\n");
34
+ }
35
+
36
+ export const registerExtensionListTool = (server: McpServer) => {
37
+ createToolRegistrar(server)(
38
+ "extension-list",
39
+ {
40
+ title: "Extension List",
41
+ description: EXTENSION_LIST_DESCRIPTION,
42
+ annotations: { readOnlyHint: true, openWorldHint: false },
43
+ inputSchema: z
44
+ .object({
45
+ enabledOnly: z.boolean().optional().describe("Return only enabled extensions."),
46
+ })
47
+ .strict(),
48
+ outputSchema: extensionToolOutputSchema,
49
+ },
50
+ async ({ enabledOnly }, requestInfo) =>
51
+ proxyExtensionsApi({
52
+ method: "GET",
53
+ path: "/api/extensions",
54
+ requestInfo,
55
+ success: (data) => {
56
+ const extensions = extensionListItems(data).filter(
57
+ (extension) => !enabledOnly || extension.enabled,
58
+ );
59
+ return toolOk(`Found ${extensions.length} extension(s).`, {
60
+ details: renderExtensionsTable(extensions),
61
+ data: { extensions },
62
+ });
63
+ },
64
+ }),
65
+ );
66
+ };
@@ -3,6 +3,7 @@ import { getApiKey } from "@/utils/api-key";
3
3
  import { getMcpBaseUrl } from "@/utils/constants";
4
4
  import {
5
5
  type RequestInfo,
6
+ type SwarmToolData,
6
7
  type SwarmToolResult,
7
8
  swarmToolOutputSchema,
8
9
  toolErr,
@@ -161,7 +162,37 @@ export async function proxyScriptsApi(args: {
161
162
  */
162
163
  uncappedDetails?: boolean;
163
164
  }): Promise<SwarmToolResult> {
164
- if (!args.requestInfo.agentId) return toolErr(SCRIPT_TRANSPORT_ERROR);
165
+ return proxySwarmApi({
166
+ ...args,
167
+ transportError: SCRIPT_TRANSPORT_ERROR,
168
+ respond: (data, res) => {
169
+ const toolData = { status: res.status, data };
170
+ const failure = describeScriptFailure(res.ok, res.status, data);
171
+ if (failure) {
172
+ return toolErr(failure.message, {
173
+ details: failure.details ?? capDetails(args.failureDetails?.(data)),
174
+ data: toolData,
175
+ });
176
+ }
177
+
178
+ const successDetails = args.successDetails?.(data);
179
+ return toolOk(args.successMessage(data), {
180
+ details: args.uncappedDetails ? successDetails : capDetails(successDetails),
181
+ data: toolData,
182
+ });
183
+ },
184
+ });
185
+ }
186
+
187
+ export async function proxySwarmApi<T extends Record<string, unknown>>(args: {
188
+ method: "GET" | "POST" | "PATCH" | "DELETE";
189
+ path: string;
190
+ body?: unknown;
191
+ requestInfo: RequestInfo;
192
+ transportError: string;
193
+ respond: (data: unknown, response: { ok: boolean; status: number }) => SwarmToolResult<T>;
194
+ }): Promise<SwarmToolResult<T>> {
195
+ if (!args.requestInfo.agentId) return toolErr(args.transportError);
165
196
 
166
197
  const apiKey = getApiKey();
167
198
  const headers: Record<string, string> = {
@@ -172,8 +203,7 @@ export async function proxyScriptsApi(args: {
172
203
  if (args.requestInfo.sourceTaskId) {
173
204
  headers["X-Source-Task-Id"] = args.requestInfo.sourceTaskId;
174
205
  }
175
- // Runtime identity is process context from the worker's MCP session — it
176
- // rides the proxy like the agent identity, never as tool or script input.
206
+ // Forward runtime identity from the worker's MCP session, like agent identity.
177
207
  if (args.requestInfo.runtimeInstanceId) {
178
208
  headers["X-Runtime-Instance-ID"] = args.requestInfo.runtimeInstanceId;
179
209
  }
@@ -189,22 +219,17 @@ export async function proxyScriptsApi(args: {
189
219
  try {
190
220
  data = JSON.parse(text);
191
221
  } catch {
192
- data = { error: text };
222
+ const contentType = res.headers.get("content-type") ?? "unknown";
223
+ // Transport-level failure: only `{ status }` is known here, and that shape
224
+ // is not statically related to the caller's `T`. Tool output schemas are
225
+ // loose with every data field optional, so the wire result stays valid.
226
+ const transportData: SwarmToolData = { status: res.status };
227
+ return toolErr<T>(
228
+ `Swarm API returned non-JSON response (HTTP ${res.status}, Content-Type: ${contentType}). Check API/proxy routing.`,
229
+ { details: capDetails(text), data: transportData as T },
230
+ );
193
231
  }
194
232
  }
195
233
 
196
- const toolData = { status: res.status, data };
197
- const failure = describeScriptFailure(res.ok, res.status, data);
198
- if (failure) {
199
- return toolErr(failure.message, {
200
- details: failure.details ?? capDetails(args.failureDetails?.(data)),
201
- data: toolData,
202
- });
203
- }
204
-
205
- const successDetails = args.successDetails?.(data);
206
- return toolOk(args.successMessage(data), {
207
- details: args.uncappedDetails ? successDetails : capDetails(successDetails),
208
- data: toolData,
209
- });
234
+ return args.respond(data, res);
210
235
  }