@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,5 +1,6 @@
1
1
  import type { WorkflowRunStatus } from "../../types";
2
2
  import { scrubSecrets } from "../../utils/secret-scrubber";
3
+ import { scrubJsonValue } from "../scrub-json";
3
4
  import { getDbClient } from "./runtime";
4
5
 
5
6
  // Parent/child links between workflow runs (migration 203): a `sub-workflow`
@@ -115,3 +116,98 @@ function emitAfterCommit(childRunId: string, parentStepId: string): void {
115
116
  );
116
117
  });
117
118
  }
119
+
120
+ /**
121
+ * Insert a foreach parent's missing child step rows as `pending` (queued), all
122
+ * in one transaction, so a crash never leaves a partial child set and the join
123
+ * always sees every item. Children that already exist (any row for the child
124
+ * node id) are skipped inside the transaction, which makes a concurrent
125
+ * re-execution of the same parent insert nothing twice. Returns how many rows
126
+ * this call inserted.
127
+ */
128
+ export async function createForeachChildSteps(
129
+ runId: string,
130
+ parentNodeId: string,
131
+ rows: Array<{
132
+ id: string;
133
+ nodeId: string;
134
+ nodeType: string;
135
+ input: unknown;
136
+ idempotencyKey: string;
137
+ }>,
138
+ ): Promise<number> {
139
+ if (rows.length === 0) return 0;
140
+ const prefix = `${parentNodeId}#`;
141
+ return getDbClient().transaction(async (tx) => {
142
+ const existing = await tx.query<{ nodeId: string }>(
143
+ "SELECT DISTINCT nodeId FROM workflow_run_steps WHERE runId = ? AND substr(nodeId, 1, ?) = ?",
144
+ [runId, prefix.length, prefix],
145
+ );
146
+ const existingNodeIds = new Set(existing.map((row) => row.nodeId));
147
+ const now = new Date().toISOString();
148
+ let created = 0;
149
+ for (const row of rows) {
150
+ if (existingNodeIds.has(row.nodeId)) continue;
151
+ await tx.run(
152
+ `INSERT INTO workflow_run_steps (id, runId, nodeId, nodeType, status, startedAt, input, idempotencyKey)
153
+ VALUES (?, ?, ?, ?, 'pending', ?, ?, ?)`,
154
+ [
155
+ row.id,
156
+ runId,
157
+ row.nodeId,
158
+ row.nodeType,
159
+ now,
160
+ scrubJsonValue(row.input),
161
+ row.idempotencyKey,
162
+ ],
163
+ );
164
+ existingNodeIds.add(row.nodeId);
165
+ created++;
166
+ }
167
+ return created;
168
+ });
169
+ }
170
+
171
+ /**
172
+ * Insert a new `pending` attempt row for each foreach item being retried. Each
173
+ * row reuses its item's child node id and gets a fresh step id, so the body
174
+ * starts a new invocation (a new task, child run, approval or wait) while the
175
+ * earlier attempt rows stay as history. `attempt` is the number of rows the
176
+ * item already has, and the idempotency key `<runId>:<childNodeId>:<attempt>`
177
+ * is UNIQUE, so a duplicated retry of the same attempt inserts nothing.
178
+ * Call it inside the retry's transaction. Returns how many rows it inserted.
179
+ */
180
+ export async function createForeachAttemptSteps(
181
+ runId: string,
182
+ rows: Array<{ nodeId: string; nodeType: string; itemKey: string; index: number }>,
183
+ ): Promise<number> {
184
+ if (rows.length === 0) return 0;
185
+ return getDbClient().transaction(async (tx) => {
186
+ const now = new Date().toISOString();
187
+ let created = 0;
188
+ for (const row of rows) {
189
+ const count = await tx.get<{ n: number }>(
190
+ "SELECT COUNT(*) AS n FROM workflow_run_steps WHERE runId = ? AND nodeId = ?",
191
+ [runId, row.nodeId],
192
+ );
193
+ const attempt = count?.n ?? 0;
194
+ const inserted = await tx.get<{ id: string }>(
195
+ `INSERT INTO workflow_run_steps (id, runId, nodeId, nodeType, status, startedAt, input, idempotencyKey)
196
+ VALUES (?, ?, ?, ?, 'pending', ?, ?, ?)
197
+ ON CONFLICT(idempotencyKey) DO NOTHING
198
+ RETURNING id`,
199
+ [
200
+ crypto.randomUUID(),
201
+ runId,
202
+ row.nodeId,
203
+ row.nodeType,
204
+ now,
205
+ scrubJsonValue({ itemKey: row.itemKey, index: row.index, attempt }),
206
+ `${runId}:${row.nodeId}:${attempt}`,
207
+ ],
208
+ );
209
+ if (inserted) created++;
210
+ }
211
+ return created;
212
+ });
213
+ }
package/src/be/db.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { Database } from "bun:sqlite";
2
2
  import pkg from "../../package.json";
3
- import { defaultAssetKey, normalizeAssetKey } from "../assets/key";
3
+ import { defaultAssetKey, normalizeAssetKey, sessionGroupKey } from "../assets/key";
4
4
  import {
5
5
  generateDefaultClaudeMd,
6
6
  generateDefaultIdentityMd,
@@ -195,7 +195,19 @@ import {
195
195
  } from "./db/context-versions";
196
196
  import { clearAgentModelProfileOnOverrideChange } from "./db/model-profiles";
197
197
  import { getDb, getDbClient } from "./db/runtime";
198
- import { type SessionViewer, sessionViewerCondition } from "./db/session-visibility";
198
+ import {
199
+ assertAssetKeyMovesAllowed,
200
+ configureSessionMoveDependencies,
201
+ moveTaskChainKeyInTx,
202
+ } from "./db/session-moves";
203
+ import {
204
+ type SessionFilters,
205
+ type SessionReadState,
206
+ type SessionRowFields,
207
+ sessionCountQuery,
208
+ sessionPageFields,
209
+ sessionSummaryQuery,
210
+ } from "./db/session-tree";
199
211
  import { classifyTaskHumanFree, reclassifyTaskHumanFree } from "./db/tasks/human-free";
200
212
  import {
201
213
  type AgentTaskRow,
@@ -269,6 +281,7 @@ export { recordKeySeatMismatch } from "./db/api-keys";
269
281
  export {
270
282
  type ApprovalRequestSummary,
271
283
  type ApprovalVote,
284
+ getPendingApprovalsForTasks,
272
285
  getPendingApprovalVoteState,
273
286
  listApprovalRequestSummaries,
274
287
  recordApprovalVotes,
@@ -281,6 +294,11 @@ export {
281
294
  getLatestContextVersion,
282
295
  } from "./db/context-versions";
283
296
  export { getScriptRunExecutionArgs } from "./db/script-runs";
297
+ export {
298
+ AssetKeyMovesBlockedError,
299
+ assertAssetKeyMovesAllowed,
300
+ moveTaskChainKeyInTx,
301
+ } from "./db/session-moves";
284
302
 
285
303
  configureAgentDependencies({
286
304
  createLogEntry: (entry) => createLogEntry(entry),
@@ -300,6 +318,7 @@ export {
300
318
  export {
301
319
  type ExistingTrackerContextWork,
302
320
  type ExistingTrackerContextWorkReason,
321
+ findActiveTasksByVcs,
303
322
  findCompletedTaskInThread,
304
323
  findExistingLinearTrackerContextWork,
305
324
  findRecentCancelledTaskInThread,
@@ -387,6 +406,11 @@ configureTaskWriteDependencies({
387
406
  cascadeFailDependents: (...args) => cascadeFailDependents(...args),
388
407
  });
389
408
 
409
+ configureSessionMoveDependencies({
410
+ auditAssetKeys: () => auditAssetKeys(getDb()),
411
+ insertAssetKeyHistory: (...args) => insertAssetKeyHistory(...args),
412
+ });
413
+
390
414
  configureTaskReadDependencies({
391
415
  checkDependencies: (taskId) => checkDependencies(taskId),
392
416
  assetKeyPrefixPattern: (input) => assetKeyPrefixPattern(input),
@@ -400,7 +424,7 @@ type TaskTelemetryContext = {
400
424
  };
401
425
 
402
426
  /** LIKE pattern matching strings that start with `prefix` literally (use with `ESCAPE '\\'`). */
403
- function literalPrefixPattern(prefix: string): string {
427
+ export function literalPrefixPattern(prefix: string): string {
404
428
  return `${prefix.replace(/[\\%_]/g, "\\$&")}%`;
405
429
  }
406
430
 
@@ -1060,7 +1084,7 @@ export async function setSlackMessageTracking(
1060
1084
  export async function updateTaskVcs(
1061
1085
  taskId: string,
1062
1086
  vcs: {
1063
- vcsProvider: "github" | "gitlab" | "azure-devops";
1087
+ vcsProvider: "github" | "gitlab" | "azure-devops" | "bitbucket";
1064
1088
  vcsRepo: string;
1065
1089
  vcsNumber: number;
1066
1090
  vcsUrl: string;
@@ -1742,17 +1766,7 @@ export async function moveAssetKey(input: {
1742
1766
  descendantTaskIds?: readonly string[];
1743
1767
  }): Promise<false | { movedTaskIds?: string[] }> {
1744
1768
  const key = normalizeAssetKey(input.key);
1745
- // auditAssetKeys stays sync (shared with the boot audit); run it inside a
1746
- // client transaction so this request-path read cannot observe another
1747
- // request's uncommitted writes on the shared connection.
1748
- const audit = await getDbClient().transaction(async () => auditAssetKeys(getDb()), {
1749
- readOnly: true,
1750
- });
1751
- if (!audit.ok) {
1752
- throw new Error(
1753
- "Asset namespace moves are blocked until structural errors, unknown personal users, or provider mapping drift are repaired.",
1754
- );
1755
- }
1769
+ await assertAssetKeyMovesAllowed();
1756
1770
  const previousKey = await currentAssetKey(input.entityType, input.id);
1757
1771
  if (!previousKey) return false;
1758
1772
  if (previousKey === key && input.entityType !== "task") return {};
@@ -1769,60 +1783,13 @@ export async function moveAssetKey(input: {
1769
1783
  return getDbClient().transaction(async (tx) => {
1770
1784
  switch (input.entityType) {
1771
1785
  case "task": {
1772
- const root = await getTaskById(input.id);
1773
- if (!root) return false;
1774
- const tasks = [root];
1775
- for (const id of input.descendantTaskIds ?? []) {
1776
- const task = id === root.id ? null : await getTaskById(id);
1777
- if (task) tasks.push(task);
1778
- }
1779
- const movedTaskIds: string[] = [];
1780
- for (const task of tasks) {
1781
- if (task.key === key) continue;
1782
- // A file shared with another task keeps the namespace of the task whose attachment
1783
- // sources its mapping. Mappings with no attachment source follow any attaching task.
1784
- const mappedFiles = await tx.query<{ id: string; key: string }>(
1785
- `SELECT DISTINCT m.id, m."key" AS key
1786
- FROM asset_key_mappings m
1787
- JOIN task_attachments a
1788
- ON m.provider_id = COALESCE(NULLIF(a.provider_id, ''), 'agent-fs')
1789
- AND m.provider_org_id = COALESCE(a.agent_fs_org_id, '')
1790
- AND m.provider_drive_id = COALESCE(a.agent_fs_drive_id, '')
1791
- AND m.provider_key = COALESCE(NULLIF(a.provider_key, ''), a.path)
1792
- WHERE a.task_id = ?
1793
- AND (m.source_entity_type IS NOT 'task-attachment'
1794
- OR m.source_entity_id IN (SELECT id FROM task_attachments WHERE task_id = ?))`,
1795
- [task.id, task.id],
1796
- );
1797
- // The namespace is metadata, and lastUpdatedAt tracks task activity, as in updateTaskTitle.
1798
- await tx.run('UPDATE agent_tasks SET "key" = ?, updated_by = ? WHERE id = ?', [
1799
- key,
1800
- input.changedBy ?? null,
1801
- task.id,
1802
- ]);
1803
- for (const mapping of mappedFiles) {
1804
- if (mapping.key === key) continue;
1805
- await tx.run(
1806
- 'UPDATE asset_key_mappings SET "key" = ?, updated_at = ?, updated_by = ? WHERE id = ?',
1807
- [key, now, input.changedBy ?? null, mapping.id],
1808
- );
1809
- await insertAssetKeyHistory({
1810
- entityType: "file",
1811
- entityId: mapping.id,
1812
- previousKey: mapping.key,
1813
- newKey: key,
1814
- changedBy: input.changedBy,
1815
- });
1816
- }
1817
- await insertAssetKeyHistory({
1818
- entityType: "task",
1819
- entityId: task.id,
1820
- previousKey: task.key,
1821
- newKey: key,
1822
- changedBy: input.changedBy,
1823
- });
1824
- movedTaskIds.push(task.id);
1825
- }
1786
+ const movedTaskIds = await moveTaskChainKeyInTx(
1787
+ input.id,
1788
+ input.descendantTaskIds ?? [],
1789
+ key,
1790
+ input.changedBy,
1791
+ );
1792
+ if (movedTaskIds === false) return false;
1826
1793
  return { movedTaskIds };
1827
1794
  }
1828
1795
  case "workflow":
@@ -2605,7 +2572,7 @@ export async function createTaskExtended(
2605
2572
  }
2606
2573
 
2607
2574
  // VCS identity (GitHub / GitLab issue / PR / MR + webhook routing)
2608
- // Webhook handlers locate active work via `findTaskByVcs(repo, number)`,
2575
+ // Webhook handlers locate active work via `findTaskByVcs(repo, number, provider)`,
2609
2576
  // so a resume / follow-up child MUST carry the full VCS identity or
2610
2577
  // subsequent review/update events get dropped.
2611
2578
  if (parent.vcsProvider && !options.vcsProvider) {
@@ -2676,7 +2643,11 @@ export async function createTaskExtended(
2676
2643
  }
2677
2644
 
2678
2645
  // Attribution
2679
- if (parent.requestedByUserId && !options.requestedByUserId) {
2646
+ if (
2647
+ parent.requestedByUserId &&
2648
+ !options.requestedByUserId &&
2649
+ options.inheritParentRequester !== false
2650
+ ) {
2680
2651
  options.requestedByUserId = parent.requestedByUserId;
2681
2652
  requestedByUserIdInherited = true;
2682
2653
  }
@@ -12917,7 +12888,7 @@ export async function getRootTaskChain(rootTaskId: string): Promise<AgentTask[]>
12917
12888
  return rows.map(rowToAgentTask);
12918
12889
  }
12919
12890
 
12920
- export interface SessionListItem {
12891
+ export interface SessionListItem extends SessionRowFields {
12921
12892
  root: AgentTask;
12922
12893
  chainTaskCount: number;
12923
12894
  lastActivityAt: string;
@@ -12930,42 +12901,18 @@ export interface SessionListItem {
12930
12901
  * The session list only renders a brief of the root; the full root + chain are
12931
12902
  * on `GET /api/sessions/{rootTaskId}`.
12932
12903
  */
12933
- export interface SessionListItemSummary {
12904
+ export interface SessionListItemSummary extends SessionRowFields {
12934
12905
  root: AgentTaskSummary;
12935
12906
  chainTaskCount: number;
12936
12907
  lastActivityAt: string;
12937
12908
  latestStatus: AgentTaskStatus;
12938
12909
  }
12939
12910
 
12940
- /**
12941
- * List the most recent sessions ordered by chain-wide latest activity.
12942
- * A "session" here is any task with `parentTaskId IS NULL` — its descendants
12943
- * (children, grand-children, …) are summarized via the recursive CTE.
12944
- *
12945
- * Single-pass CTE: seeds with root tasks matching the filter, walks the full
12946
- * descendant tree once, then aggregates chainCount / lastActivityAt /
12947
- * latestStatus in two lightweight non-recursive CTEs — replacing the original
12948
- * pattern of 3 correlated subqueries each re-running the recursion per row.
12949
- */
12950
- interface ListRecentSessionsOpts {
12911
+ interface ListRecentSessionsOpts extends SessionFilters {
12951
12912
  limit?: number;
12952
12913
  offset?: number;
12953
- /** Filter to root tasks whose `source` is in this list. Empty/undefined → no source filter. */
12954
- source?: string[];
12955
- /** Case-insensitive substring match against `r.task`. */
12956
- q?: string;
12957
- /** When set, restrict to root tasks where `requestedByUserId` equals this value. NULL rows are excluded. */
12958
- requestedByUserId?: string;
12959
- /**
12960
- * When set, restrict to root tasks whose `contextKey` starts with this
12961
- * literal prefix (`%`, `_` and `\` are escaped). Powers the UI contextual
12962
- * session panel's per-page dropdown.
12963
- */
12964
- contextKeyPrefix?: string;
12965
- /** When true, return slim `SessionListItemSummary` rows (default: full). */
12914
+ /** Return a slim root task summary instead of the full task. */
12966
12915
  slim?: boolean;
12967
- /** Restrict to sessions this viewer may open (session sharing). */
12968
- viewer?: SessionViewer;
12969
12916
  }
12970
12917
 
12971
12918
  export async function listRecentSessions(
@@ -12975,149 +12922,50 @@ export async function listRecentSessions(
12975
12922
  opts: ListRecentSessionsOpts & { slim: true },
12976
12923
  ): Promise<SessionListItemSummary[]>;
12977
12924
  export async function listRecentSessions(
12978
- opts?: ListRecentSessionsOpts,
12925
+ opts: ListRecentSessionsOpts = {},
12979
12926
  ): Promise<SessionListItem[] | SessionListItemSummary[]> {
12980
- const limit = opts?.limit ?? 25;
12981
- const offset = opts?.offset ?? 0;
12982
- const sources = opts?.source?.filter((s) => s.length > 0) ?? [];
12983
- const q = opts?.q?.trim();
12984
- const requestedByUserId = opts?.requestedByUserId?.trim() || undefined;
12985
-
12986
- const conditions: string[] = ["r.parentTaskId IS NULL"];
12987
- const params: (string | number | null)[] = [];
12988
-
12989
- if (sources.length > 0) {
12990
- conditions.push(`r.source IN (${sources.map(() => "?").join(", ")})`);
12991
- params.push(...sources);
12992
- }
12993
- if (q && q.length > 0) {
12994
- conditions.push("(lower(r.task) LIKE ? OR lower(COALESCE(r.title, '')) LIKE ?)");
12995
- const like = `%${q.toLowerCase()}%`;
12996
- params.push(like, like);
12997
- }
12998
- if (requestedByUserId) {
12999
- conditions.push("r.requestedByUserId = ?");
13000
- params.push(requestedByUserId);
13001
- }
13002
- if (opts?.contextKeyPrefix) {
13003
- conditions.push(`r.contextKey LIKE ? ESCAPE '\\'`);
13004
- params.push(literalPrefixPattern(opts.contextKeyPrefix));
13005
- }
13006
- if (opts?.viewer) {
13007
- const viewer = sessionViewerCondition(opts.viewer);
13008
- conditions.push(viewer.sql);
13009
- params.push(...viewer.params);
13010
- }
13011
- params.push(limit, offset);
13012
-
12927
+ const query = sessionSummaryQuery(opts);
13013
12928
  const rootRows = await getDbClient().query<
13014
- AgentTaskRow & { __chainCount: number; __lastActivityAt: string; __latestStatus: string }
12929
+ AgentTaskRow & {
12930
+ __chainCount: number;
12931
+ __lastActivityAt: string;
12932
+ __latestStatus: AgentTaskStatus;
12933
+ __readState: SessionReadState | null;
12934
+ __snoozeUntil: string | null;
12935
+ __needsYou: number;
12936
+ }
13015
12937
  >(
13016
- `WITH RECURSIVE chain(root_id, id, lastUpdatedAt, status) AS (
13017
- SELECT r.id, r.id, r.lastUpdatedAt, r.status
13018
- FROM agent_tasks r
13019
- WHERE ${conditions.join(" AND ")}
13020
- UNION ALL
13021
- SELECT c.root_id, t.id, t.lastUpdatedAt, t.status
13022
- FROM agent_tasks t
13023
- JOIN chain c ON t.parentTaskId = c.id
13024
- ),
13025
- agg AS (
13026
- SELECT
13027
- root_id,
13028
- COUNT(*) AS chainCount,
13029
- MAX(lastUpdatedAt) AS lastActivityAt
13030
- FROM chain
13031
- GROUP BY root_id
13032
- ),
13033
- latest_status AS (
13034
- SELECT c.root_id, c.status AS latestStatus
13035
- FROM chain c
13036
- JOIN agg a ON c.root_id = a.root_id AND c.lastUpdatedAt = a.lastActivityAt
13037
- GROUP BY c.root_id
13038
- )
13039
- SELECT
13040
- r.*,
13041
- a.chainCount AS __chainCount,
13042
- a.lastActivityAt AS __lastActivityAt,
13043
- COALESCE(ls.latestStatus, r.status) AS __latestStatus
13044
- FROM agent_tasks r
13045
- JOIN agg a ON a.root_id = r.id
13046
- LEFT JOIN latest_status ls ON ls.root_id = r.id
13047
- ORDER BY a.lastActivityAt DESC, r.createdAt DESC, r.id DESC
13048
- LIMIT ? OFFSET ?`,
13049
- params,
13050
- );
13051
-
13052
- if (opts?.slim) {
13053
- return rootRows.map((row): SessionListItemSummary => {
13054
- const { __chainCount, __lastActivityAt, __latestStatus, ...taskRow } = row;
13055
- return {
13056
- root: rowToAgentTaskSummary(taskRow as AgentTaskRow),
13057
- chainTaskCount: __chainCount,
13058
- lastActivityAt: __lastActivityAt ?? row.lastUpdatedAt,
13059
- latestStatus: (__latestStatus as AgentTaskStatus) ?? row.status,
13060
- };
13061
- });
13062
- }
13063
-
13064
- return rootRows.map((row): SessionListItem => {
13065
- const { __chainCount, __lastActivityAt, __latestStatus, ...taskRow } = row;
13066
- return {
13067
- root: rowToAgentTask(taskRow as AgentTaskRow),
13068
- chainTaskCount: __chainCount,
13069
- lastActivityAt: __lastActivityAt ?? row.lastUpdatedAt,
13070
- latestStatus: (__latestStatus as AgentTaskStatus) ?? row.status,
13071
- };
13072
- });
12938
+ `SELECT r.*, a.chainCount AS __chainCount, a.lastActivityAt AS __lastActivityAt,
12939
+ a.latestStatus AS __latestStatus, a.readState AS __readState,
12940
+ a.snoozeUntil AS __snoozeUntil, a.needsYou AS __needsYou
12941
+ FROM (${query.sql}) a CROSS JOIN agent_tasks r ON r.id = a.root_id
12942
+ ORDER BY ${opts.triage === "later" ? "a.snoozeUntil ASC," : ""}
12943
+ a.lastActivityAt DESC, r.createdAt DESC, r.id DESC LIMIT ? OFFSET ?`,
12944
+ [...query.params, opts.limit ?? 25, opts.offset ?? 0],
12945
+ );
12946
+ const fields = await sessionPageFields(
12947
+ rootRows.map((row) => ({
12948
+ id: row.id,
12949
+ requestedByUserId: row.requestedByUserId ?? undefined,
12950
+ })),
12951
+ );
12952
+ return rootRows.map((row) => ({
12953
+ root: opts.slim ? rowToAgentTaskSummary(row) : rowToAgentTask(row),
12954
+ chainTaskCount: row.__chainCount,
12955
+ lastActivityAt: row.__lastActivityAt,
12956
+ latestStatus: row.__latestStatus,
12957
+ ...(row.__readState ? { readState: row.__readState } : {}),
12958
+ snoozeUntil: row.__snoozeUntil,
12959
+ needsYou: !!row.__needsYou,
12960
+ groupKey: sessionGroupKey(row.key),
12961
+ ...fields.get(row.id),
12962
+ }));
13073
12963
  }
13074
12964
 
13075
- /**
13076
- * Filter-aware count of sessions (root tasks) matching the same `source` / `q`
13077
- * / `requestedByUserId` filters as `listRecentSessions`. Powers a correct
13078
- * `total` in the `/api/sessions` pager — a session is a root task, so this is
13079
- * a plain count, no recursive chain walk needed.
13080
- */
13081
- export async function countSessions(
13082
- opts?: Pick<
13083
- ListRecentSessionsOpts,
13084
- "source" | "q" | "requestedByUserId" | "contextKeyPrefix" | "viewer"
13085
- >,
13086
- ): Promise<number> {
13087
- const sources = opts?.source?.filter((s) => s.length > 0) ?? [];
13088
- const q = opts?.q?.trim();
13089
- const requestedByUserId = opts?.requestedByUserId?.trim() || undefined;
13090
-
13091
- const conditions: string[] = ["r.parentTaskId IS NULL"];
13092
- const params: (string | null)[] = [];
13093
-
13094
- if (sources.length > 0) {
13095
- conditions.push(`r.source IN (${sources.map(() => "?").join(", ")})`);
13096
- params.push(...sources);
13097
- }
13098
- if (q && q.length > 0) {
13099
- conditions.push("(lower(r.task) LIKE ? OR lower(COALESCE(r.title, '')) LIKE ?)");
13100
- const like = `%${q.toLowerCase()}%`;
13101
- params.push(like, like);
13102
- }
13103
- if (requestedByUserId) {
13104
- conditions.push("r.requestedByUserId = ?");
13105
- params.push(requestedByUserId);
13106
- }
13107
- if (opts?.contextKeyPrefix) {
13108
- conditions.push(`r.contextKey LIKE ? ESCAPE '\\'`);
13109
- params.push(literalPrefixPattern(opts.contextKeyPrefix));
13110
- }
13111
- if (opts?.viewer) {
13112
- const viewer = sessionViewerCondition(opts.viewer);
13113
- conditions.push(viewer.sql);
13114
- params.push(...viewer.params);
13115
- }
13116
-
13117
- const row = await getDbClient().get<{ count: number }>(
13118
- `SELECT COUNT(*) AS count FROM agent_tasks r WHERE ${conditions.join(" AND ")}`,
13119
- params,
13120
- );
12965
+ /** Count roots directly unless a filter needs chain activity or participants. */
12966
+ export async function countSessions(opts: SessionFilters = {}): Promise<number> {
12967
+ const query = sessionCountQuery(opts);
12968
+ const row = await getDbClient().get<{ count: number }>(query.sql, query.params);
13121
12969
  return row?.count ?? 0;
13122
12970
  }
13123
12971
 
@@ -37,6 +37,12 @@ export interface IndexMemoryContentParams {
37
37
  * out, the writer has no authority. Throws `MemoryKeyError`.
38
38
  */
39
39
  writer?: MemoryKeyWriter;
40
+ /**
41
+ * With `key`: replace this owner's existing document under the key (same
42
+ * scope and source) in the same transaction as the insert, instead of
43
+ * failing on the unique key index. A re-write may change the chunk count.
44
+ */
45
+ replaceExisting?: boolean;
40
46
  }
41
47
 
42
48
  export interface IndexMemoryContentResult {
@@ -48,6 +54,14 @@ export interface IndexMemoryContentResult {
48
54
  chunks: number;
49
55
  }
50
56
 
57
+ /**
58
+ * Key shared by every chunk of one task's completion memory, so the chunks
59
+ * group as one document (access counting, chunk listing, search collapse).
60
+ */
61
+ export function taskCompletionMemoryKey(taskId: string): string {
62
+ return `/task-completions/${taskId.toLowerCase().replace(/[^a-z0-9._-]/g, "-")}`;
63
+ }
64
+
51
65
  export async function indexMemoryContent(
52
66
  params: IndexMemoryContentParams,
53
67
  ): Promise<IndexMemoryContentResult> {
@@ -111,23 +125,28 @@ export async function indexMemoryContent(
111
125
  }
112
126
 
113
127
  // Atomic batch insert: all chunks or none
114
- const memories = await store.storeBatch(
115
- contentChunks.map((chunk) => ({
116
- agentId: agentId || null,
117
- content: chunk.content,
118
- name,
119
- scope,
120
- source,
121
- sourcePath: sourcePath || null,
122
- sourceTaskId: sourceTaskId || null,
123
- chunkIndex: chunk.chunkIndex,
124
- totalChunks: chunk.totalChunks,
125
- tags: tags || [],
126
- contextKey: params.contextKey ?? null,
127
- intent: params.intent ?? "index memory content",
128
- key: rowKey,
129
- })),
130
- );
128
+ const inputs = contentChunks.map((chunk) => ({
129
+ agentId: agentId || null,
130
+ content: chunk.content,
131
+ name,
132
+ scope,
133
+ source,
134
+ sourcePath: sourcePath || null,
135
+ sourceTaskId: sourceTaskId || null,
136
+ chunkIndex: chunk.chunkIndex,
137
+ totalChunks: chunk.totalChunks,
138
+ tags: tags || [],
139
+ contextKey: params.contextKey ?? null,
140
+ intent: params.intent ?? "index memory content",
141
+ key: rowKey,
142
+ }));
143
+ const memories =
144
+ params.replaceExisting && params.key
145
+ ? await store.replaceDocument(
146
+ { key: params.key, scope, agentId: agentId || null, source },
147
+ inputs,
148
+ )
149
+ : await store.storeBatch(inputs);
131
150
 
132
151
  // Resolve and store deterministic links (wikilinks, PR refs, agent-fs paths)
133
152
  if (agentId) {