@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
package/src/be/db.ts CHANGED
@@ -313,7 +313,11 @@ export {
313
313
  getContextVersionHistory,
314
314
  getLatestContextVersion,
315
315
  } from "./db/context-versions";
316
- export { getScriptRunExecutionArgs } from "./db/script-runs";
316
+ export {
317
+ getScriptRunExecutionArgs,
318
+ getScriptRunFollowUpGuard,
319
+ setScriptRunFollowUpGuard,
320
+ } from "./db/script-runs";
317
321
  export {
318
322
  AssetKeyMovesBlockedError,
319
323
  assertAssetKeyMovesAllowed,
@@ -336,12 +340,14 @@ export {
336
340
  } from "./db/runtime";
337
341
 
338
342
  export {
343
+ countAgentDispatchesInThread,
339
344
  type ExistingTrackerContextWork,
340
345
  type ExistingTrackerContextWorkReason,
341
346
  findActiveTasksByVcs,
342
- findCompletedTaskInThread,
347
+ findCompletedTasksInThread,
343
348
  findExistingLinearTrackerContextWork,
344
349
  findRecentCancelledTaskInThread,
350
+ findRecentFailedTaskInThread,
345
351
  findTaskByGitHub,
346
352
  findTaskByVcs,
347
353
  getAgentCurrentTask,
@@ -6283,7 +6289,7 @@ export interface UpdateScheduledTaskData {
6283
6289
  description?: string;
6284
6290
  cronExpression?: string | null;
6285
6291
  intervalMs?: number | null;
6286
- taskTemplate?: string;
6292
+ taskTemplate?: string | null;
6287
6293
  taskType?: string;
6288
6294
  tags?: string[];
6289
6295
  priority?: number;
@@ -13073,6 +13079,9 @@ export async function listRecentSessions(
13073
13079
  ): Promise<SessionListItem[] | SessionListItemSummary[]> {
13074
13080
  const query = sessionSummaryQuery(opts);
13075
13081
  const threadRoots = threadRootsSql();
13082
+ const order = (a: string) =>
13083
+ `${opts.triage === "later" ? `${a}.snoozeUntil ASC,` : ""}
13084
+ ${a}.lastActivityAt DESC, ${a}.rootCreatedAt DESC, ${a}.root_id DESC`;
13076
13085
  const rootRows = await getDbClient().query<
13077
13086
  AgentTaskRow & {
13078
13087
  __chainCount: number;
@@ -13084,13 +13093,14 @@ export async function listRecentSessions(
13084
13093
  __threadRoots: number;
13085
13094
  }
13086
13095
  >(
13096
+ // Page first on the summaries, then read agent_tasks for the page roots only.
13087
13097
  `SELECT r.*, a.chainCount AS __chainCount, a.lastActivityAt AS __lastActivityAt,
13088
13098
  a.latestStatus AS __latestStatus, a.readState AS __readState,
13089
13099
  a.snoozeUntil AS __snoozeUntil, a.needsYou AS __needsYou,
13090
13100
  ${threadRoots.sql} AS __threadRoots
13091
- FROM (${query.sql}) a CROSS JOIN agent_tasks r ON r.id = a.root_id
13092
- ORDER BY ${opts.triage === "later" ? "a.snoozeUntil ASC," : ""}
13093
- a.lastActivityAt DESC, r.createdAt DESC, r.id DESC LIMIT ? OFFSET ?`,
13101
+ FROM (SELECT * FROM (${query.sql}) page ORDER BY ${order("page")} LIMIT ? OFFSET ?) a
13102
+ CROSS JOIN agent_tasks r ON r.id = a.root_id
13103
+ ORDER BY ${order("a")}`,
13094
13104
  [...threadRoots.params, ...query.params, opts.limit ?? 25, opts.offset ?? 0],
13095
13105
  );
13096
13106
  const fields = await sessionPageFields(
@@ -14112,6 +14122,7 @@ type ScriptRunRow = {
14112
14122
  last_heartbeat_at: string | null;
14113
14123
  idempotencyKey: string | null;
14114
14124
  requestedByUserId: string | null;
14125
+ sourceTaskId: string | null;
14115
14126
  created_by: string | null;
14116
14127
  updated_by: string | null;
14117
14128
  };
@@ -14156,6 +14167,7 @@ function rowToScriptRun(row: ScriptRunRow): ScriptRun {
14156
14167
  lastHeartbeatAt: row.last_heartbeat_at ?? undefined,
14157
14168
  idempotencyKey: row.idempotencyKey ?? undefined,
14158
14169
  requestedByUserId: row.requestedByUserId ?? undefined,
14170
+ sourceTaskId: row.sourceTaskId ?? undefined,
14159
14171
  };
14160
14172
  }
14161
14173
 
@@ -14184,6 +14196,7 @@ export async function createScriptRun(data: {
14184
14196
  scriptName?: string;
14185
14197
  idempotencyKey?: string;
14186
14198
  requestedByUserId?: string;
14199
+ sourceTaskId?: string;
14187
14200
  createdBy?: string;
14188
14201
  updatedBy?: string;
14189
14202
  }): Promise<{ run: ScriptRun; existing: boolean }> {
@@ -14198,8 +14211,9 @@ export async function createScriptRun(data: {
14198
14211
 
14199
14212
  const row = await client.get<ScriptRunRow>(
14200
14213
  `INSERT INTO script_runs
14201
- (id, agentId, scriptName, source, args, idempotencyKey, requestedByUserId, created_by, updated_by)
14202
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
14214
+ (id, agentId, scriptName, source, args, idempotencyKey, requestedByUserId, sourceTaskId,
14215
+ created_by, updated_by)
14216
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
14203
14217
  RETURNING *`,
14204
14218
  [
14205
14219
  data.id,
@@ -14212,6 +14226,7 @@ export async function createScriptRun(data: {
14212
14226
  sealJson(data.args ?? null),
14213
14227
  data.idempotencyKey ?? null,
14214
14228
  data.requestedByUserId ?? null,
14229
+ data.sourceTaskId ?? null,
14215
14230
  data.createdBy ?? null,
14216
14231
  data.updatedBy ?? data.createdBy ?? null,
14217
14232
  ],
@@ -45,14 +45,18 @@ function armFetchLimit(limit: number): number {
45
45
  // lookup against it is a full table scan per outer row. The old backfill used
46
46
  // `WHERE NOT EXISTS (SELECT 1 FROM memory_fts f WHERE f.memory_id = m.id)`,
47
47
  // which is N x N: at ~18k memories it held the main connection for minutes at
48
- // boot. Both statements below use a non-correlated `NOT IN (subquery)`, which
49
- // SQLite evaluates once into an ephemeral index, so each is one pass over each
50
- // table. `memory_id IS NOT NULL` keeps a stray NULL row from turning every
51
- // NOT IN into NULL (which would insert nothing).
52
- export const FTS_DELETE_EXTRA_SQL = `DELETE FROM memory_fts
53
- WHERE memory_id NOT IN (SELECT id FROM agent_memory)`;
54
- export const FTS_MISSING_IDS_SQL = `SELECT m.id FROM agent_memory m
55
- WHERE m.id NOT IN (SELECT memory_id FROM memory_fts WHERE memory_id IS NOT NULL)`;
48
+ // boot. The diff below reads each table once, in rowid batches (FTS5 seeks a
49
+ // rowid range), and yields the event loop between batches: one whole-table
50
+ // statement held the loop for seconds on a large DB. A memory_fts row is an
51
+ // orphan when its memory is gone; that check is a lookup on the agent_memory
52
+ // primary key. A NULL memory_id is never an orphan, as with the old NOT IN.
53
+ export const FTS_SCAN_BATCH_SQL = `SELECT rowid AS rowid, memory_id AS memoryId,
54
+ memory_id IS NOT NULL
55
+ AND NOT EXISTS (SELECT 1 FROM agent_memory m WHERE m.id = memory_fts.memory_id) AS orphan
56
+ FROM memory_fts WHERE rowid > ? ORDER BY rowid LIMIT ?`;
57
+ export const MEMORY_ID_BATCH_SQL = `SELECT rowid AS rowid, id FROM agent_memory
58
+ WHERE rowid > ? ORDER BY rowid LIMIT ?`;
59
+ const FTS_SCAN_BATCH_SIZE = 1000;
56
60
  const FTS_POPULATE_BATCH_SIZE = 500;
57
61
 
58
62
  export type AgentMemoryRow = {
@@ -251,12 +255,45 @@ export class SqliteMemoryStore implements MemoryStore {
251
255
  private async populateFtsTable(): Promise<void> {
252
256
  const client = getDbClient();
253
257
  const startedAt = performance.now();
254
- const deletedExtra = await client.run(FTS_DELETE_EXTRA_SQL);
255
- if (deletedExtra.changes > 0) {
256
- console.warn(`[memory-fts] removed_extra_rows count=${deletedExtra.changes}`);
258
+ const yieldLoop = () => new Promise<void>((resolve) => setImmediate(resolve));
259
+
260
+ // Drop orphan rows and collect the memory ids the index holds.
261
+ const indexed = new Set<string>();
262
+ let removed = 0;
263
+ for (let after = 0; ; ) {
264
+ const rows = await client.query<{ rowid: number; memoryId: string | null; orphan: number }>(
265
+ FTS_SCAN_BATCH_SQL,
266
+ [after, FTS_SCAN_BATCH_SIZE],
267
+ );
268
+ if (rows.length === 0) break;
269
+ after = rows[rows.length - 1]!.rowid;
270
+ const orphans = rows.filter((row) => row.orphan).map((row) => row.rowid);
271
+ if (orphans.length > 0) {
272
+ await client.run(
273
+ `DELETE FROM memory_fts WHERE rowid IN (${orphans.map(() => "?").join(",")})`,
274
+ orphans,
275
+ );
276
+ removed += orphans.length;
277
+ }
278
+ for (const row of rows) if (!row.orphan && row.memoryId) indexed.add(row.memoryId);
279
+ await yieldLoop();
280
+ }
281
+ if (removed > 0) {
282
+ console.warn(`[memory-fts] removed_extra_rows count=${removed}`);
283
+ }
284
+
285
+ const missing: { id: string }[] = [];
286
+ for (let after = 0; ; ) {
287
+ const rows = await client.query<{ rowid: number; id: string }>(MEMORY_ID_BATCH_SQL, [
288
+ after,
289
+ FTS_SCAN_BATCH_SIZE,
290
+ ]);
291
+ if (rows.length === 0) break;
292
+ after = rows[rows.length - 1]!.rowid;
293
+ for (const row of rows) if (!indexed.has(row.id)) missing.push(row);
294
+ await yieldLoop();
257
295
  }
258
296
 
259
- const missing = await client.query<{ id: string }>(FTS_MISSING_IDS_SQL);
260
297
  for (let i = 0; i < missing.length; i += FTS_POPULATE_BATCH_SIZE) {
261
298
  const ids = missing.slice(i, i + FTS_POPULATE_BATCH_SIZE).map((row) => row.id);
262
299
  const placeholders = ids.map(() => "?").join(",");
@@ -0,0 +1,111 @@
1
+ -- What the session read path needs from session_summaries (migration 253):
2
+ -- the key and source indexes its filters use, and openTaskCount, the number
3
+ -- of chain tasks that are not finished (status outside completed, failed,
4
+ -- cancelled, superseded).
5
+ --
6
+ -- Auto-Done (src/be/db/session-tree.ts) moves a quiet Inbox session to Done
7
+ -- only when every chain task is finished. The session read path now reads
8
+ -- session_summaries instead of walking the chain, so the summary carries the
9
+ -- count. Same consistency rules as migration 253:
10
+ -- - Inserts add the new task's count incrementally.
11
+ -- - A status change applies its +1 or -1 in a BEFORE trigger. An AFTER
12
+ -- trigger of the same statement that recomputes the chain (a rewound
13
+ -- lastUpdatedAt, a requester or source edit) then overwrites it with the
14
+ -- exact count, so the delta is never applied on top of a recompute.
15
+ -- - session_summary_recompute_apply recomputes it with the other aggregates.
16
+
17
+ ALTER TABLE session_summaries ADD COLUMN openTaskCount INTEGER NOT NULL DEFAULT 0;
18
+
19
+ -- The group (key range) and source filters of the list and count. Activity
20
+ -- writes do not touch either column, so neither index costs the hot path.
21
+ CREATE INDEX idx_session_summaries_key ON session_summaries("key");
22
+ CREATE INDEX idx_session_summaries_source ON session_summaries(source);
23
+
24
+ DROP TRIGGER session_summary_recompute_apply;
25
+ CREATE TRIGGER session_summary_recompute_apply
26
+ INSTEAD OF INSERT ON session_summary_recompute
27
+ BEGIN
28
+ UPDATE session_summaries SET
29
+ chainCount = (SELECT COUNT(*) FROM session_members m WHERE m.rootId = NEW.rootId),
30
+ lastActivityAt = (SELECT MAX(t.lastUpdatedAt) FROM session_members m
31
+ JOIN agent_tasks t ON t.id = m.taskId WHERE m.rootId = NEW.rootId),
32
+ latestStatus = (SELECT t.status FROM session_members m
33
+ JOIN agent_tasks t ON t.id = m.taskId WHERE m.rootId = NEW.rootId
34
+ ORDER BY t.lastUpdatedAt DESC LIMIT 1),
35
+ pendingApprovalCount = (SELECT COUNT(*) FROM session_members m
36
+ JOIN approval_requests ar ON ar.sourceTaskId = m.taskId
37
+ WHERE m.rootId = NEW.rootId AND ar.status = 'pending'),
38
+ openTaskCount = (SELECT COUNT(*) FROM session_members m
39
+ JOIN agent_tasks t ON t.id = m.taskId WHERE m.rootId = NEW.rootId
40
+ AND t.status NOT IN ('completed', 'failed', 'cancelled', 'superseded')),
41
+ lastHumanAt = (SELECT MAX(at) FROM (
42
+ SELECT t.createdAt AS at FROM session_members m JOIN agent_tasks t ON t.id = m.taskId
43
+ WHERE m.rootId = NEW.rootId AND t.requestedByUserId IS NOT NULL
44
+ AND t.requestedByUserIdInherited = 0
45
+ AND COALESCE(t.source, '') NOT IN ('mcp', 'system', 'schedule', 'workflow')
46
+ UNION ALL
47
+ SELECT sm.created_at FROM session_members m
48
+ JOIN task_steering_messages sm ON sm.task_id = m.taskId
49
+ WHERE m.rootId = NEW.rootId AND sm.created_by_kind = 'user' AND sm.status != 'cancelled'
50
+ UNION ALL
51
+ SELECT ar.createdAt FROM session_members m
52
+ JOIN approval_requests ar ON ar.sourceTaskId = m.taskId
53
+ WHERE m.rootId = NEW.rootId AND ar.status = 'pending'))
54
+ WHERE rootId = NEW.rootId
55
+ AND EXISTS (SELECT 1 FROM agent_tasks WHERE id = NEW.rootId);
56
+ END;
57
+
58
+ DROP TRIGGER session_task_insert;
59
+ CREATE TRIGGER session_task_insert
60
+ AFTER INSERT ON agent_tasks
61
+ BEGIN
62
+ INSERT OR REPLACE INTO session_members (taskId, rootId)
63
+ SELECT NEW.id, NEW.id WHERE NEW.parentTaskId IS NULL
64
+ UNION ALL
65
+ SELECT NEW.id, m.rootId FROM session_members m WHERE m.taskId = NEW.parentTaskId;
66
+
67
+ INSERT OR REPLACE INTO session_summaries (rootId, createdAt, source, "key", requestedByUserId,
68
+ scheduleId, taskType, slackChannelId, contextKey, chainCount, lastActivityAt, latestStatus,
69
+ pendingApprovalCount, openTaskCount, lastHumanAt)
70
+ SELECT NEW.id, NEW.createdAt, NEW.source, NEW."key", NEW.requestedByUserId, NEW.scheduleId,
71
+ NEW.taskType, NEW.slackChannelId, NEW.contextKey, 1, NEW.lastUpdatedAt, NEW.status, 0,
72
+ NEW.status NOT IN ('completed', 'failed', 'cancelled', 'superseded'),
73
+ CASE WHEN NEW.requestedByUserId IS NOT NULL AND NEW.requestedByUserIdInherited = 0
74
+ AND COALESCE(NEW.source, '') NOT IN ('mcp', 'system', 'schedule', 'workflow')
75
+ THEN NEW.createdAt END
76
+ WHERE NEW.parentTaskId IS NULL;
77
+
78
+ UPDATE session_summaries SET
79
+ chainCount = chainCount + 1,
80
+ openTaskCount = openTaskCount
81
+ + (NEW.status NOT IN ('completed', 'failed', 'cancelled', 'superseded')),
82
+ latestStatus = CASE WHEN NEW.lastUpdatedAt >= lastActivityAt THEN NEW.status ELSE latestStatus END,
83
+ lastActivityAt = MAX(lastActivityAt, NEW.lastUpdatedAt),
84
+ lastHumanAt = CASE WHEN NEW.requestedByUserId IS NOT NULL AND NEW.requestedByUserIdInherited = 0
85
+ AND COALESCE(NEW.source, '') NOT IN ('mcp', 'system', 'schedule', 'workflow')
86
+ AND (lastHumanAt IS NULL OR NEW.createdAt > lastHumanAt)
87
+ THEN NEW.createdAt ELSE lastHumanAt END
88
+ WHERE NEW.parentTaskId IS NOT NULL
89
+ AND rootId = (SELECT rootId FROM session_members WHERE taskId = NEW.id);
90
+ END;
91
+
92
+ -- BEFORE, so an AFTER recompute in the same statement has the last word.
93
+ CREATE TRIGGER session_task_open
94
+ BEFORE UPDATE OF status ON agent_tasks
95
+ WHEN (OLD.status NOT IN ('completed', 'failed', 'cancelled', 'superseded'))
96
+ IS NOT (NEW.status NOT IN ('completed', 'failed', 'cancelled', 'superseded'))
97
+ BEGIN
98
+ UPDATE session_summaries SET
99
+ openTaskCount = openTaskCount
100
+ + CASE WHEN NEW.status NOT IN ('completed', 'failed', 'cancelled', 'superseded') THEN 1 ELSE -1 END
101
+ WHERE rootId = (SELECT rootId FROM session_members WHERE taskId = NEW.id);
102
+ END;
103
+
104
+ -- Backfill: one pass over the unfinished tasks.
105
+ UPDATE session_summaries SET openTaskCount = unfinished.n
106
+ FROM (
107
+ SELECT m.rootId, COUNT(*) AS n FROM agent_tasks t JOIN session_members m ON m.taskId = t.id
108
+ WHERE t.status NOT IN ('completed', 'failed', 'cancelled', 'superseded')
109
+ GROUP BY m.rootId
110
+ ) AS unfinished
111
+ WHERE unfinished.rootId = session_summaries.rootId;
@@ -0,0 +1,20 @@
1
+ -- NULL preserves the ephemeral-only behavior of existing pools.
2
+ ALTER TABLE runtime_pools ADD COLUMN cache TEXT
3
+ CHECK (cache IS NULL OR json_valid(cache));
4
+
5
+ -- Retain release intent across API restarts and pool deletion.
6
+ CREATE TABLE runtime_cache_releases (
7
+ id TEXT PRIMARY KEY,
8
+ pool_id TEXT NOT NULL,
9
+ controller_id TEXT NOT NULL REFERENCES runtime_controllers(id) ON DELETE RESTRICT,
10
+ owner_id TEXT NOT NULL,
11
+ cache_key TEXT NOT NULL,
12
+ runtime_ids TEXT NOT NULL CHECK (json_valid(runtime_ids) AND json_type(runtime_ids) = 'array'),
13
+ state TEXT NOT NULL DEFAULT 'pending' CHECK (state IN ('pending', 'deleting')),
14
+ last_error TEXT,
15
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
16
+ updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
17
+ created_by TEXT REFERENCES users(id) ON DELETE SET NULL,
18
+ updated_by TEXT REFERENCES users(id) ON DELETE SET NULL,
19
+ UNIQUE(controller_id, owner_id, cache_key)
20
+ );
@@ -0,0 +1,18 @@
1
+ -- Covering indexes for the session visibility walk (readableSessionCondition
2
+ -- in src/be/db/tasks/read.ts). Every task list and count a dashboard user
3
+ -- loads computes the set of tasks in sessions the user cannot read: roots
4
+ -- owned by someone else, plus all their descendants.
5
+ --
6
+ -- agent_tasks rows carry large `task` text, and parentTaskId, scheduleId and
7
+ -- requestedByUserId sit after it, in the overflow pages. Without these
8
+ -- indexes the root step read every unscheduled task row and the recursive
9
+ -- step read the row of every descendant. With them, both steps stay in the
10
+ -- index. Production has no sqlite_stat1; the planner picks both without it.
11
+
12
+ -- Root step: `scheduleId IS NULL` plus the root, owner and id columns.
13
+ CREATE INDEX IF NOT EXISTS idx_agent_tasks_session_owner
14
+ ON agent_tasks(scheduleId, parentTaskId, requestedByUserId, id);
15
+
16
+ -- Recursive step: the children of a hidden task, by id.
17
+ CREATE INDEX IF NOT EXISTS idx_agent_tasks_parentTaskId_id
18
+ ON agent_tasks(parentTaskId, id);
@@ -0,0 +1,11 @@
1
+ -- Covering index for the default task list count (getTasksCount in
2
+ -- src/be/db/tasks/read.ts). The task list hides heartbeat tasks by default:
3
+ --
4
+ -- IFNULL(taskType, '') NOT IN (...) AND tags NOT LIKE '%"heartbeat"%'
5
+ --
6
+ -- A NOT IN and a NOT LIKE cannot seek, so the count scans. taskType and tags
7
+ -- sit after the large `task` text, in the overflow pages, so the scan read
8
+ -- every task body. This index holds the two filter columns and `id` (the
9
+ -- session visibility filter), so the count scans the index only.
10
+ CREATE INDEX IF NOT EXISTS idx_agent_tasks_list_filter
11
+ ON agent_tasks(taskType, tags, id);
@@ -0,0 +1,9 @@
1
+ -- Covering index for the Slack render-v2 watcher sweep (getSlackTreeMessages
2
+ -- in src/be/db.ts), which runs on every watcher tick, even when idle. For
3
+ -- each stored tree it tests the thread's tasks by createdAt, status, source
4
+ -- and lastUpdatedAt. Those columns sit after the large `task` text, in the
5
+ -- overflow pages, so each test read whole task rows. This index holds them,
6
+ -- so the tests stay in the index. Partial: only Slack tasks carry a channel.
7
+ CREATE INDEX IF NOT EXISTS idx_agent_tasks_slack_thread_cover
8
+ ON agent_tasks(slackChannelId, slackThreadTs, createdAt, status, source, lastUpdatedAt, id)
9
+ WHERE slackChannelId IS NOT NULL;
@@ -0,0 +1,11 @@
1
+ -- The task whose session launched a durable script run (the caller's
2
+ -- X-Source-Task-Id, kept only when that task belongs to the launching agent).
3
+ -- The follow-up re-delegation guard reads it when the run's ctx.step.agentTask
4
+ -- dispatches, the same way send-task reads the caller's source task.
5
+ ALTER TABLE script_runs ADD COLUMN sourceTaskId TEXT;
6
+
7
+ -- The run's follow-up re-delegation guard decision, taken once for the whole
8
+ -- run at its first new step: NULL (not decided yet), 'passed', or the rule that
9
+ -- blocked it ('same-work' or 'loop-cap'). Every later step follows it, so
10
+ -- parallel steps of one run are dispatched all or none.
11
+ ALTER TABLE script_runs ADD COLUMN followUpGuard TEXT;
@@ -7,7 +7,8 @@
7
7
  * - a `latest:<provider>/<family>` alias that is valid grammar and resolves to a catalog model;
8
8
  * - a catalog id, bare (`claude-opus-5-5`, `gpt-5.6-sol`) or provider-qualified
9
9
  * (`openrouter/deepseek/deepseek-v4.1-flash`, `amazon-bedrock/...`);
10
- * - a Claude CLI shortname (`opus`, `sonnet`, ...), which the CLI resolves itself.
10
+ * - a Claude CLI shortname (`opus`, `sonnet`, ...), which the CLI resolves itself;
11
+ * - a ChatGPT subscription id `openai-codex/<id>` whose `<id>` is in the `openai` section.
11
12
  *
12
13
  * Escape hatch: `allowCustomModel: true` on the call (tools, REST bodies, workflow node
13
14
  * config, the agent runtime PATCH). Custom ids skip the check and are stored as given, for
@@ -23,6 +24,7 @@
23
24
  */
24
25
  import {
25
26
  buildClaudeShortnameMap,
27
+ chatgptSubscriptionModelId,
26
28
  type HarnessCatalogSections,
27
29
  harnessModelMismatch,
28
30
  isAlias,
@@ -55,6 +57,8 @@ function hasModel(catalog: CatalogSections, provider: string, modelId: string):
55
57
  export function isKnownCatalogModel(model: string, catalog: CatalogSections): boolean {
56
58
  const id = model.trim().replace(CONTEXT_SUFFIX_RE, "");
57
59
  if (!id) return false;
60
+ const subscriptionId = chatgptSubscriptionModelId(id);
61
+ if (subscriptionId) return hasModel(catalog, "openai", subscriptionId);
58
62
  const slash = id.indexOf("/");
59
63
  if (slash > 0) {
60
64
  const provider = id.slice(0, slash);
@@ -40,8 +40,9 @@ const ROUTING_PREFIXES_BY_PROVIDER: Record<PricingProvider, readonly string[]> =
40
40
  // openai, … — strip whichever proxy prefix the user picked.
41
41
  opencode: ["openrouter/", "github-copilot/"],
42
42
  // pi-mono can hit openrouter mirrors, the github-copilot proxy, or native
43
- // anthropic/openai/google providers.
44
- pi: ["openrouter/", "github-copilot/"],
43
+ // anthropic/openai/google providers. `openai-codex/<id>` is an OpenAI id
44
+ // served by the ChatGPT subscription pool, priced like codex on that pool.
45
+ pi: ["openrouter/", "github-copilot/", "openai-codex/"],
45
46
  // codex normally reports a bare id, but a user may set MODEL_OVERRIDE to a
46
47
  // prefixed form. Be forgiving on the lookup side.
47
48
  codex: ["openai/", "github-copilot/"],
@@ -69,6 +70,10 @@ const ROUTING_PREFIXES_BY_PROVIDER: Record<PricingProvider, readonly string[]> =
69
70
  // The Grok CLI reports a bare xAI id (`grok-4.6`) or `openrouter/<vendor>/<id>`
70
71
  // for an OpenRouter model; stripping the prefix lands it on the openrouter rows.
71
72
  grok: ["openrouter/", "xai/"],
73
+ // fx reports the swarm model string: `openrouter/<vendor>/<id>` (OpenRouter
74
+ // connection), `vercel/<vendor>/<id>` (AI Gateway) or a bare OpenAI id.
75
+ // Stripping the route prefix lands the first two on `<vendor>/<id>` rows.
76
+ fx: ["openrouter/", "vercel/"],
72
77
  };
73
78
 
74
79
  /** `gpt-5-nano-2025-08-07` -> `gpt-5-nano`. Anthropic's 8-digit dates (`-20251001`) are real catalog ids and are left alone. */
@@ -0,0 +1,109 @@
1
+ import type { ControllerCommands } from "../../runtime-providers/controller";
2
+ import { poolCacheKey, type RuntimeCacheReference } from "../../runtime-providers/types";
3
+ import type { RuntimePool } from "../../types";
4
+ import { scrubSecrets } from "../../utils/secret-scrubber";
5
+ import { getDbClient, getSwarmConfigs } from "../db";
6
+
7
+ /** The stored key is private API metadata; legacy caches retain their original name. */
8
+ export async function getPoolCache(poolId: string): Promise<{
9
+ key: string;
10
+ mountPath: string;
11
+ controllerId: string;
12
+ } | null> {
13
+ const row = await getDbClient().get<{ cache: string | null; controllerId: string }>(
14
+ "SELECT cache, json_extract(placements, '$[0]') AS controllerId FROM runtime_pools WHERE id = ?",
15
+ [poolId],
16
+ );
17
+ if (!row?.cache) return null;
18
+ const cache = JSON.parse(row.cache);
19
+ return {
20
+ key: cache.key ?? poolCacheKey(poolId),
21
+ mountPath: cache.mountPath,
22
+ controllerId: row.controllerId,
23
+ };
24
+ }
25
+
26
+ /** Call in the pool write transaction; no infrastructure calls occur here. */
27
+ export async function queuePoolCacheRelease(
28
+ pool: RuntimePool,
29
+ actor: string | null,
30
+ ): Promise<void> {
31
+ if (!pool.cache) return;
32
+ const owner = (await getSwarmConfigs({ scope: "global", key: "runtime_owner_id" }))[0];
33
+ // No runtime could have created an owned cache before the owner was allocated.
34
+ if (!owner) return;
35
+ const cache = (await getPoolCache(pool.id))!;
36
+ const runtimes = await getDbClient().query<{ id: string }>(
37
+ `SELECT id FROM runtime_instances WHERE pool_id = ?
38
+ AND (controller_id = ? OR controller_id IS NULL) AND status != 'offline'`,
39
+ [pool.id, pool.placements[0]!],
40
+ );
41
+ await getDbClient().run(
42
+ `INSERT INTO runtime_cache_releases
43
+ (id, pool_id, controller_id, owner_id, cache_key, runtime_ids, created_by, updated_by)
44
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(controller_id, owner_id, cache_key) DO NOTHING`,
45
+ [
46
+ crypto.randomUUID(),
47
+ pool.id,
48
+ pool.placements[0]!,
49
+ owner.value,
50
+ cache.key,
51
+ JSON.stringify(runtimes.map((r) => r.id)),
52
+ actor,
53
+ actor,
54
+ ],
55
+ );
56
+ }
57
+
58
+ interface CacheRelease extends RuntimeCacheReference {
59
+ id: string;
60
+ controllerId: string;
61
+ }
62
+
63
+ const RELEASE_LEASE_MS = 5 * 60_000;
64
+
65
+ /** Claims are atomic across reconcilers; all network calls are outside DB transactions. */
66
+ export async function reconcileCacheReleases(
67
+ commands: (id: string) => Promise<ControllerCommands>,
68
+ signal: AbortSignal,
69
+ now?: Date,
70
+ ): Promise<void> {
71
+ const jobs = await getDbClient().query<CacheRelease>(
72
+ `SELECT id, pool_id AS poolId, controller_id AS controllerId, owner_id AS ownerId,
73
+ cache_key AS key FROM runtime_cache_releases ORDER BY created_at`,
74
+ );
75
+ for (const job of jobs) {
76
+ if (signal.aborted) return;
77
+ const claimed = now ?? new Date();
78
+ const claimedAt = claimed.toISOString();
79
+ // Reclaim abandoned work after five minutes; generation keys fence late provider calls.
80
+ const reserved = await getDbClient().run(
81
+ `UPDATE runtime_cache_releases SET state = 'deleting', updated_at = ?
82
+ WHERE id = ? AND (state = 'pending' OR updated_at < ?) AND NOT EXISTS (
83
+ SELECT 1 FROM runtime_instances WHERE status != 'offline'
84
+ AND id IN (SELECT value FROM json_each(runtime_cache_releases.runtime_ids))
85
+ )`,
86
+ [claimedAt, job.id, new Date(claimed.getTime() - RELEASE_LEASE_MS).toISOString()],
87
+ );
88
+ if (!reserved.changes) continue;
89
+ try {
90
+ signal.throwIfAborted();
91
+ const controller = await commands(job.controllerId);
92
+ if (!controller.releaseCache)
93
+ throw new Error("Controller cannot release caches; upgrade it before cleanup");
94
+ await controller.releaseCache({ key: job.key, poolId: job.poolId, ownerId: job.ownerId });
95
+ await getDbClient().run(
96
+ "DELETE FROM runtime_cache_releases WHERE id = ? AND state = 'deleting' AND updated_at = ?",
97
+ [job.id, claimedAt],
98
+ );
99
+ } catch (error) {
100
+ const message = scrubSecrets(String(error));
101
+ await getDbClient().run(
102
+ `UPDATE runtime_cache_releases SET state = 'pending', last_error = ?, updated_at = ?
103
+ WHERE id = ? AND state = 'deleting' AND updated_at = ?`,
104
+ [message, new Date().toISOString(), job.id, claimedAt],
105
+ );
106
+ if (!signal.aborted) console.error("[runtime-pools] Cache cleanup deferred:", message);
107
+ }
108
+ }
109
+ }