@desplega.ai/agent-swarm 2.4.4 → 2.5.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 (382) hide show
  1. package/README.md +4 -2
  2. package/dist/{acp-adapter-thq04adh.js → acp-adapter-11gp67nr.js} +8 -6
  3. package/dist/{actions-j6ywh6x5.js → actions-8p2kze8j.js} +24 -22
  4. package/dist/{amp-adapter-k8b5xmj8.js → amp-adapter-z4qvhacj.js} +2 -2
  5. package/dist/{anthropic-messages-g6pqmqk4.js → anthropic-messages-txs190dd.js} +17 -20
  6. package/dist/{app-rb7vc02s.js → app-0qp0a2gk.js} +3 -3
  7. package/dist/{assistant-ftta1zjf.js → assistant-jedd3ktm.js} +27 -25
  8. package/dist/{azure-openai-responses-9dbmj346.js → azure-openai-responses-9kfwhmn7.js} +18 -13
  9. package/dist/{boot-reembed-xns7h5h9.js → boot-reembed-4xwq5tg0.js} +1 -1
  10. package/dist/{boot-reembed-srp6v6af.js → boot-reembed-n4wq5dd2.js} +1 -1
  11. package/dist/{boot-scrub-sweep-3qqrh2mz.js → boot-scrub-sweep-69h90xcq.js} +4 -4
  12. package/dist/{boot-seeds-9ks8hce7.js → boot-seeds-3vn0m9sg.js} +4 -4
  13. package/dist/{claude-adapter-k5ksyres.js → claude-adapter-221mvad5.js} +8 -6
  14. package/dist/{claude-managed-adapter-h2y889ge.js → claude-managed-adapter-gt6pa35d.js} +2 -2
  15. package/dist/{claude-managed-setup-n5a5eqks.js → claude-managed-setup-c3hp36ph.js} +4 -4
  16. package/dist/{claude-sdk-session-2j3j4kwn.js → claude-sdk-session-8thjsk5e.js} +9729 -9628
  17. package/dist/{cli-b9fcxxev.js → cli-007cnhm1.js} +50 -4
  18. package/dist/{cli-j4rqdf2n.js → cli-03vpq45j.js} +2 -2
  19. package/dist/{cli-amyhm3gj.js → cli-0y6f0ckq.js} +1 -1
  20. package/dist/{cli-4dg55wsk.js → cli-0z3vjrpm.js} +1 -1
  21. package/dist/{cli-pccfmtb5.js → cli-179xcyky.js} +11 -11
  22. package/dist/{cli-3r8samv3.js → cli-1f0gp9vz.js} +3 -2
  23. package/dist/{cli-hj3debgw.js → cli-1pr672hz.js} +3 -3
  24. package/dist/{cli-0bqteszy.js → cli-2nf3b08t.js} +328 -25
  25. package/dist/{cli-t7cyef5g.js → cli-36j4jnk3.js} +4 -4
  26. package/dist/{cli-er83cjbn.js → cli-3qv8naq8.js} +527 -231
  27. package/dist/cli-3zwye9sn.js +1193 -0
  28. package/dist/{cli-60cgh1rv.js → cli-459fk97x.js} +7 -7
  29. package/dist/{cli-xj0re5tm.js → cli-4w24f7z3.js} +1 -1
  30. package/dist/{cli-v3yr2gbg.js → cli-5kzk3472.js} +2198 -968
  31. package/dist/{cli-h7gc0b4r.js → cli-5m84zd2k.js} +55 -18
  32. package/dist/{cli-w6nadkh4.js → cli-5rvg1ddk.js} +5 -5
  33. package/dist/{cli-dvkmp9bt.js → cli-6ctwrf1z.js} +20 -10
  34. package/dist/{cli-9qtjfd60.js → cli-6rg2jn2y.js} +3 -2
  35. package/dist/{cli-2v045xyf.js → cli-7bwtc6yx.js} +3 -3
  36. package/dist/{cli-3cgngyd8.js → cli-7g40g8hq.js} +35 -17
  37. package/dist/{cli-q6r7t299.js → cli-92mqenkc.js} +2379 -154
  38. package/dist/{cli-cynvc9fj.js → cli-9ce2xw98.js} +8 -8
  39. package/dist/{cli-gqvh8d8a.js → cli-bgdtv62r.js} +36 -22
  40. package/dist/{cli-wxbn3yfx.js → cli-cz5nmdxt.js} +2 -2
  41. package/dist/{cli-wt645v7q.js → cli-dabehxhp.js} +1 -1
  42. package/dist/{cli-vfha21c9.js → cli-e5bxnjps.js} +2 -2
  43. package/dist/{cli-p20r8w42.js → cli-fcc5qe3f.js} +763 -268
  44. package/dist/{cli-1ephgwnq.js → cli-fes51ya2.js} +3 -3
  45. package/dist/{cli-1d70crsy.js → cli-ftmq4nvk.js} +2 -2
  46. package/dist/{cli-qkhc5aqy.js → cli-fy1jyyym.js} +13 -13
  47. package/dist/{cli-s95dknxh.js → cli-g1cw7xtt.js} +1 -1
  48. package/dist/{cli-qje9k2nh.js → cli-g9ypd5en.js} +12 -2
  49. package/dist/{cli-74c1wpm0.js → cli-gpv60278.js} +1 -1
  50. package/dist/{cli-pemywhfq.js → cli-ha9rmwgq.js} +2 -1
  51. package/dist/{cli-bnz0r3kc.js → cli-j1eghqfm.js} +5 -5
  52. package/dist/{cli-2zqpkvtt.js → cli-jpxsrxcb.js} +1 -1
  53. package/dist/{cli-ejs3m1sx.js → cli-kxpfx2xr.js} +1 -1
  54. package/dist/{cli-37yb7307.js → cli-mbfj11fq.js} +1 -1
  55. package/dist/{cli-pnp2j5zg.js → cli-mmhj8vd7.js} +2 -2
  56. package/dist/{cli-xf234ws8.js → cli-n4c8e3s5.js} +2 -0
  57. package/dist/{cli-a0kcty51.js → cli-n6kmqngw.js} +2 -2
  58. package/dist/{cli-te6b5wvg.js → cli-nnvjb17s.js} +3 -3
  59. package/dist/{cli-1dmcq2eh.js → cli-nzfnt2gs.js} +1 -1
  60. package/dist/{cli-625tb7aq.js → cli-p859hcc8.js} +2 -2
  61. package/dist/{cli-smvcw9z5.js → cli-pasyvjmm.js} +1 -1
  62. package/dist/cli-pkaqk57w.js +128 -0
  63. package/dist/{cli-8nyhf7za.js → cli-qqzmjxkf.js} +129 -105
  64. package/dist/{cli-zrwpctp4.js → cli-r25qpaxt.js} +161 -63
  65. package/dist/{cli-nc35pxb7.js → cli-rh5smt8t.js} +19 -4
  66. package/dist/{cli-qwkcwjdn.js → cli-rycdaj0a.js} +1 -1
  67. package/dist/{cli-esxerzg7.js → cli-s4sntwt4.js} +2 -2
  68. package/dist/{cli-b8k7epj1.js → cli-segs7hx9.js} +8 -8
  69. package/dist/{cli-36ckw0wx.js → cli-stxdrqs2.js} +2 -2
  70. package/dist/{cli-vdm20wej.js → cli-sy8f9705.js} +4 -4
  71. package/dist/{cli-1m17dc9d.js → cli-ta8bd7th.js} +2 -2
  72. package/dist/{cli-2y91wnbh.js → cli-we0ra057.js} +222 -10
  73. package/dist/{cli-55qmbp6z.js → cli-x21dqn18.js} +5 -5
  74. package/dist/{cli-dqb4n629.js → cli-x6vf4sad.js} +11 -11
  75. package/dist/{cli-d2mb5s8a.js → cli-x77gen2p.js} +680 -440
  76. package/dist/{cli-vq97g1jq.js → cli-xawhpgc9.js} +95 -195
  77. package/dist/{cli-82d3nkc3.js → cli-xd4eqtr2.js} +2 -2
  78. package/dist/{cli-9fhkkpj5.js → cli-xt768r5m.js} +2 -2
  79. package/dist/{cli-zrk6jxv8.js → cli-y4x8ww6s.js} +2 -2
  80. package/dist/{cli-xc27fwzg.js → cli-y9jvxfhk.js} +3 -3
  81. package/dist/{cli-44159k28.js → cli-z63a269p.js} +2 -2
  82. package/dist/{cli-sm8rses9.js → cli-zfdp01hn.js} +8 -5
  83. package/dist/cli.js +31 -29
  84. package/dist/{cloudflare-workers-ai-system-one-ch2wxfcp.js → cloudflare-workers-ai-system-one-43v9qkjk.js} +7 -2
  85. package/dist/{codex-adapter-968xwfy7.js → codex-adapter-tcg1q3jk.js} +6 -4
  86. package/dist/{codex-login-m819c41r.js → codex-login-rn7c5gxt.js} +1 -1
  87. package/dist/{codex-session-runner-ht4z99fa.js → codex-session-runner-xfx4wd9x.js} +7 -5
  88. package/dist/{commands-pcymaeg9.js → commands-ew83nj70.js} +2 -2
  89. package/dist/{controller-fpjyk46r.js → controller-gnq1k1mf.js} +4 -4
  90. package/dist/{controllers-connect-mwzx1fh6.js → controllers-connect-tw1dan9r.js} +26 -24
  91. package/dist/{cursor-adapter-cp4s2e2f.js → cursor-adapter-yq5xgfcg.js} +1 -1
  92. package/dist/{db-xsjg9jqq.js → db-7gbc53fd.js} +14 -2
  93. package/dist/{db-sgd12bt9.js → db-t86xrnes.js} +3 -3
  94. package/dist/{devin-adapter-zgqadvnm.js → devin-adapter-4ycy4vrf.js} +1 -1
  95. package/dist/{dispatcher-x0jfw2c9.js → dispatcher-64bdyhps.js} +23 -21
  96. package/dist/{docker-fkgtr75t.js → docker-nfy2gc8t.js} +2 -2
  97. package/dist/{dsh-adapter-jrq7ma7h.js → dsh-adapter-tvhc4k2d.js} +1 -1
  98. package/dist/{e2b-16d6ch39.js → e2b-9gkdhm3c.js} +2 -2
  99. package/dist/{e2b-zn0b4w5w.js → e2b-cjtf8k1z.js} +2 -2
  100. package/dist/{easter-egg-3d-5cgynaja.js → easter-egg-3d-e4qv8dxj.js} +1 -1
  101. package/dist/{event-bus-9vpk8gmq.js → event-bus-3z78ep5n.js} +1 -1
  102. package/dist/{execute-t4e6m3rw.js → execute-0xpz8950.js} +85 -23
  103. package/dist/{external-channel-8njj4h3b.js → external-channel-e9nw24zv.js} +2 -2
  104. package/dist/{google-generative-ai-bmq8gkf3.js → google-generative-ai-mk9vcpe1.js} +15 -15
  105. package/dist/{google-vertex-b1rvp52s.js → google-vertex-q580kz8k.js} +20 -20
  106. package/dist/{grok-adapter-kkr97v2f.js → grok-adapter-v82srhrx.js} +9 -7
  107. package/dist/handlers-c9ajzhbn.js +41 -0
  108. package/dist/{heartbeat-wd03zhn0.js → heartbeat-2vqs1319.js} +26 -24
  109. package/dist/{hook-xzj2ts2p.js → hook-01nk13wx.js} +7 -5
  110. package/dist/{hook-f0vghj42.js → hook-jzpheegg.js} +9 -7
  111. package/dist/{http-4hwxv62p.js → http-fc1k6ec8.js} +859 -252
  112. package/dist/index-07ef7nkm.js +42 -0
  113. package/dist/{index-g6tbb6ve.js → index-31ceyf42.js} +1 -1
  114. package/dist/index-7yts8n9s.js +43 -0
  115. package/dist/{index-c4ykd0f1.js → index-dr94zbnh.js} +13348 -11611
  116. package/dist/index-q3t40ryv.js +46 -0
  117. package/dist/{index-ww2vznkp.js → index-sepja052.js} +1 -1
  118. package/dist/{index-97q4xn03.js → index-zdycv3ja.js} +135 -79
  119. package/dist/{index-g216q6jr.js → index-zsyx51ap.js} +1 -4
  120. package/dist/{keepalive-hrh7xnr9.js → keepalive-5nh2rhmr.js} +4 -4
  121. package/dist/lead-aayq64jn.js +79 -0
  122. package/dist/{llama-cpp-classify-vv43gswy.js → llama-cpp-classify-ee7vmd7q.js} +4 -1
  123. package/dist/{maintenance-e8wnwnqx.js → maintenance-hjhzdt3k.js} +1 -1
  124. package/dist/{mistral-conversations-1medfwv7.js → mistral-conversations-sd0xf1tn.js} +13 -13
  125. package/dist/{node-3v3f08f7.js → node-jzjj7r9x.js} +10 -9
  126. package/dist/{oauth-refresh-sweep-zm0yj4sj.js → oauth-refresh-sweep-y3n1knmd.js} +3 -3
  127. package/dist/{onboard-dtwcq603.js → onboard-1zq3m7db.js} +2 -2
  128. package/dist/{openai-codex-responses-de9pvs4d.js → openai-codex-responses-79s8xnbh.js} +29 -23
  129. package/dist/{openai-completions-mtqge3nf.js → openai-completions-tdbyts0x.js} +21 -20
  130. package/dist/{openai-decisions-ygk4kkxd.js → openai-decisions-bqhnjyfz.js} +2 -1
  131. package/dist/{openai-responses-dkyng71g.js → openai-responses-dm3t5vqp.js} +26 -17
  132. package/dist/{opencode-adapter-x4tqzhkh.js → opencode-adapter-e56086m1.js} +7 -5
  133. package/dist/{openrouter-images-wvv7camj.js → openrouter-images-ssttr1y4.js} +6 -3
  134. package/dist/{otel-impl-w3fbhpn0.js → otel-impl-h9yyza47.js} +2 -2
  135. package/dist/{pi-messages-nhh1pkz2.js → pi-messages-3nf8bq1a.js} +6 -4
  136. package/dist/{pi-mono-adapter-mcfkn9vf.js → pi-mono-adapter-tzps8f38.js} +9 -37
  137. package/dist/{pricing-refresh-j38dehfs.js → pricing-refresh-nevhat8h.js} +3 -3
  138. package/dist/{rbac-roles-ae8yttq4.js → rbac-roles-4y6ngvzq.js} +1 -1
  139. package/dist/{rbac-roles-ejxx9b35.js → rbac-roles-exbg8xb6.js} +2 -2
  140. package/dist/{registry-rxw10xzg.js → registry-ajhexe44.js} +1 -1
  141. package/dist/{render-v2-n16n0phe.js → render-v2-cfsh13z3.js} +3 -3
  142. package/dist/{resume-z0fgjc38.js → resume-5s2t0dbs.js} +10 -8
  143. package/dist/{runtime-xe3aqy4x.js → runtime-bt5pkpm1.js} +1 -1
  144. package/dist/{session-access-cv3mq6mc.js → session-access-dnbyp3cn.js} +6 -4
  145. package/dist/{setup-mafhj3yt.js → setup-1wj1y77j.js} +1 -1
  146. package/dist/{typesafe-system-one-2hgpayzy.js → typesafe-system-one-bre38hvs.js} +4 -2
  147. package/dist/{virtual-modules-4r3zkc20.js → virtual-modules-mnfbh5mr.js} +8 -2
  148. package/dist/{work-objects-8b86mgjp.js → work-objects-7mg6216g.js} +26 -24
  149. package/dist/worker-nwb789mg.js +79 -0
  150. package/dist/{x-y45mthwn.js → x-pbpezr0s.js} +2 -2
  151. package/openapi.json +793 -6
  152. package/package.json +11 -11
  153. package/src/apps/catalog.generated.json +206 -4
  154. package/src/apps/page-validator.ts +13 -1
  155. package/src/assets/key.ts +17 -0
  156. package/src/azure-devops/handlers.ts +1 -1
  157. package/src/be/chunking.ts +42 -9
  158. package/src/be/db/approvals.ts +18 -0
  159. package/src/be/db/session-moves.ts +164 -0
  160. package/src/be/db/session-tree.ts +384 -0
  161. package/src/be/db/tasks/draft-text.ts +15 -0
  162. package/src/be/db/tasks/read.ts +54 -5
  163. package/src/be/db/workflow-runs.ts +96 -0
  164. package/src/be/db.ts +85 -237
  165. package/src/be/memory/index-content.ts +36 -17
  166. package/src/be/memory/providers/openai-embedding.ts +115 -61
  167. package/src/be/memory/providers/sqlite-store.ts +69 -23
  168. package/src/be/memory/reranker.ts +31 -1
  169. package/src/be/memory/search-candidates.ts +47 -0
  170. package/src/be/memory/types.ts +14 -0
  171. package/src/be/onboarding.ts +3 -0
  172. package/src/be/scripts/typecheck.ts +2 -0
  173. package/src/be/seed-scripts/catalog/heartbeat-jev-gate.ts +106 -54
  174. package/src/be/seed-scripts/index.ts +2 -2
  175. package/src/be/skill-sync.ts +43 -2
  176. package/src/be/swarm-config-guard.ts +25 -1
  177. package/src/be/task-attachment-store.ts +30 -0
  178. package/src/bitbucket/api.ts +81 -0
  179. package/src/bitbucket/auth.ts +64 -0
  180. package/src/bitbucket/handlers.ts +434 -0
  181. package/src/bitbucket/index.ts +28 -0
  182. package/src/bitbucket/templates.ts +134 -0
  183. package/src/bitbucket/types.ts +65 -0
  184. package/src/commands/runner.ts +55 -3
  185. package/src/e2b/env.ts +1 -0
  186. package/src/extensions/catalog.generated.json +4 -4
  187. package/src/extensions/contract-types.generated.ts +1 -1
  188. package/src/extensions/contract.ts +9 -2
  189. package/src/extensions/post-bridge.ts +1 -0
  190. package/src/fs/provider.ts +13 -1
  191. package/src/github/handlers.ts +6 -6
  192. package/src/gitlab/handlers.ts +5 -5
  193. package/src/heartbeat/heartbeat.ts +31 -11
  194. package/src/http/assets.ts +14 -2
  195. package/src/http/core.ts +12 -0
  196. package/src/http/fs.ts +54 -34
  197. package/src/http/index.ts +4 -0
  198. package/src/http/mcp-body.ts +37 -0
  199. package/src/http/mcp-user.ts +3 -5
  200. package/src/http/mcp.ts +6 -7
  201. package/src/http/memory.ts +26 -15
  202. package/src/http/onboarding.ts +1 -0
  203. package/src/http/page-proxy.ts +61 -5
  204. package/src/http/pages-public.ts +27 -44
  205. package/src/http/pages.ts +4 -4
  206. package/src/http/script-runs.ts +2 -0
  207. package/src/http/sessions.ts +366 -12
  208. package/src/http/skills.ts +48 -1
  209. package/src/http/tasks.ts +1 -1
  210. package/src/http/users.ts +13 -1
  211. package/src/http/webhooks.ts +141 -0
  212. package/src/integrations/chat-sdk/adapters.ts +24 -4
  213. package/src/integrations/chat-sdk/attachments.ts +90 -12
  214. package/src/integrations/chat-sdk/facts.ts +138 -0
  215. package/src/integrations/chat-sdk/inbound.ts +156 -8
  216. package/src/integrations/chat-sdk/limits.ts +24 -0
  217. package/src/integrations/chat-sdk/outbound-files.ts +186 -0
  218. package/src/integrations/chat-sdk/outbound.ts +272 -5
  219. package/src/integrations/chat-sdk/relay.ts +4 -1
  220. package/src/integrations/chat-sdk/router.ts +28 -2
  221. package/src/integrations/chat-sdk/steering.ts +9 -0
  222. package/src/integrations/chat-sdk/templates.ts +14 -0
  223. package/src/integrations/chat-sdk/thread-buffer.ts +4 -0
  224. package/src/mcp-limits.ts +20 -0
  225. package/src/pages/browser-sdk.ts +8 -3
  226. package/src/pages/feedback-overlay.ts +1 -1
  227. package/src/providers/acp-targets.ts +1 -1
  228. package/src/providers/amp-adapter.ts +1 -1
  229. package/src/providers/codex-adapter.ts +106 -119
  230. package/src/providers/codex-agents-md.ts +35 -229
  231. package/src/providers/cursor-adapter.ts +1 -1
  232. package/src/providers/pi-mono-adapter.ts +5 -53
  233. package/src/realtime/transport.ts +1 -2
  234. package/src/script-workflows/workflow-ctx.ts +1 -0
  235. package/src/scripts-runtime/types/stdlib.d.ts +2 -0
  236. package/src/scripts-runtime/types/swarm-extension.d.ts +14 -4
  237. package/src/scripts-runtime/types/swarm-sdk.d.ts +2 -0
  238. package/src/server-user.ts +2 -0
  239. package/src/server.ts +2 -0
  240. package/src/slack/assistant.ts +2 -2
  241. package/src/slack/files.ts +2 -0
  242. package/src/slack/handlers.test.ts +158 -0
  243. package/src/slack/handlers.ts +23 -32
  244. package/src/slack/inbound-files.ts +39 -6
  245. package/src/slack/thread-buffer.ts +2 -1
  246. package/src/tasks/context-key.ts +61 -1
  247. package/src/tasks/task-terminal-effects.ts +21 -17
  248. package/src/tests/apps-card-grid.test.ts +197 -0
  249. package/src/tests/apps-catalog-additions.test.ts +120 -0
  250. package/src/tests/apps-tree.test.ts +129 -0
  251. package/src/tests/bitbucket-auth.test.ts +156 -0
  252. package/src/tests/bitbucket-handlers.test.ts +425 -0
  253. package/src/tests/bitbucket-webhook.test.ts +220 -0
  254. package/src/tests/chat-sdk-attachments-reply.test.ts +52 -0
  255. package/src/tests/chat-sdk-config.test.ts +11 -0
  256. package/src/tests/chat-sdk-facts.test.ts +23 -0
  257. package/src/tests/chat-sdk-inbound.test.ts +597 -4
  258. package/src/tests/chat-sdk-outbound-files.test.ts +652 -0
  259. package/src/tests/chat-sdk-telegram-markdown.test.ts +92 -0
  260. package/src/tests/codex-adapter.test.ts +167 -242
  261. package/src/tests/config-api-only-keys.test.ts +9 -0
  262. package/src/tests/context-key.test.ts +50 -0
  263. package/src/tests/core-auth.test.ts +5 -0
  264. package/src/tests/cors-allowlist.test.ts +1 -1
  265. package/src/tests/db-task-read-contract.test.ts +7 -0
  266. package/src/tests/db-task-write-contract.test.ts +6 -0
  267. package/src/tests/docs-index-footprint.test.js +3 -3
  268. package/src/tests/extensions-example-slack-reply-gate.test.ts +262 -91
  269. package/src/tests/fixtures/bitbucket/pullrequest-comment-created-inline.json +174 -0
  270. package/src/tests/fixtures/bitbucket/pullrequest-comment-created.json +170 -0
  271. package/src/tests/fixtures/bitbucket/pullrequest-created-mention.json +132 -0
  272. package/src/tests/fixtures/bitbucket/pullrequest-created-reviewer.json +140 -0
  273. package/src/tests/fixtures/bitbucket/pullrequest-fulfilled.json +134 -0
  274. package/src/tests/fixtures/bitbucket/pullrequest-rejected.json +133 -0
  275. package/src/tests/generate-mcp-docs.test.ts +101 -0
  276. package/src/tests/gitlab-handlers.test.ts +3 -3
  277. package/src/tests/gitlab-vcs-db.test.ts +4 -4
  278. package/src/tests/heartbeat-checklist.test.ts +33 -0
  279. package/src/tests/heartbeat-jev-gate-credentials.test.ts +44 -0
  280. package/src/tests/heartbeat-jev-gate.test.ts +242 -1
  281. package/src/tests/integration-disable-flags.test.ts +36 -0
  282. package/src/tests/kv-page-proxy.test.ts +91 -5
  283. package/src/tests/launch-password-rejection.test.ts +2 -2
  284. package/src/tests/mcp-input-caps.test.ts +294 -0
  285. package/src/tests/mcp-tools.test.ts +1 -1
  286. package/src/tests/memory-chunked-search-starvation.test.ts +200 -0
  287. package/src/tests/memory-embedding-input-cap.test.ts +114 -0
  288. package/src/tests/memory.test.ts +14 -0
  289. package/src/tests/onboarding.test.ts +12 -0
  290. package/src/tests/page-guest-session.test.ts +19 -8
  291. package/src/tests/page-proxy-authed.test.ts +1 -1
  292. package/src/tests/page-proxy.test.ts +17 -10
  293. package/src/tests/page-session.test.ts +102 -1
  294. package/src/tests/pages-authed-mode.test.ts +82 -12
  295. package/src/tests/pages-password-mode.test.ts +5 -5
  296. package/src/tests/pi-codemode.test.ts +8 -2
  297. package/src/tests/preload-env.test.ts +57 -0
  298. package/src/tests/preload.ts +21 -0
  299. package/src/tests/prompt-template-remaining.test.ts +38 -0
  300. package/src/tests/realtime-transport.test.ts +13 -7
  301. package/src/tests/run-root-coverage.test.ts +39 -0
  302. package/src/tests/runner-bitbucket-vcs.test.ts +191 -0
  303. package/src/tests/runner-skills-refresh.test.ts +58 -0
  304. package/src/tests/script-runs-http.test.ts +84 -0
  305. package/src/tests/scripts-http.test.ts +10 -1
  306. package/src/tests/secret-scrubber.test.ts +15 -0
  307. package/src/tests/sessions-move.test.ts +576 -0
  308. package/src/tests/sessions-tree.test.ts +493 -0
  309. package/src/tests/skill-files-http.test.ts +50 -0
  310. package/src/tests/skills-bundle.test.ts +99 -0
  311. package/src/tests/swarm-config-export-to-env.test.ts +23 -3
  312. package/src/tests/task-audio-transcription.test.ts +299 -0
  313. package/src/tests/task-completion-memory-chunking.test.ts +434 -0
  314. package/src/tests/task-terminal-secret-scrub.test.ts +6 -1
  315. package/src/tests/transcription.test.ts +299 -0
  316. package/src/tests/vcs-bitbucket.test.ts +91 -0
  317. package/src/tests/vcs-provider-isolation.test.ts +123 -0
  318. package/src/tests/vcs-provider.test.ts +16 -1
  319. package/src/tests/vcs-tracking.test.ts +3 -3
  320. package/src/tests/workflow-foreach-bodies.test.ts +637 -0
  321. package/src/tests/workflow-foreach-concurrency.test.ts +342 -0
  322. package/src/tests/workflow-foreach-retry.test.ts +537 -0
  323. package/src/tests/workflow-foreach.test.ts +44 -23
  324. package/src/tests/workflow-step-claims.test.ts +6 -6
  325. package/src/tests/workflow-system-one-decision-providers.test.ts +98 -1
  326. package/src/tests/workflow-system-one-decision.test.ts +2 -1
  327. package/src/tests/workflow-tla-races.test.ts +2 -2
  328. package/src/tools/chat-access.ts +10 -0
  329. package/src/tools/chat-post.ts +31 -11
  330. package/src/tools/chat-reply.ts +32 -10
  331. package/src/tools/memory-search.ts +3 -1
  332. package/src/tools/script-delete.ts +1 -1
  333. package/src/tools/system-one-decide.ts +17 -4
  334. package/src/tools/workflows/create-workflow.ts +5 -3
  335. package/src/types.ts +9 -2
  336. package/src/utils/config-env-export.ts +4 -1
  337. package/src/utils/page-session.ts +133 -20
  338. package/src/utils/secret-scrubber.ts +4 -0
  339. package/src/utils/skills-refresh.ts +83 -43
  340. package/src/utils/transcription.ts +246 -0
  341. package/src/vcs/bitbucket.ts +58 -0
  342. package/src/vcs/index.ts +2 -0
  343. package/src/vcs/types.ts +2 -2
  344. package/src/workflows/approval-resolution.ts +4 -1
  345. package/src/workflows/checkpoint.ts +9 -1
  346. package/src/workflows/constants.ts +3 -0
  347. package/src/workflows/definition.ts +48 -17
  348. package/src/workflows/engine.ts +25 -15
  349. package/src/workflows/executors/foreach.ts +45 -99
  350. package/src/workflows/executors/system-one-decision.ts +28 -7
  351. package/src/workflows/executors/system-one-providers.ts +39 -4
  352. package/src/workflows/foreach-body.ts +146 -0
  353. package/src/workflows/foreach-driver.ts +552 -0
  354. package/src/workflows/foreach-join.ts +206 -7
  355. package/src/workflows/node-timeout.ts +21 -0
  356. package/src/workflows/recovery.ts +68 -3
  357. package/src/workflows/resume.ts +221 -43
  358. package/src/workflows/retry-poller.ts +39 -24
  359. package/src/workflows/step-input.ts +59 -0
  360. package/src/workflows/task-step-routing.ts +114 -56
  361. package/templates/extensions/slack-reply-gate/README.md +24 -22
  362. package/templates/extensions/slack-reply-gate/hooks.ts +146 -95
  363. package/templates/extensions/slack-reply-gate/manifest.json +2 -2
  364. package/templates/skills/apps/SKILL.md +28 -6
  365. package/templates/skills/apps/content.md +28 -6
  366. package/templates/skills/chat-sdk/content.md +6 -2
  367. package/templates/skills/code-quality/content.md +2 -1
  368. package/templates/skills/heartbeat-runbook/content.md +5 -5
  369. package/templates/skills/pages/content.md +6 -4
  370. package/templates/skills/slack-interaction/content.md +5 -0
  371. package/templates/skills/swarm-scripts/SKILL.md +2 -2
  372. package/templates/skills/swarm-scripts/content.md +2 -2
  373. package/templates/skills/work-on-task/SKILL.md +1 -1
  374. package/templates/skills/work-on-task/content.md +1 -1
  375. package/dist/cli-51y09c6p.js +0 -2657
  376. package/dist/cli-yw91rnmw.js +0 -13
  377. package/dist/handlers-0pvee9nw.js +0 -39
  378. package/dist/index-gdh8jrdm.js +0 -43
  379. package/dist/index-hg0x2cdv.js +0 -40
  380. package/dist/index-thm0y6a8.js +0 -41
  381. package/dist/lead-ee8vazz2.js +0 -77
  382. package/dist/worker-16s75hm5.js +0 -77
@@ -4,6 +4,7 @@ import {
4
4
  agentmailContextKey,
5
5
  azureDevOpsContextKey,
6
6
  azureDevOpsWorkItemContextKey,
7
+ bitbucketContextKey,
7
8
  buildJiraContextKey,
8
9
  chatSdkContextKey,
9
10
  githubContextKey,
@@ -157,6 +158,55 @@ describe("azure-devops work item keys", () => {
157
158
  });
158
159
  });
159
160
 
161
+ describe("bitbucket pull request keys", () => {
162
+ const UUID = "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d";
163
+
164
+ test("strips the braces Bitbucket wraps repository UUIDs in", () => {
165
+ expect(bitbucketContextKey({ repoUuid: `{${UUID}}`, pullRequestId: 12 })).toBe(
166
+ `task:trackers:bitbucket:${UUID}:pr:12`,
167
+ );
168
+ expect(bitbucketContextKey({ repoUuid: UUID, pullRequestId: "12" })).toBe(
169
+ `task:trackers:bitbucket:${UUID}:pr:12`,
170
+ );
171
+ });
172
+
173
+ test("lowercases the UUID so payload casing cannot fork a thread", () => {
174
+ expect(bitbucketContextKey({ repoUuid: `{${UUID.toUpperCase()}}`, pullRequestId: 3 })).toBe(
175
+ `task:trackers:bitbucket:${UUID}:pr:3`,
176
+ );
177
+ });
178
+
179
+ test("round-trips through parseContextKey", () => {
180
+ const key = bitbucketContextKey({ repoUuid: `{${UUID}}`, pullRequestId: 42 });
181
+ expect(parseContextKey(key)).toEqual({
182
+ family: "trackers",
183
+ subFamily: "bitbucket",
184
+ parts: { repoUuid: UUID, kind: "pr", pullRequestId: 42 },
185
+ });
186
+ });
187
+
188
+ test("rejects empty, braces-only, unbalanced or separator-bearing UUIDs", () => {
189
+ expect(() => bitbucketContextKey({ repoUuid: "", pullRequestId: 1 })).toThrow();
190
+ expect(() => bitbucketContextKey({ repoUuid: "{}", pullRequestId: 1 })).toThrow();
191
+ expect(() => bitbucketContextKey({ repoUuid: "{{abc}", pullRequestId: 1 })).toThrow();
192
+ expect(() => bitbucketContextKey({ repoUuid: "a:b", pullRequestId: 1 })).toThrow();
193
+ });
194
+
195
+ test("rejects a non-integer pull request id", () => {
196
+ expect(() => bitbucketContextKey({ repoUuid: UUID, pullRequestId: 1.5 })).toThrow(/integer/);
197
+ expect(() => bitbucketContextKey({ repoUuid: UUID, pullRequestId: "abc" })).toThrow(/integer/);
198
+ expect(() => bitbucketContextKey({ repoUuid: UUID, pullRequestId: -1 })).toThrow(/integer/);
199
+ });
200
+
201
+ test("parseContextKey rejects malformed bitbucket keys", () => {
202
+ expect(() => parseContextKey(`task:trackers:bitbucket:${UUID}:pr:abc`)).toThrow();
203
+ expect(() => parseContextKey(`task:trackers:bitbucket:${UUID}:issue:1`)).toThrow();
204
+ expect(() => parseContextKey(`task:trackers:bitbucket:${UUID}:pr`)).toThrow();
205
+ expect(() => parseContextKey("task:trackers:bitbucket::pr:1")).toThrow();
206
+ expect(() => parseContextKey(`task:trackers:bitbucket:${UUID}:pr:1:extra`)).toThrow();
207
+ });
208
+ });
209
+
160
210
  describe("parseContextKey", () => {
161
211
  test("round-trips slack keys", () => {
162
212
  const key = slackContextKey({ channelId: "C0A4J7GB0UD", threadTs: "1776800534.257079" });
@@ -88,6 +88,11 @@ describe("handleCore auth middleware (route() auth.apiKey=false is honored)", ()
88
88
  expect(res.status).not.toBe(401);
89
89
  });
90
90
 
91
+ test("Bitbucket webhook is public", async () => {
92
+ const res = await fetch(`http://localhost:${port}/api/bitbucket/webhook`, { method: "POST" });
93
+ expect(res.status).not.toBe(401);
94
+ });
95
+
91
96
  test("AgentMail webhook is still public", async () => {
92
97
  const res = await fetch(`http://localhost:${port}/api/agentmail/webhook`, { method: "POST" });
93
98
  expect(res.status).not.toBe(401);
@@ -336,7 +336,7 @@ describe("page-session CORS over HTTP", () => {
336
336
  const cookie = response.headers.get("set-cookie")!.split(";")[0]!;
337
337
  for (const path of [`/p/${pageId}.json`, `/@swarm/api/pages/${pageId}`]) {
338
338
  const page = await fetch(`${base}${path}`, {
339
- headers: { Origin: origin, Cookie: cookie },
339
+ headers: { Origin: origin, Cookie: cookie, "X-Swarm-Page-Id": pageId },
340
340
  });
341
341
  expect(page.status).toBe(200);
342
342
  expect(page.headers.get("access-control-allow-origin")).toBe(
@@ -102,6 +102,7 @@ type:UpsertAssetKeyMappingInput
102
102
  type:WorkflowFilters
103
103
  type:WorkflowRunListOptions
104
104
  type:WorkflowRunPage
105
+ value:AssetKeyMovesBlockedError
105
106
  value:EXTENSION_AGENT_ROLE
106
107
  value:ExtensionAgentAssignmentError
107
108
  value:KvTypeCollisionError
@@ -116,6 +117,7 @@ value:UNATTRIBUTED_USER_ID
116
117
  value:__resetSqliteVecExtensionPathCacheForTests
117
118
  value:abandonSlackOutcomeDelivery
118
119
  value:acceptTask
120
+ value:assertAssetKeyMovesAllowed
119
121
  value:assignUnassignedTaskPending
120
122
  value:autoEncryptLegacyPlaintextSecrets
121
123
  value:backfillSupersedeTaskResumeTaskId
@@ -215,6 +217,7 @@ value:ensureSlackRenderV2Activation
215
217
  value:extensionAgentAssignmentError
216
218
  value:failPendingResumeIfUnclaimed
217
219
  value:failTask
220
+ value:findActiveTasksByVcs
218
221
  value:findCompletedTaskInThread
219
222
  value:findExistingLinearTrackerContextWork
220
223
  value:findRecentCancelledTaskInThread
@@ -335,6 +338,7 @@ value:getPageVersion
335
338
  value:getPageVersions
336
339
  value:getPausedTasksForAgent
337
340
  value:getPendingApprovalVoteState
341
+ value:getPendingApprovalsForTasks
338
342
  value:getPendingChatRelayTasks
339
343
  value:getPendingEventWaitNames
340
344
  value:getPendingSlackRelayTasks
@@ -486,6 +490,7 @@ value:listUserFavorites
486
490
  value:listWorkflowRuns
487
491
  value:listWorkflowRunsPage
488
492
  value:listWorkflows
493
+ value:literalPrefixPattern
489
494
  value:markChatRelayAttempted
490
495
  value:markChatRelayDelivered
491
496
  value:markChatRelayGivenUp
@@ -505,6 +510,7 @@ value:markTaskSlackReplySent
505
510
  value:markTasksNotified
506
511
  value:maskSecrets
507
512
  value:moveAssetKey
513
+ value:moveTaskChainKeyInTx
508
514
  value:moveTaskFromBacklog
509
515
  value:moveTaskToBacklog
510
516
  value:normalizeSkillFilePath
@@ -615,6 +621,7 @@ type:PendingChatRelay
615
621
  type:TaskFilters
616
622
  value:SUPERSEDED_RESUME_REPAIR_WINDOW_MS
617
623
  value:TASK_LINEAGE_LIMITS
624
+ value:findActiveTasksByVcs
618
625
  value:findCompletedTaskInThread
619
626
  value:findExistingLinearTrackerContextWork
620
627
  value:findRecentCancelledTaskInThread
@@ -125,6 +125,7 @@ type:UpsertAssetKeyMappingInput
125
125
  type:WorkflowFilters
126
126
  type:WorkflowRunListOptions
127
127
  type:WorkflowRunPage
128
+ value:AssetKeyMovesBlockedError
128
129
  value:EXTENSION_AGENT_ROLE
129
130
  value:ExtensionAgentAssignmentError
130
131
  value:KvTypeCollisionError
@@ -139,6 +140,7 @@ value:UNATTRIBUTED_USER_ID
139
140
  value:__resetSqliteVecExtensionPathCacheForTests
140
141
  value:abandonSlackOutcomeDelivery
141
142
  value:acceptTask
143
+ value:assertAssetKeyMovesAllowed
142
144
  value:assignUnassignedTaskPending
143
145
  value:autoEncryptLegacyPlaintextSecrets
144
146
  value:backfillSupersedeTaskResumeTaskId
@@ -238,6 +240,7 @@ value:ensureSlackRenderV2Activation
238
240
  value:extensionAgentAssignmentError
239
241
  value:failPendingResumeIfUnclaimed
240
242
  value:failTask
243
+ value:findActiveTasksByVcs
241
244
  value:findCompletedTaskInThread
242
245
  value:findExistingLinearTrackerContextWork
243
246
  value:findRecentCancelledTaskInThread
@@ -358,6 +361,7 @@ value:getPageVersion
358
361
  value:getPageVersions
359
362
  value:getPausedTasksForAgent
360
363
  value:getPendingApprovalVoteState
364
+ value:getPendingApprovalsForTasks
361
365
  value:getPendingChatRelayTasks
362
366
  value:getPendingEventWaitNames
363
367
  value:getPendingSlackRelayTasks
@@ -509,6 +513,7 @@ value:listUserFavorites
509
513
  value:listWorkflowRuns
510
514
  value:listWorkflowRunsPage
511
515
  value:listWorkflows
516
+ value:literalPrefixPattern
512
517
  value:markChatRelayAttempted
513
518
  value:markChatRelayDelivered
514
519
  value:markChatRelayGivenUp
@@ -528,6 +533,7 @@ value:markTaskSlackReplySent
528
533
  value:markTasksNotified
529
534
  value:maskSecrets
530
535
  value:moveAssetKey
536
+ value:moveTaskChainKeyInTx
531
537
  value:moveTaskFromBacklog
532
538
  value:moveTaskToBacklog
533
539
  value:normalizeSkillFilePath
@@ -78,7 +78,7 @@ describe("docs API index footprint", () => {
78
78
  expect(tasks).toContain('"path":"/api/task-templates","method":"get"');
79
79
  });
80
80
 
81
- test("OpenAPI and generated pages keep all 425 operations", async () => {
81
+ test("OpenAPI and generated pages keep all 444 operations", async () => {
82
82
  const spec = await Bun.file(resolve(repoRoot, "openapi.json")).json();
83
83
  const operationKeys = [];
84
84
  const tags = new Set();
@@ -103,8 +103,8 @@ describe("docs API index footprint", () => {
103
103
  expect(spec.paths["/api/agents/{id}/runtime"]?.get).toBeDefined();
104
104
 
105
105
  const generatedKeys = await generatedOperationKeys();
106
- expect(operationKeys).toHaveLength(440);
107
- expect(generatedKeys).toHaveLength(440);
106
+ expect(operationKeys).toHaveLength(445);
107
+ expect(generatedKeys).toHaveLength(445);
108
108
  expect(new Set(generatedKeys)).toEqual(new Set(operationKeys));
109
109
  });
110
110
 
@@ -1,8 +1,12 @@
1
1
  import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, mock, test } from "bun:test";
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { closeDb, createAgent, getDbClient, initDb, upsertSwarmConfig } from "../be/db";
2
4
  import * as contractRuntime from "../extensions/contract-runtime";
5
+ import { registerSystemOneDecideTool } from "../tools/system-one-decide";
3
6
 
4
- // Unit tests for templates/extensions/slack-reply-gate/hooks.ts. Every Jev and Slack
5
- // call goes to a mocked fetch; nothing reaches the network.
7
+ // Unit tests for templates/extensions/slack-reply-gate/hooks.ts. Decisions go to a
8
+ // stubbed `ctx.swarm.systemOne_decide` and Slack calls to a mocked fetch; nothing
9
+ // reaches the network. The last block runs the real system-one-decide tool behind it.
6
10
 
7
11
  // `swarm-extension` is a bare import the extension loader shims to contract-runtime.
8
12
  mock.module("swarm-extension", () => contractRuntime);
@@ -11,11 +15,14 @@ type Handler = (event: any, ctx: any) => Promise<any>;
11
15
  let handler: Handler;
12
16
  let mod: any;
13
17
 
14
- // The hook reads its secrets from the API process env. Each test sets its own; the
15
- // runner's values are saved here and restored after the file.
18
+ // The hook reads credential presence and the Slack token from the API process env.
19
+ // Each test sets its own; the runner's values are saved here and restored after the file.
16
20
  const ENV_KEYS = [
17
21
  "TYPESAFE_API_KEY",
18
22
  "OPENROUTER_API_KEY",
23
+ "CLOUDFLARE_API_TOKEN",
24
+ "CLOUDFLARE_API_KEY",
25
+ "CLOUDFLARE_ACCOUNT_ID",
19
26
  "SLACK_BOT_TOKEN",
20
27
  ...["ACCEPTED", "BUFFERED", "NOW", "STEERED"].map((k) => `SLACK_REACTION_${k}`),
21
28
  ];
@@ -54,8 +61,25 @@ const slackOk = (method: string, body: any): Response => {
54
61
  };
55
62
  let slackReply: SlackReply = slackOk;
56
63
 
57
- /** Mocked Jev client: answer every call with `reply(body)`. Slack calls go to `slackReply`. */
58
- const mockJev = (reply: (body: any) => Response | Promise<Response>) => {
64
+ /** What the MCP bridge returns for a tool call: the tool's structured content in `data`. */
65
+ type BridgeResult = { success: boolean; status: number; data: Record<string, unknown> };
66
+ const served = (data: Record<string, unknown>): BridgeResult => ({
67
+ success: true,
68
+ status: 200,
69
+ data: { success: true, message: "Decision", ...data },
70
+ });
71
+ const refused = (message: string): BridgeResult => ({
72
+ success: true,
73
+ status: 200,
74
+ data: { success: false, message },
75
+ });
76
+
77
+ type DecideReply = (args: any) => BridgeResult | Promise<BridgeResult>;
78
+ let decideReply: DecideReply = () => refused("no decision stub");
79
+
80
+ /** Stub `systemOne_decide` with `reply(args)`. Slack calls go to `slackReply` through fetch. */
81
+ const mockJev = (reply: DecideReply) => {
82
+ decideReply = reply;
59
83
  globalThis.fetch = (async (url: any, init: any) => {
60
84
  const body = JSON.parse(String(init?.body ?? "{}"));
61
85
  const href = String(url);
@@ -64,8 +88,7 @@ const mockJev = (reply: (body: any) => Response | Promise<Response>) => {
64
88
  slackCalls.push({ method, body, auth: String(init?.headers?.Authorization ?? "") });
65
89
  return slackReply(method, body);
66
90
  }
67
- jevCalls.push({ url: href, body, signal: init?.signal });
68
- return reply(body);
91
+ throw new Error(`unexpected fetch: ${href}`);
69
92
  }) as typeof fetch;
70
93
  };
71
94
  const jevChoice = (
@@ -73,27 +96,29 @@ const jevChoice = (
73
96
  confidence = 0.9,
74
97
  probabilities: Record<string, number> | null = { [choice]: confidence, other: 1 - confidence },
75
98
  ) =>
76
- mockJev(
77
- () =>
78
- new Response(
79
- JSON.stringify({
80
- model: "jev-1.13.0",
81
- answers: {
82
- reason: {
83
- type: "choice",
84
- choice,
85
- confidence,
86
- ...(probabilities ? { probabilities } : {}),
87
- },
88
- },
89
- }),
90
- { status: 200 },
91
- ),
99
+ mockJev(() =>
100
+ served({
101
+ model: "jev-1.13.0",
102
+ answers: {
103
+ reason: {
104
+ type: "choice",
105
+ choice,
106
+ confidence,
107
+ ...(probabilities ? { probabilities } : {}),
108
+ },
109
+ },
110
+ }),
92
111
  );
93
112
 
94
113
  const makeCtx = (
95
114
  config: Record<string, unknown> = {},
96
- opts: { key?: string; openrouterKey?: string; slackToken?: string } = {},
115
+ opts: {
116
+ key?: string;
117
+ openrouterKey?: string;
118
+ cloudflareToken?: string;
119
+ cloudflareAccount?: string;
120
+ slackToken?: string;
121
+ } = {},
97
122
  ) => {
98
123
  const kv: Record<string, unknown> = {};
99
124
  const counters: Record<string, number> = {};
@@ -103,8 +128,14 @@ const makeCtx = (
103
128
  };
104
129
  setEnv("TYPESAFE_API_KEY", opts.key ?? "tsk-test");
105
130
  setEnv("OPENROUTER_API_KEY", opts.openrouterKey ?? "");
131
+ setEnv("CLOUDFLARE_API_TOKEN", opts.cloudflareToken ?? "");
132
+ setEnv("CLOUDFLARE_ACCOUNT_ID", opts.cloudflareAccount ?? "");
106
133
  setEnv("SLACK_BOT_TOKEN", opts.slackToken ?? "xoxb-test");
107
- const swarm = {
134
+ const swarm: Record<string, (args: any) => Promise<unknown>> = {
135
+ systemOne_decide: async (args: any) => {
136
+ jevCalls.push({ provider: args.provider, body: args });
137
+ return decideReply(args);
138
+ },
108
139
  kv_set: async ({ key, value }: any) => {
109
140
  kv[key] = value;
110
141
  return { success: true };
@@ -154,6 +185,7 @@ beforeEach(() => {
154
185
  jevCalls = [];
155
186
  slackCalls = [];
156
187
  slackReply = slackOk;
188
+ decideReply = () => refused("no decision stub");
157
189
  mod.resetKeyCache();
158
190
  });
159
191
  afterEach(async () => {
@@ -325,107 +357,112 @@ describe("decide", () => {
325
357
  });
326
358
  });
327
359
 
328
- describe("Jev transport", () => {
360
+ describe("provider", () => {
329
361
  const cfg = (extra: Record<string, unknown> = {}) => mod.config.parse(extra);
362
+ const CF_ACCOUNT = "0123456789abcdef0123456789abcdef";
330
363
 
331
- test("auto prefers TypeSafe when both keys are set", () => {
332
- makeCtx({}, { key: "tsk-test", openrouterKey: "or-test" });
333
- expect(mod.resolveTransport(cfg())).toEqual({
334
- provider: "typesafe",
335
- endpoint: "https://api.typesafe.ai/v1/systemone",
336
- key: "tsk-test",
337
- model: "jev-latest",
338
- });
364
+ test("auto prefers TypeSafe when every credential is set", () => {
365
+ makeCtx(
366
+ {},
367
+ {
368
+ key: "tsk-test",
369
+ openrouterKey: "or-test",
370
+ cloudflareToken: "cf",
371
+ cloudflareAccount: CF_ACCOUNT,
372
+ },
373
+ );
374
+ expect(mod.resolveProvider(cfg())).toEqual({ provider: "typesafe", model: "jev-latest" });
339
375
  });
340
376
 
341
- test("auto falls back to OpenRouter when only its key is set", () => {
342
- makeCtx({}, { key: "", openrouterKey: "or-test" });
343
- expect(mod.resolveTransport(cfg())).toEqual({
377
+ test("auto falls back to OpenRouter, then to Clef", () => {
378
+ makeCtx(
379
+ {},
380
+ { key: "", openrouterKey: "or-test", cloudflareToken: "cf", cloudflareAccount: CF_ACCOUNT },
381
+ );
382
+ expect(mod.resolveProvider(cfg())).toEqual({
344
383
  provider: "openrouter",
345
- endpoint: "https://openrouter.ai/api/alpha/decisions",
346
- key: "or-test",
347
384
  model: "typesafe/jev-1.13",
348
385
  });
386
+ mod.resetKeyCache();
387
+ makeCtx({}, { key: "", cloudflareToken: "cf", cloudflareAccount: CF_ACCOUNT });
388
+ expect(mod.resolveProvider(cfg())).toEqual({ provider: "cloudflare", model: "clef" });
349
389
  });
350
390
 
351
- test("an explicit provider wins over auto order and needs its own key", () => {
352
- makeCtx({}, { key: "tsk-test", openrouterKey: "or-test" });
353
- expect(mod.resolveTransport(cfg({ provider: "openrouter" })).provider).toBe("openrouter");
391
+ test("auto accepts CLOUDFLARE_API_KEY and needs the account id", () => {
392
+ makeCtx({}, { key: "", cloudflareAccount: CF_ACCOUNT });
393
+ process.env.CLOUDFLARE_API_KEY = "cf-key";
394
+ expect(mod.resolveProvider(cfg()).provider).toBe("cloudflare");
354
395
  mod.resetKeyCache();
355
- makeCtx({}, { key: "", openrouterKey: "or-test" });
356
- expect(() => mod.resolveTransport(cfg({ provider: "typesafe" }))).toThrow(
357
- "no TYPESAFE_API_KEY",
358
- );
396
+ makeCtx({}, { key: "", cloudflareToken: "cf" });
397
+ delete process.env.CLOUDFLARE_API_KEY;
398
+ expect(() => mod.resolveProvider(cfg())).toThrow("CLOUDFLARE_ACCOUNT_ID");
399
+ });
400
+
401
+ test("an explicit provider is used as is; the server checks its credential", () => {
402
+ makeCtx({}, { key: "tsk-test", openrouterKey: "or-test" });
403
+ expect(mod.resolveProvider(cfg({ provider: "openrouter" })).provider).toBe("openrouter");
404
+ expect(
405
+ mod.resolveProvider(cfg({ provider: "cloudflare", cloudflareModel: "clef-flash" })),
406
+ ).toEqual({
407
+ provider: "cloudflare",
408
+ model: "clef-flash",
409
+ });
410
+ // A host with no model setting sends none: the server uses the host's default.
411
+ expect(mod.resolveProvider(cfg({ provider: "laya" }))).toEqual({
412
+ provider: "laya",
413
+ model: undefined,
414
+ });
359
415
  });
360
416
 
361
- test("auto with no key names both secrets", () => {
417
+ test("auto with no credential names every secret", () => {
362
418
  makeCtx({}, { key: "", openrouterKey: "" });
363
- expect(() => mod.resolveTransport(cfg())).toThrow("no TYPESAFE_API_KEY or OPENROUTER_API_KEY");
364
- });
365
-
366
- test("an OpenRouter-only swarm classifies through the Decisions endpoint", async () => {
367
- // Shape captured from a live OpenRouter Decisions call to typesafe/jev-1.13.
368
- mockJev(() =>
369
- Response.json({
370
- model: "typesafe/jev-1.13-20260917",
371
- answers: {
372
- reason: {
373
- type: "choice",
374
- choice: "reaction_only",
375
- probabilities: { direct_ask: 0, reaction_only: 0.95, banter_between_others: 0.05 },
376
- confidence: 0.93,
377
- },
378
- },
379
- usage: { input_tokens: 430, output_tokens: 53, cost: 0.00001806 },
380
- id: "gen-dec-1",
381
- provider: "TypeSafe",
382
- }),
419
+ expect(() => mod.resolveProvider(cfg())).toThrow(
420
+ "no TYPESAFE_API_KEY, OPENROUTER_API_KEY, or CLOUDFLARE_API_TOKEN with CLOUDFLARE_ACCOUNT_ID",
383
421
  );
422
+ });
423
+
424
+ test("the decision goes through systemOne_decide, never a direct fetch", async () => {
425
+ jevChoice("reaction_only", 0.95);
384
426
  const { ctx, kv } = makeCtx({ mode: "enforce" }, { key: "", openrouterKey: "or-test" });
385
427
  const result = await handler(slackEvent(["lol nice"]), ctx);
386
- expect(result).toEqual({ action: "block", reason: expect.stringContaining("p(skip) 1.00") });
428
+ expect(result).toEqual({ action: "block", reason: expect.stringContaining("reaction_only") });
387
429
  expect(jevCalls).toHaveLength(1);
388
- expect(jevCalls[0].url).toBe("https://openrouter.ai/api/alpha/decisions");
389
- expect(jevCalls[0].body.model).toBe("typesafe/jev-1.13");
430
+ expect(jevCalls[0].body).toMatchObject({
431
+ provider: "openrouter",
432
+ model: "typesafe/jev-1.13",
433
+ timeoutMs: 3000,
434
+ maxRetries: 0,
435
+ });
390
436
  expect(Object.keys(jevCalls[0].body.questions.reason.criteria)).toEqual(
391
437
  Object.keys(mod.REASONS),
392
438
  );
439
+ expect(JSON.stringify(jevCalls[0].body)).not.toContain("or-test");
393
440
  expect(kv.last).toMatchObject({ provider: "openrouter", reason: "reaction_only" });
394
441
  });
395
442
 
396
- test("an OpenRouter HTTP error fails open and names the provider", async () => {
397
- mockJev(() => Response.json({ error: { message: "no credits" } }, { status: 402 }));
443
+ test("a provider error relayed by the server fails open and names the provider", async () => {
444
+ mockJev(() => refused("OpenRouter returned HTTP 402"));
398
445
  const { ctx, kv } = makeCtx({ mode: "enforce" }, { key: "", openrouterKey: "or-test" });
399
446
  expect(await handler(slackEvent(["lol"]), ctx)).toBeUndefined();
400
447
  expect(kv.last).toMatchObject({
401
448
  action: "fail-open",
402
- error: expect.stringContaining("openrouter 402"),
449
+ error: expect.stringContaining("openrouter: OpenRouter returned HTTP 402"),
403
450
  });
404
451
  });
405
452
  });
406
453
 
407
454
  describe("fail open", () => {
408
455
  const cases: Array<[string, () => void, Record<string, unknown>]> = [
409
- ["HTTP 500", () => mockJev(() => new Response("boom", { status: 500 })), {}],
410
- ["network error", () => mockJev(() => Promise.reject(new Error("ECONNRESET"))), {}],
456
+ ["provider error", () => mockJev(() => refused("TypeSafe returned HTTP 500")), {}],
411
457
  [
412
- "malformed answer",
413
- () => mockJev(() => new Response(JSON.stringify({ answers: {} }), { status: 200 })),
458
+ "bridge error",
459
+ () => mockJev(() => ({ success: false, status: 500, data: { error: "boom" } })),
414
460
  {},
415
461
  ],
462
+ ["network error", () => mockJev(() => Promise.reject(new Error("ECONNRESET"))), {}],
463
+ ["malformed answer", () => mockJev(() => served({ answers: {} })), {}],
416
464
  ["unknown label", () => jevChoice("definitely_skip"), {}],
417
- [
418
- "timeout",
419
- () =>
420
- mockJev(
421
- (_body) =>
422
- new Promise<Response>((_resolve, reject) => {
423
- const signal = jevCalls.at(-1)?.signal as AbortSignal;
424
- signal.addEventListener("abort", () => reject(new Error("aborted")));
425
- }),
426
- ),
427
- { timeoutMs: 50 },
428
- ],
465
+ ["timeout", () => mockJev(() => new Promise<BridgeResult>(() => {})), { timeoutMs: 50 }],
429
466
  ];
430
467
  for (const [label, arrange, cfg] of cases) {
431
468
  test(`${label} -> continue, error recorded`, async () => {
@@ -440,7 +477,7 @@ describe("fail open", () => {
440
477
  });
441
478
  }
442
479
 
443
- test("missing API key -> continue without calling Jev", async () => {
480
+ test("missing API key under auto -> continue without a decision call", async () => {
444
481
  jevChoice("reaction_only");
445
482
  const { ctx, counters } = makeCtx({ mode: "enforce" }, { key: "" });
446
483
  const result = await handler(slackEvent(["lol"]), ctx);
@@ -754,3 +791,137 @@ describe("mute on drop (enforce)", () => {
754
791
  expect(slackCalls).toHaveLength(0);
755
792
  });
756
793
  });
794
+
795
+ describe("on the real system-one-decide tool", () => {
796
+ const CF_ACCOUNT = "0123456789abcdef0123456789abcdef";
797
+ const CF_TOKEN = "cf-reply-gate-example-token";
798
+ const CLEF_URL = `https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT}/ai/run/@cf/cloudflare/clef`;
799
+ let agentId: string;
800
+ let workersAi: Array<{ url: string; authorization: string | null; body: any }> = [];
801
+
802
+ beforeAll(async () => {
803
+ initDb(":memory:");
804
+ agentId = (await createAgent({ name: "reply-gate-ext", isLead: false, status: "idle" })).id;
805
+ await upsertSwarmConfig({ scope: "global", key: "CLOUDFLARE_ACCOUNT_ID", value: CF_ACCOUNT });
806
+ });
807
+ afterAll(() => closeDb());
808
+
809
+ /** `ctx.swarm.systemOne_decide` as the MCP bridge serves it, backed by the registered tool. */
810
+ const viaTool = (ctx: any) => {
811
+ const server = new McpServer({ name: "reply-gate-decide", version: "1" });
812
+ registerSystemOneDecideTool(server);
813
+ const tool = (server as any)._registeredTools["system-one-decide"];
814
+ ctx.swarm.systemOne_decide = async (args: any) => {
815
+ jevCalls.push({ provider: args.provider, body: args });
816
+ const result = await tool.handler(args, {
817
+ sessionId: "extension",
818
+ requestInfo: { headers: { "x-agent-id": agentId } },
819
+ });
820
+ return { success: true, status: 200, data: result.structuredContent };
821
+ };
822
+ };
823
+
824
+ /** Workers AI answering in its REST envelope; Slack calls go to `slackReply`. */
825
+ const mockWorkersAi = (choice: string, p: number) => {
826
+ workersAi = [];
827
+ globalThis.fetch = (async (input: any, init: any) => {
828
+ const url = String(input);
829
+ const body = JSON.parse(String(init?.body ?? "{}"));
830
+ if (url.startsWith("https://slack.com/api/")) {
831
+ const method = url.slice("https://slack.com/api/".length);
832
+ slackCalls.push({ method, body, auth: String(init?.headers?.authorization ?? "") });
833
+ return slackReply(method, body);
834
+ }
835
+ workersAi.push({ url, authorization: new Headers(init?.headers).get("authorization"), body });
836
+ const labels = Object.keys(mod.REASONS);
837
+ const rest = (1 - p) / (labels.length - 1);
838
+ const probabilities = Object.fromEntries(labels.map((l) => [l, l === choice ? p : rest]));
839
+ return Response.json({
840
+ result: {
841
+ model: "clef",
842
+ answers: { reason: { type: "choice", choice, probabilities, confidence: p } },
843
+ usage: { input_tokens: 412, output_tokens: 0 },
844
+ },
845
+ success: true,
846
+ errors: [],
847
+ messages: [],
848
+ });
849
+ }) as typeof fetch;
850
+ };
851
+
852
+ test("Clef classifies on the server; the hook never holds the token", async () => {
853
+ await upsertSwarmConfig({
854
+ scope: "global",
855
+ key: "CLOUDFLARE_API_TOKEN",
856
+ value: CF_TOKEN,
857
+ isSecret: true,
858
+ });
859
+ mockWorkersAi("reaction_only", 0.9);
860
+ const { ctx, kv } = makeCtx(
861
+ { mode: "enforce" },
862
+ { key: "", cloudflareToken: "present", cloudflareAccount: CF_ACCOUNT },
863
+ );
864
+ viaTool(ctx);
865
+
866
+ const result = await handler(slackEvent(["lol nice"]), ctx);
867
+ expect(result).toEqual({ action: "block", reason: expect.stringContaining("reaction_only") });
868
+ expect(workersAi).toHaveLength(1);
869
+ expect(workersAi[0]).toMatchObject({ url: CLEF_URL, authorization: `Bearer ${CF_TOKEN}` });
870
+ expect(workersAi[0]!.body.model).toBe("clef");
871
+ expect(kv.last).toMatchObject({
872
+ provider: "cloudflare",
873
+ reason: "reaction_only",
874
+ action: "skipped",
875
+ });
876
+ expect(JSON.stringify(kv)).not.toContain(CF_TOKEN);
877
+ });
878
+
879
+ test("auto skips OpenRouter behind a gateway, which the server refuses, and reaches Clef", async () => {
880
+ await upsertSwarmConfig({
881
+ scope: "global",
882
+ key: "CLOUDFLARE_API_TOKEN",
883
+ value: CF_TOKEN,
884
+ isSecret: true,
885
+ });
886
+ const prevBase = process.env.OPENROUTER_BASE_URL;
887
+ process.env.OPENROUTER_BASE_URL = "https://llm-gateway.example.com/v1";
888
+ try {
889
+ mockWorkersAi("reaction_only", 0.9);
890
+ const { ctx, kv } = makeCtx(
891
+ { mode: "enforce" },
892
+ {
893
+ key: "",
894
+ openrouterKey: "or-gateway-token",
895
+ cloudflareToken: "present",
896
+ cloudflareAccount: CF_ACCOUNT,
897
+ },
898
+ );
899
+ viaTool(ctx);
900
+
901
+ const result = await handler(slackEvent(["lol nice"]), ctx);
902
+ expect(result).toEqual({ action: "block", reason: expect.stringContaining("reaction_only") });
903
+ expect(jevCalls.map((c) => c.provider)).toEqual(["openrouter", "cloudflare"]);
904
+ expect(workersAi).toHaveLength(1);
905
+ expect(workersAi[0]!.url).toBe(CLEF_URL);
906
+ expect(kv.last).toMatchObject({ provider: "cloudflare", action: "skipped" });
907
+ } finally {
908
+ if (prevBase === undefined) delete process.env.OPENROUTER_BASE_URL;
909
+ else process.env.OPENROUTER_BASE_URL = prevBase;
910
+ }
911
+ });
912
+
913
+ test("a token missing on the server fails open, as before", async () => {
914
+ await getDbClient().run("DELETE FROM swarm_config WHERE key = 'CLOUDFLARE_API_TOKEN'");
915
+ mockWorkersAi("reaction_only", 0.9);
916
+ const { ctx, kv, counters } = makeCtx({ mode: "enforce", provider: "cloudflare" }, { key: "" });
917
+ viaTool(ctx);
918
+
919
+ expect(await handler(slackEvent(["lol"]), ctx)).toBeUndefined();
920
+ expect(workersAi).toHaveLength(0);
921
+ expect(counters.error).toBe(1);
922
+ expect(kv.last).toMatchObject({
923
+ action: "fail-open",
924
+ error: expect.stringContaining("CLOUDFLARE_API_TOKEN is not configured"),
925
+ });
926
+ });
927
+ });