@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
@@ -35,6 +35,9 @@ export async function snapshotPage(
35
35
  passwordHash: page.passwordHash,
36
36
  body: page.body,
37
37
  needsCredentials: page.needsCredentials,
38
+ theme: page.theme,
39
+ themeTokens: page.themeTokens,
40
+ designVersion: page.designVersion,
38
41
  };
39
42
 
40
43
  return createPageVersion({
@@ -45,7 +45,7 @@ import type {
45
45
  * default; the image installs exactly this version and the per-task settings
46
46
  * set `amp.updates.mode: "disabled"`. Bump it deliberately, with a live run.
47
47
  */
48
- export const AMP_PACKAGE = "@ampcode/cli@0.0.1793275344-g17bf52";
48
+ export const AMP_PACKAGE = "@ampcode/cli@0.0.1793450357-gd44904";
49
49
 
50
50
  /** MCP server name. Amp exposes its tools as `agent_swarm` / `store_progress` (dashes become underscores). */
51
51
  const AMP_MCP_SERVER_NAME = "agent-swarm";
@@ -2053,6 +2053,7 @@ export async function createInProcessCodexSession(
2053
2053
  const appServerFactory = opts.appServerFactory ?? ((options) => new CodexAppServer(options));
2054
2054
  const client = appServerFactory({
2055
2055
  ...(codexPathOverride ? { codexPath: codexPathOverride } : {}),
2056
+ ...(gateway ? { configOverrides: codexGatewayConfig(gateway) } : {}),
2056
2057
  env,
2057
2058
  onStderr: (line) => {
2058
2059
  if (liveSession) liveSession.emitTransportStderr(line);
@@ -26,6 +26,7 @@ export interface CodexAppServerOptions {
26
26
  env: Readonly<Record<string, string | undefined>>;
27
27
  requestTimeoutMs?: number;
28
28
  onStderr?: (line: string) => void;
29
+ configOverrides?: Readonly<Record<string, CodexConfigValue | undefined>>;
29
30
  }
30
31
 
31
32
  type PendingRequest = {
@@ -53,6 +54,25 @@ export class CodexAppServerRpcError extends Error {
53
54
  }
54
55
  }
55
56
 
57
+ /** `-c key=<TOML value>` pairs, one per top-level key; tables become inline tables. */
58
+ export function configOverrideArgs(
59
+ overrides: Readonly<Record<string, CodexConfigValue | undefined>> = {},
60
+ ): string[] {
61
+ return Object.entries(overrides).flatMap(([key, value]) =>
62
+ value === undefined ? [] : ["-c", `${key}=${tomlValue(value)}`],
63
+ );
64
+ }
65
+
66
+ function tomlValue(value: CodexConfigValue): string {
67
+ if (typeof value === "string") return JSON.stringify(value);
68
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
69
+ if (Array.isArray(value)) return `[${value.map(tomlValue).join(", ")}]`;
70
+ const fields = Object.entries(value as Record<string, CodexConfigValue | undefined>).flatMap(
71
+ ([key, field]) => (field === undefined ? [] : [`${JSON.stringify(key)} = ${tomlValue(field)}`]),
72
+ );
73
+ return `{ ${fields.join(", ")} }`;
74
+ }
75
+
56
76
  /** Thin JSONL client for one `codex app-server --listen stdio://` process. */
57
77
  export class CodexAppServer {
58
78
  private readonly proc: ReturnType<typeof Bun.spawn>;
@@ -79,7 +99,13 @@ export class CodexAppServer {
79
99
  this.onStderr = options.onStderr;
80
100
 
81
101
  const codexPath = options.codexPath ?? resolveCodexPath(options.env.PATH);
82
- const command = [codexPath, "app-server", "--listen", "stdio://"];
102
+ const command = [
103
+ codexPath,
104
+ ...configOverrideArgs(options.configOverrides),
105
+ "app-server",
106
+ "--listen",
107
+ "stdio://",
108
+ ];
83
109
 
84
110
  const env = Object.fromEntries(
85
111
  Object.entries(options.env).filter(
@@ -21,7 +21,7 @@ import type {
21
21
  * Dockerfile.worker; bump the two together). Every ACP shape this adapter
22
22
  * relies on was measured against it.
23
23
  */
24
- export const FX_PINNED_VERSION = "0.0.15";
24
+ export const FX_PINNED_VERSION = "0.0.16";
25
25
 
26
26
  /** OpenAI's Chat Completions prefix; fx appends `/chat/completions`. */
27
27
  export const FX_OPENAI_BASE_URL = "https://api.openai.com/v1";
@@ -8,6 +8,13 @@
8
8
 
9
9
  import type { ExtensionFactory, McpServerConfig } from "@earendil-works/pi-coding-agent";
10
10
  import { buildRatingsFromLlm, fetchRetrievalsForTask, postRatings } from "../be/memory/raters/llm";
11
+ import {
12
+ advanceIdentityBaselines,
13
+ contentSha256,
14
+ PROFILE_FILE_IO,
15
+ type ProfileFileIO,
16
+ readIdentityBaselines,
17
+ } from "../commands/profile-sync";
11
18
  import { checkToolLoop, clearToolHistory } from "../hooks/tool-loop-detection";
12
19
  import { renderGoalReminder } from "../prompts/goal-reminder";
13
20
  import { isSessionSummarySkipped } from "../utils/env-flag";
@@ -100,11 +107,16 @@ export async function fetchTaskDetails(
100
107
  }
101
108
  }
102
109
 
103
- /** Sync identity files (SOUL.md, IDENTITY.md, TOOLS.md) to server */
110
+ /**
111
+ * Sync identity files (SOUL.md, IDENTITY.md, TOOLS.md) to server. A file whose
112
+ * hash still equals its baseline was not edited and is skipped, so a stale copy
113
+ * never overwrites a DB-side edit made during the session.
114
+ */
104
115
  export async function syncIdentityFilesToServer(
105
116
  config: SwarmHooksConfig,
106
117
  changeSource: "self_edit" | "session_sync" = "session_sync",
107
118
  signal?: AbortSignal,
119
+ io: ProfileFileIO = PROFILE_FILE_IO,
108
120
  ): Promise<void> {
109
121
  const updates: Record<string, string> = {};
110
122
  const paths: Record<string, string> = {
@@ -112,15 +124,14 @@ export async function syncIdentityFilesToServer(
112
124
  identityMd: "/workspace/IDENTITY.md",
113
125
  toolsMd: "/workspace/TOOLS.md",
114
126
  };
127
+ const baselines = await readIdentityBaselines(io.readFile);
115
128
 
116
129
  for (const [key, path] of Object.entries(paths)) {
117
130
  try {
118
- const file = Bun.file(path);
119
- if (await file.exists()) {
120
- const content = await file.text();
121
- if (content.trim() && content.length <= 65536) {
122
- updates[key] = content;
123
- }
131
+ const content = await io.readFile(path);
132
+ if (content === undefined || baselines?.[key] === contentSha256(content)) continue;
133
+ if (content.trim() && content.length <= 65536) {
134
+ updates[key] = content;
124
135
  }
125
136
  } catch {
126
137
  /* skip */
@@ -130,28 +141,27 @@ export async function syncIdentityFilesToServer(
130
141
  if (Object.keys(updates).length === 0) return;
131
142
 
132
143
  try {
133
- await fetch(`${config.apiUrl}/api/agents/${config.agentId}/profile`, {
144
+ const resp = await fetch(`${config.apiUrl}/api/agents/${config.agentId}/profile`, {
134
145
  method: "PUT",
135
146
  headers: apiHeaders(config),
136
147
  body: JSON.stringify({ ...updates, changeSource }),
137
148
  signal,
138
149
  });
150
+ if (resp.ok) await advanceIdentityBaselines(updates, io);
139
151
  } catch {
140
152
  /* silently fail */
141
153
  }
142
154
  }
143
155
 
144
156
  /** Sync setup script to server */
145
- async function syncSetupScriptToServer(
157
+ export async function syncSetupScriptToServer(
146
158
  config: SwarmHooksConfig,
147
159
  changeSource: "self_edit" | "session_sync" = "session_sync",
160
+ io: ProfileFileIO = PROFILE_FILE_IO,
148
161
  ): Promise<void> {
149
162
  try {
150
- const file = Bun.file("/workspace/start-up.sh");
151
- if (!(await file.exists())) return;
152
-
153
- const raw = await file.text();
154
- if (!raw.trim()) return;
163
+ const raw = await io.readFile("/workspace/start-up.sh");
164
+ if (!raw?.trim()) return;
155
165
 
156
166
  const markerStart = "# === Agent-managed setup (from DB) ===";
157
167
  const markerEnd = "# === End agent-managed setup ===";
@@ -166,12 +176,17 @@ async function syncSetupScriptToServer(
166
176
  }
167
177
 
168
178
  if (!content || content.length > 65536) return;
179
+ const baselines = await readIdentityBaselines(io.readFile);
180
+ if (baselines?.setupScript === contentSha256(content)) return;
169
181
 
170
- await fetch(`${config.apiUrl}/api/agents/${config.agentId}/profile`, {
182
+ const resp = await fetch(`${config.apiUrl}/api/agents/${config.agentId}/profile`, {
171
183
  method: "PUT",
172
184
  headers: apiHeaders(config),
173
185
  body: JSON.stringify({ setupScript: content, changeSource }),
174
186
  });
187
+ if (resp.ok) {
188
+ await advanceIdentityBaselines({ setupScript: content }, io);
189
+ }
175
190
  } catch {
176
191
  /* silently fail */
177
192
  }
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { RuntimeProviderKindSchema } from "../types";
2
+ import { RuntimePoolCacheSchema, RuntimeProviderKindSchema } from "../types";
3
3
 
4
4
  export const CONTROLLER_PROTOCOL_VERSION = 1;
5
5
  export const CONTROLLER_COMMAND_TIMEOUT_MS = 60_000;
@@ -16,6 +16,7 @@ const traitsSchema = z.object({
16
16
  canProvision: z.boolean(),
17
17
  ephemeralDisk: z.boolean(),
18
18
  supportsPause: z.boolean(),
19
+ persistentCache: z.boolean().optional(),
19
20
  maxTtlSec: z.number().positive().optional(),
20
21
  maxContinuousSec: z.number().positive().optional(),
21
22
  });
@@ -31,9 +32,21 @@ const provisionSchema = z.object({
31
32
  .object({ cpu: z.number().positive().optional(), memoryMb: z.number().positive().optional() })
32
33
  .optional(),
33
34
  ttlSec: z.number().positive().optional(),
35
+ cache: RuntimePoolCacheSchema.extend({
36
+ key: z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_.-]+$/),
37
+ }).optional(),
34
38
  });
35
39
  const command = { type: z.literal("cmd"), id: z.string().min(1) };
36
40
  export const ControllerCommandSchema = z.discriminatedUnion("op", [
41
+ z.object({
42
+ ...command,
43
+ op: z.literal("release-cache"),
44
+ payload: z.object({
45
+ key: z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_.-]+$/),
46
+ poolId: z.string().min(1),
47
+ ownerId: z.string().min(1),
48
+ }),
49
+ }),
37
50
  z.object({ ...command, op: z.literal("provision"), payload: provisionSchema }),
38
51
  z.object({
39
52
  ...command,
@@ -1,6 +1,7 @@
1
1
  import { scrubSecrets } from "../utils/secret-scrubber";
2
2
  import type {
3
3
  ProvisionRequest,
4
+ RuntimeCacheReference,
4
5
  RuntimeHandle,
5
6
  RuntimeProvider,
6
7
  RuntimeProviderTraits,
@@ -25,6 +26,7 @@ export interface ControllerCommands {
25
26
  terminate(handle: RuntimeHandle, reason: string): Promise<void>;
26
27
  status(handles: RuntimeHandle[]): Promise<Record<string, RuntimeStatus>>;
27
28
  list(ownerId: string): Promise<RuntimeHandle[]>;
29
+ releaseCache?(cache: RuntimeCacheReference): Promise<void>;
28
30
  renew?(handle: RuntimeHandle, ttlSec: number): Promise<{ endAt: string }>;
29
31
  pause?(handle: RuntimeHandle): Promise<void>;
30
32
  resume?(handle: RuntimeHandle): Promise<void>;
@@ -36,6 +38,7 @@ export class BuiltinController implements ControllerCommands {
36
38
  readonly renew?: ControllerCommands["renew"];
37
39
  readonly pause?: ControllerCommands["pause"];
38
40
  readonly resume?: ControllerCommands["resume"];
41
+ readonly releaseCache?: ControllerCommands["releaseCache"];
39
42
 
40
43
  constructor(
41
44
  readonly id: string,
@@ -47,6 +50,8 @@ export class BuiltinController implements ControllerCommands {
47
50
  this.workerApiUrl = workerApiUrl;
48
51
  if (provider.renew)
49
52
  this.renew = (handle, ttlSec) => this.call(() => provider.renew!(target, handle, ttlSec));
53
+ if (provider.releaseCache)
54
+ this.releaseCache = (cache) => this.call(() => provider.releaseCache!(target, cache));
50
55
  if (provider.pause) this.pause = (handle) => this.call(() => provider.pause!(target, handle));
51
56
  if (provider.resume)
52
57
  this.resume = (handle) => this.call(() => provider.resume!(target, handle));
@@ -4,6 +4,7 @@ import {
4
4
  type ConfigStatus,
5
5
  type ProvisionRequest,
6
6
  RUNTIME_LABELS,
7
+ type RuntimeCacheReference,
7
8
  type RuntimeHandle,
8
9
  type RuntimeProvider,
9
10
  type RuntimeStatus,
@@ -63,6 +64,22 @@ class DockerApiError extends Error {
63
64
  }
64
65
  }
65
66
 
67
+ function isCacheMountError(error: unknown): boolean {
68
+ return error instanceof DockerApiError && /\b(?:mount\w*|volume\w*)\b/i.test(error.message);
69
+ }
70
+
71
+ type CacheVolume = { Name: string; Labels?: Record<string, string> };
72
+
73
+ function assertCacheOwner(volume: CacheVolume, cache: RuntimeCacheReference): void {
74
+ if (
75
+ volume.Name !== cache.key ||
76
+ volume.Labels?.[RUNTIME_LABELS.owner] !== cache.ownerId ||
77
+ volume.Labels?.[RUNTIME_LABELS.poolId] !== cache.poolId ||
78
+ volume.Labels?.["swarm.cache"] !== "true"
79
+ )
80
+ throw new Error(`Cache volume ${cache.key} is not owned by this swarm and pool`);
81
+ }
82
+
66
83
  async function dockerApi<T>(
67
84
  target: RuntimeTargetConfig,
68
85
  method: string,
@@ -119,7 +136,12 @@ async function dockerApi<T>(
119
136
 
120
137
  export class DockerRuntimeProvider implements RuntimeProvider {
121
138
  readonly kind = "docker" as const;
122
- readonly traits = { canProvision: true, ephemeralDisk: true, supportsPause: false };
139
+ readonly traits = {
140
+ canProvision: true,
141
+ ephemeralDisk: true,
142
+ supportsPause: false,
143
+ persistentCache: true,
144
+ };
123
145
 
124
146
  checkConfig(target: RuntimeTargetConfig): ConfigStatus {
125
147
  try {
@@ -132,6 +154,29 @@ export class DockerRuntimeProvider implements RuntimeProvider {
132
154
 
133
155
  async provision(target: RuntimeTargetConfig, req: ProvisionRequest): Promise<RuntimeHandle> {
134
156
  try {
157
+ let cacheWarning: string | undefined;
158
+ let cacheMount: { Type: "volume"; Source: string; Target: string } | undefined;
159
+ if (req.cache) {
160
+ try {
161
+ const volume = await dockerApi<CacheVolume>(target, "POST", "/volumes/create", {
162
+ Name: req.cache.key,
163
+ Labels: {
164
+ [RUNTIME_LABELS.owner]: req.ownerId,
165
+ [RUNTIME_LABELS.poolId]: req.poolId,
166
+ "swarm.cache": "true",
167
+ },
168
+ });
169
+ // Docker reuses an existing name without replacing its labels.
170
+ assertCacheOwner(volume, {
171
+ key: req.cache.key,
172
+ poolId: req.poolId,
173
+ ownerId: req.ownerId,
174
+ });
175
+ cacheMount = { Type: "volume", Source: req.cache.key, Target: req.cache.mountPath };
176
+ } catch (error) {
177
+ cacheWarning = redactWithEnv(scrubSecrets(String(error)), req.env);
178
+ }
179
+ }
135
180
  const path = `/containers/create?name=${encodeURIComponent(`swarm-rt-${req.runtimeId}`)}`;
136
181
  const body = {
137
182
  Image: req.image,
@@ -154,40 +199,80 @@ export class DockerRuntimeProvider implements RuntimeProvider {
154
199
  NetworkMode: target.DOCKER_NETWORK,
155
200
  Memory: req.resources?.memoryMb ? req.resources.memoryMb * 1024 * 1024 : undefined,
156
201
  NanoCpus: req.resources?.cpu ? Math.round(req.resources.cpu * 1e9) : undefined,
202
+ ...(cacheMount ? { Mounts: [cacheMount] } : {}),
157
203
  },
158
204
  };
159
- let created: { Id: string };
160
- try {
161
- created = await dockerApi(target, "POST", path, body);
162
- } catch (err) {
163
- if (
164
- !(err instanceof DockerApiError) ||
165
- err.statusCode !== 404 ||
166
- !err.message.includes("No such image")
167
- ) {
168
- throw err;
205
+ const createAndStart = async (): Promise<string> => {
206
+ let created: { Id: string };
207
+ try {
208
+ created = await dockerApi(target, "POST", path, body);
209
+ } catch (err) {
210
+ if (
211
+ !(err instanceof DockerApiError) ||
212
+ err.statusCode !== 404 ||
213
+ !err.message.includes("No such image")
214
+ )
215
+ throw err;
216
+ const pullParams = new URLSearchParams({ fromImage: req.image });
217
+ if (
218
+ !req.image.includes("@") &&
219
+ req.image.lastIndexOf(":") <= req.image.lastIndexOf("/")
220
+ ) {
221
+ pullParams.set("tag", "latest");
222
+ }
223
+ await dockerApi(target, "POST", `/images/create?${pullParams}`, undefined, true);
224
+ created = await dockerApi(target, "POST", path, body);
169
225
  }
170
- const pullParams = new URLSearchParams({ fromImage: req.image });
171
- if (!req.image.includes("@") && req.image.lastIndexOf(":") <= req.image.lastIndexOf("/")) {
172
- pullParams.set("tag", "latest");
226
+ try {
227
+ await dockerApi(target, "POST", `/containers/${created.Id}/start`);
228
+ } catch (err) {
229
+ try {
230
+ await dockerApi(target, "DELETE", `/containers/${created.Id}?force=true&v=true`);
231
+ } catch (cleanupError) {
232
+ if (
233
+ cacheMount &&
234
+ isCacheMountError(err) &&
235
+ !(cleanupError instanceof DockerApiError && cleanupError.statusCode === 404)
236
+ )
237
+ throw new Error(
238
+ `${String(err)}; failed-start cleanup failed: ${String(cleanupError)}`,
239
+ );
240
+ }
241
+ throw err;
173
242
  }
174
- await dockerApi(target, "POST", `/images/create?${pullParams}`, undefined, true);
175
- created = await dockerApi(target, "POST", path, body);
176
- }
243
+ return created.Id;
244
+ };
245
+ let externalId: string;
177
246
  try {
178
- await dockerApi(target, "POST", `/containers/${created.Id}/start`);
247
+ externalId = await createAndStart();
179
248
  } catch (err) {
180
- await dockerApi(target, "DELETE", `/containers/${created.Id}?force=true&v=true`).catch(
181
- () => {},
182
- );
183
- throw err;
249
+ if (!cacheMount || !isCacheMountError(err)) throw err;
250
+ cacheWarning = redactWithEnv(scrubSecrets(String(err)), req.env);
251
+ delete body.HostConfig.Mounts;
252
+ externalId = await createAndStart();
184
253
  }
185
- return { kind: "docker", externalId: created.Id };
254
+ return {
255
+ kind: "docker",
256
+ externalId,
257
+ ...(cacheWarning ? { meta: { cacheWarning } } : {}),
258
+ };
186
259
  } catch (err) {
187
260
  throw new Error(redactWithEnv(String(err), req.env));
188
261
  }
189
262
  }
190
263
 
264
+ async releaseCache(target: RuntimeTargetConfig, cache: RuntimeCacheReference): Promise<void> {
265
+ const path = `/volumes/${encodeURIComponent(cache.key)}`;
266
+ try {
267
+ const volume = await dockerApi<CacheVolume>(target, "GET", path);
268
+ assertCacheOwner(volume, cache);
269
+ // A volume still attached to any container must remain intact (Docker returns 409).
270
+ await dockerApi(target, "DELETE", path);
271
+ } catch (error) {
272
+ if (!(error instanceof DockerApiError && error.statusCode === 404)) throw error;
273
+ }
274
+ }
275
+
191
276
  async terminate(target: RuntimeTargetConfig, handle: RuntimeHandle): Promise<void> {
192
277
  try {
193
278
  // v=true also removes the container's anonymous volumes (the worker image declares
@@ -19,6 +19,8 @@ export interface RuntimeProviderTraits {
19
19
  ephemeralDisk: boolean;
20
20
  /** Provider can pause and resume a runtime. */
21
21
  supportsPause: boolean;
22
+ /** Can mount a persistent cache shared by all runtimes of one pool. */
23
+ persistentCache?: boolean;
22
24
  /** Provider hard cap on runtime lifetime, if any. */
23
25
  maxTtlSec?: number;
24
26
  /** Continuous runtime limit for the configured infrastructure tier. */
@@ -45,11 +47,23 @@ export interface ProvisionRequest {
45
47
  /** Worker env: MCP_BASE_URL, AGENT_ID, API key or scoped token, pool env. */
46
48
  env: Record<string, string>;
47
49
  resources?: { cpu?: number; memoryMb?: number };
50
+ /** Provider-neutral cache identity, allocated by the API rather than the operator. */
51
+ cache?: { key: string; mountPath: string };
48
52
  ttlSec?: number;
49
53
  /** Ownership labels, used by list() to find runtimes this swarm started. */
50
54
  labels: Record<string, string>;
51
55
  }
52
56
 
57
+ export interface RuntimeCacheReference {
58
+ key: string;
59
+ poolId: string;
60
+ ownerId: string;
61
+ }
62
+
63
+ export function poolCacheKey(poolId: string, generation?: string): string {
64
+ return `swarm-cache-${poolId}${generation ? `-${generation}` : ""}`;
65
+ }
66
+
53
67
  export interface RuntimeHandle {
54
68
  kind: RuntimeProviderKind;
55
69
  /** Container id, sandbox id, or pod name. */
@@ -74,6 +88,8 @@ export interface RuntimeProvider {
74
88
  status(target: RuntimeTargetConfig, handle: RuntimeHandle): Promise<RuntimeStatus>;
75
89
  /** Every runtime this swarm owns on the target, found by labels. Used to reap orphans. */
76
90
  list(target: RuntimeTargetConfig, ownerId: string): Promise<RuntimeHandle[]>;
91
+ /** Remove an owned pool cache after its runtimes stop; never force detach. */
92
+ releaseCache?(target: RuntimeTargetConfig, cache: RuntimeCacheReference): Promise<void>;
77
93
  renew?(
78
94
  target: RuntimeTargetConfig,
79
95
  handle: RuntimeHandle,
@@ -44,6 +44,14 @@ type AgentTaskConfig = {
44
44
  parentTaskId?: string;
45
45
  requestedByUserId?: string;
46
46
  outputSchema?: Record<string, unknown>;
47
+ /**
48
+ * Dispatch even when the follow-up re-delegation guard blocks this step.
49
+ * The guard applies only to a run launched from a follow-up task in a Slack
50
+ * thread, and decides once for the whole run at its first new step: it
51
+ * blocks a repeat of work already completed in the thread, or a run past the
52
+ * thread's dispatch cap. A blocked run's later new steps are blocked too.
53
+ */
54
+ allowRedelegation?: boolean;
47
55
  /**
48
56
  * Block until the dispatched task reaches a terminal status and journal
49
57
  * its real output. Defaults to `true` — this is the natural semantic for
@@ -63,6 +63,8 @@ declare module "swarm-sdk" {
63
63
  parentTaskId?: string;
64
64
  requestedByUserId?: string;
65
65
  outputSchema?: Record<string, unknown>;
66
+ /** Dispatch even when the follow-up re-delegation guard blocks this step (a run launched from a follow-up task in a Slack thread, checked at its first new step). */
67
+ allowRedelegation?: boolean;
66
68
  /** Wait for the dispatched task to reach a terminal status before resolving. Default: true. */
67
69
  waitForCompletion?: boolean;
68
70
  /** Max ms to wait for a terminal status before throwing. Default: 2h. Only used when waitForCompletion is true. */
@@ -760,6 +762,26 @@ declare module "swarm-sdk" {
760
762
  label: string,
761
763
  config: { prompt: string; model?: string; schema?: Record<string, unknown> },
762
764
  ): Promise<unknown>;
765
+ /**
766
+ * Dispatch a task and journal its result under `label`.
767
+ *
768
+ * Resolves to the task's OUTPUT on success: the stored output string (JSON
769
+ * text under `outputSchema`, so parse it yourself), or `null` when the task
770
+ * stored none. It is never a task record: on a string, `result.taskId` and
771
+ * `result.status` are undefined; on `null`, reading them throws a TypeError.
772
+ * Other shapes:
773
+ * - `failOnTaskFailure: false` and the task ends failed/cancelled/superseded:
774
+ * `{ taskId, status: "failed", error }`.
775
+ * - `waitForCompletion: false`: the first call still waits up to ~30s while
776
+ * the server polls the task. Done in that window: the output, as on
777
+ * success. Failed or cancelled in that window: throws, or
778
+ * `{ taskId, status: "failed", error }` with `failOnTaskFailure: false`.
779
+ * Still running: `{ taskId, status }`.
780
+ *
781
+ * To get a step's task id, query by its context key:
782
+ * `SELECT id, status, output FROM agent_tasks WHERE contextKey = 'script-run:<runId>:<label>'
783
+ * AND taskType = 'script-run-step' ORDER BY createdAt DESC, rowid DESC LIMIT 1`.
784
+ */
763
785
  agentTask(label: string, config: AgentTaskStepConfig): Promise<unknown>;
764
786
  swarmScript(
765
787
  label: string,
@@ -45,6 +45,8 @@ declare module "swarm-sdk" {
45
45
  parentTaskId?: string;
46
46
  requestedByUserId?: string;
47
47
  outputSchema?: Record<string, unknown>;
48
+ /** Dispatch even when the follow-up re-delegation guard blocks this step (a run launched from a follow-up task in a Slack thread, checked at its first new step). */
49
+ allowRedelegation?: boolean;
48
50
  /** Wait for the dispatched task to reach a terminal status before resolving. Default: true. */
49
51
  waitForCompletion?: boolean;
50
52
  /** Max ms to wait for a terminal status before throwing. Default: 2h. Only used when waitForCompletion is true. */
@@ -742,6 +744,26 @@ declare module "swarm-sdk" {
742
744
  label: string,
743
745
  config: { prompt: string; model?: string; schema?: Record<string, unknown> },
744
746
  ): Promise<unknown>;
747
+ /**
748
+ * Dispatch a task and journal its result under `label`.
749
+ *
750
+ * Resolves to the task's OUTPUT on success: the stored output string (JSON
751
+ * text under `outputSchema`, so parse it yourself), or `null` when the task
752
+ * stored none. It is never a task record: on a string, `result.taskId` and
753
+ * `result.status` are undefined; on `null`, reading them throws a TypeError.
754
+ * Other shapes:
755
+ * - `failOnTaskFailure: false` and the task ends failed/cancelled/superseded:
756
+ * `{ taskId, status: "failed", error }`.
757
+ * - `waitForCompletion: false`: the first call still waits up to ~30s while
758
+ * the server polls the task. Done in that window: the output, as on
759
+ * success. Failed or cancelled in that window: throws, or
760
+ * `{ taskId, status: "failed", error }` with `failOnTaskFailure: false`.
761
+ * Still running: `{ taskId, status }`.
762
+ *
763
+ * To get a step's task id, query by its context key:
764
+ * `SELECT id, status, output FROM agent_tasks WHERE contextKey = 'script-run:<runId>:<label>'
765
+ * AND taskType = 'script-run-step' ORDER BY createdAt DESC, rowid DESC LIMIT 1`.
766
+ */
745
767
  agentTask(label: string, config: AgentTaskStepConfig): Promise<unknown>;
746
768
  swarmScript(
747
769
  label: string,
@@ -130,6 +130,7 @@ export function createUserServer(user: User): McpServer {
130
130
  offerMode: false,
131
131
  leadOnly: false,
132
132
  allowDuplicate: false,
133
+ allowRedelegation: false,
133
134
  overrideSlackContext: false,
134
135
  });
135
136
  });
@@ -2,6 +2,7 @@ import type { App } from "@slack/bolt";
2
2
  import { getAllAgents, getAllTasks } from "../be/db";
3
3
  import { isEnvFlagEnabled } from "../utils/env-flag";
4
4
  import { EXTERNAL_CHANNEL_BLOCKED_REPLY, shouldBlockChannel } from "./external-channel";
5
+ import { isSlackLeaveThreadDisabled } from "./left-threads";
5
6
 
6
7
  export function registerCommandHandler(app: App): void {
7
8
  app.command("/agent-swarm-status", async ({ ack, respond, command, client }) => {
@@ -88,9 +89,12 @@ export function registerCommandHandler(app: App): void {
88
89
  • Follow-up tasks auto-depend on the active task in the thread for natural sequencing`);
89
90
  }
90
91
 
92
+ const leaveLine = isSlackLeaveThreadDisabled()
93
+ ? ""
94
+ : "\n• `!leave` in a thread - Make the swarm ignore that thread until someone @mentions it";
91
95
  sections.push(`*Commands:*
92
96
  • \`/agent-swarm-status\` - Show all agents and their current status
93
- • \`/agent-swarm-help\` - Show this help message`);
97
+ • \`/agent-swarm-help\` - Show this help message${leaveLine}`);
94
98
 
95
99
  const blocks = [
96
100
  {
@@ -52,7 +52,13 @@ import {
52
52
  leaveExternalChannel,
53
53
  shouldBlockChannel,
54
54
  } from "./external-channel";
55
- import { shouldSkipLeftSlackThread } from "./left-threads";
55
+ import {
56
+ isSlackLeaveThreadCommand,
57
+ isSlackLeaveThreadDisabled,
58
+ leaveSlackThread,
59
+ SLACK_LEFT_THREAD_CONFIRMATION,
60
+ shouldSkipLeftSlackThread,
61
+ } from "./left-threads";
56
62
  import {
57
63
  collectSlackMessageFiles,
58
64
  extractSlackMessageText,
@@ -491,7 +497,36 @@ export function registerMessageHandler(app: App): void {
491
497
  // Check if bot was mentioned (in original text only)
492
498
  const botMentioned = !!cachedBotUserId && !!msg.text?.includes(`<@${cachedBotUserId}>`);
493
499
 
494
- // A thread the swarm left (slack-leave-thread) stays silent until someone
500
+ // `!leave` in a thread the swarm is in: a human makes the swarm leave it,
501
+ // in code, with no workflow event, buffering, task, or model call.
502
+ // Any task bound to the thread counts, assigned or not, as does a thread
503
+ // the swarm started. Checked before the left-thread gate so `@bot !leave`
504
+ // leaves rather than rejoining. The confirmation is ephemeral, to the
505
+ // sender only.
506
+ if (
507
+ msg.thread_ts &&
508
+ !isSlackLeaveThreadDisabled() &&
509
+ isSlackLeaveThreadCommand(msg.text, cachedBotUserId) &&
510
+ ((await getMostRecentTaskInThread(msg.channel, msg.thread_ts)) !== null ||
511
+ (await wasThreadStartedBySwarm(client, msg.channel, msg.thread_ts)))
512
+ ) {
513
+ noteSlackInboundSideEffect("thread_leave");
514
+ await leaveSlackThread(msg.channel, msg.thread_ts, { slackUserId: msg.user });
515
+ console.log(`[Slack] Left thread ${msg.channel}/${msg.thread_ts}: !leave from ${msg.user}`);
516
+ try {
517
+ await client.chat.postEphemeral({
518
+ channel: msg.channel,
519
+ user: msg.user,
520
+ thread_ts: msg.thread_ts,
521
+ text: SLACK_LEFT_THREAD_CONFIRMATION,
522
+ });
523
+ } catch (error) {
524
+ console.error("[Slack] Failed to confirm !leave:", error);
525
+ }
526
+ return;
527
+ }
528
+
529
+ // A thread the swarm left (slack-leave-thread or `!leave`) stays silent until someone
495
530
  // @mentions the bot. Checked before the workflow event, buffering, routing,
496
531
  // or any AI call.
497
532
  if (await shouldSkipLeftSlackThread(msg.channel, msg.thread_ts, botMentioned)) {