@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
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  bedrockProviderModule
3
- } from "./cli-dvkmp9bt.js";
3
+ } from "./cli-6ctwrf1z.js";
4
+ import"./cli-2nf3b08t.js";
4
5
  import"./cli-kvzgak7c.js";
5
6
  import"./cli-445c1vp7.js";
6
7
  import"./cli-qz93y3x5.js";
@@ -42,6 +43,9 @@ import {
42
43
  recordUnmappedIdentity,
43
44
  MAX_TASK_ATTACHMENT_BYTES,
44
45
  recordTaskAttachmentUpload,
46
+ scopeFromAttachment,
47
+ backingProviderId,
48
+ transcribeAudioNotes,
45
49
  handleWebhookTrigger,
46
50
  verifyHmacSignature,
47
51
  WebhookError,
@@ -126,6 +130,16 @@ import {
126
130
  getTelemetryIngestAnchor,
127
131
  initApiTelemetry,
128
132
  syncTelemetryEnabled,
133
+ isBitbucketEnabled,
134
+ initBitbucket,
135
+ verifyBitbucketWebhook,
136
+ isDuplicateDelivery,
137
+ markDeliveryProcessed,
138
+ pullRequestEventOf,
139
+ pullRequestCommentEventOf,
140
+ handlePullRequestCreated2,
141
+ handlePullRequestCommentCreated,
142
+ handlePullRequestClosed,
129
143
  handlePullRequest,
130
144
  handleIssue,
131
145
  handleComment,
@@ -168,14 +182,18 @@ import {
168
182
  handleAgentSessionPrompted,
169
183
  isLinearEnabled,
170
184
  initLinear,
171
- parseCookieHeader,
185
+ isCookieSafePageId,
186
+ extractPageSession,
187
+ extractLegacyPageSession,
172
188
  extractAndVerifyCookie,
173
- issuePageSessionCookie,
189
+ pageSessionSetCookies,
174
190
  resolveHttpRequestAuth,
175
191
  loadGlobalConfigsIntoEnv,
176
192
  reloadGlobalConfigsAndIntegrations,
177
193
  scheduleIntegrationsReload,
178
194
  handleCore,
195
+ MCP_MAX_TOOL_INPUT_ELEMENTS,
196
+ MCP_MAX_REQUEST_BODY_BYTES,
179
197
  userCtx,
180
198
  requestPrincipal,
181
199
  agentFirstPrincipal,
@@ -205,6 +223,9 @@ import {
205
223
  hydrateOAuthAppFromPreset,
206
224
  scopedResourceScopeIdSchema,
207
225
  resolveScopedResourceId,
226
+ MemoryKeyError,
227
+ refreshLinks,
228
+ indexMemoryContent,
208
229
  dedupeMemoryDocumentIds,
209
230
  recordMemoryAccesses,
210
231
  recordRetrievals,
@@ -221,12 +242,9 @@ import {
221
242
  reservedRoomKeyError,
222
243
  updateMcpServerChecked,
223
244
  updateTouchesStdioExecution,
224
- MemoryKeyError,
225
- refreshLinks,
226
245
  canReadMemory,
227
246
  getLinksForMemory,
228
247
  expandCandidatesWithGraph,
229
- indexMemoryContent,
230
248
  isApiDraining,
231
249
  drainApi,
232
250
  markKapsoMessageRead,
@@ -283,7 +301,7 @@ import {
283
301
  hasCapability,
284
302
  getEnabledCapabilities,
285
303
  createServer
286
- } from "./cli-v3yr2gbg.js";
304
+ } from "./cli-5kzk3472.js";
287
305
  import {
288
306
  chatThreadOfContextKey,
289
307
  agentContextKey,
@@ -378,6 +396,7 @@ import {
378
396
  listApprovalRequestSummaries2,
379
397
  getPendingApprovalVoteState2,
380
398
  recordApprovalVotes2,
399
+ getPendingApprovalsForTasks2,
381
400
  computeContentHash2,
382
401
  getTaskById2,
383
402
  TASK_LINEAGE_LIMITS2,
@@ -397,6 +416,15 @@ import {
397
416
  deleteModelProfile,
398
417
  taskProfileError,
399
418
  init_model_profiles,
419
+ AssetKeyMovesBlockedError2,
420
+ assertAssetKeyMovesAllowed2,
421
+ moveSessionChains,
422
+ listPrefixRoots,
423
+ listSharedPrefixRoots,
424
+ init_session_moves,
425
+ getSessionReadState,
426
+ getSessionTree,
427
+ init_session_tree,
400
428
  setHumanFreeQueueListener,
401
429
  drainHumanFreeReclassifyQueue,
402
430
  init_human_free,
@@ -480,6 +508,8 @@ import {
480
508
  init_key_paths,
481
509
  rerank,
482
510
  init_reranker,
511
+ searchMemoryCandidates,
512
+ init_search_candidates,
483
513
  getEmbeddingProvider2,
484
514
  getMemoryStore2,
485
515
  init_memory,
@@ -720,38 +750,40 @@ import {
720
750
  countScriptRunJournalSteps2,
721
751
  countScriptRunJournalAgentTaskSteps2,
722
752
  init_db3
723
- } from "./cli-p20r8w42.js";
753
+ } from "./cli-fcc5qe3f.js";
724
754
  import {
725
755
  getUserGrant2,
726
756
  init_rbac_roles
727
- } from "./cli-4dg55wsk.js";
728
- import"./cli-t7cyef5g.js";
757
+ } from "./cli-0z3vjrpm.js";
758
+ import"./cli-36j4jnk3.js";
729
759
  import {
730
760
  normalizeModelKey
731
- } from "./cli-amyhm3gj.js";
761
+ } from "./cli-0y6f0ckq.js";
732
762
  import {
733
763
  upsertModelCatalogOverlay,
734
764
  deleteModelCatalogOverlay,
735
765
  loadModelsCatalog,
736
766
  requestModelCatalogRefresh2
737
- } from "./cli-xc27fwzg.js";
767
+ } from "./cli-y9jvxfhk.js";
738
768
  import {
739
769
  AMP_ESTIMATE_MODELS,
740
770
  ampBillsCacheWrites,
741
771
  resolveAmpModel
742
- } from "./cli-2zqpkvtt.js";
772
+ } from "./cli-jpxsrxcb.js";
743
773
  import {
744
774
  automationIntegrationFixUrl,
745
775
  preflightAutomation,
746
776
  getAutomationSetupStates,
747
777
  listEnabledAutomationPreflightInputs,
778
+ createApprovalFollowUpTask,
748
779
  validateJsonSchema,
749
780
  workflowSaveWarnings,
750
- retryFailedRun2,
751
- cancelWorkflowRun2,
752
781
  TriggerSchemaError,
753
- startWorkflowExecution2
754
- } from "./cli-51y09c6p.js";
782
+ startWorkflowExecution2,
783
+ generateEdges,
784
+ definitionNodeIds,
785
+ validateDefinition
786
+ } from "./cli-92mqenkc.js";
755
787
  import {
756
788
  require_dist2,
757
789
  isInvalidClientError,
@@ -831,14 +863,14 @@ import {
831
863
  typecheckWithAmbient,
832
864
  typecheckScript,
833
865
  parseSkillContent
834
- } from "./cli-2y91wnbh.js";
866
+ } from "./cli-we0ra057.js";
835
867
  import {
836
868
  buildAuthorizationUrl,
837
869
  exchangeAuthorizationCode,
838
870
  oauthAppRowToProviderConfig,
839
871
  forceRefreshAuthorizationOrThrow,
840
872
  forceRefreshTokenOrThrow
841
- } from "./cli-esxerzg7.js";
873
+ } from "./cli-s4sntwt4.js";
842
874
  import {
843
875
  signalProcessGroup,
844
876
  detachedProcessGroup,
@@ -866,15 +898,9 @@ import {
866
898
  getSlackApp2,
867
899
  startSlackApp2,
868
900
  stopSlackApp2
869
- } from "./cli-qkhc5aqy.js";
870
- import"./cli-1d70crsy.js";
871
- import {
872
- createApprovalFollowUpTask,
873
- generateEdges,
874
- definitionNodeIds,
875
- validateDefinition
876
- } from "./cli-q6r7t299.js";
877
- import"./cli-8nyhf7za.js";
901
+ } from "./cli-fy1jyyym.js";
902
+ import"./cli-ftmq4nvk.js";
903
+ import"./cli-qqzmjxkf.js";
878
904
  import {
879
905
  _fetchHolder,
880
906
  CLIENT_ID,
@@ -885,11 +911,15 @@ import {
885
911
  codexOAuthKeyForSlot,
886
912
  loadAllCodexOAuthSlots,
887
913
  getValidCodexOAuth
888
- } from "./cli-ejs3m1sx.js";
914
+ } from "./cli-kxpfx2xr.js";
889
915
  import {
890
916
  workflowEventBus2,
891
917
  init_event_bus
892
- } from "./cli-wt645v7q.js";
918
+ } from "./cli-dabehxhp.js";
919
+ import {
920
+ retryFailedRun2,
921
+ cancelWorkflowRun2
922
+ } from "./cli-3zwye9sn.js";
893
923
  import {
894
924
  duplicateGuardedQueryKey,
895
925
  warnIfCorsAllowsAnyOrigin,
@@ -901,6 +931,7 @@ import {
901
931
  wireHttpSpanLifecycle,
902
932
  safeRequestUrlForLog,
903
933
  agentWithCapacity,
934
+ RequestBodyTooLargeError,
904
935
  parseBody,
905
936
  BODY_TOO_LARGE,
906
937
  enforceContentLengthCap,
@@ -926,13 +957,14 @@ import {
926
957
  canReadSession2,
927
958
  readableByFor2,
928
959
  SESSION_WRITE_DENIED2,
960
+ hasSessionWrite2,
929
961
  denyParentAttach2,
930
962
  runtimeInstanceHeader,
931
963
  bodyTask,
932
964
  bodyListTask,
933
965
  describeRequestRoute,
934
966
  route
935
- } from "./cli-cynvc9fj.js";
967
+ } from "./cli-9ce2xw98.js";
936
968
  import {
937
969
  isMultiRuntimeEnabled
938
970
  } from "./cli-e8xyjvfc.js";
@@ -942,7 +974,7 @@ import {
942
974
  agentFsRequestTimeoutMs,
943
975
  AgentFsProvider,
944
976
  getFileStorageProvider2
945
- } from "./cli-qje9k2nh.js";
977
+ } from "./cli-g9ypd5en.js";
946
978
  import {
947
979
  RuntimePoolError2,
948
980
  mintControllerToken,
@@ -963,13 +995,13 @@ import {
963
995
  resolvePoolDelivery2,
964
996
  createPoolAgent2,
965
997
  init_db
966
- } from "./cli-w6nadkh4.js";
998
+ } from "./cli-5rvg1ddk.js";
967
999
  import {
968
1000
  RATING_MODEL_MAX_LENGTH,
969
1001
  REFERENCES_SOURCE_MAX_LENGTH,
970
1002
  sanitizeReferencesSource
971
1003
  } from "./cli-3h26v7yj.js";
972
- import"./cli-smvcw9z5.js";
1004
+ import"./cli-pasyvjmm.js";
973
1005
  import {
974
1006
  startPoolRouting,
975
1007
  stopPoolRouting,
@@ -979,8 +1011,8 @@ import {
979
1011
  checkHeartbeatChecklist2,
980
1012
  runHeartbeatSweep2,
981
1013
  stopHeartbeat2
982
- } from "./cli-3cgngyd8.js";
983
- import"./cli-82d3nkc3.js";
1014
+ } from "./cli-7g40g8hq.js";
1015
+ import"./cli-xd4eqtr2.js";
984
1016
  import {
985
1017
  import_websocket,
986
1018
  import_websocket_server
@@ -989,7 +1021,7 @@ import {
989
1021
  runtimeSupervisorStatus,
990
1022
  startRuntimeSupervisor,
991
1023
  stopRuntimeSupervisor
992
- } from "./cli-dqb4n629.js";
1024
+ } from "./cli-x6vf4sad.js";
993
1025
  import"./cli-ns59dc6r.js";
994
1026
  import {
995
1027
  ACP_TARGET_IDS
@@ -1009,14 +1041,14 @@ import {
1009
1041
  memoryRelevance,
1010
1042
  isUnknownModelError,
1011
1043
  writeSkillsToFilesystem
1012
- } from "./cli-xj0re5tm.js";
1044
+ } from "./cli-4w24f7z3.js";
1013
1045
  import {
1014
1046
  inviteEmailToSharedOrg,
1015
1047
  ensureAgentFsCredentialsForAgent
1016
- } from "./cli-hj3debgw.js";
1048
+ } from "./cli-1pr672hz.js";
1017
1049
  import {
1018
1050
  Anthropic
1019
- } from "./cli-er83cjbn.js";
1051
+ } from "./cli-3qv8naq8.js";
1020
1052
  import {
1021
1053
  isCodexOAuthConfigKey
1022
1054
  } from "./cli-j4bwnpkm.js";
@@ -1052,12 +1084,13 @@ import {
1052
1084
  revokeAgentTokens,
1053
1085
  fingerprintApiKey,
1054
1086
  init_users
1055
- } from "./cli-9fhkkpj5.js";
1087
+ } from "./cli-xt768r5m.js";
1056
1088
  import"./cli-v41kn3cd.js";
1057
1089
  import"./cli-wgtk7xxr.js";
1090
+ import"./cli-1f0gp9vz.js";
1058
1091
  import {
1059
1092
  setBedrockProviderModule
1060
- } from "./cli-qwkcwjdn.js";
1093
+ } from "./cli-rycdaj0a.js";
1061
1094
  import {
1062
1095
  isRbacEnabled,
1063
1096
  decideToolAdmission,
@@ -1068,6 +1101,11 @@ import {
1068
1101
  init_rbac
1069
1102
  } from "./cli-ba8ne1es.js";
1070
1103
  import {
1104
+ ASSET_KEY_MAX_LENGTH,
1105
+ normalizeAssetKey,
1106
+ defaultAssetKey,
1107
+ parseAssetKey,
1108
+ init_key,
1071
1109
  isSafeBundlePath,
1072
1110
  init_bundle_path,
1073
1111
  init_zod_openapi,
@@ -1208,12 +1246,12 @@ import {
1208
1246
  KvKeySchema,
1209
1247
  KvEntrySchema,
1210
1248
  init_types
1211
- } from "./cli-nc35pxb7.js";
1249
+ } from "./cli-rh5smt8t.js";
1212
1250
  import {
1213
1251
  IDENTITY_FIELD_BUDGETS,
1214
1252
  IdentityFieldBudgetError,
1215
1253
  init_identity_field_budget
1216
- } from "./cli-74c1wpm0.js";
1254
+ } from "./cli-gpv60278.js";
1217
1255
  import {
1218
1256
  getSlackConfiguration,
1219
1257
  init_config
@@ -1281,7 +1319,7 @@ import {
1281
1319
  import {
1282
1320
  init_package,
1283
1321
  package_default
1284
- } from "./cli-pccfmtb5.js";
1322
+ } from "./cli-179xcyky.js";
1285
1323
  import {
1286
1324
  realtimeBus,
1287
1325
  init_bus
@@ -1310,7 +1348,7 @@ import {
1310
1348
  scrubObject,
1311
1349
  registerVolatileSecret,
1312
1350
  init_secret_scrubber
1313
- } from "./cli-xf234ws8.js";
1351
+ } from "./cli-n4c8e3s5.js";
1314
1352
  import {
1315
1353
  isAlias,
1316
1354
  init_src2
@@ -2463,8 +2501,7 @@ async function authenticate(req) {
2463
2501
  const page = await getPage2(pageId);
2464
2502
  if (!page)
2465
2503
  throw new Error("Page not found");
2466
- const cookie = await extractAndVerifyCookie(req);
2467
- const session = cookie?.pageId === pageId ? cookie : null;
2504
+ const session = await extractAndVerifyCookie(req, pageId);
2468
2505
  if (!session && page.authMode !== "public")
2469
2506
  throw new Error("Page session required");
2470
2507
  if (session?.uid && (await findUserById(session.uid))?.status !== "active")
@@ -6769,6 +6806,10 @@ async function handleAssets(req, res, pathSegments, queryParams, myAgentId) {
6769
6806
  jsonError(res, "Asset not found", 404);
6770
6807
  return true;
6771
6808
  }
6809
+ if (!await hasSessionWrite2(req, myAgentId, task.id)) {
6810
+ jsonError(res, await canReadSession2(req, myAgentId, task.id) ? "You can view this session but not move it." : SESSION_READ_DENIED2, 403);
6811
+ return true;
6812
+ }
6772
6813
  const tasks = parsed.body.cascade ? await getRootTaskChain2(task.id) : [task];
6773
6814
  for (const target of tasks) {
6774
6815
  if (!await canMutateTaskNamespace(target, myAgentId, req)) {
@@ -6825,7 +6866,7 @@ async function handleAssets(req, res, pathSegments, queryParams, myAgentId) {
6825
6866
  return true;
6826
6867
  }
6827
6868
  const message = error instanceof Error ? error.message : String(error);
6828
- jsonError(res, message, message.includes("blocked until") ? 409 : 400);
6869
+ jsonError(res, message, error instanceof AssetKeyMovesBlockedError2 ? 409 : 400);
6829
6870
  }
6830
6871
  return true;
6831
6872
  }
@@ -7076,6 +7117,7 @@ var OnboardingIntegrationMethodSchema = _enum2([
7076
7117
  "slack",
7077
7118
  "github",
7078
7119
  "gitlab",
7120
+ "bitbucket",
7079
7121
  "linear_oauth",
7080
7122
  "jira_oauth"
7081
7123
  ]);
@@ -7143,6 +7185,7 @@ var OnboardingSignalsSchema = object2({
7143
7185
  slack: boolean(),
7144
7186
  github: boolean(),
7145
7187
  gitlab: boolean(),
7188
+ bitbucket: boolean(),
7146
7189
  linear: boolean(),
7147
7190
  jira: boolean()
7148
7191
  }),
@@ -7364,6 +7407,8 @@ function inferIntegrationMethod(signals) {
7364
7407
  return "github";
7365
7408
  if (signals.integrations.gitlab)
7366
7409
  return "gitlab";
7410
+ if (signals.integrations.bitbucket)
7411
+ return "bitbucket";
7367
7412
  if (signals.integrations.linear)
7368
7413
  return "linear_oauth";
7369
7414
  if (signals.integrations.jira)
@@ -9644,6 +9689,7 @@ declare module "swarm-extension" {
9644
9689
  | "github"
9645
9690
  | "gitlab"
9646
9691
  | "azure-devops"
9692
+ | "bitbucket"
9647
9693
  | "system"
9648
9694
  | "linear"
9649
9695
  | "jira"
@@ -9670,7 +9716,7 @@ declare module "swarm-extension" {
9670
9716
  slackTriggerMessageTs?: string | undefined;
9671
9717
  slackUserId?: string | undefined;
9672
9718
  overrideSlackContext?: boolean | undefined;
9673
- vcsProvider?: "github" | "gitlab" | "azure-devops" | undefined;
9719
+ vcsProvider?: "github" | "gitlab" | "azure-devops" | "bitbucket" | undefined;
9674
9720
  vcsRepo?: string | undefined;
9675
9721
  vcsEventType?: string | undefined;
9676
9722
  vcsNumber?: number | undefined;
@@ -9696,6 +9742,7 @@ declare module "swarm-extension" {
9696
9742
  sourceTaskId?: string | undefined;
9697
9743
  outputSchema?: Record<string, unknown> | undefined;
9698
9744
  inheritParentOutputSchema?: boolean | undefined;
9745
+ inheritParentRequester?: boolean | undefined;
9699
9746
  inheritParentRoutingAffinity?: boolean | undefined;
9700
9747
  inheritParentFollowUpConfig?: boolean | undefined;
9701
9748
  followUpConfig?:
@@ -9760,6 +9807,7 @@ declare module "swarm-extension" {
9760
9807
  | "github"
9761
9808
  | "gitlab"
9762
9809
  | "azure-devops"
9810
+ | "bitbucket"
9763
9811
  | "system"
9764
9812
  | "linear"
9765
9813
  | "jira"
@@ -9800,7 +9848,7 @@ declare module "swarm-extension" {
9800
9848
  slackUserId?: string | undefined;
9801
9849
  slackProgressMessageTs?: string | undefined;
9802
9850
  slackTreeRootMessageTs?: string | undefined;
9803
- vcsProvider?: "github" | "gitlab" | "azure-devops" | undefined;
9851
+ vcsProvider?: "github" | "gitlab" | "azure-devops" | "bitbucket" | undefined;
9804
9852
  vcsRepo?: string | undefined;
9805
9853
  vcsEventType?: string | undefined;
9806
9854
  vcsNumber?: number | undefined;
@@ -10114,8 +10162,15 @@ declare module "swarm-extension" {
10114
10162
  userName?: string;
10115
10163
  /** The buffered messages, joined. */
10116
10164
  text: string;
10165
+ /** True for an @mention and for a reply to one of the bot's messages. */
10117
10166
  botMentioned: boolean;
10118
10167
  isDirectMessage: boolean;
10168
+ /** Platform chat type, such as Telegram \`private\`, \`group\`, \`supergroup\` or \`channel\`. */
10169
+ chatType?: string;
10170
+ /** Set for a Telegram supergroup with topics. */
10171
+ isForum?: boolean;
10172
+ /** A buffered message replies to one of the bot's own messages. */
10173
+ replyToBot: boolean;
10119
10174
  /** Set when earlier swarm tasks exist in this thread. */
10120
10175
  threadContext?: {
10121
10176
  adapter: string;
@@ -10246,7 +10301,7 @@ declare module "swarm-extension" {
10246
10301
  /** The text, or a placeholder naming the type for a non-text message. */
10247
10302
  text: string;
10248
10303
  }
10249
- export type VcsProvider = "github" | "gitlab" | "azure-devops";
10304
+ export type VcsProvider = "github" | "gitlab" | "azure-devops" | "bitbucket";
10250
10305
  /**
10251
10306
  * The provider object the event is about, as the provider names it. GitLab
10252
10307
  * calls a pull request a merge request, and a comment a \`note\`.
@@ -10269,7 +10324,7 @@ declare module "swarm-extension" {
10269
10324
  provider: VcsProvider;
10270
10325
  kind: VcsEventKind;
10271
10326
  action: string;
10272
- /** \`owner/name\` on GitHub and GitLab, the repository URL on Azure DevOps. */
10327
+ /** \`owner/name\` on GitHub and GitLab, \`workspace/repo_slug\` on Bitbucket, the repository URL on Azure DevOps. */
10273
10328
  repo: string;
10274
10329
  /** The PR, merge request, or issue number. Absent for a pipeline outside a merge request. */
10275
10330
  number?: number;
@@ -11442,8 +11497,8 @@ export default extension;
11442
11497
  manifest: {
11443
11498
  $schema: "../manifest.schema.json",
11444
11499
  name: "slack-reply-gate",
11445
- description: "Asks Jev (TypeSafe or OpenRouter) whether Lead should answer a buffered Slack thread follow-up, and drops the wake-up when it should not",
11446
- version: "1.2.0",
11500
+ description: "Asks a SystemOne decision model (Jev or Clef) whether Lead should answer a buffered Slack thread follow-up, and drops the wake-up when it should not",
11501
+ version: "1.3.0",
11447
11502
  runtime: "api",
11448
11503
  assets: {
11449
11504
  hooks: "hooks.ts"
@@ -11458,8 +11513,9 @@ import { z } from "zod";
11458
11513
  *
11459
11514
  * Every human message in a Slack thread the swarm takes part in is buffered and
11460
11515
  * flushed into a Lead task ("[Thread follow-up — N message(s) buffered]"), even
11461
- * "lol", ":joy:" or two people bantering. This hook asks TypeSafe Jev whether
11462
- * Lead should answer, and drops the wake-up when the answer is no.
11516
+ * "lol", ":joy:" or two people bantering. This hook asks a SystemOne decision
11517
+ * model (Jev or Clef) whether Lead should answer, and drops the wake-up when the
11518
+ * answer is no.
11463
11519
  *
11464
11520
  * Hook: \`pre.task.create\`, origin "slack", description carrying the
11465
11521
  * thread-buffer header. \`pre.slack.route\` cannot be used: the buffered thread
@@ -11467,17 +11523,20 @@ import { z } from "zod";
11467
11523
  * re-target a message, never drop it.
11468
11524
  *
11469
11525
  * Rules:
11470
- * - A message that @mentions Lead passes without a Jev call.
11471
- * - Otherwise one Jev \`choice\` question classifies the message into a reason;
11526
+ * - A message that @mentions Lead passes without a decision call.
11527
+ * - Otherwise one \`choice\` question classifies the message into a reason;
11472
11528
  * reply=false only for reaction_only | banter_between_others |
11473
11529
  * not_addressed_to_lead, and only when the summed probability of those
11474
11530
  * three labels is >= minConfidence (a banter/reaction split is still a skip).
11475
11531
  * - Fails OPEN on every error, timeout or missing key.
11476
11532
  *
11477
- * Transport (config.provider): Jev is served by TypeSafe directly and by
11478
- * OpenRouter's Decisions endpoint. Both take the same {model, state, questions}
11479
- * body and return the same \`answers\` shape. \`auto\` uses TYPESAFE_API_KEY when
11480
- * set, else OPENROUTER_API_KEY.
11533
+ * Provider (config.provider): the decision is made server-side by the
11534
+ * \`system-one-decide\` tool (\`ctx.swarm.systemOne_decide\`), the same path as a
11535
+ * \`system-one-decision\` workflow node, so every SystemOne host works and no key
11536
+ * enters this hook. \`auto\` picks the first host with a configured credential:
11537
+ * typesafe (Jev, TYPESAFE_API_KEY), openrouter (Jev, OPENROUTER_API_KEY), then
11538
+ * cloudflare (Clef, CLOUDFLARE_API_TOKEN or CLOUDFLARE_API_KEY, plus
11539
+ * CLOUDFLARE_ACCOUNT_ID). laya and openai are explicit choices only.
11481
11540
  *
11482
11541
  * modes (config.mode):
11483
11542
  * off -> never evaluate
@@ -11500,17 +11559,19 @@ import { z } from "zod";
11500
11559
  export const config = z.object({
11501
11560
  mode: z.enum(["off", "shadow", "enforce"]).default("shadow"),
11502
11561
  timeoutMs: z.number().int().positive().default(3000),
11503
- provider: z.enum(["auto", "typesafe", "openrouter"]).default("auto"),
11504
- /** Jev model on the TypeSafe transport. */
11562
+ provider: z
11563
+ .enum(["auto", "typesafe", "openrouter", "cloudflare", "laya", "openai"])
11564
+ .default("auto"),
11565
+ /** Jev model on TypeSafe. */
11505
11566
  model: z.string().default("jev-latest"),
11506
- /** Jev model on the OpenRouter transport. */
11567
+ /** Jev model on OpenRouter. */
11507
11568
  openrouterModel: z.string().default("typesafe/jev-1.13"),
11569
+ /** Clef model on Cloudflare Workers AI: clef or clef-flash. */
11570
+ cloudflareModel: z.string().default("clef"),
11508
11571
  minConfidence: z.number().min(0).max(1).default(0.55),
11509
11572
  contextMessages: z.number().int().min(0).max(20).default(6),
11510
11573
  leadAliases: z.array(z.string()).default(["lead"]),
11511
11574
  botSlackUserIds: z.array(z.string()).default([]),
11512
- /** Explicit TypeSafe key; otherwise TYPESAFE_API_KEY from the API process env. */
11513
- apiKey: z.string().optional(),
11514
11575
  /**
11515
11576
  * Reaction for a dropped message in enforce mode. "" only removes the bot's
11516
11577
  * acceptance reaction(s) and adds nothing.
@@ -11523,13 +11584,13 @@ type Cfg = z.infer<typeof config>;
11523
11584
  type Swarm = ApiCtx["swarm"];
11524
11585
 
11525
11586
  const NS = "ext:slack-reply-gate";
11526
- const TYPESAFE_ENDPOINT = "https://api.typesafe.ai/v1/systemone";
11527
- const OPENROUTER_ENDPOINT = "https://openrouter.ai/api/alpha/decisions";
11528
11587
  const HEADER_LINE_RE = /(?:^|\\n)\\[Thread follow-up — \\d+ message\\(s\\) buffered\\]\\n/g;
11529
11588
  const CTX_OPEN = "<thread_context>";
11530
11589
  const CTX_CLOSE = "</thread_context>";
11531
11590
  // The dispatcher cancels a handler after 5 s; leave room for the KV writes.
11532
11591
  const MAX_TIMEOUT_MS = 4000;
11592
+ // The smallest time budget system-one-decide accepts.
11593
+ const MIN_DECIDE_TIMEOUT_MS = 1000;
11533
11594
 
11534
11595
  export const REASONS = {
11535
11596
  direct_ask:
@@ -11631,14 +11692,14 @@ const usable = (v: string): boolean => Boolean(v) && !v.startsWith("[REDACTED");
11631
11692
  const envOf = (name: string): string => String(process.env[name] ?? "");
11632
11693
 
11633
11694
  /**
11634
- * Resolve a secret: explicit value, then the API process env. The API process loads
11635
- * global swarm_config rows into process.env at boot and on config reload.
11695
+ * Resolve a secret from the API process env. The API process loads global
11696
+ * swarm_config rows into process.env at boot and on config reload.
11636
11697
  * ctx.swarm.config_get cannot serve it: the SDK scrubs secrets to "[REDACTED:<name>]".
11637
11698
  */
11638
- const secret = (name: string, explicit?: string): string => {
11699
+ const secret = (name: string): string => {
11639
11700
  const cached = secretCache.get(name);
11640
11701
  if (cached && Date.now() - cached.at < KEY_TTL_MS) return cached.value;
11641
- const value = [String(explicit ?? ""), envOf(name)].find(usable) ?? "";
11702
+ const value = usable(envOf(name)) ? envOf(name) : "";
11642
11703
  if (usable(value)) secretCache.set(name, { value, at: Date.now() });
11643
11704
  return value;
11644
11705
  };
@@ -11648,46 +11709,51 @@ export const resetKeyCache = (): void => {
11648
11709
  secretCache.clear();
11649
11710
  };
11650
11711
 
11651
- export type JevTransport = {
11652
- provider: "typesafe" | "openrouter";
11653
- endpoint: string;
11654
- key: string;
11655
- model: string;
11656
- };
11712
+ export type ProviderId = Exclude<Cfg["provider"], "auto">;
11713
+
11714
+ /** Where the decision is made. No model means the provider's own default. */
11715
+ export type DecisionTarget = { provider: ProviderId; model?: string };
11716
+
11717
+ /** What \`auto\` needs per provider: every group must have one usable value. */
11718
+ const AUTO_CREDENTIALS: ReadonlyArray<[ProviderId, string[][]]> = [
11719
+ ["typesafe", [["TYPESAFE_API_KEY"]]],
11720
+ ["openrouter", [["OPENROUTER_API_KEY"]]],
11721
+ ["cloudflare", [["CLOUDFLARE_API_TOKEN", "CLOUDFLARE_API_KEY"], ["CLOUDFLARE_ACCOUNT_ID"]]],
11722
+ ];
11723
+
11724
+ const modelFor = (provider: ProviderId, cfg: Cfg): string | undefined =>
11725
+ provider === "typesafe"
11726
+ ? cfg.model
11727
+ : provider === "openrouter"
11728
+ ? cfg.openrouterModel
11729
+ : provider === "cloudflare"
11730
+ ? cfg.cloudflareModel
11731
+ : undefined;
11657
11732
 
11658
11733
  /**
11659
- * Pick the Jev transport. \`auto\` prefers TypeSafe and falls back to OpenRouter.
11660
- * Throws when the chosen provider has no usable key; the handler fails open.
11734
+ * The decision providers to try, in order. An explicit one is used as is: the
11735
+ * server checks its credential. \`auto\` lists every provider in \`AUTO_CREDENTIALS\`
11736
+ * whose credential is set and throws when none is; the handler then fails open.
11661
11737
  */
11662
- export const resolveTransport = (cfg: Cfg): JevTransport => {
11663
- const typesafe = (): JevTransport | null => {
11664
- const key = secret("TYPESAFE_API_KEY", cfg.apiKey);
11665
- return usable(key)
11666
- ? { provider: "typesafe", endpoint: TYPESAFE_ENDPOINT, key, model: cfg.model }
11667
- : null;
11668
- };
11669
- const openrouter = (): JevTransport | null => {
11670
- const key = secret("OPENROUTER_API_KEY");
11671
- return usable(key)
11672
- ? { provider: "openrouter", endpoint: OPENROUTER_ENDPOINT, key, model: cfg.openrouterModel }
11673
- : null;
11674
- };
11675
- const transport =
11676
- cfg.provider === "typesafe"
11677
- ? typesafe()
11678
- : cfg.provider === "openrouter"
11679
- ? openrouter()
11680
- : (typesafe() ?? openrouter());
11681
- if (transport) return transport;
11738
+ export const resolveProviders = (cfg: Cfg): DecisionTarget[] => {
11739
+ if (cfg.provider !== "auto") {
11740
+ return [{ provider: cfg.provider, model: modelFor(cfg.provider, cfg) }];
11741
+ }
11742
+ const targets = AUTO_CREDENTIALS.filter(([, groups]) =>
11743
+ groups.every((keys) => keys.some((key) => usable(secret(key)))),
11744
+ ).map(([provider]) => ({ provider, model: modelFor(provider, cfg) }));
11745
+ if (targets.length > 0) return targets;
11682
11746
  throw new Error(
11683
- cfg.provider === "typesafe"
11684
- ? "no TYPESAFE_API_KEY"
11685
- : cfg.provider === "openrouter"
11686
- ? "no OPENROUTER_API_KEY"
11687
- : "no TYPESAFE_API_KEY or OPENROUTER_API_KEY",
11747
+ "no TYPESAFE_API_KEY, OPENROUTER_API_KEY, or CLOUDFLARE_API_TOKEN with CLOUDFLARE_ACCOUNT_ID",
11688
11748
  );
11689
11749
  };
11690
11750
 
11751
+ /** The first provider \`resolveProviders\` would try. */
11752
+ export const resolveProvider = (cfg: Cfg): DecisionTarget => resolveProviders(cfg)[0]!;
11753
+
11754
+ /** The server refused the provider for a deployment setting and sent nothing. */
11755
+ export class ProviderUnavailableError extends Error {}
11756
+
11691
11757
  const SLACK_API = "https://slack.com/api";
11692
11758
  // The engine's acceptance reactions (src/slack/reaction-shortcode.ts), by config key.
11693
11759
  const ACCEPTANCE_REACTIONS: ReadonlyArray<[string, string]> = [
@@ -11775,7 +11841,7 @@ export const settleMutes = async (): Promise<void> => {
11775
11841
  await Promise.allSettled([...pendingMutes]);
11776
11842
  };
11777
11843
 
11778
- export type JevVerdict = {
11844
+ export type Verdict = {
11779
11845
  reason: string;
11780
11846
  confidence: number | null;
11781
11847
  probabilities: Record<string, number> | null;
@@ -11783,56 +11849,73 @@ export type JevVerdict = {
11783
11849
  };
11784
11850
 
11785
11851
  /**
11786
- * One Jev call: classify the newest message into a reason. Throws on any failure.
11787
- * TypeSafe and OpenRouter share the request body and the \`answers\` response shape.
11852
+ * One decision: classify the newest message into a reason. Throws on any failure.
11853
+ * The server makes the call (\`system-one-decide\`), so every provider returns the
11854
+ * same \`answers\` shape. The time budget is enforced here and on the server.
11788
11855
  */
11789
- export const askJev = async (
11856
+ export const askModel = async (
11857
+ swarm: Swarm,
11790
11858
  input: {
11791
11859
  messages: string[];
11792
11860
  context: string[];
11793
11861
  sender: string;
11794
11862
  leadNames: readonly string[];
11795
11863
  },
11796
- transport: JevTransport,
11864
+ target: DecisionTarget,
11797
11865
  cfg: Cfg,
11798
11866
  outer?: AbortSignal,
11799
- ): Promise<JevVerdict> => {
11800
- const controller = new AbortController();
11801
- const timer = setTimeout(() => controller.abort(), Math.min(cfg.timeoutMs, MAX_TIMEOUT_MS));
11802
- const onOuterAbort = () => controller.abort();
11803
- outer?.addEventListener("abort", onOuterAbort);
11867
+ ): Promise<Verdict> => {
11868
+ const budgetMs = Math.min(cfg.timeoutMs, MAX_TIMEOUT_MS);
11869
+ let timer: ReturnType<typeof setTimeout> | undefined;
11870
+ let onOuterAbort: (() => void) | undefined;
11871
+ const stop = new Promise<never>((_resolve, reject) => {
11872
+ timer = setTimeout(() => reject(new Error(\`timed out after \${budgetMs}ms\`)), budgetMs);
11873
+ onOuterAbort = () => reject(new Error("aborted"));
11874
+ outer?.addEventListener("abort", onOuterAbort);
11875
+ });
11804
11876
  const t0 = Date.now();
11805
11877
  try {
11806
- const res = await fetch(transport.endpoint, {
11807
- method: "POST",
11808
- headers: {
11809
- "Content-Type": "application/json",
11810
- Authorization: \`Bearer \${transport.key}\`,
11878
+ const call = swarm.systemOne_decide({
11879
+ provider: target.provider,
11880
+ ...(target.model ? { model: target.model } : {}),
11881
+ state: {
11882
+ role: "Decide whether Lead, the coordinating AI agent of a team's agent swarm, should answer a new Slack message in a thread it takes part in. Lead is woken by every message in the thread, including ones not meant for it. Answering noise wastes everyone's time; missing a real ask is worse.",
11883
+ lead_names: [...input.leadNames],
11884
+ recent_thread_messages: input.context,
11885
+ new_message: input.messages.join("\\n---\\n"),
11886
+ new_message_sender: input.sender,
11811
11887
  },
11812
- body: JSON.stringify({
11813
- state: {
11814
- role: "Decide whether Lead, the coordinating AI agent of a team's agent swarm, should answer a new Slack message in a thread it takes part in. Lead is woken by every message in the thread, including ones not meant for it. Answering noise wastes everyone's time; missing a real ask is worse.",
11815
- lead_names: input.leadNames,
11816
- recent_thread_messages: input.context,
11817
- new_message: input.messages.join("\\n---\\n"),
11818
- new_message_sender: input.sender,
11888
+ questions: {
11889
+ reason: {
11890
+ type: "choice",
11891
+ instructions:
11892
+ "Which label best describes new_message from Lead's point of view? Use recent_thread_messages to tell who it is talking to. Lines starting with [Agent] are swarm bots.",
11893
+ criteria: REASONS,
11819
11894
  },
11820
- model: transport.model,
11821
- questions: {
11822
- reason: {
11823
- type: "choice",
11824
- instructions:
11825
- "Which label best describes new_message from Lead's point of view? Use recent_thread_messages to tell who it is talking to. Lines starting with [Agent] are swarm bots.",
11826
- criteria: REASONS,
11827
- },
11828
- },
11829
- }),
11830
- signal: controller.signal,
11895
+ },
11896
+ timeoutMs: Math.max(budgetMs, MIN_DECIDE_TIMEOUT_MS),
11897
+ maxRetries: 0,
11831
11898
  });
11832
- if (!res.ok) throw new Error(\`\${transport.provider} \${res.status}\`);
11833
- const body = (await res.json()) as {
11899
+ // After a timeout nobody awaits the call; its late failure must not go unhandled.
11900
+ call.catch(() => {});
11901
+ const res = (await Promise.race([call, stop])) as {
11902
+ success?: boolean;
11903
+ status?: number;
11904
+ data?: unknown;
11905
+ } | null;
11906
+ const body = (res?.data ?? res) as {
11907
+ success?: boolean;
11908
+ message?: unknown;
11909
+ error?: unknown;
11910
+ providerUnavailable?: unknown;
11834
11911
  answers?: { reason?: { choice?: unknown; confidence?: unknown; probabilities?: unknown } };
11835
11912
  } | null;
11913
+ if (res?.success === false || body?.success === false) {
11914
+ const why = \`\${target.provider}: \${String(body?.message ?? body?.error ?? \`HTTP \${res?.status}\`).slice(0, 200)}\`;
11915
+ throw body?.providerUnavailable === true
11916
+ ? new ProviderUnavailableError(why)
11917
+ : new Error(why);
11918
+ }
11836
11919
  const answer = body?.answers?.reason;
11837
11920
  const reason = typeof answer?.choice === "string" ? answer.choice : "";
11838
11921
  if (!(reason in REASONS)) throw new Error(\`unexpected choice: \${String(reason).slice(0, 60)}\`);
@@ -11844,12 +11927,34 @@ export const askJev = async (
11844
11927
  return { reason, confidence, probabilities, latencyMs: Date.now() - t0 };
11845
11928
  } finally {
11846
11929
  clearTimeout(timer);
11847
- outer?.removeEventListener("abort", onOuterAbort);
11930
+ if (onOuterAbort) outer?.removeEventListener("abort", onOuterAbort);
11848
11931
  }
11849
11932
  };
11850
11933
 
11934
+ /**
11935
+ * \`askModel\` on each target in turn, moving on only when the server refused the
11936
+ * provider for a deployment setting (no request was sent). Any other failure, or
11937
+ * a refusal of the last target, throws.
11938
+ */
11939
+ export const askFirstAvailable = async (
11940
+ swarm: Swarm,
11941
+ input: Parameters<typeof askModel>[1],
11942
+ targets: readonly DecisionTarget[],
11943
+ cfg: Cfg,
11944
+ outer?: AbortSignal,
11945
+ ): Promise<{ verdict: Verdict; target: DecisionTarget }> => {
11946
+ for (const [i, target] of targets.entries()) {
11947
+ try {
11948
+ return { verdict: await askModel(swarm, input, target, cfg, outer), target };
11949
+ } catch (err) {
11950
+ if (!(err instanceof ProviderUnavailableError) || i === targets.length - 1) throw err;
11951
+ }
11952
+ }
11953
+ throw new Error("no decision provider");
11954
+ };
11955
+
11851
11956
  /** Probability that the message needs no reply: the summed mass of the skip labels. */
11852
- export const skipProbability = (verdict: JevVerdict): number | null => {
11957
+ export const skipProbability = (verdict: Verdict): number | null => {
11853
11958
  if (verdict.probabilities) {
11854
11959
  let sum = 0;
11855
11960
  for (const [label, p] of Object.entries(verdict.probabilities)) {
@@ -11860,9 +11965,9 @@ export const skipProbability = (verdict: JevVerdict): number | null => {
11860
11965
  return verdict.confidence;
11861
11966
  };
11862
11967
 
11863
- /** Map a Jev verdict to the typed decision. An unsure skip verdict replies. */
11968
+ /** Map a verdict to the typed decision. An unsure skip verdict replies. */
11864
11969
  export const decide = (
11865
- verdict: JevVerdict,
11970
+ verdict: Verdict,
11866
11971
  minConfidence: number,
11867
11972
  ): { reply: boolean; reason: Reason; skipP: number | null; lowConfidence: boolean } => {
11868
11973
  const reason = verdict.reason as Reason;
@@ -11959,7 +12064,7 @@ const extension: SwarmExtension = (api) => {
11959
12064
  return;
11960
12065
  }
11961
12066
 
11962
- const transport = resolveTransport(cfg);
12067
+ const targets = resolveProviders(cfg);
11963
12068
 
11964
12069
  const sender =
11965
12070
  opts.slackUserId && cfg.botSlackUserIds.includes(opts.slackUserId)
@@ -11968,14 +12073,15 @@ const extension: SwarmExtension = (api) => {
11968
12073
  ? "human (known teammate)"
11969
12074
  : "unknown (not a known teammate; may be a bot or an external person)";
11970
12075
 
11971
- const verdict = await askJev(
12076
+ const { verdict, target } = await askFirstAvailable(
12077
+ ctx.swarm,
11972
12078
  {
11973
12079
  messages,
11974
12080
  context: context.slice(-cfg.contextMessages).map((m) => m.slice(0, 500)),
11975
12081
  sender,
11976
12082
  leadNames: cfg.leadAliases,
11977
12083
  },
11978
- transport,
12084
+ targets,
11979
12085
  cfg,
11980
12086
  ctx.signal,
11981
12087
  );
@@ -11994,7 +12100,7 @@ const extension: SwarmExtension = (api) => {
11994
12100
  skipP: decision.skipP,
11995
12101
  lowConfidence: decision.lowConfidence,
11996
12102
  latencyMs: verdict.latencyMs,
11997
- provider: transport.provider,
12103
+ provider: target.provider,
11998
12104
  sender,
11999
12105
  preview,
12000
12106
  },
@@ -12034,7 +12140,7 @@ const extension: SwarmExtension = (api) => {
12034
12140
  export default extension;
12035
12141
  `
12036
12142
  },
12037
- readme: '# slack-reply-gate\n\nLead is woken by every human message in a Slack thread the swarm takes part in, including "lol", an emoji, or two people chatting with each other. This extension asks Jev, [TypeSafe](https://typesafe.ai)\'s decision model, whether Lead should answer a new thread message, and drops the wake-up when it should not.\n\nInstall config:\n\n```json\n{ "mode": "shadow" }\n```\n\nStart in `shadow`, read the decisions in KV for a few days, then switch to `enforce`.\n\n## Modes\n\n| `mode` | Effect |\n|---|---|\n| `off` | Never evaluates. |\n| `shadow` (default) | Asks Jev and records the verdict. Never blocks. |\n| `enforce` | Asks Jev, records the verdict, and blocks the Lead task when the answer is no. The message gets :mute: instead: no Lead task, no reply, no outcome card. |\n\n## What it gates\n\nHook: `pre.task.create`, for tasks with origin `slack` whose description carries the thread buffer\'s header `[Thread follow-up — N message(s) buffered]`. Those are the non-mention thread replies that `src/slack/thread-buffer.ts` batches into a Lead task. A direct @mention of the bot creates a task without that header and is never gated.\n\nThe header counts only at the start of the description or right after the closing `</thread_context>`. Thread context quotes old messages verbatim, and an older Lead reply can quote the header itself, so a header inside the context is ignored. Only the text after the real header is treated as new.\n\n## Decision\n\n1. A follow-up with downloaded files (status `draft`) passes without a Jev call.\n2. **@mention pass-through.** A new message that @mentions Lead passes without a Jev call. A mention is the bot mention the engine rewrites to `<@U…> (that\'s you)`, a resolved `<@U…|lead>`, or a literal `@lead` (any name in `leadAliases`). Only the new messages are scanned, never the thread context, so an old mention earlier in the thread does not pass later banter.\n3. Otherwise one Jev call (one `choice` question, see [Jev transport](#jev-transport)) with the new message(s), the sender kind, and the last `contextMessages` thread messages. Labels: `direct_ask`, `followup_to_lead`, `instruction_to_lead`, `reaction_only`, `banter_between_others`, `not_addressed_to_lead`, `other`.\n4. The answer is no only when the top label is `reaction_only`, `banter_between_others`, or `not_addressed_to_lead`, and the summed probability of those three labels is at least `minConfidence`. A split between two skip labels is still a skip. An unsure verdict replies.\n\nSeveral buffered messages are judged together. One mention in any of them passes the batch.\n\n## Jev transport\n\nJev is reachable two ways. Both take the same request and return the same answer, so the decision is identical whichever one runs.\n\n| `provider` | Endpoint | Key | Model |\n|---|---|---|---|\n| `typesafe` | `POST https://api.typesafe.ai/v1/systemone` | `TYPESAFE_API_KEY` (or `apiKey`) | `model`, default `jev-latest` |\n| `openrouter` | `POST https://openrouter.ai/api/alpha/decisions` | `OPENROUTER_API_KEY` | `openrouterModel`, default `typesafe/jev-1.13` |\n| `auto` (default) | TypeSafe when its key is set, else OpenRouter | | |\n\nUnder `auto`, `TYPESAFE_API_KEY` wins when both keys are set. With neither key, every message fails open and the KV row names both secrets. Each decision row records the `provider` that answered.\n\n## Fail open\n\nEvery error lets the task through: a missing key, a TypeSafe HTTP error, a network error, a timeout, a malformed answer, or an unknown label. The handler never throws, so an outage cannot auto-disable the extension. Each failure is recorded as `fail-open` in KV and logged as a warning.\n\n## :mute: on drop (enforce)\n\nNo swarm tool or extension `ctx` method adds a Slack reaction, so the hook calls the Slack Web API with the bot token. It needs the `reactions:write` scope, which the Slack app already uses for its acceptance reactions.\n\n1. `reactions.remove` for each acceptance reaction the engine may have put on the triggering message: `eyes`, `heavy_plus_sign`, `zap`, `speech_balloon`, or their `SLACK_REACTION_ACCEPTED`, `SLACK_REACTION_BUFFERED`, `SLACK_REACTION_NOW`, and `SLACK_REACTION_STEERED` overrides. Slack only removes the bot\'s own reaction, so a human\'s :eyes: stays.\n2. `reactions.add` with `muteReaction` (default `mute`). `already_reacted` counts as success. Skipped when `muteReaction` is `""`.\n\nThe block returns first and the Slack calls run after it. A slow or failing Slack call never delays the drop or turns it back into a task. Set `muteReaction` to `""` for a true no-op: step 1 still clears the bot\'s acceptance reaction(s), and nothing is added.\n\n## Config\n\n| Key | Default | Notes |\n|---|---|---|\n| `mode` | `shadow` | `off`, `shadow`, or `enforce` |\n| `timeoutMs` | `3000` | Jev call timeout, capped at 4000 (the dispatcher cancels a handler at 5 s) |\n| `provider` | `auto` | `auto`, `typesafe`, or `openrouter`. See [Jev transport](#jev-transport) |\n| `model` | `jev-latest` | Jev model on TypeSafe |\n| `openrouterModel` | `typesafe/jev-1.13` | Jev model on OpenRouter |\n| `minConfidence` | `0.55` | Minimum summed skip probability to skip |\n| `contextMessages` | `6` | Thread messages sent to Jev, 0 to 20 |\n| `leadAliases` | `["lead"]` | A literal `@alias` in a new message counts as a mention |\n| `botSlackUserIds` | `[]` | Slack user ids labelled `bot` as the sender for Jev |\n| `apiKey` | unset | Explicit TypeSafe key. Prefer the `TYPESAFE_API_KEY` secret below |\n| `muteReaction` | `mute` | Reaction for a dropped message; `""` only removes the acceptance reaction(s) |\n| `slackTimeoutMs` | `5000` | Timeout per Slack call |\n\n## Required secrets\n\nAll are read from the API process environment at call time. Set them as global `swarm_config` secrets: the API process loads global rows into `process.env` at boot and on config reload.\n\n- `TYPESAFE_API_KEY` or `OPENROUTER_API_KEY`: one is required for any evaluation. Under `auto`, TypeSafe wins when both are set. Without either, every message fails open.\n- `SLACK_BOT_TOKEN`: the Slack app\'s bot token. A swarm with Slack connected already has it. Without it, `enforce` still drops the message but cannot touch its reactions, and logs a `mute-error`.\n\nNo key is written to config, KV, logs, or a description.\n\n## KV namespace `ext:slack-reply-gate`\n\n- `d:<epochMs>:<channel>_<threadTs>`: one row per decision, with `action` (`pass`, `shadow-skip`, `skipped`, `fail-open`), `provider`, `reason`, `confidence`, `skipP`, `latencyMs`, `sender`, and a 300-character `preview`.\n- `m:<epochMs>:<channel>_<threadTs>`: one row per mute attempt, with `action` (`muted`, `unreacted` when `muteReaction` is `""`, `mute-error`), `removed`, `added`, and `errors`.\n- `last`: the latest decision. Mute rows never overwrite it.\n- Counters: `total`, `mention`, `files`, `reply`, `shadow-skip`, `skipped`, `error`, `muted`, `unreacted`, `mute-error`, and `reason:<label>`.\n\n## Known limits\n\n- When Lead already has an active task in the thread, the buffer steers that task instead of creating one, so `pre.task.create` does not fire and the gate does not run.\n- A blocked flush makes the thread buffer log a `TaskCreationBlockedError`. Nothing is lost: the next follow-up\'s `<thread_context>` carries the earlier messages.\n- A flush of several messages mutes only the triggering (last) message. The others keep their acceptance reaction.\n'
12143
+ readme: '# slack-reply-gate\n\nLead is woken by every human message in a Slack thread the swarm takes part in, including "lol", an emoji, or two people chatting with each other. This extension asks a SystemOne decision model, Jev ([TypeSafe](https://typesafe.ai)) or Clef (Cloudflare Workers AI), whether Lead should answer a new thread message, and drops the wake-up when it should not.\n\nInstall config:\n\n```json\n{ "mode": "shadow" }\n```\n\nStart in `shadow`, read the decisions in KV for a few days, then switch to `enforce`.\n\n## Modes\n\n| `mode` | Effect |\n|---|---|\n| `off` | Never evaluates. |\n| `shadow` (default) | Asks the model and records the verdict. Never blocks. |\n| `enforce` | Asks the model, records the verdict, and blocks the Lead task when the answer is no. The message gets :mute: instead: no Lead task, no reply, no outcome card. |\n\n## What it gates\n\nHook: `pre.task.create`, for tasks with origin `slack` whose description carries the thread buffer\'s header `[Thread follow-up — N message(s) buffered]`. Those are the non-mention thread replies that `src/slack/thread-buffer.ts` batches into a Lead task. A direct @mention of the bot creates a task without that header and is never gated.\n\nThe header counts only at the start of the description or right after the closing `</thread_context>`. Thread context quotes old messages verbatim, and an older Lead reply can quote the header itself, so a header inside the context is ignored. Only the text after the real header is treated as new.\n\n## Decision\n\n1. A follow-up with downloaded files (status `draft`) passes without a decision call.\n2. **@mention pass-through.** A new message that @mentions Lead passes without a decision call. A mention is the bot mention the engine rewrites to `<@U…> (that\'s you)`, a resolved `<@U…|lead>`, or a literal `@lead` (any name in `leadAliases`). Only the new messages are scanned, never the thread context, so an old mention earlier in the thread does not pass later banter.\n3. Otherwise one decision call (one `choice` question, see [Provider](#provider)) with the new message(s), the sender kind, and the last `contextMessages` thread messages. Labels: `direct_ask`, `followup_to_lead`, `instruction_to_lead`, `reaction_only`, `banter_between_others`, `not_addressed_to_lead`, `other`.\n4. The answer is no only when the top label is `reaction_only`, `banter_between_others`, or `not_addressed_to_lead`, and the summed probability of those three labels is at least `minConfidence`. A split between two skip labels is still a skip. An unsure verdict replies.\n\nSeveral buffered messages are judged together. One mention in any of them passes the batch.\n\n## Provider\n\nThe decision is made on the API server by the `system-one-decide` tool (`ctx.swarm.systemOne_decide`), the same path a `system-one-decision` workflow node takes. The server reads the provider\'s credential, sends the request, and validates the answer, so every SystemOne host works and no key enters the hook.\n\n| `provider` | Model | Credential |\n|---|---|---|\n| `typesafe` | Jev, `model` (default `jev-latest`) | `TYPESAFE_API_KEY` |\n| `openrouter` | Jev, `openrouterModel` (default `typesafe/jev-1.13`) | `OPENROUTER_API_KEY` |\n| `cloudflare` | Clef, `cloudflareModel` (default `clef`, or `clef-flash`) | `CLOUDFLARE_API_TOKEN` (or `CLOUDFLARE_API_KEY`) and `CLOUDFLARE_ACCOUNT_ID` |\n| `laya`, `openai` | The host\'s default model | See the workflows guide |\n| `auto` (default) | The first of typesafe, openrouter, cloudflare whose credential is set. A provider the server refuses for a deployment setting (openrouter when `OPENROUTER_BASE_URL` points at a gateway) is skipped for the next one | |\n\nWith no credential for `auto`, every message fails open and the KV row names the secrets. Each decision row records the `provider` that answered. `minConfidence` was tuned on Jev; check Clef\'s verdicts in `shadow` before you enforce on it.\n\n## Fail open\n\nEvery error lets the task through: a missing key, a provider error relayed by the server, a network error, a timeout, a malformed answer, or an unknown label. The handler never throws, so an outage cannot auto-disable the extension. Each failure is recorded as `fail-open` in KV and logged as a warning.\n\n## :mute: on drop (enforce)\n\nNo swarm tool or extension `ctx` method adds a Slack reaction, so the hook calls the Slack Web API with the bot token. It needs the `reactions:write` scope, which the Slack app already uses for its acceptance reactions.\n\n1. `reactions.remove` for each acceptance reaction the engine may have put on the triggering message: `eyes`, `heavy_plus_sign`, `zap`, `speech_balloon`, or their `SLACK_REACTION_ACCEPTED`, `SLACK_REACTION_BUFFERED`, `SLACK_REACTION_NOW`, and `SLACK_REACTION_STEERED` overrides. Slack only removes the bot\'s own reaction, so a human\'s :eyes: stays.\n2. `reactions.add` with `muteReaction` (default `mute`). `already_reacted` counts as success. Skipped when `muteReaction` is `""`.\n\nThe block returns first and the Slack calls run after it. A slow or failing Slack call never delays the drop or turns it back into a task. Set `muteReaction` to `""` for a true no-op: step 1 still clears the bot\'s acceptance reaction(s), and nothing is added.\n\n## Config\n\n| Key | Default | Notes |\n|---|---|---|\n| `mode` | `shadow` | `off`, `shadow`, or `enforce` |\n| `timeoutMs` | `3000` | Decision call timeout, capped at 4000 (the dispatcher cancels a handler at 5 s) |\n| `provider` | `auto` | `auto`, `typesafe`, `openrouter`, `cloudflare`, `laya`, or `openai`. See [Provider](#provider) |\n| `model` | `jev-latest` | Jev model on TypeSafe |\n| `openrouterModel` | `typesafe/jev-1.13` | Jev model on OpenRouter |\n| `cloudflareModel` | `clef` | Clef model on Cloudflare Workers AI |\n| `minConfidence` | `0.55` | Minimum summed skip probability to skip |\n| `contextMessages` | `6` | Thread messages sent to the model, 0 to 20 |\n| `leadAliases` | `["lead"]` | A literal `@alias` in a new message counts as a mention |\n| `botSlackUserIds` | `[]` | Slack user ids labelled `bot` as the sender for the model |\n| `muteReaction` | `mute` | Reaction for a dropped message; `""` only removes the acceptance reaction(s) |\n| `slackTimeoutMs` | `5000` | Timeout per Slack call |\n\n## Required secrets\n\nSet them as global `swarm_config` values. The server reads the provider credential from the global row when it makes the decision. `auto` and the Slack token read the API process environment, which loads global rows at boot and on config reload.\n\n- One provider credential from the [Provider](#provider) table is required for any evaluation. Without one, every message fails open. The `apiKey` config field of earlier versions is gone: the hook never holds a key, so store it as the global `TYPESAFE_API_KEY` secret.\n- `SLACK_BOT_TOKEN`: the Slack app\'s bot token. A swarm with Slack connected already has it. Without it, `enforce` still drops the message but cannot touch its reactions, and logs a `mute-error`.\n\nNo key is written to config, KV, logs, or a description.\n\n## KV namespace `ext:slack-reply-gate`\n\n- `d:<epochMs>:<channel>_<threadTs>`: one row per decision, with `action` (`pass`, `shadow-skip`, `skipped`, `fail-open`), `provider`, `reason`, `confidence`, `skipP`, `latencyMs`, `sender`, and a 300-character `preview`.\n- `m:<epochMs>:<channel>_<threadTs>`: one row per mute attempt, with `action` (`muted`, `unreacted` when `muteReaction` is `""`, `mute-error`), `removed`, `added`, and `errors`.\n- `last`: the latest decision. Mute rows never overwrite it.\n- Counters: `total`, `mention`, `files`, `reply`, `shadow-skip`, `skipped`, `error`, `muted`, `unreacted`, `mute-error`, and `reason:<label>`.\n\n## Known limits\n\n- When Lead already has an active task in the thread, the buffer steers that task instead of creating one, so `pre.task.create` does not fire and the gate does not run.\n- A blocked flush makes the thread buffer log a `TaskCreationBlockedError`. Nothing is lost: the next follow-up\'s `<thread_context>` carries the earlier messages.\n- A flush of several messages mutes only the triggering (last) message. The others keep their acceptance reaction.\n'
12038
12144
  },
12039
12145
  "task-digest": {
12040
12146
  manifestFile: "manifest.yaml",
@@ -13220,6 +13326,17 @@ async function handleFleetBenchmark(req, res, pathSegments, queryParams) {
13220
13326
  // src/http/fs.ts
13221
13327
  init_zod();
13222
13328
  init_db3();
13329
+
13330
+ // src/be/db/tasks/draft-text.ts
13331
+ init_runtime();
13332
+ async function appendDraftTaskText(taskId, block) {
13333
+ const result = await getDbClient2().run("UPDATE agent_tasks SET task = task || ?, lastUpdatedAt = ? WHERE id = ? AND status = 'draft'", [`
13334
+
13335
+ ${block}`, new Date().toISOString(), taskId]);
13336
+ return result.changes > 0;
13337
+ }
13338
+
13339
+ // src/http/fs.ts
13223
13340
  init_rbac();
13224
13341
  init_types();
13225
13342
 
@@ -13343,7 +13460,7 @@ var uploadTaskFileRoute = route({
13343
13460
  pattern: ["api", "fs", "tasks", null, "files"],
13344
13461
  session: { taskParam: "taskId" },
13345
13462
  summary: "Upload a binary task file attachment",
13346
- description: "Accepts a raw binary request body. Pass the display/path name as the `name` query parameter.",
13463
+ description: "Accepts a raw binary request body. Pass the display/path name as the `name` query parameter. An `audio/*` file uploaded to a `draft` task gets a `[File: …]` line and its transcript (or why there is none) appended to the task text before the response; a failed transcription never fails the upload.",
13347
13464
  tags: ["FS"],
13348
13465
  params: taskParams,
13349
13466
  query: uploadQuery,
@@ -13504,7 +13621,7 @@ async function handleFs(req, res, pathSegments, queryParams, myAgentId) {
13504
13621
  return notFound(res, "Task not found");
13505
13622
  if (!await canMutateTask(task, myAgentId, req))
13506
13623
  return forbidden(res);
13507
- return sendUpload(req, res, parsed.params.taskId, parsed.query, myAgentId ?? null);
13624
+ return sendUpload(req, res, parsed.params.taskId, parsed.query, myAgentId ?? null, task.status === "draft");
13508
13625
  }
13509
13626
  if (listTaskFilesRoute.match(req.method, pathSegments)) {
13510
13627
  const parsed = await listTaskFilesRoute.parse(req, res, pathSegments, queryParams);
@@ -13531,7 +13648,7 @@ async function handleFs(req, res, pathSegments, queryParams, myAgentId) {
13531
13648
  }
13532
13649
  return false;
13533
13650
  }
13534
- async function sendUpload(req, res, taskId, query, agentId) {
13651
+ async function sendUpload(req, res, taskId, query, agentId, isDraft) {
13535
13652
  const body = await readRawBody(req, MAX_TASK_ATTACHMENT_BYTES);
13536
13653
  if (body === BODY_TOO_LARGE) {
13537
13654
  jsonError(res, `Payload too large (max ${MAX_TASK_ATTACHMENT_BYTES} bytes)`, 413);
@@ -13572,9 +13689,27 @@ async function sendUpload(req, res, taskId, query, agentId) {
13572
13689
  isPrimary: query.isPrimary === "true",
13573
13690
  createdBy: auth?.kind === "user" ? auth.userId : undefined
13574
13691
  });
13692
+ const mimeType = contentType.split(";")[0].trim().toLowerCase();
13693
+ if (isDraft && mimeType.startsWith("audio/")) {
13694
+ await appendAudioTranscript(taskId, query.name, mimeType, body);
13695
+ }
13575
13696
  uploadTaskFileRoute.respond(res, 201, attachment);
13576
13697
  return true;
13577
13698
  }
13699
+ async function appendAudioTranscript(taskId, name, mimeType, body) {
13700
+ try {
13701
+ const notes = await transcribeAudioNotes([{ key: name, name, data: new Blob([body], { type: mimeType }), mimeType }], "[fs]");
13702
+ const note = notes.get(name);
13703
+ if (!note)
13704
+ return;
13705
+ if (!await appendDraftTaskText(taskId, `[File: ${name} (${mimeType})]
13706
+ ${note}`)) {
13707
+ console.warn(`[fs] task ${taskId} left draft before the transcript of ${name} was added`);
13708
+ }
13709
+ } catch (error) {
13710
+ console.warn(scrubSecrets(`[fs] could not add the transcript of ${name} to task ${taskId}: ${error instanceof Error ? error.message : String(error)}`));
13711
+ }
13712
+ }
13578
13713
  async function sendDownload(res, attachment) {
13579
13714
  const provider = getFileStorageProvider2();
13580
13715
  if (backingProviderId(attachment) !== provider.id) {
@@ -13634,23 +13769,6 @@ async function sendDelete(res, attachment) {
13634
13769
  res.end();
13635
13770
  return true;
13636
13771
  }
13637
- function scopeFromAttachment(attachment) {
13638
- const storedKey = attachment.providerKey ?? attachment.path ?? "";
13639
- return {
13640
- taskId: attachment.taskId,
13641
- name: attachment.name,
13642
- key: storedKey.trim() ? storedKey : undefined,
13643
- orgId: attachment.orgId,
13644
- driveId: attachment.driveId
13645
- };
13646
- }
13647
- function backingProviderId(attachment) {
13648
- if (attachment.kind === "agent-fs")
13649
- return attachment.providerId ?? "agent-fs";
13650
- if (attachment.providerId === "local-fs")
13651
- return "local-fs";
13652
- return null;
13653
- }
13654
13772
  async function findAttachment(req, myAgentId, taskId, attachmentId, res) {
13655
13773
  if (!await getTaskById2(taskId)) {
13656
13774
  notFound(res, "Task not found");
@@ -16408,6 +16526,29 @@ init_db3();
16408
16526
  init_env_flag();
16409
16527
  init_request_auth_context();
16410
16528
  init_task_tool_manifest();
16529
+
16530
+ // src/http/mcp-body.ts
16531
+ var MCP_BODY_TOO_LARGE = Symbol("mcp-body-too-large");
16532
+ async function readMcpBody(req, res) {
16533
+ try {
16534
+ return await parseBody(req, MCP_MAX_REQUEST_BODY_BYTES);
16535
+ } catch (error) {
16536
+ if (!(error instanceof RequestBodyTooLargeError))
16537
+ throw error;
16538
+ res.writeHead(413, { "Content-Type": "application/json", Connection: "close" });
16539
+ res.end(JSON.stringify({
16540
+ jsonrpc: "2.0",
16541
+ error: {
16542
+ code: -32000,
16543
+ message: `Payload Too Large: Request body must not exceed ${MCP_MAX_REQUEST_BODY_BYTES} bytes`
16544
+ },
16545
+ id: null
16546
+ }));
16547
+ return MCP_BODY_TOO_LARGE;
16548
+ }
16549
+ }
16550
+
16551
+ // src/http/mcp.ts
16411
16552
  var DEFAULT_MCP_TRANSPORT_IDLE_TIMEOUT_MS = 2 * 60 * 60 * 1000;
16412
16553
  function respondNoMcpSession(res, sessionId) {
16413
16554
  const notFound = Boolean(sessionId);
@@ -16507,17 +16648,16 @@ async function handleMcp(req, res, transports, sessionActivity = {}, sessionAgen
16507
16648
  if (agentMismatch)
16508
16649
  return true;
16509
16650
  if (req.method === "POST") {
16510
- const chunks = [];
16511
- for await (const chunk of req) {
16512
- chunks.push(chunk);
16513
- }
16514
- const body = JSON.parse(Buffer.concat(chunks).toString());
16515
- if (!sessionId && body.method === "server/discover") {
16651
+ const body = await readMcpBody(req, res);
16652
+ if (body === MCP_BODY_TOO_LARGE)
16653
+ return true;
16654
+ const rpc = body;
16655
+ if (!sessionId && rpc?.method === "server/discover") {
16516
16656
  res.writeHead(200, { "Content-Type": "application/json" });
16517
16657
  res.end(JSON.stringify({
16518
16658
  jsonrpc: "2.0",
16519
16659
  error: { code: -32601, message: "Method not found" },
16520
- id: body.id ?? null
16660
+ id: rpc.id ?? null
16521
16661
  }));
16522
16662
  return true;
16523
16663
  }
@@ -17854,7 +17994,8 @@ function createUserServer(user) {
17854
17994
  }, {
17855
17995
  capabilities: {
17856
17996
  logging: {}
17857
- }
17997
+ },
17998
+ maxToolInputElements: MCP_MAX_TOOL_INPUT_ELEMENTS
17858
17999
  });
17859
18000
  const registerTool = createToolRegistrar(server);
17860
18001
  const sendTaskConfig = {
@@ -17972,11 +18113,9 @@ async function handleMcpUser(req, res, transports, sessionUsers, sessionActivity
17972
18113
  return unauthorized2(res);
17973
18114
  }
17974
18115
  if (req.method === "POST") {
17975
- const chunks = [];
17976
- for await (const chunk of req) {
17977
- chunks.push(chunk);
17978
- }
17979
- const body = JSON.parse(Buffer.concat(chunks).toString());
18116
+ const body = await readMcpBody(req, res);
18117
+ if (body === MCP_BODY_TOO_LARGE)
18118
+ return true;
17980
18119
  let transport;
17981
18120
  if (sessionId && transports[sessionId]) {
17982
18121
  transport = transports[sessionId];
@@ -18118,6 +18257,9 @@ async function hasRetrievalForTask(taskId, memoryId) {
18118
18257
  return row != null;
18119
18258
  }
18120
18259
 
18260
+ // src/http/memory.ts
18261
+ init_search_candidates();
18262
+
18121
18263
  // src/be/memory/usefulness-stats.ts
18122
18264
  init_db3();
18123
18265
  async function getUsefulnessStats(options = {}) {
@@ -18958,7 +19100,7 @@ async function handleMemory(req, res, pathSegments, callerAgentId) {
18958
19100
  const store = getMemoryStore2();
18959
19101
  const queryEmbedding = await provider.embed(query);
18960
19102
  const candidateLimit = Math.min(limit, 20) * CANDIDATE_SET_MULTIPLIER;
18961
- const candidates = await store.search(queryEmbedding ?? new Float32Array(0), myAgentId, {
19103
+ const candidates = await searchMemoryCandidates(store, queryEmbedding ?? new Float32Array(0), myAgentId, {
18962
19104
  scope,
18963
19105
  limit: candidateLimit,
18964
19106
  source,
@@ -19039,7 +19181,7 @@ async function handleMemory(req, res, pathSegments, callerAgentId) {
19039
19181
  const provider = getEmbeddingProvider2();
19040
19182
  const queryEmbedding = await provider.embed(query.trim());
19041
19183
  const candidateLimit = Math.min(4096, Math.max(offset + pageLimit, pageLimit) * CANDIDATE_SET_MULTIPLIER);
19042
- let candidates = await store.search(queryEmbedding ?? new Float32Array(0), viewer.agentId, {
19184
+ let candidates = await searchMemoryCandidates(store, queryEmbedding ?? new Float32Array(0), viewer.agentId, {
19043
19185
  scope,
19044
19186
  limit: candidateLimit,
19045
19187
  isLead: viewer.seesAll,
@@ -21675,6 +21817,7 @@ async function buildSignals(state) {
21675
21817
  slack: integrations.slack === "configured",
21676
21818
  github: Boolean(process.env.GITHUB_TOKEN?.trim()) || integrations.github === "verified",
21677
21819
  gitlab: Boolean(process.env.GITLAB_TOKEN?.trim()),
21820
+ bitbucket: Boolean(process.env.BITBUCKET_TOKEN?.trim()),
21678
21821
  linear: integrations.linear === "verified",
21679
21822
  jira: integrations.jira === "verified"
21680
21823
  },
@@ -21974,6 +22117,35 @@ route({
21974
22117
  auth: { apiKey: false }
21975
22118
  });
21976
22119
  var PROXY_PREFIX = "/@swarm/api/";
22120
+ var PAGE_ID_REQUEST_HEADER = "x-swarm-page-id";
22121
+ var PAGE_PATH_RE = /^\/p\/([^/.]+)/;
22122
+ function requestedPageId(req) {
22123
+ const raw = req.headers[PAGE_ID_REQUEST_HEADER];
22124
+ const fromHeader = Array.isArray(raw) ? raw[0] : raw;
22125
+ if (fromHeader !== undefined)
22126
+ return isCookieSafePageId(fromHeader) ? fromHeader : null;
22127
+ const referer = req.headers.referer;
22128
+ if (!referer)
22129
+ return;
22130
+ let refererUrl;
22131
+ try {
22132
+ refererUrl = new URL(referer);
22133
+ } catch {
22134
+ return;
22135
+ }
22136
+ if (refererUrl.host !== req.headers.host)
22137
+ return;
22138
+ const match = PAGE_PATH_RE.exec(refererUrl.pathname);
22139
+ if (!match)
22140
+ return;
22141
+ let fromReferer;
22142
+ try {
22143
+ fromReferer = decodeURIComponent(match[1]);
22144
+ } catch {
22145
+ return;
22146
+ }
22147
+ return isCookieSafePageId(fromReferer) ? fromReferer : undefined;
22148
+ }
21977
22149
  function isSafeProxySuffix(suffix) {
21978
22150
  if (suffix.length === 0)
21979
22151
  return true;
@@ -22012,11 +22184,13 @@ async function handlePageProxy(req, res) {
22012
22184
  jsonError(res, "not found", 404);
22013
22185
  return true;
22014
22186
  }
22015
- const payload = await extractAndVerifyCookie(req);
22016
- if (!payload) {
22187
+ const claimedPageId = requestedPageId(req);
22188
+ const session = claimedPageId === undefined ? await extractLegacyPageSession(req) : claimedPageId === null ? null : await extractPageSession(req, claimedPageId);
22189
+ if (!session) {
22017
22190
  jsonError(res, "no page session", 401);
22018
22191
  return true;
22019
22192
  }
22193
+ const { payload } = session;
22020
22194
  const page = await getPage2(payload.pageId);
22021
22195
  if (!page) {
22022
22196
  jsonError(res, "page session no longer valid", 401);
@@ -22034,7 +22208,7 @@ async function handlePageProxy(req, res) {
22034
22208
  const headers = {
22035
22209
  Authorization: `Bearer ${apiKey}`,
22036
22210
  "X-Page-Id": page.id,
22037
- "X-Page-Session": parseCookieHeader(req.headers.cookie, "page_session") ?? ""
22211
+ "X-Page-Session": session.token
22038
22212
  };
22039
22213
  const reqContentType = req.headers["content-type"];
22040
22214
  if (reqContentType) {
@@ -22678,7 +22852,7 @@ var updateTaskVcsRoute = route({
22678
22852
  tags: ["Tasks"],
22679
22853
  params: object2({ id: string2() }),
22680
22854
  body: object2({
22681
- vcsProvider: _enum2(["github", "gitlab", "azure-devops"]),
22855
+ vcsProvider: _enum2(["github", "gitlab", "azure-devops", "bitbucket"]),
22682
22856
  vcsRepo: string2(),
22683
22857
  vcsNumber: number2().int().positive(),
22684
22858
  vcsUrl: string2().url()
@@ -24378,12 +24552,12 @@ ${defuseFeedbackTags(note)}
24378
24552
  }
24379
24553
  const auth = getRequestAuth(req);
24380
24554
  const identity = auth?.kind === "user" ? { uid: auth.userId, name: auth.user.name } : auth?.kind === "operator" ? { name: `guest-${randomUUID4().slice(0, 8)}`, op: true } : undefined;
24381
- const cookie = await issuePageSessionCookie(page.id, {
24555
+ const cookies = await pageSessionSetCookies(req, page.id, {
24382
24556
  dev: isDevRequest(req),
24383
24557
  ...identity
24384
24558
  });
24385
24559
  applyLaunchCors(req, res);
24386
- res.setHeader("Set-Cookie", cookie);
24560
+ res.setHeader("Set-Cookie", cookies);
24387
24561
  res.writeHead(204);
24388
24562
  res.end();
24389
24563
  return true;
@@ -24405,10 +24579,15 @@ class SwarmSDK {
24405
24579
  this.channel = async (name) => (await loadRealtime()).channel(name);
24406
24580
 
24407
24581
  const base = '/@swarm/api';
24582
+ // Each page has its own session cookie. Naming the page tells the proxy
24583
+ // which one to read; the cookie's signed payload must still match it.
24584
+ const pathParts = typeof location === 'undefined' ? [] : location.pathname.split('/');
24585
+ const pageId = pathParts[1] === 'p' && pathParts[2] ? pathParts[2].split('.')[0] : '';
24408
24586
  const call = async (method, path, body) => {
24409
- const init = { method };
24587
+ const init = { method, headers: {} };
24588
+ if (pageId) init.headers['X-Swarm-Page-Id'] = pageId;
24410
24589
  if (body !== undefined) {
24411
- init.headers = { 'Content-Type': 'application/json' };
24590
+ init.headers['Content-Type'] = 'application/json';
24412
24591
  init.body = JSON.stringify(body);
24413
24592
  }
24414
24593
  const res = await fetch(base + path, init);
@@ -25593,7 +25772,7 @@ var FEEDBACK_OVERLAY_JS = `
25593
25772
  return fetch('/@swarm/api/pages/' + encodeURIComponent(pageId) + '/feedback', {
25594
25773
  method: 'POST',
25595
25774
  credentials: 'same-origin',
25596
- headers: { 'Content-Type': 'application/json' },
25775
+ headers: { 'Content-Type': 'application/json', 'X-Swarm-Page-Id': pageId },
25597
25776
  body: JSON.stringify(payload),
25598
25777
  }).then(function (res) {
25599
25778
  return res.text().then(function (text) {
@@ -25979,23 +26158,8 @@ function buildCsp() {
25979
26158
  function isAccessible(page, cookiePayload) {
25980
26159
  if (page.authMode === "public")
25981
26160
  return { ok: true };
25982
- if (cookiePayload) {
25983
- if (cookiePayload.pageId === page.id)
25984
- return { ok: true };
25985
- if (page.authMode === "password") {
25986
- return {
25987
- ok: false,
25988
- status: 401,
25989
- reason: "password required",
25990
- needsPassword: true
25991
- };
25992
- }
25993
- return {
25994
- ok: false,
25995
- status: 403,
25996
- reason: "page-session cookie scoped to a different page id"
25997
- };
25998
- }
26161
+ if (cookiePayload?.pageId === page.id)
26162
+ return { ok: true };
25999
26163
  if (page.authMode === "authed") {
26000
26164
  return {
26001
26165
  ok: false,
@@ -26070,9 +26234,9 @@ async function handlePagesPublic(req, res, pathSegments, queryParams) {
26070
26234
  res.end(JSON.stringify({ error: "Page not found" }));
26071
26235
  return true;
26072
26236
  }
26073
- const cookiePayload = await extractAndVerifyCookie(req);
26237
+ const cookiePayload = await extractAndVerifyCookie(req, page.id);
26074
26238
  const access = isAccessible(page, cookiePayload);
26075
- let inlineSetCookie = "";
26239
+ let inlineSetCookie = [];
26076
26240
  if (!access.ok) {
26077
26241
  if (access.needsPassword) {
26078
26242
  const candidate = extractPasswordCandidate(req, queryParams);
@@ -26084,7 +26248,7 @@ async function handlePagesPublic(req, res, pathSegments, queryParams) {
26084
26248
  matched = false;
26085
26249
  }
26086
26250
  if (matched) {
26087
- inlineSetCookie = await issuePageSessionCookie(page.id, {
26251
+ inlineSetCookie = await pageSessionSetCookies(req, page.id, {
26088
26252
  dev: isLocalhostRequest(req)
26089
26253
  });
26090
26254
  } else {
@@ -26111,7 +26275,7 @@ async function handlePagesPublic(req, res, pathSegments, queryParams) {
26111
26275
  }
26112
26276
  if (isJsonRoute) {
26113
26277
  res.setHeader("Cache-Control", "no-store");
26114
- if (inlineSetCookie)
26278
+ if (inlineSetCookie.length > 0)
26115
26279
  res.setHeader("Set-Cookie", inlineSetCookie);
26116
26280
  publicPageJsonRoute.respond(res, 200, {
26117
26281
  id: page.id,
@@ -26132,7 +26296,7 @@ async function handlePagesPublic(req, res, pathSegments, queryParams) {
26132
26296
  "X-Content-Type-Options": "nosniff",
26133
26297
  "Content-Security-Policy": "default-src 'none'; style-src 'unsafe-inline'; sandbox"
26134
26298
  };
26135
- if (inlineSetCookie)
26299
+ if (inlineSetCookie.length > 0)
26136
26300
  headers["Set-Cookie"] = inlineSetCookie;
26137
26301
  res.writeHead(200, headers);
26138
26302
  res.end(page.body);
@@ -26148,15 +26312,17 @@ async function handlePagesPublic(req, res, pathSegments, queryParams) {
26148
26312
  "Content-Security-Policy": buildCsp(),
26149
26313
  "X-Content-Type-Options": "nosniff"
26150
26314
  };
26151
- if (inlineSetCookie)
26315
+ if (inlineSetCookie.length > 0)
26152
26316
  headers["Set-Cookie"] = inlineSetCookie;
26153
26317
  res.writeHead(200, headers);
26154
26318
  res.end(printableJsonPageHtml(page));
26155
26319
  await bumpViewCount(page.id);
26156
26320
  return true;
26157
26321
  }
26158
- const headers = { Location: `${getAppBaseUrl2()}/pages/${page.id}` };
26159
- if (inlineSetCookie)
26322
+ const headers = {
26323
+ Location: `${getAppBaseUrl2()}/pages/${page.id}`
26324
+ };
26325
+ if (inlineSetCookie.length > 0)
26160
26326
  headers["Set-Cookie"] = inlineSetCookie;
26161
26327
  res.writeHead(302, headers);
26162
26328
  res.end();
@@ -26170,7 +26336,7 @@ async function handlePagesPublic(req, res, pathSegments, queryParams) {
26170
26336
  "Content-Security-Policy": buildCsp(),
26171
26337
  "X-Content-Type-Options": "nosniff"
26172
26338
  };
26173
- if (inlineSetCookie)
26339
+ if (inlineSetCookie.length > 0)
26174
26340
  headers["Set-Cookie"] = inlineSetCookie;
26175
26341
  res.writeHead(200, headers);
26176
26342
  res.end(html);
@@ -31137,6 +31303,7 @@ var agentTaskBodySchema = object2({
31137
31303
  routingNote: string2().max(200).optional(),
31138
31304
  tags: array2(string2()).optional(),
31139
31305
  priority: number2().int().min(0).max(100).optional(),
31306
+ dependsOn: array2(uuid()).optional(),
31140
31307
  offerMode: boolean().optional(),
31141
31308
  dir: string2().min(1).optional(),
31142
31309
  vcsRepo: string2().min(1).optional(),
@@ -31617,6 +31784,7 @@ async function handleScriptRuns(req, res, pathSegments, queryParams, agentId) {
31617
31784
  routingSource: parsed.body.routingReason ? "declared" : parsed.body.agentId ? "engine_default" : undefined,
31618
31785
  tags: parsed.body.tags,
31619
31786
  priority: parsed.body.priority,
31787
+ dependsOn: parsed.body.dependsOn,
31620
31788
  offeredTo: parsed.body.offerMode ? parsed.body.agentId : undefined,
31621
31789
  taskType: "script-run-step",
31622
31790
  source: "mcp",
@@ -33101,7 +33269,10 @@ async function handleSessionData(req, res, pathSegments, queryParams, myAgentId)
33101
33269
 
33102
33270
  // src/http/sessions.ts
33103
33271
  init_zod();
33272
+ init_key();
33104
33273
  init_db3();
33274
+ init_session_moves();
33275
+ init_session_tree();
33105
33276
  init_rbac_roles();
33106
33277
  init_steering();
33107
33278
  init_users();
@@ -33143,7 +33314,54 @@ var SessionListItemSchema = object2({
33143
33314
  root: union2([AgentTaskSchema, AgentTaskSummarySchema2]),
33144
33315
  chainTaskCount: number2().int(),
33145
33316
  lastActivityAt: string2(),
33146
- latestStatus: AgentTaskStatusSchema
33317
+ latestStatus: AgentTaskStatusSchema,
33318
+ readState: _enum2(["unread", "read", "later", "done"]).optional(),
33319
+ snoozeUntil: string2().nullable().optional(),
33320
+ needsYou: boolean().optional(),
33321
+ groupKey: string2().optional(),
33322
+ shared: boolean().optional(),
33323
+ people: array2(object2({ id: string2(), name: string2() })).optional(),
33324
+ peopleCount: number2().int().optional(),
33325
+ preview: string2().nullable().optional(),
33326
+ shares: array2(object2({
33327
+ userId: string2().nullable(),
33328
+ name: string2().nullable(),
33329
+ access: _enum2(["read", "write"])
33330
+ })).optional()
33331
+ });
33332
+ var SessionSectionCountsSchema = object2({
33333
+ total: number2().int(),
33334
+ unread: number2().int(),
33335
+ running: number2().int(),
33336
+ needsYou: number2().int()
33337
+ });
33338
+ var SessionTreeGroupSchema = SessionSectionCountsSchema.extend({
33339
+ key: string2(),
33340
+ lastActivityAt: string2()
33341
+ });
33342
+ var SessionTreeResponseSchema = object2({
33343
+ groups: array2(SessionTreeGroupSchema),
33344
+ inbox: SessionSectionCountsSchema,
33345
+ later: SessionSectionCountsSchema,
33346
+ done: SessionSectionCountsSchema,
33347
+ noGroup: SessionSectionCountsSchema
33348
+ });
33349
+ var SessionApprovalSchema = object2({
33350
+ id: string2(),
33351
+ status: literal2("pending"),
33352
+ sourceTaskId: string2(),
33353
+ title: string2()
33354
+ });
33355
+ var SessionKeyFilterSchema = string2().transform((value, ctx) => {
33356
+ try {
33357
+ return normalizeAssetKey(value);
33358
+ } catch (error) {
33359
+ ctx.addIssue({
33360
+ code: "custom",
33361
+ message: error instanceof Error ? error.message : "Invalid asset key"
33362
+ });
33363
+ return NEVER;
33364
+ }
33147
33365
  });
33148
33366
  var TaskWithSteeringSchema = AgentTaskSchema.extend({
33149
33367
  citations: array2(TaskCitationSchema),
@@ -33399,8 +33617,14 @@ var listSessions = route({
33399
33617
  q: string2().optional(),
33400
33618
  requestedByUserId: string2().min(1).optional(),
33401
33619
  contextKeyPrefix: string2().min(1).optional(),
33620
+ keyPrefix: SessionKeyFilterSchema.optional(),
33621
+ groupKey: SessionKeyFilterSchema.optional(),
33622
+ noGroup: _enum2(["true", "false"]).transform((v) => v === "true").optional(),
33623
+ triage: _enum2(["inbox", "later", "done", "unread"]).optional(),
33624
+ status: _enum2(["running", "failed", "needs"]).optional(),
33625
+ withUserId: string2().min(1).optional(),
33402
33626
  fields: _enum2(["full", "slim"]).optional(),
33403
- scope: _enum2(["mine", "shared"]).optional()
33627
+ scope: _enum2(["mine", "shared", "tree"]).optional().describe("mine: owned sessions. shared: sessions shared with me. tree: Sessions that GET /api/session-tree counts: mine plus shared with me, no ownerless roots. A bypass actor sees every root with tree scope.")
33404
33628
  }),
33405
33629
  responses: {
33406
33630
  200: {
@@ -33416,6 +33640,110 @@ var listSessions = route({
33416
33640
  },
33417
33641
  auth: { apiKey: true }
33418
33642
  });
33643
+ var sessionTree = route({
33644
+ method: "get",
33645
+ path: "/api/session-tree",
33646
+ pattern: ["api", "session-tree"],
33647
+ summary: "Get session groups and triage counts",
33648
+ tags: ["Sessions"],
33649
+ responses: {
33650
+ 200: { description: "Visible groups and section counts", schema: SessionTreeResponseSchema },
33651
+ 401: { description: "Unauthorized" }
33652
+ },
33653
+ auth: { apiKey: true }
33654
+ });
33655
+ var SESSION_MOVE_LIMIT = 200;
33656
+ var SESSION_MOVE_OWNER_ONLY = "Only the session owner can move this group";
33657
+ var SESSION_MOVE_UNAVAILABLE = "Session not found or not accessible";
33658
+ var SessionMoveResultSchema = object2({
33659
+ moved: array2(string2()),
33660
+ skipped: array2(object2({ rootTaskId: string2(), reason: string2() }))
33661
+ });
33662
+ var sessionMoveRbac = {
33663
+ ungated: "per-session access, per-task task.fs.mutate, and asset-key write checks in the handler, as PATCH /api/assets/task/{id}/key"
33664
+ };
33665
+ var sessionMoveResponses = {
33666
+ 400: { description: "Invalid namespace or request" },
33667
+ 403: { description: "Personal destination not authorized" },
33668
+ 409: { description: "Moves blocked until audit warnings are repaired" }
33669
+ };
33670
+ var moveSessions = route({
33671
+ method: "post",
33672
+ path: "/api/session-moves",
33673
+ pattern: ["api", "session-moves"],
33674
+ summary: "Move sessions into a group or reset their keys",
33675
+ tags: ["Sessions"],
33676
+ session: { none: "The handler checks each root and reports denied roots as skipped" },
33677
+ body: object2({
33678
+ rootTaskIds: array2(string2().min(1)).min(1).max(SESSION_MOVE_LIMIT),
33679
+ groupKey: AssetKeySchema.nullable()
33680
+ }),
33681
+ responses: {
33682
+ 200: { description: "Moved and skipped sessions", schema: SessionMoveResultSchema },
33683
+ ...sessionMoveResponses
33684
+ },
33685
+ auth: { apiKey: true },
33686
+ rbac: sessionMoveRbac
33687
+ });
33688
+ var moveSessionPrefix = route({
33689
+ method: "post",
33690
+ path: "/api/session-moves/prefix",
33691
+ pattern: ["api", "session-moves", "prefix"],
33692
+ summary: "Rename or delete a session group",
33693
+ tags: ["Sessions"],
33694
+ body: object2({ from: AssetKeySchema, to: AssetKeySchema.nullable() }),
33695
+ responses: {
33696
+ 200: {
33697
+ description: "Moved and skipped sessions and remaining movable roots",
33698
+ schema: SessionMoveResultSchema.extend({ remaining: number2().int().min(0) })
33699
+ },
33700
+ ...sessionMoveResponses
33701
+ },
33702
+ auth: { apiKey: true },
33703
+ rbac: sessionMoveRbac
33704
+ });
33705
+ async function withSessionMove(req, res, myAgentId, destination, run) {
33706
+ try {
33707
+ const changedBy = await resolveHttpAuditUserId(req, myAgentId);
33708
+ const actor = await resolveSessionActor2(req, myAgentId);
33709
+ const key = destination === null ? null : await authorizeAssetKeyWrite(destination, changedBy);
33710
+ await assertAssetKeyMovesAllowed2();
33711
+ await run({ actor, changedBy: changedBy ?? undefined, key });
33712
+ } catch (error) {
33713
+ if (error instanceof AssetKeyAuthorizationError) {
33714
+ jsonError(res, error.message, error.statusCode);
33715
+ } else if (error instanceof AssetKeyMovesBlockedError2) {
33716
+ jsonError(res, error.message, 409);
33717
+ } else {
33718
+ throw error;
33719
+ }
33720
+ }
33721
+ }
33722
+ async function checkSessionMove(rootTaskId, actor, req, myAgentId, options = {}) {
33723
+ const root = await getTaskById2(rootTaskId);
33724
+ if (!root)
33725
+ return { reason: SESSION_MOVE_UNAVAILABLE };
33726
+ if (options.ownerOnly) {
33727
+ if (actor.kind === "user" && (!actor.userId || root.requestedByUserId !== actor.userId)) {
33728
+ return { reason: SESSION_MOVE_OWNER_ONLY };
33729
+ }
33730
+ } else {
33731
+ const access = await sessionAccessFor2(actor, root.id);
33732
+ if (access === "none")
33733
+ return { reason: SESSION_MOVE_UNAVAILABLE };
33734
+ if (!hasSessionAccess(access, "write"))
33735
+ return { reason: "Session write access required" };
33736
+ }
33737
+ if (root.parentTaskId)
33738
+ return { reason: "Task is not a session root" };
33739
+ const tasks = await getRootTaskChain2(root.id);
33740
+ for (const task of tasks) {
33741
+ if (!await canMutateTaskNamespace(task, myAgentId, req)) {
33742
+ return { reason: "Not authorized to move this task namespace" };
33743
+ }
33744
+ }
33745
+ return { taskIds: tasks.map((task) => task.id) };
33746
+ }
33419
33747
  var getSession = route({
33420
33748
  method: "get",
33421
33749
  path: "/api/sessions/{rootTaskId}",
@@ -33431,7 +33759,10 @@ var getSession = route({
33431
33759
  root: TaskWithSteeringSchema,
33432
33760
  chain: array2(TaskWithSteeringSchema),
33433
33761
  access: SessionAccessSchema,
33434
- owner: SessionOwnerSchema.nullable()
33762
+ owner: SessionOwnerSchema.nullable(),
33763
+ approvals: array2(SessionApprovalSchema).optional(),
33764
+ readState: _enum2(["unread", "read", "later", "done"]).optional(),
33765
+ lastActivityAt: string2().optional()
33435
33766
  })
33436
33767
  },
33437
33768
  401: { description: "Unauthorized" },
@@ -33562,11 +33893,12 @@ async function handleSessions(req, res, pathSegments, queryParams, myAgentId) {
33562
33893
  return true;
33563
33894
  const sources = parsed.query.source ? parsed.query.source.split(",").map((s) => s.trim()).filter(Boolean) : undefined;
33564
33895
  const actor = await resolveSessionActor2(req, myAgentId);
33565
- if (actor.kind === "bypass" && parsed.query.scope) {
33896
+ const limit = Math.min(200, Math.max(0, parsed.query.limit ?? 25));
33897
+ if (actor.kind === "bypass" && parsed.query.scope && parsed.query.scope !== "tree") {
33566
33898
  listSessions.respond(res, 200, {
33567
33899
  sessions: [],
33568
33900
  total: 0,
33569
- limit: parsed.query.limit ?? 25,
33901
+ limit,
33570
33902
  offset: parsed.query.offset ?? 0
33571
33903
  });
33572
33904
  return true;
@@ -33576,11 +33908,24 @@ async function handleSessions(req, res, pathSegments, queryParams, myAgentId) {
33576
33908
  q: parsed.query.q,
33577
33909
  requestedByUserId: parsed.query.requestedByUserId,
33578
33910
  contextKeyPrefix: parsed.query.contextKeyPrefix,
33579
- ...actor.kind === "user" ? { viewer: { userId: actor.userId, scope: parsed.query.scope ?? "all" } } : {}
33911
+ keyPrefix: parsed.query.keyPrefix,
33912
+ groupKey: parsed.query.groupKey,
33913
+ noGroup: parsed.query.noGroup,
33914
+ triage: parsed.query.triage,
33915
+ status: parsed.query.status,
33916
+ withUserId: parsed.query.withUserId,
33917
+ now: new Date().toISOString(),
33918
+ tree: parsed.query.scope === "tree",
33919
+ ...actor.kind === "user" ? {
33920
+ viewer: {
33921
+ userId: actor.userId,
33922
+ scope: parsed.query.scope === "tree" ? "all" : parsed.query.scope ?? "all"
33923
+ }
33924
+ } : {}
33580
33925
  };
33581
33926
  const baseOpts = {
33582
33927
  ...filterOpts,
33583
- limit: parsed.query.limit,
33928
+ limit,
33584
33929
  offset: parsed.query.offset
33585
33930
  };
33586
33931
  const sessions = parsed.query.fields === "full" ? await listRecentSessions2(baseOpts) : await listRecentSessions2({ ...baseOpts, slim: true });
@@ -33588,11 +33933,104 @@ async function handleSessions(req, res, pathSegments, queryParams, myAgentId) {
33588
33933
  listSessions.respond(res, 200, {
33589
33934
  sessions,
33590
33935
  total,
33591
- limit: parsed.query.limit ?? 25,
33936
+ limit,
33592
33937
  offset: parsed.query.offset ?? 0
33593
33938
  });
33594
33939
  return true;
33595
33940
  }
33941
+ if (sessionTree.match(req.method, pathSegments)) {
33942
+ const parsed = await sessionTree.parse(req, res, pathSegments, queryParams);
33943
+ if (!parsed)
33944
+ return true;
33945
+ sessionTree.respond(res, 200, await getSessionTree(await resolveSessionActor2(req, myAgentId)));
33946
+ return true;
33947
+ }
33948
+ if (moveSessions.match(req.method, pathSegments)) {
33949
+ const parsed = await moveSessions.parse(req, res, pathSegments, queryParams);
33950
+ if (!parsed)
33951
+ return true;
33952
+ await withSessionMove(req, res, myAgentId, parsed.body.groupKey, async ({ actor, changedBy, key }) => {
33953
+ const skipped = [];
33954
+ const moves = [];
33955
+ for (const rootId of new Set(parsed.body.rootTaskIds)) {
33956
+ const checked = await checkSessionMove(rootId, actor, req, myAgentId);
33957
+ if ("reason" in checked) {
33958
+ skipped.push({ rootTaskId: rootId, reason: checked.reason });
33959
+ } else {
33960
+ moves.push({
33961
+ rootId,
33962
+ taskIds: checked.taskIds,
33963
+ key: key ?? defaultAssetKey("task", rootId)
33964
+ });
33965
+ }
33966
+ }
33967
+ const moved = await moveSessionChains({ moves, changedBy });
33968
+ moveSessions.respond(res, 200, { moved, skipped });
33969
+ });
33970
+ return true;
33971
+ }
33972
+ if (moveSessionPrefix.match(req.method, pathSegments)) {
33973
+ const parsed = await moveSessionPrefix.parse(req, res, pathSegments, queryParams);
33974
+ if (!parsed)
33975
+ return true;
33976
+ const from = normalizeAssetKey(parsed.body.from);
33977
+ if (parseAssetKey(from).relativeSegments.length === 0) {
33978
+ jsonError(res, "Cannot move a namespace root", 400);
33979
+ return true;
33980
+ }
33981
+ await withSessionMove(req, res, myAgentId, parsed.body.to, async ({ actor, changedBy, key }) => {
33982
+ if (key?.startsWith(from)) {
33983
+ jsonError(res, "Destination must be outside the source prefix", 400);
33984
+ return;
33985
+ }
33986
+ const skipped = actor.kind === "user" ? (await listSharedPrefixRoots({ from, userId: actor.userId })).map((root) => ({
33987
+ rootTaskId: root.id,
33988
+ reason: SESSION_MOVE_OWNER_ONLY
33989
+ })) : [];
33990
+ const moves = [];
33991
+ let remaining = 0;
33992
+ for (let offset = 0;; offset += SESSION_MOVE_LIMIT) {
33993
+ const roots = await listPrefixRoots({
33994
+ from,
33995
+ userId: actor.kind === "user" ? actor.userId : undefined,
33996
+ limit: SESSION_MOVE_LIMIT,
33997
+ offset
33998
+ });
33999
+ for (const root of roots) {
34000
+ const nextKey = key === null ? defaultAssetKey("task", root.id) : key + root.key.slice(from.length);
34001
+ if (nextKey.startsWith(from)) {
34002
+ skipped.push({
34003
+ rootTaskId: root.id,
34004
+ reason: "The new key stays inside the source group"
34005
+ });
34006
+ continue;
34007
+ }
34008
+ if (nextKey.length > ASSET_KEY_MAX_LENGTH) {
34009
+ skipped.push({
34010
+ rootTaskId: root.id,
34011
+ reason: `Asset key cannot exceed ${ASSET_KEY_MAX_LENGTH} characters`
34012
+ });
34013
+ continue;
34014
+ }
34015
+ const checked = await checkSessionMove(root.id, actor, req, myAgentId, {
34016
+ ownerOnly: true
34017
+ });
34018
+ if ("reason" in checked) {
34019
+ skipped.push({ rootTaskId: root.id, reason: checked.reason });
34020
+ } else if (moves.length < SESSION_MOVE_LIMIT) {
34021
+ moves.push({ rootId: root.id, taskIds: checked.taskIds, key: nextKey });
34022
+ } else {
34023
+ remaining++;
34024
+ }
34025
+ }
34026
+ if (roots.length < SESSION_MOVE_LIMIT)
34027
+ break;
34028
+ }
34029
+ const moved = await moveSessionChains({ moves, changedBy });
34030
+ moveSessionPrefix.respond(res, 200, { moved, skipped, remaining });
34031
+ });
34032
+ return true;
34033
+ }
33596
34034
  if (getSession.match(req.method, pathSegments)) {
33597
34035
  const parsed = await getSession.parse(req, res, pathSegments, queryParams);
33598
34036
  if (!parsed)
@@ -33606,7 +34044,8 @@ async function handleSessions(req, res, pathSegments, queryParams, myAgentId) {
33606
34044
  id: root.id,
33607
34045
  requestedByUserId: root.requestedByUserId ?? null
33608
34046
  };
33609
- const access = await accessForRoot(await resolveSessionActor2(req, myAgentId), sessionRoot);
34047
+ const actor = await resolveSessionActor2(req, myAgentId);
34048
+ const access = await accessForRoot(actor, sessionRoot);
33610
34049
  if (access === "none") {
33611
34050
  jsonError(res, "You don't have access to this session. Ask the owner to share it.", 403);
33612
34051
  return true;
@@ -33618,9 +34057,11 @@ async function handleSessions(req, res, pathSegments, queryParams, myAgentId) {
33618
34057
  } : null;
33619
34058
  const chain = await getRootTaskChain2(parsed.params.rootTaskId);
33620
34059
  const tasks = [root, ...chain];
33621
- const [steering, citations] = await Promise.all([
34060
+ const [steering, citations, approvals, read] = await Promise.all([
33622
34061
  getTaskSteeringFieldsForTasks(tasks),
33623
- getTaskCitationsForTasks(tasks.map((task) => task.id))
34062
+ getTaskCitationsForTasks(tasks.map((task) => task.id)),
34063
+ getPendingApprovalsForTasks2(tasks.map((task) => task.id)),
34064
+ actor.kind === "user" && actor.userId ? getSessionReadState(sessionRoot.id, actor.userId) : Promise.resolve(null)
33624
34065
  ]);
33625
34066
  const decorate = (task) => ({
33626
34067
  ...task,
@@ -33631,7 +34072,9 @@ async function handleSessions(req, res, pathSegments, queryParams, myAgentId) {
33631
34072
  root: decorate(root),
33632
34073
  chain: chain.map(decorate),
33633
34074
  access,
33634
- owner
34075
+ owner,
34076
+ ...approvals.length ? { approvals } : {},
34077
+ ...read ?? {}
33635
34078
  });
33636
34079
  return true;
33637
34080
  }
@@ -33720,8 +34163,7 @@ async function syncSkillsToFilesystem(agentId, harnessType = "all", homeOverride
33720
34163
  })));
33721
34164
  return writeSkillsToFilesystem(entries, harnessType, home);
33722
34165
  }
33723
- async function computeAgentSkillsSignature(agentId) {
33724
- const skills = await getAgentSkills2(agentId);
34166
+ function computeSkillsSignature(skills) {
33725
34167
  const sorted = [...skills].sort((a, b) => a.id.localeCompare(b.id));
33726
34168
  const canonical = JSON.stringify(sorted.map((s) => [
33727
34169
  s.id,
@@ -33736,6 +34178,18 @@ async function computeAgentSkillsSignature(agentId) {
33736
34178
  const hash = new Bun.CryptoHasher("sha256").update(canonical).digest("hex");
33737
34179
  return { hash, count: sorted.length };
33738
34180
  }
34181
+ async function computeAgentSkillsSignature(agentId) {
34182
+ return computeSkillsSignature(await getAgentSkills2(agentId));
34183
+ }
34184
+ async function buildSkillsBundle(readSkills, readFiles) {
34185
+ const skills = await readSkills();
34186
+ const signature = computeSkillsSignature(skills).hash;
34187
+ const entries = await Promise.all(skills.map(async (skill) => ({
34188
+ ...skill,
34189
+ files: skill.isComplex ? await readFiles(skill.id) : []
34190
+ })));
34191
+ return { skills: entries, total: entries.length, signature };
34192
+ }
33739
34193
 
33740
34194
  // src/be/skill-usage.ts
33741
34195
  init_db3();
@@ -34021,6 +34475,13 @@ var agentSkillsListResponseSchema = object2({
34021
34475
  total: number2(),
34022
34476
  signature: string2()
34023
34477
  });
34478
+ var agentSkillsBundleResponseSchema = object2({
34479
+ skills: array2(SkillWithInstallInfoSchema.extend({
34480
+ files: array2(SkillFileSchema)
34481
+ })),
34482
+ total: number2(),
34483
+ signature: string2()
34484
+ });
34024
34485
  var agentSkillsSignatureResponseSchema = object2({
34025
34486
  hash: string2(),
34026
34487
  count: number2(),
@@ -34415,7 +34876,28 @@ var getAgentSkillsSignatureRoute = route({
34415
34876
  200: { description: "Skills signature", schema: agentSkillsSignatureResponseSchema }
34416
34877
  }
34417
34878
  });
34879
+ var getAgentSkillsBundleRoute = route({
34880
+ method: "get",
34881
+ path: "/api/agents/{id}/skills/bundle",
34882
+ pattern: ["api", "agents", null, "skills", "bundle"],
34883
+ summary: "Get an agent's installed skills together with their bundled files",
34884
+ description: "Single-request boot payload: every installed-and-enabled skill with the full content of its bundled files, plus the same signature the cheap probe returns. Lets a worker sync its local skill tree without an N+1 file fan-out.",
34885
+ tags: ["Skills"],
34886
+ auth: { apiKey: true },
34887
+ params: object2({ id: string2() }),
34888
+ responses: {
34889
+ 200: { description: "Agent skills bundle", schema: agentSkillsBundleResponseSchema }
34890
+ }
34891
+ });
34418
34892
  async function handleSkills(req, res, pathSegments, queryParams, myAgentId) {
34893
+ if (getAgentSkillsBundleRoute.match(req.method, pathSegments)) {
34894
+ const parsed = await getAgentSkillsBundleRoute.parse(req, res, pathSegments, queryParams);
34895
+ if (!parsed)
34896
+ return true;
34897
+ const bundle = await buildSkillsBundle(() => getAgentSkills2(parsed.params.id), (skillId) => getSkillFiles2(skillId));
34898
+ getAgentSkillsBundleRoute.respond(res, 200, bundle);
34899
+ return true;
34900
+ }
34419
34901
  if (getAgentSkillsSignatureRoute.match(req.method, pathSegments)) {
34420
34902
  const parsed = await getAgentSkillsSignatureRoute.parse(req, res, pathSegments, queryParams);
34421
34903
  if (!parsed)
@@ -36569,7 +37051,18 @@ var deleteIdentityRoute = route({
36569
37051
  },
36570
37052
  auth: { apiKey: true }
36571
37053
  });
36572
- var UNMAPPED_KINDS = ["slack", "github", "gitlab", "azure-devops", "linear", "kapso"];
37054
+ var UNMAPPED_KINDS = [
37055
+ "slack",
37056
+ "github",
37057
+ "gitlab",
37058
+ "azure-devops",
37059
+ "bitbucket",
37060
+ "linear",
37061
+ "kapso",
37062
+ "telegram",
37063
+ "discord",
37064
+ "teams"
37065
+ ];
36573
37066
  async function collectUnmappedForKind(kind, limit) {
36574
37067
  const namespace = `integration:unmapped:${kind}`;
36575
37068
  const rows = await listKv2(namespace, { limit: Math.min(limit * 2, 1000), offset: 0 });
@@ -37346,6 +37839,24 @@ var azureDevOpsWebhook = route({
37346
37839
  503: { description: "Azure DevOps integration not configured" }
37347
37840
  }
37348
37841
  });
37842
+ var bitbucketWebhook = route({
37843
+ method: "post",
37844
+ path: "/api/bitbucket/webhook",
37845
+ pattern: ["api", "bitbucket", "webhook"],
37846
+ summary: "Handle Bitbucket Cloud webhook events",
37847
+ tags: ["Webhooks"],
37848
+ auth: { apiKey: false },
37849
+ rbac: {
37850
+ ungated: "provider webhook: authenticated by the per-webhook HMAC secret, not a swarm principal"
37851
+ },
37852
+ responses: {
37853
+ 200: { description: "Event processed", schema: WebhookDispatchResultSchema },
37854
+ 401: { description: "Invalid signature" },
37855
+ 413: { description: "Body exceeds 1 MiB" },
37856
+ 503: { description: "Bitbucket integration not configured" }
37857
+ }
37858
+ });
37859
+ var BITBUCKET_WEBHOOK_MAX_BODY_BYTES = 1024 * 1024;
37349
37860
  var agentmailWebhook = route({
37350
37861
  method: "post",
37351
37862
  path: "/api/agentmail/webhook",
@@ -37802,6 +38313,101 @@ async function handleWebhooks(req, res, pathSegments) {
37802
38313
  }
37803
38314
  return true;
37804
38315
  }
38316
+ if (bitbucketWebhook.match(req.method, pathSegments)) {
38317
+ if (!isBitbucketEnabled()) {
38318
+ res.writeHead(503, { "Content-Type": "application/json" });
38319
+ res.end(JSON.stringify({ error: "Bitbucket integration not configured" }));
38320
+ return true;
38321
+ }
38322
+ if (enforceContentLengthCap(req, res, BITBUCKET_WEBHOOK_MAX_BODY_BYTES) === BODY_TOO_LARGE) {
38323
+ return true;
38324
+ }
38325
+ const chunks = [];
38326
+ let receivedBytes = 0;
38327
+ let tooLarge = false;
38328
+ for await (const chunk of req) {
38329
+ if (tooLarge)
38330
+ continue;
38331
+ receivedBytes += chunk.byteLength;
38332
+ if (receivedBytes > BITBUCKET_WEBHOOK_MAX_BODY_BYTES) {
38333
+ tooLarge = true;
38334
+ chunks.length = 0;
38335
+ jsonError(res, `Payload too large (max ${BITBUCKET_WEBHOOK_MAX_BODY_BYTES} bytes)`, 413);
38336
+ continue;
38337
+ }
38338
+ chunks.push(chunk);
38339
+ }
38340
+ if (tooLarge)
38341
+ return true;
38342
+ const rawBody = Buffer.concat(chunks).toString();
38343
+ const signature = req.headers["x-hub-signature"];
38344
+ if (!verifyBitbucketWebhook(rawBody, signature)) {
38345
+ console.log("[Bitbucket] Invalid webhook signature");
38346
+ res.writeHead(401, { "Content-Type": "application/json" });
38347
+ res.end(JSON.stringify({ error: "Invalid signature" }));
38348
+ return true;
38349
+ }
38350
+ let body;
38351
+ try {
38352
+ body = JSON.parse(rawBody);
38353
+ } catch {
38354
+ res.writeHead(400, { "Content-Type": "application/json" });
38355
+ res.end(JSON.stringify({ error: "Invalid JSON body" }));
38356
+ return true;
38357
+ }
38358
+ const eventKey = req.headers["x-event-key"];
38359
+ const requestUuid = req.headers["x-request-uuid"];
38360
+ if (isDuplicateDelivery(requestUuid)) {
38361
+ console.log(`[Bitbucket] Skipping duplicate delivery ${requestUuid}`);
38362
+ bitbucketWebhook.respond(res, 200, { created: false });
38363
+ return true;
38364
+ }
38365
+ console.log(`[Bitbucket] Received ${eventKey} event`);
38366
+ try {
38367
+ const result = await runBlockableHandler("Bitbucket", async () => {
38368
+ switch (eventKey) {
38369
+ case "pullrequest:created":
38370
+ return await handlePullRequestCreated2(body);
38371
+ case "pullrequest:comment_created":
38372
+ return await handlePullRequestCommentCreated(body);
38373
+ case "pullrequest:fulfilled":
38374
+ return await handlePullRequestClosed(body, "merged");
38375
+ case "pullrequest:rejected":
38376
+ return await handlePullRequestClosed(body, "declined");
38377
+ default:
38378
+ console.log(`[Bitbucket] Ignoring unsupported event type: ${eventKey}`);
38379
+ return { created: false };
38380
+ }
38381
+ });
38382
+ const workflowEvent = {
38383
+ "pullrequest:created": "created",
38384
+ "pullrequest:comment_created": "commented",
38385
+ "pullrequest:fulfilled": "merged",
38386
+ "pullrequest:rejected": "declined"
38387
+ }[eventKey ?? ""];
38388
+ const prEvent = workflowEvent ? pullRequestEventOf(body) : null;
38389
+ if (workflowEvent && prEvent) {
38390
+ const pr = prEvent.pullrequest;
38391
+ const comment = workflowEvent === "commented" ? pullRequestCommentEventOf(body)?.comment : undefined;
38392
+ const user = comment?.user ?? prEvent.actor;
38393
+ workflowEventBus2.emit(`bitbucket.pull_request.${workflowEvent}`, {
38394
+ repo: prEvent.repository.full_name,
38395
+ number: pr.id,
38396
+ ...workflowEvent === "commented" ? { body: comment?.content.raw } : { title: pr.title, body: pr.description ?? undefined },
38397
+ action: workflowEvent,
38398
+ user_login: user.account_id ?? user.nickname ?? user.display_name
38399
+ });
38400
+ }
38401
+ markDeliveryProcessed(requestUuid);
38402
+ bitbucketWebhook.respond(res, 200, result);
38403
+ } catch (err) {
38404
+ const errorMessage = err instanceof Error ? err.message : String(err);
38405
+ console.error(`[Bitbucket] Error handling ${eventKey} event: ${scrubSecrets(errorMessage)}`);
38406
+ res.writeHead(500, { "Content-Type": "application/json" });
38407
+ res.end(JSON.stringify({ error: "Internal server error" }));
38408
+ }
38409
+ return true;
38410
+ }
37805
38411
  if (agentmailWebhook.match(req.method, pathSegments)) {
37806
38412
  if (!isAgentMailEnabled()) {
37807
38413
  res.writeHead(503, { "Content-Type": "application/json" });
@@ -39264,7 +39870,7 @@ var httpServer = createHttpServer(async (req, res) => {
39264
39870
  });
39265
39871
  });
39266
39872
  var detachRealtime = attachRealtimeTransport(httpServer);
39267
- var { attachControllerTransport } = await import("./controllers-connect-mwzx1fh6.js");
39873
+ var { attachControllerTransport } = await import("./controllers-connect-tw1dan9r.js");
39268
39874
  var detachControllers = attachControllerTransport(httpServer);
39269
39875
  var stopWorkerNudges = startWorkerNudgeListener();
39270
39876
  var stopRoomDeletion = realtimeBus.subscribe("room:namespace-deleted", (namespace) => {
@@ -39293,7 +39899,7 @@ async function shutdown() {
39293
39899
  sessionsProcessed: getServerSessionsProcessed()
39294
39900
  });
39295
39901
  if (hasCapability("scheduling")) {
39296
- const { stopScheduler } = await import("./index-hg0x2cdv.js");
39902
+ const { stopScheduler } = await import("./index-07ef7nkm.js");
39297
39903
  stopScheduler();
39298
39904
  }
39299
39905
  stopHeartbeat2();
@@ -39305,7 +39911,7 @@ async function shutdown() {
39305
39911
  await stopSlackApp2();
39306
39912
  await stopChatSdk();
39307
39913
  if (process.env.OAUTH_KEEPALIVE_DISABLE !== "true") {
39308
- const { stopOAuthKeepalive } = await import("./keepalive-hrh7xnr9.js");
39914
+ const { stopOAuthKeepalive } = await import("./keepalive-5nh2rhmr.js");
39309
39915
  await stopOAuthKeepalive();
39310
39916
  }
39311
39917
  stopOAuthPendingGc();
@@ -39385,21 +39991,21 @@ try {
39385
39991
  console.warn("[startup] CAPABILITIES upgrade seed failed (non-fatal):", err);
39386
39992
  }
39387
39993
  try {
39388
- const { ensurePricingSeeded } = await import("./boot-seeds-9ks8hce7.js");
39994
+ const { ensurePricingSeeded } = await import("./boot-seeds-3vn0m9sg.js");
39389
39995
  ensurePricingSeeded();
39390
- const { startPricingRefreshLoop } = await import("./pricing-refresh-j38dehfs.js");
39996
+ const { startPricingRefreshLoop } = await import("./pricing-refresh-nevhat8h.js");
39391
39997
  startPricingRefreshLoop();
39392
39998
  } catch (err) {
39393
39999
  console.error("[startup] Failed to seed pricing rows:", err);
39394
40000
  }
39395
40001
  try {
39396
- const { ensureModelProfilesSeeded } = await import("./boot-seeds-9ks8hce7.js");
40002
+ const { ensureModelProfilesSeeded } = await import("./boot-seeds-3vn0m9sg.js");
39397
40003
  await ensureModelProfilesSeeded();
39398
40004
  } catch (err) {
39399
40005
  console.error("[startup] Failed to seed model profiles:", err);
39400
40006
  }
39401
40007
  try {
39402
- const { ensureRbacSeeded } = await import("./boot-seeds-9ks8hce7.js");
40008
+ const { ensureRbacSeeded } = await import("./boot-seeds-3vn0m9sg.js");
39403
40009
  ensureRbacSeeded();
39404
40010
  } catch (err) {
39405
40011
  console.error("[startup] Failed to sync RBAC seed rows:", err);
@@ -39407,7 +40013,7 @@ try {
39407
40013
  throw err;
39408
40014
  }
39409
40015
  try {
39410
- const { runAllSeeders } = await import("./index-97q4xn03.js");
40016
+ const { runAllSeeders } = await import("./index-zdycv3ja.js");
39411
40017
  await runAllSeeders({ scriptEmbeddingMode: "skip" });
39412
40018
  } catch (err) {
39413
40019
  console.error("[startup] Failed to seed built-in entities:", err);
@@ -39463,45 +40069,46 @@ httpServer.listen(port, async () => {
39463
40069
  initGitLab();
39464
40070
  initAzureDevOps();
39465
40071
  initAzureDevOpsOutbound();
40072
+ initBitbucket();
39466
40073
  initAgentMail();
39467
40074
  await initLinear();
39468
40075
  await initJira();
39469
40076
  await initWorkflows2();
39470
40077
  await startScriptRunSupervisor(getMcpBaseUrl());
39471
40078
  if (hasCapability("scheduling")) {
39472
- const { startScheduler } = await import("./index-hg0x2cdv.js");
39473
- const { getExecutorRegistry } = await import("./index-gdh8jrdm.js");
40079
+ const { startScheduler } = await import("./index-07ef7nkm.js");
40080
+ const { getExecutorRegistry } = await import("./index-q3t40ryv.js");
39474
40081
  const intervalMs = Number(process.env.SCHEDULER_INTERVAL_MS) || 1e4;
39475
40082
  startScheduler(getExecutorRegistry(), intervalMs, {
39476
40083
  runId: globalState.__runId
39477
40084
  });
39478
40085
  }
39479
40086
  if (!isEnvFlagEnabled("HEARTBEAT_DISABLE", false)) {
39480
- const { startHeartbeat } = await import("./index-thm0y6a8.js");
40087
+ const { startHeartbeat } = await import("./index-7yts8n9s.js");
39481
40088
  const heartbeatMs = Number(process.env.HEARTBEAT_INTERVAL_MS) || 90000;
39482
40089
  startHeartbeat(heartbeatMs);
39483
40090
  }
39484
40091
  startPoolRouting();
39485
40092
  startRuntimeSupervisor();
39486
40093
  if (process.env.OAUTH_KEEPALIVE_DISABLE !== "true") {
39487
- const { startOAuthKeepalive } = await import("./keepalive-hrh7xnr9.js");
40094
+ const { startOAuthKeepalive } = await import("./keepalive-5nh2rhmr.js");
39488
40095
  startOAuthKeepalive();
39489
40096
  }
39490
40097
  if (process.env.OAUTH_REFRESH_SWEEP_DISABLE !== "true") {
39491
- const { startOAuthRefreshSweep } = await import("./oauth-refresh-sweep-zm0yj4sj.js");
40098
+ const { startOAuthRefreshSweep } = await import("./oauth-refresh-sweep-y3n1knmd.js");
39492
40099
  startOAuthRefreshSweep();
39493
40100
  }
39494
40101
  startOAuthPendingGc();
39495
40102
  await startMemoryGc();
39496
40103
  await startDbRetention();
39497
40104
  startHumanFreeDrain();
39498
- import("./boot-reembed-srp6v6af.js").then(({ runBootReembed }) => runBootReembed()).catch((err) => {
40105
+ import("./boot-reembed-n4wq5dd2.js").then(({ runBootReembed }) => runBootReembed()).catch((err) => {
39499
40106
  console.error("[boot-reembed] startup backfill failed (non-fatal):", err);
39500
40107
  });
39501
- import("./boot-reembed-xns7h5h9.js").then(({ runBootReembedScripts }) => runBootReembedScripts()).catch((err) => {
40108
+ import("./boot-reembed-4xwq5tg0.js").then(({ runBootReembedScripts }) => runBootReembedScripts()).catch((err) => {
39502
40109
  console.error("[boot-reembed-scripts] startup backfill failed (non-fatal):", err);
39503
40110
  });
39504
- import("./boot-scrub-sweep-3qqrh2mz.js").then(({ runBootScrubSweep }) => runBootScrubSweep()).catch((err) => {
40111
+ import("./boot-scrub-sweep-69h90xcq.js").then(({ runBootScrubSweep }) => runBootScrubSweep()).catch((err) => {
39505
40112
  console.error("[boot-scrub-sweep] startup scrub failed (non-fatal):", err);
39506
40113
  });
39507
40114
  }).on("error", (err) => {