@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
@@ -4,6 +4,7 @@ import {
4
4
  createRuntimeProvider,
5
5
  type RuntimeTargetConfig,
6
6
  } from "../../runtime-providers";
7
+ import { poolCacheKey } from "../../runtime-providers/types";
7
8
  import type { Agent, DeliveryMode, RuntimeController, RuntimePool } from "../../types";
8
9
  import {
9
10
  RuntimeControllerCreateSchema,
@@ -17,6 +18,7 @@ import { decryptSecret, encryptSecret, getEncryptionKey } from "../crypto";
17
18
  import { createAgent, getAgentById, getAllAgents, getDbClient, upsertSwarmConfig } from "../db";
18
19
  import { type AgentRow, rowToAgent } from "../db/agents";
19
20
  import { registerStoredSecret } from "../secret-registry";
21
+ import { getPoolCache, queuePoolCacheRelease } from "./cache-cleanup";
20
22
  import { RuntimePoolError } from "./errors";
21
23
  import { revokeControllerTokens } from "./tokens";
22
24
 
@@ -29,10 +31,11 @@ type ControllerRow = Omit<
29
31
  config: string;
30
32
  secretConfig: string | null;
31
33
  };
32
- type PoolRow = Omit<RuntimePool, "placements" | "env" | "resources" | "enabled"> & {
34
+ type PoolRow = Omit<RuntimePool, "placements" | "env" | "resources" | "cache" | "enabled"> & {
33
35
  placements: string;
34
36
  env: string;
35
37
  resources: string | null;
38
+ cache: string | null;
36
39
  enabled: number;
37
40
  };
38
41
  const controllerColumns = `id, name, kind, placement, config, secret_config AS secretConfig,
@@ -40,7 +43,7 @@ const controllerColumns = `id, name, kind, placement, config, secret_config AS s
40
43
  created_by AS createdBy, updated_by AS updatedBy, created_at AS createdAt, updated_at AS updatedAt`;
41
44
  const poolColumns = `id, name, kind, placements, image, lifecycle, idle_minutes AS idleMinutes,
42
45
  slots_per_runtime AS slotsPerRuntime, max_runtimes AS maxRuntimes, min_runtimes AS minRuntimes,
43
- delivery, env, resources, ttl_sec AS ttlSec, resolved_image AS resolvedImage, enabled,
46
+ delivery, env, resources, cache, ttl_sec AS ttlSec, resolved_image AS resolvedImage, enabled,
44
47
  consecutive_failures AS consecutiveFailures, circuit_open_until AS circuitOpenUntil,
45
48
  last_error AS lastError, last_error_at AS lastErrorAt, created_by AS createdBy,
46
49
  updated_by AS updatedBy, created_at AS createdAt, updated_at AS updatedAt`;
@@ -65,11 +68,13 @@ function rowToController(row: ControllerRow): RuntimeController {
65
68
  };
66
69
  }
67
70
  function rowToPool(row: PoolRow): RuntimePool {
71
+ const cache = row.cache ? JSON.parse(row.cache) : null;
68
72
  return {
69
73
  ...row,
70
74
  placements: JSON.parse(row.placements),
71
75
  env: JSON.parse(row.env),
72
76
  resources: row.resources ? JSON.parse(row.resources) : null,
77
+ cache: cache ? { mountPath: cache.mountPath } : null,
73
78
  enabled: row.enabled === 1,
74
79
  };
75
80
  }
@@ -121,6 +126,17 @@ export async function getRuntimeControllerTarget(
121
126
  E2B_MAX_CONTINUOUS_SEC: env.E2B_MAX_CONTINUOUS_SEC,
122
127
  RUNTIME_WORKER_API_URL: env.RUNTIME_WORKER_API_URL,
123
128
  PUBLIC_MCP_BASE_URL: env.PUBLIC_MCP_BASE_URL,
129
+ ...(controller.kind === "boxd" && controller.placement === "builtin"
130
+ ? {
131
+ BOXD_ORG: env.BOXD_ORG,
132
+ BOXD_BASE_URL: env.BOXD_BASE_URL,
133
+ BOXD_SNAPSHOT: env.BOXD_SNAPSHOT,
134
+ BOXD_DELETE_AFTER_SEC: env.BOXD_DELETE_AFTER_SEC,
135
+ BOXD_START_TIMEOUT_SEC: env.BOXD_START_TIMEOUT_SEC,
136
+ BOXD_STATUS_TIMEOUT_SEC: env.BOXD_STATUS_TIMEOUT_SEC,
137
+ BOXD_TEARDOWN_TIMEOUT_SEC: env.BOXD_TEARDOWN_TIMEOUT_SEC,
138
+ }
139
+ : {}),
124
140
  ...controller.config,
125
141
  ...secrets,
126
142
  };
@@ -234,11 +250,32 @@ export async function updateRuntimeController(
234
250
  )
235
251
  throw new RuntimePoolError("Controller name already exists", 409);
236
252
  const pools = await listRuntimePools();
253
+ const oldConfig: Record<string, string> = JSON.parse(row.config);
254
+ const configChanged =
255
+ Object.keys(data.config).length !== Object.keys(oldConfig).length ||
256
+ Object.entries(data.config).some(([key, value]) => oldConfig[key] !== value);
257
+ const secrets = patch.secretConfig !== undefined ? controllerSecrets(row) : {};
258
+ const secretsChanged = Object.entries(patch.secretConfig ?? {}).some(([key, value]) =>
259
+ value === null ? key in secrets : secrets[key] !== value,
260
+ );
261
+ if (
262
+ (data.kind !== row.kind ||
263
+ data.placement !== row.placement ||
264
+ configChanged ||
265
+ secretsChanged) &&
266
+ (pools.some((pool) => pool.cache && pool.placements.includes(id)) ||
267
+ (await tx.get("SELECT id FROM runtime_cache_releases WHERE controller_id = ? LIMIT 1", [
268
+ id,
269
+ ])))
270
+ )
271
+ throw new RuntimePoolError(
272
+ "Disable pool caches and wait for cleanup before changing controller configuration or placement",
273
+ 409,
274
+ );
237
275
  if (pools.some((pool) => pool.placements.includes(id) && pool.kind !== data.kind))
238
276
  throw new RuntimePoolError("Pool kind must equal controller kind");
239
277
  let encrypted = row.secretConfig;
240
278
  if (patch.secretConfig !== undefined) {
241
- const secrets = controllerSecrets(row);
242
279
  for (const [key, value] of Object.entries(patch.secretConfig)) {
243
280
  if (value === null) delete secrets[key];
244
281
  else secrets[key] = value;
@@ -272,6 +309,8 @@ export async function deleteRuntimeController(id: string): Promise<void> {
272
309
  if (!(await getRuntimeController(id))) throw new RuntimePoolError("Controller not found", 404);
273
310
  if ((await listRuntimePools()).some((pool) => pool.placements.includes(id)))
274
311
  throw new RuntimePoolError("Controller has runtime pools", 409);
312
+ if (await tx.get("SELECT id FROM runtime_cache_releases WHERE controller_id = ? LIMIT 1", [id]))
313
+ throw new RuntimePoolError("Controller has pending cache cleanup", 409);
275
314
  await revokeControllerTokens(id);
276
315
  await tx.run("DELETE FROM runtime_controllers WHERE id = ?", [id]);
277
316
  });
@@ -291,6 +330,9 @@ export async function getRuntimePool(id: string): Promise<RuntimePool | null> {
291
330
  }
292
331
  async function validatePool(data: z.output<typeof RuntimePoolCreateSchema>): Promise<void> {
293
332
  supportedKind(data.kind);
333
+ if (data.cache && !(await createRuntimeProvider(data.kind)).traits.persistentCache) {
334
+ throw new RuntimePoolError(`${data.kind} does not support persistent pool caches`);
335
+ }
294
336
  const controller = await getRuntimeController(data.placements[0]!);
295
337
  if (!controller) throw new RuntimePoolError("Controller not found");
296
338
  if (data.kind !== controller.kind)
@@ -316,7 +358,7 @@ export async function createRuntimePool(
316
358
  if (await tx.get("SELECT id FROM runtime_pools WHERE name = ? OR id = ?", [data.name, id]))
317
359
  throw new RuntimePoolError("Pool already exists", 409);
318
360
  await tx.run(
319
- `INSERT INTO runtime_pools (id, name, kind, placements, image, lifecycle, idle_minutes, slots_per_runtime, max_runtimes, min_runtimes, delivery, env, resources, ttl_sec, enabled, created_by, updated_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
361
+ `INSERT INTO runtime_pools (id, name, kind, placements, image, lifecycle, idle_minutes, slots_per_runtime, max_runtimes, min_runtimes, delivery, env, resources, cache, ttl_sec, enabled, created_by, updated_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
320
362
  [
321
363
  id,
322
364
  data.name,
@@ -331,6 +373,9 @@ export async function createRuntimePool(
331
373
  data.delivery,
332
374
  JSON.stringify(data.env),
333
375
  data.resources ? JSON.stringify(data.resources) : null,
376
+ data.cache
377
+ ? JSON.stringify({ ...data.cache, key: poolCacheKey(id, crypto.randomUUID()) })
378
+ : null,
334
379
  data.ttlSec,
335
380
  Number(data.enabled),
336
381
  actor,
@@ -364,8 +409,19 @@ export async function updateRuntimePool(
364
409
  await validatePool(data);
365
410
  if (await tx.get("SELECT id FROM runtime_pools WHERE name = ? AND id != ?", [data.name, id]))
366
411
  throw new RuntimePoolError("Pool name already exists", 409);
412
+ const cache = data.cache
413
+ ? {
414
+ ...data.cache,
415
+ key:
416
+ existing.cache && data.placements[0] === existing.placements[0]
417
+ ? (await getPoolCache(id))!.key
418
+ : poolCacheKey(id, crypto.randomUUID()),
419
+ }
420
+ : null;
421
+ if (existing.cache && (!data.cache || data.placements[0] !== existing.placements[0]))
422
+ await queuePoolCacheRelease(existing, actor);
367
423
  await tx.run(
368
- `UPDATE runtime_pools SET name = ?, kind = ?, placements = ?, image = ?, resolved_image = ?, lifecycle = ?, idle_minutes = ?, slots_per_runtime = ?, max_runtimes = ?, min_runtimes = ?, delivery = ?, env = ?, resources = ?, ttl_sec = ?, enabled = ?, updated_by = COALESCE(?, updated_by), updated_at = ? WHERE id = ?`,
424
+ `UPDATE runtime_pools SET name = ?, kind = ?, placements = ?, image = ?, resolved_image = ?, lifecycle = ?, idle_minutes = ?, slots_per_runtime = ?, max_runtimes = ?, min_runtimes = ?, delivery = ?, env = ?, resources = ?, cache = ?, ttl_sec = ?, enabled = ?, updated_by = COALESCE(?, updated_by), updated_at = ? WHERE id = ?`,
369
425
  [
370
426
  data.name,
371
427
  data.kind,
@@ -384,6 +440,7 @@ export async function updateRuntimePool(
384
440
  data.delivery,
385
441
  JSON.stringify(data.env),
386
442
  data.resources ? JSON.stringify(data.resources) : null,
443
+ cache ? JSON.stringify(cache) : null,
387
444
  data.ttlSec,
388
445
  Number(data.enabled),
389
446
  actor,
@@ -400,7 +457,8 @@ export async function deleteRuntimePool(
400
457
  actor: string | null = null,
401
458
  ): Promise<void> {
402
459
  await getDbClient().transaction(async (tx) => {
403
- if (!(await getRuntimePool(id))) throw new RuntimePoolError("Pool not found", 404);
460
+ const pool = await getRuntimePool(id);
461
+ if (!pool) throw new RuntimePoolError("Pool not found", 404);
404
462
  if (
405
463
  await tx.get(
406
464
  "SELECT id FROM runtime_instances WHERE pool_id = ? AND status != 'offline' LIMIT 1",
@@ -419,6 +477,7 @@ export async function deleteRuntimePool(
419
477
  [new Date().toISOString(), actor, id],
420
478
  );
421
479
  }
480
+ await queuePoolCacheRelease(pool, actor);
422
481
  await tx.run("DELETE FROM runtime_pools WHERE id = ?", [id]);
423
482
  });
424
483
  }
@@ -53,6 +53,7 @@ export class RemoteController implements ControllerCommands {
53
53
  readonly renew?: ControllerCommands["renew"];
54
54
  readonly pause?: ControllerCommands["pause"];
55
55
  readonly resume?: ControllerCommands["resume"];
56
+ readonly releaseCache?: ControllerCommands["releaseCache"];
56
57
 
57
58
  constructor(
58
59
  readonly id: string,
@@ -61,6 +62,10 @@ export class RemoteController implements ControllerCommands {
61
62
  readonly workerApiUrl?: string,
62
63
  private readonly timeouts: { commandTimeoutMs?: number; provisionTimeoutMs?: number } = {},
63
64
  ) {
65
+ if (traits.persistentCache)
66
+ this.releaseCache = async (cache) => {
67
+ await this.call({ op: "release-cache", payload: cache });
68
+ };
64
69
  if (traits.maxTtlSec)
65
70
  this.renew = async (handle, ttlSec) =>
66
71
  z
@@ -29,6 +29,7 @@ import {
29
29
  runtimeStaleThresholdMinutes,
30
30
  } from "../multi-runtime";
31
31
  import { mintRuntimeToken } from "../users";
32
+ import { getPoolCache, reconcileCacheReleases } from "./cache-cleanup";
32
33
  import {
33
34
  clearRuntimeOwnerIdCache,
34
35
  ensureRuntimeOwnerId,
@@ -245,6 +246,13 @@ export async function provisionRuntime(
245
246
  if (pool.lifecycle === "per-task") env.SWARM_EXIT_AFTER_TASKS = "1";
246
247
  if (templateId) env.TEMPLATE_ID = templateId;
247
248
  const ttlSec = pool.kind === "e2b" ? poolLeaseTtl(pool, controller) : undefined;
249
+ const currentPoolCache = await getPoolCache(pool.id);
250
+ const currentCache =
251
+ currentPoolCache?.controllerId === pool.placements[0] ? currentPoolCache : null;
252
+ const cache =
253
+ currentCache && controller.traits.persistentCache
254
+ ? { key: currentCache.key, mountPath: currentCache.mountPath }
255
+ : undefined;
248
256
  signal?.throwIfAborted();
249
257
  const handle = await controller.provision({
250
258
  runtimeId,
@@ -255,6 +263,7 @@ export async function provisionRuntime(
255
263
  ...(ttlSec === undefined ? {} : { ttlSec }),
256
264
  env,
257
265
  resources: pool.resources ?? undefined,
266
+ ...(cache ? { cache } : {}),
258
267
  labels: {
259
268
  [RUNTIME_LABELS.runtimeId]: runtimeId,
260
269
  [RUNTIME_LABELS.poolId]: pool.id,
@@ -267,6 +276,25 @@ export async function provisionRuntime(
267
276
  await markRuntimeTerminated(runtimeId, "Supervisor stopped during provision");
268
277
  return false;
269
278
  }
279
+ const cacheWarning = currentCache
280
+ ? !controller.traits.persistentCache
281
+ ? "Controller does not support persistent caches; runtime started without cache"
282
+ : handle.meta?.cacheWarning
283
+ : undefined;
284
+ // A cache is optional: surface degradation without counting a provision failure.
285
+ const cacheErrorPrefix = "Persistent cache: ";
286
+ const cacheError = cacheWarning ? cacheErrorPrefix + scrubSecrets(cacheWarning) : null;
287
+ await getDbClient().run(
288
+ `UPDATE runtime_pools SET last_error = ?, last_error_at = ?
289
+ WHERE id = ? AND (last_error IS NULL OR substr(last_error, 1, ?) = ?)`,
290
+ [
291
+ cacheError,
292
+ cacheError ? new Date().toISOString() : null,
293
+ pool.id,
294
+ cacheErrorPrefix.length,
295
+ cacheErrorPrefix,
296
+ ],
297
+ );
270
298
  await getDbClient().run(
271
299
  "UPDATE runtime_instances SET external_handle = ?, lease_expires_at = ?, created_at = COALESCE(?, created_at), updated_at = ? WHERE id = ?",
272
300
  [
@@ -886,11 +914,14 @@ export async function reconcileOnce(
886
914
  opts: { now?: Date; getControllerCommands?: typeof getControllerCommands } = {},
887
915
  ): Promise<void> {
888
916
  if (!isRuntimePoolsEnabled() || !isMultiRuntimeEnabled() || inFlight) return;
889
- tickAbort = new AbortController();
917
+ const tick = new AbortController();
918
+ tickAbort = tick;
890
919
  inFlight = reconcilePools(
891
920
  opts.now ?? new Date(),
892
921
  opts.getControllerCommands ?? getControllerCommands,
893
- tickAbort.signal,
922
+ tick.signal,
923
+ ).then(() =>
924
+ reconcileCacheReleases(opts.getControllerCommands ?? getControllerCommands, tick.signal),
894
925
  );
895
926
  try {
896
927
  await inFlight;
@@ -65,6 +65,8 @@ export type AgentTaskStepConfig = {
65
65
  parentTaskId?: string;
66
66
  requestedByUserId?: string;
67
67
  outputSchema?: Record<string, unknown>;
68
+ /** 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). */
69
+ allowRedelegation?: boolean;
68
70
  /** Wait for the dispatched task to reach a terminal status before resolving. Default: true. */
69
71
  waitForCompletion?: boolean;
70
72
  /** Max ms to wait for a terminal status before throwing. Default: 2h. Only used when waitForCompletion is true. */
@@ -315,6 +317,7 @@ export interface SwarmSdk {
315
317
  slack_downloadFile(args: { url: string }): Promise<unknown>;
316
318
  slack_delete(args: { channelId: string; messageTs: string }): Promise<unknown>;
317
319
  slack_update(args: { channelId: string; messageTs: string; message: string }): Promise<unknown>;
320
+ slack_leaveThread(args: { taskId?: string; channelId?: string; threadTs?: string }): Promise<unknown>;
318
321
 
319
322
  // --- write: chat sdk ---
320
323
  chat_reply(args: { text: string }): Promise<unknown>;
@@ -496,6 +499,26 @@ export interface ScriptWorkflowSteps {
496
499
  label: string,
497
500
  config: { prompt: string; model?: string; schema?: Record<string, unknown> },
498
501
  ): Promise<unknown>;
502
+ /**
503
+ * Dispatch a task and journal its result under \`label\`.
504
+ *
505
+ * Resolves to the task's OUTPUT on success: the stored output string (JSON
506
+ * text under \`outputSchema\`, so parse it yourself), or \`null\` when the task
507
+ * stored none. It is never a task record: on a string, \`result.taskId\` and
508
+ * \`result.status\` are undefined; on \`null\`, reading them throws a TypeError.
509
+ * Other shapes:
510
+ * - \`failOnTaskFailure: false\` and the task ends failed/cancelled/superseded:
511
+ * \`{ taskId, status: "failed", error }\`.
512
+ * - \`waitForCompletion: false\`: the first call still waits up to ~30s while
513
+ * the server polls the task. Done in that window: the output, as on
514
+ * success. Failed or cancelled in that window: throws, or
515
+ * \`{ taskId, status: "failed", error }\` with \`failOnTaskFailure: false\`.
516
+ * Still running: \`{ taskId, status }\`.
517
+ *
518
+ * To get a step's task id, query by its context key:
519
+ * \`SELECT id, status, output FROM agent_tasks WHERE contextKey = 'script-run:<runId>:<label>'
520
+ * AND taskType = 'script-run-step' ORDER BY createdAt DESC, rowid DESC LIMIT 1\`.
521
+ */
499
522
  agentTask(
500
523
  label: string,
501
524
  config: AgentTaskStepConfig,
@@ -76,8 +76,11 @@ import {
76
76
  computeContentHash,
77
77
  createScheduledTask,
78
78
  getScheduledTaskByName,
79
+ getWorkflow,
80
+ listWorkflows,
79
81
  updateScheduledTask,
80
82
  } from "../db";
83
+ import { getScript } from "../scripts/db";
81
84
  import { resolveSeededEnabled } from "./automation-toggle";
82
85
  import type { Seeder, SeedItem } from "./types";
83
86
  import { canonicalJson } from "./workflows-seeder";
@@ -98,8 +101,27 @@ type ScheduleBlock = {
98
101
  cron: string;
99
102
  timezone?: string;
100
103
  enabled?: boolean;
104
+ /** What a fire runs. Default `agent-task`: the `## Scheduled Task` prompt becomes a task. */
105
+ targetType?: ScheduleTargetType;
106
+ /** `targetType: "workflow"`: the workflow's name, resolved to its id at apply time. */
107
+ workflow?: string;
108
+ /** `targetType: "script"`: a global script, run directly with `scriptArgs`. */
109
+ scriptName?: string;
110
+ scriptArgs?: Record<string, unknown>;
101
111
  };
102
112
 
113
+ type ScheduleTargetType = "agent-task" | "workflow" | "script";
114
+ const TARGET_TYPES: readonly ScheduleTargetType[] = ["agent-task", "workflow", "script"];
115
+
116
+ /**
117
+ * The non-default target of a seeded schedule. Agent-task schedules carry none
118
+ * of these keys, so their seed hash is unchanged from before targets existed
119
+ * and recorded seed state on existing installs still matches.
120
+ */
121
+ type SeedScheduleTarget =
122
+ | { targetType: "workflow"; workflow: string }
123
+ | { targetType: "script"; scriptName: string; scriptArgs: Record<string, unknown> };
124
+
103
125
  export type SeedSchedule = {
104
126
  name: string;
105
127
  description: string;
@@ -112,7 +134,7 @@ export type SeedSchedule = {
112
134
  params: Record<string, unknown>;
113
135
  requiredParams: string[];
114
136
  requires: AutomationIntegrationId[];
115
- };
137
+ } & (SeedScheduleTarget | { targetType?: undefined });
116
138
 
117
139
  export type ScheduleTemplateSource = { config: string; content: string };
118
140
  type ScheduleSeedItem = SeedItem & { schedule: SeedSchedule };
@@ -167,10 +189,21 @@ function parseScheduleSource(source: ScheduleTemplateSource): SeedSchedule | nul
167
189
  const block = JSON.parse(blockMatch[1]) as ScheduleBlock;
168
190
  const afterBlock = scheduleStart + blockMatch.index! + blockMatch[0].length;
169
191
  const taskHeading = source.content.indexOf("## Scheduled Task", afterBlock);
170
- const taskTemplate = source.content
171
- .slice(taskHeading >= 0 ? taskHeading + "## Scheduled Task".length : afterBlock)
172
- .trim();
173
- if (!taskTemplate) throw new Error(`Schedule template ${config.name} has no task prompt`);
192
+ const target = parseScheduleTarget(config.name, block);
193
+ let taskTemplate = "";
194
+ if (target) {
195
+ // A workflow or script fire never reads a prompt: shipping one is a mistake.
196
+ if (taskHeading >= 0) {
197
+ throw new Error(
198
+ `Schedule template ${config.name} targets a ${target.targetType} but ships a ## Scheduled Task prompt it would ignore`,
199
+ );
200
+ }
201
+ } else {
202
+ taskTemplate = source.content
203
+ .slice(taskHeading >= 0 ? taskHeading + "## Scheduled Task".length : afterBlock)
204
+ .trim();
205
+ if (!taskTemplate) throw new Error(`Schedule template ${config.name} has no task prompt`);
206
+ }
174
207
 
175
208
  const requires = config.requires ?? [];
176
209
  const requiredParams = config.placeholders ?? [];
@@ -196,7 +229,37 @@ function parseScheduleSource(source: ScheduleTemplateSource): SeedSchedule | nul
196
229
  params: {},
197
230
  requiredParams,
198
231
  requires,
232
+ ...target,
233
+ };
234
+ }
235
+
236
+ /** Validate the block's target fields. Returns null for the default agent-task target. */
237
+ function parseScheduleTarget(name: string, block: ScheduleBlock): SeedScheduleTarget | null {
238
+ const targetType = block.targetType ?? "agent-task";
239
+ if (!TARGET_TYPES.includes(targetType)) {
240
+ throw new Error(`Schedule template ${name} has unknown targetType "${targetType}"`);
241
+ }
242
+ const stray = (fields: (keyof ScheduleBlock)[]) => {
243
+ const set = fields.filter((field) => block[field] !== undefined);
244
+ if (set.length > 0) {
245
+ throw new Error(
246
+ `Schedule template ${name} (targetType ${targetType}) sets ${set.join(", ")}, which it would ignore`,
247
+ );
248
+ }
199
249
  };
250
+ switch (targetType) {
251
+ case "agent-task":
252
+ stray(["workflow", "scriptName", "scriptArgs"]);
253
+ return null;
254
+ case "workflow":
255
+ stray(["scriptName", "scriptArgs"]);
256
+ if (!block.workflow) throw new Error(`Schedule template ${name} has no workflow name`);
257
+ return { targetType, workflow: block.workflow };
258
+ case "script":
259
+ stray(["workflow"]);
260
+ if (!block.scriptName) throw new Error(`Schedule template ${name} has no scriptName`);
261
+ return { targetType, scriptName: block.scriptName, scriptArgs: block.scriptArgs ?? {} };
262
+ }
200
263
  }
201
264
 
202
265
  export function loadSeedSchedules(
@@ -212,7 +275,27 @@ function scheduleSeedHash(schedule: SeedSchedule): string {
212
275
  return computeContentHash(canonicalJson(schedule));
213
276
  }
214
277
 
215
- function scheduleFromRow(schedule: ScheduledTask): SeedSchedule {
278
+ async function scheduleTargetFromRow(
279
+ schedule: ScheduledTask,
280
+ ): Promise<SeedScheduleTarget | undefined> {
281
+ switch (schedule.targetType) {
282
+ case "workflow": {
283
+ const workflow = schedule.workflowId ? await getWorkflow(schedule.workflowId) : null;
284
+ // A dangling id can never equal a seeded name, so the row reads as edited.
285
+ return { targetType: "workflow", workflow: workflow?.name ?? `id:${schedule.workflowId}` };
286
+ }
287
+ case "script":
288
+ return {
289
+ targetType: "script",
290
+ scriptName: schedule.scriptName ?? "",
291
+ scriptArgs: schedule.scriptArgs ?? {},
292
+ };
293
+ default:
294
+ return undefined;
295
+ }
296
+ }
297
+
298
+ async function scheduleFromRow(schedule: ScheduledTask): Promise<SeedSchedule> {
216
299
  return {
217
300
  name: schedule.name,
218
301
  description: schedule.description ?? "",
@@ -225,9 +308,48 @@ function scheduleFromRow(schedule: ScheduledTask): SeedSchedule {
225
308
  params: schedule.params ?? {},
226
309
  requiredParams: schedule.requiredParams ?? [],
227
310
  requires: schedule.requires ?? [],
311
+ ...(await scheduleTargetFromRow(schedule)),
228
312
  };
229
313
  }
230
314
 
315
+ /** Resolve a seeded target to row fields, failing the item when its target is missing. */
316
+ async function resolveTargetFields(schedule: SeedSchedule) {
317
+ switch (schedule.targetType) {
318
+ case "workflow": {
319
+ const name = schedule.workflow;
320
+ const workflow = (await listWorkflows()).find((candidate) => candidate.name === name);
321
+ if (!workflow) throw new Error(`Workflow "${name}" not found`);
322
+ return {
323
+ targetType: "workflow" as const,
324
+ workflowId: workflow.id,
325
+ scriptName: null,
326
+ scriptArgs: null,
327
+ taskTemplate: null,
328
+ };
329
+ }
330
+ case "script": {
331
+ if (!(await getScript({ name: schedule.scriptName, scope: "global" }))) {
332
+ throw new Error(`Global script "${schedule.scriptName}" not found`);
333
+ }
334
+ return {
335
+ targetType: "script" as const,
336
+ workflowId: null,
337
+ scriptName: schedule.scriptName,
338
+ scriptArgs: schedule.scriptArgs,
339
+ taskTemplate: null,
340
+ };
341
+ }
342
+ default:
343
+ return {
344
+ targetType: "agent-task" as const,
345
+ workflowId: null,
346
+ scriptName: null,
347
+ scriptArgs: null,
348
+ taskTemplate: schedule.taskTemplate,
349
+ };
350
+ }
351
+ }
352
+
231
353
  function nextRunAt(schedule: SeedSchedule): string | null {
232
354
  try {
233
355
  // Disabled templates can await a required timezone parameter. Validate the
@@ -263,7 +385,7 @@ export function createSchedulesSeeder(
263
385
 
264
386
  async upstreamHash(item): Promise<string | null> {
265
387
  const existing = await getScheduledTaskByName(item.key);
266
- return existing ? scheduleSeedHash(scheduleFromRow(existing)) : null;
388
+ return existing ? scheduleSeedHash(await scheduleFromRow(existing)) : null;
267
389
  },
268
390
 
269
391
  async apply(item): Promise<void> {
@@ -274,8 +396,7 @@ export function createSchedulesSeeder(
274
396
  cronExpression: schedule.cronExpression,
275
397
  timezone: schedule.timezone,
276
398
  enabled: schedule.enabled,
277
- targetType: "agent-task" as const,
278
- taskTemplate: schedule.taskTemplate,
399
+ ...(await resolveTargetFields(schedule)),
279
400
  taskType: schedule.taskType,
280
401
  tags: schedule.tags,
281
402
  params: schedule.params,
@@ -284,11 +405,16 @@ export function createSchedulesSeeder(
284
405
  };
285
406
  const existing = await getScheduledTaskByName(schedule.name);
286
407
  if (existing) {
408
+ // Nulls clear the previous target's fields when a template changes target.
287
409
  await updateScheduledTask(existing.id, { ...data, nextRunAt: resolvedNextRunAt });
288
410
  } else {
289
411
  await createScheduledTask({
290
412
  name: schedule.name,
291
413
  ...data,
414
+ workflowId: data.workflowId ?? undefined,
415
+ scriptName: data.scriptName ?? undefined,
416
+ scriptArgs: data.scriptArgs ?? undefined,
417
+ taskTemplate: data.taskTemplate ?? undefined,
292
418
  nextRunAt: resolvedNextRunAt ?? undefined,
293
419
  });
294
420
  }
@@ -71,10 +71,18 @@ import type {
71
71
  TriggerConfig,
72
72
  Workflow,
73
73
  WorkflowDefinition,
74
+ WorkflowSnapshot,
74
75
  } from "../../types";
75
76
  import { validateDefinition } from "../../workflows/definition";
76
- import { computeContentHash, createWorkflow, listWorkflows, updateWorkflow } from "../db";
77
+ import {
78
+ computeContentHash,
79
+ createWorkflow,
80
+ getWorkflowVersions,
81
+ listWorkflows,
82
+ updateWorkflow,
83
+ } from "../db";
77
84
  import { resolveSeededEnabled } from "./automation-toggle";
85
+ import { getSeedState } from "./state-db";
78
86
  import type { Seeder, SeedItem } from "./types";
79
87
 
80
88
  type AutomationTemplateConfig = {
@@ -220,6 +228,62 @@ function workflowFromRow(workflow: Workflow): SeedWorkflow {
220
228
  };
221
229
  }
222
230
 
231
+ function workflowFromSnapshot(snapshot: WorkflowSnapshot): SeedWorkflow {
232
+ return {
233
+ name: snapshot.name,
234
+ description: snapshot.description ?? "",
235
+ enabled: snapshot.enabled,
236
+ definition: snapshot.definition,
237
+ triggers: snapshot.triggers,
238
+ cooldown: snapshot.cooldown,
239
+ input: snapshot.input,
240
+ triggerSchema: snapshot.triggerSchema,
241
+ params: snapshot.params ?? {},
242
+ requiredParams: snapshot.requiredParams ?? [],
243
+ requires: snapshot.requires ?? [],
244
+ };
245
+ }
246
+
247
+ /**
248
+ * What a run executes, without the settings an operator is told to change:
249
+ * inputs (plain data, resolved the same for every caller), on/off, cooldown,
250
+ * description and event triggers.
251
+ */
252
+ function executableSurface(workflow: SeedWorkflow): string {
253
+ const {
254
+ input: _input,
255
+ enabled: _enabled,
256
+ cooldown: _cooldown,
257
+ description: _description,
258
+ triggers: _triggers,
259
+ ...surface
260
+ } = workflow;
261
+ return canonicalJson(surface);
262
+ }
263
+
264
+ /**
265
+ * Whether a live workflow still runs seed source: `pristine` while what it
266
+ * executes matches what the seeder last wrote for its name, `edited` once
267
+ * anyone changed that, `unseeded` for a workflow the seeder never wrote.
268
+ * Operator settings (see `executableSurface`) keep it `pristine`. The seeded
269
+ * state is the live row itself or, once edited, the version snapshot taken
270
+ * before the first edit; an edit with no such snapshot counts as `edited`.
271
+ */
272
+ export async function seededWorkflowState(
273
+ workflow: Workflow,
274
+ ): Promise<"pristine" | "edited" | "unseeded"> {
275
+ const state = await getSeedState("workflow", workflow.name);
276
+ if (!state) return "unseeded";
277
+ const live = workflowFromRow(workflow);
278
+ if (workflowSeedHash(live) === state.seededHash) return "pristine";
279
+ for (const version of await getWorkflowVersions(workflow.id)) {
280
+ const seeded = workflowFromSnapshot(version.snapshot);
281
+ if (workflowSeedHash(seeded) !== state.seededHash) continue;
282
+ return executableSurface(seeded) === executableSurface(live) ? "pristine" : "edited";
283
+ }
284
+ return "edited";
285
+ }
286
+
223
287
  export function createWorkflowsSeeder(
224
288
  sources: readonly WorkflowTemplateSource[] = BUILT_IN_WORKFLOW_SOURCES,
225
289
  ): Seeder<WorkflowSeedItem> {
@@ -299,6 +299,8 @@ export function buildModelsDevSeedRows(cache: ModelsDevCache): PricingSeedRow[]
299
299
  // strips the prefix, so we seed the bare id under `pi` too. Without this
300
300
  // every gh-copilot-backed pi run fell through to `costSource='unpriced'`.
301
301
  add(id, projectCostBlock("pi", id, model.cost));
302
+ // fx runs a bare OpenAI id on its `openai` custom connection.
303
+ add(id, projectCostBlock("fx", id, model.cost));
302
304
  }
303
305
 
304
306
  // ---- Cursor (bare vendor ids through Cursor's hosted inference) --------
@@ -358,6 +360,13 @@ export function buildModelsDevSeedRows(cache: ModelsDevCache): PricingSeedRow[]
358
360
  anthropicBilled: id.startsWith("anthropic/"),
359
361
  }),
360
362
  );
363
+ // fx runs `openrouter/<id>` on its OpenRouter custom connection.
364
+ add(
365
+ id,
366
+ projectCostBlock("fx", id, model.cost, {
367
+ anthropicBilled: id.startsWith("anthropic/"),
368
+ }),
369
+ );
361
370
  // Gemini specifically: also project under the 'gemini' provider so
362
371
  // internal-ai callers that tag with provider='gemini' find a hit.
363
372
  if (id.startsWith("google/")) {
@@ -369,6 +378,20 @@ export function buildModelsDevSeedRows(cache: ModelsDevCache): PricingSeedRow[]
369
378
  }
370
379
  }
371
380
 
381
+ // ---- fx on Vercel AI Gateway (`vercel/<vendor>/<id>`) -------------------
382
+ // The prefix is stripped at lookup, so a gateway id lands on the same key as
383
+ // its OpenRouter twin. OpenRouter's row wins where both list the model (one
384
+ // source per key); the gateway section fills the ids OpenRouter lacks.
385
+ for (const [id, model] of Object.entries(cache.vercel?.models ?? {})) {
386
+ if (!model?.cost || openrouter[id]?.cost) continue;
387
+ add(
388
+ id,
389
+ projectCostBlock("fx", id, model.cost, {
390
+ anthropicBilled: id.startsWith("anthropic/"),
391
+ }),
392
+ );
393
+ }
394
+
372
395
  // ---- OpenAI-compatible registry providers ($0 flat-rate subscriptions) --
373
396
  // pi and opencode run these as `<provider>/<id>`. The models.dev `nan`
374
397
  // section and the registry-synthesized `helmcode` section