@desplega.ai/agent-swarm 2.5.4 → 2.7.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 (354) hide show
  1. package/README.md +4 -2
  2. package/dist/{acp-adapter-ek38cwsv.js → acp-adapter-ma1js5n7.js} +9 -9
  3. package/dist/{actions-92mepq03.js → actions-1vhmtvg9.js} +21 -21
  4. package/dist/{amp-adapter-9gaxg015.js → amp-adapter-sxqqvmdk.js} +4 -4
  5. package/dist/{app-ctn8gsgx.js → app-mwx1d5a9.js} +3 -3
  6. package/dist/{assistant-zyr5kz47.js → assistant-yx1p6g82.js} +30 -26
  7. package/dist/{boot-reembed-w75maga2.js → boot-reembed-5h37a2c2.js} +1 -1
  8. package/dist/{boot-reembed-dwzfttdr.js → boot-reembed-7h78vmvv.js} +2 -1
  9. package/dist/{boot-scrub-sweep-e9kq43kf.js → boot-scrub-sweep-8mawd4gw.js} +4 -4
  10. package/dist/{boot-seeds-x28v3fhb.js → boot-seeds-ca84dhnn.js} +4 -4
  11. package/dist/{claude-adapter-f0afqnne.js → claude-adapter-wqt4hfkq.js} +9 -9
  12. package/dist/{claude-managed-adapter-x81a7r65.js → claude-managed-adapter-6fjqtr2z.js} +2 -2
  13. package/dist/{claude-managed-setup-bqzwjd9x.js → claude-managed-setup-rqv98sea.js} +10 -3
  14. package/dist/{claude-sdk-session-7b7whkaq.js → claude-sdk-session-f29f3b2x.js} +11 -11
  15. package/dist/{cli-sxgtf5fw.js → cli-07q03xm2.js} +1 -1
  16. package/dist/{cli-kxpfx2xr.js → cli-0z1xjzwa.js} +2 -2
  17. package/dist/{cli-g9vd9ece.js → cli-1hhbzkbb.js} +14 -16
  18. package/dist/{cli-qqzmjxkf.js → cli-1m9x8dr5.js} +11 -6
  19. package/dist/{cli-jbnn4vxb.js → cli-2pvzmas1.js} +3 -3
  20. package/dist/{cli-x77gen2p.js → cli-2y7v133v.js} +7 -7
  21. package/dist/{cli-dew6x255.js → cli-39aet655.js} +2 -2
  22. package/dist/{cli-g1cw7xtt.js → cli-39rrw8yp.js} +1 -1
  23. package/dist/{cli-jb30dzvk.js → cli-48gxjmcz.js} +530 -190
  24. package/dist/{cli-pzatvw94.js → cli-54sj64fc.js} +2 -2
  25. package/dist/{cli-bvpgr9ny.js → cli-60sty27p.js} +5 -5
  26. package/dist/{cli-asq5ydm4.js → cli-6a9qfa8b.js} +411 -69
  27. package/dist/{cli-277c31dw.js → cli-6mgdba42.js} +225 -3
  28. package/dist/{cli-19202kew.js → cli-6srv653c.js} +5 -5
  29. package/dist/{cli-n4c8e3s5.js → cli-708wdy32.js} +1 -1
  30. package/dist/{cli-pasyvjmm.js → cli-737pyqxr.js} +1 -1
  31. package/dist/{cli-6x9ma5w5.js → cli-803188pb.js} +1 -1
  32. package/dist/{cli-hh5we352.js → cli-86ehn4s5.js} +7 -7
  33. package/dist/{cli-9s05422w.js → cli-8mbc9za9.js} +1 -1
  34. package/dist/{cli-ff1tm9n9.js → cli-8wmj18cz.js} +1 -1
  35. package/dist/{cli-1r5ad7j5.js → cli-9gsm2rzp.js} +12 -12
  36. package/dist/{cli-mz9afxxf.js → cli-9pshx6rd.js} +17 -3
  37. package/dist/{cli-s682zgqz.js → cli-a6ds2epe.js} +1 -1
  38. package/dist/{cli-6a1xg1p7.js → cli-a89zdqz9.js} +2 -2
  39. package/dist/{cli-03vpq45j.js → cli-ay40dnj3.js} +2 -2
  40. package/dist/{cli-tsma7p8v.js → cli-aypd09nf.js} +2 -2
  41. package/dist/{cli-ethgy7ha.js → cli-b3yzyc57.js} +2 -2
  42. package/dist/{cli-bn0rewsy.js → cli-c081bj3b.js} +2 -2
  43. package/dist/{cli-mmhj8vd7.js → cli-c668vvcf.js} +11 -2
  44. package/dist/{cli-sbwsxvtd.js → cli-d20qvpr2.js} +2 -2
  45. package/dist/{cli-c952c7ac.js → cli-e1595bgx.js} +3 -3
  46. package/dist/{cli-621ce7kw.js → cli-f12mr20z.js} +3 -3
  47. package/dist/{cli-dqygeeh5.js → cli-f1e0ptjj.js} +10 -1
  48. package/dist/{cli-xzfq4at3.js → cli-f4dbh7a5.js} +16 -12
  49. package/dist/{cli-ha9rmwgq.js → cli-fb8m699c.js} +2 -2
  50. package/dist/{cli-5bnsc0e1.js → cli-gsfk58nw.js} +5 -5
  51. package/dist/cli-gt4h8cxw.js +895 -0
  52. package/dist/{cli-nwyw3zhg.js → cli-gxee0bqa.js} +1 -1
  53. package/dist/{cli-25n3mv5d.js → cli-h5jeg46p.js} +6 -6
  54. package/dist/{cli-gn5af0x5.js → cli-he9gbdx0.js} +62 -12
  55. package/dist/{cli-gbfxp48k.js → cli-j0ppywdr.js} +42 -35
  56. package/dist/{cli-k4tjsdx9.js → cli-j3y0x2n4.js} +277 -199
  57. package/dist/{cli-7p4jxgtt.js → cli-j9bpmykh.js} +75 -27
  58. package/dist/{cli-dabehxhp.js → cli-kt907da6.js} +1 -1
  59. package/dist/{cli-wx2nj19m.js → cli-kv7sbc2c.js} +6 -6
  60. package/dist/{cli-abs8ndtr.js → cli-m30k9777.js} +26 -26
  61. package/dist/{cli-cz5nmdxt.js → cli-m5e802r7.js} +2 -2
  62. package/dist/{cli-0shx9j93.js → cli-pfk92eqz.js} +2 -2
  63. package/dist/{cli-xtrz6bzs.js → cli-ptfq202x.js} +5 -3
  64. package/dist/{cli-6n8axjv1.js → cli-qj442fws.js} +2 -2
  65. package/dist/{cli-c61mbdds.js → cli-rd5m70gj.js} +4 -4
  66. package/dist/{cli-eftzyh9y.js → cli-s09j9vp5.js} +2 -2
  67. package/dist/{cli-m4ecdxvz.js → cli-s0mgx214.js} +7 -7
  68. package/dist/{cli-wghjkr0e.js → cli-sgqm2582.js} +10 -7
  69. package/dist/{cli-2655k16q.js → cli-sjctjr48.js} +2 -2
  70. package/dist/{cli-nzfnt2gs.js → cli-tf1n0r0b.js} +1 -1
  71. package/dist/{cli-x0f2v69c.js → cli-twctnkng.js} +12 -7
  72. package/dist/{cli-j6rktc91.js → cli-v6shg7na.js} +1 -1
  73. package/dist/{cli-gx4rm0qx.js → cli-vz78s5wm.js} +1 -1
  74. package/dist/{cli-gwhjmh8g.js → cli-wqr7cea4.js} +5 -5
  75. package/dist/{cli-e42vss7b.js → cli-x37r7qwf.js} +16 -8
  76. package/dist/{cli-4b2z6n0m.js → cli-yd2nreqw.js} +17 -8
  77. package/dist/{cli-64mh9mf3.js → cli-ymjpkxgn.js} +69 -17
  78. package/dist/{cli-5kqnemen.js → cli-yvy48785.js} +1 -1
  79. package/dist/{cli-9j129brq.js → cli-yxbsmx56.js} +6 -6
  80. package/dist/cli.js +22 -20
  81. package/dist/{codex-adapter-wch12q99.js → codex-adapter-zk59x2zh.js} +7 -7
  82. package/dist/{codex-hook-brpnycn5.js → codex-hook-xyjhvxwd.js} +1 -1
  83. package/dist/{codex-login-rn7c5gxt.js → codex-login-8cg0vhf2.js} +1 -1
  84. package/dist/{codex-session-runner-41pfcr95.js → codex-session-runner-kh6s4j3n.js} +8 -8
  85. package/dist/{commands-9849dkj6.js → commands-68et1qv2.js} +2 -2
  86. package/dist/{controller-ja0sd95p.js → controller-fyrbnzg4.js} +4 -4
  87. package/dist/{controllers-connect-xp05dj6x.js → controllers-connect-nep9h2xv.js} +23 -23
  88. package/dist/{cursor-adapter-623qxqew.js → cursor-adapter-yss3pg7t.js} +2 -2
  89. package/dist/{db-w759qmx0.js → db-28gjyvxj.js} +4 -2
  90. package/dist/{db-47vj40dy.js → db-8ftwvzba.js} +3 -3
  91. package/dist/{devin-adapter-4ycy4vrf.js → devin-adapter-byen2cxt.js} +1 -1
  92. package/dist/{dispatcher-7b8e4z2j.js → dispatcher-hayexmkr.js} +20 -20
  93. package/dist/{docker-s84hjs91.js → docker-fdzyewpz.js} +2 -2
  94. package/dist/{dsh-adapter-2ezw3xea.js → dsh-adapter-46wawff9.js} +2 -2
  95. package/dist/{e2b-9gkdhm3c.js → e2b-222hsajc.js} +2 -2
  96. package/dist/{e2b-cjtf8k1z.js → e2b-fn3zacvt.js} +2 -2
  97. package/dist/{easter-egg-3d-e4qv8dxj.js → easter-egg-3d-8d1e0h4m.js} +1 -1
  98. package/dist/{event-bus-3z78ep5n.js → event-bus-pghg8hqy.js} +1 -1
  99. package/dist/{execute-0xpz8950.js → execute-cjrfxfnm.js} +1 -1
  100. package/dist/{external-channel-e2s028da.js → external-channel-xbmxcg09.js} +2 -2
  101. package/dist/{grok-adapter-ys7gaqee.js → grok-adapter-ag4wks2a.js} +10 -10
  102. package/dist/handlers-tzmevqb5.js +41 -0
  103. package/dist/{heartbeat-w3mntghj.js → heartbeat-psfvcgwp.js} +23 -23
  104. package/dist/{hook-g7kvv6x9.js → hook-8pz04697.js} +6 -6
  105. package/dist/{hook-3jjywnpb.js → hook-cyq4mryn.js} +8 -8
  106. package/dist/{http-tkp7d4kb.js → http-haj1nd6k.js} +921 -169
  107. package/dist/{index-q7m26kvs.js → index-jwt2bj6f.js} +265 -97
  108. package/dist/{index-3n8m9ea2.js → index-q52f47pe.js} +1 -1
  109. package/dist/{index-31ceyf42.js → index-syvhnvtw.js} +1 -1
  110. package/dist/index-y1ajx04a.js +43 -0
  111. package/dist/{index-1ef1myq2.js → index-z5h22pes.js} +20 -20
  112. package/dist/{index-xdaahthb.js → index-zyq8ct52.js} +20 -20
  113. package/dist/{keepalive-v4ta8wgg.js → keepalive-xdvsamvj.js} +4 -4
  114. package/dist/lead-hqg57kdp.js +79 -0
  115. package/dist/{maintenance-84n1ex21.js → maintenance-4y2xzh3a.js} +1 -1
  116. package/dist/{oauth-refresh-sweep-vjnnandw.js → oauth-refresh-sweep-jd4r7zbs.js} +9 -4
  117. package/dist/{onboard-sfmpe4np.js → onboard-18447q9q.js} +6 -6
  118. package/dist/{opencode-adapter-jmpst4q4.js → opencode-adapter-26b2frdf.js} +7 -7
  119. package/dist/{otel-impl-v6cc0zve.js → otel-impl-f9tgr8yw.js} +2 -2
  120. package/dist/{pi-mono-adapter-fk8fkcf6.js → pi-mono-adapter-qn6qbga1.js} +38 -17
  121. package/dist/{pricing-refresh-fsk1e1zc.js → pricing-refresh-dw2tgb53.js} +3 -3
  122. package/dist/{rbac-roles-prmkdnkw.js → rbac-roles-aygm2760.js} +2 -2
  123. package/dist/{rbac-roles-zxmxqa39.js → rbac-roles-s4nwj2m3.js} +1 -1
  124. package/dist/render-v2-yfvdfr0x.js +16 -0
  125. package/dist/{resume-frdtgfa0.js → resume-1bmvjbf3.js} +9 -9
  126. package/dist/{runtime-bt5pkpm1.js → runtime-f7tpzedy.js} +1 -1
  127. package/dist/{session-access-fpez4y5q.js → session-access-r5garej6.js} +4 -4
  128. package/dist/session-source-backfill-424wzv0r.js +110 -0
  129. package/dist/{setup-mq6m3m8q.js → setup-8tygvxkq.js} +1 -1
  130. package/dist/{virtual-modules-mnfbh5mr.js → virtual-modules-xsw323b3.js} +2 -2
  131. package/dist/{work-objects-kfahhjsq.js → work-objects-ggnghpgf.js} +23 -23
  132. package/dist/worker-sqjd1nqf.js +79 -0
  133. package/dist/{x-pbpezr0s.js → x-wxrzvzx8.js} +2 -2
  134. package/openapi.json +1324 -14
  135. package/package.json +5 -3
  136. package/src/assets/source-key.ts +95 -0
  137. package/src/be/automation-preflight.ts +2 -0
  138. package/src/be/db/api-keys.ts +89 -0
  139. package/src/be/db/session-labels.ts +28 -0
  140. package/src/be/db/session-moves.ts +5 -6
  141. package/src/be/db/session-tree.ts +153 -14
  142. package/src/be/db/tasks/read.ts +11 -0
  143. package/src/be/db/tasks/write.ts +6 -1
  144. package/src/be/db/workflow-runs.ts +219 -6
  145. package/src/be/db-retention.ts +61 -6
  146. package/src/be/db.ts +135 -132
  147. package/src/be/memory/providers/openai-embedding.ts +5 -1
  148. package/src/be/memory/task-learning.ts +155 -0
  149. package/src/be/migrations/244_api_key_validation.sql +4 -0
  150. package/src/be/migrations/245_script_executor.sql +3 -0
  151. package/src/be/oauth-refresh-sweep.ts +2 -1
  152. package/src/be/onboarding.ts +4 -0
  153. package/src/be/scripts/boot-reembed.ts +2 -0
  154. package/src/be/scripts/db.ts +14 -4
  155. package/src/be/scripts/embeddings.ts +2 -0
  156. package/src/be/scripts/run-saved.ts +1 -0
  157. package/src/be/scripts/typecheck.ts +10 -4
  158. package/src/be/seed-skills/index.ts +7 -0
  159. package/src/be/session-source-backfill.ts +163 -0
  160. package/src/be/swarm-config-guard.ts +1 -0
  161. package/src/comb/review-batch.ts +28 -1
  162. package/src/comb/templates.ts +7 -2
  163. package/src/commands/claude-managed-setup.ts +7 -1
  164. package/src/commands/credential-refresh.ts +76 -1
  165. package/src/commands/credential-wait.ts +2 -2
  166. package/src/commands/onboard/compose-generator.ts +5 -3
  167. package/src/commands/provider-credentials.ts +212 -171
  168. package/src/commands/runner.ts +25 -4
  169. package/src/extensions/apply-task-create.ts +8 -1
  170. package/src/extensions/contract-types.generated.ts +1 -1
  171. package/src/extensions/contract.ts +5 -0
  172. package/src/heartbeat/heartbeat.ts +14 -1
  173. package/src/heartbeat/script-runner-probe.ts +47 -0
  174. package/src/http/all-routes.ts +2 -0
  175. package/src/http/api-keys.ts +77 -9
  176. package/src/http/assets.ts +15 -0
  177. package/src/http/codex-oauth-accounts.ts +191 -0
  178. package/src/http/comb.ts +15 -2
  179. package/src/http/config.ts +1 -1
  180. package/src/http/core.ts +6 -0
  181. package/src/http/fleet-benchmark.ts +5 -2
  182. package/src/http/idempotency-key.ts +1 -1
  183. package/src/http/index.ts +14 -3
  184. package/src/http/integrations.ts +5 -1
  185. package/src/http/kv.ts +8 -3
  186. package/src/http/onboarding.ts +6 -1
  187. package/src/http/pages-public.ts +38 -5
  188. package/src/http/pool-keys.ts +354 -0
  189. package/src/http/scripts.ts +27 -0
  190. package/src/http/sessions.ts +37 -12
  191. package/src/http/stats.ts +1 -0
  192. package/src/http/status.ts +26 -12
  193. package/src/http/tasks.ts +3 -0
  194. package/src/http/used-by.ts +7 -2
  195. package/src/http/workflows.ts +88 -9
  196. package/src/http/x.ts +5 -0
  197. package/src/integrations/chat-sdk/inbound.ts +27 -0
  198. package/src/kv-reserved-namespaces.ts +19 -0
  199. package/src/prompts/base-prompt.ts +21 -1
  200. package/src/prompts/session-templates.ts +17 -3
  201. package/src/providers/acp-targets.ts +1 -1
  202. package/src/providers/amp-adapter.ts +1 -1
  203. package/src/providers/claude-adapter.ts +5 -1
  204. package/src/providers/claude-managed-adapter.ts +16 -9
  205. package/src/providers/codex-adapter.ts +71 -11
  206. package/src/providers/dsh-adapter.ts +1 -1
  207. package/src/providers/pi-mono-adapter.ts +41 -11
  208. package/src/scheduler/scheduler.ts +5 -3
  209. package/src/scripts-runtime/eval-harness.ts +8 -1
  210. package/src/scripts-runtime/executors/native.ts +22 -16
  211. package/src/scripts-runtime/executors/quickjs-prelude.ts +103 -1
  212. package/src/scripts-runtime/executors/quickjs-runner.ts +2 -1
  213. package/src/scripts-runtime/executors/quickjs.ts +7 -0
  214. package/src/scripts-runtime/executors/registry.ts +32 -0
  215. package/src/scripts-runtime/executors/types.ts +7 -0
  216. package/src/scripts-runtime/host-validated-args.ts +133 -0
  217. package/src/scripts-runtime/loader.ts +30 -3
  218. package/src/scripts-runtime/swarm-sdk.ts +16 -3
  219. package/src/scripts-runtime/types/stdlib.d.ts +10 -2
  220. package/src/scripts-runtime/types/swarm-extension.d.ts +17 -8
  221. package/src/scripts-runtime/types/swarm-sdk.d.ts +10 -2
  222. package/src/slack/assistant.ts +7 -0
  223. package/src/slack/channel-labels.ts +74 -0
  224. package/src/slack/handlers.ts +8 -0
  225. package/src/slack/inbound-files.ts +5 -2
  226. package/src/slack/thread-buffer.ts +4 -0
  227. package/src/tasks/sibling-awareness.ts +12 -3
  228. package/src/telemetry-context.ts +5 -6
  229. package/src/telemetry-identity.ts +3 -3
  230. package/src/tests/api-key-status-by-suffix.test.ts +242 -0
  231. package/src/tests/api-keys-validation.test.ts +170 -0
  232. package/src/tests/base-prompt.test.ts +70 -2
  233. package/src/tests/claude-managed-adapter.test.ts +46 -0
  234. package/src/tests/claude-routes-e2e.test.ts +7 -2
  235. package/src/tests/codex-credential-pools.test.ts +120 -0
  236. package/src/tests/codex-gateway-route.test.ts +165 -0
  237. package/src/tests/codex-oauth-accounts.test.ts +357 -0
  238. package/src/tests/comb-review-batch.test.ts +63 -2
  239. package/src/tests/community-linear-delivery-loop.test.ts +2099 -0
  240. package/src/tests/config-pool-keys.test.ts +533 -0
  241. package/src/tests/credential-check.test.ts +294 -4
  242. package/src/tests/credential-refresh.test.ts +172 -1
  243. package/src/tests/db-retention.test.ts +9 -3
  244. package/src/tests/db-task-read-contract.test.ts +1 -0
  245. package/src/tests/db-task-write-contract.test.ts +1 -3
  246. package/src/tests/docs-index-footprint.test.js +3 -3
  247. package/src/tests/docs-index.test.ts +135 -0
  248. package/src/tests/dsh-adapter.test.ts +1 -1
  249. package/src/tests/extensions-pre-task.test.ts +53 -1
  250. package/src/tests/fetch-resolved-env.test.ts +16 -2
  251. package/src/tests/fixtures/extensions/record-task-create/hooks.ts +9 -0
  252. package/src/tests/fixtures/extensions/record-task-create/manifest.json +7 -0
  253. package/src/tests/fleet-benchmark.test.ts +8 -0
  254. package/src/tests/get-workflow-run-tool.test.ts +98 -0
  255. package/src/tests/heartbeat-checklist.test.ts +89 -0
  256. package/src/tests/heartbeat-script-runner-probe.test.ts +83 -0
  257. package/src/tests/integrations-http.test.ts +32 -2
  258. package/src/tests/internal-ai/complete-structured.test.ts +37 -1
  259. package/src/tests/internal-ai/credentials.test.ts +23 -0
  260. package/src/tests/kv-http.test.ts +32 -0
  261. package/src/tests/kv-tool.test.ts +25 -0
  262. package/src/tests/multi-runtime-registration.test.ts +12 -12
  263. package/src/tests/oauth-refresh-sweep.test.ts +34 -1
  264. package/src/tests/onboard-compose.test.ts +20 -0
  265. package/src/tests/onboarding.test.ts +108 -1
  266. package/src/tests/openai-embedding-credentials.test.ts +35 -0
  267. package/src/tests/opencode-adapter.test.ts +2 -1
  268. package/src/tests/openrouter-attribution-call-sites.test.ts +2 -2
  269. package/src/tests/org-website.test.ts +36 -0
  270. package/src/tests/pages-frame-ancestors.test.ts +162 -0
  271. package/src/tests/pi-codemode.test.ts +2 -1
  272. package/src/tests/pi-mono-adapter.test.ts +54 -0
  273. package/src/tests/pi-native-mcp.test.ts +13 -1
  274. package/src/tests/pi-tool-exposure.test.ts +8 -8
  275. package/src/tests/probe-tool-schema-tokens.test.ts +144 -0
  276. package/src/tests/prompt-template-session.test.ts +4 -3
  277. package/src/tests/provider-key-probes.test.ts +181 -0
  278. package/src/tests/rbac-e2e-helpers.test.ts +88 -1
  279. package/src/tests/rbac-e2e-helpers.ts +36 -11
  280. package/src/tests/request-composition-derive.test.ts +329 -0
  281. package/src/tests/script-connections.test.ts +4 -0
  282. package/src/tests/script-executor-conformance.test.ts +36 -1
  283. package/src/tests/script-executor-quickjs-pool.test.ts +50 -0
  284. package/src/tests/script-executor-quickjs.test.ts +140 -0
  285. package/src/tests/script-host-validated-args-native.test.ts +196 -0
  286. package/src/tests/script-per-script-executor.test.ts +415 -0
  287. package/src/tests/script-runs-retention.test.ts +212 -0
  288. package/src/tests/scripts-external-api.test.ts +45 -0
  289. package/src/tests/sdk-allowlist.test.ts +42 -2
  290. package/src/tests/self-improvement.test.ts +4 -4
  291. package/src/tests/session-source-backfill.test.ts +429 -0
  292. package/src/tests/session-source-keys.test.ts +236 -0
  293. package/src/tests/session-source-labels.test.ts +419 -0
  294. package/src/tests/sessions-detail-batch.test.ts +2 -0
  295. package/src/tests/sessions-move.test.ts +131 -0
  296. package/src/tests/sessions-tree.test.ts +299 -2
  297. package/src/tests/slack-inbound-files.test.ts +103 -23
  298. package/src/tests/status.test.ts +101 -18
  299. package/src/tests/store-progress-learning.test.ts +288 -0
  300. package/src/tests/tasks-workflow-run-filter.test.ts +146 -0
  301. package/src/tests/test-net.ts +31 -0
  302. package/src/tests/transcription.test.ts +8 -0
  303. package/src/tests/ui-logs-parser.test.ts +180 -0
  304. package/src/tests/used-by-route.test.ts +11 -0
  305. package/src/tests/workflow-cascade-cancel.test.ts +172 -0
  306. package/src/tests/workflow-engine-v2.test.ts +116 -1
  307. package/src/tests/workflow-http-v2.test.ts +131 -0
  308. package/src/tests/workflow-mcp-trigger-schema.test.ts +209 -0
  309. package/src/tests/workflow-run-finished-event.test.ts +263 -0
  310. package/src/tests/workflow-step-order.test.ts +92 -0
  311. package/src/tests/workflow-sub-workflow.test.ts +30 -0
  312. package/src/tests/workflow-swarm-script.test.ts +34 -0
  313. package/src/tools/get-tasks.ts +8 -0
  314. package/src/tools/kv/kv-read-auth.ts +4 -2
  315. package/src/tools/script-common.ts +1 -0
  316. package/src/tools/script-run.ts +7 -0
  317. package/src/tools/script-upsert.ts +7 -0
  318. package/src/tools/store-progress.ts +67 -1
  319. package/src/tools/task-action.ts +1 -1
  320. package/src/tools/workflows/get-workflow-run.ts +14 -4
  321. package/src/tools/workflows/trigger-workflow.ts +104 -10
  322. package/src/types.ts +36 -1
  323. package/src/utils/company-profile.ts +51 -1
  324. package/src/utils/credentials.ts +10 -4
  325. package/src/utils/internal-ai/complete-structured.ts +5 -0
  326. package/src/utils/internal-ai/credentials.ts +4 -3
  327. package/src/utils/provider-key-probes.ts +328 -0
  328. package/src/utils/transcription.ts +4 -1
  329. package/src/workflows/engine.ts +18 -16
  330. package/src/workflows/executors/sub-workflow.ts +8 -1
  331. package/src/workflows/executors/swarm-script.ts +5 -0
  332. package/src/workflows/foreach-driver.ts +5 -11
  333. package/src/workflows/node-timeout.ts +30 -0
  334. package/templates/ai-toolbox.manifest.json +2 -2
  335. package/templates/community/linear-delivery-loop/PLAYBOOK.md +323 -0
  336. package/templates/community/linear-delivery-loop/README.md +38 -0
  337. package/templates/community/linear-delivery-loop/config.example.json +31 -0
  338. package/templates/community/linear-delivery-loop/lead-prompt.md +34 -0
  339. package/templates/community/linear-delivery-loop/schedule.json +10 -0
  340. package/templates/community/linear-delivery-loop/scripts/linear-delivery-setup.ts +148 -0
  341. package/templates/community/linear-delivery-loop/scripts/linear-delivery-tick.ts +1781 -0
  342. package/templates/skills/researching/content.md +2 -0
  343. package/templates/skills/swarm-docs/config.json +14 -0
  344. package/templates/skills/swarm-docs/content.md +144 -0
  345. package/templates/skills/swarm-scripts/SKILL.md +1 -0
  346. package/templates/skills/swarm-scripts/content.md +1 -0
  347. package/dist/cli-7421jzsw.js +0 -84
  348. package/dist/cli-e45e4m6b.js +0 -413
  349. package/dist/cli-zw88zjba.js +0 -307
  350. package/dist/handlers-wy1dr6ja.js +0 -41
  351. package/dist/index-nmm79qgr.js +0 -43
  352. package/dist/lead-sawnyqxk.js +0 -79
  353. package/dist/render-v2-pjwxvnkw.js +0 -12
  354. package/dist/worker-v5e5rxj3.js +0 -79
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Unforgeable host-only capability symbol set on `RunScriptInput` strictly at
3
+ * the host boundary (`POST /api/x/script/{endpointId}`) after `validateJsonSchema`
4
+ * succeeds. Because `Symbol` keys cannot be produced by JSON deserialization,
5
+ * callers that forward request-shaped JSON fields into `RunScriptInput` can
6
+ * never opt out of `argsSchema` validation.
7
+ */
8
+ export const HOST_VALIDATED_ARGS = Symbol("swarm.hostValidatedArgs");
9
+
10
+ /**
11
+ * Checks whether a runtime `argsSchema` (Zod v4 / `zod/mini` schema) and its
12
+ * candidate `value` are semantically equivalent to the host-side JSON Schema
13
+ * validation performed by `validateJsonSchema` in `src/http/x.ts`.
14
+ *
15
+ * Returns `true` only when `schema.parse(value)` is guaranteed to succeed and
16
+ * return a value deeply identical to `value` without running any defaults,
17
+ * coercions, transforms, refinements, or unknown-key stripping.
18
+ */
19
+ export function isHostValidationEquivalent(schema: unknown, value: unknown): boolean {
20
+ if (!schema || typeof schema !== "object") return false;
21
+ const zodInternal = (schema as { _zod?: { def?: Record<string, unknown>; values?: unknown } })
22
+ ._zod;
23
+ const def = zodInternal?.def;
24
+ if (!def || typeof def !== "object") return false;
25
+
26
+ if (def.coerce) return false;
27
+ if (Array.isArray(def.checks) && def.checks.length > 0) return false;
28
+
29
+ switch (def.type) {
30
+ case "string":
31
+ return typeof value === "string" && !def.format;
32
+ case "number":
33
+ return typeof value === "number" && Number.isFinite(value) && !def.format;
34
+ case "boolean":
35
+ return typeof value === "boolean";
36
+ case "literal": {
37
+ const rawValues = def.values;
38
+ const values = Array.isArray(rawValues)
39
+ ? rawValues
40
+ : rawValues instanceof Set
41
+ ? Array.from(rawValues)
42
+ : [];
43
+ return (
44
+ values.length > 0 &&
45
+ values.some((v) => v === value && typeof v !== "bigint" && typeof v !== "symbol")
46
+ );
47
+ }
48
+ case "enum": {
49
+ if (zodInternal?.values instanceof Set) {
50
+ return zodInternal.values.has(value);
51
+ }
52
+ const entriesObj =
53
+ def.entries && typeof def.entries === "object"
54
+ ? (def.entries as Record<string, unknown>)
55
+ : null;
56
+ if (!entriesObj) return false;
57
+ const entries = Object.values(entriesObj).filter(
58
+ (v) => !(typeof v === "string" && typeof entriesObj[v] === "number"),
59
+ );
60
+ return entries.length > 0 && entries.includes(value);
61
+ }
62
+ case "optional":
63
+ return value === undefined
64
+ ? isStructuralSchemaNode(def.innerType)
65
+ : isHostValidationEquivalent(def.innerType, value);
66
+ case "nullable":
67
+ return value === null
68
+ ? isStructuralSchemaNode(def.innerType)
69
+ : isHostValidationEquivalent(def.innerType, value);
70
+ case "array":
71
+ return (
72
+ Array.isArray(value) &&
73
+ isStructuralSchemaNode(def.element) &&
74
+ value.every((item) => isHostValidationEquivalent(def.element, item))
75
+ );
76
+ case "object": {
77
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
78
+ const rawShape = typeof def.shape === "function" ? def.shape() : def.shape;
79
+ if (!rawShape || typeof rawShape !== "object") return false;
80
+ const shape = rawShape as Record<string, unknown>;
81
+ const obj = value as Record<string, unknown>;
82
+
83
+ // If `value` carries extra keys not declared in `shape`, let `.parse()`
84
+ // handle key stripping (`z.object`), passthrough (`z.looseObject`), or
85
+ // rejection (`z.strictObject`).
86
+ for (const key of Object.keys(obj)) {
87
+ if (!Object.hasOwn(shape, key)) return false;
88
+ }
89
+
90
+ for (const [key, propSchema] of Object.entries(shape)) {
91
+ // Retain Zod's property lookup semantics: if `key` is absent as an own
92
+ // property on `obj` but resolves via `Object.prototype` (e.g. `constructor`,
93
+ // `toString`), fall back to `.parse()` so Zod validates/rejects the
94
+ // inherited prototype value identically to `main`.
95
+ if (!Object.hasOwn(obj, key) && key in obj) return false;
96
+ const propValue = obj[key];
97
+ if (!isHostValidationEquivalent(propSchema, propValue)) return false;
98
+ }
99
+ return true;
100
+ }
101
+ default:
102
+ return false;
103
+ }
104
+ }
105
+
106
+ function isStructuralSchemaNode(schema: unknown): boolean {
107
+ if (!schema || typeof schema !== "object") return false;
108
+ const def = (schema as { _zod?: { def?: Record<string, unknown> } })._zod?.def;
109
+ if (!def || typeof def !== "object") return false;
110
+ if (def.coerce) return false;
111
+ if (Array.isArray(def.checks) && def.checks.length > 0) return false;
112
+ switch (def.type) {
113
+ case "string":
114
+ case "number":
115
+ return !def.format;
116
+ case "boolean":
117
+ case "literal":
118
+ case "enum":
119
+ return true;
120
+ case "optional":
121
+ case "nullable":
122
+ return isStructuralSchemaNode(def.innerType);
123
+ case "array":
124
+ return isStructuralSchemaNode(def.element);
125
+ case "object": {
126
+ const rawShape = typeof def.shape === "function" ? def.shape() : def.shape;
127
+ if (!rawShape || typeof rawShape !== "object") return false;
128
+ return Object.values(rawShape as Record<string, unknown>).every(isStructuralSchemaNode);
129
+ }
130
+ default:
131
+ return false;
132
+ }
133
+ }
@@ -6,16 +6,18 @@ import { createValueScrubber, scrubObject, scrubSecrets } from "../utils/secret-
6
6
  import type { ScriptApiConnectionDescriptor, ScriptMcpConnectionDescriptor } from "./api-types";
7
7
  import type { FailedCredentialBinding } from "./credential-broker";
8
8
  import { buildEgressSecrets } from "./egress-secrets";
9
- import { getScriptExecutor } from "./executors/registry";
9
+ import { isExecutorAllowedByDeployment, resolveScriptExecutor } from "./executors/registry";
10
10
  import {
11
11
  DEFAULT_SCRIPT_RESOURCES,
12
12
  type EgressSecretEntry,
13
13
  type ExecutorOutput,
14
14
  type ScriptAgentFsDescriptor,
15
+ type ScriptExecutorName,
15
16
  type ScriptFsMode,
16
17
  type ScriptResourcePolicy,
17
18
  type SwarmConfigPayload,
18
19
  } from "./executors/types";
20
+ import { HOST_VALIDATED_ARGS } from "./host-validated-args";
19
21
  import { validateScriptImports } from "./import-allowlist";
20
22
 
21
23
  /**
@@ -38,7 +40,19 @@ const CAPACITY_EXCEEDED_RETRY_DELAYS_MS = [200, 750];
38
40
  export type RunScriptInput = {
39
41
  source: string;
40
42
  args?: unknown;
43
+ /**
44
+ * Unforgeable host-only capability symbol set only when the host has already
45
+ * validated `args` against the script's `argsJsonSchema` (e.g. `POST /api/x/script/{endpointId}`).
46
+ * Keyed by a `Symbol` so request-shaped JSON inputs forwarded into `RunScriptInput`
47
+ * can never opt out of `argsSchema` validation.
48
+ */
49
+ [HOST_VALIDATED_ARGS]?: boolean;
41
50
  fsMode?: ScriptFsMode;
51
+ /**
52
+ * Optional per-script executor override ("native" | "quickjs"). When null or
53
+ * undefined, `getScriptExecutor` falls back to `SCRIPT_EXECUTOR ?? "native"`.
54
+ */
55
+ executor?: ScriptExecutorName | null;
42
56
  agentId: string;
43
57
  /**
44
58
  * Bearer for the run's `ctx.swarm` SDK calls. Api-side callers pass a
@@ -118,6 +132,18 @@ export async function runScript(input: RunScriptInput): Promise<RunScriptOutput>
118
132
  };
119
133
  }
120
134
 
135
+ if (!isExecutorAllowedByDeployment(input.executor)) {
136
+ return {
137
+ result: undefined,
138
+ stdout: "",
139
+ stderr: 'Executor "native" is not permitted when deployment SCRIPT_EXECUTOR="quickjs"',
140
+ truncated: { stdout: false, stderr: false },
141
+ durationMs: 0,
142
+ exitCode: 1,
143
+ error: "executor_error",
144
+ };
145
+ }
146
+
121
147
  // The global scrubber skips values under its length floor, so this run's own
122
148
  // secret config is redacted here at any length before the global pass. Built
123
149
  // before the import check: its diagnostic quotes source, which can hold a value.
@@ -150,6 +176,7 @@ export async function runScript(input: RunScriptInput): Promise<RunScriptOutput>
150
176
  const executorInput = {
151
177
  source: input.source,
152
178
  args: input.args ?? null,
179
+ ...(input[HOST_VALIDATED_ARGS] === true ? { hostValidatedArgs: true } : {}),
153
180
  configPayload,
154
181
  resources,
155
182
  fsMode: input.fsMode ?? "none",
@@ -157,11 +184,11 @@ export async function runScript(input: RunScriptInput): Promise<RunScriptOutput>
157
184
  signal: input.signal,
158
185
  };
159
186
 
160
- let output = await getScriptExecutor().run(executorInput);
187
+ let output = await resolveScriptExecutor(input.executor).run(executorInput);
161
188
  for (const delayMs of CAPACITY_EXCEEDED_RETRY_DELAYS_MS) {
162
189
  if (output.error !== "capacity_exceeded" || input.signal?.aborted) break;
163
190
  await Bun.sleep(delayMs);
164
- output = await getScriptExecutor().run(executorInput);
191
+ output = await resolveScriptExecutor(input.executor).run(executorInput);
165
192
  }
166
193
 
167
194
  return {
@@ -8,6 +8,7 @@ type BridgeRequest = {
8
8
  method: string;
9
9
  path: string;
10
10
  body?: unknown;
11
+ headers?: Record<string, string>;
11
12
  };
12
13
 
13
14
  function headers(config: SwarmConfig): Record<string, string> {
@@ -319,7 +320,13 @@ function bridgeRequestFor(name: string, args: unknown): BridgeRequest | null {
319
320
  case "workflow_getRun": {
320
321
  const id = typeof body.id === "string" ? body.id : undefined;
321
322
  if (!id) throw new Error("workflow_getRun requires string `id`");
322
- return { method: "GET", path: `/api/workflow-runs/${encodeURIComponent(id)}` };
323
+ if (body.fields !== undefined && body.fields !== "full" && body.fields !== "slim") {
324
+ throw new Error('workflow_getRun `fields` must be "full" or "slim"');
325
+ }
326
+ return {
327
+ method: "GET",
328
+ path: appendQuery(`/api/workflow-runs/${encodeURIComponent(id)}`, { fields: body.fields }),
329
+ };
323
330
  }
324
331
  case "workflow_delete": {
325
332
  const id = typeof body.id === "string" ? body.id : undefined;
@@ -356,10 +363,16 @@ function bridgeRequestFor(name: string, args: unknown): BridgeRequest | null {
356
363
  case "workflow_trigger": {
357
364
  const id = typeof body.id === "string" ? body.id : undefined;
358
365
  if (!id) throw new Error("workflow_trigger requires string `id`");
366
+ const path = `/api/workflows/${encodeURIComponent(id)}/trigger`;
359
367
  return {
360
368
  method: "POST",
361
- path: `/api/workflows/${encodeURIComponent(id)}/trigger`,
369
+ path: body.wait === false ? `${path}?wait=false` : path,
362
370
  body: body.triggerData ?? {},
371
+ // The route's own Idempotency-Key: recorded in the run's transaction,
372
+ // so a retry after a kill replays the run instead of starting another.
373
+ ...(typeof body.idempotencyKey === "string"
374
+ ? { headers: { "Idempotency-Key": body.idempotencyKey } }
375
+ : {}),
363
376
  };
364
377
  }
365
378
  case "workflow_retryRun": {
@@ -485,7 +498,7 @@ async function callBridgeApi(
485
498
 
486
499
  const res = await fetch(`${baseUrl}${request.path}`, {
487
500
  method: request.method,
488
- headers: headers(config),
501
+ headers: { ...headers(config), ...request.headers },
489
502
  body: request.body === undefined ? undefined : JSON.stringify(request.body),
490
503
  });
491
504
  const data = await readScriptSdkJsonResponse(res, `ctx.swarm.${name}`);
@@ -26,6 +26,7 @@ declare module "swarm-sdk" {
26
26
  | { [key: string]: JsonValue };
27
27
  export type ScriptScope = "agent" | "global";
28
28
  export type ScriptFsMode = "none" | "workspace-rw";
29
+ export type ScriptExecutorName = "native" | "quickjs";
29
30
  export type ScriptApiRawOptions = { raw: true };
30
31
  export type ScriptApiDefaultOptions = { raw?: false };
31
32
  export type RoutingReason =
@@ -304,6 +305,7 @@ declare module "swarm-sdk" {
304
305
  intent?: string;
305
306
  scope?: ScriptScope;
306
307
  fsMode?: ScriptFsMode;
308
+ executor?: ScriptExecutorName | null;
307
309
  idempotencyKey?: string;
308
310
  }): Promise<unknown>;
309
311
  // --- swarm / agent ---
@@ -378,7 +380,7 @@ declare module "swarm-sdk" {
378
380
  limit?: number;
379
381
  offset?: number;
380
382
  }): Promise<unknown>;
381
- workflow_getRun(args: { id: string }): Promise<unknown>;
383
+ workflow_getRun(args: { id: string; fields?: "full" | "slim" }): Promise<unknown>;
382
384
  // --- prompt templates ---
383
385
  prompt_list(args?: {
384
386
  eventType?: string;
@@ -535,7 +537,12 @@ declare module "swarm-sdk" {
535
537
  workflow_patch(args: Record<string, unknown>): Promise<unknown>;
536
538
  workflow_patchNode(args: Record<string, unknown>): Promise<unknown>;
537
539
  workflow_delete(args: { id: string }): Promise<unknown>;
538
- workflow_trigger(args: { id: string; triggerData?: Record<string, unknown> }): Promise<unknown>;
540
+ workflow_trigger(args: {
541
+ id: string;
542
+ triggerData?: Record<string, unknown>;
543
+ wait?: boolean;
544
+ idempotencyKey?: string;
545
+ }): Promise<unknown>;
539
546
  workflow_retryRun(args: { id: string }): Promise<unknown>;
540
547
  workflow_cancelRun(args: { id: string }): Promise<unknown>;
541
548
 
@@ -552,6 +559,7 @@ declare module "swarm-sdk" {
552
559
  intent?: string;
553
560
  scope?: ScriptScope;
554
561
  fsMode?: ScriptFsMode;
562
+ executor?: ScriptExecutorName | null;
555
563
  }): Promise<unknown>;
556
564
  script_delete(args: { name: string; scope?: ScriptScope }): Promise<unknown>;
557
565
  script_queryTypes(args: { name: string; scope?: ScriptScope }): Promise<unknown>;
@@ -9,16 +9,16 @@ declare module "swarm-extension" {
9
9
  | "schedule"
10
10
  | "slack"
11
11
  | "agentmail"
12
- | "mcp"
13
- | "api"
14
- | "ui"
15
12
  | "github"
16
13
  | "gitlab"
17
14
  | "azure-devops"
18
15
  | "bitbucket"
19
- | "system"
20
16
  | "linear"
21
17
  | "jira"
18
+ | "mcp"
19
+ | "api"
20
+ | "ui"
21
+ | "system"
22
22
  | "comb"
23
23
  | "chat"
24
24
  | undefined;
@@ -127,16 +127,16 @@ declare module "swarm-extension" {
127
127
  | "schedule"
128
128
  | "slack"
129
129
  | "agentmail"
130
- | "mcp"
131
- | "api"
132
- | "ui"
133
130
  | "github"
134
131
  | "gitlab"
135
132
  | "azure-devops"
136
133
  | "bitbucket"
137
- | "system"
138
134
  | "linear"
139
135
  | "jira"
136
+ | "mcp"
137
+ | "api"
138
+ | "ui"
139
+ | "system"
140
140
  | "comb"
141
141
  | "chat";
142
142
  tags: string[];
@@ -401,6 +401,15 @@ declare module "swarm-extension" {
401
401
  description: string;
402
402
  origin: TaskCreateOrigin;
403
403
  requestInfo?: RequestInfo;
404
+ /** Slack ingress metadata; not persisted or writable through modify. */
405
+ slack?: {
406
+ trigger: "mention" | "thread-buffer";
407
+ messages: {
408
+ ts: string;
409
+ userId: string;
410
+ text: string;
411
+ }[];
412
+ };
404
413
  }
405
414
  /** A pre.task.create hook blocked a task before it was created. */
406
415
  export interface TaskBlockedEvent {
@@ -8,6 +8,7 @@ declare module "swarm-sdk" {
8
8
  | { [key: string]: JsonValue };
9
9
  export type ScriptScope = "agent" | "global";
10
10
  export type ScriptFsMode = "none" | "workspace-rw";
11
+ export type ScriptExecutorName = "native" | "quickjs";
11
12
  export type ScriptApiRawOptions = { raw: true };
12
13
  export type ScriptApiDefaultOptions = { raw?: false };
13
14
  export type RoutingReason =
@@ -286,6 +287,7 @@ declare module "swarm-sdk" {
286
287
  intent?: string;
287
288
  scope?: ScriptScope;
288
289
  fsMode?: ScriptFsMode;
290
+ executor?: ScriptExecutorName | null;
289
291
  idempotencyKey?: string;
290
292
  }): Promise<unknown>;
291
293
  // --- swarm / agent ---
@@ -360,7 +362,7 @@ declare module "swarm-sdk" {
360
362
  limit?: number;
361
363
  offset?: number;
362
364
  }): Promise<unknown>;
363
- workflow_getRun(args: { id: string }): Promise<unknown>;
365
+ workflow_getRun(args: { id: string; fields?: "full" | "slim" }): Promise<unknown>;
364
366
  // --- prompt templates ---
365
367
  prompt_list(args?: {
366
368
  eventType?: string;
@@ -517,7 +519,12 @@ declare module "swarm-sdk" {
517
519
  workflow_patch(args: Record<string, unknown>): Promise<unknown>;
518
520
  workflow_patchNode(args: Record<string, unknown>): Promise<unknown>;
519
521
  workflow_delete(args: { id: string }): Promise<unknown>;
520
- workflow_trigger(args: { id: string; triggerData?: Record<string, unknown> }): Promise<unknown>;
522
+ workflow_trigger(args: {
523
+ id: string;
524
+ triggerData?: Record<string, unknown>;
525
+ wait?: boolean;
526
+ idempotencyKey?: string;
527
+ }): Promise<unknown>;
521
528
  workflow_retryRun(args: { id: string }): Promise<unknown>;
522
529
  workflow_cancelRun(args: { id: string }): Promise<unknown>;
523
530
 
@@ -534,6 +541,7 @@ declare module "swarm-sdk" {
534
541
  intent?: string;
535
542
  scope?: ScriptScope;
536
543
  fsMode?: ScriptFsMode;
544
+ executor?: ScriptExecutorName | null;
537
545
  }): Promise<unknown>;
538
546
  script_delete(args: { name: string; scope?: ScriptScope }): Promise<unknown>;
539
547
  script_queryTypes(args: { name: string; scope?: ScriptScope }): Promise<unknown>;
@@ -213,6 +213,10 @@ export function createAssistant(): Assistant {
213
213
  buildEffectiveText(messageText, inbound.files, inbound.failed, inbound.notes),
214
214
  cachedBotUserId ?? undefined,
215
215
  );
216
+ const slack = {
217
+ trigger: "mention" as const,
218
+ messages: [{ ts: message.ts, userId, text: messageText }],
219
+ };
216
220
 
217
221
  if (workingAgent && workingAgent.status !== "offline") {
218
222
  // Otherwise, create a follow-up task for the working agent
@@ -236,6 +240,7 @@ export function createAssistant(): Assistant {
236
240
  contextKey: slackContextKey({ channelId, threadTs }),
237
241
  },
238
242
  inbound,
243
+ slack,
239
244
  );
240
245
  await ackSlackMessage(
241
246
  client,
@@ -285,6 +290,7 @@ export function createAssistant(): Assistant {
285
290
  contextKey: slackContextKey({ channelId, threadTs }),
286
291
  },
287
292
  inbound,
293
+ slack,
288
294
  );
289
295
  await ackSlackMessage(
290
296
  client,
@@ -318,6 +324,7 @@ export function createAssistant(): Assistant {
318
324
  contextKey: slackContextKey({ channelId, threadTs }),
319
325
  },
320
326
  inbound,
327
+ slack,
321
328
  );
322
329
  await ackSlackMessage(client, channelId, message.ts, reactionName("accepted"), "accepted");
323
330
  await notifySlackFileFailures(client, channelId, threadTs, unattached);
@@ -0,0 +1,74 @@
1
+ import type { WebClient } from "@slack/web-api";
2
+ import { rememberSessionLabel } from "../be/db/session-labels";
3
+ import { getSlackApp } from "./app";
4
+ import { callSlackWithRetry, slackErrorCode } from "./render-v2";
5
+
6
+ const LABEL_TTL_MS = 24 * 60 * 60 * 1000;
7
+ const FAILURE_TTL_MS = 10 * 60 * 1000;
8
+ const FAILURE_CACHE_MAX = 500;
9
+ const LOOKUP_QUEUE_MAX = 50;
10
+ const inFlight = new Set<string>();
11
+ const failedUntil = new Map<string, number>();
12
+ let pending = Promise.resolve();
13
+
14
+ async function channelLabel(client: WebClient, id: string): Promise<string> {
15
+ const result = await callSlackWithRetry(client, "conversations.info", {
16
+ channel: id.toUpperCase(),
17
+ });
18
+ const channel = result.channel as
19
+ | { name?: string; is_im?: boolean; is_mpim?: boolean; user?: string }
20
+ | undefined;
21
+ if (result.ok === false || !channel) throw new Error("Channel lookup failed");
22
+ if (channel.is_mpim) return "Group DM";
23
+ if (channel.is_im && channel.user) {
24
+ const result = await callSlackWithRetry(client, "users.info", { user: channel.user });
25
+ const user = result.user as
26
+ | { profile?: { display_name?: string; real_name?: string }; real_name?: string }
27
+ | undefined;
28
+ const name = user?.profile?.display_name || user?.real_name || user?.profile?.real_name;
29
+ if (result.ok !== false && name) return `DM · ${name}`;
30
+ } else if (channel.name) {
31
+ return `#${channel.name}`;
32
+ }
33
+ throw new Error("Channel label unavailable");
34
+ }
35
+
36
+ const CHANNEL_GROUP_KEY = /^shared\/slack\/([^/]+)\/$/;
37
+
38
+ /**
39
+ * Queue a Slack lookup for each channel group key (`shared/slack/<channel>/`)
40
+ * without delaying the tree response. Other keys are ignored.
41
+ */
42
+ export function warmSlackChannelLabels(groupKeys: string[]): void {
43
+ const client = getSlackApp()?.client;
44
+ if (!client) return;
45
+ for (const groupKey of groupKeys) {
46
+ const id = CHANNEL_GROUP_KEY.exec(groupKey)?.[1];
47
+ if (!id) continue;
48
+ if (inFlight.has(id) || (failedUntil.get(id) ?? 0) > Date.now()) continue;
49
+ failedUntil.delete(id);
50
+ if (inFlight.size >= LOOKUP_QUEUE_MAX) continue;
51
+ // Reserve failure capacity for queued work. Live failures must retain their TTL.
52
+ if (failedUntil.size + inFlight.size >= FAILURE_CACHE_MAX) {
53
+ const now = Date.now();
54
+ for (const [failedId, until] of failedUntil) {
55
+ if (until <= now) failedUntil.delete(failedId);
56
+ }
57
+ if (failedUntil.size + inFlight.size >= FAILURE_CACHE_MAX) continue;
58
+ }
59
+ inFlight.add(id);
60
+ pending = pending.then(async () => {
61
+ try {
62
+ const label = await channelLabel(client, id);
63
+ await rememberSessionLabel(groupKey, label, LABEL_TTL_MS);
64
+ } catch (error) {
65
+ failedUntil.set(id, Date.now() + FAILURE_TTL_MS);
66
+ console.warn(
67
+ `[Slack] Could not cache a session channel label: ${slackErrorCode(error) ?? "unknown error"}`,
68
+ );
69
+ } finally {
70
+ inFlight.delete(id);
71
+ }
72
+ });
73
+ }
74
+ }
@@ -679,6 +679,11 @@ export function registerMessageHandler(app: App): void {
679
679
  routingThreadContext,
680
680
  );
681
681
 
682
+ const slack = {
683
+ trigger: "mention" as const,
684
+ messages: [{ ts: msg.ts, userId: msg.user, text: msg.text ?? "" }],
685
+ };
686
+
682
687
  if (matches.length === 0) {
683
688
  if (!botMentioned && !isImplicitMention) return ignoreSlackInbound("not_addressed");
684
689
 
@@ -742,6 +747,7 @@ export function registerMessageHandler(app: App): void {
742
747
  contextKey: slackContextKey({ channelId: msg.channel, threadTs }),
743
748
  },
744
749
  inbound,
750
+ slack,
745
751
  );
746
752
  await ackSlackMessage(client, msg.channel, msg.ts, reactionName("accepted"), "accepted");
747
753
  await notifySlackFileFailures(client, msg.channel, threadTs, unattached);
@@ -864,6 +870,7 @@ export function registerMessageHandler(app: App): void {
864
870
  contextKey: slackContextKey({ channelId: msg.channel, threadTs }),
865
871
  },
866
872
  inbound,
873
+ slack,
867
874
  );
868
875
  const { task } = created;
869
876
  unattached.push(...created.unattached);
@@ -888,6 +895,7 @@ export function registerMessageHandler(app: App): void {
888
895
  contextKey: slackContextKey({ channelId: msg.channel, threadTs }),
889
896
  },
890
897
  inbound,
898
+ slack,
891
899
  );
892
900
  const { task } = created;
893
901
  unattached.push(...created.unattached);
@@ -22,6 +22,7 @@ import { join } from "node:path";
22
22
  import type { WebClient } from "@slack/web-api";
23
23
  import { getTaskAttachments, getTaskById, promoteDraftTask, refreshDraftTaskLease } from "../be/db";
24
24
  import { MAX_TASK_ATTACHMENT_BYTES, recordTaskAttachmentUpload } from "../be/task-attachment-store";
25
+ import type { TaskCreateEvent } from "../extensions/contract";
25
26
  import { providerPath } from "../fs/provider";
26
27
  import { getFileStorageProvider } from "../fs/registry";
27
28
  import { resolveTemplate } from "../prompts/resolver";
@@ -320,17 +321,19 @@ export async function createSlackTaskWithFiles(
320
321
  description: string,
321
322
  options: CreateTaskOptions,
322
323
  inbound: InboundSlackFiles,
324
+ slack?: TaskCreateEvent["slack"],
323
325
  ): Promise<{ task: AgentTask; unattached: SlackFileFailure[] }> {
324
326
  noteSlackInboundSideEffect("task_created");
327
+ const context = { origin: "slack" as const, slack };
325
328
  if (inbound.fetched.length === 0) {
326
- const task = await createTaskWithSiblingAwareness(description, options, { origin: "slack" });
329
+ const task = await createTaskWithSiblingAwareness(description, options, context);
327
330
  return { task, unattached: inbound.failed };
328
331
  }
329
332
 
330
333
  const task = await createTaskWithSiblingAwareness(
331
334
  description,
332
335
  { ...options, status: "draft" },
333
- { origin: "slack" },
336
+ context,
334
337
  );
335
338
  const unattached = [...inbound.failed];
336
339
  // Uploads run one after another and can outlast the abandoned-draft sweep's
@@ -313,6 +313,10 @@ async function slackFlush(
313
313
  contextKey: slackContextKey({ channelId, threadTs }),
314
314
  },
315
315
  inbound,
316
+ {
317
+ trigger: "thread-buffer",
318
+ messages: items.map(({ ts, userId, text }) => ({ ts, userId, text })),
319
+ },
316
320
  );
317
321
  } catch (error) {
318
322
  if (!(error instanceof TaskCreationBlockedError)) throw error;
@@ -18,7 +18,7 @@ import {
18
18
  getInProgressTasksByContextKey,
19
19
  } from "../be/db";
20
20
  import { applyPreTaskCreate } from "../extensions/apply-task-create";
21
- import type { TaskCreateOrigin } from "../extensions/contract";
21
+ import type { TaskCreateEvent, TaskCreateOrigin } from "../extensions/contract";
22
22
  import type { AgentTask } from "../types";
23
23
  import { TaskCreationBlockedError } from "./errors";
24
24
  import {
@@ -151,7 +151,11 @@ export type PreparedTaskCreate = { description: string; options: CreateTaskOptio
151
151
  export async function prepareTaskWithSiblingAwareness(
152
152
  description: string,
153
153
  options: CreateTaskOptions,
154
- args: { origin?: TaskCreateOrigin; allowCustomModel?: boolean } = {},
154
+ args: {
155
+ origin?: TaskCreateOrigin;
156
+ allowCustomModel?: boolean;
157
+ slack?: TaskCreateEvent["slack"];
158
+ } = {},
155
159
  ): Promise<PreparedTaskCreate> {
156
160
  const preCreate = await applyPreTaskCreate({
157
161
  description,
@@ -159,6 +163,7 @@ export async function prepareTaskWithSiblingAwareness(
159
163
  // Preserve the plan's REST default for callers without an explicit ingress origin.
160
164
  origin: args.origin ?? "rest",
161
165
  allowCustomModel: args.allowCustomModel,
166
+ slack: args.slack,
162
167
  });
163
168
  if (preCreate.kind === "blocked") {
164
169
  throw new TaskCreationBlockedError(preCreate.reason, preCreate.extension);
@@ -169,7 +174,11 @@ export async function prepareTaskWithSiblingAwareness(
169
174
  export async function createTaskWithSiblingAwareness(
170
175
  description: string,
171
176
  options: CreateTaskOptions,
172
- args: { origin?: TaskCreateOrigin; allowCustomModel?: boolean } = {},
177
+ args: {
178
+ origin?: TaskCreateOrigin;
179
+ allowCustomModel?: boolean;
180
+ slack?: TaskCreateEvent["slack"];
181
+ } = {},
173
182
  ): Promise<AgentTask> {
174
183
  const { description: d, options: o } = await prepareTaskWithSiblingAwareness(
175
184
  description,
@@ -12,8 +12,8 @@ import catalog from "./telemetry-contract/catalog.json";
12
12
  import {
13
13
  COMPANY_ACQUISITION_SOURCES,
14
14
  COMPANY_TEAM_SIZES,
15
- COMPANY_USE_CASES,
16
- USER_FUNCTIONS,
15
+ reportedUseCase,
16
+ reportedUserFunction,
17
17
  } from "./utils/company-profile";
18
18
 
19
19
  /** `org_` + 16 lowercase hex (self-host, minted here) or 27 alphanumerics (cloud). Same pattern the proxy enforces. */
@@ -115,10 +115,9 @@ export function buildContext(input: ContextInput): EventContext {
115
115
  const orgDomain = validOrgDomain(input.orgDomain);
116
116
  const orgWebsite = validOrgDomain(input.orgWebsite);
117
117
  const teamSize = COMPANY_TEAM_SIZES.find((value) => value === input.teamSize);
118
- const useCase = COMPANY_USE_CASES.find((value) => value === input.useCase);
119
- const userFunction = userId
120
- ? USER_FUNCTIONS.find((value) => value === input.userFunction)
121
- : undefined;
118
+ // A value the catalog does not list yet is left out, as if unset.
119
+ const useCase = reportedUseCase(input.useCase);
120
+ const userFunction = userId ? reportedUserFunction(input.userFunction) : undefined;
122
121
  return {
123
122
  org_id: input.orgId,
124
123
  org_name: orgName ? orgName : null,