@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
@@ -0,0 +1,294 @@
1
+ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
2
+ import { unlink } from "node:fs/promises";
3
+ import {
4
+ createServer as createHttpServer,
5
+ request as httpRequest,
6
+ type IncomingMessage,
7
+ type Server,
8
+ type ServerResponse,
9
+ } from "node:http";
10
+ import type { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
11
+ import { closeDb, createAgent, createUser, getDbClient, initDb } from "../be/db";
12
+ import { type IdentityActor, mintToken } from "../be/users";
13
+ import { handleCore } from "../http/core";
14
+ import { handleMcp } from "../http/mcp";
15
+ import { handleMcpUser } from "../http/mcp-user";
16
+ import { MCP_MAX_REQUEST_BODY_BYTES, MCP_MAX_TOOL_INPUT_ELEMENTS } from "../mcp-limits";
17
+ import { listenOnFreePort } from "./test-net";
18
+
19
+ // GHSA-46jr-4wx8-v453: both MCP surfaces must bound tools/call argument size
20
+ // and the raw HTTP body they read themselves.
21
+
22
+ const TEST_DB_PATH = "./test-mcp-input-caps.sqlite";
23
+ const API_KEY = "example-test-mcp-caps-key";
24
+ const ACTOR: IdentityActor = { kind: "operator", id: "test" };
25
+ const TOO_LARGE_MESSAGE = `Payload Too Large: Request body must not exceed ${MCP_MAX_REQUEST_BODY_BYTES} bytes`;
26
+
27
+ async function removeDbFiles(path: string): Promise<void> {
28
+ for (const suffix of ["", "-wal", "-shm"]) {
29
+ try {
30
+ await unlink(path + suffix);
31
+ } catch (error) {
32
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
33
+ }
34
+ }
35
+ }
36
+
37
+ let server: Server;
38
+ let port: number;
39
+
40
+ beforeAll(async () => {
41
+ await removeDbFiles(TEST_DB_PATH);
42
+ initDb(TEST_DB_PATH);
43
+ const transports: Record<string, StreamableHTTPServerTransport> = {};
44
+ const transportsUser: Record<string, StreamableHTTPServerTransport> = {};
45
+ const sessionAgents: Record<string, string> = {};
46
+ const sessionUsers: Record<string, string> = {};
47
+ server = createHttpServer(async (req: IncomingMessage, res: ServerResponse) => {
48
+ const myAgentId = req.headers["x-agent-id"] as string | undefined;
49
+ if (await handleCore(req, res, myAgentId, API_KEY)) return;
50
+ if (await handleMcp(req, res, transports, {}, sessionAgents)) return;
51
+ if (await handleMcpUser(req, res, transportsUser, sessionUsers)) return;
52
+ res.writeHead(404);
53
+ res.end("Not Found");
54
+ });
55
+ port = await listenOnFreePort(server, "127.0.0.1");
56
+ });
57
+
58
+ afterAll(async () => {
59
+ await new Promise<void>((resolve) => server.close(() => resolve()));
60
+ closeDb();
61
+ await removeDbFiles(TEST_DB_PATH);
62
+ });
63
+
64
+ beforeEach(async () => {
65
+ const client = getDbClient();
66
+ await client.run("DELETE FROM user_tokens");
67
+ await client.run("DELETE FROM agent_tasks");
68
+ await client.run("DELETE FROM users");
69
+ await client.run("DELETE FROM agents");
70
+ });
71
+
72
+ type Surface = {
73
+ name: string;
74
+ path: "/mcp" | "/mcp-user";
75
+ /** Credentials headers for a fresh caller on this surface. */
76
+ headers: () => Promise<Record<string, string>>;
77
+ /** A tools/call that should succeed with the given extra argument members. */
78
+ call: (extra: Record<string, unknown>) => { name: string; arguments: Record<string, unknown> };
79
+ };
80
+
81
+ const surfaces: Surface[] = [
82
+ {
83
+ name: "agent MCP",
84
+ path: "/mcp",
85
+ headers: async () => {
86
+ const agent = await createAgent({ name: "Caps Agent", isLead: false, status: "idle" });
87
+ return { Authorization: `Bearer ${API_KEY}`, "X-Agent-ID": agent.id };
88
+ },
89
+ call: (extra) => ({ name: "get-swarm", arguments: { ...extra } }),
90
+ },
91
+ {
92
+ name: "user MCP",
93
+ path: "/mcp-user",
94
+ headers: async () => {
95
+ await createAgent({ name: "Caps Lead", isLead: true, status: "idle" });
96
+ const user = await createUser({ name: "Caps User" });
97
+ const token = (await mintToken(user.id, "caps", ACTOR)).plaintext;
98
+ return { Authorization: `Bearer ${token}` };
99
+ },
100
+ call: (extra) => ({ name: "send-task", arguments: { task: "caps check", ...extra } }),
101
+ },
102
+ ];
103
+
104
+ function parsePayload(text: string): unknown {
105
+ const trimmed = text.trim();
106
+ if (trimmed.startsWith("event:") || trimmed.startsWith("data:")) {
107
+ return JSON.parse(
108
+ trimmed
109
+ .split("\n")
110
+ .filter((line) => line.startsWith("data:"))
111
+ .map((line) => line.slice("data:".length).trim())
112
+ .join("\n"),
113
+ );
114
+ }
115
+ return JSON.parse(trimmed);
116
+ }
117
+
118
+ async function post(
119
+ path: string,
120
+ headers: Record<string, string>,
121
+ body: string,
122
+ sessionId?: string,
123
+ ): Promise<{ status: number; payload: unknown; sessionId: string | null }> {
124
+ const response = await fetch(`http://127.0.0.1:${port}${path}`, {
125
+ method: "POST",
126
+ headers: {
127
+ Accept: "application/json, text/event-stream",
128
+ "Content-Type": "application/json",
129
+ ...headers,
130
+ ...(sessionId ? { "mcp-session-id": sessionId } : {}),
131
+ },
132
+ body,
133
+ });
134
+ const text = await response.text();
135
+ return {
136
+ status: response.status,
137
+ payload: text ? parsePayload(text) : null,
138
+ sessionId: response.headers.get("mcp-session-id"),
139
+ };
140
+ }
141
+
142
+ async function openSession(path: string, headers: Record<string, string>): Promise<string> {
143
+ const init = await post(
144
+ path,
145
+ headers,
146
+ JSON.stringify({
147
+ jsonrpc: "2.0",
148
+ id: 1,
149
+ method: "initialize",
150
+ params: {
151
+ protocolVersion: "2024-11-05",
152
+ clientInfo: { name: "caps-test", version: "1" },
153
+ capabilities: {},
154
+ },
155
+ }),
156
+ );
157
+ expect(init.status).toBe(200);
158
+ if (!init.sessionId) throw new Error("initialize returned no mcp-session-id");
159
+ const ack = await post(
160
+ path,
161
+ headers,
162
+ JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" }),
163
+ init.sessionId,
164
+ );
165
+ expect([200, 202]).toContain(ack.status);
166
+ return init.sessionId;
167
+ }
168
+
169
+ /** POST a body with no Content-Length (chunked), so only the streaming cap can stop it. */
170
+ function postChunked(
171
+ path: string,
172
+ headers: Record<string, string>,
173
+ totalBytes: number,
174
+ ): Promise<{ status: number; body: string }> {
175
+ return new Promise((resolve, reject) => {
176
+ const req = httpRequest(
177
+ {
178
+ host: "127.0.0.1",
179
+ port,
180
+ path,
181
+ method: "POST",
182
+ headers: { "Content-Type": "application/json", ...headers },
183
+ },
184
+ (res) => {
185
+ let body = "";
186
+ res.setEncoding("utf8");
187
+ res.on("data", (chunk: string) => {
188
+ body += chunk;
189
+ });
190
+ res.on("end", () => resolve({ status: res.statusCode ?? 0, body }));
191
+ },
192
+ );
193
+ // The server may close the socket mid-upload after answering 413.
194
+ req.on("error", (error) => {
195
+ if (!req.writableFinished) return;
196
+ reject(error);
197
+ });
198
+ const chunk = Buffer.alloc(64 * 1024, 0x20);
199
+ let sent = 0;
200
+ const pump = () => {
201
+ while (sent < totalBytes) {
202
+ sent += chunk.byteLength;
203
+ if (!req.write(chunk)) {
204
+ req.once("drain", pump);
205
+ return;
206
+ }
207
+ }
208
+ req.end();
209
+ };
210
+ pump();
211
+ });
212
+ }
213
+
214
+ for (const surface of surfaces) {
215
+ describe(`${surface.name} input caps`, () => {
216
+ test("a normal tools/call passes", async () => {
217
+ const headers = await surface.headers();
218
+ const sessionId = await openSession(surface.path, headers);
219
+ const { status, payload } = await post(
220
+ surface.path,
221
+ headers,
222
+ JSON.stringify({
223
+ jsonrpc: "2.0",
224
+ id: 2,
225
+ method: "tools/call",
226
+ params: surface.call({}),
227
+ }),
228
+ sessionId,
229
+ );
230
+ expect(status).toBe(200);
231
+ const result = payload as { result?: { isError?: boolean } };
232
+ expect(result.result).toBeDefined();
233
+ expect(result.result?.isError).not.toBe(true);
234
+ });
235
+
236
+ test("a tools/call over the element limit is rejected before the handler runs", async () => {
237
+ const headers = await surface.headers();
238
+ const sessionId = await openSession(surface.path, headers);
239
+ const { status, payload } = await post(
240
+ surface.path,
241
+ headers,
242
+ JSON.stringify({
243
+ jsonrpc: "2.0",
244
+ id: 3,
245
+ method: "tools/call",
246
+ params: surface.call({ tags: new Array(MCP_MAX_TOOL_INPUT_ELEMENTS + 1).fill("t") }),
247
+ }),
248
+ sessionId,
249
+ );
250
+ expect(status).toBe(200);
251
+ const result = payload as {
252
+ result: { isError: boolean; content: Array<{ type: string; text: string }> };
253
+ };
254
+ expect(result.result.isError).toBe(true);
255
+ expect(result.result.content[0]?.text).toContain(
256
+ `more than the maximum of ${MCP_MAX_TOOL_INPUT_ELEMENTS} elements`,
257
+ );
258
+ const tasks = await getDbClient().query<{ id: string }>("SELECT id FROM agent_tasks");
259
+ expect(tasks).toHaveLength(0);
260
+ });
261
+
262
+ test("a body over the cap with Content-Length gets 413 and a JSON-RPC error", async () => {
263
+ const headers = await surface.headers();
264
+ const oversized = JSON.stringify({
265
+ jsonrpc: "2.0",
266
+ id: 4,
267
+ method: "tools/call",
268
+ params: surface.call({ blob: "x".repeat(MCP_MAX_REQUEST_BODY_BYTES) }),
269
+ });
270
+ const { status, payload } = await post(surface.path, headers, oversized);
271
+ expect(status).toBe(413);
272
+ expect(payload).toEqual({
273
+ jsonrpc: "2.0",
274
+ error: { code: -32000, message: TOO_LARGE_MESSAGE },
275
+ id: null,
276
+ });
277
+ });
278
+
279
+ test("a chunked body that crosses the cap gets 413", async () => {
280
+ const headers = await surface.headers();
281
+ const { status, body } = await postChunked(
282
+ surface.path,
283
+ headers,
284
+ MCP_MAX_REQUEST_BODY_BYTES + 128 * 1024,
285
+ );
286
+ expect(status).toBe(413);
287
+ expect(JSON.parse(body)).toEqual({
288
+ jsonrpc: "2.0",
289
+ error: { code: -32000, message: TOO_LARGE_MESSAGE },
290
+ id: null,
291
+ });
292
+ });
293
+ });
294
+ }
@@ -52,7 +52,7 @@ describe("script MCP tools", () => {
52
52
  "script-upsert":
53
53
  'Typecheck and persist a TypeScript script to the swarm catalog under your agent scope (or global if you\'re a lead). Import `ScriptContext` from "swarm-sdk" for a real context type. Other agents and workflow nodes will be able to find and run it. For local-only scripts, use code-mode `save`.',
54
54
  "script-delete":
55
- "Remove a swarm-shared script from the catalog. Versions table preserves history.",
55
+ "Permanently remove a swarm-shared script and all its versions from the catalog. This cannot be undone.",
56
56
  "script-query-types":
57
57
  // Updated for the frozen SwarmToolResult contract refactor: `name` is
58
58
  // now optional — omit it to get the swarm-wide type surface instead
@@ -0,0 +1,200 @@
1
+ import { afterAll, afterEach, beforeAll, describe, expect, mock, test } from "bun:test";
2
+ import { randomUUID } from "node:crypto";
3
+ import { unlink } from "node:fs/promises";
4
+ import type { IncomingMessage, ServerResponse } from "node:http";
5
+ import { Readable } from "node:stream";
6
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7
+ import { closeDb, createAgent, initDb } from "../be/db";
8
+ import { taskCompletionMemoryKey } from "../be/memory/index-content";
9
+ import { SqliteMemoryStore } from "../be/memory/providers/sqlite-store";
10
+ import { countCandidateDocuments, searchMemoryCandidates } from "../be/memory/search-candidates";
11
+
12
+ // Candidate starvation (PR #2302 review): rerank() collapses the chunks of one
13
+ // task_completion document, but the store's candidate window (limit x 3) is
14
+ // cut before the collapse. 32 high-similarity chunks of one task filled the
15
+ // whole window, so limit 3 returned one result while five relevant manual
16
+ // memories sat past the cutoff. Everything below the query embedding is real:
17
+ // the SQLite store (vec and hybrid paths), the HTTP routes and the MCP tool.
18
+ //
19
+ // The 128-chunk document is the round-2 case: the hybrid and FTS arms capped
20
+ // their fetch at 100 rows, so widening past 100 returned the same rows and
21
+ // read as exhaustion while the manual memories sat behind chunk 100.
22
+
23
+ /** Unit vector: `cos` on axis 2 (the query axis), the remainder on `axis`. */
24
+ function mix(cos: number, axis: number): Float32Array {
25
+ const embedding = new Float32Array(512);
26
+ embedding[2] = cos;
27
+ embedding[axis] = Math.sqrt(1 - cos * cos);
28
+ return embedding;
29
+ }
30
+
31
+ const queryEmbedding = mix(1, 3);
32
+
33
+ mock.module("../be/memory", () => ({
34
+ getEmbeddingProvider: () => ({
35
+ name: "test",
36
+ dimensions: 512,
37
+ embed: async () => queryEmbedding,
38
+ embedBatch: async (texts: string[]) => texts.map(() => queryEmbedding),
39
+ isConfigured: () => true,
40
+ }),
41
+ getMemoryStore: () => new SqliteMemoryStore(),
42
+ }));
43
+
44
+ const { handleMemory } = await import("../http/memory");
45
+ const { registerMemorySearchTool } = await import("../tools/memory-search");
46
+
47
+ const TEST_DB_PATH = "./test-memory-chunked-search-starvation.sqlite";
48
+ const QUERY = "starvation probe";
49
+ const MANUAL_DOCS = 5;
50
+ const LIMIT = 3;
51
+ let prevHybridFlag: string | undefined;
52
+
53
+ /** One agent per chunk count: agent-scope rows keep the scenarios apart. */
54
+ const scenarios = [32, 128].map((chunks) => ({
55
+ chunks,
56
+ agentId: randomUUID(),
57
+ manualIds: new Set<string>(),
58
+ }));
59
+
60
+ async function postJson(agentId: string, url: string, segments: string[]): Promise<string[]> {
61
+ const body = { query: QUERY, limit: LIMIT };
62
+ const req = Readable.from([Buffer.from(JSON.stringify(body))]) as IncomingMessage;
63
+ req.method = "POST";
64
+ req.url = url;
65
+ req.headers = {};
66
+ let payload: { results: { id: string }[] } = { results: [] };
67
+ const res = {
68
+ writeHead() {
69
+ return this;
70
+ },
71
+ end(chunk?: unknown) {
72
+ payload = typeof chunk === "string" ? JSON.parse(chunk) : (chunk as typeof payload);
73
+ return this;
74
+ },
75
+ } as unknown as ServerResponse;
76
+ expect(await handleMemory(req, res, segments, agentId)).toBe(true);
77
+ return payload.results.map((r) => r.id);
78
+ }
79
+
80
+ const searchHttp = (agentId: string) =>
81
+ postJson(agentId, "/api/memory/search", ["api", "memory", "search"]);
82
+ const listHttp = (agentId: string) =>
83
+ postJson(agentId, "/api/memory/list", ["api", "memory", "list"]);
84
+
85
+ async function searchTool(agentId: string): Promise<string[]> {
86
+ const server = new McpServer({ name: "chunked-search-starvation", version: "1.0.0" });
87
+ registerMemorySearchTool(server);
88
+ const tool = (
89
+ server as unknown as {
90
+ _registeredTools: Record<
91
+ string,
92
+ {
93
+ handler: (
94
+ args: unknown,
95
+ extra: unknown,
96
+ ) => Promise<{ structuredContent: { results: { id: string }[] } }>;
97
+ }
98
+ >;
99
+ }
100
+ )._registeredTools["memory-search"]!;
101
+ const result = await tool.handler(
102
+ { query: QUERY, limit: LIMIT, scope: "all" },
103
+ { sessionId: "starvation", requestInfo: { headers: { "x-agent-id": agentId } } },
104
+ );
105
+ return result.structuredContent.results.map((r) => r.id);
106
+ }
107
+
108
+ beforeAll(async () => {
109
+ prevHybridFlag = process.env.MEMORY_HYBRID_SEARCH;
110
+ for (const suffix of ["", "-wal", "-shm"]) {
111
+ try {
112
+ await unlink(TEST_DB_PATH + suffix);
113
+ } catch {}
114
+ }
115
+ initDb(TEST_DB_PATH);
116
+ const store = new SqliteMemoryStore();
117
+ for (const { chunks, agentId, manualIds } of scenarios) {
118
+ await createAgent({
119
+ id: agentId,
120
+ name: `Starvation Agent ${chunks}`,
121
+ isLead: false,
122
+ status: "idle",
123
+ });
124
+ const taskKey = taskCompletionMemoryKey(randomUUID());
125
+ // One long task output, every chunk closer to the query than any manual
126
+ // memory.
127
+ for (let i = 0; i < chunks; i++) {
128
+ const chunk = await store.store({
129
+ agentId,
130
+ scope: "agent",
131
+ name: "Task: long dreaming run",
132
+ content: `${QUERY} chunk ${i}: ${"lane starvation probe finding ".repeat(4)}`,
133
+ source: "task_completion",
134
+ key: taskKey,
135
+ chunkIndex: i,
136
+ totalChunks: chunks,
137
+ });
138
+ await store.updateEmbedding(chunk.id, mix(0.99 - i * 0.0001, 10 + i), "test");
139
+ }
140
+ for (let j = 0; j < MANUAL_DOCS; j++) {
141
+ const doc = await store.store({
142
+ agentId,
143
+ scope: "agent",
144
+ name: `manual note ${j}`,
145
+ content: `${QUERY} manual note ${j}`,
146
+ source: "manual",
147
+ });
148
+ manualIds.add(doc.id);
149
+ await store.updateEmbedding(doc.id, mix(0.8 - j * 0.01, 300 + j), "test");
150
+ }
151
+ }
152
+ });
153
+
154
+ afterEach(() => {
155
+ if (prevHybridFlag === undefined) delete process.env.MEMORY_HYBRID_SEARCH;
156
+ else process.env.MEMORY_HYBRID_SEARCH = prevHybridFlag;
157
+ });
158
+
159
+ afterAll(async () => {
160
+ closeDb();
161
+ for (const suffix of ["", "-wal", "-shm"]) {
162
+ try {
163
+ await unlink(TEST_DB_PATH + suffix);
164
+ } catch {}
165
+ }
166
+ });
167
+
168
+ describe("chunks of one task do not starve the result set", () => {
169
+ for (const { chunks, agentId, manualIds } of scenarios) {
170
+ for (const hybrid of ["0", "1"]) {
171
+ for (const [surface, search] of [
172
+ ["POST /api/memory/search", searchHttp],
173
+ ["POST /api/memory/list", listHttp],
174
+ ["memory-search tool", searchTool],
175
+ ] as const) {
176
+ test(`${chunks} chunks, ${surface}, hybrid=${hybrid}: limit ${LIMIT} returns ${LIMIT} documents`, async () => {
177
+ process.env.MEMORY_HYBRID_SEARCH = hybrid;
178
+ const ids = await search(agentId);
179
+ expect(ids).toHaveLength(LIMIT);
180
+ expect(new Set(ids).size).toBe(LIMIT);
181
+ // At most one slot for the task document; the rest are manual memories.
182
+ expect(ids.filter((id) => !manualIds.has(id)).length).toBeLessThanOrEqual(1);
183
+ });
184
+ }
185
+ }
186
+ }
187
+
188
+ test("FTS-only path (no query embedding) widens past 100 chunks of one task", async () => {
189
+ const { agentId, manualIds } = scenarios[1]!;
190
+ // A missing query embedding routes the store to searchFts.
191
+ const candidates = await searchMemoryCandidates(
192
+ new SqliteMemoryStore(),
193
+ new Float32Array(0),
194
+ agentId,
195
+ { limit: LIMIT, queryText: QUERY, scope: "all" },
196
+ );
197
+ expect(countCandidateDocuments(candidates)).toBeGreaterThanOrEqual(LIMIT);
198
+ expect(candidates.filter((c) => manualIds.has(c.id)).length).toBeGreaterThan(0);
199
+ });
200
+ });
@@ -0,0 +1,114 @@
1
+ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
2
+ import {
3
+ MAX_EMBEDDING_INPUT_CHARS,
4
+ OpenAIEmbeddingProvider,
5
+ } from "../be/memory/providers/openai-embedding";
6
+
7
+ // A gateway that rejects any input over REJECT_OVER chars the way the OpenAI
8
+ // API rejects an input over 8,192 tokens: a 400 for the whole request.
9
+ const REJECT_OVER = 10_000;
10
+ let server: ReturnType<typeof Bun.serve>;
11
+ const requests: string[][] = [];
12
+ const previousBaseUrl = process.env.EMBEDDING_API_BASE_URL;
13
+
14
+ beforeAll(() => {
15
+ server = Bun.serve({
16
+ hostname: "127.0.0.1",
17
+ port: 0,
18
+ async fetch(req) {
19
+ const body = (await req.json()) as { input: string | string[] };
20
+ const inputs = Array.isArray(body.input) ? body.input : [body.input];
21
+ requests.push(inputs);
22
+ const tooLong = inputs.findIndex((input) => input.length > REJECT_OVER);
23
+ if (tooLong !== -1) {
24
+ const param = Array.isArray(body.input) ? `input[${tooLong}]` : "input";
25
+ return Response.json(
26
+ {
27
+ error: {
28
+ message: `Invalid '${param}': maximum input length is 8192 tokens.`,
29
+ type: "invalid_request_error",
30
+ param: null,
31
+ code: null,
32
+ },
33
+ },
34
+ { status: 400 },
35
+ );
36
+ }
37
+ return Response.json({
38
+ object: "list",
39
+ data: inputs.map((input, index) => ({
40
+ object: "embedding",
41
+ index,
42
+ embedding: [input.length, 0, 0, 1],
43
+ })),
44
+ model: "test-embedding-model",
45
+ usage: { prompt_tokens: 1, total_tokens: 1 },
46
+ });
47
+ },
48
+ });
49
+ process.env.EMBEDDING_API_BASE_URL = `http://127.0.0.1:${server.port}/v1`;
50
+ });
51
+
52
+ afterAll(() => {
53
+ server.stop(true);
54
+ if (previousBaseUrl === undefined) delete process.env.EMBEDDING_API_BASE_URL;
55
+ else process.env.EMBEDDING_API_BASE_URL = previousBaseUrl;
56
+ });
57
+
58
+ beforeEach(() => {
59
+ requests.length = 0;
60
+ });
61
+
62
+ function provider(): OpenAIEmbeddingProvider {
63
+ return new OpenAIEmbeddingProvider({
64
+ apiKey: "test-key",
65
+ model: "test-embedding-model",
66
+ dimensions: 4,
67
+ });
68
+ }
69
+
70
+ /** The embedded length, which the fake gateway echoes as the first component. */
71
+ function embeddedLength(vector: Float32Array | null): number | null {
72
+ return vector ? vector[0]! : null;
73
+ }
74
+
75
+ describe("embedding input cap", () => {
76
+ test("an over-cap input is cut down until the provider accepts it", async () => {
77
+ const vector = await provider().embed("x".repeat(50_000));
78
+
79
+ expect(embeddedLength(vector)).toBe(MAX_EMBEDDING_INPUT_CHARS / 4);
80
+ expect(requests.map((r) => r[0]!.length)).toEqual([
81
+ MAX_EMBEDDING_INPUT_CHARS,
82
+ MAX_EMBEDDING_INPUT_CHARS / 2,
83
+ MAX_EMBEDDING_INPUT_CHARS / 4,
84
+ ]);
85
+ });
86
+
87
+ test("no input is ever sent over the char cap", async () => {
88
+ await provider().embedBatch(["a".repeat(100_000), "b".repeat(30_000)]);
89
+ for (const request of requests) {
90
+ for (const input of request)
91
+ expect(input.length).toBeLessThanOrEqual(MAX_EMBEDDING_INPUT_CHARS);
92
+ }
93
+ });
94
+
95
+ test("one oversized input in a batch does not fail the other inputs", async () => {
96
+ const vectors = await provider().embedBatch(["small one", "y".repeat(40_000), "small two"]);
97
+
98
+ expect(vectors.map(embeddedLength)).toEqual([
99
+ "small one".length,
100
+ MAX_EMBEDDING_INPUT_CHARS / 4,
101
+ "small two".length,
102
+ ]);
103
+ // The whole batch first, then each input alone after the rejection.
104
+ expect(requests[0]).toHaveLength(3);
105
+ expect(requests.slice(1).every((r) => r.length === 1)).toBe(true);
106
+ });
107
+
108
+ test("a batch with no oversized input goes out as one request", async () => {
109
+ const vectors = await provider().embedBatch(["one", "", "three"]);
110
+
111
+ expect(vectors.map(embeddedLength)).toEqual([3, null, 5]);
112
+ expect(requests).toEqual([["one", "three"]]);
113
+ });
114
+ });
@@ -214,6 +214,20 @@ describe("Memory System", () => {
214
214
  expect(xChars).toBeGreaterThanOrEqual(2500);
215
215
  });
216
216
 
217
+ test("keeps sections under MIN_CHUNK_SIZE, packed into shared chunks", () => {
218
+ const preamble = "Report preamble with enough words to stand as its own chunk here.";
219
+ const sections = Array.from({ length: 100 }, (_, i) => `## Lane ${i}\n\nfact-${i} ok`);
220
+ const chunks = chunkContent([preamble, ...sections].join("\n\n"));
221
+
222
+ const stored = chunks.map((c) => c.content).join("\n");
223
+ for (let i = 0; i < 100; i++) {
224
+ expect(stored).toContain(`## Lane ${i}\n\nfact-${i} ok`);
225
+ }
226
+ expect(chunks[0]!.content).toBe(preamble);
227
+ expect(chunks.length).toBeLessThan(6);
228
+ for (const chunk of chunks) expect(chunk.content.length).toBeLessThanOrEqual(2000);
229
+ });
230
+
217
231
  test("chunk indices are sequential", () => {
218
232
  const longText = "A ".repeat(3000);
219
233
  const chunks = chunkContent(longText);
@@ -34,6 +34,7 @@ const ENV_KEYS = [
34
34
  "EMBEDDING_MODEL",
35
35
  "GITHUB_TOKEN",
36
36
  "GITLAB_TOKEN",
37
+ "BITBUCKET_TOKEN",
37
38
  "SLACK_BOT_TOKEN",
38
39
  "SLACK_APP_TOKEN",
39
40
  "SLACK_MODE",
@@ -380,6 +381,17 @@ describe("onboarding state", () => {
380
381
  expect(body.state.steps.integrations).toMatchObject({ status: "done", method: "github" });
381
382
  });
382
383
 
384
+ test("GET derives the Bitbucket integration from BITBUCKET_TOKEN", async () => {
385
+ process.env.BITBUCKET_TOKEN = "example-bitbucket-token";
386
+ const body = (await (await request("/api/onboarding")).json()) as {
387
+ state: OnboardingState;
388
+ signals: { integrations: Record<string, boolean> };
389
+ };
390
+
391
+ expect(body.signals.integrations.bitbucket).toBe(true);
392
+ expect(body.state.steps.integrations).toMatchObject({ status: "done", method: "bitbucket" });
393
+ });
394
+
383
395
  test("the selected first task completes onboarding after that task completes", async () => {
384
396
  // Onboarding starts before the first task exists (the task alone would mark an existing install).
385
397
  await request("/api/onboarding");