@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,164 @@
1
+ import { keyPrefixRange } from "../../assets/key";
2
+ import type { AssetEntityType } from "../../types";
3
+ import type { AssetKeyAuditResult } from "../asset-key-audit";
4
+ import { isInTransaction } from "../db-client";
5
+ import { getDbClient } from "./runtime";
6
+ import { sessionViewerCondition } from "./session-visibility";
7
+ import { getTaskById } from "./tasks/read";
8
+
9
+ type SessionMoveDependencies = {
10
+ /** The sync boot audit (`auditAssetKeys`) over the raw handle, which only db.ts may use. */
11
+ auditAssetKeys: () => AssetKeyAuditResult;
12
+ insertAssetKeyHistory: (input: {
13
+ entityType: AssetEntityType;
14
+ entityId: string;
15
+ previousKey?: string | null;
16
+ newKey: string;
17
+ changedBy?: string;
18
+ }) => Promise<void>;
19
+ };
20
+
21
+ let insertAssetKeyHistory: SessionMoveDependencies["insertAssetKeyHistory"];
22
+ let auditAssetKeys: SessionMoveDependencies["auditAssetKeys"];
23
+
24
+ export function configureSessionMoveDependencies(value: SessionMoveDependencies): void {
25
+ insertAssetKeyHistory = value.insertAssetKeyHistory;
26
+ auditAssetKeys = value.auditAssetKeys;
27
+ }
28
+
29
+ export class AssetKeyMovesBlockedError extends Error {
30
+ constructor() {
31
+ super(
32
+ "Asset namespace moves are blocked until structural errors, unknown personal users, or provider mapping drift are repaired.",
33
+ );
34
+ this.name = "AssetKeyMovesBlockedError";
35
+ }
36
+ }
37
+
38
+ export async function assertAssetKeyMovesAllowed(): Promise<void> {
39
+ // auditAssetKeys stays sync (shared with the boot audit); run it inside a
40
+ // client transaction so this request-path read cannot observe another
41
+ // request's uncommitted writes on the shared connection.
42
+ const audit = await getDbClient().transaction(async () => auditAssetKeys(), {
43
+ readOnly: true,
44
+ });
45
+ if (!audit.ok) {
46
+ throw new AssetKeyMovesBlockedError();
47
+ }
48
+ }
49
+
50
+ /** Move only authorized task ids. The caller owns the transaction and audit gate. */
51
+ export async function moveTaskChainKeyInTx(
52
+ rootId: string,
53
+ taskIds: readonly string[],
54
+ key: string,
55
+ changedBy?: string,
56
+ ): Promise<string[] | false> {
57
+ if (!isInTransaction()) {
58
+ throw new Error("moveTaskChainKeyInTx requires an active transaction");
59
+ }
60
+ const client = getDbClient();
61
+ const now = new Date().toISOString();
62
+ const root = await getTaskById(rootId);
63
+ if (!root) return false;
64
+ const tasks = [root];
65
+ for (const id of taskIds) {
66
+ const task = id === root.id ? null : await getTaskById(id);
67
+ if (task) tasks.push(task);
68
+ }
69
+ const movedTaskIds: string[] = [];
70
+ for (const task of tasks) {
71
+ if (task.key === key) continue;
72
+ // A file shared with another task keeps the namespace of the task whose attachment
73
+ // sources its mapping. Mappings with no attachment source follow any attaching task.
74
+ const mappedFiles = await client.query<{ id: string; key: string }>(
75
+ `SELECT DISTINCT m.id, m."key" AS key
76
+ FROM asset_key_mappings m
77
+ JOIN task_attachments a
78
+ ON m.provider_id = COALESCE(NULLIF(a.provider_id, ''), 'agent-fs')
79
+ AND m.provider_org_id = COALESCE(a.agent_fs_org_id, '')
80
+ AND m.provider_drive_id = COALESCE(a.agent_fs_drive_id, '')
81
+ AND m.provider_key = COALESCE(NULLIF(a.provider_key, ''), a.path)
82
+ WHERE a.task_id = ?
83
+ AND (m.source_entity_type IS NOT 'task-attachment'
84
+ OR m.source_entity_id IN (SELECT id FROM task_attachments WHERE task_id = ?))`,
85
+ [task.id, task.id],
86
+ );
87
+ // The namespace is metadata, and lastUpdatedAt tracks task activity, as in updateTaskTitle.
88
+ await client.run('UPDATE agent_tasks SET "key" = ?, updated_by = ? WHERE id = ?', [
89
+ key,
90
+ changedBy ?? null,
91
+ task.id,
92
+ ]);
93
+ for (const mapping of mappedFiles) {
94
+ if (mapping.key === key) continue;
95
+ await client.run(
96
+ 'UPDATE asset_key_mappings SET "key" = ?, updated_at = ?, updated_by = ? WHERE id = ?',
97
+ [key, now, changedBy ?? null, mapping.id],
98
+ );
99
+ await insertAssetKeyHistory({
100
+ entityType: "file",
101
+ entityId: mapping.id,
102
+ previousKey: mapping.key,
103
+ newKey: key,
104
+ changedBy,
105
+ });
106
+ }
107
+ await insertAssetKeyHistory({
108
+ entityType: "task",
109
+ entityId: task.id,
110
+ previousKey: task.key,
111
+ newKey: key,
112
+ changedBy,
113
+ });
114
+ movedTaskIds.push(task.id);
115
+ }
116
+ return movedTaskIds;
117
+ }
118
+
119
+ export type SessionKeyMove = { rootId: string; taskIds: string[]; key: string };
120
+
121
+ /** The caller must complete the key audit before this operation. */
122
+ export async function moveSessionChains(input: {
123
+ moves: SessionKeyMove[];
124
+ changedBy?: string;
125
+ }): Promise<string[]> {
126
+ const moved: string[] = [];
127
+ for (const move of input.moves) {
128
+ const taskIds = await getDbClient().transaction(async () =>
129
+ moveTaskChainKeyInTx(move.rootId, move.taskIds, move.key, input.changedBy),
130
+ );
131
+ if (taskIds && taskIds.length > 0) moved.push(move.rootId);
132
+ }
133
+ return moved;
134
+ }
135
+
136
+ export async function listPrefixRoots(input: {
137
+ from: string;
138
+ userId?: string | null;
139
+ limit: number;
140
+ offset?: number;
141
+ }): Promise<{ id: string; key: string }[]> {
142
+ const { lo, hi } = keyPrefixRange(input.from);
143
+ return getDbClient().query<{ id: string; key: string }>(
144
+ `SELECT r.id, r."key" FROM agent_tasks r
145
+ WHERE r.parentTaskId IS NULL AND r."key" >= ? AND r."key" < ?
146
+ ${input.userId !== undefined ? "AND r.requestedByUserId = ?" : ""}
147
+ ORDER BY r.id LIMIT ? OFFSET ?`,
148
+ [lo, hi, ...(input.userId !== undefined ? [input.userId] : []), input.limit, input.offset ?? 0],
149
+ );
150
+ }
151
+
152
+ export async function listSharedPrefixRoots(input: {
153
+ from: string;
154
+ userId: string | null;
155
+ }): Promise<{ id: string }[]> {
156
+ const { lo, hi } = keyPrefixRange(input.from);
157
+ const visibility = sessionViewerCondition({ userId: input.userId, scope: "shared" });
158
+ return getDbClient().query<{ id: string }>(
159
+ `SELECT r.id FROM agent_tasks r
160
+ WHERE r.parentTaskId IS NULL AND r."key" >= ? AND r."key" < ?
161
+ AND ${visibility.sql} ORDER BY r.id`,
162
+ [lo, hi, ...visibility.params],
163
+ );
164
+ }
@@ -0,0 +1,384 @@
1
+ import {
2
+ keyPrefixRange,
3
+ normalizeAssetKey,
4
+ SESSION_LEAF_KINDS,
5
+ sessionGroupKey,
6
+ } from "../../assets/key";
7
+ import { isTerminalTaskStatus, TERMINAL_TASK_STATUSES } from "../../types";
8
+ import { getDbClient, literalPrefixPattern } from "../db";
9
+ import { type SessionViewer, sessionViewerCondition } from "./session-visibility";
10
+
11
+ export type SessionReadState = "unread" | "read" | "later" | "done";
12
+ export interface SessionFilters {
13
+ /** Restrict roots to these task sources. */
14
+ source?: string[];
15
+ /** Match root task text or title with a case-insensitive substring. */
16
+ q?: string;
17
+ /** Match the root requester, excluding NULL requester rows. */
18
+ requestedByUserId?: string;
19
+ /** Literal context prefix for the UI session panel, escaping %, _ and backslash. */
20
+ contextKeyPrefix?: string;
21
+ /** Apply this user's session ownership and share visibility. */
22
+ viewer?: SessionViewer;
23
+ /** Match roots under this literal canonical asset key prefix. */
24
+ keyPrefix?: string;
25
+ /** Match this exact group and its direct resource leaves. */
26
+ groupKey?: string;
27
+ /** Match shared and viewer-personal namespace roots and direct leaves. */
28
+ noGroup?: boolean;
29
+ /** Filter by chain-wide read state or the Inbox section. */
30
+ triage?: "inbox" | "later" | "done" | "unread";
31
+ /** Filter by latest chain status or pending human attention. */
32
+ status?: "running" | "failed" | "needs";
33
+ /** Match a requester anywhere in the chain or an active share recipient. */
34
+ withUserId?: string;
35
+ /** Use this ISO timestamp for read-state comparisons. */
36
+ now?: string;
37
+ /** Exclude ownerless roots for user viewers to match the tree's visibility. */
38
+ tree?: boolean;
39
+ /** Match exactly this root (one session's summary, e.g. its read state). */
40
+ rootId?: string;
41
+ }
42
+
43
+ type SqlFragment = { sql: string; params: (string | number | null)[] };
44
+
45
+ /** Shared read-state rule with bound ISO timestamps for the list and tree. */
46
+ function sessionReadStateSql(now = new Date().toISOString()): SqlFragment {
47
+ return {
48
+ sql: `CASE
49
+ WHEN s.id IS NULL THEN CASE WHEN a.lastActivityAt > ? THEN 'unread' ELSE 'read' END
50
+ WHEN s.status = 'dismissed' THEN 'done'
51
+ WHEN s.status = 'done' THEN CASE WHEN a.lastActivityAt > s.lastUpdatedAt THEN 'unread' ELSE 'done' END
52
+ WHEN s.status = 'snoozed' THEN CASE WHEN s.snoozeUntil > ? THEN 'later' ELSE 'unread' END
53
+ WHEN a.lastActivityAt > COALESCE(s.lastReadAt, s.lastUpdatedAt) THEN 'unread'
54
+ ELSE 'read'
55
+ END`,
56
+ params: [new Date(Date.parse(now) - 7 * 86400000).toISOString(), now],
57
+ };
58
+ }
59
+
60
+ function groupCondition(group: string): SqlFragment {
61
+ const { lo, hi } = keyPrefixRange(group);
62
+ // The remaining key must be one resource leaf, with no nested path.
63
+ // SQLite substr/length count code points, so count them here too (not UTF-16 units).
64
+ const start = [...lo].length + 1;
65
+ return {
66
+ sql: `(r."key" >= ? AND r."key" < ? AND (r."key" = ? OR (
67
+ instr(substr(r."key", ?, length(r."key") - ?), '/') = 0 AND
68
+ (${SESSION_LEAF_KINDS.map(() => 'substr(r."key", ?) GLOB ?').join(" OR ")})
69
+ )))`,
70
+ params: [
71
+ lo,
72
+ hi,
73
+ lo,
74
+ start,
75
+ start,
76
+ ...SESSION_LEAF_KINDS.flatMap((kind) => [start, `${kind}:?*/`]),
77
+ ],
78
+ };
79
+ }
80
+
81
+ export function sessionRootFilter(opts: SessionFilters = {}): SqlFragment {
82
+ const conditions = ["r.parentTaskId IS NULL"];
83
+ const params: SqlFragment["params"] = [];
84
+ const add = (fragment: SqlFragment) => {
85
+ conditions.push(fragment.sql);
86
+ params.push(...fragment.params);
87
+ };
88
+ if (opts.rootId) add({ sql: "r.id = ?", params: [opts.rootId] });
89
+ const sources = opts.source?.filter(Boolean) ?? [];
90
+ if (sources.length)
91
+ add({ sql: `r.source IN (${sources.map(() => "?").join(",")})`, params: sources });
92
+ if (opts.q?.trim()) {
93
+ const like = `%${opts.q.trim().toLowerCase()}%`;
94
+ add({
95
+ sql: "(lower(r.task) LIKE ? OR lower(COALESCE(r.title, '')) LIKE ?)",
96
+ params: [like, like],
97
+ });
98
+ }
99
+ if (opts.requestedByUserId?.trim())
100
+ add({ sql: "r.requestedByUserId = ?", params: [opts.requestedByUserId.trim()] });
101
+ if (opts.contextKeyPrefix)
102
+ add({
103
+ sql: "r.contextKey LIKE ? ESCAPE '\\'",
104
+ params: [literalPrefixPattern(opts.contextKeyPrefix)],
105
+ });
106
+ if (opts.viewer) {
107
+ add(sessionViewerCondition(opts.viewer));
108
+ if (opts.tree) conditions.push("r.requestedByUserId IS NOT NULL");
109
+ }
110
+ if (opts.keyPrefix) {
111
+ const { lo, hi } = keyPrefixRange(opts.keyPrefix);
112
+ add({ sql: 'r."key" >= ? AND r."key" < ?', params: [lo, hi] });
113
+ }
114
+ if (opts.groupKey) add(groupCondition(normalizeAssetKey(opts.groupKey)));
115
+ if (opts.noGroup) {
116
+ const groups = [groupCondition("shared/")];
117
+ if (opts.viewer?.userId) groups.push(groupCondition(`personal/${opts.viewer.userId}/`));
118
+ add({
119
+ sql: `(${groups.map((g) => g.sql).join(" OR ")})`,
120
+ params: groups.flatMap((g) => g.params),
121
+ });
122
+ }
123
+ if (opts.triage === "later" || opts.triage === "done") {
124
+ add({
125
+ sql: `r.id IN (SELECT itemId FROM inbox_item_state
126
+ WHERE userId = ? AND itemType = 'to_read' AND status IN (${opts.triage === "later" ? "'snoozed'" : "'done','dismissed'"}))`,
127
+ params: [opts.viewer?.userId ?? null],
128
+ });
129
+ }
130
+ return { sql: conditions.join(" AND "), params };
131
+ }
132
+
133
+ function excludesTriaged(opts: SessionFilters): boolean {
134
+ return !!(opts.viewer?.userId && (opts.groupKey || opts.keyPrefix || opts.noGroup));
135
+ }
136
+
137
+ function hasSessionChainFilter(opts: SessionFilters): boolean {
138
+ return !!(opts.triage || opts.status || opts.withUserId || excludesTriaged(opts));
139
+ }
140
+
141
+ const runningSql = `latestStatus NOT IN (${TERMINAL_TASK_STATUSES.map((s) => `'${s}'`).join(",")})`;
142
+
143
+ /**
144
+ * A session starts at a parentTaskId IS NULL root. One recursive CTE computes
145
+ * chain-wide latest activity for list ordering. Root filters precede recursion.
146
+ * Read state and chain filters follow aggregation.
147
+ */
148
+ export function sessionSummaryQuery(opts: SessionFilters = {}): SqlFragment {
149
+ const root = sessionRootFilter(opts);
150
+ const read = opts.viewer?.userId ? sessionReadStateSql(opts.now) : { sql: "NULL", params: [] };
151
+ const params = [...root.params];
152
+ if (opts.withUserId) params.push(opts.withUserId);
153
+ params.push(...read.params, opts.viewer?.userId ?? null);
154
+ const filters: string[] = [];
155
+ if (opts.triage === "unread") filters.push("readState = 'unread'");
156
+ else if (opts.triage === "later" || opts.triage === "done") {
157
+ filters.push("readState = ?");
158
+ params.push(opts.triage);
159
+ } else if (opts.triage === "inbox" || excludesTriaged(opts)) {
160
+ filters.push("(readState IS NULL OR readState IN ('read','unread'))");
161
+ }
162
+ if (opts.status === "running") filters.push(runningSql);
163
+ if (opts.status === "failed") filters.push("latestStatus = 'failed'");
164
+ if (opts.status === "needs") filters.push("needsYou = 1");
165
+ if (opts.withUserId) {
166
+ filters.push(`(a.withUser > 0 OR EXISTS (SELECT 1 FROM session_shares sh
167
+ WHERE sh.rootTaskId = a.root_id AND sh.revokedAt IS NULL AND sh.userId = ?))`);
168
+ params.push(opts.withUserId);
169
+ }
170
+ return {
171
+ // SQLite takes the bare status from the row with MAX(lastUpdatedAt).
172
+ // Keep this as the only MIN/MAX aggregate so status follows activity.
173
+ sql: `WITH RECURSIVE chain(root_id, id, lastUpdatedAt, status, requestedByUserId) AS (
174
+ SELECT r.id, r.id, r.lastUpdatedAt, r.status, r.requestedByUserId
175
+ FROM agent_tasks r WHERE ${root.sql}
176
+ UNION ALL
177
+ SELECT c.root_id, t.id, t.lastUpdatedAt, t.status, t.requestedByUserId
178
+ FROM chain c JOIN agent_tasks t ON t.parentTaskId = c.id
179
+ ), agg AS (
180
+ SELECT root_id, COUNT(*) AS chainCount,
181
+ status AS latestStatus, MAX(lastUpdatedAt) AS lastActivityAt,
182
+ ${opts.withUserId ? "SUM(requestedByUserId = ?) AS withUser," : ""}
183
+ SUM(EXISTS (SELECT 1 FROM approval_requests ar
184
+ WHERE ar.sourceTaskId = chain.id AND ar.status = 'pending')) AS pendingApproval
185
+ FROM chain GROUP BY root_id
186
+ ), state AS (
187
+ SELECT a.*, ${read.sql} AS readState, s.snoozeUntil
188
+ FROM agg a LEFT JOIN inbox_item_state s
189
+ ON s.userId = ? AND s.itemType = 'to_read' AND s.itemId = a.root_id
190
+ ), summary AS (
191
+ SELECT *, (COALESCE(readState = 'unread' AND latestStatus = 'failed', 0) OR pendingApproval) AS needsYou
192
+ FROM state
193
+ )
194
+ SELECT a.* FROM summary a ${filters.length ? `WHERE ${filters.join(" AND ")}` : ""}`,
195
+ params,
196
+ };
197
+ }
198
+
199
+ /**
200
+ * One session's read state for a user, with the same rule and chain activity
201
+ * as the list rows. Null when the user cannot see the root.
202
+ */
203
+ export async function getSessionReadState(
204
+ rootId: string,
205
+ userId: string,
206
+ now?: string,
207
+ ): Promise<{ readState: SessionReadState; lastActivityAt: string } | null> {
208
+ const query = sessionSummaryQuery({ rootId, viewer: { userId, scope: "all" }, now });
209
+ const row = await getDbClient().get<{ readState: SessionReadState; lastActivityAt: string }>(
210
+ `SELECT readState, lastActivityAt FROM (${query.sql})`,
211
+ query.params,
212
+ );
213
+ return row?.readState ? { readState: row.readState, lastActivityAt: row.lastActivityAt } : null;
214
+ }
215
+
216
+ /** Count roots directly unless a filter needs chain activity or participants. */
217
+ export function sessionCountQuery(opts: SessionFilters = {}): SqlFragment {
218
+ const chain = hasSessionChainFilter(opts);
219
+ const query = chain ? sessionSummaryQuery(opts) : sessionRootFilter(opts);
220
+ return {
221
+ sql: chain
222
+ ? `SELECT COUNT(*) AS count FROM (${query.sql})`
223
+ : `SELECT COUNT(*) AS count FROM agent_tasks r WHERE ${query.sql}`,
224
+ params: query.params,
225
+ };
226
+ }
227
+
228
+ export interface SessionRowFields {
229
+ readState?: SessionReadState;
230
+ snoozeUntil?: string | null;
231
+ needsYou?: boolean;
232
+ groupKey?: string;
233
+ shared?: boolean;
234
+ people?: { id: string; name: string }[];
235
+ peopleCount?: number;
236
+ preview?: string | null;
237
+ /** Active shares with their access. `userId` null means everyone on the swarm. */
238
+ shares?: { userId: string | null; name: string | null; access: "read" | "write" }[];
239
+ }
240
+
241
+ /** Two page-wide queries load people, shares and bounded output previews. */
242
+ export async function sessionPageFields(
243
+ roots: { id: string; requestedByUserId?: string }[],
244
+ ): Promise<Map<string, SessionRowFields>> {
245
+ const result = new Map<string, SessionRowFields>();
246
+ if (!roots.length) return result;
247
+ const ids = roots.map((r) => r.id);
248
+ const [tasks, shares] = await Promise.all([
249
+ getDbClient().query<{
250
+ root_id: string;
251
+ id: string | null;
252
+ name: string | null;
253
+ preview: string | null;
254
+ }>(
255
+ `WITH RECURSIVE chain(root_id, id) AS (
256
+ SELECT id, id FROM agent_tasks WHERE id IN (${ids.map(() => "?").join(",")})
257
+ UNION ALL SELECT c.root_id, t.id FROM chain c JOIN agent_tasks t ON t.parentTaskId = c.id
258
+ ) SELECT c.root_id, t.requestedByUserId AS id, u.name,
259
+ CASE WHEN t.source = 'system' AND t.taskType = 'follow-up' THEN NULL ELSE NULLIF(trim(substr(t.output, 1, 200)), '') END AS preview
260
+ FROM chain c CROSS JOIN agent_tasks t ON t.id = c.id
261
+ LEFT JOIN users u ON u.id = t.requestedByUserId
262
+ ORDER BY t.lastUpdatedAt DESC, t.createdAt DESC, t.id DESC`,
263
+ ids,
264
+ ),
265
+ getDbClient().query<{
266
+ root_id: string;
267
+ id: string | null;
268
+ name: string | null;
269
+ access: "read" | "write";
270
+ }>(
271
+ `SELECT sh.rootTaskId AS root_id, sh.userId AS id, u.name, sh.access FROM session_shares sh
272
+ LEFT JOIN users u ON u.id = sh.userId
273
+ WHERE sh.revokedAt IS NULL AND sh.rootTaskId IN (${ids.map(() => "?").join(",")})
274
+ ORDER BY sh.createdAt, sh.id`,
275
+ ids,
276
+ ),
277
+ ]);
278
+ const people = new Map<string, Map<string, string>>();
279
+ for (const root of roots) {
280
+ result.set(root.id, { shared: false, preview: null, shares: [] });
281
+ people.set(root.id, new Map());
282
+ }
283
+ // Chain rows are newest first. Reserve the requester slot before adding other people.
284
+ for (const root of roots) {
285
+ if (!root.requestedByUserId) continue;
286
+ people.get(root.id)!.set(root.requestedByUserId, root.requestedByUserId);
287
+ }
288
+ for (const task of tasks) {
289
+ if (task.id) people.get(task.root_id)!.set(task.id, task.name ?? task.id);
290
+ const fields = result.get(task.root_id)!;
291
+ if (fields.preview === null && task.preview !== null) fields.preview = task.preview;
292
+ }
293
+ for (const share of shares) {
294
+ const fields = result.get(share.root_id)!;
295
+ fields.shared = true;
296
+ fields.shares!.push({ userId: share.id, name: share.name, access: share.access });
297
+ if (share.id) people.get(share.root_id)!.set(share.id, share.name ?? share.id);
298
+ }
299
+ for (const [id, members] of people) {
300
+ Object.assign(result.get(id)!, {
301
+ people: [...members].slice(0, 8).map(([id, name]) => ({ id, name })),
302
+ peopleCount: members.size,
303
+ });
304
+ }
305
+ return result;
306
+ }
307
+
308
+ export interface SessionSectionCounts {
309
+ total: number;
310
+ unread: number;
311
+ running: number;
312
+ needsYou: number;
313
+ }
314
+ export interface SessionTreeGroup extends SessionSectionCounts {
315
+ key: string;
316
+ lastActivityAt: string;
317
+ }
318
+
319
+ export interface SessionTree {
320
+ groups: SessionTreeGroup[];
321
+ inbox: SessionSectionCounts;
322
+ later: SessionSectionCounts;
323
+ done: SessionSectionCounts;
324
+ noGroup: SessionSectionCounts;
325
+ }
326
+
327
+ export async function getSessionTree(
328
+ actor: { kind: "bypass" } | { kind: "user"; userId: string | null },
329
+ now?: string,
330
+ ): Promise<SessionTree> {
331
+ const query = sessionSummaryQuery({
332
+ ...(actor.kind === "user" ? { viewer: { userId: actor.userId, scope: "all" } as const } : {}),
333
+ now,
334
+ tree: true,
335
+ });
336
+ const rows = await getDbClient().query<{
337
+ key: string;
338
+ lastActivityAt: string;
339
+ latestStatus: string;
340
+ readState: SessionReadState | null;
341
+ needsYou: number;
342
+ }>(
343
+ `SELECT r."key", a.lastActivityAt, a.latestStatus, a.readState, a.needsYou
344
+ FROM (${query.sql}) a CROSS JOIN agent_tasks r ON r.id = a.root_id`,
345
+ query.params,
346
+ );
347
+ const counts = (): SessionSectionCounts => ({ total: 0, unread: 0, running: 0, needsYou: 0 });
348
+ const result: SessionTree = {
349
+ groups: [],
350
+ inbox: counts(),
351
+ later: counts(),
352
+ done: counts(),
353
+ noGroup: counts(),
354
+ };
355
+ const groups = new Map<string, SessionTreeGroup>();
356
+ const add = (section: SessionSectionCounts, row: (typeof rows)[number]) => {
357
+ section.total++;
358
+ if (row.readState === "unread") section.unread++;
359
+ if (!isTerminalTaskStatus(row.latestStatus)) section.running++;
360
+ if (row.needsYou) section.needsYou++;
361
+ };
362
+ for (const row of rows) {
363
+ const key = sessionGroupKey(row.key);
364
+ const noGroup =
365
+ key === "shared/" || (actor.kind === "user" && key === `personal/${actor.userId}/`);
366
+ let group: SessionTreeGroup | undefined;
367
+ if (!noGroup) {
368
+ group = groups.get(key);
369
+ if (!group) {
370
+ group = { key, ...counts(), lastActivityAt: row.lastActivityAt };
371
+ groups.set(key, group);
372
+ }
373
+ if (row.lastActivityAt > group.lastActivityAt) group.lastActivityAt = row.lastActivityAt;
374
+ }
375
+ if (row.readState === "later" || row.readState === "done") add(result[row.readState], row);
376
+ else {
377
+ add(result.inbox, row);
378
+ if (noGroup) add(result.noGroup, row);
379
+ else if (group) add(group, row);
380
+ }
381
+ }
382
+ result.groups = [...groups.values()].sort((a, b) => a.key.localeCompare(b.key));
383
+ return result;
384
+ }
@@ -0,0 +1,15 @@
1
+ import { getDbClient } from "../runtime";
2
+
3
+ /**
4
+ * Append a block to a draft's task text (an uploaded voice note's transcript),
5
+ * as its own paragraph. Only a draft: nobody has read its text yet. One
6
+ * statement, so parallel uploads never drop each other's block. Returns
7
+ * whether the task was still a draft.
8
+ */
9
+ export async function appendDraftTaskText(taskId: string, block: string): Promise<boolean> {
10
+ const result = await getDbClient().run(
11
+ "UPDATE agent_tasks SET task = task || ?, lastUpdatedAt = ? WHERE id = ? AND status = 'draft'",
12
+ [`\n\n${block}`, new Date().toISOString(), taskId],
13
+ );
14
+ return result.changes > 0;
15
+ }
@@ -18,6 +18,7 @@ import {
18
18
  parseModelTier,
19
19
  ReasoningEffortSchema,
20
20
  RoutingAffinitySchema,
21
+ TERMINAL_TASK_STATUSES,
21
22
  } from "../../../types";
22
23
  import { parseProviderMeta } from "../../../utils/provider-metadata";
23
24
  import { scrubSecrets } from "../../../utils/secret-scrubber";
@@ -296,7 +297,8 @@ export function rowToAgentTask(row: AgentTaskRow): AgentTask {
296
297
  slackReplySent: !!row.slackReplySent,
297
298
  slackProgressMessageTs: row.slackProgressMessageTs ?? undefined,
298
299
  slackTreeRootMessageTs: row.slackTreeRootMessageTs ?? undefined,
299
- vcsProvider: (row.vcsProvider as "github" | "gitlab" | "azure-devops" | null) ?? undefined,
300
+ vcsProvider:
301
+ (row.vcsProvider as "github" | "gitlab" | "azure-devops" | "bitbucket" | null) ?? undefined,
300
302
  vcsRepo: row.vcsRepo ?? undefined,
301
303
  vcsEventType: row.vcsEventType ?? undefined,
302
304
  vcsNumber: row.vcsNumber ?? undefined,
@@ -712,18 +714,53 @@ export async function getTasksByStatus(status: AgentTaskStatus): Promise<AgentTa
712
714
  * in `src/types.ts`. SQL strings can't import a TS const — if you add a
713
715
  * new terminal status, grep for `NOT IN ('completed'` across this file.
714
716
  */
715
- export async function findTaskByVcs(vcsRepo: string, vcsNumber: number): Promise<AgentTask | null> {
717
+ /**
718
+ * Most recent active task for a provider + repo + PR/issue number. The
719
+ * provider is required: `owner/repo` exists on GitHub, GitLab and Bitbucket
720
+ * alike, so an unscoped lookup lets one host's webhook act on another host's
721
+ * task. A NULL `vcsProvider` is a legacy GitHub row (pre-provider writers), so
722
+ * the GitHub lookup still matches it.
723
+ */
724
+ export async function findTaskByVcs(
725
+ vcsRepo: string,
726
+ vcsNumber: number,
727
+ vcsProvider: NonNullable<AgentTask["vcsProvider"]>,
728
+ ): Promise<AgentTask | null> {
729
+ const providerClause =
730
+ vcsProvider === "github" ? "(vcsProvider = ? OR vcsProvider IS NULL)" : "vcsProvider = ?";
716
731
  const row = await getDbClient().get<AgentTaskRow>(
717
732
  `SELECT * FROM agent_tasks
718
- WHERE vcsRepo = ? AND vcsNumber = ?
733
+ WHERE vcsRepo = ? AND vcsNumber = ? AND ${providerClause}
719
734
  AND status NOT IN ('completed', 'failed', 'cancelled', 'superseded')
720
735
  ORDER BY createdAt DESC
721
736
  LIMIT 1`,
722
- [vcsRepo, vcsNumber],
737
+ [vcsRepo, vcsNumber, vcsProvider],
723
738
  );
724
739
  return row ? rowToAgentTask(row) : null;
725
740
  }
726
741
 
742
+ /**
743
+ * Every active task for a provider + repo + PR/issue number, newest first.
744
+ * Same scoping as `findTaskByVcs`; for callers that must settle all of them
745
+ * (a PR close leaves no task worth running).
746
+ */
747
+ export async function findActiveTasksByVcs(
748
+ vcsRepo: string,
749
+ vcsNumber: number,
750
+ vcsProvider: NonNullable<AgentTask["vcsProvider"]>,
751
+ ): Promise<AgentTask[]> {
752
+ const providerClause =
753
+ vcsProvider === "github" ? "(vcsProvider = ? OR vcsProvider IS NULL)" : "vcsProvider = ?";
754
+ const rows = await getDbClient().query<AgentTaskRow>(
755
+ `SELECT * FROM agent_tasks
756
+ WHERE vcsRepo = ? AND vcsNumber = ? AND ${providerClause}
757
+ AND status NOT IN ('completed', 'failed', 'cancelled', 'superseded')
758
+ ORDER BY createdAt DESC`,
759
+ [vcsRepo, vcsNumber, vcsProvider],
760
+ );
761
+ return rows.map(rowToAgentTask);
762
+ }
763
+
727
764
  /** @deprecated Use findTaskByVcs instead */
728
765
  export const findTaskByGitHub = findTaskByVcs;
729
766
 
@@ -1688,14 +1725,26 @@ export async function getLatestLeadTaskInThread(
1688
1725
  export async function getAgentWorkingOnContextKey(
1689
1726
  contextKey: string,
1690
1727
  source?: string,
1728
+ /** ISO time: when set, only tasks still open or finished at or after it count. */
1729
+ endedSince?: string,
1691
1730
  ): Promise<Agent | null> {
1692
1731
  const taskRow = await getDbClient().get<AgentTaskRow>(
1693
1732
  `SELECT agentId FROM agent_tasks
1694
1733
  WHERE contextKey = ?
1695
1734
  AND (? IS NULL OR source = ?)
1735
+ AND (? IS NULL
1736
+ OR status NOT IN (${TERMINAL_TASK_STATUSES.map(() => "?").join(", ")})
1737
+ OR finishedAt >= ?)
1696
1738
  ORDER BY createdAt DESC, rowid DESC
1697
1739
  LIMIT 1`,
1698
- [contextKey, source ?? null, source ?? null],
1740
+ [
1741
+ contextKey,
1742
+ source ?? null,
1743
+ source ?? null,
1744
+ endedSince ?? null,
1745
+ ...TERMINAL_TASK_STATUSES,
1746
+ endedSince ?? null,
1747
+ ],
1699
1748
  );
1700
1749
 
1701
1750
  if (taskRow?.agentId) return getAgentById(taskRow.agentId);