@desplega.ai/agent-swarm 1.131.1 → 1.134.0

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 (360) hide show
  1. package/README.md +3 -3
  2. package/dist/{actions-jgcy477k.js → actions-7kast243.js} +20 -18
  3. package/dist/{anthropic-messages-rjmd2e7n.js → anthropic-messages-706yad38.js} +33 -17
  4. package/dist/app-0g84t8wf.js +28 -0
  5. package/dist/{artifact-jysj3wrg.js → artifact-dje5ke4h.js} +37 -131
  6. package/dist/{assistant-f2vkk1hj.js → assistant-ac4htcky.js} +25 -22
  7. package/dist/{azure-openai-responses-c0yjmbfd.js → azure-openai-responses-be59fbsz.js} +13 -13
  8. package/dist/{boot-reembed-q7mp8hcr.js → boot-reembed-b1rcqn85.js} +13 -11
  9. package/dist/{boot-reembed-xmw8wcx6.js → boot-reembed-kw2541c4.js} +14 -12
  10. package/dist/{boot-scrub-logs-dsqrv79n.js → boot-scrub-logs-ja29t92g.js} +11 -9
  11. package/dist/{claude-adapter-q6x7yspx.js → claude-adapter-5cg1fv8s.js} +33 -29
  12. package/dist/{claude-managed-adapter-wsg37bt8.js → claude-managed-adapter-9z9sdytx.js} +9 -9
  13. package/dist/{claude-managed-setup-52z4mq9w.js → claude-managed-setup-3zqp51cr.js} +5 -5
  14. package/dist/{claude-1ex232fc.js → claude-wqn3dtca.js} +1 -1
  15. package/dist/{cli-4wgngmwv.js → cli-00w4hbam.js} +1 -1
  16. package/dist/{cli-er3a4bb0.js → cli-15429qdw.js} +6 -1
  17. package/dist/{cli-e5dabkx0.js → cli-1cwhyvta.js} +4 -4
  18. package/dist/{cli-zsspvvm2.js → cli-1ybr6vw2.js} +237 -237
  19. package/dist/{cli-xpxde7rm.js → cli-2wv4kj0e.js} +60 -31
  20. package/dist/cli-34w2584r.js +530 -0
  21. package/dist/{cli-se2p6rkb.js → cli-38taxznr.js} +6 -6
  22. package/dist/{cli-9sbhdpvb.js → cli-3cgfyfzm.js} +14 -14
  23. package/dist/{cli-5n6e5yfn.js → cli-3zmdzfjf.js} +79 -74
  24. package/dist/{cli-3ty0t082.js → cli-43x36mty.js} +197 -8
  25. package/dist/{cli-ycrnynpw.js → cli-480nehnh.js} +29 -22
  26. package/dist/{cli-0258203w.js → cli-492e7ap5.js} +140 -20
  27. package/dist/{cli-3fj60etf.js → cli-497b685t.js} +3 -3
  28. package/dist/{cli-pxfxkx5m.js → cli-4bap00k0.js} +1 -1
  29. package/dist/{cli-w2wxd56v.js → cli-4bc8vmhm.js} +848 -770
  30. package/dist/{cli-aca5pz9f.js → cli-589599p9.js} +1 -1
  31. package/dist/{cli-t930g0xh.js → cli-5tnr64pm.js} +1 -1
  32. package/dist/{cli-jxg3s9ey.js → cli-65d93ves.js} +7 -7
  33. package/dist/{cli-6dpytdr2.js → cli-6x363v1z.js} +2 -2
  34. package/dist/{cli-dxak0fqd.js → cli-787ph852.js} +11 -11
  35. package/dist/{cli-793jvbsh.js → cli-7bv8nckk.js} +16 -11
  36. package/dist/{cli-zyxs93y9.js → cli-7jcc9y1r.js} +3 -3
  37. package/dist/{cli-f1am9szv.js → cli-87t2gstf.js} +1 -1
  38. package/dist/{cli-9aryf9tv.js → cli-95m51ztz.js} +1 -1
  39. package/dist/{cli-4w4fmq9x.js → cli-9w49enmh.js} +2 -2
  40. package/dist/{cli-v69vk5h0.js → cli-9y5b02vr.js} +13 -13
  41. package/dist/{cli-x23me2mg.js → cli-a1rrkrb4.js} +1 -1
  42. package/dist/{cli-psrqtw2m.js → cli-a3pkfpqs.js} +2 -2
  43. package/dist/{cli-h0rgxd24.js → cli-a4ajmfvp.js} +6 -6
  44. package/dist/{cli-ygrbmjja.js → cli-aw8bb6sf.js} +6 -6
  45. package/dist/{cli-g74vpjp2.js → cli-brm3et47.js} +55 -22
  46. package/dist/{cli-czkae1fp.js → cli-c5ddbdfj.js} +9 -9
  47. package/dist/{cli-114fvtmj.js → cli-c79a9zrc.js} +1 -1
  48. package/dist/{cli-49n9f49x.js → cli-dx3dz0ms.js} +3 -3
  49. package/dist/{cli-6dp3ydxa.js → cli-e07exmsb.js} +18 -14
  50. package/dist/cli-et1bbwqf.js +13 -0
  51. package/dist/{cli-yazx6hap.js → cli-f3s9dry0.js} +14 -14
  52. package/dist/{cli-t8d349cy.js → cli-f4wj0s3m.js} +3 -3
  53. package/dist/{cli-d6n0thxw.js → cli-f64gdfxc.js} +1 -1
  54. package/dist/{cli-pwfsqc2x.js → cli-fyd58xgb.js} +55 -55
  55. package/dist/{cli-7dm2zqy2.js → cli-g0j14czv.js} +2 -2
  56. package/dist/{cli-b0p7rfnd.js → cli-g2p9bd1z.js} +14 -14
  57. package/dist/{cli-z905tvmm.js → cli-g814jbst.js} +2 -2
  58. package/dist/{cli-5dsbcjjw.js → cli-ghexzd2d.js} +970 -970
  59. package/dist/{cli-jvv0g8bh.js → cli-gpt9kf01.js} +15 -15
  60. package/dist/{cli-rgrfzg8z.js → cli-grt6qv91.js} +380 -380
  61. package/dist/{cli-wz9115x0.js → cli-gtafvdy0.js} +8 -8
  62. package/dist/{cli-q5dzzak0.js → cli-gy07xk4s.js} +2 -2
  63. package/dist/{cli-sy7jk3tx.js → cli-h0djenzm.js} +2 -2
  64. package/dist/{cli-nrkkknat.js → cli-h37gbjbf.js} +2 -281
  65. package/dist/{cli-jevgrb4w.js → cli-h39xxnq6.js} +1200 -491
  66. package/dist/{cli-gz9sbyk3.js → cli-jepmgn7r.js} +1 -1
  67. package/dist/{cli-ccf9vy7q.js → cli-jmvk6et8.js} +7 -7
  68. package/dist/{cli-2rk0cx7s.js → cli-jxj92gkt.js} +51 -41
  69. package/dist/{cli-bqw0mt1n.js → cli-k46t4664.js} +18 -3
  70. package/dist/{cli-50vbt00w.js → cli-m07313tr.js} +1153 -696
  71. package/dist/{cli-4j2mrzag.js → cli-mkq1rk7b.js} +1 -1
  72. package/dist/{cli-1z7048dy.js → cli-mnm7w46e.js} +1 -1
  73. package/dist/{cli-kd1ck4rx.js → cli-mp9sg4cg.js} +14 -8
  74. package/dist/{cli-jbcva0xj.js → cli-mrammd8c.js} +60 -35
  75. package/dist/cli-nanaxrz6.js +27 -0
  76. package/dist/{cli-p9swy5t3.js → cli-pq2ee5ja.js} +8 -6
  77. package/dist/{cli-2wadx7p2.js → cli-pt43pdp1.js} +2 -2
  78. package/dist/{cli-7sgrxg9a.js → cli-q6d0vvey.js} +4 -4
  79. package/dist/{cli-k9qkf1hw.js → cli-qf9kpsve.js} +9 -9
  80. package/dist/cli-qh4sqfws.js +15 -0
  81. package/dist/{cli-e15jhafb.js → cli-qntkxysp.js} +14 -14
  82. package/dist/{cli-sajgpkqg.js → cli-qs9hhsvb.js} +1723 -325
  83. package/dist/{cli-z2zcxes1.js → cli-qt5bt3vk.js} +1 -1
  84. package/dist/{cli-wx82tw1k.js → cli-qtekrty1.js} +3 -3
  85. package/dist/{cli-g1re8r05.js → cli-rh6f3s9n.js} +3 -3
  86. package/dist/{cli-n0krnc6d.js → cli-rzhcrw2p.js} +2 -2
  87. package/dist/{cli-gecy3z2s.js → cli-snw62ze5.js} +4 -4
  88. package/dist/{cli-n6h8qt8n.js → cli-snz3p4yf.js} +15 -10
  89. package/dist/{cli-r6kqqbj0.js → cli-tkf8n6cw.js} +11 -11
  90. package/dist/{cli-4rkfg1wx.js → cli-tkrr24mz.js} +14 -9
  91. package/dist/{cli-9dr5qzc0.js → cli-tv9f1kyb.js} +54 -43
  92. package/dist/{cli-gm9cbhbq.js → cli-vtz6yj3v.js} +28 -9
  93. package/dist/{cli-2f07m0xr.js → cli-wf86d5t7.js} +1 -1
  94. package/dist/{cli-7zd05xb4.js → cli-whrvct19.js} +45 -45
  95. package/dist/{cli-ap1czx8f.js → cli-wrrea8xm.js} +3 -3
  96. package/dist/{cli-57khth04.js → cli-xmyqv00z.js} +120 -4
  97. package/dist/{cli-cwy5hqbq.js → cli-yjmk7tzc.js} +1 -1
  98. package/dist/{cli-6pcz9h15.js → cli-zykjrwcx.js} +180 -394
  99. package/dist/cli.js +45 -45
  100. package/dist/{codex-adapter-htjkn22b.js → codex-adapter-hsq6mz3m.js} +21 -19
  101. package/dist/{codex-hook-hq54t9ft.js → codex-hook-8c500gq8.js} +8 -7
  102. package/dist/{codex-login-z6yhbkmk.js → codex-login-x7603481.js} +3 -3
  103. package/dist/{codex-session-runner-wjfc7smz.js → codex-session-runner-c1x3x143.js} +15 -13
  104. package/dist/{commands-xhrfwqsk.js → commands-rwtddtst.js} +11 -9
  105. package/dist/{db-anvwb22f.js → db-j2b355vz.js} +458 -444
  106. package/dist/{devin-adapter-v1sarqtw.js → devin-adapter-8qmhqpy3.js} +3 -3
  107. package/dist/{devtools-2y1r7yqk.js → devtools-j2rnj8w7.js} +4 -4
  108. package/dist/{e2b-jdkwemv2.js → e2b-znpx78eg.js} +16 -16
  109. package/dist/{es5-vjgtkgp6.js → es5-fmqrjzy3.js} +2 -2
  110. package/dist/{event-bus-axn823e9.js → event-bus-n8sb35pw.js} +4 -4
  111. package/dist/{getMachineId-bsd-6tc9wxdj.js → getMachineId-bsd-wpzbhkm6.js} +4 -4
  112. package/dist/{getMachineId-darwin-sjt9sgnc.js → getMachineId-darwin-45zabjy0.js} +4 -4
  113. package/dist/{getMachineId-linux-v5n18y9p.js → getMachineId-linux-warnp5pd.js} +3 -3
  114. package/dist/{getMachineId-unsupported-yrjyqw3d.js → getMachineId-unsupported-5spent2a.js} +3 -3
  115. package/dist/{getMachineId-win-w3r8063c.js → getMachineId-win-v4bvcw7a.js} +4 -4
  116. package/dist/{google-generative-ai-hysrd8qx.js → google-generative-ai-b0wzss55.js} +17 -14
  117. package/dist/{google-vertex-p5arrga0.js → google-vertex-e8q4dpya.js} +18 -15
  118. package/dist/{handlers-b9kbc621.js → handlers-hchgrxkh.js} +32 -29
  119. package/dist/{hook-xkvn6c5h.js → hook-p49egqc0.js} +66 -52
  120. package/dist/hook-spb00w76.js +34 -0
  121. package/dist/{http-9xh6ta4r.js → http-nh14kkms.js} +663 -181
  122. package/dist/index-1qthq7vc.js +6 -0
  123. package/dist/{index-0g68f0p6.js → index-20xqmn3v.js} +3 -3
  124. package/dist/{index-vdkr5z2w.js → index-3e03k6e4.js} +114 -114
  125. package/dist/{index-ywds4812.js → index-4cbyhq2g.js} +43 -41
  126. package/dist/index-67pzqfrb.js +36 -0
  127. package/dist/{index-bccrkdkn.js → index-7m8yxkw5.js} +16 -16
  128. package/dist/{index-eaxyqcmj.js → index-dvsk64sm.js} +163 -83
  129. package/dist/{index-dqkpnmqm.js → index-e19s2m8k.js} +18 -18
  130. package/dist/{index-j30k5zxn.js → index-emvxcy98.js} +16 -16
  131. package/dist/{index-7ndw6ecy.js → index-gqnmr60d.js} +9 -9
  132. package/dist/{index-9znhar3x.js → index-j02xvjtd.js} +184 -185
  133. package/dist/{index-1s2442yw.js → index-ns5kksc1.js} +7 -7
  134. package/dist/{index-dpy2hth8.js → index-pdxc8kfh.js} +71 -71
  135. package/dist/{index-z1m7pw0v.js → index-ptcqm8bc.js} +107 -107
  136. package/dist/{index-070v4eyb.js → index-pyp7rexm.js} +5 -6
  137. package/dist/{index-86znjz1p.js → index-shne41sd.js} +13 -13
  138. package/dist/{index-2qn2qndv.js → index-t36q2v4t.js} +183 -183
  139. package/dist/index-tcnvv8w6.js +7 -0
  140. package/dist/index-w91qbfv1.js +7 -0
  141. package/dist/{index-fzc0mwvj.js → index-yqweff5n.js} +32 -32
  142. package/dist/index-yrfphye1.js +39 -0
  143. package/dist/{keepalive-vc7s2aa6.js → keepalive-9k51nqhc.js} +21 -15
  144. package/dist/lead-2xq3dxbz.js +88 -0
  145. package/dist/maintenance-0c0g6nym.js +28 -0
  146. package/dist/mistral-conversations-2njwdwhj.js +717 -0
  147. package/dist/{multipart-parser-qaktc7p8.js → multipart-parser-6gftesw0.js} +2 -2
  148. package/dist/{oauth-refresh-sweep-yecgjpk4.js → oauth-refresh-sweep-273a64r2.js} +14 -12
  149. package/dist/{onboard-xj0f73f9.js → onboard-tsv9wwhz.js} +12 -12
  150. package/dist/{openai-codex-responses-5g27pd6f.js → openai-codex-responses-kt84eq86.js} +26 -26
  151. package/dist/{openai-completions-3vkmfz2a.js → openai-completions-mrypq7qp.js} +17 -16
  152. package/dist/{openai-responses-dg4t7j7a.js → openai-responses-qhrk21ez.js} +20 -17
  153. package/dist/{opencode-adapter-1gm38aba.js → opencode-adapter-rar9676j.js} +13 -11
  154. package/dist/{openrouter-images-g3gm8931.js → openrouter-images-684fysx9.js} +5 -5
  155. package/dist/{otel-impl-nxg0n0b3.js → otel-impl-wp1mfzd2.js} +752 -537
  156. package/dist/{photon_rs-ky7dj5qw.js → photon_rs-8dwt2pzy.js} +581 -581
  157. package/dist/{pi-messages-tqj8q6b9.js → pi-messages-txp9f97r.js} +7 -7
  158. package/dist/{pi-mono-adapter-4g5kby31.js → pi-mono-adapter-yz47kk8e.js} +1910 -1180
  159. package/dist/pricing-refresh-jw1yw2fn.js +22 -0
  160. package/dist/rbac-roles-dg964c8p.js +32 -0
  161. package/dist/{rbac-roles-1hcxdtg7.js → rbac-roles-p53cg5cv.js} +19 -17
  162. package/dist/{registry-02cy9z5z.js → registry-mh12y17v.js} +4 -4
  163. package/dist/render-v2-56vn2rk9.js +36 -0
  164. package/dist/seed-pricing-52xx6z63.js +19 -0
  165. package/dist/{setup-46xtgq3c.js → setup-e8gp79sy.js} +7 -7
  166. package/dist/token-util-zy908s1a.js +6 -0
  167. package/dist/{token-fxkn2zf2.js → token-xg5711x5.js} +4 -4
  168. package/dist/worker-s17f1g46.js +88 -0
  169. package/dist/{x-6mg4kfpe.js → x-xkfh0w0v.js} +5 -5
  170. package/openapi.json +566 -22
  171. package/package.json +6 -6
  172. package/src/be/budget-refusal-notify.ts +14 -7
  173. package/src/be/db-queries/task-artifacts.ts +182 -0
  174. package/src/be/db.ts +834 -171
  175. package/src/be/events.ts +5 -0
  176. package/src/be/migrations/131_session_logs_created_at_index.sql +2 -0
  177. package/src/be/migrations/132_multi_runtime_instances.sql +44 -0
  178. package/src/be/migrations/133_workflow_run_trigger_type.sql +12 -0
  179. package/src/be/migrations/135_backfill_task_pull_request_attachments.sql +228 -0
  180. package/src/be/migrations/136_task_requester_provenance.sql +37 -0
  181. package/src/be/migrations/137_memory_retrieval_composite_index.sql +1 -0
  182. package/src/be/migrations/runner.ts +17 -11
  183. package/src/be/multi-runtime.ts +430 -0
  184. package/src/be/scripts/extract-schema.ts +3 -1
  185. package/src/be/scripts/typecheck.ts +6 -2
  186. package/src/be/seed-prompt-templates.ts +25 -2
  187. package/src/be/seed-skills/index.ts +7 -0
  188. package/src/be/steering.ts +1 -1
  189. package/src/be/swarm-config-guard.ts +18 -0
  190. package/src/commands/credential-wait.ts +54 -0
  191. package/src/commands/onboard/steps/core-credentials.tsx +1 -1
  192. package/src/commands/onboard/steps/custom-templates.tsx +1 -1
  193. package/src/commands/onboard/steps/harness-credentials.tsx +1 -1
  194. package/src/commands/onboard/steps/health-check.tsx +1 -1
  195. package/src/commands/onboard/steps/start.tsx +1 -1
  196. package/src/commands/profile-sync-audit.ts +56 -0
  197. package/src/commands/profile-sync.ts +287 -15
  198. package/src/commands/provider-credentials.ts +46 -19
  199. package/src/commands/runner.ts +199 -33
  200. package/src/commands/templates.ts +22 -0
  201. package/src/github/handlers.ts +55 -9
  202. package/src/heartbeat/heartbeat.ts +109 -17
  203. package/src/hooks/hook.ts +49 -33
  204. package/src/http/active-sessions.ts +11 -1
  205. package/src/http/agents.ts +261 -30
  206. package/src/http/all-routes.ts +1 -0
  207. package/src/http/config.ts +13 -2
  208. package/src/http/core.ts +102 -4
  209. package/src/http/db-query-bounded.ts +313 -0
  210. package/src/http/db-query-shared.ts +249 -0
  211. package/src/http/db-query.ts +54 -42
  212. package/src/http/events.ts +2 -0
  213. package/src/http/index.ts +23 -2
  214. package/src/http/mcp-bridge.ts +37 -3
  215. package/src/http/mcp.ts +12 -0
  216. package/src/http/memory.ts +13 -2
  217. package/src/http/openapi.ts +1 -0
  218. package/src/http/poll.ts +70 -27
  219. package/src/http/prompt-templates.ts +36 -13
  220. package/src/http/route-def.ts +43 -9
  221. package/src/http/schedules.ts +2 -2
  222. package/src/http/scripts.ts +10 -1
  223. package/src/http/session-data.ts +44 -0
  224. package/src/http/tasks.ts +2 -2
  225. package/src/http/users.ts +74 -52
  226. package/src/http/webhooks.ts +5 -2
  227. package/src/http/workflows.ts +1 -4
  228. package/src/integrations/kapso/inbound.ts +1 -1
  229. package/src/jira/outbound.ts +39 -8
  230. package/src/jira/webhook-lifecycle.ts +13 -2
  231. package/src/linear/outbound.ts +43 -10
  232. package/src/oauth/keepalive.ts +13 -2
  233. package/src/prompts/base-prompt.ts +1 -1
  234. package/src/prompts/default-drift.ts +21 -0
  235. package/src/prompts/session-templates.ts +56 -7
  236. package/src/providers/claude-adapter.ts +6 -0
  237. package/src/providers/codex-adapter.ts +6 -0
  238. package/src/providers/opencode-adapter.ts +6 -0
  239. package/src/providers/pi-mono-adapter.ts +8 -0
  240. package/src/providers/pricing-sources.md +24 -0
  241. package/src/queue-stall-alarm.ts +216 -0
  242. package/src/rbac/legacy-policy.ts +3 -0
  243. package/src/rbac/permissions.ts +12 -0
  244. package/src/scheduler/scheduler.ts +7 -0
  245. package/src/script-workflows/executor.ts +2 -1
  246. package/src/scripts-runtime/executors/native.ts +2 -1
  247. package/src/scripts-runtime/executors/types.ts +5 -0
  248. package/src/scripts-runtime/loader.ts +5 -0
  249. package/src/scripts-runtime/sdk-allowlist.ts +3 -0
  250. package/src/scripts-runtime/swarm-config.ts +8 -0
  251. package/src/scripts-runtime/swarm-sdk.ts +7 -0
  252. package/src/scripts-runtime/types/stdlib.d.ts +4 -0
  253. package/src/scripts-runtime/types/swarm-sdk.d.ts +4 -0
  254. package/src/server.ts +6 -0
  255. package/src/slack/channel-join.ts +22 -3
  256. package/src/slack/channel-lifecycle.ts +114 -0
  257. package/src/tests/budget-claim-gate.test.ts +20 -0
  258. package/src/tests/claude-adapter.test.ts +32 -0
  259. package/src/tests/codex-adapter.test.ts +30 -0
  260. package/src/tests/create-task-validation.test.ts +128 -0
  261. package/src/tests/credential-wait.test.ts +67 -1
  262. package/src/tests/db-query.test.ts +711 -2
  263. package/src/tests/heartbeat-checklist.test.ts +28 -0
  264. package/src/tests/http-api-integration.test.ts +81 -0
  265. package/src/tests/http-users.test.ts +166 -1
  266. package/src/tests/identity-field-budget.test.ts +176 -0
  267. package/src/tests/mcp-tools-user.test.ts +34 -0
  268. package/src/tests/mcp-user-route.test.ts +17 -0
  269. package/src/tests/metrics-http.test.ts +44 -0
  270. package/src/tests/migration-failure.test.ts +86 -0
  271. package/src/tests/multi-runtime-registration.test.ts +2810 -0
  272. package/src/tests/openapi-response-contract.test.ts +24 -0
  273. package/src/tests/openapi-route-headers.test.ts +83 -0
  274. package/src/tests/opencode-adapter.test.ts +40 -0
  275. package/src/tests/outbound-listener-rejection.test.ts +138 -0
  276. package/src/tests/pi-mono-adapter.test.ts +98 -0
  277. package/src/tests/preload.ts +2 -0
  278. package/src/tests/profile-sync-warning.test.ts +277 -0
  279. package/src/tests/profile-sync.test.ts +232 -6
  280. package/src/tests/prompt-template-resolver.test.ts +177 -6
  281. package/src/tests/prompt-template-session.test.ts +39 -3
  282. package/src/tests/queue-stall-alarm.test.ts +226 -0
  283. package/src/tests/rbac-charact-slack.test.ts +29 -0
  284. package/src/tests/rbac-engine.test.ts +3 -0
  285. package/src/tests/requester-comms.test.ts +32 -0
  286. package/src/tests/runner-requester-profile.test.ts +38 -0
  287. package/src/tests/schedule-target-type.test.ts +38 -0
  288. package/src/tests/script-connections.test.ts +13 -1
  289. package/src/tests/scripts-runtime-identity.test.ts +375 -0
  290. package/src/tests/scripts-runtime.test.ts +4 -3
  291. package/src/tests/sdk-allowlist.test.ts +47 -0
  292. package/src/tests/send-task-requested-by.test.ts +9 -0
  293. package/src/tests/session-costs.test.ts +506 -1
  294. package/src/tests/shutdown-error-scrubbing.test.ts +92 -0
  295. package/src/tests/slack-channel-lifecycle.test.ts +241 -0
  296. package/src/tests/slack-render-v2.test.ts +43 -0
  297. package/src/tests/steering-core.test.ts +28 -0
  298. package/src/tests/swarm-config-reserved-keys.test.ts +22 -0
  299. package/src/tests/task-artifacts.test.ts +386 -0
  300. package/src/tests/task-attachments-schema.test.ts +17 -1
  301. package/src/tests/task-completion-idempotency.test.ts +57 -0
  302. package/src/tests/task-pull-request-backfill-migration.test.ts +321 -0
  303. package/src/tests/tool-annotations.test.ts +4 -0
  304. package/src/tests/update-profile-api.test.ts +67 -39
  305. package/src/tests/update-profile-auth.test.ts +48 -4
  306. package/src/tests/user-attribution-migration.test.ts +54 -0
  307. package/src/tests/user-identity.test.ts +72 -0
  308. package/src/tests/workflow-async-v2.test.ts +39 -1
  309. package/src/tests/workflow-http-v2.test.ts +3 -2
  310. package/src/tests/workflow-mcp-trigger-schema.test.ts +52 -3
  311. package/src/tests/workflow-retry-v2.test.ts +45 -1
  312. package/src/tools/db-query.ts +18 -8
  313. package/src/tools/get-task-details.ts +24 -3
  314. package/src/tools/manage-user.ts +36 -3
  315. package/src/tools/poll-task.ts +93 -14
  316. package/src/tools/script-common.ts +5 -0
  317. package/src/tools/send-task.ts +1 -4
  318. package/src/tools/slack-archive-channel.ts +70 -0
  319. package/src/tools/slack-create-channel.ts +74 -0
  320. package/src/tools/slack-invite-to-channel.ts +78 -0
  321. package/src/tools/store-progress.ts +13 -2
  322. package/src/tools/swarm-config/delete-config.ts +9 -0
  323. package/src/tools/swarm-config/set-config.ts +3 -2
  324. package/src/tools/task-action.ts +30 -0
  325. package/src/tools/task-tool-ctx.ts +8 -1
  326. package/src/tools/tool-config.ts +4 -1
  327. package/src/tools/update-profile.ts +16 -21
  328. package/src/tools/utils.ts +5 -0
  329. package/src/tools/workflows/trigger-workflow.ts +7 -1
  330. package/src/types.ts +165 -1
  331. package/src/utils/github-pull-request.ts +76 -0
  332. package/src/utils/identity-field-budget.ts +73 -0
  333. package/src/utils/multi-runtime.ts +20 -0
  334. package/src/utils/requester-comms.ts +24 -0
  335. package/src/workflows/engine.ts +22 -2
  336. package/src/workflows/resume.ts +19 -11
  337. package/src/workflows/retry-poller.ts +1 -0
  338. package/src/workflows/triggers.ts +4 -0
  339. package/templates/skills/db-query-guidance/config.json +14 -0
  340. package/templates/skills/db-query-guidance/content.md +57 -0
  341. package/dist/app-5da78bd4.js +0 -26
  342. package/dist/cli-3te4jdkm.js +0 -225
  343. package/dist/cli-bmze986d.js +0 -7
  344. package/dist/cli-kh3ba2zf.js +0 -607
  345. package/dist/hook-cgk4wxmx.js +0 -31
  346. package/dist/index-25wjsdv2.js +0 -34
  347. package/dist/index-76at2ymp.js +0 -7
  348. package/dist/index-hase6zbe.js +0 -6
  349. package/dist/index-vg4snay4.js +0 -36
  350. package/dist/index-vy8edt27.js +0 -7
  351. package/dist/lead-r2yqfmx7.js +0 -85
  352. package/dist/maintenance-mw70k468.js +0 -26
  353. package/dist/mistral-conversations-81fwav0j.js +0 -30578
  354. package/dist/otel-nqa12wf3.js +0 -3149
  355. package/dist/pricing-refresh-2cn6effr.js +0 -20
  356. package/dist/rbac-roles-vynday5g.js +0 -30
  357. package/dist/render-v2-tc0bh8kk.js +0 -34
  358. package/dist/seed-pricing-8kceb43f.js +0 -17
  359. package/dist/token-util-pqyc8kng.js +0 -6
  360. package/dist/worker-qrbz6hmv.js +0 -85
@@ -0,0 +1,430 @@
1
+ /**
2
+ * Persistence for multi-runtime agents: the runtime-instance lifecycle and the
3
+ * logical task-concurrency policy that sits above it.
4
+ *
5
+ * Server-side by design — these helpers own SQLite statements, so they live
6
+ * under `src/be` rather than beside the DB-free flag reader in
7
+ * `src/utils/multi-runtime.ts`, keeping the worker/API database boundary
8
+ * intact (`scripts/check-db-boundary.sh`).
9
+ */
10
+
11
+ import type { Agent, RuntimeInstance, RuntimeInstanceStatus, SwarmConfig } from "../types";
12
+ import { isMultiRuntimeEnabled } from "../utils/multi-runtime";
13
+ import {
14
+ getActiveTaskCount,
15
+ getAgentById,
16
+ getDb,
17
+ getSwarmConfigs,
18
+ updateAgentMaxTasks,
19
+ updateAgentStatus,
20
+ upsertSwarmConfig,
21
+ } from "./db";
22
+
23
+ // ─── Logical task policy ─────────────────────────────────────────────────────
24
+
25
+ /**
26
+ * Agent-scoped swarm_config key holding the logical maxTasks policy in
27
+ * multi-runtime mode. Operators edit this row; `agents.maxTasks` stays the
28
+ * enforcement mirror that hasCapacity()/getRemainingCapacity() read. Same
29
+ * column-vs-config split as HARNESS_PROVIDER.
30
+ */
31
+ export const AGENT_MAX_TASKS_CONFIG_KEY = "AGENT_MAX_TASKS";
32
+
33
+ /** Bounds match AgentSchema.maxTasks, so a mirrored write stays schema-valid. */
34
+ function parseAgentMaxTasksValue(raw: string): number | null {
35
+ const str = raw.trim();
36
+ if (!/^\d+$/.test(str)) return null;
37
+ const value = Number(str);
38
+ return value >= 1 && value <= 100 ? value : null;
39
+ }
40
+
41
+ /**
42
+ * Concurrency an agent falls back to with no explicit policy. Mirrors the
43
+ * role defaults the runner resolves for itself (`resolveMaxConcurrent`), so a
44
+ * lead keeps room for the follow-up work it waits on.
45
+ */
46
+ export function defaultMaxTasksForAgent(agent: Pick<Agent, "isLead">): number {
47
+ return agent.isLead ? 2 : 1;
48
+ }
49
+
50
+ export function getAgentMaxTasksConfig(agentId: string): SwarmConfig | null {
51
+ return (
52
+ getSwarmConfigs({ scope: "agent", scopeId: agentId, key: AGENT_MAX_TASKS_CONFIG_KEY })[0] ??
53
+ null
54
+ );
55
+ }
56
+
57
+ /**
58
+ * Reconcile the AGENT_MAX_TASKS policy row with its agents.maxTasks mirror,
59
+ * never consuming the runtime-reported concurrency.
60
+ *
61
+ * With no row yet, seed from the persisted agents.maxTasks — the value the
62
+ * swarm is already enforcing — so enabling multi-runtime mode cannot change
63
+ * behaviour. An existing row is authoritative and repairs the mirror instead.
64
+ *
65
+ * Runs inside the registration transaction so concurrent runtimes serialize:
66
+ * the first seeds, the rest observe.
67
+ */
68
+ export function reconcileAgentMaxTasksPolicy(agentId: string): void {
69
+ const agent = getAgentById(agentId);
70
+ if (!agent) return;
71
+ const existing = getAgentMaxTasksConfig(agentId);
72
+ if (existing) {
73
+ const authoritative = parseAgentMaxTasksValue(existing.value);
74
+ if (authoritative !== null && authoritative !== (agent.maxTasks ?? 1)) {
75
+ updateAgentMaxTasks(agentId, authoritative);
76
+ }
77
+ return;
78
+ }
79
+ upsertSwarmConfig({
80
+ scope: "agent",
81
+ scopeId: agentId,
82
+ key: AGENT_MAX_TASKS_CONFIG_KEY,
83
+ value: String(agent.maxTasks ?? defaultMaxTasksForAgent(agent)),
84
+ description: "Logical agent max-tasks policy (seeded from agents.maxTasks)",
85
+ });
86
+ }
87
+
88
+ /**
89
+ * Deleting the policy returns the agent to its role default rather than to a
90
+ * fixed single slot — otherwise resetting a lead would leave it unable to
91
+ * start the follow-up its own task waits on.
92
+ */
93
+ export function resetAgentMaxTasksMirror(agentId: string): void {
94
+ const agent = getAgentById(agentId);
95
+ if (!agent) return;
96
+ updateAgentMaxTasks(agentId, defaultMaxTasksForAgent(agent));
97
+ }
98
+
99
+ /**
100
+ * Config upsert that keeps the agents.maxTasks enforcement mirror in step
101
+ * with an agent-scoped AGENT_MAX_TASKS write, in one transaction, so policy
102
+ * and enforcement cannot diverge — including with no runtime connected.
103
+ * Every other key passes straight through.
104
+ */
105
+ export function upsertSwarmConfigWithPolicyMirror(
106
+ data: Parameters<typeof upsertSwarmConfig>[0],
107
+ ): SwarmConfig {
108
+ const scopeId = data.scope === "agent" ? data.scopeId : null;
109
+ if (!scopeId || data.key !== AGENT_MAX_TASKS_CONFIG_KEY) {
110
+ return upsertSwarmConfig(data);
111
+ }
112
+ return getDb().transaction(() => {
113
+ const row = upsertSwarmConfig(data);
114
+ // Guard, not validation — both write paths already ran validateConfigValue.
115
+ const value = parseAgentMaxTasksValue(data.value);
116
+ if (value !== null) updateAgentMaxTasks(scopeId, value);
117
+ return row;
118
+ })();
119
+ }
120
+
121
+ // ─── Runtime instances ───────────────────────────────────────────────────────
122
+
123
+ interface RuntimeInstanceRow {
124
+ id: string;
125
+ agent_id: string;
126
+ status: string;
127
+ reported_slots: number;
128
+ credential_ready: number | null;
129
+ metadata: string | null;
130
+ last_seen_at: string;
131
+ created_at: string;
132
+ updated_at: string;
133
+ created_by: string | null;
134
+ updated_by: string | null;
135
+ }
136
+
137
+ function rowToRuntimeInstance(row: RuntimeInstanceRow): RuntimeInstance {
138
+ let metadata: Record<string, unknown> | null = null;
139
+ if (row.metadata) {
140
+ try {
141
+ metadata = JSON.parse(row.metadata) as Record<string, unknown>;
142
+ } catch {
143
+ metadata = null;
144
+ }
145
+ }
146
+ return {
147
+ id: row.id,
148
+ agentId: row.agent_id,
149
+ status: row.status as RuntimeInstanceStatus,
150
+ reportedSlots: row.reported_slots,
151
+ credentialReady: row.credential_ready === null ? null : row.credential_ready === 1,
152
+ metadata,
153
+ lastSeenAt: row.last_seen_at,
154
+ createdAt: row.created_at,
155
+ updatedAt: row.updated_at,
156
+ };
157
+ }
158
+
159
+ /**
160
+ * Minutes without a ping before a runtime stops counting as live. The runner
161
+ * pings every poll iteration (~2s), so this is a wide margin that will not
162
+ * expire a healthy idle worker; it matches the "worker clearly dead" window
163
+ * the stalled-task classifier already uses.
164
+ */
165
+ export function runtimeStaleThresholdMinutes(): number {
166
+ return Number(process.env.RUNTIME_STALE_THRESHOLD_MIN) || 5;
167
+ }
168
+
169
+ function runtimeLivenessCutoff(): string {
170
+ return new Date(Date.now() - runtimeStaleThresholdMinutes() * 60 * 1000).toISOString();
171
+ }
172
+
173
+ /**
174
+ * Register (or refresh) a runtime instance serving a logical agent. A
175
+ * re-registration from the same process refreshes its row in place rather
176
+ * than accumulating rows.
177
+ *
178
+ * The `DO UPDATE ... WHERE` guard makes runtime ownership permanent: an id
179
+ * already held by one agent can never be reassigned to another, so a
180
+ * duplicate id cannot move a live runtime between agents. Returns null in
181
+ * that case (no row written).
182
+ */
183
+ export function upsertRuntimeInstance(instance: {
184
+ id: string;
185
+ agentId: string;
186
+ reportedSlots: number;
187
+ metadata?: Record<string, unknown> | null;
188
+ }): RuntimeInstance | null {
189
+ const metadataJson = instance.metadata ? JSON.stringify(instance.metadata) : null;
190
+ const row = getDb()
191
+ .prepare<RuntimeInstanceRow, [string, string, number, string | null]>(
192
+ `INSERT INTO runtime_instances (id, agent_id, status, reported_slots, metadata)
193
+ VALUES (?, ?, 'active', ?, ?)
194
+ ON CONFLICT(id) DO UPDATE SET
195
+ status = 'active',
196
+ reported_slots = excluded.reported_slots,
197
+ metadata = COALESCE(excluded.metadata, runtime_instances.metadata),
198
+ last_seen_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now'),
199
+ updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
200
+ WHERE runtime_instances.agent_id = excluded.agent_id
201
+ RETURNING *`,
202
+ )
203
+ .get(instance.id, instance.agentId, instance.reportedSlots, metadataJson);
204
+ return row ? rowToRuntimeInstance(row) : null;
205
+ }
206
+
207
+ export function getRuntimeInstanceById(id: string): RuntimeInstance | null {
208
+ const row = getDb()
209
+ .prepare<RuntimeInstanceRow, [string]>("SELECT * FROM runtime_instances WHERE id = ?")
210
+ .get(id);
211
+ return row ? rowToRuntimeInstance(row) : null;
212
+ }
213
+
214
+ /**
215
+ * Refresh a live runtime instance's liveness from the worker's ping cadence.
216
+ *
217
+ * Matches only a row that is already live and owned by the caller, so worker
218
+ * traffic can refresh liveness but never resurrect a closed or expired
219
+ * runtime — registration stays the only path back to `active`. Returns false
220
+ * when nothing matched, which callers use as "this identity is not live".
221
+ */
222
+ export function touchRuntimeInstance(id: string, agentId: string): boolean {
223
+ const result = getDb()
224
+ .prepare(
225
+ `UPDATE runtime_instances
226
+ SET last_seen_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now'),
227
+ updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
228
+ WHERE id = ? AND agent_id = ? AND status = 'active' AND last_seen_at >= ?`,
229
+ )
230
+ .run(id, agentId, runtimeLivenessCutoff());
231
+ return result.changes > 0;
232
+ }
233
+
234
+ /** Mark one runtime instance offline (scoped to its agent, like touch). */
235
+ export function markRuntimeInstanceOffline(id: string, agentId: string): RuntimeInstance | null {
236
+ const row = getDb()
237
+ .prepare<RuntimeInstanceRow, [string, string]>(
238
+ `UPDATE runtime_instances
239
+ SET status = 'offline', updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
240
+ WHERE id = ? AND agent_id = ? RETURNING *`,
241
+ )
242
+ .get(id, agentId);
243
+ return row ? rowToRuntimeInstance(row) : null;
244
+ }
245
+
246
+ /**
247
+ * Runtimes currently serving an agent. A crashed process never reaches
248
+ * `/close`, so `active` alone would keep a dead runtime counted forever;
249
+ * liveness is therefore the conjunction of status and a fresh `last_seen_at`.
250
+ */
251
+ export function countActiveRuntimeInstancesForAgent(agentId: string): number {
252
+ const result = getDb()
253
+ .prepare<{ count: number }, [string, string]>(
254
+ `SELECT COUNT(*) as count FROM runtime_instances
255
+ WHERE agent_id = ? AND status = 'active' AND last_seen_at >= ?`,
256
+ )
257
+ .get(agentId, runtimeLivenessCutoff());
258
+ return result?.count ?? 0;
259
+ }
260
+
261
+ /**
262
+ * Read-only listing for operator observability. `isLive` applies the same
263
+ * status+freshness conjunction as `isRuntimeInstanceLive`, so a stale row
264
+ * that the sweep has not pruned yet is never reported as live.
265
+ */
266
+ export function listRuntimeInstancesForAgent(
267
+ agentId: string,
268
+ ): Array<RuntimeInstance & { isLive: boolean }> {
269
+ const rows = getDb()
270
+ .prepare<RuntimeInstanceRow & { is_live: number }, [string, string]>(
271
+ `SELECT *, (status = 'active' AND last_seen_at >= ?) AS is_live
272
+ FROM runtime_instances
273
+ WHERE agent_id = ?
274
+ ORDER BY created_at ASC, id ASC`,
275
+ )
276
+ .all(runtimeLivenessCutoff(), agentId);
277
+ return rows.map((row) => ({ ...rowToRuntimeInstance(row), isLive: row.is_live === 1 }));
278
+ }
279
+
280
+ /**
281
+ * Whether this identity names a runtime of `agentId` that is still reporting.
282
+ * Dispatch uses it so a retired process cannot be handed work alongside its
283
+ * replacement; an absent identity is never live.
284
+ */
285
+ export function isRuntimeInstanceLive(id: string | undefined, agentId: string): boolean {
286
+ if (!id) return false;
287
+ const row = getDb()
288
+ .prepare<{ c: number }, [string, string, string]>(
289
+ `SELECT COUNT(*) c FROM runtime_instances
290
+ WHERE id = ? AND agent_id = ? AND status = 'active' AND last_seen_at >= ?`,
291
+ )
292
+ .get(id, agentId, runtimeLivenessCutoff());
293
+ return (row?.c ?? 0) > 0;
294
+ }
295
+
296
+ /**
297
+ * Record this runtime's credential readiness. Process-local by design: a
298
+ * sibling's state is never touched. Only a live runtime may report.
299
+ */
300
+ export function setRuntimeCredentialReady(id: string, agentId: string, ready: boolean): boolean {
301
+ const result = getDb()
302
+ .prepare(
303
+ `UPDATE runtime_instances
304
+ SET credential_ready = ?, updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
305
+ WHERE id = ? AND agent_id = ? AND status = 'active' AND last_seen_at >= ?`,
306
+ )
307
+ .run(ready ? 1 : 0, id, agentId, runtimeLivenessCutoff());
308
+ return result.changes > 0;
309
+ }
310
+
311
+ /**
312
+ * Whether any live runtime can currently execute work. A runtime that never
313
+ * reported (NULL) counts as ready, so workers opting out of credential checks
314
+ * stay dispatchable.
315
+ */
316
+ export function hasReadyLiveRuntime(agentId: string): boolean {
317
+ const row = getDb()
318
+ .prepare<{ c: number }, [string, string]>(
319
+ `SELECT COUNT(*) c FROM runtime_instances
320
+ WHERE agent_id = ? AND status = 'active' AND last_seen_at >= ?
321
+ AND (credential_ready IS NULL OR credential_ready = 1)`,
322
+ )
323
+ .get(agentId, runtimeLivenessCutoff());
324
+ return (row?.c ?? 0) > 0;
325
+ }
326
+
327
+ /**
328
+ * Recompute the logical agent's state from its live runtimes.
329
+ *
330
+ * Precedence: no live runtime wins (offline), then active work (busy), then
331
+ * credential availability, then idle. Aggregating here is what stops one
332
+ * process's report from overwriting a sibling's — a waiting runtime cannot
333
+ * park an agent a ready sibling is serving, and a ready one cannot pull a
334
+ * busy agent back to idle.
335
+ */
336
+ export function reconcileAgentStatusFromRuntimes(agentId: string): void {
337
+ const agent = getAgentById(agentId);
338
+ if (!agent) return;
339
+ if (countActiveRuntimeInstancesForAgent(agentId) === 0) {
340
+ if (agent.status !== "offline") updateAgentStatus(agentId, "offline");
341
+ return;
342
+ }
343
+ if (!hasReadyLiveRuntime(agentId)) {
344
+ if (agent.status !== "waiting_for_credentials") {
345
+ updateAgentStatus(agentId, "waiting_for_credentials");
346
+ }
347
+ return;
348
+ }
349
+ const next = getActiveTaskCount(agentId) > 0 ? "busy" : "idle";
350
+ if (agent.status !== next) updateAgentStatus(agentId, next);
351
+ }
352
+
353
+ /**
354
+ * Agents with at least one runtime still reporting. While multi-runtime mode
355
+ * is on this is the eligibility set for dispatch: an agent with no live
356
+ * runtime — whether its runtimes died or it has not re-registered since the
357
+ * flag was enabled — cannot poll for work, so assigning to it would strand
358
+ * the task.
359
+ */
360
+ export function agentsWithLiveRuntime(): Set<string> {
361
+ const rows = getDb()
362
+ .prepare<{ agent_id: string }, [string]>(
363
+ `SELECT DISTINCT agent_id FROM runtime_instances
364
+ WHERE status = 'active' AND last_seen_at >= ?`,
365
+ )
366
+ .all(runtimeLivenessCutoff());
367
+ return new Set(rows.map((r) => r.agent_id));
368
+ }
369
+
370
+ /**
371
+ * Retire runtimes that stopped pinging and take their agents offline when
372
+ * nothing live remains.
373
+ *
374
+ * Inert unless multi-runtime mode is on: after a rollback, workers stop
375
+ * refreshing their rows, and expiring them would offline agents that are
376
+ * running perfectly well under legacy semantics.
377
+ *
378
+ * Deleting the retired rows rather than keeping them is deliberate. Runtime
379
+ * identity is per boot, so retaining them would grow the table once per boot
380
+ * per agent forever; nothing reads a runtime after it stops being live.
381
+ *
382
+ * Expiry deliberately does NOT touch active_sessions. Runtime liveness and
383
+ * in-flight work liveness answer different questions: a stale runtime row is
384
+ * enough to stop NEW dispatch, but sessions are heartbeated by tool activity
385
+ * only — a healthy worker inside a long model call or shell command can be
386
+ * quiet past this cutoff — so it is not evidence the CURRENT work is dead.
387
+ * Crash classification stays owned by the heartbeat's stalled-task
388
+ * remediation, which requires both a stale session heartbeat and a stale
389
+ * task before reclaiming (and cleans the session when it does); the sweep's
390
+ * 30-minute stale-session cleanup backstops any leftover row.
391
+ */
392
+ export function expireStaleRuntimeInstances(): {
393
+ expired: number;
394
+ agentsOffline: number;
395
+ pruned: number;
396
+ } {
397
+ if (!isMultiRuntimeEnabled()) {
398
+ return { expired: 0, agentsOffline: 0, pruned: 0 };
399
+ }
400
+
401
+ return getDb().transaction(() => {
402
+ const cutoff = runtimeLivenessCutoff();
403
+ // Still-active rows past the window never reached /close; retiring them
404
+ // removes the process from new-work eligibility. Whether its in-flight
405
+ // work is dead is decided separately (see doc comment above).
406
+ const stale = getDb()
407
+ .prepare<{ id: string; agent_id: string }, [string]>(
408
+ `SELECT id, agent_id FROM runtime_instances
409
+ WHERE status = 'active' AND last_seen_at < ?`,
410
+ )
411
+ .all(cutoff);
412
+
413
+ // Prune every row past the window, closed ones included. Runtime identity
414
+ // is per boot, so keeping them would add a row per boot per agent forever
415
+ // and nothing reads a runtime once it stops being live.
416
+ const pruned = getDb()
417
+ .prepare("DELETE FROM runtime_instances WHERE last_seen_at < ?")
418
+ .run(cutoff).changes;
419
+
420
+ // Recompute every affected agent from what survived: losing the only
421
+ // ready runtime has to park the agent even though a waiting sibling is
422
+ // still live, or the same sweep would assign work it cannot execute.
423
+ let agentsOffline = 0;
424
+ for (const agentId of new Set(stale.map((r) => r.agent_id))) {
425
+ reconcileAgentStatusFromRuntimes(agentId);
426
+ if (getAgentById(agentId)?.status === "offline") agentsOffline++;
427
+ }
428
+ return { expired: stale.length, agentsOffline, pruned };
429
+ })();
430
+ }
@@ -7,10 +7,12 @@
7
7
  * Extraction is best-effort and non-blocking — failures return null.
8
8
  */
9
9
 
10
+ import { fileURLToPath } from "node:url";
11
+
10
12
  const TIMEOUT_MS = 5_000;
11
13
 
12
14
  function extractorPath(): string {
13
- return new URL("../../scripts-runtime/extract-args-schema.ts", import.meta.url).pathname;
15
+ return fileURLToPath(new URL("../../scripts-runtime/extract-args-schema.ts", import.meta.url));
14
16
  }
15
17
 
16
18
  export async function extractArgsJsonSchema(source: string): Promise<string | null> {
@@ -1,3 +1,4 @@
1
+ import { fileURLToPath } from "node:url";
1
2
  import ts from "typescript";
2
3
  import { getScriptAppTypes } from "@/apps/script-types";
3
4
  import { getScriptApiTypes, getScriptMcpTypes } from "@/be/script-connections";
@@ -127,7 +128,7 @@ export interface SwarmSdk {
127
128
  // --- repos ---
128
129
  repo_list(args?: Record<string, unknown>): Promise<unknown>;
129
130
  // --- schedules ---
130
- schedule_list(args?: { enabled?: boolean; name?: string; scheduleType?: "recurring" | "one_time"; targetType?: "agent-task" | "workflow" | "script"; workflowId?: string; scriptName?: string; hideCompleted?: boolean; consecutiveErrorsMin?: number; lastRunStatus?: "failed" | "succeeded" }): Promise<unknown>;
131
+ schedule_list(args?: { enabled?: boolean; name?: string; scheduleType?: "recurring" | "one_time"; targetType?: "agent-task" | "workflow" | "script"; workflowId?: string; scriptName?: string; hideCompleted?: boolean; consecutiveErrorsMin?: number; lastRunStatus?: "failed" | "succeeded"; includeFull?: boolean }): Promise<unknown>;
131
132
  // --- scripts ---
132
133
  script_search(args: { query?: string; scope?: ScriptScope; limit?: number }): Promise<unknown>;
133
134
  script_run(args: { name?: string; source?: string; args?: unknown; intent?: string; scope?: ScriptScope; fsMode?: ScriptFsMode; idempotencyKey?: string }): Promise<unknown>;
@@ -194,6 +195,9 @@ export interface SwarmSdk {
194
195
  slack_post(args: { channelId: string; message: string; blocks?: unknown }): Promise<unknown>;
195
196
  slack_reply(args: { channelId?: string; threadTs?: string; message: string; taskId?: string; blocks?: unknown }): Promise<unknown>;
196
197
  slack_startThread(args: { channelId: string; message: string; blocks?: unknown }): Promise<unknown>;
198
+ slack_createChannel(args: { name: string; isPrivate?: boolean }): Promise<unknown>;
199
+ slack_inviteToChannel(args: { channelId: string; userIds: string[] }): Promise<unknown>;
200
+ slack_archiveChannel(args: { channelId: string }): Promise<unknown>;
197
201
  slack_uploadFile(args: Record<string, unknown>): Promise<unknown>;
198
202
  slack_downloadFile(args: { url: string }): Promise<unknown>;
199
203
  slack_delete(args: { channelId: string; messageTs: string }): Promise<unknown>;
@@ -799,7 +803,7 @@ const RUNTIME_GLOBALS_FILE = "/virtual/runtime-globals.d.ts";
799
803
  function scriptTypesBase(): string {
800
804
  const dir = process.env.SCRIPT_TYPES_DIR;
801
805
  if (dir) return `${dir}/index.ts`;
802
- return new URL("../../index.ts", import.meta.url).pathname;
806
+ return fileURLToPath(new URL("../../index.ts", import.meta.url));
803
807
  }
804
808
 
805
809
  function createCompilerHost(
@@ -6,6 +6,7 @@
6
6
  * prompt_templates table.
7
7
  */
8
8
 
9
+ import { getPromptTemplateDefaultDrift } from "../prompts/default-drift";
9
10
  import { getAllTemplateDefinitions } from "../prompts/registry";
10
11
  import { getPromptTemplates, resetPromptTemplateToDefault, upsertPromptTemplate } from "./db";
11
12
 
@@ -23,7 +24,8 @@ import "../tools/templates";
23
24
  * For each registered EventTemplateDefinition:
24
25
  * - If no global record exists at all, insert one as default (isDefault=true, state=enabled)
25
26
  * - If a global default (isDefault=true) exists and its body differs from code, update it
26
- * - Never touch records where isDefault=false (user customizations)
27
+ * - Reclassify a customized record as default when its body is byte-identical to code
28
+ * - Warn, but never overwrite, when a genuinely customized record differs from code
27
29
  */
28
30
  export function seedDefaultTemplates(): void {
29
31
  const definitions = getAllTemplateDefinitions();
@@ -55,8 +57,29 @@ export function seedDefaultTemplates(): void {
55
57
  // Global default exists but body has drifted from code — update it.
56
58
  // Only update if the record is still marked as default (not user-customized).
57
59
  resetPromptTemplateToDefault(globalRecord.id, def.defaultBody);
60
+ } else if (!globalRecord.isDefault) {
61
+ const drift = getPromptTemplateDefaultDrift(globalRecord, def.defaultBody);
62
+ if (drift.defaultDrifted) {
63
+ const ageMs = Math.max(0, Date.now() - Date.parse(globalRecord.updatedAt));
64
+ const frozenDays = Math.floor(ageMs / 86_400_000);
65
+ const frozenHours = Math.floor(ageMs / 3_600_000);
66
+ const frozenFor =
67
+ frozenDays > 0
68
+ ? `${frozenDays} day${frozenDays === 1 ? "" : "s"}`
69
+ : `${frozenHours} hour${frozenHours === 1 ? "" : "s"}`;
70
+ const signedByteDelta = drift.byteDelta > 0 ? `+${drift.byteDelta}` : `${drift.byteDelta}`;
71
+
72
+ console.warn(
73
+ `[prompt-templates] Customized template "${def.eventType}" has drifted from its code default; frozen for ${frozenFor} since ${globalRecord.updatedAt}; byte delta ${signedByteDelta} (code default ${drift.defaultBytes} bytes, customization ${drift.customizedBytes} bytes). The customization was preserved; reset it manually to reconcile.`,
74
+ );
75
+ } else if (globalRecord.state === "enabled") {
76
+ // The row was marked customized without any content change. Restore default ownership so
77
+ // future code-default updates continue to reconcile it. Non-enabled states are deliberate
78
+ // operator choices, and resetPromptTemplateToDefault would silently enable them.
79
+ resetPromptTemplateToDefault(globalRecord.id, def.defaultBody);
80
+ }
58
81
  }
59
- // If record exists with isDefault=false (user customization): leave it alone
82
+ // If record exists with isDefault=false and a different body: never overwrite it
60
83
  // If record exists with isDefault=true and body matches: leave it alone
61
84
  }
62
85
  }
@@ -58,6 +58,12 @@ import composioGoogleDocsConfig from "../../../templates/skills/composio-google-
58
58
  import composioGoogleDocsContent from "../../../templates/skills/composio-google-docs/content.md" with {
59
59
  type: "text",
60
60
  };
61
+ import dbQueryGuidanceConfig from "../../../templates/skills/db-query-guidance/config.json" with {
62
+ type: "text",
63
+ };
64
+ import dbQueryGuidanceContent from "../../../templates/skills/db-query-guidance/content.md" with {
65
+ type: "text",
66
+ };
61
67
  import delegateWorkConfig from "../../../templates/skills/delegate-work/config.json" with {
62
68
  type: "text",
63
69
  };
@@ -270,6 +276,7 @@ const BUILT_IN_SKILL_SOURCES = [
270
276
  { config: composioGmailConfig, body: composioGmailContent },
271
277
  { config: composioGoogleCalendarConfig, body: composioGoogleCalendarContent },
272
278
  { config: composioGoogleDocsConfig, body: composioGoogleDocsContent },
279
+ { config: dbQueryGuidanceConfig, body: dbQueryGuidanceContent },
273
280
  { config: delegateWorkConfig, body: delegateWorkContent },
274
281
  { config: designDocsConfig, body: designDocsContent },
275
282
  { config: downloadTaskAttachmentConfig, body: downloadTaskAttachmentContent },
@@ -76,7 +76,7 @@ export function promoteSteeringToTask(task: AgentTask, message: SteeringMessage)
76
76
  source: message.source === "script" ? "api" : message.source,
77
77
  taskType: "follow-up",
78
78
  parentTaskId: task.id,
79
- requestedByUserId: message.createdByUserId ?? task.requestedByUserId,
79
+ requestedByUserId: message.createdByUserId,
80
80
  bypassTrackerContextDedup: true,
81
81
  });
82
82
  }
@@ -126,7 +126,19 @@ const VALIDATED_KEYS: Record<string, ConfigValidator> = {
126
126
  if (value === "sdk" || value === "bearer") return null;
127
127
  return "Invalid BEDROCK_AUTH_MODE value (must be one of: sdk, bearer)";
128
128
  },
129
+ // Logical agent concurrency policy (agent scope) — the authoritative source
130
+ // that upsertSwarmConfigWithPolicyMirror mirrors into agents.maxTasks.
131
+ // Bounded to AgentSchema's maxTasks range [1, 100] so the mirrored column
132
+ // stays schema-valid.
133
+ AGENT_MAX_TASKS: (value) => {
134
+ const str = String(value).trim();
135
+ if (!/^\d+$/.test(str) || Number(str) < 1 || Number(str) > 100) {
136
+ return "Invalid AGENT_MAX_TASKS (must be an integer between 1 and 100)";
137
+ }
138
+ return null;
139
+ },
129
140
  ...booleanValidators([
141
+ "MULTI_RUNTIME_ENABLED",
130
142
  "STEERING_ENABLED",
131
143
  "MEMORY_HYBRID_SEARCH",
132
144
  "MEMORY_GRAPH_EXPANSION",
@@ -150,6 +162,7 @@ const VALIDATED_KEYS: Record<string, ConfigValidator> = {
150
162
  "OTEL_TRACE_POLL",
151
163
  "ANONYMIZED_TELEMETRY",
152
164
  "SWARM_HIDE_CLOUD_PROMO",
165
+ "DB_QUERY_BOUNDED_ENABLED",
153
166
  ]),
154
167
  ...enumValidator("SLACK_THREAD_STEERING", ["lead", "all"]),
155
168
  ...enumValidator("SLACK_THREAD_STEERING_MODE", ["steer", "queue"]),
@@ -167,8 +180,13 @@ const VALIDATED_KEYS: Record<string, ConfigValidator> = {
167
180
  "WORKFLOW_MAX_ITERATIONS",
168
181
  "WORKFLOW_MAX_STEPS_PER_RUN",
169
182
  "SCHEDULER_INTERVAL_MS",
183
+ "RUNTIME_STALE_THRESHOLD_MIN",
170
184
  "SCRIPT_RUN_CONCURRENCY_CAP",
171
185
  "WORKER_API_READY_TIMEOUT_SECONDS",
186
+ "DB_QUERY_HTTP_BUDGET_MS",
187
+ "DB_QUERY_HTTP_MAX_ROWS",
188
+ "DB_QUERY_MCP_BUDGET_MS",
189
+ "DB_QUERY_MCP_MAX_ROWS",
172
190
  ],
173
191
  1,
174
192
  ),
@@ -209,3 +209,57 @@ export async function awaitCredentials(opts: AwaitCredentialsOptions): Promise<C
209
209
  }
210
210
  return status;
211
211
  }
212
+
213
+ /** Options for {@link retryBootStep}; injectable sleep/log keep tests deterministic. */
214
+ export interface RetryBootStepOptions {
215
+ /** Names the step in retry logs and the exhaustion error. */
216
+ label?: string;
217
+ attempts?: number;
218
+ initialDelayMs?: number;
219
+ maxDelayMs?: number;
220
+ sleep?: (ms: number) => Promise<void>;
221
+ log?: (line: string) => void;
222
+ }
223
+
224
+ /**
225
+ * Bounded retry for a recovery-critical boot step after the credential wait.
226
+ * A runtime that went stale mid-wait is only revived by a SUCCESSFUL
227
+ * registration, and the readiness report that follows is the only transition
228
+ * out of `waiting_for_credentials` — so unlike the runner's best-effort
229
+ * periodic paths, these steps retry with bounded backoff and propagate
230
+ * failure once exhausted. The caller decides what exhaustion means (the
231
+ * runner treats it like boot-registration failure rather than entering the
232
+ * work loop as though recovery succeeded).
233
+ */
234
+ export async function retryBootStep(
235
+ step: () => Promise<void>,
236
+ opts: RetryBootStepOptions = {},
237
+ ): Promise<void> {
238
+ const label = opts.label ?? "boot step";
239
+ const attempts = opts.attempts ?? 5;
240
+ const sleep = opts.sleep ?? ((ms: number) => Bun.sleep(ms));
241
+ const log = opts.log ?? ((line: string) => console.warn(line));
242
+ const maxDelayMs = opts.maxDelayMs ?? 10_000;
243
+ let delayMs = opts.initialDelayMs ?? 1_000;
244
+ let lastError: unknown;
245
+
246
+ for (let attempt = 1; attempt <= attempts; attempt++) {
247
+ try {
248
+ await step();
249
+ return;
250
+ } catch (err) {
251
+ lastError = err;
252
+ if (attempt < attempts) {
253
+ log(
254
+ `[boot] ${label} failed (attempt ${attempt}/${attempts}, retry in ${delayMs}ms): ${err}`,
255
+ );
256
+ await sleep(delayMs);
257
+ delayMs = Math.min(delayMs * 2, maxDelayMs);
258
+ }
259
+ }
260
+ }
261
+
262
+ throw lastError instanceof Error
263
+ ? lastError
264
+ : new Error(`${label} failed after ${attempts} attempt(s): ${lastError}`);
265
+ }
@@ -61,7 +61,7 @@ export function CoreCredentialsStep({ state, goToNext, addLog }: StepProps) {
61
61
  setTimeout(() => setReady(true), 1000);
62
62
  };
63
63
 
64
- generate();
64
+ generate().catch((err) => addLog(`Credential generation failed: ${err}`));
65
65
  }, [state.services, addLog]);
66
66
 
67
67
  useEffect(() => {
@@ -40,7 +40,7 @@ export function CustomTemplatesStep({
40
40
  }
41
41
  };
42
42
 
43
- load();
43
+ load().catch((err) => addLog(`Template load failed: ${err}`));
44
44
  }, [addLog, goToError]);
45
45
 
46
46
  if (subStep === "loading") {