@desplega.ai/agent-swarm 2.7.5 → 2.7.7

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 (319) hide show
  1. package/dist/{acp-adapter-g3ads55s.js → acp-adapter-7b4vk3h5.js} +5 -4
  2. package/dist/{actions-zwfykqqk.js → actions-3tt153af.js} +18 -17
  3. package/dist/{amp-adapter-z3s0ssdt.js → amp-adapter-njpts0yr.js} +1 -1
  4. package/dist/{anthropic-messages-x4q6rfq5.js → anthropic-messages-xey2skwx.js} +7 -7
  5. package/dist/{app-vmp4fgep.js → app-nky4378f.js} +3 -3
  6. package/dist/{assistant-vwde653f.js → assistant-jnb669pa.js} +24 -22
  7. package/dist/{azure-openai-responses-s1w5n70z.js → azure-openai-responses-26t7j9ra.js} +5 -5
  8. package/dist/{boot-reembed-375t7tbk.js → boot-reembed-9mgvaztt.js} +1 -1
  9. package/dist/{boot-reembed-gq9rcpnj.js → boot-reembed-qs2djdej.js} +1 -1
  10. package/dist/{boot-scrub-sweep-nd938kyf.js → boot-scrub-sweep-yjpdj7s1.js} +3 -3
  11. package/dist/{boot-seeds-aykx51v3.js → boot-seeds-5cm6ambd.js} +4 -4
  12. package/dist/{boxd-k1j26g3h.js → boxd-nv507va8.js} +5 -5
  13. package/dist/{claude-adapter-8j4hm099.js → claude-adapter-fn287zmm.js} +5 -4
  14. package/dist/{claude-managed-setup-0wyynpn1.js → claude-managed-setup-wtdz2mj2.js} +1 -1
  15. package/dist/{claude-sdk-session-q6wj5hmy.js → claude-sdk-session-x91kqyhm.js} +9677 -9514
  16. package/dist/{cli-j3jvnpe8.js → cli-08bmz9z9.js} +1 -1
  17. package/dist/{cli-gjr2y2xk.js → cli-0agcv1v5.js} +4 -1
  18. package/dist/{cli-y24sc29k.js → cli-1c6tzt8g.js} +30 -8
  19. package/dist/{cli-mjzynrse.js → cli-35rsp8ye.js} +1 -1
  20. package/dist/{cli-rpm2sgv0.js → cli-3fg0bdcv.js} +19 -2
  21. package/dist/{cli-nn4d8dne.js → cli-5b456bpf.js} +2 -2
  22. package/dist/{cli-bhjt02p8.js → cli-5jnhv72d.js} +1 -1
  23. package/dist/{cli-fqzx7gwa.js → cli-5zx699j9.js} +2 -2
  24. package/dist/{cli-hf6zt5m6.js → cli-69szn7w7.js} +17 -1
  25. package/dist/{cli-vqypxzh4.js → cli-6g0hb4cp.js} +8 -8
  26. package/dist/{cli-tqgg40ek.js → cli-6pfd54vx.js} +9 -8
  27. package/dist/{cli-bsn8nhrq.js → cli-6vcp76qk.js} +3 -3
  28. package/dist/{cli-zppwg9p9.js → cli-72v799xn.js} +1 -1
  29. package/dist/cli-82j36f2m.js +45 -0
  30. package/dist/{cli-hn9hxmz9.js → cli-8305gbq0.js} +29 -16
  31. package/dist/{cli-z8eh3ex8.js → cli-8sckt74b.js} +8 -8
  32. package/dist/{cli-bnz7a7qf.js → cli-95jzxcp3.js} +1 -1
  33. package/dist/{cli-0bxqnvq9.js → cli-adeegh7d.js} +29 -7
  34. package/dist/{cli-g61qxmge.js → cli-afkqkrtw.js} +3 -3
  35. package/dist/{cli-7a42nfy0.js → cli-ayayvfg4.js} +24 -4
  36. package/dist/{cli-bqt1mptt.js → cli-az48n0nr.js} +4 -4
  37. package/dist/{cli-z6jkx23q.js → cli-bfv7vdg6.js} +9 -9
  38. package/dist/{cli-pywf3eh5.js → cli-caj33vcq.js} +5 -5
  39. package/dist/{cli-5pag6rmn.js → cli-cb2a8pna.js} +1 -1
  40. package/dist/{cli-wvaawacd.js → cli-cppkf404.js} +5236 -72
  41. package/dist/{cli-mry0qzym.js → cli-ddaanqkd.js} +123 -10
  42. package/dist/{cli-0gey5fzy.js → cli-e0kmphd6.js} +2 -2
  43. package/dist/{cli-zh3fp8v2.js → cli-e46hg0xw.js} +1 -1
  44. package/dist/{cli-jm2vfpfd.js → cli-e5bzrn01.js} +1 -1
  45. package/dist/{cli-qxnf8z2z.js → cli-excnnjg2.js} +165 -153
  46. package/dist/{cli-71ptav7x.js → cli-ferkkrg9.js} +1 -1
  47. package/dist/{cli-vpf89f34.js → cli-fmjd0t1y.js} +3 -3
  48. package/dist/{cli-rm6hqj3z.js → cli-fyt2g4wd.js} +33 -139
  49. package/dist/cli-g08zdaec.js +55 -0
  50. package/dist/{cli-ehq736cn.js → cli-g7s47cc5.js} +4 -4
  51. package/dist/{cli-82ae98pz.js → cli-gz6q4eve.js} +3 -3
  52. package/dist/{cli-82jg7fp9.js → cli-he5nxaa8.js} +1 -1
  53. package/dist/{cli-rqkxrmgw.js → cli-je518txm.js} +39 -14
  54. package/dist/{cli-8hs41zy2.js → cli-jtjtabxe.js} +1 -1
  55. package/dist/{cli-611a1294.js → cli-k0wwfqbg.js} +18 -1
  56. package/dist/{cli-nzxr556s.js → cli-k1nje3v3.js} +38 -43
  57. package/dist/{cli-3h4nhjn8.js → cli-k8196sh2.js} +9 -7
  58. package/dist/{cli-afrrppsw.js → cli-k899ehvh.js} +2 -2
  59. package/dist/{cli-0731hvbw.js → cli-m0da8j35.js} +23 -9
  60. package/dist/cli-m2z8jagn.js +12078 -0
  61. package/dist/{cli-8veytvbh.js → cli-mkk9rnzz.js} +5 -5
  62. package/dist/{cli-7wspm5gm.js → cli-n6bv12jn.js} +528 -6
  63. package/dist/{cli-spknvxbq.js → cli-nmzhp1b5.js} +14 -12
  64. package/dist/{cli-p599f1qm.js → cli-p82cnnjq.js} +1 -1
  65. package/dist/{cli-xtjhfmpc.js → cli-pc6n7fre.js} +1 -1
  66. package/dist/{cli-vbyfwh92.js → cli-pjctyea3.js} +2 -2
  67. package/dist/{cli-nmbmy64z.js → cli-pq73h5mz.js} +3 -3
  68. package/dist/{cli-aa6yxc7m.js → cli-pz4rk021.js} +1 -1
  69. package/dist/{cli-7wcyk2s5.js → cli-q3tkegby.js} +1 -1
  70. package/dist/cli-qghe0h66.js +67 -0
  71. package/dist/{cli-6s7vdcta.js → cli-qh4d4tvk.js} +4 -4
  72. package/dist/{cli-mmwm7zbf.js → cli-rtt8g4v9.js} +1 -1
  73. package/dist/{cli-kg8hchyg.js → cli-sa02jsqt.js} +1 -1
  74. package/dist/{cli-1h3wcpbw.js → cli-snpj5zbj.js} +2 -2
  75. package/dist/{cli-a7v854rf.js → cli-tgrv9cjx.js} +263 -12
  76. package/dist/{cli-a47xy8eq.js → cli-vz22fyqv.js} +186 -54
  77. package/dist/{cli-1ayk1eas.js → cli-wk5yrfwd.js} +285 -42
  78. package/dist/{cli-1338796d.js → cli-ws19768n.js} +1755 -119
  79. package/dist/{cli-70r77n04.js → cli-xae4g51r.js} +1 -1
  80. package/dist/{cli-gf5hbmdr.js → cli-yjdgea32.js} +3 -3
  81. package/dist/{cli-xbkdm710.js → cli-yndmfxb3.js} +3 -3
  82. package/dist/{cli-rhw40jgb.js → cli-yyj3j5m4.js} +2 -2
  83. package/dist/cli.js +26 -25
  84. package/dist/{cloudflare-workers-ai-system-one-bm60fjm0.js → cloudflare-workers-ai-system-one-ef2a980q.js} +2 -2
  85. package/dist/{codex-adapter-q6k3gnyg.js → codex-adapter-cf2yxe9m.js} +3 -2
  86. package/dist/{codex-hook-9efm2x1m.js → codex-hook-a0c6g6t6.js} +1 -1
  87. package/dist/{codex-session-runner-3mwmk3d9.js → codex-session-runner-6vfzdgbd.js} +3 -2
  88. package/dist/{commands-evrenjn9.js → commands-t3mfy7vn.js} +9 -3
  89. package/dist/{controller-xjp8vswh.js → controller-q7xrbaz0.js} +8 -3
  90. package/dist/{controllers-connect-pp5jnxvc.js → controllers-connect-c6nm70kg.js} +20 -19
  91. package/dist/{cursor-adapter-yf47w4t0.js → cursor-adapter-ftd81ga8.js} +1 -1
  92. package/dist/{db-zxpw6aht.js → db-dk572x25.js} +3 -3
  93. package/dist/{db-rgfsh91f.js → db-vemtpmcc.js} +14 -4
  94. package/dist/{devtools-82fzqra8.js → devtools-qxfgbsqz.js} +1 -1
  95. package/dist/{dispatcher-smasgws7.js → dispatcher-kh7ecxh9.js} +17 -16
  96. package/dist/{docker-rrjhaqt9.js → docker-c1a3gqmf.js} +89 -22
  97. package/dist/{dsh-adapter-ftyy8dnp.js → dsh-adapter-v8628r89.js} +1 -1
  98. package/dist/{e2b-w1p0bnpm.js → e2b-fr12yccx.js} +5 -5
  99. package/dist/{e2b-qcaj675y.js → e2b-x6ywzak3.js} +3 -3
  100. package/dist/{easter-egg-3d-yyts7p16.js → easter-egg-3d-nenw4x63.js} +1 -1
  101. package/dist/{embedding-space-1z0mbg95.js → embedding-space-0jepb239.js} +6 -6
  102. package/dist/{embeddings-ygeg7tm7.js → embeddings-xxt49yxg.js} +1 -1
  103. package/dist/{execute-14qhqp4d.js → execute-1mhw6e4x.js} +1 -1
  104. package/dist/{external-channel-ttgrs3sx.js → external-channel-55v12e60.js} +2 -2
  105. package/dist/{fx-adapter-gt6pncj2.js → fx-adapter-bs8wqrk5.js} +6 -5
  106. package/dist/{google-generative-ai-yvmdxyxa.js → google-generative-ai-v94hr7h0.js} +5 -5
  107. package/dist/{google-vertex-jrtdh9gs.js → google-vertex-g7vr6cf8.js} +5 -5
  108. package/dist/{grok-adapter-sm4m5k4g.js → grok-adapter-gpjqmkcq.js} +6 -5
  109. package/dist/handlers-zxmp1ta4.js +32 -0
  110. package/dist/{heartbeat-qahz4p62.js → heartbeat-k42svnr0.js} +20 -19
  111. package/dist/{hook-4fd5wq5w.js → hook-114m7hap.js} +4 -3
  112. package/dist/{hook-kj3d398m.js → hook-6nphweh0.js} +24 -9
  113. package/dist/{http-1ted0j1k.js → http-r8cc4wz7.js} +1196 -211
  114. package/dist/index-a8phxqnk.js +37 -0
  115. package/dist/{index-q4b917zs.js → index-e3s8wmxy.js} +1 -1
  116. package/dist/{index-6rq3r5zb.js → index-fpk4bcd9.js} +1 -1
  117. package/dist/{index-5rk6rfzr.js → index-fvzke5kx.js} +1 -1
  118. package/dist/index-k6h00t4c.js +30 -0
  119. package/dist/{index-7v51awbn.js → index-nh3wyqdt.js} +1027 -577
  120. package/dist/{index-yqhz88yy.js → index-pxn30mse.js} +8 -8
  121. package/dist/index-xrz7te8h.js +33 -0
  122. package/dist/{keepalive-6cf311t5.js → keepalive-x060nt9p.js} +3 -3
  123. package/dist/lead-956sd2yc.js +71 -0
  124. package/dist/{maintenance-a4bm1bmt.js → maintenance-2xxfexhf.js} +1 -1
  125. package/dist/{mistral-conversations-4pmwj1c0.js → mistral-conversations-3gjpdb4b.js} +4 -4
  126. package/dist/{oauth-refresh-sweep-3h0dfw1k.js → oauth-refresh-sweep-fckc4ar5.js} +2 -2
  127. package/dist/{onboard-myn5hraa.js → onboard-zc48j237.js} +5 -5
  128. package/dist/{openai-codex-responses-44j573nm.js → openai-codex-responses-8pbxq9h5.js} +5 -5
  129. package/dist/{openai-completions-a4gee2a8.js → openai-completions-rm00s912.js} +4 -4
  130. package/dist/{openai-decisions-w4qkrcar.js → openai-decisions-8v957r2r.js} +1 -1
  131. package/dist/{openai-responses-rbp6h5kr.js → openai-responses-agxpf96k.js} +5 -5
  132. package/dist/{opencode-adapter-p2nkb5am.js → opencode-adapter-r690h1cr.js} +4 -3
  133. package/dist/{otel-impl-a1a0ef6b.js → otel-impl-r5xbh81q.js} +4 -4
  134. package/dist/{pi-mono-adapter-rzmpb72a.js → pi-mono-adapter-c7v8sdec.js} +7 -6
  135. package/dist/{pricing-refresh-83drrnxj.js → pricing-refresh-3pmre11v.js} +3 -3
  136. package/dist/{rbac-roles-s8y6ecb5.js → rbac-roles-3vbshwsb.js} +1 -1
  137. package/dist/{rbac-roles-0fb6e7pj.js → rbac-roles-910mmkgz.js} +2 -2
  138. package/dist/{render-v2-twmbgrz4.js → render-v2-pxykkbab.js} +3 -3
  139. package/dist/{resume-zfqc36xj.js → resume-adda0bwy.js} +6 -6
  140. package/dist/{runtime-qwggdpqr.js → runtime-fnbm7stk.js} +1 -1
  141. package/dist/{session-access-sc9pn9da.js → session-access-2fvamrdc.js} +4 -4
  142. package/dist/{session-source-backfill-qh07xr8g.js → session-source-backfill-08xvv0sm.js} +2 -2
  143. package/dist/{setup-4fr0m7a1.js → setup-1aqmvmh1.js} +3 -3
  144. package/dist/{typesafe-system-one-5mavm259.js → typesafe-system-one-0bmqcxfg.js} +2 -2
  145. package/dist/{virtual-modules-fh4ha15b.js → virtual-modules-d2pjk9j1.js} +5 -5
  146. package/dist/{work-objects-0wr8zxct.js → work-objects-9s0yrm5j.js} +20 -19
  147. package/dist/{worker-follow-up-ktyyazyp.js → worker-follow-up-3q78fqmr.js} +17 -16
  148. package/dist/worker-y15hy08h.js +71 -0
  149. package/openapi.json +289 -4
  150. package/package.json +9 -8
  151. package/src/apps/definition.ts +6 -5
  152. package/src/be/db/page-theme-tokens.ts +12 -0
  153. package/src/be/db/script-runs.ts +23 -0
  154. package/src/be/db/tasks/read.ts +131 -4
  155. package/src/be/db/tasks/write.ts +19 -0
  156. package/src/be/db-client.ts +114 -11
  157. package/src/be/db.ts +49 -6
  158. package/src/be/memory/providers/sqlite-store.ts +49 -12
  159. package/src/be/migrations/260_runtime_pool_cache.sql +20 -0
  160. package/src/be/migrations/262_session_visibility_indexes.sql +18 -0
  161. package/src/be/migrations/263_task_list_filter_index.sql +11 -0
  162. package/src/be/migrations/264_slack_thread_cover_index.sql +9 -0
  163. package/src/be/migrations/266_script_runs_source_task.sql +11 -0
  164. package/src/be/migrations/269_pages_design_system.sql +23 -0
  165. package/src/be/runtime-pools/cache-cleanup.ts +109 -0
  166. package/src/be/runtime-pools/db.ts +65 -6
  167. package/src/be/runtime-pools/remote-controller.ts +5 -0
  168. package/src/be/runtime-pools/supervisor.ts +33 -2
  169. package/src/be/scripts/typecheck.ts +22 -0
  170. package/src/be/seed/schedules-seeder.ts +0 -10
  171. package/src/be/seed-scripts/catalog/catalog-report.inline.ts +2 -0
  172. package/src/be/seed-scripts/catalog/catalog-report.ts +2 -0
  173. package/src/be/seed-scripts/catalog/memory-eval.ts +2 -0
  174. package/src/be/seed-scripts/catalog/swarm-update-check.ts +150 -0
  175. package/src/be/seed-scripts/catalog/task-failure-audit.ts +5 -1
  176. package/src/be/seed-scripts/index.ts +9 -0
  177. package/src/be/seed-skills/bundled-files.generated.json +3 -3
  178. package/src/be/swarm-config-guard.ts +32 -0
  179. package/src/be/task-auto-title-listener.ts +127 -0
  180. package/src/commands/controller.ts +4 -0
  181. package/src/commands/entrypoint-api-readiness.ts +74 -0
  182. package/src/commands/onboard/agent-fs-version.ts +1 -1
  183. package/src/commands/profile-sync.ts +151 -16
  184. package/src/commands/runner.ts +15 -7
  185. package/src/hooks/hook.ts +20 -9
  186. package/src/http/index.ts +4 -1
  187. package/src/http/list-total-cache.ts +14 -7
  188. package/src/http/memory.ts +38 -9
  189. package/src/http/pages-public.ts +93 -129
  190. package/src/http/pages.ts +94 -21
  191. package/src/http/script-runs.ts +174 -5
  192. package/src/http/status.ts +10 -2
  193. package/src/http/tasks.ts +4 -2
  194. package/src/pages/browser-sdk.ts +48 -21
  195. package/src/pages/color-literals.ts +115 -0
  196. package/src/pages/page-head.ts +766 -0
  197. package/src/pages/version.ts +3 -0
  198. package/src/providers/amp-adapter.ts +1 -1
  199. package/src/providers/codex-adapter.ts +1 -0
  200. package/src/providers/codex-app-server.ts +27 -1
  201. package/src/providers/fx-adapter.ts +1 -1
  202. package/src/providers/pi-mono-extension.ts +30 -15
  203. package/src/runtime-providers/controller-protocol.ts +14 -1
  204. package/src/runtime-providers/controller.ts +5 -0
  205. package/src/runtime-providers/docker.ts +108 -23
  206. package/src/runtime-providers/types.ts +16 -0
  207. package/src/script-workflows/workflow-ctx.ts +8 -0
  208. package/src/scripts-runtime/types/stdlib.d.ts +22 -0
  209. package/src/scripts-runtime/types/swarm-sdk.d.ts +22 -0
  210. package/src/server-user.ts +1 -0
  211. package/src/slack/commands.ts +5 -1
  212. package/src/slack/handlers.ts +37 -2
  213. package/src/slack/left-threads.ts +27 -4
  214. package/src/tasks/follow-up-gate.ts +318 -0
  215. package/src/tasks/follow-up-redelegation-guard.ts +155 -0
  216. package/src/tasks/worker-follow-up.ts +21 -1
  217. package/src/tests/claude-artifacts-read-script.test.ts +209 -0
  218. package/src/tests/codex-app-server.test.ts +59 -0
  219. package/src/tests/codex-gateway-route.test.ts +50 -1
  220. package/src/tests/create-page-tool.test.ts +501 -0
  221. package/src/tests/db-client-busy-retry.test.ts +81 -0
  222. package/src/tests/db-client.test.ts +21 -0
  223. package/src/tests/db-task-read-contract.test.ts +9 -2
  224. package/src/tests/db-task-write-contract.test.ts +7 -1
  225. package/src/tests/db-tasks-read.test.ts +21 -0
  226. package/src/tests/entrypoint-api-readiness.test.ts +416 -210
  227. package/src/tests/explicit-model-validation.test.ts +14 -8
  228. package/src/tests/fixtures/pages/head-v1.html +56 -0
  229. package/src/tests/follow-up-gate-shadow.test.ts +513 -0
  230. package/src/tests/follow-up-redelivery-guard.test.ts +432 -202
  231. package/src/tests/identity-field-budget.test.ts +1 -1
  232. package/src/tests/internal-ai/credentials.test.ts +62 -0
  233. package/src/tests/internal-ai/task-title.test.ts +283 -0
  234. package/src/tests/list-total-cache.test.ts +27 -2
  235. package/src/tests/memory-fts-populate.test.ts +11 -9
  236. package/src/tests/memory-retrieval-purge.test.ts +80 -0
  237. package/src/tests/merge-gate-detect-changes.test.ts +145 -0
  238. package/src/tests/migration-260-runtime-pool-cache.test.ts +118 -0
  239. package/src/tests/page-proxy.test.ts +1 -1
  240. package/src/tests/page-session-dev-cookie.test.ts +188 -0
  241. package/src/tests/pages-frame-ancestors.test.ts +28 -1
  242. package/src/tests/pages-head-v2.test.ts +534 -0
  243. package/src/tests/pages-http.test.ts +330 -0
  244. package/src/tests/pages-public-html.test.ts +43 -0
  245. package/src/tests/pages-storage.test.ts +182 -0
  246. package/src/tests/pages-versioning.test.ts +72 -0
  247. package/src/tests/profile-sync-stale-writeback.test.ts +334 -0
  248. package/src/tests/profile-sync.test.ts +11 -15
  249. package/src/tests/query-audit-plans.test.ts +27 -0
  250. package/src/tests/runtime-cache-cleanup.test.ts +367 -0
  251. package/src/tests/runtime-e2b-pools.test.ts +41 -0
  252. package/src/tests/runtime-pools-db.test.ts +98 -0
  253. package/src/tests/runtime-pools-http.test.ts +29 -0
  254. package/src/tests/runtime-providers-docker.test.ts +253 -0
  255. package/src/tests/runtime-remote-controller.test.ts +66 -2
  256. package/src/tests/runtime-supervisor.test.ts +66 -2
  257. package/src/tests/scheduled-task-required-params-migration.test.ts +3 -1
  258. package/src/tests/script-connections.test.ts +13 -0
  259. package/src/tests/script-runs-http.test.ts +684 -2
  260. package/src/tests/seed-registry.test.ts +28 -6
  261. package/src/tests/seed-schedules.test.ts +61 -8
  262. package/src/tests/seed-scripts.test.ts +168 -2
  263. package/src/tests/slack-leave-thread.test.ts +145 -5
  264. package/src/tests/slack-render-v2-query-plans.test.ts +24 -2
  265. package/src/tests/status.test.ts +11 -0
  266. package/src/tests/swarm-diff.test.ts +121 -2
  267. package/src/tests/task-auto-title-config-guard.test.ts +37 -0
  268. package/src/tests/task-auto-title-listener.test.ts +165 -0
  269. package/src/tests/tasks-readable-row.test.ts +155 -0
  270. package/src/tests/theme-import.test.ts +758 -0
  271. package/src/tests/themes-page-theme.test.ts +155 -0
  272. package/src/tests/themes-presets.test.ts +172 -0
  273. package/src/tests/workflow-script-principal.test.ts +2 -1
  274. package/src/themes/classics.ts +949 -0
  275. package/src/themes/import.ts +420 -0
  276. package/src/themes/page-fields.ts +40 -0
  277. package/src/themes/page-theme.ts +102 -0
  278. package/src/themes/presets.ts +561 -0
  279. package/src/themes/tokens.ts +218 -0
  280. package/src/tools/app-upsert.ts +1 -1
  281. package/src/tools/create-page.ts +118 -12
  282. package/src/tools/send-task.ts +35 -47
  283. package/src/tools/slack-leave-thread.ts +1 -1
  284. package/src/tools/task-dedup.ts +45 -21
  285. package/src/tools/utils.ts +33 -0
  286. package/src/types.ts +83 -3
  287. package/src/utils/constants.ts +19 -0
  288. package/src/utils/follow-up-gate-schedule-ids.ts +13 -0
  289. package/src/utils/internal-ai/credentials.ts +146 -93
  290. package/src/utils/internal-ai/index.ts +10 -0
  291. package/src/utils/internal-ai/models.ts +18 -0
  292. package/src/utils/internal-ai/task-title.ts +282 -0
  293. package/src/utils/page-session.ts +37 -0
  294. package/templates/schedules/daily-status-report/content.md +88 -2
  295. package/templates/schedules/daily-swarm-update-check/content.md +16 -40
  296. package/templates/skills/apps/SKILL.md +1 -1
  297. package/templates/skills/apps/content.md +1 -1
  298. package/templates/skills/composio-google-docs/content.md +10 -0
  299. package/templates/skills/heartbeat-runbook/content.md +1 -1
  300. package/templates/skills/pages/config.json +1 -1
  301. package/templates/skills/pages/content.md +539 -86
  302. package/templates/skills/pages/files/examples/annotated-pr.html +21 -12
  303. package/templates/skills/pages/files/examples/multiplayer-board.html +41 -41
  304. package/templates/skills/pages/files/examples/report-page.html +125 -124
  305. package/templates/skills/script-workflows/content.md +19 -1
  306. package/templates/skills/swarm-docs/content.md +1 -0
  307. package/templates/skills/swarm-scripts/SKILL.md +2 -0
  308. package/templates/skills/swarm-scripts/content.md +2 -0
  309. package/dist/cli-2pnswgh8.js +0 -189
  310. package/dist/cli-dznrgaep.js +0 -30
  311. package/dist/cli-w3hcrxht.js +0 -6985
  312. package/dist/handlers-vwmba1m2.js +0 -31
  313. package/dist/index-5r71rf98.js +0 -32
  314. package/dist/index-anmwxs87.js +0 -29
  315. package/dist/index-spp0ra5c.js +0 -36
  316. package/dist/lead-fjy418cw.js +0 -69
  317. package/dist/worker-0c5tyzqe.js +0 -69
  318. package/templates/schedules/daily-workflow-health-audit/config.json +0 -19
  319. package/templates/schedules/daily-workflow-health-audit/content.md +0 -191
@@ -28,6 +28,16 @@ const EXAMPLE_HUNK = {
28
28
  ],
29
29
  };
30
30
 
31
+ /** The rendered diff rows: old-side and new-side gutter numbers plus the row HTML. */
32
+ function diffRows(html: string): { old: string; new: string; html: string }[] {
33
+ return [...html.matchAll(/<tr\b[^>]*>([\s\S]*?)<\/tr>/g)].map(([, row]) => {
34
+ const [oldCell = "", newCell = ""] = [
35
+ ...row!.matchAll(/<td class="swarm-diff-gutter"[^>]*>([^<]*)<\/td>/g),
36
+ ].map(([, cell]) => cell!);
37
+ return { old: oldCell, new: newCell, html: row! };
38
+ });
39
+ }
40
+
31
41
  type StubInstance = {
32
42
  innerHTML: string;
33
43
  textContent: string;
@@ -55,12 +65,15 @@ function makeRig(
55
65
  ): {
56
66
  Ctor: new () => StubInstance;
57
67
  CursorsCtor: new () => StubInstance;
68
+ JumpsCtor: new () => StubInstance;
58
69
  SdkCtor?: new () => { room: (name?: string, options?: unknown) => Promise<unknown> };
59
70
  body: StubInstance;
60
71
  setText: (el: StubInstance, attrs: Record<string, string>, text: string) => void;
61
72
  fireDocumentEvent: (name: string, event: unknown) => void;
62
73
  fireNextFrame: () => void;
63
74
  flushMicrotasks: () => Promise<void>;
75
+ /** Sets what `document.querySelectorAll` returns (the jump list's anchors). */
76
+ setAnchors: (anchors: unknown[]) => void;
64
77
  } {
65
78
  const registry = new Map<string, new () => StubInstance>();
66
79
  const documentListeners = new Map<string, ((event: unknown) => void)[]>();
@@ -143,12 +156,13 @@ function makeRig(
143
156
 
144
157
  // Provide `document` stubs the jump-list path uses.
145
158
  const body = new StubHTMLElement();
159
+ let anchors: unknown[] = [];
146
160
  const doc = {
147
161
  readyState,
148
162
  documentElement: { scrollWidth: 1000, scrollHeight: 500, clientWidth: 1000, clientHeight: 500 },
149
163
  body,
150
164
  createElement: () => new StubHTMLElement(),
151
- querySelectorAll: () => [],
165
+ querySelectorAll: () => anchors,
152
166
  addEventListener: (name: string, handler: (event: unknown) => void) => {
153
167
  const handlers = documentListeners.get(name) ?? [];
154
168
  handlers.push(handler);
@@ -176,7 +190,7 @@ function makeRig(
176
190
  "CustomEvent",
177
191
  "queueMicrotask",
178
192
  "console",
179
- `${SWARM_UI_JS}\nreturn { diff: window.customElements.get('swarm-diff'), cursors: window.customElements.get('swarm-cursors'), sdk: window.SwarmSDK };`,
193
+ `${SWARM_UI_JS}\nreturn { diff: window.customElements.get('swarm-diff'), cursors: window.customElements.get('swarm-cursors'), jumps: window.customElements.get('swarm-diff-jumps'), sdk: window.SwarmSDK };`,
180
194
  );
181
195
  const constructors = factory(
182
196
  win,
@@ -189,16 +203,20 @@ function makeRig(
189
203
  ) as {
190
204
  diff?: new () => StubInstance;
191
205
  cursors?: new () => StubInstance;
206
+ jumps?: new () => StubInstance;
192
207
  sdk?: new () => { room: (name?: string, options?: unknown) => Promise<unknown> };
193
208
  };
194
209
  const Ctor = constructors.diff;
195
210
  if (!Ctor) throw new Error("custom element did not register");
196
211
  const CursorsCtor = constructors.cursors;
197
212
  if (!CursorsCtor) throw new Error("swarm-cursors custom element did not register");
213
+ const JumpsCtor = constructors.jumps;
214
+ if (!JumpsCtor) throw new Error("swarm-diff-jumps custom element did not register");
198
215
 
199
216
  return {
200
217
  Ctor,
201
218
  CursorsCtor,
219
+ JumpsCtor,
202
220
  SdkCtor: constructors.sdk,
203
221
  body,
204
222
  setText(el, attrs, text) {
@@ -220,6 +238,9 @@ function makeRig(
220
238
  // Drain the chained async room-open and render callbacks.
221
239
  for (let i = 0; i < 5; i++) await Promise.resolve();
222
240
  },
241
+ setAnchors(list) {
242
+ anchors = list;
243
+ },
223
244
  };
224
245
  }
225
246
 
@@ -517,6 +538,58 @@ describe("<swarm-diff> render", () => {
517
538
  expect(html).toContain("Avoid raw console.log");
518
539
  });
519
540
 
541
+ test("shows an annotation once, on its new-side line, not on a context row with that old-side number", async () => {
542
+ // Three added lines shift the new side: the context row `});` is old 63,
543
+ // new 66, and the annotation names new line 63.
544
+ const shifted = {
545
+ hunks: [
546
+ {
547
+ old_start: 60,
548
+ old_lines: 4,
549
+ new_start: 60,
550
+ new_lines: 7,
551
+ lines: [
552
+ { type: "context", text: "start();" },
553
+ { type: "add", text: "one();" },
554
+ { type: "add", text: "two();" },
555
+ { type: "add", text: "three();" },
556
+ { type: "context", text: "four();" },
557
+ { type: "context", text: "five();" },
558
+ { type: "context", text: "});" },
559
+ ],
560
+ annotations: [{ line: 63, severity: "warn", text: "Note on new line 63" }],
561
+ },
562
+ ],
563
+ };
564
+ const html = await renderViaStub(JSON.stringify(shifted), { file: "src/a.ts" });
565
+ const rows = diffRows(html);
566
+ expect(rows.find((row) => row.html.includes("});"))).toMatchObject({ old: "63", new: "66" });
567
+ const annotated = rows.filter((row) => row.html.includes("Note on new line 63"));
568
+ expect(annotated.map((row) => [row.old, row.new])).toEqual([["", "63"]]);
569
+ });
570
+
571
+ test("matches a deleted row's annotation on its old-side number", async () => {
572
+ const deleted = {
573
+ hunks: [
574
+ {
575
+ old_start: 10,
576
+ old_lines: 3,
577
+ new_start: 20,
578
+ new_lines: 2,
579
+ lines: [
580
+ { type: "context", text: "keep();" },
581
+ { type: "del", text: "drop();" },
582
+ { type: "context", text: "keepToo();" },
583
+ ],
584
+ annotations: [{ line: 11, severity: "error", text: "Why drop this?" }],
585
+ },
586
+ ],
587
+ };
588
+ const html = await renderViaStub(JSON.stringify(deleted), { file: "src/b.ts" });
589
+ const annotated = diffRows(html).filter((row) => row.html.includes("Why drop this?"));
590
+ expect(annotated.map((row) => [row.old, row.new])).toEqual([["11", ""]]);
591
+ });
592
+
520
593
  test("handles empty/missing JSON body gracefully (no rows, no throw)", async () => {
521
594
  const html = await renderViaStub("", { file: "empty.ts" });
522
595
  // Should still render an outer container with the file name.
@@ -545,6 +618,52 @@ describe("<swarm-diff> render", () => {
545
618
  });
546
619
  });
547
620
 
621
+ describe("<swarm-diff-jumps> labels", () => {
622
+ const hunk = (oldStart: number, newStart: number) => ({
623
+ old_start: oldStart,
624
+ old_lines: 1,
625
+ new_start: newStart,
626
+ new_lines: 1,
627
+ lines: [{ type: "context", text: "x();" }],
628
+ });
629
+
630
+ test("a file with more than one hunk shows each hunk's new-side start line", async () => {
631
+ const rig = makeRig();
632
+ const render = async (file: string, hunks: unknown[]) => {
633
+ const el = new rig.Ctor();
634
+ rig.setText(el, { file }, JSON.stringify({ hunks }));
635
+ el.connectedCallback?.();
636
+ await rig.flushMicrotasks();
637
+ return el;
638
+ };
639
+ // The anchors the jump list finds in the document, built from the rendered HTML.
640
+ const anchorsOf = (diff: StubInstance) =>
641
+ [
642
+ ...diff.innerHTML.matchAll(
643
+ /<a id="([^"]*)" class="swarm-diff-anchor" data-hunk="([^"]*)" data-new-start="([^"]*)"><\/a>/g,
644
+ ),
645
+ ].map(([, id, slug, newStart]) => {
646
+ const attrs: Record<string, string> = { "data-hunk": slug!, "data-new-start": newStart! };
647
+ return { id, getAttribute: (name: string) => attrs[name] ?? null, closest: () => diff };
648
+ });
649
+ const x = await render("src/x.ts", [hunk(10, 58), hunk(100, 120)]);
650
+ const y = await render("src/y.ts", [hunk(5, 5)]);
651
+ rig.setAnchors([...anchorsOf(x), ...anchorsOf(y)]);
652
+
653
+ const jumps = new rig.JumpsCtor();
654
+ jumps.connectedCallback?.();
655
+ await rig.flushMicrotasks();
656
+ const links = [
657
+ ...jumps.innerHTML.matchAll(/<a href="#([^"]*)" target="_self"[^>]*>([^<]*)<\/a>/g),
658
+ ].map(([, href, label]) => [href, label]);
659
+ expect(links).toEqual([
660
+ ["swarm-diff-src-x-ts-10", "src/x.ts:58"],
661
+ ["swarm-diff-src-x-ts-100", "src/x.ts:120"],
662
+ ["swarm-diff-src-y-ts-5", "src/y.ts"],
663
+ ]);
664
+ });
665
+ });
666
+
548
667
  describe("<swarm-diff> parse-order regression (Bug #479-1)", () => {
549
668
  // Real browsers fire connectedCallback when the parser sees the opening
550
669
  // tag, BEFORE the JSON text children are parsed. The element MUST defer
@@ -0,0 +1,37 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { validateConfigValue } from "../be/swarm-config-guard";
3
+
4
+ describe("task auto-title config validation", () => {
5
+ test("TASK_AUTO_TITLE_ENABLED accepts boolean literals only", () => {
6
+ expect(validateConfigValue("TASK_AUTO_TITLE_ENABLED", "true")).toBeNull();
7
+ expect(validateConfigValue("TASK_AUTO_TITLE_ENABLED", "false")).toBeNull();
8
+ expect(validateConfigValue("TASK_AUTO_TITLE_ENABLED", "maybe")).toMatch(
9
+ /TASK_AUTO_TITLE_ENABLED/,
10
+ );
11
+ });
12
+
13
+ test("TASK_AUTO_TITLE_MODEL accepts provider/model-id for a callable provider, a claude alias, or empty", () => {
14
+ for (const value of [
15
+ "openrouter/deepseek/deepseek-v4.1-flash",
16
+ "anthropic/claude-haiku-5-5",
17
+ "openai/gpt-5-nano",
18
+ "openai-codex/gpt-6-luna",
19
+ "haiku",
20
+ "",
21
+ " anthropic/claude-haiku-5-5 ",
22
+ ]) {
23
+ expect(validateConfigValue("TASK_AUTO_TITLE_MODEL", value)).toBeNull();
24
+ }
25
+ });
26
+
27
+ test("TASK_AUTO_TITLE_MODEL rejects unsupported providers and malformed strings", () => {
28
+ expect(validateConfigValue("TASK_AUTO_TITLE_MODEL", "google/gemini-2.5-flash-lite")).toMatch(
29
+ /provider must be/,
30
+ );
31
+ expect(validateConfigValue("TASK_AUTO_TITLE_MODEL", "openai/")).toMatch(/provider\/model-id/);
32
+ expect(validateConfigValue("TASK_AUTO_TITLE_MODEL", "openai/gpt 5")).toMatch(
33
+ /provider\/model-id/,
34
+ );
35
+ expect(validateConfigValue("TASK_AUTO_TITLE_MODEL", 42)).toMatch(/must be a string/);
36
+ });
37
+ });
@@ -0,0 +1,165 @@
1
+ import { afterAll, afterEach, beforeAll, describe, expect, test } from "bun:test";
2
+ import { unlink } from "node:fs/promises";
3
+ import {
4
+ closeDb,
5
+ createAgent,
6
+ createTaskExtended,
7
+ getTaskById,
8
+ initDb,
9
+ updateTaskTitle,
10
+ } from "../be/db";
11
+ import { setTaskTitleIfUnset } from "../be/db/tasks/write";
12
+ import {
13
+ isTaskAutoTitleEnabled,
14
+ startTaskAutoTitleListener,
15
+ type TaskAutoTitleListener,
16
+ } from "../be/task-auto-title-listener";
17
+ import type { generateTaskTitle } from "../utils/internal-ai/task-title";
18
+ import { workflowEventBus } from "../workflows/event-bus";
19
+
20
+ const TEST_DB_PATH = "./test-task-auto-title-listener.sqlite";
21
+
22
+ /**
23
+ * `createTaskExtended` emits `task.created` from an after-commit hook that
24
+ * imports the event bus asynchronously, so the event lands after the call
25
+ * returns. Create the task through this helper to await that emission.
26
+ */
27
+ async function createTaskAndAwaitEvent(task: string): Promise<string> {
28
+ const created = new Promise<void>((resolve) => {
29
+ const handler = () => {
30
+ workflowEventBus.off("task.created", handler);
31
+ resolve();
32
+ };
33
+ workflowEventBus.on("task.created", handler);
34
+ });
35
+ const row = await createTaskExtended(task, { agentId, source: "api" });
36
+ await created;
37
+ return row.id;
38
+ }
39
+ const LONG_TASK =
40
+ "Investigate why the nightly Playwright suite times out on the sessions page after the sidebar redesign and fix the flake.";
41
+
42
+ let agentId: string;
43
+ let listener: TaskAutoTitleListener | null = null;
44
+
45
+ type Generate = typeof generateTaskTitle;
46
+
47
+ /** Generator stub that returns `title` and records every prompt it saw. */
48
+ function generator(title: string | null, seen: string[] = []) {
49
+ const generate: Generate = async (opts) => {
50
+ seen.push(opts.task);
51
+ return title
52
+ ? { ok: true, title, model: "test/model", kind: "openrouter" }
53
+ : { ok: false, reason: "all-models-failed" };
54
+ };
55
+ return { generate, seen };
56
+ }
57
+
58
+ beforeAll(async () => {
59
+ initDb(TEST_DB_PATH);
60
+ agentId = (await createAgent({ name: "auto-title-worker", isLead: false, status: "idle" })).id;
61
+ });
62
+
63
+ afterEach(() => {
64
+ listener?.stop();
65
+ listener = null;
66
+ });
67
+
68
+ afterAll(async () => {
69
+ closeDb();
70
+ for (const suffix of ["", "-wal", "-shm"]) {
71
+ try {
72
+ await unlink(`${TEST_DB_PATH}${suffix}`);
73
+ } catch {}
74
+ }
75
+ });
76
+
77
+ describe("isTaskAutoTitleEnabled", () => {
78
+ test("defaults on and honours false-ish values", () => {
79
+ expect(isTaskAutoTitleEnabled({})).toBe(true);
80
+ expect(isTaskAutoTitleEnabled({ TASK_AUTO_TITLE_ENABLED: "" })).toBe(true);
81
+ expect(isTaskAutoTitleEnabled({ TASK_AUTO_TITLE_ENABLED: "false" })).toBe(false);
82
+ expect(isTaskAutoTitleEnabled({ TASK_AUTO_TITLE_ENABLED: "0" })).toBe(false);
83
+ expect(isTaskAutoTitleEnabled({ TASK_AUTO_TITLE_ENABLED: "true" })).toBe(true);
84
+ });
85
+ });
86
+
87
+ describe("setTaskTitleIfUnset", () => {
88
+ test("writes once and never replaces an existing title", async () => {
89
+ const task = await createTaskExtended(LONG_TASK, { agentId, source: "api" });
90
+ expect(task.title).toBeUndefined();
91
+
92
+ const first = await setTaskTitleIfUnset(task.id, " Generated title ");
93
+ expect(first?.title).toBe("Generated title");
94
+
95
+ const second = await setTaskTitleIfUnset(task.id, "Another title");
96
+ expect(second).toBeNull();
97
+ expect((await getTaskById(task.id))?.title).toBe("Generated title");
98
+
99
+ expect(await setTaskTitleIfUnset(task.id, " ")).toBeNull();
100
+ });
101
+ });
102
+
103
+ describe("startTaskAutoTitleListener", () => {
104
+ test("titles a new task after commit", async () => {
105
+ const { generate, seen } = generator("Fix sessions page Playwright flake");
106
+ listener = startTaskAutoTitleListener({ env: {}, generate });
107
+
108
+ const taskId = await createTaskAndAwaitEvent(LONG_TASK);
109
+ await listener.idle();
110
+
111
+ expect(seen).toEqual([LONG_TASK]);
112
+ expect((await getTaskById(taskId))?.title).toBe("Fix sessions page Playwright flake");
113
+ });
114
+
115
+ test("keeps a title a person set while the model was running", async () => {
116
+ let release: () => void = () => {};
117
+ const gate = new Promise<void>((resolve) => {
118
+ release = resolve;
119
+ });
120
+ const generate: Generate = async () => {
121
+ await gate;
122
+ return { ok: true, title: "Model title", model: "test/model", kind: "openrouter" };
123
+ };
124
+ listener = startTaskAutoTitleListener({ env: {}, generate });
125
+
126
+ const taskId = await createTaskAndAwaitEvent(LONG_TASK);
127
+ await updateTaskTitle(taskId, "Human title");
128
+ release();
129
+ await listener.idle();
130
+
131
+ expect((await getTaskById(taskId))?.title).toBe("Human title");
132
+ });
133
+
134
+ test("leaves the task untouched when generation fails", async () => {
135
+ const { generate, seen } = generator(null);
136
+ listener = startTaskAutoTitleListener({ env: {}, generate });
137
+
138
+ const taskId = await createTaskAndAwaitEvent(LONG_TASK);
139
+ await listener.idle();
140
+
141
+ expect(seen.length).toBe(1);
142
+ expect((await getTaskById(taskId))?.title).toBeUndefined();
143
+ });
144
+
145
+ test("does nothing when TASK_AUTO_TITLE_ENABLED is false", async () => {
146
+ const { generate, seen } = generator("Should not land");
147
+ listener = startTaskAutoTitleListener({ env: { TASK_AUTO_TITLE_ENABLED: "false" }, generate });
148
+
149
+ const taskId = await createTaskAndAwaitEvent(LONG_TASK);
150
+ await listener.idle();
151
+
152
+ expect(seen).toEqual([]);
153
+ expect((await getTaskById(taskId))?.title).toBeUndefined();
154
+ });
155
+
156
+ test("stop() detaches from the event bus", async () => {
157
+ const { generate, seen } = generator("Late title");
158
+ const handle = startTaskAutoTitleListener({ env: {}, generate });
159
+ handle.stop();
160
+
161
+ await createTaskAndAwaitEvent(LONG_TASK);
162
+ await handle.idle();
163
+ expect(seen).toEqual([]);
164
+ });
165
+ });
@@ -0,0 +1,155 @@
1
+ import { afterAll, beforeAll, describe, expect, spyOn, test } from "bun:test";
2
+ import { unlink } from "node:fs/promises";
3
+ import { closeDb, getAllTasks, getDb, getDbClient, initDb } from "../be/db";
4
+ import { readableSessionCondition } from "../be/db/tasks/read";
5
+
6
+ const TEST_DB_PATH = "./test-tasks-readable-row.sqlite";
7
+ const AT = "2026-10-01T00:00:00.000Z";
8
+
9
+ async function removeDbFiles(): Promise<void> {
10
+ for (const suffix of ["", "-wal", "-shm"])
11
+ await unlink(`${TEST_DB_PATH}${suffix}`).catch(() => undefined);
12
+ }
13
+
14
+ async function task(
15
+ id: string,
16
+ opts: { parent?: string; owner?: string; scheduleId?: string } = {},
17
+ ): Promise<void> {
18
+ await getDbClient().run(
19
+ `INSERT INTO agent_tasks (id, task, status, source, parentTaskId, requestedByUserId, scheduleId,
20
+ createdAt, lastUpdatedAt)
21
+ VALUES (?, ?, 'completed', 'api', ?, ?, ?, ?, ?)`,
22
+ [id, `task ${id}`, opts.parent ?? null, opts.owner ?? null, opts.scheduleId ?? null, AT, AT],
23
+ );
24
+ }
25
+
26
+ describe("readableSessionRowCondition", () => {
27
+ beforeAll(async () => {
28
+ await removeDbFiles();
29
+ initDb(TEST_DB_PATH);
30
+ for (const id of ["u1", "u2", "u3"]) {
31
+ await getDbClient().run(
32
+ "INSERT INTO users (id, name, role, status, createdAt, lastUpdatedAt) VALUES (?, ?, 'member', 'active', ?, ?)",
33
+ [id, id, AT, AT],
34
+ );
35
+ }
36
+ // u1's session, three deep.
37
+ await task("a", { owner: "u1" });
38
+ await task("a1", { parent: "a", owner: "u1" });
39
+ await task("a2", { parent: "a1" });
40
+ // u2's session, with a child that names u1.
41
+ await task("b", { owner: "u2" });
42
+ await task("b1", { parent: "b", owner: "u1" });
43
+ // u2's schedule run: everyone reads it.
44
+ await task("c", { owner: "u2", scheduleId: "schedule-1" });
45
+ await task("c1", { parent: "c" });
46
+ // u2's session shared with u1, and one shared with everyone.
47
+ await task("d", { owner: "u2" });
48
+ await task("d1", { parent: "d" });
49
+ await task("e", { owner: "u2" });
50
+ await getDbClient().run(
51
+ `INSERT INTO session_shares (id, rootTaskId, userId, access, createdAt)
52
+ VALUES ('share-d', 'd', 'u1', 'read', ?), ('share-e', 'e', NULL, 'read', ?)`,
53
+ [AT, AT],
54
+ );
55
+ // A revoked share hides again.
56
+ await task("f", { owner: "u2" });
57
+ await getDbClient().run(
58
+ `INSERT INTO session_shares (id, rootTaskId, userId, access, createdAt, revokedAt)
59
+ VALUES ('share-f', 'f', 'u1', 'read', ?, ?)`,
60
+ [AT, AT],
61
+ );
62
+ // A session nobody owns.
63
+ await task("g");
64
+ await task("g1", { parent: "g", owner: "u2" });
65
+ // Orphan subtrees: the top's parent is gone, so the top is the root.
66
+ await task("h", { owner: "u2" });
67
+ await task("h1", { parent: "h" });
68
+ await task("h2", { parent: "h1", owner: "u1" });
69
+ await task("i", { owner: "u1" });
70
+ await task("i1", { parent: "i", owner: "u2" });
71
+ await task("i2", { parent: "i1" });
72
+ // Chains deeper than the walk's hop limit fall back to the set form.
73
+ for (const [root, owner] of [
74
+ ["deep-u1", "u1"],
75
+ ["deep-u2", "u2"],
76
+ ] as const) {
77
+ await task(root, { owner });
78
+ let parent: string = root;
79
+ for (let i = 1; i <= 70; i++) {
80
+ await task(`${root}-${i}`, { parent });
81
+ parent = `${root}-${i}`;
82
+ }
83
+ }
84
+ getDb().run("PRAGMA foreign_keys = OFF");
85
+ getDb().run("DELETE FROM agent_tasks WHERE id IN ('h', 'i')");
86
+ getDb().run("PRAGMA foreign_keys = ON");
87
+ });
88
+
89
+ afterAll(async () => {
90
+ closeDb();
91
+ await removeDbFiles();
92
+ });
93
+
94
+ for (const userId of ["u1", "u2", "u3", null]) {
95
+ test(`the page shows the rows the set form shows, for ${userId ?? "a guest"}`, async () => {
96
+ const set = readableSessionCondition(userId);
97
+ const expected = (
98
+ await getDbClient().query<{ id: string }>(
99
+ `SELECT id FROM agent_tasks WHERE ${set.sql} ORDER BY id`,
100
+ set.params,
101
+ )
102
+ ).map((row) => row.id);
103
+ const page = await getAllTasks({ readableBy: { userId }, limit: 1000 });
104
+ expect(page.map((t) => t.id).sort()).toEqual(expected);
105
+ });
106
+ }
107
+
108
+ test("u1 sees exactly the expected sessions", async () => {
109
+ const page = await getAllTasks({ readableBy: { userId: "u1" }, limit: 1000 });
110
+ const deep = Array.from({ length: 70 }, (_, i) => `deep-u1-${i + 1}`);
111
+ expect(page.map((t) => t.id).sort()).toEqual(
112
+ [
113
+ "a",
114
+ "a1",
115
+ "a2",
116
+ "c",
117
+ "c1",
118
+ "d",
119
+ "d1",
120
+ "e",
121
+ "g",
122
+ "g1",
123
+ "h1",
124
+ "h2",
125
+ "deep-u1",
126
+ ...deep,
127
+ ].sort(),
128
+ );
129
+ });
130
+
131
+ test("the page walks up from each row it reads", async () => {
132
+ const query = spyOn(getDbClient(), "query");
133
+ let sql = "";
134
+ let params: unknown[] = [];
135
+ try {
136
+ await getAllTasks({ status: "completed", readableBy: { userId: "u1" } });
137
+ [sql, params = []] = query.mock.calls.find(([s]) => s.includes("WITH page AS"))! as [
138
+ string,
139
+ unknown[]?,
140
+ ];
141
+ } finally {
142
+ query.mockRestore();
143
+ }
144
+ const plan = getDb()
145
+ .query<{ detail: string }, any[]>(`EXPLAIN QUERY PLAN ${sql}`)
146
+ .all(...(params as any[]))
147
+ .map((row) => row.detail)
148
+ .join("\n");
149
+ // The walk is a correlated lookup per row. The set form appears only as
150
+ // the fallback branch, which SQLite builds when a walk finds no root.
151
+ expect(plan).toContain("CORRELATED SCALAR SUBQUERY");
152
+ expect(plan).toContain("SEARCH u USING INDEX sqlite_autoindex_agent_tasks_1 (id=?)");
153
+ expect(plan).toContain("SEARCH p USING INDEX sqlite_autoindex_agent_tasks_1 (id=?)");
154
+ });
155
+ });