@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,15 @@
1
+ import type { Attachment, FileUpload } from "chat";
1
2
  import { scrubSecrets } from "@/utils/secret-scrubber";
2
3
  import { workflowEventBus } from "@/workflows/event-bus";
3
- import { chatMessageLimit, splitChatMessage } from "./limits";
4
+ import { formatFileSize } from "./attachments";
5
+ import {
6
+ CHAT_FILES_PER_MESSAGE,
7
+ chatMessageLimit,
8
+ chatUploadLimit,
9
+ splitChatMessage,
10
+ TELEGRAM_PHOTO_LIMIT,
11
+ } from "./limits";
12
+ import { errorMessage } from "./logger";
4
13
  import { getChatInstance } from "./runtime";
5
14
  import { assertTrustedChatTarget } from "./teams-origin";
6
15
 
@@ -39,12 +48,76 @@ export interface ChatPostResult {
39
48
  adapter: string;
40
49
  threadId: string;
41
50
  channelId: string;
42
- /** One id per chunk, in send order. */
51
+ /** One id per chunk and per file message, in send order. */
43
52
  messageIds: string[];
53
+ /** Files that did not reach the thread. The thread got a note naming each one. */
54
+ unsentFiles: UnsentChatFile[];
55
+ }
56
+
57
+ /** A file to send after the text. `load` reads its bytes. */
58
+ export interface OutboundChatFile {
59
+ name: string;
60
+ mimeType?: string;
61
+ /** Size known before loading, so an oversized file is never read. */
62
+ sizeBytes?: number;
63
+ /** Throws `ChatFileTooLargeError` when the file is larger than `maxBytes`. */
64
+ load(maxBytes: number): Promise<{ data: Uint8Array; mimeType?: string }>;
65
+ }
66
+
67
+ export interface UnsentChatFile {
68
+ name: string;
69
+ reason: string;
70
+ }
71
+
72
+ export class ChatFileTooLargeError extends Error {
73
+ constructor(readonly sizeBytes: number) {
74
+ super(`file is ${formatFileSize(sizeBytes)}`);
75
+ this.name = "ChatFileTooLargeError";
76
+ }
44
77
  }
45
78
 
46
79
  interface PostTarget {
47
- post(message: { markdown: string }): Promise<{ id: string; threadId?: string }>;
80
+ post(message: {
81
+ markdown: string;
82
+ files?: FileUpload[];
83
+ attachments?: Attachment[];
84
+ }): Promise<{ id: string; threadId?: string }>;
85
+ }
86
+
87
+ /** A fence opener or closer: up to 3 spaces, then 3+ backticks or tildes. */
88
+ const FENCE_LINE = /^ {0,3}(`{3,}|~{3,})/;
89
+ /**
90
+ * A `#` run at the start of a line's text (after blockquote, list and task
91
+ * markers) that CommonMark does not read as a heading: 7+ `#`, or `#` followed
92
+ * by a character other than whitespace or `#`.
93
+ */
94
+ const NON_HEADING_HASH =
95
+ /^( {0,3}(?:>[ \t]?)*[ \t]*(?:(?:[-*+]|\d{1,9}[.)])[ \t]+(?:\[[ xX]\][ \t]+)?)?)(#{7,}|#{1,6}(?=[^\s#]))/;
96
+
97
+ /**
98
+ * Telegram renders `{ markdown }` posts with `sendRichMessage`, and Telegram's
99
+ * own Markdown parser reads a line-leading `#2203` or `#tag` as a heading.
100
+ * Escape the first `#` of such a line so only CommonMark ATX headings
101
+ * (`# Title`) stay headings. Fenced code is left as is. The MarkdownV2
102
+ * fallback parses the escape back to a literal `#`.
103
+ */
104
+ export function escapeTelegramHashLines(markdown: string): string {
105
+ let fence: string | undefined;
106
+ return markdown
107
+ .split("\n")
108
+ .map((line) => {
109
+ const marker = FENCE_LINE.exec(line)?.[1];
110
+ if (fence) {
111
+ if (marker && marker[0] === fence[0] && marker.length >= fence.length) fence = undefined;
112
+ return line;
113
+ }
114
+ if (marker) {
115
+ fence = marker;
116
+ return line;
117
+ }
118
+ return line.replace(NON_HEADING_HASH, "$1\\$2");
119
+ })
120
+ .join("\n");
48
121
  }
49
122
 
50
123
  async function postChunks(
@@ -55,17 +128,194 @@ async function postChunks(
55
128
  const messageIds: string[] = [];
56
129
  let threadId: string | undefined;
57
130
  for (const chunk of splitChatMessage(text, chatMessageLimit(adapter))) {
58
- const sent = await target.post({ markdown: chunk });
131
+ const markdown = adapter === "telegram" ? escapeTelegramHashLines(chunk) : chunk;
132
+ const sent = await target.post({ markdown });
59
133
  messageIds.push(sent.id);
60
134
  threadId ??= sent.threadId;
61
135
  }
62
136
  return { messageIds, threadId };
63
137
  }
64
138
 
139
+ type LoadedFile = { name: string; mimeType: string; data: Buffer };
140
+
141
+ /** First usable MIME type, else a guess from the file extension. */
142
+ function resolveMimeType(name: string, ...candidates: (string | null | undefined)[]): string {
143
+ for (const candidate of candidates) {
144
+ const type = candidate?.split(";")[0]?.trim().toLowerCase();
145
+ if (type && type !== "application/octet-stream") return type;
146
+ }
147
+ return Bun.file(name).type.split(";")[0] ?? "application/octet-stream";
148
+ }
149
+
150
+ /**
151
+ * How Telegram shows a file. Only types its media methods take are sent as
152
+ * photo, video or audio; everything else, and a photo over 10 MB, goes as a
153
+ * document.
154
+ */
155
+ export function telegramAttachmentType(mimeType: string, sizeBytes: number): Attachment["type"] {
156
+ if (
157
+ (mimeType === "image/jpeg" || mimeType === "image/png") &&
158
+ sizeBytes <= TELEGRAM_PHOTO_LIMIT
159
+ ) {
160
+ return "image";
161
+ }
162
+ if (mimeType === "video/mp4") return "video";
163
+ if (mimeType === "audio/mpeg" || mimeType === "audio/mp4" || mimeType === "audio/x-m4a") {
164
+ return "audio";
165
+ }
166
+ return "file";
167
+ }
168
+
169
+ /**
170
+ * Telegram media groups mix photos and videos only; documents and audio group
171
+ * with their own kind. Other adapters put any files in one message.
172
+ */
173
+ function fileGroupKey(adapter: string, file: LoadedFile): string {
174
+ if (adapter !== "telegram") return "files";
175
+ const type = telegramAttachmentType(file.mimeType, file.data.byteLength);
176
+ return type === "image" || type === "video" ? "visual" : type;
177
+ }
178
+
179
+ function fileMessage(adapter: string, files: LoadedFile[]) {
180
+ // Telegram uploads `files` only as documents; typed `attachments` become
181
+ // photos, videos and audio. A list of 2 to 10 is sent as one media group.
182
+ if (adapter === "telegram") {
183
+ return {
184
+ markdown: "",
185
+ attachments: files.map(
186
+ (file): Attachment => ({
187
+ type: telegramAttachmentType(file.mimeType, file.data.byteLength),
188
+ name: file.name,
189
+ mimeType: file.mimeType,
190
+ size: file.data.byteLength,
191
+ data: file.data,
192
+ }),
193
+ ),
194
+ };
195
+ }
196
+ return {
197
+ markdown: "",
198
+ files: files.map((file) => ({ data: file.data, filename: file.name, mimeType: file.mimeType })),
199
+ };
200
+ }
201
+
202
+ function platformName(adapter: string): string {
203
+ return adapter.charAt(0).toUpperCase() + adapter.slice(1);
204
+ }
205
+
206
+ /**
207
+ * Send files as file messages, up to 10 per message, grouped the way the
208
+ * platform allows. Files are read one by one and a group is sent once full, so
209
+ * at most one group is held in memory. Never throws: a file that is too large,
210
+ * cannot be read, or is refused by the platform lands in `unsent`.
211
+ */
212
+ async function postFiles(
213
+ adapter: string,
214
+ target: PostTarget,
215
+ files: OutboundChatFile[],
216
+ ): Promise<{ messageIds: string[]; unsent: UnsentChatFile[] }> {
217
+ const limit = chatUploadLimit(adapter);
218
+ const tooLarge = (size: number) =>
219
+ `${formatFileSize(size)}, over the ${formatFileSize(limit)} upload limit of ${platformName(adapter)}`;
220
+ const messageIds: string[] = [];
221
+ const unsent: UnsentChatFile[] = [];
222
+ const groups = new Map<string, LoadedFile[]>();
223
+
224
+ const send = async (group: LoadedFile[]) => {
225
+ try {
226
+ const sent = await target.post(fileMessage(adapter, group));
227
+ messageIds.push(sent.id);
228
+ } catch (error) {
229
+ const reason = `refused by ${platformName(adapter)} (${scrubSecrets(errorMessage(error))})`;
230
+ for (const file of group) unsent.push({ name: file.name, reason });
231
+ }
232
+ };
233
+
234
+ for (const file of files) {
235
+ if (file.sizeBytes !== undefined && file.sizeBytes > limit) {
236
+ unsent.push({ name: file.name, reason: tooLarge(file.sizeBytes) });
237
+ continue;
238
+ }
239
+ let loaded: LoadedFile;
240
+ try {
241
+ const { data, mimeType } = await file.load(limit);
242
+ if (data.byteLength > limit) throw new ChatFileTooLargeError(data.byteLength);
243
+ loaded = {
244
+ name: file.name,
245
+ mimeType: resolveMimeType(file.name, file.mimeType, mimeType),
246
+ data: Buffer.from(data.buffer, data.byteOffset, data.byteLength),
247
+ };
248
+ } catch (error) {
249
+ unsent.push({
250
+ name: file.name,
251
+ reason:
252
+ error instanceof ChatFileTooLargeError
253
+ ? tooLarge(error.sizeBytes)
254
+ : `could not be read (${scrubSecrets(errorMessage(error))})`,
255
+ });
256
+ continue;
257
+ }
258
+ const key = fileGroupKey(adapter, loaded);
259
+ const group = [...(groups.get(key) ?? []), loaded];
260
+ if (group.length === CHAT_FILES_PER_MESSAGE) {
261
+ groups.delete(key);
262
+ await send(group);
263
+ } else {
264
+ groups.set(key, group);
265
+ }
266
+ }
267
+ for (const group of groups.values()) await send(group);
268
+ return { messageIds, unsent };
269
+ }
270
+
271
+ /** Tool-result suffix naming the files that did not go out; empty when all did. */
272
+ export function unsentFilesSummary(unsent: UnsentChatFile[]): string {
273
+ return unsent.length === 0 ? "" : ` ${formatUnsentFilesNote(unsent)}`;
274
+ }
275
+
276
+ /** One line of untrusted text: control characters and line breaks become spaces. */
277
+ function singleLine(text: string): string {
278
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: stripping them is the point
279
+ return text.replace(/[\u0000-\u001f\u007f\u2028\u2029]+/g, " ").trim();
280
+ }
281
+
282
+ /** A zero-width space after `@` and `<!` keeps adapters from turning text into mentions. */
283
+ function breakMentions(text: string): string {
284
+ return text.replace(/@/g, "@\u200b").replace(/<!/g, "<\u200b!");
285
+ }
286
+
287
+ /**
288
+ * A file name as a Markdown code span, so it renders literally: no emphasis,
289
+ * links, or mentions. The fence is one backtick longer than the longest run
290
+ * inside, and the padding keeps an edge backtick from joining the fence.
291
+ */
292
+ function codeSpan(text: string): string {
293
+ const value = breakMentions(singleLine(text)) || "(unnamed)";
294
+ const longestRun = Math.max(0, ...(value.match(/`+/g) ?? []).map((run) => run.length));
295
+ const fence = "`".repeat(longestRun + 1);
296
+ const pad = value.startsWith("`") || value.endsWith("`") ? " " : "";
297
+ return `${fence}${pad}${value}${pad}${fence}`;
298
+ }
299
+
300
+ /**
301
+ * The note posted after the files when some did not go out. File names come
302
+ * from senders and agents, so they render as code spans and cannot add
303
+ * formatting or mentions to the bot's message.
304
+ */
305
+ export function formatUnsentFilesNote(unsent: UnsentChatFile[]): string {
306
+ const lines = unsent.map(
307
+ (file) => `- ${codeSpan(file.name)}: ${breakMentions(singleLine(file.reason))}`,
308
+ );
309
+ const count = unsent.length === 1 ? "1 file" : `${unsent.length} files`;
310
+ return [`Could not send ${count}:`, ...lines].join("\n");
311
+ }
312
+
65
313
  /**
66
314
  * Post markdown to a Chat SDK thread or channel. Scrubs the text, splits it by
67
315
  * the adapter limit, and emits one `chat.message.sent` for the whole message
68
- * once every chunk was accepted. Throws `ChatAdapterUnavailableError` when the
316
+ * once every chunk was accepted. `files` go out after the text; a file that
317
+ * does not reach the thread is named in a note there and in `unsentFiles`,
318
+ * and does not fail the call. Throws `ChatAdapterUnavailableError` when the
69
319
  * adapter is not running, `UntrustedTeamsServiceUrlError` before any send
70
320
  * when a Teams id points at a host that is not Bot Framework, and the adapter
71
321
  * error when a send fails.
@@ -80,6 +330,7 @@ export async function postChatMessage(args: {
80
330
  channelId?: string;
81
331
  text: string;
82
332
  taskId?: string;
333
+ files?: OutboundChatFile[];
83
334
  }): Promise<ChatPostResult> {
84
335
  // Ids come from tools and stored context keys. Check the Teams origin
85
336
  // before the adapter sends its token.
@@ -89,28 +340,44 @@ export async function postChatMessage(args: {
89
340
  const text = scrubSecrets(args.text);
90
341
 
91
342
  let result: ChatPostResult;
343
+ let target: PostTarget;
92
344
  if (args.threadId) {
93
345
  const thread = chat.thread(args.threadId);
346
+ target = thread;
94
347
  const sent = await postChunks(args.adapter, thread, text);
95
348
  result = {
96
349
  adapter: args.adapter,
97
350
  threadId: thread.id,
98
351
  channelId: thread.channelId,
99
352
  messageIds: sent.messageIds,
353
+ unsentFiles: [],
100
354
  };
101
355
  } else if (args.channelId) {
102
356
  const channel = chat.channel(args.channelId);
357
+ target = channel;
103
358
  const sent = await postChunks(args.adapter, channel, text);
104
359
  result = {
105
360
  adapter: args.adapter,
106
361
  threadId: sent.threadId ?? channel.id,
107
362
  channelId: channel.id,
108
363
  messageIds: sent.messageIds,
364
+ unsentFiles: [],
109
365
  };
110
366
  } else {
111
367
  throw new Error("postChatMessage needs a threadId or a channelId");
112
368
  }
113
369
 
370
+ if (args.files?.length) {
371
+ const sent = await postFiles(args.adapter, target, args.files);
372
+ result.messageIds.push(...sent.messageIds);
373
+ result.unsentFiles = sent.unsent;
374
+ if (sent.unsent.length > 0) {
375
+ const note = formatUnsentFilesNote(sent.unsent);
376
+ const noted = await postChunks(args.adapter, target, scrubSecrets(note));
377
+ result.messageIds.push(...noted.messageIds);
378
+ }
379
+ }
380
+
114
381
  emitChatMessageSent({
115
382
  adapter: result.adapter,
116
383
  channelId: result.channelId,
@@ -12,6 +12,7 @@ import { scrubSecrets } from "@/utils/secret-scrubber";
12
12
  import { workflowEventBus } from "@/workflows/event-bus";
13
13
  import { errorMessage } from "./logger";
14
14
  import { postChatMessage } from "./outbound";
15
+ import { taskOutputChatFiles } from "./outbound-files";
15
16
  import { getChatInstance, getChatSdkHealth } from "./runtime";
16
17
  import { sweepExpired } from "./state";
17
18
  import { stopTypingKeepalive } from "./typing";
@@ -49,7 +50,8 @@ export function formatChatRelayText(task: AgentTask): string {
49
50
  *
50
51
  * Delivery is at-least-once: a chunk failure re-posts the earlier chunks on
51
52
  * retry, and a crash between the send and `markChatRelayDelivered` re-sends the
52
- * message.
53
+ * message. The task's output attachments go after the text; a file that cannot
54
+ * be sent gets a note in the thread and does not fail the delivery.
53
55
  *
54
56
  * Once `signal` aborts, no further row is claimed. A send already in flight is
55
57
  * not cancelled (`postChatMessage` takes no signal); its row keeps the claim
@@ -99,6 +101,7 @@ export async function processPendingChatRelays(
99
101
  threadId: target.threadId,
100
102
  text: formatChatRelayText(task),
101
103
  taskId: task.id,
104
+ files: await taskOutputChatFiles(task.id),
102
105
  });
103
106
  stopTypingKeepalive(target.threadId);
104
107
  await markChatRelayDelivered(task.id, now);
@@ -83,6 +83,11 @@ export async function deleteChatChannelMapping(
83
83
  return deleteKv(CHAT_CHANNELS_NAMESPACE, chatChannelKey(adapter, channelId));
84
84
  }
85
85
 
86
+ /** True when the text carries a `swarm#<uuid>` or `swarm#all` routing token. */
87
+ export function hasRoutingToken(text: string): boolean {
88
+ return /swarm#(?:[a-f0-9-]{36}|all)/i.test(text);
89
+ }
90
+
86
91
  /** Remove `swarm#<uuid>` and `swarm#all` routing tokens from task text. */
87
92
  export function stripRoutingTokens(text: string): string {
88
93
  return text
@@ -91,6 +96,20 @@ export function stripRoutingTokens(text: string): string {
91
96
  .trim();
92
97
  }
93
98
 
99
+ const DEFAULT_OWNER_WINDOW_MINUTES = 15;
100
+
101
+ /**
102
+ * Start of the thread-owner window: a thread keeps its owner while the
103
+ * owner's task is open and for `CHAT_SDK_OWNER_WINDOW_MIN` (default 15,
104
+ * 0 = only while open) after it ends. Read per call, so a change applies at once.
105
+ */
106
+ export function chatOwnerWindowStart(now: number = Date.now()): string {
107
+ const raw = process.env.CHAT_SDK_OWNER_WINDOW_MIN?.trim();
108
+ const value = raw ? Number(raw) : Number.NaN;
109
+ const minutes = Number.isFinite(value) && value >= 0 ? value : DEFAULT_OWNER_WINDOW_MINUTES;
110
+ return new Date(now - minutes * 60_000).toISOString();
111
+ }
112
+
94
113
  async function leadMatch(reachable: Agent[], matchedText: string): Promise<ChatAgentMatch> {
95
114
  const lead = reachable.find((agent) => agent.isLead) ?? (await getLeadAgent());
96
115
  return { agent: lead, reason: "skill", matchedText };
@@ -99,7 +118,8 @@ async function leadMatch(reachable: Agent[], matchedText: string): Promise<ChatA
99
118
  /**
100
119
  * Port of `src/slack/router.ts` for Chat SDK threads. Order: `swarm#<uuid>`,
101
120
  * `swarm#all`, KV channel mapping (agent, or workflow for a mention or DM), the
102
- * thread owner (lead when the owner is offline), the lead for a mention or DM.
121
+ * thread owner (inside its window in a Telegram group; lead when the owner is
122
+ * offline), the lead for a mention or DM.
103
123
  * Returns null when the message is not addressed to the swarm.
104
124
  */
105
125
  export async function routeChatMessage(
@@ -148,7 +168,13 @@ export async function routeChatMessage(
148
168
  );
149
169
  }
150
170
 
151
- const owner = await getAgentWorkingOnContextKey(chatSdkContextKey(ctx.threadId), "chat");
171
+ // Only a Telegram group expires its owner: one shared conversation, where a
172
+ // stale owner would catch the next topic. DMs and threads keep theirs.
173
+ const owner = await getAgentWorkingOnContextKey(
174
+ chatSdkContextKey(ctx.threadId),
175
+ "chat",
176
+ ctx.adapter === "telegram" && !ctx.isDirectMessage ? chatOwnerWindowStart() : undefined,
177
+ );
152
178
  if (owner && (await isAgentReachable(owner))) {
153
179
  return {
154
180
  kind: "agents",
@@ -14,15 +14,23 @@ export interface ChatThreadSteeringResult {
14
14
  * rule as the Slack default (`SLACK_THREAD_STEERING=lead`, queue mode) as
15
15
  * constants: `isSteeringEnabled()` is the only switch. Returns null when the
16
16
  * caller should buffer the message into a follow-up task instead.
17
+ *
18
+ * Only the task's own requester steers it: a steering row carries the task's
19
+ * attribution, and an undeliverable one is promoted to a follow-up of this
20
+ * task. `resolveSender` returns the sender's canonical user, undefined when
21
+ * unknown; it runs only when there is a task to steer.
17
22
  */
18
23
  export async function requestChatThreadSteering(args: {
19
24
  contextKey: string;
20
25
  message: string;
26
+ resolveSender: () => Promise<string | undefined>;
21
27
  }): Promise<ChatThreadSteeringResult | null> {
22
28
  if (!isSteeringEnabled()) return null;
23
29
 
24
30
  const task = await getLatestLeadTaskByContextKey(args.contextKey, "chat");
25
31
  if (!task || task.status !== "in_progress") return null;
32
+ const senderUserId = await args.resolveSender();
33
+ if (senderUserId !== task.requestedByUserId) return null;
26
34
 
27
35
  try {
28
36
  const result = await requestSteering({
@@ -32,6 +40,7 @@ export async function requestChatThreadSteering(args: {
32
40
  onUnsupported: "degrade",
33
41
  source: "chat",
34
42
  createdByKind: "user",
43
+ createdByUserId: senderUserId,
35
44
  });
36
45
  return { task, result };
37
46
  } catch (error) {
@@ -14,3 +14,17 @@ registerTemplate({
14
14
  ],
15
15
  category: "system",
16
16
  });
17
+
18
+ registerTemplate({
19
+ eventType: "chat.message.header",
20
+ header: "",
21
+ defaultBody: "[chat] {{chat_facts}}",
22
+ variables: [
23
+ {
24
+ name: "chat_facts",
25
+ description:
26
+ "Adapter, chat type and title, forum topic, sender, why the message was taken (addressed) and the replied-to snippet, joined with ' · '",
27
+ },
28
+ ],
29
+ category: "system",
30
+ });
@@ -1,6 +1,7 @@
1
1
  import type { Attachment } from "chat";
2
2
  import { type AdditiveBuffer, createAdditiveBuffer } from "@/tasks/additive-buffer";
3
3
  import { trackBackground } from "./background";
4
+ import type { ChatAddressedReason, ChatMessageFacts } from "./facts";
4
5
  import { flushChatThread } from "./inbound";
5
6
  import { stopTypingKeepalive } from "./typing";
6
7
 
@@ -14,6 +15,9 @@ export interface ChatBufferedMessage {
14
15
  userName?: string;
15
16
  botMentioned: boolean;
16
17
  isDirectMessage: boolean;
18
+ /** Why the message was taken; `none` when it is unaddressed. */
19
+ addressed: ChatAddressedReason;
20
+ facts: ChatMessageFacts;
17
21
  attachments: Attachment[];
18
22
  }
19
23
 
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Input caps shared by both MCP surfaces (`/mcp` and `/mcp-user`).
3
+ *
4
+ * The SDK leaves both bounds off when you build the server yourself
5
+ * (GHSA-46jr-4wx8-v453): `McpServer` only counts `tools/call` arguments when
6
+ * `maxToolInputElements` is set, and our HTTP handlers read the request body
7
+ * themselves, so the transport's own body cap never runs.
8
+ */
9
+
10
+ /**
11
+ * Largest combined number of array elements and object members a single
12
+ * `tools/call` `arguments` payload may contain. Sized for the largest
13
+ * legitimate call: `app-rows-create` takes up to 500 rows, which is about
14
+ * 24,000 elements at 48 fields per row. Script SDK calls go through
15
+ * `/api/mcp-bridge`, which does not apply it.
16
+ */
17
+ export const MCP_MAX_TOOL_INPUT_ELEMENTS = 25_000;
18
+
19
+ /** Largest MCP HTTP request body we read, in bytes. Matches the SDK default (4 MiB). */
20
+ export const MCP_MAX_REQUEST_BODY_BYTES = 4 * 1024 * 1024;
@@ -2,7 +2,7 @@
2
2
  //
3
3
  // Exposes a domain-grouped API on `window.SwarmSDK` (class) and a ready-to-use
4
4
  // singleton `window.swarmSdk`. All calls route through the `/@swarm/api/*`
5
- // proxy, which strips the page-session cookie and forwards to `/api/*` with
5
+ // proxy, which strips the page's own session cookie and forwards to `/api/*` with
6
6
  // a server-side bearer and signed page session. Memory operations use the
7
7
  // page owner's agent scope. Viewer identity and permissions remain separate.
8
8
  // The browser never handles the server bearer.
@@ -30,10 +30,15 @@ class SwarmSDK {
30
30
  this.channel = async (name) => (await loadRealtime()).channel(name);
31
31
 
32
32
  const base = '/@swarm/api';
33
+ // Each page has its own session cookie. Naming the page tells the proxy
34
+ // which one to read; the cookie's signed payload must still match it.
35
+ const pathParts = typeof location === 'undefined' ? [] : location.pathname.split('/');
36
+ const pageId = pathParts[1] === 'p' && pathParts[2] ? pathParts[2].split('.')[0] : '';
33
37
  const call = async (method, path, body) => {
34
- const init = { method };
38
+ const init = { method, headers: {} };
39
+ if (pageId) init.headers['X-Swarm-Page-Id'] = pageId;
35
40
  if (body !== undefined) {
36
- init.headers = { 'Content-Type': 'application/json' };
41
+ init.headers['Content-Type'] = 'application/json';
37
42
  init.body = JSON.stringify(body);
38
43
  }
39
44
  const res = await fetch(base + path, init);
@@ -415,7 +415,7 @@ const FEEDBACK_OVERLAY_JS = `
415
415
  return fetch('/@swarm/api/pages/' + encodeURIComponent(pageId) + '/feedback', {
416
416
  method: 'POST',
417
417
  credentials: 'same-origin',
418
- headers: { 'Content-Type': 'application/json' },
418
+ headers: { 'Content-Type': 'application/json', 'X-Swarm-Page-Id': pageId },
419
419
  body: JSON.stringify(payload),
420
420
  }).then(function (res) {
421
421
  return res.text().then(function (text) {
@@ -359,7 +359,7 @@ const copilotTargetProfile: AcpTargetProfile = {
359
359
  },
360
360
  };
361
361
 
362
- /** Qwen Code's `--auth-type` choices (0.25.3). `qwen-oauth` needs a browser, so it is left out. */
362
+ /** Qwen Code's `--auth-type` choices (0.26.0). `qwen-oauth` needs a browser, so it is left out. */
363
363
  const QWEN_AUTH_TYPES = ["openai", "openai-responses", "anthropic", "gemini", "vertex-ai"] as const;
364
364
 
365
365
  // Qwen Code is a Gemini CLI fork: QWEN_SYSTEM_MD replaces its built-in prompt
@@ -44,7 +44,7 @@ import type {
44
44
  * default; the image installs exactly this version and the per-task settings
45
45
  * set `amp.updates.mode: "disabled"`. Bump it deliberately, with a live run.
46
46
  */
47
- export const AMP_PACKAGE = "@ampcode/cli@0.0.1792506219-g025e7e";
47
+ export const AMP_PACKAGE = "@ampcode/cli@0.0.1792670445-ge8d5b3";
48
48
 
49
49
  /** MCP server name. Amp exposes its tools as `agent_swarm` / `store_progress` (dashes become underscores). */
50
50
  const AMP_MCP_SERVER_NAME = "agent-swarm";