@desplega.ai/agent-swarm 2.5.3 → 2.6.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 (349) hide show
  1. package/README.md +2 -2
  2. package/dist/{acp-adapter-gr2fcyyj.js → acp-adapter-dgfakfpw.js} +9 -9
  3. package/dist/{actions-wv766ghf.js → actions-fbqma8aa.js} +21 -21
  4. package/dist/{amp-adapter-speb906t.js → amp-adapter-en0jy6hs.js} +4 -4
  5. package/dist/{app-ag29n14f.js → app-k8a5cgpp.js} +3 -3
  6. package/dist/{assistant-3x8bz3eg.js → assistant-a16433gr.js} +30 -26
  7. package/dist/{boot-reembed-jsmfrpzf.js → boot-reembed-6c1sjfnr.js} +1 -1
  8. package/dist/{boot-reembed-5b3d2eea.js → boot-reembed-f4nn561n.js} +1 -1
  9. package/dist/{boot-scrub-sweep-s73gerxy.js → boot-scrub-sweep-d4w9gajf.js} +4 -4
  10. package/dist/{boot-seeds-5zmax2wr.js → boot-seeds-9bmkspyw.js} +4 -4
  11. package/dist/{claude-adapter-hswdnm3t.js → claude-adapter-nys1kjxa.js} +9 -9
  12. package/dist/{claude-managed-adapter-cvbzj1h6.js → claude-managed-adapter-55207973.js} +3 -3
  13. package/dist/{claude-managed-setup-3k8m3hhc.js → claude-managed-setup-3cm35gey.js} +11 -4
  14. package/dist/{claude-sdk-session-fkbyk66x.js → claude-sdk-session-nw5yd8cp.js} +9313 -9260
  15. package/dist/{cli-210xd1f4.js → cli-03gg9pd1.js} +4 -4
  16. package/dist/{cli-kxpfx2xr.js → cli-0z1xjzwa.js} +2 -2
  17. package/dist/cli-1tem6ns9.js +894 -0
  18. package/dist/{cli-25j01dpj.js → cli-24j1jeeb.js} +3 -3
  19. package/dist/{cli-ta8bd7th.js → cli-2sjhpsns.js} +2 -2
  20. package/dist/{cli-p859hcc8.js → cli-39aet655.js} +2 -2
  21. package/dist/{cli-g1cw7xtt.js → cli-39rrw8yp.js} +1 -1
  22. package/dist/{cli-e2ar4bp1.js → cli-3aqd4jaa.js} +121 -47
  23. package/dist/{cli-jjznk3f7.js → cli-3fwqy71j.js} +69 -35
  24. package/dist/{cli-p9rafdrb.js → cli-3gfvmzht.js} +7 -7
  25. package/dist/{cli-03vpq45j.js → cli-3ndmpxxv.js} +2 -2
  26. package/dist/{cli-aecdcr77.js → cli-4hxrf0p4.js} +228 -74
  27. package/dist/{cli-mz9afxxf.js → cli-4rf0f5vg.js} +15 -3
  28. package/dist/{cli-qzme5949.js → cli-5rj83bam.js} +284 -202
  29. package/dist/{cli-fsdprwrg.js → cli-5sckpqcx.js} +2 -2
  30. package/dist/{cli-cz5nmdxt.js → cli-683y0b3a.js} +2 -2
  31. package/dist/{cli-3ngdk5te.js → cli-6y51ryva.js} +2 -2
  32. package/dist/{cli-n4c8e3s5.js → cli-708wdy32.js} +1 -1
  33. package/dist/{cli-pasyvjmm.js → cli-737pyqxr.js} +1 -1
  34. package/dist/{cli-gx4rm0qx.js → cli-7enjwya9.js} +1 -1
  35. package/dist/{cli-6x9ma5w5.js → cli-803188pb.js} +1 -1
  36. package/dist/{cli-avdvgcay.js → cli-9abdgn5t.js} +28 -9
  37. package/dist/{cli-99xgw26r.js → cli-a2rr0fmg.js} +1 -1
  38. package/dist/{cli-e5bxnjps.js → cli-a34pr3w1.js} +7 -7
  39. package/dist/{cli-502eqv93.js → cli-abkm71vr.js} +1 -1
  40. package/dist/{cli-mmhj8vd7.js → cli-ag2yrnt5.js} +11 -2
  41. package/dist/{cli-n45mr7zh.js → cli-aj8djyqt.js} +5 -5
  42. package/dist/{cli-1fc85mvt.js → cli-art7c1zh.js} +5 -5
  43. package/dist/{cli-40jsnrjm.js → cli-az25fn5f.js} +10 -8
  44. package/dist/{cli-9s05422w.js → cli-bqztfyve.js} +1 -1
  45. package/dist/{cli-bn0rewsy.js → cli-c081bj3b.js} +2 -2
  46. package/dist/{cli-svrj71pg.js → cli-c2habvsf.js} +12 -12
  47. package/dist/{cli-s49hpn37.js → cli-ca6banwz.js} +85 -19
  48. package/dist/{cli-hhxjnjzs.js → cli-cz37jqss.js} +12 -10
  49. package/dist/{cli-6n8axjv1.js → cli-d2dpf95n.js} +2 -2
  50. package/dist/{cli-tx00c0h6.js → cli-dqygeeh5.js} +17 -1
  51. package/dist/{cli-z21whrrg.js → cli-emhjcj2y.js} +17 -17
  52. package/dist/{cli-e8jyq7d8.js → cli-esakngh8.js} +7 -3
  53. package/dist/{cli-ha9rmwgq.js → cli-fb8m699c.js} +2 -2
  54. package/dist/{cli-35gtk1p6.js → cli-ffvsj2qw.js} +44 -13
  55. package/dist/{cli-ce0ak9ms.js → cli-g20jjwt4.js} +26 -26
  56. package/dist/{cli-kxawjrxg.js → cli-g8ka5j4j.js} +772 -194
  57. package/dist/{cli-vwn81jbs.js → cli-gf95q3bm.js} +5 -5
  58. package/dist/{cli-s53vp709.js → cli-gnsb1zxa.js} +2 -2
  59. package/dist/{cli-nwyw3zhg.js → cli-gxee0bqa.js} +1 -1
  60. package/dist/{cli-gpv60278.js → cli-hfeb68z4.js} +7 -7
  61. package/dist/{cli-0ebey9hg.js → cli-j6rktc91.js} +1 -1
  62. package/dist/{cli-ewm3pwbq.js → cli-k21gx5ht.js} +46 -23
  63. package/dist/{cli-egjggehm.js → cli-kf4jree9.js} +2 -2
  64. package/dist/{cli-dabehxhp.js → cli-kt907da6.js} +1 -1
  65. package/dist/{cli-p0rzjkya.js → cli-m5a9m0ak.js} +32 -13
  66. package/dist/{cli-fzcn3d87.js → cli-m70bajzr.js} +3 -3
  67. package/dist/{cli-w1zwctj2.js → cli-mtnj4fwa.js} +11 -5
  68. package/dist/{cli-gpsz7g88.js → cli-mvjz5an3.js} +16 -12
  69. package/dist/{cli-hcpj2xqz.js → cli-n7asmce6.js} +225 -3
  70. package/dist/{cli-y561h5a9.js → cli-nn6vx2mq.js} +5 -5
  71. package/dist/cli-nq677csy.js +140 -0
  72. package/dist/{cli-a5ebc95n.js → cli-pfk92eqz.js} +18 -15
  73. package/dist/{cli-p6zs9h4y.js → cli-rr7zx19j.js} +767 -249
  74. package/dist/{cli-qgf8emrn.js → cli-sqma6vw1.js} +10 -7
  75. package/dist/{cli-0xk505e8.js → cli-sxgtf5fw.js} +1 -1
  76. package/dist/{cli-nzfnt2gs.js → cli-tf1n0r0b.js} +1 -1
  77. package/dist/{cli-qqzmjxkf.js → cli-v49fkghp.js} +11 -6
  78. package/dist/{cli-x77gen2p.js → cli-w2656ka1.js} +7 -7
  79. package/dist/{cli-5kqnemen.js → cli-yvy48785.js} +1 -1
  80. package/dist/{cli-8s5wx3nz.js → cli-zqne1qcj.js} +44 -17
  81. package/dist/cli.js +22 -22
  82. package/dist/{codex-adapter-n8ka66vf.js → codex-adapter-d95q4qsn.js} +7 -7
  83. package/dist/{codex-hook-e2he7aca.js → codex-hook-brpnycn5.js} +1 -1
  84. package/dist/{codex-login-rn7c5gxt.js → codex-login-8cg0vhf2.js} +1 -1
  85. package/dist/{codex-session-runner-fsebts37.js → codex-session-runner-gdwd8h0c.js} +8 -8
  86. package/dist/{commands-t04rt2pc.js → commands-jqmgk54b.js} +2 -2
  87. package/dist/{controller-rgna04r2.js → controller-z3jq589f.js} +30 -4
  88. package/dist/{controllers-connect-bqm1rtft.js → controllers-connect-r2za39ef.js} +23 -23
  89. package/dist/{cursor-adapter-swr2k95y.js → cursor-adapter-4mr4jgkw.js} +2 -3
  90. package/dist/{db-a4m7haw0.js → db-ppv2tze7.js} +3 -3
  91. package/dist/{db-24ck09xy.js → db-w36wwb5g.js} +4 -2
  92. package/dist/{devin-adapter-4ycy4vrf.js → devin-adapter-byen2cxt.js} +1 -1
  93. package/dist/{dispatcher-nwf9ss0q.js → dispatcher-pv5h77vf.js} +20 -20
  94. package/dist/{docker-nfy2gc8t.js → docker-fdzyewpz.js} +4 -4
  95. package/dist/{dsh-adapter-kpdnhjst.js → dsh-adapter-d4pdpb3r.js} +2 -3
  96. package/dist/{e2b-9gkdhm3c.js → e2b-222hsajc.js} +2 -2
  97. package/dist/{e2b-cjtf8k1z.js → e2b-fn3zacvt.js} +2 -2
  98. package/dist/{easter-egg-3d-e4qv8dxj.js → easter-egg-3d-4sp4dvmc.js} +1 -1
  99. package/dist/{event-bus-3z78ep5n.js → event-bus-pghg8hqy.js} +1 -1
  100. package/dist/{execute-0xpz8950.js → execute-jev839y1.js} +1 -1
  101. package/dist/{external-channel-j1e2p34p.js → external-channel-y6nkzhnn.js} +2 -2
  102. package/dist/{grok-adapter-qtjz0whk.js → grok-adapter-t08b9ntm.js} +10 -10
  103. package/dist/handlers-eqsnmv95.js +41 -0
  104. package/dist/{heartbeat-vtesda5d.js → heartbeat-vn31xwdk.js} +23 -23
  105. package/dist/{hook-b2hhj6rd.js → hook-6d0zbvvt.js} +6 -5
  106. package/dist/{hook-8wqaszda.js → hook-cpjs8q51.js} +13 -7
  107. package/dist/{http-mxcqhhcs.js → http-9c6medda.js} +1085 -136
  108. package/dist/{index-spwxjgay.js → index-38t35h0n.js} +20 -20
  109. package/dist/{index-1r8wfpzs.js → index-g6k9tctj.js} +1 -1
  110. package/dist/{index-srg522m6.js → index-k0yt1tvq.js} +20 -20
  111. package/dist/{index-31ceyf42.js → index-pkma1yf8.js} +1 -1
  112. package/dist/{index-n1vbvta9.js → index-qhjf2htm.js} +29 -24
  113. package/dist/index-yqgbfgaj.js +43 -0
  114. package/dist/{keepalive-dsd8rtgx.js → keepalive-em0117bp.js} +4 -4
  115. package/dist/lead-23e7z59t.js +79 -0
  116. package/dist/{maintenance-jqmqwnet.js → maintenance-kbme3qy9.js} +1 -1
  117. package/dist/{oauth-refresh-sweep-q2c3bnvm.js → oauth-refresh-sweep-xysj4yse.js} +3 -3
  118. package/dist/{onboard-w4yd7s8t.js → onboard-p6q0vz97.js} +3 -3
  119. package/dist/{opencode-adapter-9tppvxar.js → opencode-adapter-esv3k9yn.js} +7 -7
  120. package/dist/{otel-impl-f36byzbd.js → otel-impl-54qhqh1z.js} +2 -2
  121. package/dist/{pi-mono-adapter-tqsge1ay.js → pi-mono-adapter-mymbwhj0.js} +41 -14
  122. package/dist/{pricing-refresh-xvak5p4v.js → pricing-refresh-yb75wv33.js} +3 -3
  123. package/dist/{rbac-roles-gfn30fb8.js → rbac-roles-1vp2mw4p.js} +2 -2
  124. package/dist/{rbac-roles-682k2mt5.js → rbac-roles-dexhc9x8.js} +1 -1
  125. package/dist/render-v2-tjay5hhf.js +16 -0
  126. package/dist/{resume-2dvq348g.js → resume-8ayf37g5.js} +11 -9
  127. package/dist/{runtime-bt5pkpm1.js → runtime-hqe5cqcd.js} +1 -1
  128. package/dist/{session-access-7f21ekq9.js → session-access-fdhp987s.js} +4 -4
  129. package/dist/session-source-backfill-qgtc86z1.js +110 -0
  130. package/dist/{setup-epffxtbk.js → setup-4rrmqcq3.js} +1 -1
  131. package/dist/{virtual-modules-mnfbh5mr.js → virtual-modules-52km00d6.js} +2 -2
  132. package/dist/{work-objects-xz1a5rqk.js → work-objects-p28fgd70.js} +23 -23
  133. package/dist/worker-e3w7vye1.js +79 -0
  134. package/dist/{x-pbpezr0s.js → x-wxrzvzx8.js} +2 -2
  135. package/openapi.json +1323 -16
  136. package/package.json +5 -5
  137. package/plugin/commands/create-pr.md +2 -2
  138. package/plugin/pi-skills/create-pr/SKILL.md +2 -2
  139. package/src/assets/source-key.ts +95 -0
  140. package/src/be/db/api-keys.ts +80 -0
  141. package/src/be/db/approvals.ts +48 -0
  142. package/src/be/db/session-labels.ts +28 -0
  143. package/src/be/db/session-moves.ts +5 -6
  144. package/src/be/db/session-tree.ts +153 -14
  145. package/src/be/db/tasks/read.ts +11 -0
  146. package/src/be/db/tasks/write.ts +21 -6
  147. package/src/be/db/workflow-idempotency.ts +183 -0
  148. package/src/be/db/workflow-runs.ts +500 -7
  149. package/src/be/db-retention.ts +61 -6
  150. package/src/be/db.ts +126 -131
  151. package/src/be/memory/providers/openai-embedding.ts +5 -1
  152. package/src/be/migrations/242_workflow_idempotency_keys.sql +40 -0
  153. package/src/be/migrations/244_api_key_validation.sql +4 -0
  154. package/src/be/onboarding.ts +4 -0
  155. package/src/be/scripts/typecheck.ts +1 -1
  156. package/src/be/sealed-json.ts +1 -0
  157. package/src/be/seed-scripts/catalog/compound-insights.inline.ts +2 -2
  158. package/src/be/seed-scripts/catalog/compound-insights.ts +2 -2
  159. package/src/be/seed-skills/bundled-files.generated.json +1 -1
  160. package/src/be/session-source-backfill.ts +163 -0
  161. package/src/be/sql-connections.ts +7 -1
  162. package/src/be/swarm-config-guard.ts +3 -0
  163. package/src/be/users.ts +9 -0
  164. package/src/comb/review-batch.ts +28 -1
  165. package/src/comb/templates.ts +7 -2
  166. package/src/commands/claude-managed-setup.ts +7 -1
  167. package/src/commands/controller.ts +33 -0
  168. package/src/commands/credential-refresh.ts +76 -1
  169. package/src/commands/credential-wait.ts +2 -2
  170. package/src/commands/provider-credentials.ts +210 -170
  171. package/src/commands/runner.ts +31 -6
  172. package/src/extensions/apply-task-create.ts +64 -11
  173. package/src/extensions/contract-types.generated.ts +1 -1
  174. package/src/extensions/contract.ts +30 -0
  175. package/src/extensions/dispatcher.ts +4 -0
  176. package/src/extensions/post-bridge.ts +8 -0
  177. package/src/hooks/context-bound-note.ts +196 -0
  178. package/src/hooks/hook.ts +12 -0
  179. package/src/http/all-routes.ts +2 -0
  180. package/src/http/api-keys.ts +53 -0
  181. package/src/http/assets.ts +15 -0
  182. package/src/http/codex-oauth-accounts.ts +191 -0
  183. package/src/http/comb.ts +15 -2
  184. package/src/http/config.ts +49 -17
  185. package/src/http/core.ts +6 -0
  186. package/src/http/fleet-benchmark.ts +5 -2
  187. package/src/http/idempotency-key.ts +117 -0
  188. package/src/http/index.ts +11 -0
  189. package/src/http/integrations.ts +5 -1
  190. package/src/http/kv.ts +8 -3
  191. package/src/http/onboarding.ts +6 -1
  192. package/src/http/pool-keys.ts +354 -0
  193. package/src/http/sessions.ts +37 -12
  194. package/src/http/stats.ts +1 -0
  195. package/src/http/status.ts +26 -12
  196. package/src/http/tasks.ts +3 -0
  197. package/src/http/used-by.ts +7 -2
  198. package/src/http/workflows.ts +207 -33
  199. package/src/integrations/chat-sdk/inbound.ts +27 -0
  200. package/src/kv-reserved-namespaces.ts +19 -0
  201. package/src/prompts/context-bound-note.ts +48 -0
  202. package/src/prompts/session-templates.ts +25 -1
  203. package/src/providers/acp-targets.ts +1 -1
  204. package/src/providers/amp-adapter.ts +30 -8
  205. package/src/providers/claude-adapter.ts +34 -5
  206. package/src/providers/claude-managed-adapter.ts +42 -9
  207. package/src/providers/claude-sdk-session.ts +10 -0
  208. package/src/providers/claude-session-events.ts +13 -0
  209. package/src/providers/codex-adapter.ts +83 -11
  210. package/src/providers/opencode-adapter.ts +18 -0
  211. package/src/providers/pi-mono-adapter.ts +35 -0
  212. package/src/runtime-providers/docker.ts +6 -2
  213. package/src/scheduler/scheduler.ts +3 -2
  214. package/src/scripts-runtime/swarm-sdk.ts +7 -1
  215. package/src/scripts-runtime/types/stdlib.d.ts +1 -1
  216. package/src/scripts-runtime/types/swarm-extension.d.ts +52 -8
  217. package/src/scripts-runtime/types/swarm-sdk.d.ts +1 -1
  218. package/src/slack/assistant.ts +7 -0
  219. package/src/slack/channel-labels.ts +74 -0
  220. package/src/slack/handlers.ts +8 -0
  221. package/src/slack/inbound-files.ts +5 -2
  222. package/src/slack/thread-buffer.ts +4 -0
  223. package/src/tasks/sibling-awareness.ts +12 -3
  224. package/src/telemetry-context.ts +5 -6
  225. package/src/telemetry-identity.ts +3 -3
  226. package/src/tests/amp-adapter.test.ts +97 -0
  227. package/src/tests/api-keys-validation.test.ts +170 -0
  228. package/src/tests/approval-requests.test.ts +10 -7
  229. package/src/tests/approval-sweeps.test.ts +12 -6
  230. package/src/tests/base-prompt.test.ts +1 -1
  231. package/src/tests/claude-hook-output.test.ts +79 -1
  232. package/src/tests/claude-managed-adapter.test.ts +321 -0
  233. package/src/tests/claude-routes-e2e.test.ts +7 -2
  234. package/src/tests/codex-adapter.test.ts +163 -0
  235. package/src/tests/codex-credential-pools.test.ts +120 -0
  236. package/src/tests/codex-gateway-route.test.ts +165 -0
  237. package/src/tests/codex-oauth-accounts.test.ts +357 -0
  238. package/src/tests/comb-review-batch.test.ts +63 -2
  239. package/src/tests/config-pool-keys.test.ts +533 -0
  240. package/src/tests/context-bound-note.test.ts +269 -0
  241. package/src/tests/credential-check.test.ts +290 -2
  242. package/src/tests/credential-refresh.test.ts +172 -1
  243. package/src/tests/db-retention.test.ts +9 -3
  244. package/src/tests/db-task-read-contract.test.ts +1 -0
  245. package/src/tests/db-task-write-contract.test.ts +1 -3
  246. package/src/tests/docs-index-footprint.test.js +3 -3
  247. package/src/tests/extensions-pre-task.test.ts +325 -0
  248. package/src/tests/fetch-resolved-env.test.ts +17 -0
  249. package/src/tests/fixtures/extensions/record-blocks/hooks.ts +10 -0
  250. package/src/tests/fixtures/extensions/record-blocks/manifest.json +7 -0
  251. package/src/tests/fixtures/extensions/record-task-create/hooks.ts +9 -0
  252. package/src/tests/fixtures/extensions/record-task-create/manifest.json +7 -0
  253. package/src/tests/fixtures/fake-amp.ts +22 -8
  254. package/src/tests/fixtures/held-template-render.ts +42 -0
  255. package/src/tests/fixtures/workflow-idempotency-trigger.child.ts +73 -0
  256. package/src/tests/fleet-benchmark.test.ts +8 -0
  257. package/src/tests/get-workflow-run-tool.test.ts +98 -0
  258. package/src/tests/integrations-http.test.ts +32 -2
  259. package/src/tests/internal-ai/complete-structured.test.ts +37 -1
  260. package/src/tests/internal-ai/credentials.test.ts +23 -0
  261. package/src/tests/kv-http.test.ts +32 -0
  262. package/src/tests/kv-tool.test.ts +25 -0
  263. package/src/tests/multi-runtime-registration.test.ts +12 -12
  264. package/src/tests/onboarding.test.ts +108 -1
  265. package/src/tests/openai-embedding-credentials.test.ts +35 -0
  266. package/src/tests/openapi-route-headers.test.ts +2 -0
  267. package/src/tests/opencode-adapter.test.ts +134 -0
  268. package/src/tests/openrouter-attribution-call-sites.test.ts +2 -2
  269. package/src/tests/org-website.test.ts +36 -0
  270. package/src/tests/pi-mono-adapter.test.ts +105 -0
  271. package/src/tests/prompt-template-session.test.ts +4 -3
  272. package/src/tests/provider-key-probes.test.ts +181 -0
  273. package/src/tests/rbac-e2e-helpers.test.ts +88 -1
  274. package/src/tests/rbac-e2e-helpers.ts +36 -11
  275. package/src/tests/runner-push-delivery.test.ts +25 -0
  276. package/src/tests/runtime-controller-liveness.test.ts +133 -0
  277. package/src/tests/runtime-providers-docker.test.ts +2 -0
  278. package/src/tests/script-connections.test.ts +5 -0
  279. package/src/tests/script-run-config-scope.test.ts +91 -3
  280. package/src/tests/script-runs-retention.test.ts +212 -0
  281. package/src/tests/sdk-allowlist.test.ts +39 -0
  282. package/src/tests/session-logs.test.ts +70 -0
  283. package/src/tests/session-source-backfill.test.ts +429 -0
  284. package/src/tests/session-source-keys.test.ts +236 -0
  285. package/src/tests/session-source-labels.test.ts +419 -0
  286. package/src/tests/sessions-detail-batch.test.ts +2 -0
  287. package/src/tests/sessions-move.test.ts +131 -0
  288. package/src/tests/sessions-tree.test.ts +336 -2
  289. package/src/tests/slack-inbound-files.test.ts +103 -23
  290. package/src/tests/status.test.ts +101 -18
  291. package/src/tests/tasks-workflow-run-filter.test.ts +146 -0
  292. package/src/tests/test-net.ts +31 -0
  293. package/src/tests/transcription.test.ts +8 -0
  294. package/src/tests/used-by-route.test.ts +11 -0
  295. package/src/tests/workflow-cascade-cancel.test.ts +1341 -0
  296. package/src/tests/workflow-foreach-supersede-retry.test.ts +488 -0
  297. package/src/tests/workflow-http-v2.test.ts +131 -0
  298. package/src/tests/workflow-idempotency.test.ts +915 -0
  299. package/src/tests/workflow-step-order.test.ts +92 -0
  300. package/src/tests/workflow-sub-workflow.test.ts +30 -0
  301. package/src/tools/get-tasks.ts +8 -0
  302. package/src/tools/kv/kv-read-auth.ts +4 -2
  303. package/src/tools/task-action.ts +1 -1
  304. package/src/tools/workflows/cancel-workflow-run.ts +1 -1
  305. package/src/tools/workflows/get-workflow-run.ts +14 -4
  306. package/src/types.ts +24 -0
  307. package/src/utils/company-profile.ts +51 -1
  308. package/src/utils/context-window.ts +8 -6
  309. package/src/utils/credentials.ts +5 -2
  310. package/src/utils/internal-ai/complete-structured.ts +5 -0
  311. package/src/utils/internal-ai/credentials.ts +4 -3
  312. package/src/utils/provider-key-probes.ts +328 -0
  313. package/src/utils/transcription.ts +4 -1
  314. package/src/workflows/approval-cancel.ts +15 -13
  315. package/src/workflows/approval-notifications.ts +5 -1
  316. package/src/workflows/checkpoint.ts +6 -3
  317. package/src/workflows/engine.ts +30 -8
  318. package/src/workflows/executors/foreach.ts +2 -2
  319. package/src/workflows/executors/sub-workflow.ts +8 -1
  320. package/src/workflows/foreach-driver.ts +10 -3
  321. package/src/workflows/foreach-join.ts +29 -6
  322. package/src/workflows/recovery.ts +6 -1
  323. package/src/workflows/resume.ts +168 -56
  324. package/src/workflows/retry-poller.ts +49 -3
  325. package/src/workflows/task-step-routing.ts +29 -7
  326. package/templates/ai-toolbox.manifest.json +8 -8
  327. package/templates/community/open-prompt-showreel/PLAYBOOK.md +5 -6
  328. package/templates/community/open-prompt-showreel/lead-prompt.md +1 -1
  329. package/templates/community/open-prompt-showreel/skills/open-prompt-showreel/SKILL.md +4 -4
  330. package/templates/skills/agent-browser/SKILL.md +4 -4
  331. package/templates/skills/agent-browser/content.md +4 -4
  332. package/templates/skills/code-quality/config.json +1 -1
  333. package/templates/skills/code-quality/content.md +2 -2
  334. package/templates/skills/delegate-work/content.md +29 -25
  335. package/templates/skills/delegate-work/files/scripts/codex-exec.sh +5 -5
  336. package/templates/skills/qa/content.md +2 -2
  337. package/templates/skills/researching/content.md +4 -0
  338. package/templates/skills/swarm-scripts/SKILL.md +1 -0
  339. package/templates/skills/swarm-scripts/content.md +1 -0
  340. package/templates/skills/work-on-task/SKILL.md +1 -1
  341. package/templates/skills/work-on-task/content.md +1 -1
  342. package/dist/cli-7421jzsw.js +0 -84
  343. package/dist/cli-e45e4m6b.js +0 -413
  344. package/dist/cli-zw88zjba.js +0 -307
  345. package/dist/handlers-19xr6dnz.js +0 -41
  346. package/dist/index-kw2mmat7.js +0 -43
  347. package/dist/lead-zyerk1x2.js +0 -79
  348. package/dist/render-v2-hgytz8nn.js +0 -12
  349. package/dist/worker-42t8emjt.js +0 -79
@@ -2,7 +2,7 @@ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
2
2
  import { randomBytes } from "node:crypto";
3
3
  import { createServer, type Server } from "node:http";
4
4
  import { closeDb, createAgent, initDb, upsertSwarmConfig } from "../be/db";
5
- import { mintScriptRunToken, revokeSessionToken } from "../be/users";
5
+ import { mintAgentToken, mintScriptRunToken, revokeSessionToken } from "../be/users";
6
6
  import { handleConfig } from "../http/config";
7
7
  import { handleCore } from "../http/core";
8
8
  import { handleSessions } from "../http/sessions";
@@ -17,12 +17,16 @@ import { listenOnFreePort } from "./test-net";
17
17
  const API_KEY = `script-run-config-operator-${randomBytes(8).toString("hex")}`;
18
18
  const WORKER_SECRET = `worker-secret-${randomBytes(8).toString("hex")}`;
19
19
  const OTHER_SECRET = `other-secret-${randomBytes(8).toString("hex")}`;
20
+ const GLOBAL_SECRET = `global-secret-${randomBytes(8).toString("hex")}`;
21
+ const SHADOWED_GLOBAL = `shadowed-global-${randomBytes(8).toString("hex")}`;
22
+ const SHADOWING_AGENT = `shadowing-agent-${randomBytes(8).toString("hex")}`;
20
23
 
21
24
  let server: Server;
22
25
  let baseUrl: string;
23
26
  let workerId: string;
24
27
  let otherId: string;
25
28
  let otherConfigId: string;
29
+ let ownConfigId: string;
26
30
 
27
31
  function headers(bearer: string, agentId?: string): Record<string, string> {
28
32
  return {
@@ -36,11 +40,32 @@ beforeAll(async () => {
36
40
  initDb(":memory:");
37
41
  workerId = (await createAgent({ name: "script-run-worker", isLead: false, status: "idle" })).id;
38
42
  otherId = (await createAgent({ name: "script-run-other", isLead: false, status: "idle" })).id;
43
+ ownConfigId = (
44
+ await upsertSwarmConfig({
45
+ scope: "agent",
46
+ scopeId: workerId,
47
+ key: "OWN_SECRET",
48
+ value: WORKER_SECRET,
49
+ isSecret: true,
50
+ })
51
+ ).id;
52
+ await upsertSwarmConfig({
53
+ scope: "global",
54
+ key: "GLOBAL_SECRET",
55
+ value: GLOBAL_SECRET,
56
+ isSecret: true,
57
+ });
58
+ await upsertSwarmConfig({
59
+ scope: "global",
60
+ key: "SHADOWED",
61
+ value: SHADOWED_GLOBAL,
62
+ isSecret: true,
63
+ });
39
64
  await upsertSwarmConfig({
40
65
  scope: "agent",
41
66
  scopeId: workerId,
42
- key: "OWN_SECRET",
43
- value: WORKER_SECRET,
67
+ key: "SHADOWED",
68
+ value: SHADOWING_AGENT,
44
69
  isSecret: true,
45
70
  });
46
71
  otherConfigId = (
@@ -118,3 +143,66 @@ describe("script run token is scoped to its executing agent", () => {
118
143
  }
119
144
  });
120
145
  });
146
+
147
+ // `ctx.swarm.config` holds the run's resolved config (own agent over global).
148
+ // The raw list and by-id reads must hand the run's token those same values,
149
+ // and only those, or a script that reads a key through `config_list` gets a
150
+ // masked placeholder and fails while the run already holds the real value.
151
+ describe("script run token reads its held secrets on the raw config routes", () => {
152
+ test("list reveals the rows the run holds and masks the rest", async () => {
153
+ const run = await mintScriptRunToken(workerId, 60_000);
154
+ try {
155
+ const res = await fetch(`${baseUrl}/api/config?includeSecrets=true`, {
156
+ headers: headers(run.plaintext),
157
+ });
158
+ expect(res.status).toBe(200);
159
+ const body = await res.text();
160
+ expect(body).toContain(WORKER_SECRET);
161
+ expect(body).toContain(GLOBAL_SECRET);
162
+ expect(body).toContain(SHADOWING_AGENT);
163
+ // The run holds the agent row for SHADOWED, not the global one.
164
+ expect(body).not.toContain(SHADOWED_GLOBAL);
165
+ expect(body).not.toContain(OTHER_SECRET);
166
+ } finally {
167
+ await revokeSessionToken(run.tokenId);
168
+ }
169
+ });
170
+
171
+ test("by-id reveals an own row and masks another agent's row", async () => {
172
+ const run = await mintScriptRunToken(workerId, 60_000);
173
+ try {
174
+ const own = await fetch(`${baseUrl}/api/config/${ownConfigId}?includeSecrets=true`, {
175
+ headers: headers(run.plaintext),
176
+ });
177
+ expect(await own.text()).toContain(WORKER_SECRET);
178
+ const other = await fetch(`${baseUrl}/api/config/${otherConfigId}?includeSecrets=true`, {
179
+ headers: headers(run.plaintext),
180
+ });
181
+ expect(other.status).toBe(200);
182
+ expect(await other.text()).not.toContain(OTHER_SECRET);
183
+ } finally {
184
+ await revokeSessionToken(run.tokenId);
185
+ }
186
+ });
187
+
188
+ test("without includeSecrets the run token still gets masked values", async () => {
189
+ const run = await mintScriptRunToken(workerId, 60_000);
190
+ try {
191
+ const res = await fetch(`${baseUrl}/api/config`, { headers: headers(run.plaintext) });
192
+ expect(await res.text()).not.toContain(WORKER_SECRET);
193
+ } finally {
194
+ await revokeSessionToken(run.tokenId);
195
+ }
196
+ });
197
+
198
+ test("the worker's own agent token keeps the masked list", async () => {
199
+ const worker = await mintAgentToken(workerId);
200
+ const res = await fetch(`${baseUrl}/api/config?includeSecrets=true`, {
201
+ headers: headers(worker.plaintext),
202
+ });
203
+ expect(res.status).toBe(200);
204
+ const body = await res.text();
205
+ expect(body).not.toContain(WORKER_SECRET);
206
+ expect(body).not.toContain(GLOBAL_SECRET);
207
+ });
208
+ });
@@ -0,0 +1,212 @@
1
+ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
2
+ import { unlink } from "node:fs/promises";
3
+ import { closeDb, getDbClient, initDb } from "../be/db";
4
+ import {
5
+ DB_RETENTION_TABLES,
6
+ getDbRetentionStats,
7
+ resetDbRetentionForTests,
8
+ runDbRetentionTick,
9
+ sweepStatements,
10
+ } from "../be/db-retention";
11
+
12
+ const TEST_DB_PATH = "./test-script-runs-retention.sqlite";
13
+ const DAYS_KEY = "SCRIPT_RUNS_RETENTION_DAYS";
14
+ // With 1 day of retention the cutoff is 2026-08-22T12:00:00.000Z.
15
+ const NOW = new Date("2026-08-23T12:00:00.000Z");
16
+ const OLD_ISO = "2026-08-01T00:00:00.000Z";
17
+ const OLD_SPACED = "2026-08-01 00:00:00";
18
+ const RECENT_ISO = "2026-08-23T11:00:00.000Z";
19
+
20
+ const SCRIPT_RUNS = DB_RETENTION_TABLES.find((table) => table.table === "script_runs");
21
+ if (!SCRIPT_RUNS) throw new Error("script_runs is not in the retention allowlist");
22
+
23
+ async function removeDbFiles(): Promise<void> {
24
+ for (const suffix of ["", "-wal", "-shm"])
25
+ await unlink(`${TEST_DB_PATH}${suffix}`).catch(() => undefined);
26
+ }
27
+
28
+ async function insertRun(
29
+ id: string,
30
+ status: string,
31
+ startedAt: string,
32
+ options: { kind?: "inline" | "workflow"; idempotencyKey?: string } = {},
33
+ ): Promise<void> {
34
+ await getDbClient().run(
35
+ `INSERT INTO script_runs (id, agentId, scriptName, source, args, status, startedAt, kind, idempotencyKey)
36
+ VALUES (?, 'agent-1', 'retention-test', 'export default () => 1', '{}', ?, ?, ?, ?)`,
37
+ [id, status, startedAt, options.kind ?? "inline", options.idempotencyKey ?? null],
38
+ );
39
+ }
40
+
41
+ async function insertJournal(runId: string, stepKey: string): Promise<void> {
42
+ await getDbClient().run(
43
+ "INSERT INTO script_run_journal (id, runId, stepKey, stepType, status) VALUES (?, ?, ?, 'tool', 'completed')",
44
+ [`${runId}-${stepKey}`, runId, stepKey],
45
+ );
46
+ }
47
+
48
+ async function runIds(): Promise<string[]> {
49
+ const rows = await getDbClient().query<{ id: string }>("SELECT id FROM script_runs ORDER BY id");
50
+ return rows.map((row) => row.id);
51
+ }
52
+
53
+ async function journalRunIds(): Promise<string[]> {
54
+ const rows = await getDbClient().query<{ runId: string }>(
55
+ "SELECT DISTINCT runId FROM script_run_journal ORDER BY runId",
56
+ );
57
+ return rows.map((row) => row.runId);
58
+ }
59
+
60
+ beforeAll(async () => {
61
+ closeDb();
62
+ await removeDbFiles();
63
+ initDb(TEST_DB_PATH);
64
+ });
65
+
66
+ afterAll(async () => {
67
+ await resetDbRetentionForTests();
68
+ delete process.env[DAYS_KEY];
69
+ delete process.env.DB_RETENTION_DRY_RUN;
70
+ closeDb();
71
+ await removeDbFiles();
72
+ });
73
+
74
+ beforeEach(async () => {
75
+ await resetDbRetentionForTests();
76
+ delete process.env[DAYS_KEY];
77
+ delete process.env.DB_RETENTION_DRY_RUN;
78
+ await getDbClient().run("DELETE FROM script_run_journal");
79
+ await getDbClient().run("DELETE FROM script_runs");
80
+ });
81
+
82
+ describe("script_runs age-based retention", () => {
83
+ test("is opt-in: an unset key deletes nothing", async () => {
84
+ await insertRun("old-completed", "completed", OLD_ISO);
85
+
86
+ await runDbRetentionTick({ now: NOW });
87
+
88
+ expect(await runIds()).toEqual(["old-completed"]);
89
+ expect(getDbRetentionStats().scriptRuns).toBeUndefined();
90
+ });
91
+
92
+ test("deletes old finished runs and keeps running, paused, recent and keyed runs", async () => {
93
+ for (const status of ["completed", "failed", "cancelled", "aborted_limit"]) {
94
+ await insertRun(`old-${status}-iso`, status, OLD_ISO);
95
+ await insertRun(`old-${status}-spaced`, status, OLD_SPACED, { kind: "workflow" });
96
+ }
97
+ await insertRun("old-running", "running", OLD_SPACED, { kind: "workflow" });
98
+ await insertRun("old-paused", "paused", OLD_SPACED, { kind: "workflow" });
99
+ await insertRun("old-keyed", "completed", OLD_SPACED, {
100
+ kind: "workflow",
101
+ idempotencyKey: "launch-once",
102
+ });
103
+ await insertRun("recent-completed", "completed", RECENT_ISO);
104
+ process.env[DAYS_KEY] = "1";
105
+
106
+ await runDbRetentionTick({ now: NOW });
107
+
108
+ expect(await runIds()).toEqual(["old-keyed", "old-paused", "old-running", "recent-completed"]);
109
+ expect(getDbRetentionStats().scriptRuns).toMatchObject({
110
+ rowsDeleted: 8,
111
+ drained: true,
112
+ backlogRemaining: 0,
113
+ dryRun: false,
114
+ });
115
+ });
116
+
117
+ test("journal rows leave with their run and stay with a surviving one", async () => {
118
+ await insertRun("old-done", "completed", OLD_SPACED, { kind: "workflow" });
119
+ await insertRun("old-paused", "paused", OLD_SPACED, { kind: "workflow" });
120
+ for (const step of ["a", "b", "c"]) {
121
+ await insertJournal("old-done", step);
122
+ await insertJournal("old-paused", step);
123
+ }
124
+ process.env[DAYS_KEY] = "1";
125
+
126
+ await runDbRetentionTick({ now: NOW });
127
+
128
+ expect(await runIds()).toEqual(["old-paused"]);
129
+ expect(await journalRunIds()).toEqual(["old-paused"]);
130
+ const journal = await getDbClient().get<{ n: number }>(
131
+ "SELECT COUNT(*) AS n FROM script_run_journal",
132
+ );
133
+ expect(journal?.n).toBe(3);
134
+ });
135
+
136
+ test("never deletes a row early across the two stored timestamp formats", async () => {
137
+ // Cutoff: 2026-08-22T12:00:00.000Z, compared as '2026-08-22 12:00:00'.
138
+ await insertRun("spaced-before", "completed", "2026-08-22 11:59:59", { kind: "workflow" });
139
+ await insertRun("spaced-at", "completed", "2026-08-22 12:00:00", { kind: "workflow" });
140
+ await insertRun("iso-previous-day", "completed", "2026-08-21T23:59:59.999Z");
141
+ // Older than the cutoff, but on the cutoff's own date: kept until a later tick.
142
+ await insertRun("iso-cutoff-day", "completed", "2026-08-22T11:00:00.000Z");
143
+ await insertRun("iso-after", "completed", "2026-08-22T12:00:00.001Z");
144
+ process.env[DAYS_KEY] = "1";
145
+
146
+ await runDbRetentionTick({ now: NOW });
147
+
148
+ expect(await runIds()).toEqual(["iso-after", "iso-cutoff-day", "spaced-at"]);
149
+
150
+ // A day later every one of them is past the cutoff's date.
151
+ await runDbRetentionTick({ now: new Date("2026-08-24T00:00:00.000Z") });
152
+
153
+ expect(await runIds()).toEqual([]);
154
+ });
155
+
156
+ test("dry run counts the would-delete rows without deleting", async () => {
157
+ await insertRun("old-completed", "completed", OLD_ISO);
158
+ await insertRun("old-failed", "failed", OLD_SPACED, { kind: "workflow" });
159
+ await insertRun("old-running", "running", OLD_SPACED, { kind: "workflow" });
160
+ process.env[DAYS_KEY] = "1";
161
+ process.env.DB_RETENTION_DRY_RUN = "true";
162
+
163
+ await runDbRetentionTick({ now: NOW });
164
+
165
+ expect(await runIds()).toHaveLength(3);
166
+ expect(getDbRetentionStats().scriptRuns).toMatchObject({
167
+ rowsDeleted: 0,
168
+ backlogRemaining: 2,
169
+ dryRun: true,
170
+ });
171
+ });
172
+
173
+ test("drains a backlog larger than one batch within the batch ceiling", async () => {
174
+ await getDbClient().run(
175
+ `WITH RECURSIVE n(v) AS (SELECT 1 UNION ALL SELECT v + 1 FROM n WHERE v < 1200)
176
+ INSERT INTO script_runs (id, agentId, source, status, startedAt)
177
+ SELECT 'bulk-' || v, 'agent-1', 'x', 'completed', ? FROM n`,
178
+ [OLD_ISO],
179
+ );
180
+ process.env[DAYS_KEY] = "1";
181
+
182
+ await runDbRetentionTick({ now: NOW });
183
+
184
+ const stats = getDbRetentionStats().scriptRuns;
185
+ expect(await runIds()).toEqual([]);
186
+ expect(stats?.rowsDeleted).toBe(1200);
187
+ expect(stats?.batches).toBeGreaterThan(1);
188
+ expect(stats?.batchSize).toBeLessThanOrEqual(500);
189
+ });
190
+
191
+ test("the delete, the backlog count and the journal cascade all use an index", async () => {
192
+ const statements = sweepStatements(SCRIPT_RUNS, 30, NOW);
193
+ const client = getDbClient();
194
+
195
+ for (const [sql, params] of [
196
+ [statements.deleteSql, [...statements.params, 100]],
197
+ [statements.backlogSql, statements.params],
198
+ ] as const) {
199
+ const plan = await client.query<{ detail: string }>(`EXPLAIN QUERY PLAN ${sql}`, [...params]);
200
+ const detail = plan.map((row) => row.detail).join("\n");
201
+ expect(detail).toContain("USING COVERING INDEX idx_script_runs_status_startedAt");
202
+ expect(detail).not.toMatch(/SCAN script_runs(?! USING)/);
203
+ }
204
+
205
+ // ON DELETE CASCADE looks up journal rows of every deleted run by runId.
206
+ const fkPlan = await client.query<{ detail: string }>(
207
+ "EXPLAIN QUERY PLAN SELECT rowid FROM script_run_journal WHERE runId = ?",
208
+ ["x"],
209
+ );
210
+ expect(fkPlan.map((row) => row.detail).join("\n")).toContain("idx_srj_runId");
211
+ });
212
+ });
@@ -98,6 +98,45 @@ describe("script SDK allowlist", () => {
98
98
  }
99
99
  });
100
100
 
101
+ test("workflow_getRun forwards fields to the run route and rejects an unknown value", async () => {
102
+ const requests: URL[] = [];
103
+ const httpServer = Bun.serve({
104
+ port: 0,
105
+ fetch(req) {
106
+ requests.push(new URL(req.url));
107
+ return Response.json({ run: { id: "run-1" }, steps: [] });
108
+ },
109
+ });
110
+ const config = new SwarmConfig({
111
+ system: {
112
+ apiKey: { value: "sdk-test-key", isSecret: true },
113
+ agentId: { value: "sdk-test-agent", isSecret: false },
114
+ mcpBaseUrl: { value: `http://127.0.0.1:${httpServer.port}`, isSecret: false },
115
+ },
116
+ user: {},
117
+ });
118
+ const sdk = createSwarmSdk(config);
119
+
120
+ try {
121
+ await sdk.workflow_getRun({ id: "run-1", fields: "slim" });
122
+ await sdk.workflow_getRun({ id: "run-1" });
123
+ expect(requests.map((url) => url.pathname)).toEqual([
124
+ "/api/workflow-runs/run-1",
125
+ "/api/workflow-runs/run-1",
126
+ ]);
127
+ expect(requests[0]?.searchParams.get("fields")).toBe("slim");
128
+ // Omitted `fields` sends no query, so the route keeps its full default.
129
+ expect(requests[1]?.search).toBe("");
130
+
131
+ await expect(sdk.workflow_getRun({ id: "run-1", fields: "tiny" })).rejects.toThrow(
132
+ '`fields` must be "full" or "slim"',
133
+ );
134
+ expect(requests).toHaveLength(2);
135
+ } finally {
136
+ httpServer.stop(true);
137
+ }
138
+ });
139
+
101
140
  test("approval_cancel posts to the cancel route with the reason", async () => {
102
141
  let request: { method: string; path: string; body: unknown } | undefined;
103
142
  const httpServer = Bun.serve({
@@ -5,6 +5,7 @@ import {
5
5
  closeDb,
6
6
  createSessionLogs,
7
7
  createTaskExtended,
8
+ getDbClient,
8
9
  getSessionLogsBySession,
9
10
  getSessionLogsByTaskId,
10
11
  getTaskById,
@@ -226,6 +227,75 @@ describe("Session Logs API", () => {
226
227
  expect(logs[3]?.iteration).toBe(2);
227
228
  expect(logs[3]?.lineNumber).toBe(1);
228
229
  });
230
+
231
+ test("keeps write order across many flushes of one iteration", async () => {
232
+ const task = await createTaskExtended("Task for multi-flush ordering");
233
+ // The runner flushes every 50 lines or 5s, so one iteration arrives as
234
+ // many POSTs. Each POST used to restart lineNumber at 0.
235
+ const written: string[] = [];
236
+ for (let flush = 0; flush < 3; flush++) {
237
+ const lines = [0, 1, 2, 3].map((i) => `flush${flush}-line${i}`);
238
+ written.push(...lines);
239
+ await createSessionLogs({
240
+ taskId: task.id,
241
+ sessionId: "multi-flush-session",
242
+ iteration: 1,
243
+ cli: "claude",
244
+ lines,
245
+ });
246
+ }
247
+
248
+ const all = await getSessionLogsByTaskId(task.id);
249
+ expect(all.map((l) => l.content)).toEqual(written);
250
+ expect(all.map((l) => l.lineNumber)).toEqual(written.map((_, i) => i));
251
+
252
+ const tail = await getSessionLogsByTaskId(task.id, 5);
253
+ expect(tail.map((l) => l.content)).toEqual(written.slice(-5));
254
+
255
+ const bySession = await getSessionLogsBySession("multi-flush-session", 1);
256
+ expect(bySession.map((l) => l.content)).toEqual(written);
257
+ });
258
+
259
+ test("reads rows stored with per-flush lineNumbers in write order", async () => {
260
+ const task = await createTaskExtended("Task with legacy session log rows");
261
+ // Rows written before the fix: lineNumber restarts per POST, and
262
+ // createdAt ties inside one POST.
263
+ const legacy = [
264
+ ["a0", 0],
265
+ ["a1", 1],
266
+ ["a2", 2],
267
+ ["b0", 0],
268
+ ["b1", 1],
269
+ ["c0", 0],
270
+ ] as const;
271
+ await getDbClient().transaction(async (tx) => {
272
+ for (const [content, lineNumber] of legacy) {
273
+ await tx.run(
274
+ `INSERT INTO session_logs (id, taskId, sessionId, iteration, cli, content, lineNumber, createdAt)
275
+ VALUES (?, ?, 'legacy-session', 1, 'claude', ?, ?, '2026-10-01T00:00:00.000Z')`,
276
+ [crypto.randomUUID(), task.id, content, lineNumber],
277
+ );
278
+ }
279
+ });
280
+ // A flush after deploy lands behind the legacy rows.
281
+ await createSessionLogs({
282
+ taskId: task.id,
283
+ sessionId: "legacy-session",
284
+ iteration: 1,
285
+ cli: "claude",
286
+ lines: ["d0", "d1"],
287
+ });
288
+
289
+ const written = ["a0", "a1", "a2", "b0", "b1", "c0", "d0", "d1"];
290
+ const all = await getSessionLogsByTaskId(task.id);
291
+ expect(all.map((l) => l.content)).toEqual(written);
292
+
293
+ const tail = await getSessionLogsByTaskId(task.id, 4);
294
+ expect(tail.map((l) => l.content)).toEqual(["b1", "c0", "d0", "d1"]);
295
+
296
+ const bySession = await getSessionLogsBySession("legacy-session", 1);
297
+ expect(bySession.map((l) => l.content)).toEqual(written);
298
+ });
229
299
  });
230
300
 
231
301
  describe("POST /api/session-logs", () => {