@desplega.ai/agent-swarm 2.7.4 → 2.7.6

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 (326) hide show
  1. package/README.md +1 -1
  2. package/dist/acp-adapter-bp91d37f.js +15 -0
  3. package/dist/{actions-gfrr9vb7.js → actions-yj0eeaqh.js} +19 -18
  4. package/dist/{amp-adapter-0s58gm46.js → amp-adapter-4pg9h34q.js} +3 -3
  5. package/dist/{app-stvq692g.js → app-909e97cr.js} +3 -3
  6. package/dist/{assistant-5s37fr9n.js → assistant-qdyb5fyy.js} +32 -23
  7. package/dist/{boot-reembed-yzy1841n.js → boot-reembed-fk0s9z18.js} +1 -1
  8. package/dist/{boot-reembed-cv82gz7y.js → boot-reembed-gs1rzrnh.js} +1 -1
  9. package/dist/{boot-scrub-sweep-8k4q95ap.js → boot-scrub-sweep-4qfd4kfx.js} +4 -4
  10. package/dist/{boot-seeds-ftt9htah.js → boot-seeds-yr00k13d.js} +4 -4
  11. package/dist/{boxd-515v1t8z.js → boxd-vdmfssd9.js} +5 -5
  12. package/dist/{claude-adapter-t00wrzeh.js → claude-adapter-qta1f1xn.js} +7 -7
  13. package/dist/{claude-managed-adapter-g6vmjccb.js → claude-managed-adapter-dvs2xw5n.js} +2 -2
  14. package/dist/{claude-managed-setup-afpa82f7.js → claude-managed-setup-sc6j4dm0.js} +3 -3
  15. package/dist/{claude-sdk-session-vj0c2azx.js → claude-sdk-session-zgrz6yvc.js} +9175 -9076
  16. package/dist/{cli-gjr2y2xk.js → cli-0agcv1v5.js} +4 -1
  17. package/dist/{cli-edmm9d9y.js → cli-0at8w34w.js} +7 -7
  18. package/dist/{cli-a81qevyg.js → cli-0qatpy8q.js} +20 -36
  19. package/dist/{cli-bcz074zx.js → cli-0yv5z2pg.js} +1 -1
  20. package/dist/{cli-a4btqqft.js → cli-1b7btd38.js} +1 -1
  21. package/dist/{cli-a2jw6ee5.js → cli-1p89xny0.js} +1 -1
  22. package/dist/{cli-v726afdm.js → cli-2pnswgh8.js} +1 -1
  23. package/dist/{cli-jkt52mgt.js → cli-386tyxxg.js} +2 -2
  24. package/dist/{cli-az0bpf8p.js → cli-3ypk9vk1.js} +3 -3
  25. package/dist/{cli-wk9md5px.js → cli-436532ck.js} +4 -4
  26. package/dist/{cli-7n5sbw0z.js → cli-4necwtsv.js} +5 -5
  27. package/dist/cli-54t00gt8.js +151 -0
  28. package/dist/{cli-655xm9ms.js → cli-583frbyr.js} +1 -1
  29. package/dist/{cli-4r12t6w6.js → cli-5c4hjzne.js} +101 -4
  30. package/dist/{cli-hf6zt5m6.js → cli-69szn7w7.js} +17 -1
  31. package/dist/{cli-rtk9g7av.js → cli-73jzwksr.js} +6 -2
  32. package/dist/{cli-jg0nfda6.js → cli-75gkpjvw.js} +52 -21
  33. package/dist/{cli-wcmnan2g.js → cli-89knwags.js} +5 -5
  34. package/dist/{cli-pwyevs29.js → cli-8bc1w0tw.js} +22 -21
  35. package/dist/{cli-t282jgab.js → cli-8snvgs5r.js} +41 -16
  36. package/dist/{cli-vqgnyh76.js → cli-98rw5dt5.js} +15 -3
  37. package/dist/{cli-9cctx1tn.js → cli-a22h291q.js} +1 -1
  38. package/dist/{cli-ycm6180s.js → cli-a7v854rf.js} +5 -3
  39. package/dist/{cli-98pke4vk.js → cli-ae7mrdf9.js} +2 -2
  40. package/dist/{cli-je8yeyc5.js → cli-bjb3z51z.js} +5 -5
  41. package/dist/{cli-mc97xy2s.js → cli-c60vetmm.js} +16 -4
  42. package/dist/{cli-j4kw3rsx.js → cli-cm5yqn35.js} +18 -0
  43. package/dist/{cli-wndv9p7g.js → cli-decfb6v8.js} +6 -6
  44. package/dist/{cli-h55y2bjc.js → cli-dhar9qay.js} +2 -2
  45. package/dist/{cli-8yy525ad.js → cli-drr5v6av.js} +2 -2
  46. package/dist/{cli-5gr1g711.js → cli-dxae2yg7.js} +2 -2
  47. package/dist/{cli-pe09p6dv.js → cli-e3r2neck.js} +8 -7
  48. package/dist/{cli-13jvmew3.js → cli-ekwks2vs.js} +7 -7
  49. package/dist/{cli-jg4p5t36.js → cli-es6va8jk.js} +121 -51
  50. package/dist/{cli-j4kt1mpb.js → cli-faxe6thg.js} +3 -3
  51. package/dist/{cli-tzrpv5sc.js → cli-ftyxyspd.js} +1 -1
  52. package/dist/cli-fw2w0kva.js +47 -0
  53. package/dist/{cli-2dfbv8qr.js → cli-fy6x31ps.js} +1 -0
  54. package/dist/{cli-aq58fk5m.js → cli-g83s9ewh.js} +7 -1
  55. package/dist/cli-gdhpcbhf.js +55 -0
  56. package/dist/{cli-82jg7fp9.js → cli-he5nxaa8.js} +1 -1
  57. package/dist/{cli-8veytvbh.js → cli-he9wchme.js} +1 -1
  58. package/dist/{cli-k76tgq03.js → cli-hgaazk69.js} +2 -2
  59. package/dist/{cli-d2pzqej1.js → cli-hr2e2fft.js} +741 -7
  60. package/dist/{cli-dae4sbgy.js → cli-jm2vfpfd.js} +2 -2
  61. package/dist/{cli-1s1dqn1j.js → cli-k2gzwsj2.js} +297 -140
  62. package/dist/{cli-b1afgtj3.js → cli-kqdg1bsb.js} +3 -3
  63. package/dist/cli-m2z8jagn.js +12078 -0
  64. package/dist/{cli-mvckssyq.js → cli-m48g6p95.js} +5 -5
  65. package/dist/{cli-bwty35gx.js → cli-mthwvjre.js} +1 -1
  66. package/dist/{cli-41z8xwj0.js → cli-nhrvpaj7.js} +34 -8
  67. package/dist/{cli-ahd90bwt.js → cli-nwj0ee2w.js} +6 -6
  68. package/dist/{cli-kvtc7aez.js → cli-p5gp8pcx.js} +44 -14
  69. package/dist/{cli-avgcz4ra.js → cli-pd7vagab.js} +436 -171
  70. package/dist/{cli-zn15c46z.js → cli-q0k1w5zd.js} +1 -1
  71. package/dist/{cli-bq2pcphn.js → cli-rmmmrjkd.js} +1 -1
  72. package/dist/{cli-2qpvmmkw.js → cli-s0qnd4wv.js} +1 -1
  73. package/dist/{cli-kg8hchyg.js → cli-sa02jsqt.js} +1 -1
  74. package/dist/{cli-1yekbyqm.js → cli-shpbx5ee.js} +283 -82
  75. package/dist/{cli-9y354g3j.js → cli-srye3vdt.js} +1 -1
  76. package/dist/{cli-byqfbnjw.js → cli-svj5090z.js} +9 -9
  77. package/dist/{cli-gcbdjt47.js → cli-vfym7wmz.js} +6 -6
  78. package/dist/{cli-v1yw3wha.js → cli-vvpx3gsx.js} +4 -4
  79. package/dist/{cli-a1fhj4n9.js → cli-w6dt9ged.js} +2 -2
  80. package/dist/{cli-1gm8jcyr.js → cli-waqbxg08.js} +5232 -68
  81. package/dist/{cli-5hjced2v.js → cli-wp9sp1qb.js} +6 -6
  82. package/dist/{cli-eny54typ.js → cli-wx3rhnm0.js} +1 -1
  83. package/dist/{cli-s0exq74r.js → cli-xbbndvjy.js} +3 -3
  84. package/dist/{cli-f255yaqn.js → cli-xds5f77d.js} +3 -3
  85. package/dist/{cli-0sbkss7h.js → cli-xnqgpshy.js} +2 -2
  86. package/dist/{cli-0fgr1w9b.js → cli-xnrwazav.js} +20 -3
  87. package/dist/{cli-gf5hbmdr.js → cli-yjdgea32.js} +3 -3
  88. package/dist/{cli-cq1pf336.js → cli-yn4kxs06.js} +124 -11
  89. package/dist/{cli-224epwwf.js → cli-yx4kfngt.js} +3 -3
  90. package/dist/{cli-eqx6k993.js → cli-yy3rhsdw.js} +47 -13
  91. package/dist/{cli-36g23z21.js → cli-zbbwez0r.js} +4 -4
  92. package/dist/{cli-ktmvb20s.js → cli-zm72anhg.js} +12 -12
  93. package/dist/cli.js +26 -25
  94. package/dist/{codex-adapter-g0gctw29.js → codex-adapter-ayd1fb6h.js} +5 -5
  95. package/dist/{codex-hook-vrzz2xhr.js → codex-hook-9efm2x1m.js} +1 -1
  96. package/dist/{codex-login-0gg46ygn.js → codex-login-y0pmff1c.js} +1 -1
  97. package/dist/{codex-session-runner-dr2cxe1c.js → codex-session-runner-jesa32xc.js} +6 -6
  98. package/dist/{commands-1p07cvff.js → commands-hanewgf2.js} +9 -3
  99. package/dist/{controller-z0s7sv6r.js → controller-dt1n3c1q.js} +9 -4
  100. package/dist/{controllers-connect-x9kvvhyk.js → controllers-connect-xx8x64n5.js} +22 -21
  101. package/dist/{cursor-adapter-m5b98pvk.js → cursor-adapter-9s4dwwdw.js} +2 -2
  102. package/dist/{db-capk2raj.js → db-3vttcbv4.js} +3 -3
  103. package/dist/{db-rsh04kh3.js → db-9dv0g1ab.js} +12 -4
  104. package/dist/{devin-adapter-yxm87fe9.js → devin-adapter-r9phbnc9.js} +1 -1
  105. package/dist/{devtools-82fzqra8.js → devtools-qxfgbsqz.js} +1 -1
  106. package/dist/{dispatcher-304q6fwa.js → dispatcher-tx9hd1sv.js} +18 -17
  107. package/dist/{docker-7rxt0rry.js → docker-c1a3gqmf.js} +91 -24
  108. package/dist/{dsh-adapter-btewem0y.js → dsh-adapter-007y4ejs.js} +2 -2
  109. package/dist/{e2b-renc0k8q.js → e2b-a19d28rz.js} +4 -4
  110. package/dist/{e2b-7fc9b9bd.js → e2b-we6hs6q5.js} +6 -6
  111. package/dist/{easter-egg-3d-fm1p3n4d.js → easter-egg-3d-rgbrqb8s.js} +1 -1
  112. package/dist/{embedding-space-s68v49ct.js → embedding-space-ny1e2940.js} +6 -6
  113. package/dist/{embeddings-4ms3604w.js → embeddings-2t386t13.js} +1 -1
  114. package/dist/{event-bus-dfjx2qdh.js → event-bus-g0ka3pbw.js} +1 -1
  115. package/dist/{execute-mcdgccjs.js → execute-pksh0ceh.js} +1 -1
  116. package/dist/{external-channel-9dxwfztn.js → external-channel-phgmhmd7.js} +2 -2
  117. package/dist/fx-adapter-fy8rr921.js +18 -0
  118. package/dist/{google-generative-ai-yvmdxyxa.js → google-generative-ai-8y3m3k4b.js} +1 -1
  119. package/dist/{google-vertex-jrtdh9gs.js → google-vertex-w3v98n7b.js} +1 -1
  120. package/dist/{grok-adapter-05ecv4h0.js → grok-adapter-q0abvpaj.js} +8 -8
  121. package/dist/handlers-by3sfxyx.js +32 -0
  122. package/dist/{heartbeat-3n94heh3.js → heartbeat-kadyek8p.js} +21 -20
  123. package/dist/{hook-aadrn69x.js → hook-5zs3msf6.js} +6 -6
  124. package/dist/{hook-zxwje86c.js → hook-9k551xnj.js} +4 -4
  125. package/dist/{http-xzxk9mst.js → http-mhz9vw1f.js} +272 -103
  126. package/dist/index-4f2pvxae.js +37 -0
  127. package/dist/index-98gz7tmp.js +30 -0
  128. package/dist/{index-5rk6rfzr.js → index-fvzke5kx.js} +1 -1
  129. package/dist/index-k08z2cak.js +33 -0
  130. package/dist/{index-yqhz88yy.js → index-pxn30mse.js} +8 -8
  131. package/dist/{index-d34f6r87.js → index-q361cg2f.js} +1 -1
  132. package/dist/{index-9j4zwcht.js → index-rvr00sg7.js} +168 -675
  133. package/dist/{index-qeb82z37.js → index-vdygbq10.js} +1 -1
  134. package/dist/{keepalive-w4aakbyk.js → keepalive-w56f32cg.js} +4 -4
  135. package/dist/lead-enq35nqp.js +70 -0
  136. package/dist/{maintenance-hbptpk6p.js → maintenance-7mdgxcea.js} +1 -1
  137. package/dist/{oauth-refresh-sweep-217semrv.js → oauth-refresh-sweep-29txy9vk.js} +3 -3
  138. package/dist/{onboard-vz2s5df3.js → onboard-kpsrkarj.js} +5 -5
  139. package/dist/{opencode-adapter-gtfd7d7v.js → opencode-adapter-j25vm3rr.js} +5 -5
  140. package/dist/{otel-impl-aefave37.js → otel-impl-6kkct7a8.js} +2 -2
  141. package/dist/{pi-mono-adapter-f8h5yseh.js → pi-mono-adapter-exhkzhn2.js} +117 -23
  142. package/dist/{pricing-refresh-m1yhh9ya.js → pricing-refresh-n2x7jh6k.js} +3 -3
  143. package/dist/{rbac-roles-rrw8twdz.js → rbac-roles-81ypx6es.js} +1 -1
  144. package/dist/{rbac-roles-svj72cdf.js → rbac-roles-b5htkjby.js} +2 -2
  145. package/dist/{render-v2-amwbnfxh.js → render-v2-qn6jmpk4.js} +3 -3
  146. package/dist/{resume-9cd6wf6z.js → resume-wd30wx23.js} +7 -7
  147. package/dist/{runtime-fp4vrx7m.js → runtime-4hsg7h60.js} +1 -1
  148. package/dist/{session-access-21t6yjyg.js → session-access-5qvfevhk.js} +4 -4
  149. package/dist/{session-source-backfill-pxbzcsc6.js → session-source-backfill-n1czavbr.js} +3 -3
  150. package/dist/{setup-4592tqz4.js → setup-dnb56qy5.js} +3 -3
  151. package/dist/{virtual-modules-gzfnpmnz.js → virtual-modules-76xxv9fs.js} +2 -2
  152. package/dist/{work-objects-t9tcf174.js → work-objects-4n21tfkm.js} +21 -20
  153. package/dist/{worker-follow-up-q32jh4yk.js → worker-follow-up-77hvzxww.js} +18 -17
  154. package/dist/worker-gg1576j3.js +70 -0
  155. package/dist/{x-b6wxntdt.js → x-vmmye1kb.js} +2 -2
  156. package/openapi.json +189 -32
  157. package/package.json +8 -7
  158. package/src/be/db/script-runs.ts +23 -0
  159. package/src/be/db/session-summaries.ts +8 -2
  160. package/src/be/db/session-tree.ts +174 -61
  161. package/src/be/db/session-visibility.ts +5 -4
  162. package/src/be/db/tasks/read.ts +122 -3
  163. package/src/be/db-client.ts +8 -5
  164. package/src/be/db.ts +23 -8
  165. package/src/be/memory/providers/sqlite-store.ts +49 -12
  166. package/src/be/migrations/259_session_summaries_open_tasks.sql +111 -0
  167. package/src/be/migrations/260_runtime_pool_cache.sql +20 -0
  168. package/src/be/migrations/262_session_visibility_indexes.sql +18 -0
  169. package/src/be/migrations/263_task_list_filter_index.sql +11 -0
  170. package/src/be/migrations/264_slack_thread_cover_index.sql +9 -0
  171. package/src/be/migrations/266_script_runs_source_task.sql +11 -0
  172. package/src/be/model-validation.ts +5 -1
  173. package/src/be/pricing-normalize.ts +7 -2
  174. package/src/be/runtime-pools/cache-cleanup.ts +109 -0
  175. package/src/be/runtime-pools/db.ts +65 -6
  176. package/src/be/runtime-pools/remote-controller.ts +5 -0
  177. package/src/be/runtime-pools/supervisor.ts +33 -2
  178. package/src/be/scripts/typecheck.ts +23 -0
  179. package/src/be/seed/schedules-seeder.ts +135 -9
  180. package/src/be/seed/workflows-seeder.ts +65 -1
  181. package/src/be/seed-pricing.ts +23 -0
  182. package/src/be/seed-scripts/catalog/task-failure-audit.ts +5 -1
  183. package/src/be/steering.ts +50 -3
  184. package/src/be/swarm-config-guard.ts +1 -0
  185. package/src/commands/controller.ts +4 -0
  186. package/src/commands/entrypoint-api-readiness.ts +74 -0
  187. package/src/commands/provider-credentials.ts +29 -2
  188. package/src/commands/runner.ts +86 -5
  189. package/src/extensions/contract-types.generated.ts +1 -1
  190. package/src/extensions/contract.ts +27 -1
  191. package/src/extensions/dispatcher.ts +4 -0
  192. package/src/http/agents.ts +1 -0
  193. package/src/http/index.ts +1 -1
  194. package/src/http/list-total-cache.ts +14 -7
  195. package/src/http/mcp-bridge.ts +4 -0
  196. package/src/http/memory.ts +38 -9
  197. package/src/http/script-runs.ts +174 -5
  198. package/src/http/status.ts +10 -2
  199. package/src/http/tasks.ts +4 -2
  200. package/src/integrations/chat-sdk/inbound.ts +15 -5
  201. package/src/integrations/chat-sdk/steering.ts +3 -1
  202. package/src/kv-reserved-namespaces.ts +2 -0
  203. package/src/prompts/base-prompt.ts +4 -1
  204. package/src/prompts/session-templates.ts +26 -0
  205. package/src/providers/acp-adapter.ts +190 -26
  206. package/src/providers/acp-targets.ts +306 -6
  207. package/src/providers/amp-adapter.ts +1 -1
  208. package/src/providers/codex-adapter.ts +9 -44
  209. package/src/providers/codex-oauth/flow.ts +24 -3
  210. package/src/providers/codex-oauth/storage.ts +56 -17
  211. package/src/providers/codex-oauth/subscription.ts +67 -0
  212. package/src/providers/fx-adapter.ts +211 -0
  213. package/src/providers/index.ts +5 -1
  214. package/src/providers/mcp-loopback-proxy.ts +94 -0
  215. package/src/providers/pi-mono-adapter.ts +98 -13
  216. package/src/providers/pricing-sources.md +8 -0
  217. package/src/providers/reasoning-effort.ts +17 -1
  218. package/src/providers/types.ts +2 -1
  219. package/src/runtime-providers/controller-protocol.ts +14 -1
  220. package/src/runtime-providers/controller.ts +5 -0
  221. package/src/runtime-providers/docker.ts +108 -23
  222. package/src/runtime-providers/types.ts +16 -0
  223. package/src/scheduler/scheduler.ts +36 -3
  224. package/src/script-workflows/workflow-ctx.ts +8 -0
  225. package/src/scripts-runtime/sdk-allowlist.ts +1 -0
  226. package/src/scripts-runtime/types/stdlib.d.ts +27 -0
  227. package/src/scripts-runtime/types/swarm-extension.d.ts +33 -0
  228. package/src/scripts-runtime/types/swarm-sdk.d.ts +27 -0
  229. package/src/server-user.ts +1 -0
  230. package/src/server.ts +7 -0
  231. package/src/slack/assistant.ts +17 -1
  232. package/src/slack/commands.ts +5 -1
  233. package/src/slack/handlers.ts +53 -5
  234. package/src/slack/left-threads.ts +88 -0
  235. package/src/slack/steering.ts +3 -0
  236. package/src/slack/thread-buffer.ts +40 -16
  237. package/src/tasks/follow-up-redelegation-guard.ts +155 -0
  238. package/src/tests/acp-adapter.test.ts +79 -0
  239. package/src/tests/base-prompt.test.ts +12 -0
  240. package/src/tests/chat-sdk-inbound.test.ts +30 -0
  241. package/src/tests/chatgpt-subscription-model-groups.test.ts +62 -0
  242. package/src/tests/chatgpt-subscription-pi.test.ts +170 -0
  243. package/src/tests/chatgpt-subscription-session-bench.test.ts +198 -0
  244. package/src/tests/claude-artifacts-read-script.test.ts +209 -0
  245. package/src/tests/codex-oauth-credential-info.test.ts +40 -0
  246. package/src/tests/codex-oauth-refresh-egress.test.ts +194 -0
  247. package/src/tests/context-window.test.ts +7 -0
  248. package/src/tests/db-client.test.ts +21 -0
  249. package/src/tests/db-task-read-contract.test.ts +8 -2
  250. package/src/tests/db-task-write-contract.test.ts +5 -1
  251. package/src/tests/entrypoint-api-readiness.test.ts +416 -210
  252. package/src/tests/explicit-model-validation.test.ts +113 -8
  253. package/src/tests/extensions-run-context.test.ts +11 -0
  254. package/src/tests/extensions-steering.test.ts +353 -0
  255. package/src/tests/extensions-validate.test.ts +5 -0
  256. package/src/tests/fixtures/extensions/steering-policy/hooks.ts +39 -0
  257. package/src/tests/fixtures/extensions/steering-policy/manifest.json +7 -0
  258. package/src/tests/fixtures/fake-fx.ts +167 -0
  259. package/src/tests/fixtures/fx/acp-responses-recorded.json +157 -0
  260. package/src/tests/fixtures/fx/acp-updates-recorded.jsonl +15 -0
  261. package/src/tests/follow-up-redelivery-guard.test.ts +432 -202
  262. package/src/tests/fx-adapter.test.ts +771 -0
  263. package/src/tests/grok-adapter.test.ts +36 -0
  264. package/src/tests/list-total-cache.test.ts +27 -2
  265. package/src/tests/mcp-loopback-proxy.test.ts +193 -0
  266. package/src/tests/memory-fts-populate.test.ts +11 -9
  267. package/src/tests/memory-retrieval-purge.test.ts +80 -0
  268. package/src/tests/migration-260-runtime-pool-cache.test.ts +118 -0
  269. package/src/tests/model-tier-resolution.test.ts +3 -3
  270. package/src/tests/pricing-refresh.test.ts +5 -5
  271. package/src/tests/prompt-template-session.test.ts +5 -3
  272. package/src/tests/provider-registration.test.ts +2 -1
  273. package/src/tests/query-audit-plans.test.ts +30 -2
  274. package/src/tests/runtime-cache-cleanup.test.ts +367 -0
  275. package/src/tests/runtime-e2b-pools.test.ts +41 -0
  276. package/src/tests/runtime-pools-db.test.ts +98 -0
  277. package/src/tests/runtime-pools-http.test.ts +29 -0
  278. package/src/tests/runtime-providers-docker.test.ts +253 -0
  279. package/src/tests/runtime-remote-controller.test.ts +66 -2
  280. package/src/tests/runtime-supervisor.test.ts +66 -2
  281. package/src/tests/script-connections.test.ts +13 -0
  282. package/src/tests/script-runs-http.test.ts +684 -2
  283. package/src/tests/seed-model-profiles.test.ts +2 -2
  284. package/src/tests/seed-schedules.test.ts +138 -0
  285. package/src/tests/seed-scripts.test.ts +26 -0
  286. package/src/tests/session-cost-recompute-unit.test.ts +39 -0
  287. package/src/tests/session-costs-golden.test.ts +26 -0
  288. package/src/tests/session-summaries-plans.test.ts +120 -0
  289. package/src/tests/session-summaries.test.ts +72 -12
  290. package/src/tests/sessions-tree.test.ts +11 -3
  291. package/src/tests/slack-leave-thread.test.ts +571 -0
  292. package/src/tests/slack-render-v2-query-plans.test.ts +24 -2
  293. package/src/tests/status.test.ts +11 -0
  294. package/src/tests/tasks-readable-row.test.ts +155 -0
  295. package/src/tests/tool-annotations.test.ts +2 -1
  296. package/src/tests/workflow-engine-v2.test.ts +42 -0
  297. package/src/tests/workflow-script-principal.test.ts +207 -7
  298. package/src/tools/send-task.ts +35 -47
  299. package/src/tools/slack-leave-thread.ts +104 -0
  300. package/src/tools/steer-task.ts +3 -2
  301. package/src/tools/task-dedup.ts +45 -21
  302. package/src/tools/tool-config.ts +2 -1
  303. package/src/types.ts +57 -0
  304. package/src/utils/codex-auth-failure.ts +30 -0
  305. package/src/utils/context-window.ts +4 -1
  306. package/src/utils/credentials.ts +7 -0
  307. package/src/utils/fx-models.ts +43 -0
  308. package/src/workflows/cooldown.ts +3 -2
  309. package/src/workflows/engine.ts +24 -9
  310. package/templates/schedules/dreaming-daily/config.json +1 -1
  311. package/templates/schedules/dreaming-daily/content.md +6 -14
  312. package/templates/skills/composio-google-docs/content.md +10 -0
  313. package/templates/skills/script-workflows/content.md +19 -1
  314. package/templates/skills/swarm-docs/content.md +2 -0
  315. package/templates/skills/swarm-scripts/SKILL.md +2 -0
  316. package/templates/skills/swarm-scripts/content.md +2 -0
  317. package/templates/workflows/dreaming/content.md +5 -1
  318. package/dist/acp-adapter-96dc64m2.js +0 -15
  319. package/dist/cli-dznrgaep.js +0 -30
  320. package/dist/cli-w3hcrxht.js +0 -6985
  321. package/dist/handlers-jg7v74ty.js +0 -31
  322. package/dist/index-8c1zbz59.js +0 -29
  323. package/dist/index-8k8tmfxd.js +0 -32
  324. package/dist/index-hq617xdj.js +0 -36
  325. package/dist/lead-567kw7x3.js +0 -68
  326. package/dist/worker-fjdqra6q.js +0 -68
@@ -61,6 +61,259 @@ afterEach(async () => {
61
61
  });
62
62
 
63
63
  describe("Docker runtime provider", () => {
64
+ for (const failureAt of ["create", "start"] as const) {
65
+ test(`cache mount failure during container ${failureAt} retries once without cache`, async () => {
66
+ const bodies: Array<{ HostConfig: { Mounts?: unknown }; Env: string[] }> = [];
67
+ const secret = "mount-failure-private-token";
68
+ handler = (url, init) => {
69
+ if (url.pathname.endsWith("/volumes/create"))
70
+ return Response.json(JSON.parse(String(init.body)));
71
+ if (url.pathname.endsWith("/containers/create")) {
72
+ bodies.push(JSON.parse(String(init.body)));
73
+ if (bodies.length === 1 && failureAt === "create")
74
+ return Response.json({ message: `invalid mount config ${secret}` }, { status: 500 });
75
+ return Response.json({ Id: `container-${bodies.length}` });
76
+ }
77
+ if (url.pathname.endsWith("/container-1/start") && failureAt === "start")
78
+ return Response.json(
79
+ { message: `error while mounting volume ${secret}` },
80
+ { status: 500 },
81
+ );
82
+ return new Response(null, { status: 204 });
83
+ };
84
+ const result = await provider.provision(target, {
85
+ ...request,
86
+ cache: { key: "swarm-cache-pool-1", mountPath: "/cache" },
87
+ env: { API_KEY: secret },
88
+ });
89
+ expect(result.externalId).toBe("container-2");
90
+ expect(result.meta?.cacheWarning).toContain("mount");
91
+ expect(result.meta?.cacheWarning).not.toContain(secret);
92
+ expect(bodies).toHaveLength(2);
93
+ expect(bodies[0]?.HostConfig.Mounts).toBeDefined();
94
+ expect(bodies[1]?.HostConfig.Mounts).toBeUndefined();
95
+ expect(bodies[1]?.Env).toEqual([`API_KEY=${secret}`]);
96
+ const operations = calls.map(({ url, init }) => `${init.method} ${url.pathname}`);
97
+ if (failureAt === "start")
98
+ expect(operations.indexOf("DELETE /v1.47/containers/container-1")).toBeLessThan(
99
+ operations.lastIndexOf("POST /v1.47/containers/create"),
100
+ );
101
+ expect(operations.at(-1)).toBe("POST /v1.47/containers/container-2/start");
102
+ });
103
+ }
104
+
105
+ test("unrelated container errors never trigger a cache fallback", async () => {
106
+ handler = (url, init) =>
107
+ url.pathname.endsWith("/volumes/create")
108
+ ? Response.json(JSON.parse(String(init.body)))
109
+ : Response.json({ message: "network not found" }, { status: 500 });
110
+ await expect(
111
+ provider.provision(target, {
112
+ ...request,
113
+ cache: { key: "swarm-cache-pool-1", mountPath: "/cache" },
114
+ }),
115
+ ).rejects.toThrow("network not found");
116
+ expect(calls.filter(({ url }) => url.pathname.endsWith("/containers/create"))).toHaveLength(1);
117
+ });
118
+
119
+ test("a failed cleanup after a mount start error prevents a duplicate container attempt", async () => {
120
+ handler = (url, init) => {
121
+ if (url.pathname.endsWith("/volumes/create"))
122
+ return Response.json(JSON.parse(String(init.body)));
123
+ if (url.pathname.endsWith("/containers/create"))
124
+ return Response.json({ Id: handle.externalId });
125
+ return Response.json(
126
+ { message: init.method === "DELETE" ? "cleanup unavailable" : "volume mount failed" },
127
+ { status: 500 },
128
+ );
129
+ };
130
+ await expect(
131
+ provider.provision(target, {
132
+ ...request,
133
+ cache: { key: "swarm-cache-pool-1", mountPath: "/cache" },
134
+ }),
135
+ ).rejects.toThrow("failed-start cleanup failed");
136
+ expect(calls.filter(({ url }) => url.pathname.endsWith("/containers/create"))).toHaveLength(1);
137
+ });
138
+
139
+ test("cache-free retry errors propagate without a third attempt", async () => {
140
+ let creates = 0;
141
+ handler = (url, init) =>
142
+ url.pathname.endsWith("/volumes/create")
143
+ ? Response.json(JSON.parse(String(init.body)))
144
+ : Response.json(
145
+ { message: ++creates === 1 ? "volume mount failed" : "daemon unavailable" },
146
+ { status: 500 },
147
+ );
148
+ await expect(
149
+ provider.provision(target, {
150
+ ...request,
151
+ cache: { key: "swarm-cache-pool-1", mountPath: "/cache" },
152
+ }),
153
+ ).rejects.toThrow("daemon unavailable");
154
+ expect(creates).toBe(2);
155
+ });
156
+
157
+ test("cache release verifies ownership and never forces removal", async () => {
158
+ const cache = { key: "swarm-cache-pool-1", poolId: request.poolId, ownerId: request.ownerId };
159
+ handler = (_url, init) =>
160
+ init.method === "GET"
161
+ ? Response.json({
162
+ Name: cache.key,
163
+ Labels: {
164
+ [RUNTIME_LABELS.owner]: cache.ownerId,
165
+ [RUNTIME_LABELS.poolId]: cache.poolId,
166
+ "swarm.cache": "true",
167
+ },
168
+ })
169
+ : new Response(null, { status: 204 });
170
+ await provider.releaseCache(target, cache);
171
+ expect(calls.map(({ init }) => init.method)).toEqual(["GET", "DELETE"]);
172
+ expect(calls[1]?.url.searchParams.has("force")).toBe(false);
173
+ calls = [];
174
+ handler = () => Response.json({ Name: cache.key, Labels: {} });
175
+ await expect(provider.releaseCache(target, cache)).rejects.toThrow("not owned");
176
+ expect(calls).toHaveLength(1);
177
+ });
178
+
179
+ test("cache release tolerates missing volumes and defers in-use volumes", async () => {
180
+ const cache = { key: "swarm-cache-pool-1", poolId: request.poolId, ownerId: request.ownerId };
181
+ handler = () => new Response("missing", { status: 404 });
182
+ await provider.releaseCache(target, cache);
183
+ handler = (_url, init) =>
184
+ init.method === "GET"
185
+ ? Response.json({
186
+ Name: cache.key,
187
+ Labels: {
188
+ [RUNTIME_LABELS.owner]: cache.ownerId,
189
+ [RUNTIME_LABELS.poolId]: cache.poolId,
190
+ "swarm.cache": "true",
191
+ },
192
+ })
193
+ : new Response("volume is in use", { status: 409 });
194
+ await expect(provider.releaseCache(target, cache)).rejects.toThrow("volume is in use");
195
+ });
196
+
197
+ test("cache volumes are shared across runtimes and survive container termination", async () => {
198
+ const volumes = new Map<
199
+ string,
200
+ { Name: string; Labels: Record<string, string>; content: string }
201
+ >();
202
+ const mounts: unknown[] = [];
203
+ handler = (url, init) => {
204
+ const body = init.body ? JSON.parse(String(init.body)) : undefined;
205
+ if (url.pathname.endsWith("/volumes/create")) {
206
+ if (!volumes.has(body.Name)) volumes.set(body.Name, { ...body, content: "" });
207
+ return Response.json(volumes.get(body.Name));
208
+ }
209
+ if (url.pathname.endsWith("/containers/create")) {
210
+ mounts.push(body.HostConfig.Mounts);
211
+ return Response.json({ Id: crypto.randomUUID() });
212
+ }
213
+ return new Response(null, { status: 204 });
214
+ };
215
+ const cache = { key: "swarm-cache-pool-1", mountPath: "/cache" };
216
+ const first = await provider.provision(target, { ...request, cache });
217
+ volumes.get(cache.key)!.content = "cached git objects";
218
+ await provider.terminate(target, first);
219
+ const second = await provider.provision(target, {
220
+ ...request,
221
+ runtimeId: crypto.randomUUID(),
222
+ cache,
223
+ });
224
+ await provider.terminate(target, second);
225
+ expect(volumes.size).toBe(1);
226
+ expect(volumes.get(cache.key)?.content).toBe("cached git objects");
227
+ expect(volumes.get(cache.key)?.Labels).toEqual({
228
+ [RUNTIME_LABELS.owner]: request.ownerId,
229
+ [RUNTIME_LABELS.poolId]: request.poolId,
230
+ "swarm.cache": "true",
231
+ });
232
+ expect(mounts).toEqual(
233
+ Array(2).fill([{ Type: "volume", Source: cache.key, Target: "/cache" }]),
234
+ );
235
+ expect(
236
+ calls
237
+ .filter(({ init }) => init.method === "DELETE")
238
+ .every(
239
+ ({ url }) =>
240
+ url.pathname.includes("/containers/") && url.searchParams.get("v") === "true",
241
+ ),
242
+ ).toBe(true);
243
+ expect(provider.traits).toMatchObject({ persistentCache: true, ephemeralDisk: true });
244
+ });
245
+
246
+ test("cache provisioning fails open with a scrubbed warning and no mount", async () => {
247
+ const secret = "cache-volume-short-secret";
248
+ handler = (url) =>
249
+ url.pathname.endsWith("/volumes/create")
250
+ ? new Response(`volume unavailable ${secret}`, { status: 500 })
251
+ : url.pathname.endsWith("/containers/create")
252
+ ? Response.json({ Id: handle.externalId })
253
+ : new Response(null, { status: 204 });
254
+ const result = await provider.provision(target, {
255
+ ...request,
256
+ cache: { key: "swarm-cache-pool-1", mountPath: "/cache" },
257
+ env: { API_KEY: secret },
258
+ });
259
+ expect(result.meta?.cacheWarning).toContain("volume unavailable");
260
+ expect(result.meta?.cacheWarning).not.toContain(secret);
261
+ const body = JSON.parse(
262
+ String(calls.find(({ url }) => url.pathname.endsWith("/containers/create"))?.init.body),
263
+ );
264
+ expect(body.HostConfig.Mounts).toBeUndefined();
265
+ expect(calls.at(-1)?.url.pathname).toEndWith("/containers/container-1/start");
266
+ });
267
+
268
+ for (const mismatched of ["owner", "poolId", "cache"] as const) {
269
+ test(`cache volumes with a mismatched ${mismatched} label are never mounted or relabeled`, async () => {
270
+ const labels = {
271
+ [RUNTIME_LABELS.owner]: request.ownerId,
272
+ [RUNTIME_LABELS.poolId]: request.poolId,
273
+ "swarm.cache": "true",
274
+ };
275
+ labels[mismatched === "cache" ? "swarm.cache" : RUNTIME_LABELS[mismatched]] = "foreign";
276
+ handler = (url) =>
277
+ url.pathname.endsWith("/volumes/create")
278
+ ? Response.json({ Name: "swarm-cache-pool-1", Labels: labels })
279
+ : url.pathname.endsWith("/containers/create")
280
+ ? Response.json({ Id: handle.externalId })
281
+ : new Response(null, { status: 204 });
282
+ const result = await provider.provision(target, {
283
+ ...request,
284
+ cache: { key: "swarm-cache-pool-1", mountPath: "/cache" },
285
+ });
286
+ expect(result.meta?.cacheWarning).toContain("not owned");
287
+ expect(JSON.parse(String(calls[1]?.init.body)).HostConfig.Mounts).toBeUndefined();
288
+ expect(calls.some(({ init }) => init.method === "DELETE")).toBe(false);
289
+ });
290
+ }
291
+
292
+ test("cache mounts survive the missing-image retry", async () => {
293
+ let creates = 0;
294
+ handler = (url, init) => {
295
+ if (url.pathname.endsWith("/volumes/create"))
296
+ return Response.json(JSON.parse(String(init.body)));
297
+ if (url.pathname.endsWith("/containers/create")) {
298
+ expect(JSON.parse(String(init.body)).HostConfig.Mounts).toEqual([
299
+ { Type: "volume", Source: "swarm-cache-pool-1", Target: "/cache" },
300
+ ]);
301
+ return ++creates === 1
302
+ ? new Response("No such image", { status: 404 })
303
+ : Response.json({ Id: handle.externalId });
304
+ }
305
+ return url.pathname.endsWith("/images/create")
306
+ ? new Response('{"status":"complete"}\n')
307
+ : new Response(null, { status: 204 });
308
+ };
309
+ await provider.provision(target, {
310
+ ...request,
311
+ cache: { key: "swarm-cache-pool-1", mountPath: "/cache" },
312
+ });
313
+ expect(creates).toBe(2);
314
+ expect(calls.filter(({ url }) => url.pathname.endsWith("/volumes/create"))).toHaveLength(1);
315
+ });
316
+
64
317
  for (const message of [
65
318
  "client version 1.44 is too old. Minimum supported API version is 1.47",
66
319
  "API version is unsupported",
@@ -17,6 +17,7 @@ import {
17
17
  markRuntimeTerminated,
18
18
  upsertRuntimeInstance,
19
19
  } from "../be/multi-runtime";
20
+ import { reconcileCacheReleases } from "../be/runtime-pools/cache-cleanup";
20
21
  import {
21
22
  clearRuntimeOwnerIdCache,
22
23
  ensureRuntimeOwnerId,
@@ -30,6 +31,7 @@ import {
30
31
  getRuntimeController,
31
32
  getRuntimePool,
32
33
  updateRuntimeController,
34
+ updateRuntimePool,
33
35
  } from "../be/runtime-pools/db";
34
36
  import { remoteControllerSession } from "../be/runtime-pools/remote-controller";
35
37
  import {
@@ -50,15 +52,29 @@ import type {
50
52
  RuntimeTargetConfig,
51
53
  } from "../runtime-providers";
52
54
  import { ControllerOfflineError } from "../runtime-providers/controller";
53
- import { RUNTIME_LABELS } from "../runtime-providers/types";
55
+ import { RUNTIME_LABELS, type RuntimeCacheReference } from "../runtime-providers/types";
54
56
  import { listenOnFreePort } from "./test-net";
55
57
  import { CHILD_PROCESS_TEST_BUDGET_MS, runChild } from "./test-proc";
56
58
 
57
59
  class FakeProvider implements RuntimeProvider {
58
60
  readonly kind = "docker" as const;
59
- readonly traits = { canProvision: true, ephemeralDisk: true, supportsPause: false };
61
+ readonly traits: RuntimeProvider["traits"] = {
62
+ canProvision: true,
63
+ ephemeralDisk: true,
64
+ supportsPause: false,
65
+ };
60
66
  requests: ProvisionRequest[] = [];
61
67
  terminated: string[] = [];
68
+ releasedCaches: RuntimeCacheReference[] = [];
69
+ caches = new Set<string>();
70
+ cacheReleaseGate?: Promise<void>;
71
+ completedReleases = 0;
72
+ async releaseCache(_target: RuntimeTargetConfig, cache: RuntimeCacheReference) {
73
+ this.releasedCaches.push(cache);
74
+ await this.cacheReleaseGate;
75
+ this.caches.delete(cache.key);
76
+ this.completedReleases++;
77
+ }
62
78
  owners: string[] = [];
63
79
  runtimes = new Map<string, { handle: RuntimeHandle; ownerId: string; status: RuntimeStatus }>();
64
80
  gate?: Promise<void>;
@@ -67,6 +83,7 @@ class FakeProvider implements RuntimeProvider {
67
83
  }
68
84
  async provision(_target: RuntimeTargetConfig, req: ProvisionRequest) {
69
85
  this.requests.push(req);
86
+ if (req.cache) this.caches.add(req.cache.key);
70
87
  await this.gate;
71
88
  const handle: RuntimeHandle = {
72
89
  kind: this.kind,
@@ -133,6 +150,7 @@ beforeEach(async () => {
133
150
  process.env.RUNTIME_CIRCUIT_THRESHOLD = "3";
134
151
  process.env.RUNTIME_PROVISION_MAX_ATTEMPTS = "1";
135
152
  for (const table of [
153
+ "runtime_cache_releases",
136
154
  "active_sessions",
137
155
  "runtime_instances",
138
156
  "session_tokens",
@@ -205,22 +223,68 @@ function request(ownerId: string): ProvisionRequest {
205
223
  }
206
224
 
207
225
  test("hello sets online and all controller commands make round trips through the CLI client", async () => {
226
+ fake.traits.persistentCache = true;
208
227
  await connect();
209
228
  const row = await getRuntimeController(controllerId);
210
229
  expect(row?.lastSeenAt).toBeTruthy();
211
230
  const commands = await getControllerCommands(controllerId);
212
231
  expect(commands.workerApiUrl).toBe("http://remote-worker.invalid");
213
232
  const req = request(await ensureRuntimeOwnerId());
233
+ expect(commands.traits.persistentCache).toBe(true);
234
+ req.cache = { key: `swarm-cache-${req.poolId}`, mountPath: "/cache" };
214
235
  const handle = await commands.provision(req);
236
+ expect(fake.requests[0]?.cache).toEqual(req.cache);
215
237
  expect(handle.controllerId).toBe(controllerId);
216
238
  expect(await commands.status([handle])).toEqual({ [handle.externalId]: "running" });
217
239
  expect(await commands.list(req.ownerId)).toEqual([handle]);
218
240
  expect(await commands.list("another-swarm")).toEqual([]);
219
241
  await commands.terminate(handle, "test complete");
242
+ const cache = { key: req.cache.key, poolId: req.poolId, ownerId: req.ownerId };
243
+ await commands.releaseCache!(cache);
244
+ expect(fake.releasedCaches).toEqual([cache]);
220
245
  expect(await commands.status([handle])).toEqual({ [handle.externalId]: "exited" });
221
246
  expect(JSON.stringify(logSpy.mock.calls)).not.toContain(token);
222
247
  });
223
248
 
249
+ test("a timed-out remote cache release cannot delete a re-enabled cache", async () => {
250
+ fake.traits.persistentCache = true;
251
+ await connect();
252
+ const pool = await createRuntimePool({
253
+ name: "cache-timeout",
254
+ kind: "docker",
255
+ placements: [controllerId],
256
+ image: "worker:test",
257
+ cache: { mountPath: "/cache" },
258
+ });
259
+ const agent = await createPoolAgent(pool.id, { name: "cache-worker" });
260
+ const commands = await getControllerCommands(controllerId);
261
+ const owner = await ensureRuntimeOwnerId();
262
+ await provisionRuntime(pool, agent, commands, owner);
263
+ const oldKey = fake.requests[0]!.cache!.key;
264
+ const runtime = (await listRuntimeInstancesForPool(pool.id))[0]!;
265
+ await commands.terminate(runtime.externalHandle as unknown as RuntimeHandle, "test");
266
+ await markRuntimeTerminated(runtime.id, "stopped");
267
+ await updateRuntimePool(pool.id, { cache: null });
268
+ const finish = Promise.withResolvers<void>();
269
+ fake.cacheReleaseGate = finish.promise;
270
+ try {
271
+ await reconcileCacheReleases(async () => commands, new AbortController().signal);
272
+ expect(fake.completedReleases).toBe(0);
273
+ const enabled = await updateRuntimePool(pool.id, { cache: { mountPath: "/cache" } });
274
+ await provisionRuntime(enabled, agent, commands, owner);
275
+ const newKey = fake.requests.at(-1)!.cache!.key;
276
+ finish.resolve();
277
+ await until(async () => fake.completedReleases === 1);
278
+ expect(fake.caches.has(oldKey)).toBe(false);
279
+ expect(fake.caches.has(newKey)).toBe(true);
280
+ expect(newKey).not.toBe(oldKey);
281
+ await reconcileCacheReleases(async () => commands, new AbortController().signal);
282
+ } finally {
283
+ finish.resolve();
284
+ await until(async () => fake.completedReleases >= 1);
285
+ }
286
+ });
287
+
224
288
  test("remote E2B provisioning, renewal, pause, and resume use the controller session", async () => {
225
289
  await updateRuntimeController(controllerId, {
226
290
  kind: "e2b",
@@ -31,6 +31,7 @@ import {
31
31
  getRuntimePool,
32
32
  } from "../be/runtime-pools/db";
33
33
  import {
34
+ provisionRuntime,
34
35
  reconcileControllerOrphans,
35
36
  reconcileOnce,
36
37
  runtimeSupervisorStatus,
@@ -53,13 +54,19 @@ import { registerVolatileSecret, scrubSecrets } from "../utils/secret-scrubber";
53
54
 
54
55
  class FakeRuntimeProvider implements RuntimeProvider {
55
56
  readonly kind = "docker" as const;
56
- readonly traits = { canProvision: true, ephemeralDisk: true, supportsPause: false };
57
+ readonly traits: RuntimeProvider["traits"] = {
58
+ canProvision: true,
59
+ ephemeralDisk: true,
60
+ supportsPause: false,
61
+ persistentCache: true,
62
+ };
57
63
  requests: ProvisionRequest[] = [];
58
64
  targets: RuntimeTargetConfig[] = [];
59
65
  terminated: string[] = [];
60
66
  owners: string[] = [];
61
67
  runtimes = new Map<string, { handle: RuntimeHandle; owner: string; status: RuntimeStatus }>();
62
68
  failure: string | null = null;
69
+ cacheWarning: string | null = null;
63
70
  gate: Promise<void> | null = null;
64
71
  terminateFailures = new Map<string, string>();
65
72
  statusFailures = new Set<string>();
@@ -74,7 +81,11 @@ class FakeRuntimeProvider implements RuntimeProvider {
74
81
  if (this.failure) throw new Error(this.failure);
75
82
  if (this.provisionFailures.has(req.agentId))
76
83
  throw new Error(this.provisionFailures.get(req.agentId));
77
- const handle: RuntimeHandle = { kind: "docker", externalId: crypto.randomUUID() };
84
+ const handle: RuntimeHandle = {
85
+ kind: "docker",
86
+ externalId: crypto.randomUUID(),
87
+ ...(this.cacheWarning ? { meta: { cacheWarning: this.cacheWarning } } : {}),
88
+ };
78
89
  this.runtimes.set(handle.externalId, { handle, owner: req.ownerId, status: "running" });
79
90
  return handle;
80
91
  }
@@ -184,6 +195,59 @@ async function activate(runtimeId: string, agentId: string, reportedSlots = 1) {
184
195
  await getDbClient().run("UPDATE agents SET status = 'idle' WHERE id = ?", [agentId]);
185
196
  }
186
197
 
198
+ test("cache identity is stable across provisions of one pool and scoped away from other pools", async () => {
199
+ const { pool, agent } = await wake({ cache: { mountPath: "/cache" } });
200
+ const controller = await getControllerCommands(controllerId);
201
+ await provisionRuntime(pool, agent, controller, await ensureRuntimeOwnerId());
202
+ const other = await setup({ cache: { mountPath: "/cache" } });
203
+ await provisionRuntime(other.pool, other.agent, controller, await ensureRuntimeOwnerId());
204
+ const [first, second, third] = fake.requests.map((req) => req.cache!);
205
+ expect(first).toEqual({
206
+ key: expect.stringContaining(`swarm-cache-${pool.id}-`),
207
+ mountPath: "/cache",
208
+ });
209
+ expect(second).toEqual(first);
210
+ expect(third.key).toStartWith(`swarm-cache-${other.pool.id}-`);
211
+ expect(third.key).not.toBe(first.key);
212
+ });
213
+
214
+ test("an older controller boots without cache and surfaces a warning without failing the provision", async () => {
215
+ const { pool, agent } = await setup({ cache: { mountPath: "/cache" } });
216
+ delete fake.traits.persistentCache;
217
+ await provisionRuntime(
218
+ pool,
219
+ agent,
220
+ await getControllerCommands(controllerId),
221
+ await ensureRuntimeOwnerId(),
222
+ );
223
+ expect(fake.requests[0]?.cache).toBeUndefined();
224
+ expect(await getRuntimePool(pool.id)).toMatchObject({
225
+ consecutiveFailures: 0,
226
+ lastError: expect.stringContaining("Controller does not support persistent caches"),
227
+ });
228
+ expect((await getAgentById(agent.id))?.runtimeProvisionAttempts).toBe(0);
229
+ });
230
+
231
+ test("cache failure warnings clear after recovery without hiding unrelated provision errors", async () => {
232
+ fake.cacheWarning = "cache unavailable";
233
+ const { pool, agent } = await wake({ cache: { mountPath: "/cache" } });
234
+ expect(await getRuntimePool(pool.id)).toMatchObject({
235
+ consecutiveFailures: 0,
236
+ lastError: "Persistent cache: cache unavailable",
237
+ });
238
+ fake.cacheWarning = null;
239
+ const commands = await getControllerCommands(controllerId);
240
+ await provisionRuntime(pool, agent, commands, await ensureRuntimeOwnerId());
241
+ expect((await getRuntimePool(pool.id))?.lastError).toBeNull();
242
+ await getDbClient().run(
243
+ "UPDATE runtime_pools SET last_error = 'Existing provision error' WHERE id = ?",
244
+ [pool.id],
245
+ );
246
+ fake.cacheWarning = "another cache failure";
247
+ await provisionRuntime(pool, agent, commands, await ensureRuntimeOwnerId());
248
+ expect((await getRuntimePool(pool.id))?.lastError).toBe("Existing provision error");
249
+ });
250
+
187
251
  test("pending task provisions once while registration is pending, with scoped env and owner labels", async () => {
188
252
  const { pool, agent, runtime } = await wake({
189
253
  env: {
@@ -665,6 +665,19 @@ describe("script connections", () => {
665
665
  markMigrationApplied(database, "254_query_audit_indexes.sql");
666
666
  // 256 alters agents, which this migration-112-only fixture does not create.
667
667
  markMigrationApplied(database, "256_agent_slack_icon.sql");
668
+ // 259 alters session_summaries, which 253 (skipped above) creates.
669
+ markMigrationApplied(database, "259_session_summaries_open_tasks.sql");
670
+ // 260 alters runtime_pools and creates runtime_cache_releases. This
671
+ // migration-112-only fixture has neither runtime_pools nor runtime_controllers.
672
+ markMigrationApplied(database, "260_runtime_pool_cache.sql");
673
+ // 262 indexes agent_tasks columns that this fixture's id-only agent_tasks lacks.
674
+ markMigrationApplied(database, "262_session_visibility_indexes.sql");
675
+ // 263 indexes agent_tasks columns that this fixture's id-only agent_tasks lacks.
676
+ markMigrationApplied(database, "263_task_list_filter_index.sql");
677
+ // 264 indexes agent_tasks columns that this fixture's id-only agent_tasks lacks.
678
+ markMigrationApplied(database, "264_slack_thread_cover_index.sql");
679
+ // 266 alters script_runs, which this migration-112-only fixture does not create.
680
+ markMigrationApplied(database, "266_script_runs_source_task.sql");
668
681
 
669
682
  const id = crypto.randomUUID();
670
683
  const now = new Date().toISOString();