@desplega.ai/agent-swarm 1.172.0 → 2.0.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 (525) hide show
  1. package/README.md +1 -1
  2. package/dist/{acp-adapter-fpecx1g1.js → acp-adapter-sj2g0m84.js} +18 -17
  3. package/dist/{actions-pw87a1a8.js → actions-fegk44gr.js} +65 -35
  4. package/dist/{amp-adapter-8jem2bc0.js → amp-adapter-ad8ft89x.js} +8 -7
  5. package/dist/{anthropic-messages-8ee2f22q.js → anthropic-messages-r6czkjv1.js} +7 -7
  6. package/dist/{app-wmf0m843.js → app-kfvgbvzy.js} +19 -13
  7. package/dist/{assistant-emp1e108.js → assistant-z7zdy9ja.js} +65 -38
  8. package/dist/{boot-reembed-1de4f4aj.js → boot-reembed-5c2z0mbe.js} +11 -9
  9. package/dist/{boot-reembed-4ymn9atn.js → boot-reembed-j8xa181e.js} +11 -9
  10. package/dist/{boot-scrub-sweep-adhk1s8c.js → boot-scrub-sweep-hnkn3rgt.js} +12 -10
  11. package/dist/{boot-seeds-8q637aew.js → boot-seeds-kwjq4d2z.js} +15 -13
  12. package/dist/{claude-adapter-t26cv8a4.js → claude-adapter-9vmvpvgd.js} +20 -19
  13. package/dist/{claude-managed-adapter-xgxhqrt2.js → claude-managed-adapter-k9qae05j.js} +7 -5
  14. package/dist/{claude-managed-setup-bnmjarjc.js → claude-managed-setup-q54zx5q2.js} +6 -4
  15. package/dist/{claude-sdk-session-gsfsh662.js → claude-sdk-session-9jye6cxr.js} +12698 -11995
  16. package/dist/{cli-k46r9hmk.js → cli-03gedhng.js} +16 -14
  17. package/dist/{cli-kb4ehzx5.js → cli-1e4b6bzh.js} +7 -7
  18. package/dist/{cli-d603qf86.js → cli-29jc5q7w.js} +10 -8
  19. package/dist/cli-2m1ktfzz.js +17795 -0
  20. package/dist/{cli-8vs9trd4.js → cli-3dj8k4vy.js} +1 -1
  21. package/dist/{cli-zxm9p3x4.js → cli-3fbmmawm.js} +5 -5
  22. package/dist/{cli-gmdajfw5.js → cli-4jxm2qtz.js} +4987 -1708
  23. package/dist/{cli-tadfgd0t.js → cli-5hkm7whv.js} +33 -4
  24. package/dist/{cli-6jb2rf77.js → cli-5t4f29ez.js} +3 -3
  25. package/dist/{cli-7a0sde2x.js → cli-6cagzc96.js} +9 -0
  26. package/dist/cli-6cvfxs8r.js +47 -0
  27. package/dist/{cli-pj6x9qce.js → cli-6jyj59nb.js} +2 -2
  28. package/dist/{cli-388egyf6.js → cli-6razj6t4.js} +2 -2
  29. package/dist/{cli-qsztpfry.js → cli-74ahz61x.js} +2 -2
  30. package/dist/{cli-4nz8p1p5.js → cli-7x0g4sn1.js} +15474 -16082
  31. package/dist/{cli-yatxqynm.js → cli-9ha6hs2d.js} +1 -290
  32. package/dist/{cli-81r1wjyv.js → cli-9nfx44eh.js} +1 -1
  33. package/dist/cli-9traj3dn.js +638 -0
  34. package/dist/{cli-smcrrmhy.js → cli-agzebbd5.js} +9 -9
  35. package/dist/{cli-bf7485pm.js → cli-apx87jne.js} +2 -2
  36. package/dist/{cli-ztrv9y21.js → cli-bzszjf2p.js} +3689 -1081
  37. package/dist/{cli-c5km5znr.js → cli-c8r3kzzb.js} +1 -1
  38. package/dist/{cli-hsem16hb.js → cli-cgtksawx.js} +7 -4
  39. package/dist/{cli-db3m6zdt.js → cli-d2qr7aj4.js} +1 -1
  40. package/dist/{cli-1hgat6pa.js → cli-d5f9ed5c.js} +5 -5
  41. package/dist/cli-e3g5mbkz.js +776 -0
  42. package/dist/{cli-7jykv7ef.js → cli-e6mpjhed.js} +1 -1
  43. package/dist/{cli-j6kpj9az.js → cli-ee9gtz70.js} +1553 -68
  44. package/dist/{cli-e0a9sk00.js → cli-eejsgvm4.js} +4 -4
  45. package/dist/{cli-tgpfnm6r.js → cli-ef1hg1wv.js} +518 -59
  46. package/dist/cli-f527ce74.js +781 -0
  47. package/dist/{cli-ppy4nnnh.js → cli-f7kr7svz.js} +2 -2
  48. package/dist/cli-hjwmhcg1.js +10622 -0
  49. package/dist/{cli-2bw3w47j.js → cli-hnhtk6cj.js} +1 -1
  50. package/dist/{cli-ywm1azg9.js → cli-hnmhtzvh.js} +6 -4
  51. package/dist/{cli-4fnb6884.js → cli-hvqbtna4.js} +7 -1259
  52. package/dist/{cli-pstq9888.js → cli-j0pfptdp.js} +220 -103
  53. package/dist/cli-jta2eyzc.js +619 -0
  54. package/dist/{cli-esnpehf5.js → cli-k382tshb.js} +12 -13
  55. package/dist/cli-k3dp5tpf.js +263 -0
  56. package/dist/{cli-wd0hz6ah.js → cli-kbrt8gqa.js} +9 -91
  57. package/dist/cli-kceah1eh.js +92 -0
  58. package/dist/{cli-prrt9fa0.js → cli-ke896fcf.js} +10 -10
  59. package/dist/{cli-nv4zd0b9.js → cli-kt6zqg0e.js} +6 -6
  60. package/dist/{cli-g6t9texm.js → cli-m50t4309.js} +5 -18
  61. package/dist/cli-n2wq4j7m.js +6985 -0
  62. package/dist/{cli-c2pq2rjb.js → cli-n3hzg89b.js} +46 -15
  63. package/dist/{cli-tmhsxhb8.js → cli-n5vwg842.js} +23 -6
  64. package/dist/{cli-5rv1j9ga.js → cli-ne236afy.js} +53 -7
  65. package/dist/cli-p0sg24zp.js +16 -0
  66. package/dist/{cli-d161g69q.js → cli-p1x85d2t.js} +50 -2
  67. package/dist/{cli-7pz3b7x5.js → cli-przpmdgr.js} +25 -0
  68. package/dist/{cli-g4f5pj26.js → cli-q0ke1yww.js} +111 -25
  69. package/dist/{cli-e3pmyt39.js → cli-q50942rs.js} +1 -1
  70. package/dist/{cli-r44t9wqc.js → cli-r106725k.js} +2 -2
  71. package/dist/{cli-gj81e47t.js → cli-r2pnvj8g.js} +2 -2
  72. package/dist/{cli-r8smj8b8.js → cli-r62a35ea.js} +19 -3
  73. package/dist/{cli-cd0hv9nh.js → cli-ra0xzggj.js} +2 -2
  74. package/dist/cli-rhs9pz33.js +294 -0
  75. package/dist/{cli-nyhb1r6x.js → cli-s2sp957d.js} +52 -29
  76. package/dist/{cli-1x7da0de.js → cli-s4setq91.js} +8 -8
  77. package/dist/{cli-xczpdp9e.js → cli-se8w6wny.js} +6 -6
  78. package/dist/cli-skxvvpap.js +10140 -0
  79. package/dist/{cli-014vns2y.js → cli-stfs0k5s.js} +208 -525
  80. package/dist/{cli-0p3enk35.js → cli-tqaazemc.js} +9 -1
  81. package/dist/{cli-cf69s3ew.js → cli-tr4sxp68.js} +3 -3
  82. package/dist/{cli-8hzfb6xz.js → cli-tsde1hdr.js} +15 -15
  83. package/dist/{cli-cceskcvt.js → cli-txqb9s38.js} +8 -8
  84. package/dist/{cli-aspcaef3.js → cli-v1yb0rqb.js} +6 -6
  85. package/dist/{cli-j1pmc07z.js → cli-vatyhshy.js} +268 -40
  86. package/dist/{cli-04emfe2q.js → cli-vdach5a9.js} +5 -5
  87. package/dist/{cli-7avy4v8w.js → cli-w1q7h2r2.js} +5 -3
  88. package/dist/{cli-rcp5aqdw.js → cli-w7m81tb7.js} +1 -1
  89. package/dist/{cli-hwwvy36w.js → cli-wayw93nt.js} +3 -3
  90. package/dist/cli-wmgrkscj.js +23254 -0
  91. package/dist/{cli-gp1rtyvk.js → cli-z5jjpaxw.js} +29 -6
  92. package/dist/{cli-451ks6aw.js → cli-zkpks3r3.js} +1 -1
  93. package/dist/cli.js +32 -48
  94. package/dist/{codex-adapter-m4tgdysy.js → codex-adapter-y81sssk0.js} +16 -15
  95. package/dist/{codex-login-841kv5fk.js → codex-login-f2rcfpp9.js} +3 -2
  96. package/dist/{codex-session-runner-ymwzm6ea.js → codex-session-runner-8vx4j6a9.js} +16 -15
  97. package/dist/{commands-n0hshcvs.js → commands-kcpvpcrr.js} +21 -10
  98. package/dist/{controller-hq5ggpcp.js → controller-ywrmt6yd.js} +5 -4
  99. package/dist/{controllers-connect-y07cqcvy.js → controllers-connect-kwpvj3rf.js} +45 -38
  100. package/dist/{cursor-adapter-q8pgv5qa.js → cursor-adapter-cehv1qns.js} +7 -6
  101. package/dist/{db-hkqvrdcb.js → db-k8ankj20.js} +13 -11
  102. package/dist/{db-4w7tmt1g.js → db-ww5d7jq3.js} +29 -9
  103. package/dist/{devin-adapter-xgtj680j.js → devin-adapter-x1022qb1.js} +3 -2
  104. package/dist/{dispatcher-5sd69y14.js → dispatcher-e8gq5m68.js} +41 -34
  105. package/dist/{docker-f503k9p0.js → docker-ff6y1190.js} +3 -2
  106. package/dist/{dsh-adapter-2h8m4cab.js → dsh-adapter-3epjm3af.js} +7 -6
  107. package/dist/{e2b-66xra4az.js → e2b-8ygwhg0f.js} +4 -3
  108. package/dist/{e2b-xwhyj8cw.js → e2b-swhnpw3n.js} +4 -3
  109. package/dist/{event-bus-vjtv82gm.js → event-bus-6axnngdj.js} +3 -2
  110. package/dist/external-channel-h3359cwn.js +52 -0
  111. package/dist/{google-generative-ai-7pavrbv7.js → google-generative-ai-g8db7x0f.js} +5 -3
  112. package/dist/{google-vertex-3f1jk41f.js → google-vertex-r2692q0r.js} +5 -3
  113. package/dist/{grok-adapter-8m9yqzbq.js → grok-adapter-0d4x7gkx.js} +19 -18
  114. package/dist/{handlers-s20xr10r.js → handlers-an1gzv21.js} +44 -37
  115. package/dist/{heartbeat-3k31q4g2.js → heartbeat-6dakeb61.js} +46 -39
  116. package/dist/{hook-gdn6a2fn.js → hook-6v6cv6d5.js} +12 -24
  117. package/dist/{hook-ygwerrdh.js → hook-sa5xye4y.js} +13 -12
  118. package/dist/{http-jdteeg81.js → http-r7f3eky0.js} +1598 -279
  119. package/dist/index-52j1aja4.js +3465 -0
  120. package/dist/{index-e0snym4t.js → index-7ep4gzdk.js} +41 -34
  121. package/dist/index-9gw58n4k.js +193 -0
  122. package/dist/index-9j87mcm8.js +56305 -0
  123. package/dist/index-bsmcresk.js +79424 -0
  124. package/dist/index-mh85qhec.js +475 -0
  125. package/dist/{index-vm3bm9t7.js → index-rmpw9a8q.js} +41 -34
  126. package/dist/{index-6ksewd5y.js → index-s2wxgzr0.js} +1015 -732
  127. package/dist/{index-tsszt7g9.js → index-v1v6n7hg.js} +46 -39
  128. package/dist/index-we27kd31.js +12810 -0
  129. package/dist/{index-ffqzqgwb.js → index-zk96aq1k.js} +11 -9
  130. package/dist/{keepalive-jekc896t.js → keepalive-be9vwy5h.js} +13 -11
  131. package/dist/{lead-4k9s9szx.js → lead-0861cwrf.js} +64 -57
  132. package/dist/{maintenance-shazch4n.js → maintenance-mzzgc8bn.js} +11 -9
  133. package/dist/node-c9we95xf.js +1 -1
  134. package/dist/{node-fkajaq96.js → node-mn1zs7an.js} +2 -1
  135. package/dist/{oauth-refresh-sweep-5gb36kn8.js → oauth-refresh-sweep-cpdnfwdj.js} +12 -10
  136. package/dist/{onboard-ydrbnvww.js → onboard-fh8qs4r3.js} +11 -3
  137. package/dist/{opencode-adapter-5zh0he20.js → opencode-adapter-nm42wwj8.js} +19 -14
  138. package/dist/{otel-impl-6pf5cv86.js → otel-impl-x6c1c9g4.js} +248 -869
  139. package/dist/{pi-mono-adapter-ef9wwx9m.js → pi-mono-adapter-9n2ya240.js} +245 -107
  140. package/dist/{pricing-refresh-s52nsa5q.js → pricing-refresh-qb0rv0ap.js} +13 -11
  141. package/dist/{rbac-roles-aq3msh4e.js → rbac-roles-1gzdwbh1.js} +12 -10
  142. package/dist/{rbac-roles-q5gb5e8v.js → rbac-roles-3mraey52.js} +13 -11
  143. package/dist/{render-v2-pd1rg54q.js → render-v2-agpa45zz.js} +19 -13
  144. package/dist/{resume-gw4a2c6v.js → resume-x0ydy9s9.js} +27 -21
  145. package/dist/{runtime-qfry88v2.js → runtime-sa2vr16g.js} +2 -2
  146. package/dist/{setup-jda1ksdh.js → setup-as9ga0d6.js} +2 -2
  147. package/dist/{virtual-modules-b3ga2m65.js → virtual-modules-9gkxqrap.js} +7 -6
  148. package/dist/{work-objects-tb7ycye8.js → work-objects-y5g1y7ev.js} +49 -37
  149. package/dist/{worker-njjccz5d.js → worker-860c3e38.js} +64 -57
  150. package/dist/{x-qfmygqqv.js → x-m477fr0h.js} +3 -2
  151. package/openapi.json +951 -356
  152. package/package.json +12 -13
  153. package/src/apps/catalog.generated.json +1309 -53
  154. package/src/apps/page-validator.ts +93 -31
  155. package/src/azure-devops/api.ts +94 -18
  156. package/src/azure-devops/auth.ts +32 -0
  157. package/src/azure-devops/boards-sync.ts +708 -0
  158. package/src/azure-devops/handlers.ts +52 -8
  159. package/src/azure-devops/html.ts +26 -0
  160. package/src/azure-devops/index.ts +15 -0
  161. package/src/azure-devops/templates.ts +86 -0
  162. package/src/azure-devops/types.ts +65 -1
  163. package/src/be/config-refs.ts +60 -0
  164. package/src/be/db/page-versions.ts +15 -0
  165. package/src/be/db/runtime.ts +35 -8
  166. package/src/be/db/skill-name-resolution.ts +36 -0
  167. package/src/be/db/tasks/read.ts +230 -0
  168. package/src/be/db/tasks/write.ts +7 -0
  169. package/src/be/db.ts +74 -13
  170. package/src/be/migrations/213_script_connections_sql.sql +79 -0
  171. package/src/be/migrations/215_swarm_config_export_to_env.sql +34 -0
  172. package/src/be/migrations/216_task_resolved_effort.sql +5 -0
  173. package/src/be/migrations/217_chat_sdk_state.sql +104 -0
  174. package/src/be/model-catalog-store.ts +5 -1
  175. package/src/be/model-tier-resolution.ts +12 -1
  176. package/src/be/model-validation.ts +5 -2
  177. package/src/be/models-catalog.ts +26 -1
  178. package/src/be/modelsdev-cache.json +198 -63
  179. package/src/be/modelsdev-cache.ts +9 -1
  180. package/src/be/script-connections.ts +47 -8
  181. package/src/be/script-credential-broker.ts +39 -2
  182. package/src/be/scripts/run-saved.ts +1 -0
  183. package/src/be/scripts/typecheck.ts +10 -8
  184. package/src/be/seed/runner.ts +13 -1
  185. package/src/be/seed/state-db.ts +5 -0
  186. package/src/be/seed/types.ts +6 -0
  187. package/src/be/seed-pricing.ts +19 -2
  188. package/src/be/seed-scripts/catalog/ops-catalog-audit.inline.ts +1 -1
  189. package/src/be/seed-scripts/catalog/ops-catalog-audit.ts +1 -1
  190. package/src/be/seed-skills/bundled-files.generated.json +10 -18
  191. package/src/be/seed-skills/index.ts +107 -3
  192. package/src/be/sql-connections.ts +118 -0
  193. package/src/be/swarm-config-guard.ts +53 -0
  194. package/src/cli.tsx +0 -16
  195. package/src/commands/onboard/non-interactive.ts +4 -2
  196. package/src/commands/profile-sync.ts +5 -3
  197. package/src/commands/provider-credentials.ts +52 -6
  198. package/src/commands/runner.ts +196 -56
  199. package/src/e2b/env.ts +5 -0
  200. package/src/extensions/contract-types.generated.ts +1 -1
  201. package/src/extensions/contract.ts +45 -0
  202. package/src/extensions/dispatcher.ts +7 -0
  203. package/src/extensions/post-bridge.ts +40 -0
  204. package/src/github/handlers.ts +11 -4
  205. package/src/heartbeat/templates.ts +8 -5
  206. package/src/hooks/hook.ts +0 -26
  207. package/src/http/all-routes.ts +0 -1
  208. package/src/http/config.ts +36 -17
  209. package/src/http/core.ts +4 -0
  210. package/src/http/index.ts +14 -2
  211. package/src/http/integrations.ts +88 -1
  212. package/src/http/mcp-oauth.ts +6 -13
  213. package/src/http/mcp.ts +15 -5
  214. package/src/http/page-proxy.ts +2 -4
  215. package/src/http/pages-public.ts +6 -5
  216. package/src/http/pages.ts +5 -14
  217. package/src/http/poll.ts +28 -68
  218. package/src/http/requested-by.ts +54 -0
  219. package/src/http/route-def.ts +7 -6
  220. package/src/http/script-connection-proxy.ts +51 -4
  221. package/src/http/script-connections.ts +5 -2
  222. package/src/http/scripts.ts +1 -0
  223. package/src/http/session-cost-recompute.ts +12 -2
  224. package/src/http/session-data.ts +4 -0
  225. package/src/http/stats.ts +1 -30
  226. package/src/http/tasks.ts +61 -5
  227. package/src/http/utils.ts +14 -0
  228. package/src/http/webhooks.ts +183 -2
  229. package/src/http/x.ts +1 -0
  230. package/src/integrations/chat-sdk/adapters.ts +129 -0
  231. package/src/integrations/chat-sdk/attachments.ts +232 -0
  232. package/src/integrations/chat-sdk/background.ts +36 -0
  233. package/src/integrations/chat-sdk/commands.ts +194 -0
  234. package/src/integrations/chat-sdk/config.ts +198 -0
  235. package/src/integrations/chat-sdk/discord-api.ts +63 -0
  236. package/src/integrations/chat-sdk/discord-gateway.ts +185 -0
  237. package/src/integrations/chat-sdk/handlers.ts +28 -0
  238. package/src/integrations/chat-sdk/inbound.ts +426 -0
  239. package/src/integrations/chat-sdk/limits.ts +48 -0
  240. package/src/integrations/chat-sdk/logger.ts +81 -0
  241. package/src/integrations/chat-sdk/outbound.ts +123 -0
  242. package/src/integrations/chat-sdk/relay.ts +172 -0
  243. package/src/integrations/chat-sdk/router.ts +169 -0
  244. package/src/integrations/chat-sdk/runtime.ts +351 -0
  245. package/src/integrations/chat-sdk/schema.ts +245 -0
  246. package/src/integrations/chat-sdk/state.ts +243 -0
  247. package/src/integrations/chat-sdk/status.ts +244 -0
  248. package/src/integrations/chat-sdk/steering.ts +58 -0
  249. package/src/integrations/chat-sdk/teams-origin.ts +77 -0
  250. package/src/integrations/chat-sdk/telegram-api.ts +56 -0
  251. package/src/integrations/chat-sdk/templates.ts +16 -0
  252. package/src/integrations/chat-sdk/test-hooks.ts +37 -0
  253. package/src/integrations/chat-sdk/thread-buffer.ts +100 -0
  254. package/src/integrations/chat-sdk/typing.ts +65 -0
  255. package/src/jira/adf.ts +3 -1
  256. package/src/kv-reserved-namespaces.ts +2 -0
  257. package/src/oauth/mcp-wrapper.ts +10 -6
  258. package/src/otel-impl.ts +2 -1
  259. package/src/pages/version.ts +4 -5
  260. package/src/prompts/base-prompt.ts +111 -45
  261. package/src/providers/acp-target-catalog.ts +6 -0
  262. package/src/providers/acp-targets.ts +56 -3
  263. package/src/providers/amp-adapter.ts +1 -1
  264. package/src/providers/claude-adapter.ts +27 -2
  265. package/src/providers/claude-flag-settings.ts +133 -0
  266. package/src/providers/claude-managed-adapter.ts +1 -1
  267. package/src/providers/claude-sdk-session.ts +7 -0
  268. package/src/providers/claude-session-events.ts +6 -1
  269. package/src/providers/dsh-adapter.ts +1 -1
  270. package/src/providers/opencode-adapter.ts +268 -37
  271. package/src/providers/opencode-billing.ts +86 -0
  272. package/src/providers/opencode-swarm-plugin.ts +51 -0
  273. package/src/providers/pi-mono-adapter.ts +398 -153
  274. package/src/providers/pricing-sources.md +37 -3
  275. package/src/providers/reasoning-effort.ts +27 -0
  276. package/src/providers/types.ts +11 -2
  277. package/src/rbac/legacy-policy.ts +8 -2
  278. package/src/rbac/permissions.ts +21 -0
  279. package/src/scheduler/scheduler.ts +4 -2
  280. package/src/scripts-runtime/api-client.ts +15 -1
  281. package/src/scripts-runtime/api-types.ts +9 -1
  282. package/src/scripts-runtime/ctx.ts +1 -1
  283. package/src/scripts-runtime/loader.ts +19 -7
  284. package/src/scripts-runtime/mcp-client.ts +30 -19
  285. package/src/scripts-runtime/sdk-allowlist.ts +8 -6
  286. package/src/scripts-runtime/swarm-sdk.ts +0 -11
  287. package/src/scripts-runtime/types/stdlib.d.ts +27 -15
  288. package/src/scripts-runtime/types/swarm-extension.d.ts +63 -1
  289. package/src/scripts-runtime/types/swarm-sdk.d.ts +27 -15
  290. package/src/server.ts +52 -18
  291. package/src/slack/actions.ts +29 -1
  292. package/src/slack/app.ts +14 -2
  293. package/src/slack/assistant.ts +36 -2
  294. package/src/slack/channel-join.ts +3 -16
  295. package/src/slack/commands.ts +9 -1
  296. package/src/slack/event-dedup.ts +1 -1
  297. package/src/slack/external-channel-egress.ts +113 -0
  298. package/src/slack/external-channel.ts +309 -0
  299. package/src/slack/handlers.ts +61 -0
  300. package/src/slack/render-v2.ts +40 -47
  301. package/src/slack/work-objects.ts +5 -0
  302. package/src/tasks/additive-buffer.ts +4 -2
  303. package/src/tasks/context-key.ts +94 -2
  304. package/src/tasks/model-pin.ts +67 -0
  305. package/src/tasks/worker-follow-up.ts +4 -31
  306. package/src/telemetry.ts +2 -1
  307. package/src/tests/acp-adapter.test.ts +42 -0
  308. package/src/tests/api-drain-worker-e2e.test.ts +1 -6
  309. package/src/tests/apps-catalog-feedback.test.ts +310 -0
  310. package/src/tests/apps-catalog-inputs.test.ts +70 -0
  311. package/src/tests/apps-catalog-table.test.ts +113 -0
  312. package/src/tests/azure-devops-boards-sync.test.ts +584 -0
  313. package/src/tests/azure-devops-html.test.ts +13 -0
  314. package/src/tests/azure-devops-webhook-malformed.test.ts +4 -0
  315. package/src/tests/base-prompt.test.ts +58 -54
  316. package/src/tests/chat-sdk-commands.test.ts +191 -0
  317. package/src/tests/chat-sdk-config.test.ts +334 -0
  318. package/src/tests/chat-sdk-discord-api.test.ts +68 -0
  319. package/src/tests/chat-sdk-discord-gateway.test.ts +252 -0
  320. package/src/tests/chat-sdk-discord-webhook.test.ts +126 -0
  321. package/src/tests/chat-sdk-fields-sync.test.ts +50 -0
  322. package/src/tests/chat-sdk-harness.ts +146 -0
  323. package/src/tests/chat-sdk-inbound.test.ts +363 -0
  324. package/src/tests/chat-sdk-relay.test.ts +470 -0
  325. package/src/tests/chat-sdk-reload.test.ts +128 -0
  326. package/src/tests/chat-sdk-shutdown.test.ts +220 -0
  327. package/src/tests/chat-sdk-slack.test.ts +268 -0
  328. package/src/tests/chat-sdk-state.test.ts +163 -0
  329. package/src/tests/chat-sdk-status-route.test.ts +382 -0
  330. package/src/tests/chat-sdk-teams.test.ts +277 -0
  331. package/src/tests/chat-sdk-tools.test.ts +520 -0
  332. package/src/tests/chat-sdk-typing.test.ts +120 -0
  333. package/src/tests/chat-sdk-webhook-telegram.test.ts +200 -0
  334. package/src/tests/claude-adapter-binary.test.ts +144 -0
  335. package/src/tests/claude-flag-settings.test.ts +114 -0
  336. package/src/tests/claude-managed-adapter.test.ts +1 -1
  337. package/src/tests/claude-sdk-lifecycle.test.ts +148 -1
  338. package/src/tests/claude-sdk-process.fixture.ts +7 -0
  339. package/src/tests/claude-thinking-tokens.test.ts +57 -0
  340. package/src/tests/claude-worker-parity.test.ts +2 -7
  341. package/src/tests/config-env-export-chat-sdk.test.ts +11 -0
  342. package/src/tests/config-refs.test.ts +66 -0
  343. package/src/tests/context-key.test.ts +51 -0
  344. package/src/tests/cors-allowlist.test.ts +25 -0
  345. package/src/tests/create-page-tool.test.ts +18 -0
  346. package/src/tests/credential-check.test.ts +139 -0
  347. package/src/tests/credentials.test.ts +21 -0
  348. package/src/tests/db-runtime.test.ts +61 -0
  349. package/src/tests/db-task-read-contract.test.ts +20 -0
  350. package/src/tests/db-task-write-contract.test.ts +10 -0
  351. package/src/tests/deferred-task-wake.test.ts +52 -4
  352. package/src/tests/docs-index-footprint.test.js +3 -3
  353. package/src/tests/dsh-adapter.test.ts +1 -1
  354. package/src/tests/enabled-capabilities.test.ts +20 -0
  355. package/src/tests/entrypoint-api-readiness.test.ts +14 -8
  356. package/src/tests/entrypoint-config-env-export.test.ts +5 -2
  357. package/src/tests/entrypoint-export-to-env.test.ts +78 -0
  358. package/src/tests/explicit-model-validation.test.ts +105 -1
  359. package/src/tests/extensions-example-route-chat-channel.test.ts +154 -0
  360. package/src/tests/extensions-post-bridge.test.ts +51 -0
  361. package/src/tests/fetch-resolved-env.test.ts +128 -0
  362. package/src/tests/fixtures/azure-boards/workitem.commented.json +89 -0
  363. package/src/tests/fixtures/azure-boards/workitem.created.json +81 -0
  364. package/src/tests/fixtures/azure-boards/workitem.deleted.json +70 -0
  365. package/src/tests/fixtures/azure-boards/workitem.restored.json +82 -0
  366. package/src/tests/fixtures/azure-boards/workitem.updated.json +130 -0
  367. package/src/tests/fixtures/catalog-feedback-definition.json.txt +860 -0
  368. package/src/tests/fixtures/catalog-inputs-definition.json.txt +910 -0
  369. package/src/tests/fixtures/catalog-table-definition.json.txt +468 -0
  370. package/src/tests/fixtures/chat-sdk/telegram-private-text.json +10 -0
  371. package/src/tests/fixtures/extensions/route-chat-channel-to-agent/hooks.ts +27 -0
  372. package/src/tests/fixtures/extensions/route-chat-channel-to-agent/manifest.json +9 -0
  373. package/src/tests/fixtures/openai-compatible-registry-extension.child.ts +146 -0
  374. package/src/tests/fixtures/runner-identity-refresh.ts +3 -0
  375. package/src/tests/fixtures/runner-legacy-resume-prompt.ts +122 -0
  376. package/src/tests/github-handlers.test.ts +35 -0
  377. package/src/tests/harness-provider-resolution.test.ts +12 -0
  378. package/src/tests/heartbeat-checklist.test.ts +3 -2
  379. package/src/tests/hook-runtime-close.test.ts +0 -2
  380. package/src/tests/http-api-integration.test.ts +0 -25
  381. package/src/tests/migration-046-budgets.test.ts +17 -16
  382. package/src/tests/migration-217-chat-sdk-state.test.ts +113 -0
  383. package/src/tests/model-tier-resolution.test.ts +93 -0
  384. package/src/tests/models-catalog.test.ts +42 -1
  385. package/src/tests/node-env-runtime.test.ts +98 -0
  386. package/src/tests/openai-compatible-providers.test.ts +134 -0
  387. package/src/tests/openai-compatible-registry-extension.test.ts +42 -0
  388. package/src/tests/opencode-adapter.test.ts +348 -3
  389. package/src/tests/opencode-billing.test.ts +28 -0
  390. package/src/tests/opencode-helmcode-sdk.test.ts +201 -0
  391. package/src/tests/opencode-shell-env.test.ts +145 -0
  392. package/src/tests/openrouter-base-url.test.ts +101 -151
  393. package/src/tests/page-proxy.test.ts +1 -1
  394. package/src/tests/pages-browser-sdk.test.ts +76 -0
  395. package/src/tests/pages-feedback.test.ts +1 -1
  396. package/src/tests/paused-tasks-requested-by.test.ts +102 -0
  397. package/src/tests/pi-custom-provider-fake-server.test.ts +442 -0
  398. package/src/tests/pi-mono-adapter.test.ts +385 -5
  399. package/src/tests/pi-shell-env.test.ts +170 -0
  400. package/src/tests/prompt-template-remaining.test.ts +35 -0
  401. package/src/tests/rbac-engine.test.ts +11 -3
  402. package/src/tests/runner-exit-after-tasks.test.ts +0 -4
  403. package/src/tests/runner-identity-refresh.test.ts +3 -0
  404. package/src/tests/runner-legacy-resume-prompt.test.ts +64 -0
  405. package/src/tests/script-connections-sql.test.ts +675 -0
  406. package/src/tests/script-connections.test.ts +7 -1
  407. package/src/tests/scripts-http.test.ts +201 -1
  408. package/src/tests/scripts-only-gating.test.ts +2 -3
  409. package/src/tests/scripts-runtime.test.ts +58 -40
  410. package/src/tests/secret-scrubber.test.ts +86 -0
  411. package/src/tests/seed-skills-bundled-files.test.ts +45 -53
  412. package/src/tests/seed-skills-retire.test.ts +182 -0
  413. package/src/tests/send-task-requester-gate.test.ts +58 -0
  414. package/src/tests/session-cost-recompute-unit.test.ts +94 -0
  415. package/src/tests/skill-get-name-resolution.test.ts +137 -0
  416. package/src/tests/slack-channel-join.test.ts +4 -1
  417. package/src/tests/slack-external-channel-config.test.ts +65 -0
  418. package/src/tests/slack-external-channel-egress.test.ts +237 -0
  419. package/src/tests/slack-external-channel-guard.test.ts +280 -0
  420. package/src/tests/slack-external-channel-tools.test.ts +173 -0
  421. package/src/tests/slack-external-channel.test.ts +370 -0
  422. package/src/tests/slack-render-v2.test.ts +86 -10
  423. package/src/tests/sonnet-5-5-registry.test.ts +9 -1
  424. package/src/tests/spec-base-url.test.ts +16 -1
  425. package/src/tests/swarm-config-export-to-env.test.ts +267 -0
  426. package/src/tests/swarm-config-reserved-keys.test.ts +16 -0
  427. package/src/tests/swarm-diff.test.ts +1 -1
  428. package/src/tests/task-model-effort-rerun.test.ts +164 -0
  429. package/src/tests/task-session-prompts.test.ts +132 -0
  430. package/src/tests/task-tool-manifest.test.ts +14 -0
  431. package/src/tests/task-tools-ctx.test.ts +2 -0
  432. package/src/tests/tool-annotations.test.ts +6 -6
  433. package/src/tests/ui-logs-parser.test.ts +43 -0
  434. package/src/tests/used-by-route.test.ts +6 -0
  435. package/src/tests/vendored-openapi.test.ts +16 -1
  436. package/src/tests/vercel-ai-gateway.test.ts +149 -0
  437. package/src/tests/workflow-swarm-script.test.ts +38 -0
  438. package/src/tests/workflow-wait-filter.test.ts +5 -3
  439. package/src/tools/chat-access.ts +41 -0
  440. package/src/tools/chat-open-dm.ts +59 -0
  441. package/src/tools/chat-post.ts +74 -0
  442. package/src/tools/chat-read-thread.ts +100 -0
  443. package/src/tools/chat-reply.ts +75 -0
  444. package/src/tools/create-page.ts +3 -13
  445. package/src/tools/defer-task.ts +17 -0
  446. package/src/tools/model-catalog/index.ts +2 -1
  447. package/src/tools/register-chat-channel.ts +139 -0
  448. package/src/tools/send-task.ts +24 -0
  449. package/src/tools/skills/skill-get.ts +17 -9
  450. package/src/tools/slack-post.ts +6 -0
  451. package/src/tools/slack-read.ts +8 -0
  452. package/src/tools/slack-reply.ts +6 -0
  453. package/src/tools/slack-start-thread.ts +6 -0
  454. package/src/tools/slack-update.ts +6 -0
  455. package/src/tools/slack-upload-file.ts +6 -0
  456. package/src/tools/swarm-config/get-config.ts +2 -1
  457. package/src/tools/swarm-config/list-config.ts +2 -1
  458. package/src/tools/swarm-config/set-config.ts +16 -3
  459. package/src/tools/tool-config.ts +8 -6
  460. package/src/types.ts +12 -2
  461. package/src/utils/config-env-export.ts +74 -0
  462. package/src/utils/credentials.test.ts +92 -0
  463. package/src/utils/credentials.ts +52 -4
  464. package/src/utils/harness-provider.ts +15 -6
  465. package/src/utils/node-env.ts +24 -0
  466. package/src/utils/runtime-model-catalog.ts +11 -1
  467. package/src/utils/secret-scrubber.ts +71 -2
  468. package/src/utils/task-tool-manifest.ts +19 -2
  469. package/src/workflows/executors/swarm-script.ts +1 -0
  470. package/templates/skills/agent-browser/SKILL.md +1 -1
  471. package/templates/skills/agent-browser/content.md +1 -1
  472. package/templates/skills/apps/SKILL.md +749 -0
  473. package/templates/skills/apps/content.md +60 -22
  474. package/templates/skills/chat-sdk/config.json +14 -0
  475. package/templates/skills/chat-sdk/content.md +84 -0
  476. package/templates/skills/composio-gmail/content.md +7 -0
  477. package/templates/skills/composio-google-docs/content.md +1 -1
  478. package/templates/skills/email-domain-health/config.json +14 -0
  479. package/templates/skills/email-domain-health/content.md +131 -0
  480. package/templates/skills/kv-storage/content.md +2 -2
  481. package/templates/skills/pages/content.md +8 -10
  482. package/templates/skills/qa-loom/config.json +14 -0
  483. package/templates/skills/qa-loom/content.md +187 -0
  484. package/templates/skills/qa-loom/files/build-loom.mjs +221 -0
  485. package/templates/skills/qa-loom/files/run-qa.sh +57 -0
  486. package/templates/skills/slack-interaction/content.md +4 -0
  487. package/templates/skills/swarm-extensions/content.md +2 -0
  488. package/templates/skills/swarm-scripts/SKILL.md +4 -2
  489. package/templates/skills/swarm-scripts/config.json +1 -1
  490. package/templates/skills/swarm-scripts/content.md +4 -2
  491. package/templates/skills/workflow-iterate/content.md +38 -0
  492. package/dist/artifact-eczsknc3.js +0 -3843
  493. package/dist/cli-9xsmdpdt.js +0 -56377
  494. package/dist/cli-bd957dgb.js +0 -153
  495. package/dist/cli-q51yprfq.js +0 -1104
  496. package/dist/cli-t1ze64h3.js +0 -811
  497. package/dist/index-9ch1n761.js +0 -1102
  498. package/src/artifact-sdk/index.ts +0 -2
  499. package/src/artifact-sdk/localtunnel.d.ts +0 -20
  500. package/src/artifact-sdk/port.ts +0 -12
  501. package/src/artifact-sdk/server.ts +0 -183
  502. package/src/artifact-sdk/tunnel.ts +0 -19
  503. package/src/commands/artifact.ts +0 -249
  504. package/src/http/ecosystem.ts +0 -77
  505. package/src/tests/artifact-commands.test.ts +0 -92
  506. package/src/tests/artifact-sdk.test.ts +0 -793
  507. package/src/tests/register-service-security.test.ts +0 -194
  508. package/src/tools/list-services.ts +0 -107
  509. package/src/tools/register-service.ts +0 -177
  510. package/src/tools/unregister-service.ts +0 -73
  511. package/src/tools/update-service-status.ts +0 -96
  512. package/templates/skills/artifacts/config.json +0 -14
  513. package/templates/skills/artifacts/content.md +0 -344
  514. package/templates/skills/artifacts/files/examples/approval-flow.ts +0 -34
  515. package/templates/skills/artifacts/files/examples/hono-dashboard.ts +0 -31
  516. package/templates/skills/artifacts/files/examples/multi-artifact.ts +0 -20
  517. package/templates/skills/artifacts/files/examples/static-report.sh +0 -17
  518. package/dist/{azure-openai-responses-ap1gwxgr.js → azure-openai-responses-ygfreq4t.js} +5 -5
  519. package/dist/{cli-5rqrj7q0.js → cli-pw7ebjdt.js} +0 -0
  520. package/dist/{openai-codex-responses-33qhkkdy.js → openai-codex-responses-3bk0grnr.js} +4 -4
  521. package/dist/{openai-completions-s88xm3ns.js → openai-completions-5d4jpakc.js} +9 -9
  522. package/dist/{openai-responses-7xd2f2e2.js → openai-responses-krb51f59.js} +5 -5
  523. package/dist/{pi-messages-xszk613n.js → pi-messages-x9gzynm1.js} +3 -3
  524. /package/src/{artifact-sdk → pages}/browser-sdk.ts +0 -0
  525. /package/src/{artifact-sdk → pages}/feedback-overlay.ts +0 -0
@@ -16,10 +16,11 @@ import {
16
16
  } from "@/scripts-runtime/credential-broker";
17
17
  import { refreshSecretScrubberCache } from "@/utils/secret-scrubber";
18
18
  import { listMcpServerTools } from "./mcp-proxy";
19
+ import { assertPostgresDsn } from "./sql-connections";
19
20
  import { readVendoredOpenapiSpec } from "./vendored-openapi";
20
21
 
21
22
  export type ScriptConnectionScope = "global" | "agent" | "repo";
22
- export type ScriptConnectionKind = "raw" | "openapi" | "mcp" | "graphql";
23
+ export type ScriptConnectionKind = "raw" | "openapi" | "mcp" | "graphql" | "sql";
23
24
  export type ScriptConnectionBaseUrlSource = "user" | "spec";
24
25
  export type ScriptConnectionAuthType = "none" | "bearer" | "header" | "query" | "oauth";
25
26
 
@@ -109,6 +110,13 @@ export const ConnectionAuthInputSchema = z.discriminatedUnion("type", [
109
110
  }),
110
111
  ]);
111
112
 
113
+ /** Postgres DSN for a `sql` connection: an inline secret or an existing swarm_config key. */
114
+ export const SqlDsnInputSchema = z
115
+ .object({ secret: authSecretSchema, configKey: authConfigKeySchema })
116
+ .refine((dsn) => (dsn.secret === undefined) !== (dsn.configKey === undefined), {
117
+ message: "Provide exactly one of `secret` or `configKey` for the SQL DSN.",
118
+ });
119
+
112
120
  export type ScriptConnectionBaseUrlMismatch = {
113
121
  specUrl: string;
114
122
  effectiveUrl: string;
@@ -1370,7 +1378,9 @@ function assertConfigKeyNotReserved(auth: ConnectionAuthInput | undefined): void
1370
1378
  */
1371
1379
  function derivedInlineSecretKey(connection: ScriptConnectionRecord | null): string | null {
1372
1380
  if (!connection) return null;
1373
- if (connection.authType === "none" || connection.authType === "oauth") return null;
1381
+ // sql rows keep auth_type 'none' but own their DSN key in auth_config_key.
1382
+ if (connection.authType === "oauth") return null;
1383
+ if (connection.authType === "none" && connection.kind !== "sql") return null;
1374
1384
  const key = connection.authConfigKey;
1375
1385
  return key && DERIVED_INLINE_SECRET_KEY.test(key) ? key : null;
1376
1386
  }
@@ -1565,6 +1575,7 @@ export async function upsertScriptConnection(data: {
1565
1575
  openapiSpecEtag?: string | null;
1566
1576
  openapiSpecFetchedAt?: string | null;
1567
1577
  mcpServerId?: string | null;
1578
+ sqlDsn?: z.infer<typeof SqlDsnInputSchema>;
1568
1579
  enabled?: boolean;
1569
1580
  agentId?: string;
1570
1581
  userId?: string | null;
@@ -1608,6 +1619,9 @@ export async function upsertScriptConnection(data: {
1608
1619
  "MCP connections resolve auth through their MCP server; `auth` is not supported.",
1609
1620
  );
1610
1621
  }
1622
+ if (data.kind === "sql" && data.auth && data.auth.type !== "none") {
1623
+ throw new Error("SQL connections authenticate through their DSN; `auth` is not supported.");
1624
+ }
1611
1625
  // Keep the reserved derived-credential namespaces exclusive so a user-supplied
1612
1626
  // configKey can never collide with (or later be mistaken for) a connection's
1613
1627
  // own derived inline secret. Checked on user input only — reconstruction of an
@@ -1638,7 +1652,7 @@ export async function upsertScriptConnection(data: {
1638
1652
  // binding, e.g. raw-fetch egress) with no `auth`.
1639
1653
  const explicitLegacyBinding = data.auth === undefined && data.credentialBindingId !== undefined;
1640
1654
  const authIntent: ConnectionAuthInput =
1641
- data.kind === "mcp" || explicitLegacyBinding
1655
+ data.kind === "mcp" || data.kind === "sql" || explicitLegacyBinding
1642
1656
  ? { type: "none" }
1643
1657
  : (data.auth ?? reconstructAuthFromConnection(existingConnection));
1644
1658
  let derived: DerivedConnectionBinding | null = null;
@@ -1795,6 +1809,26 @@ export async function upsertScriptConnection(data: {
1795
1809
  generatedAt = now;
1796
1810
  }
1797
1811
 
1812
+ // The DSN key rides auth_config_key; the DSN itself never enters these artifacts.
1813
+ let sqlDsnKey: string | null = null;
1814
+ let sqlSecretWrite: DerivedConnectionBinding["secretWrite"] = null;
1815
+ if (data.kind === "sql") {
1816
+ if (data.sqlDsn?.secret !== undefined) {
1817
+ assertPostgresDsn(data.sqlDsn.secret);
1818
+ sqlDsnKey = `connection.${normalizedSlug}.secret`;
1819
+ sqlSecretWrite = { scope, scopeId, key: sqlDsnKey, value: data.sqlDsn.secret };
1820
+ } else if (data.sqlDsn?.configKey) {
1821
+ assertConfigKeyNotReserved({ type: "bearer", configKey: data.sqlDsn.configKey });
1822
+ sqlDsnKey = data.sqlDsn.configKey;
1823
+ } else {
1824
+ sqlDsnKey = existingConnection?.kind === "sql" ? existingConnection.authConfigKey : null;
1825
+ }
1826
+ if (!sqlDsnKey) throw new Error("SQL connections require `dsn.secret` or `dsn.configKey`.");
1827
+ generatedRuntimeJson = JSON.stringify({ slug: normalizedSlug, kind: "sql", connectionId });
1828
+ generatedTypes = `export interface ${pascal(normalizedSlug)}Api {\n /** One read-only statement; pass values via params ($1, $2, ...). */\n query<T = Record<string, JsonValue>>(sql: string, params?: JsonValue[]): Promise<{ rows: T[]; rowCount: number; truncated: boolean }>;\n}`;
1829
+ generatedAt = now;
1830
+ }
1831
+
1798
1832
  if (data.kind !== "graphql") {
1799
1833
  allowedHosts =
1800
1834
  data.allowedHosts ?? (effectiveBaseUrl ? [new URL(effectiveBaseUrl).hostname] : []);
@@ -1808,7 +1842,8 @@ export async function upsertScriptConnection(data: {
1808
1842
  // metadata-only rename reconstructs the old derived key as an explicit
1809
1843
  // configKey and keeps referencing it, so it must NOT be deleted.
1810
1844
  const oldDerivedSecretKey = derivedInlineSecretKey(existingConnection);
1811
- const newReferencedConfigKey = derived?.configKey ?? explicitBinding?.configKey ?? null;
1845
+ const newReferencedConfigKey =
1846
+ derived?.configKey ?? explicitBinding?.configKey ?? sqlDsnKey ?? null;
1812
1847
  let supersededSecretDeleted = false;
1813
1848
 
1814
1849
  // Connection row and its auto-managed binding form a reference cycle
@@ -1822,6 +1857,7 @@ export async function upsertScriptConnection(data: {
1822
1857
  if (derived?.secretWrite) {
1823
1858
  await upsertSwarmConfig({ ...derived.secretWrite, isSecret: true });
1824
1859
  }
1860
+ if (sqlSecretWrite) await upsertSwarmConfig({ ...sqlSecretWrite, isSecret: true });
1825
1861
 
1826
1862
  // Drop a superseded derived inline secret (see oldDerivedSecretKey above).
1827
1863
  // Skip when the connection's binding still references that same key (same
@@ -1874,6 +1910,7 @@ export async function upsertScriptConnection(data: {
1874
1910
  : (existingConnection?.credentialBindingId ?? null);
1875
1911
  authCols = NONE_AUTH_COLUMNS;
1876
1912
  }
1913
+ if (sqlDsnKey) authCols = { ...NONE_AUTH_COLUMNS, authConfigKey: sqlDsnKey };
1877
1914
 
1878
1915
  const params = [
1879
1916
  normalizedSlug,
@@ -1951,7 +1988,9 @@ export async function upsertScriptConnection(data: {
1951
1988
  });
1952
1989
 
1953
1990
  const row = await writeConnectionAndBinding();
1954
- if (derived?.secretWrite || supersededSecretDeleted) refreshSecretScrubberCache();
1991
+ if (derived?.secretWrite || sqlSecretWrite || supersededSecretDeleted) {
1992
+ refreshSecretScrubberCache();
1993
+ }
1955
1994
  return connectionFromRow(row);
1956
1995
  }
1957
1996
 
@@ -2117,7 +2156,7 @@ export function getScriptApiConnectionDescriptors(
2117
2156
  context: { agentId?: string; repoId?: string } = {},
2118
2157
  ): ScriptApiConnectionDescriptor[] {
2119
2158
  return listScriptConnections(context)
2120
- .filter((connection) => connection.kind === "openapi" || connection.kind === "graphql")
2159
+ .filter((connection) => ["openapi", "graphql", "sql"].includes(connection.kind))
2121
2160
  .map((connection) => {
2122
2161
  if (!connection.generatedRuntimeJson) return null;
2123
2162
  try {
@@ -2145,8 +2184,8 @@ export function getScriptMcpConnectionDescriptors(
2145
2184
  }
2146
2185
 
2147
2186
  export function getScriptApiTypes(context: { agentId?: string; repoId?: string } = {}): string {
2148
- const connections = listScriptConnections(context).filter(
2149
- (connection) => connection.kind === "openapi" || connection.kind === "graphql",
2187
+ const connections = listScriptConnections(context).filter((connection) =>
2188
+ ["openapi", "graphql", "sql"].includes(connection.kind),
2150
2189
  );
2151
2190
  const blocks = connections
2152
2191
  .filter((connection) => connection.generatedTypes)
@@ -11,6 +11,7 @@ import { registerVolatileSecret } from "@/utils/secret-scrubber";
11
11
  import { getResolvedConfig } from "./db";
12
12
  import { resolveOAuthBindingToken } from "./oauth-credential-bindings";
13
13
  import { listRelationalCredentialBindings } from "./script-connections";
14
+ import { isApiOnlyConfigKey, isInternalConfigKey } from "./swarm-config-guard";
14
15
 
15
16
  // Relational-only credential binding store. The legacy SCRIPT_CREDENTIAL_BINDINGS
16
17
  // swarm-config JSON blob is retired (migrated to relational rows at boot), so
@@ -22,6 +23,32 @@ class RelationalCredentialBindingStore implements CredentialBindingStore {
22
23
  }
23
24
  }
24
25
 
26
+ export type ScriptUserConfig = Record<string, { value: string; isSecret: boolean }>;
27
+
28
+ /**
29
+ * The values behind `ctx.swarm.config.get(KEY)`: the invoking agent's resolved
30
+ * config (agent over global, plus repo when given), minus the keys the
31
+ * `/api/config/resolved` route never hands out. A script's own SDK bearer
32
+ * already reads every one of these through `ctx.swarm.config_get`, so this
33
+ * widens nothing; it only makes the documented accessor return them. Secret
34
+ * values are registered with the scrubber so they never leave in run output.
35
+ */
36
+ function toScriptUserConfig(configs: Awaited<ReturnType<typeof getResolvedConfig>>) {
37
+ const exposed = configs.filter(
38
+ (config) => !isApiOnlyConfigKey(config.key) && !isInternalConfigKey(config.key),
39
+ );
40
+ for (const config of exposed) {
41
+ if (config.isSecret && config.value) {
42
+ registerVolatileSecret(config.value, `config:${config.key}`);
43
+ }
44
+ }
45
+ // fromEntries defines own properties, so a `__proto__` key survives
46
+ // (assigning it into `{}` would hit the prototype setter instead).
47
+ return Object.fromEntries(
48
+ exposed.map((config) => [config.key, { value: config.value, isSecret: config.isSecret }]),
49
+ ) as ScriptUserConfig;
50
+ }
51
+
25
52
  /**
26
53
  * Resolve credential bindings for a script run, partitioning OAuth bindings
27
54
  * whose refresh failed into `failedBindings` (surfaced to the sandbox so the
@@ -32,8 +59,14 @@ class RelationalCredentialBindingStore implements CredentialBindingStore {
32
59
  export async function buildScriptCredentialBindingsWithFailures(input: {
33
60
  agentId?: string;
34
61
  repoId?: string;
35
- }): Promise<{ egressSecrets: EgressSecretEntry[]; failedBindings: FailedCredentialBinding[] }> {
62
+ }): Promise<{
63
+ egressSecrets: EgressSecretEntry[];
64
+ failedBindings: FailedCredentialBinding[];
65
+ userConfig: ScriptUserConfig;
66
+ }> {
36
67
  const resolvedConfigs = await getResolvedConfig(input.agentId, input.repoId);
68
+ // Before the binding loop below, so a binding's own scrubber label wins.
69
+ const userConfig = toScriptUserConfig(resolvedConfigs);
37
70
  const configMap = new Map(resolvedConfigs.map((config) => [config.key, config.value]));
38
71
  const broker = new CredentialBroker(
39
72
  new RelationalCredentialBindingStore(),
@@ -49,7 +82,11 @@ export async function buildScriptCredentialBindingsWithFailures(input: {
49
82
  for (const binding of resolved) {
50
83
  registerVolatileSecret(binding.value, binding.configKey);
51
84
  }
52
- return { egressSecrets: resolved, failedBindings: failed };
85
+ return {
86
+ egressSecrets: resolved,
87
+ failedBindings: failed,
88
+ userConfig,
89
+ };
53
90
  }
54
91
 
55
92
  export async function buildScriptCredentialBindings(input: {
@@ -52,6 +52,7 @@ export async function runSavedScriptAsAgent(
52
52
  agentId: args.agentId,
53
53
  egressSecrets: credentials.egressSecrets,
54
54
  failedBindings: credentials.failedBindings,
55
+ userConfig: credentials.userConfig,
55
56
  apiConnections: getScriptApiConnectionDescriptors({ agentId: args.agentId }),
56
57
  mcpConnections: getScriptMcpConnectionDescriptors({ agentId: args.agentId }),
57
58
  ...(args.timeoutMs ? { timeoutMs: args.timeoutMs } : {}),
@@ -235,10 +235,10 @@ export interface SwarmSdk {
235
235
  // --- slack ---
236
236
  slack_read(args?: { inboxMessageId?: string; taskId?: string; channelId?: string; threadTs?: string; limit?: number; includeFiles?: boolean }): Promise<unknown>;
237
237
  slack_listChannels(args?: { types?: Array<"public" | "private" | "dm" | "mpim">; limit?: number }): Promise<unknown>;
238
+ // --- chat sdk ---
239
+ chat_readThread(args: { threadId: string; limit?: number }): Promise<unknown>;
238
240
  // --- messaging ---
239
241
  message_read(args?: { channel?: string; limit?: number; since?: string; unreadOnly?: boolean; mentionsOnly?: boolean; markAsRead?: boolean }): Promise<unknown>;
240
- // --- services ---
241
- service_list(args?: { agentId?: string; name?: string; status?: "starting" | "healthy" | "unhealthy" | "stopped"; includeOwn?: boolean }): Promise<unknown>;
242
242
  // --- context / profiles ---
243
243
  context_history(args?: { agentId?: string; field?: "soulMd" | "identityMd" | "toolsMd" | "claudeMd" | "setupScript"; limit?: number }): Promise<unknown>;
244
244
  context_diff(args: { versionId: string; compareToVersionId?: string }): Promise<unknown>;
@@ -281,7 +281,7 @@ export interface SwarmSdk {
281
281
  task_action(args: Record<string, unknown>): Promise<unknown>;
282
282
 
283
283
  // --- write: config ---
284
- config_set(args: { key: string; value: unknown; scope?: "global" | "agent" | "repo"; scopeId?: string; isSecret?: boolean }): Promise<unknown>;
284
+ config_set(args: { key: string; value: unknown; scope?: "global" | "agent" | "repo"; scopeId?: string; isSecret?: boolean; exportToEnv?: boolean }): Promise<unknown>;
285
285
  config_delete(args: { id: string }): Promise<unknown>;
286
286
 
287
287
  // --- write: model catalog ---
@@ -311,17 +311,19 @@ export interface SwarmSdk {
311
311
  slack_delete(args: { channelId: string; messageTs: string }): Promise<unknown>;
312
312
  slack_update(args: { channelId: string; messageTs: string; message: string }): Promise<unknown>;
313
313
 
314
+ // --- write: chat sdk ---
315
+ chat_reply(args: { text: string }): Promise<unknown>;
316
+ chat_post(args: { adapter: "telegram" | "slack" | "discord" | "teams"; threadId?: string; channelId?: string; text: string }): Promise<unknown>;
317
+ chat_openDm(args: { adapter: "telegram" | "slack" | "discord" | "teams"; userId: string }): Promise<unknown>;
318
+ chat_registerChannel(args: { adapter: "telegram" | "slack" | "discord" | "teams"; channelId: string; agentId?: string; workflowId?: string; name?: string }): Promise<unknown>;
319
+ chat_unregisterChannel(args: { adapter: "telegram" | "slack" | "discord" | "teams"; channelId: string }): Promise<unknown>;
320
+
314
321
  // --- write: messaging (internal) ---
315
322
  message_post(args: { channel?: string; content: string; to?: string }): Promise<unknown>;
316
323
 
317
324
  // --- write: profiles ---
318
325
  profile_update(args: Record<string, unknown>): Promise<unknown>;
319
326
 
320
- // --- write: services ---
321
- service_register(args: Record<string, unknown>): Promise<unknown>;
322
- service_unregister(args: { name: string }): Promise<unknown>;
323
- service_updateStatus(args: { name: string; status: "starting" | "healthy" | "unhealthy" | "stopped" }): Promise<unknown>;
324
-
325
327
  // --- write: schedules ---
326
328
  schedule_create(args: Record<string, unknown>): Promise<unknown>;
327
329
  schedule_update(args: Record<string, unknown>): Promise<unknown>;
@@ -22,6 +22,7 @@ export async function runSeeder(seeder: Seeder, opts?: SeederRunOptions): Promis
22
22
  updated: 0,
23
23
  skippedUnchanged: 0,
24
24
  skippedUserModified: 0,
25
+ retired: 0,
25
26
  failed: [],
26
27
  };
27
28
 
@@ -77,11 +78,22 @@ export async function runSeeder(seeder: Seeder, opts?: SeederRunOptions): Promis
77
78
  }
78
79
  }
79
80
 
81
+ if (seeder.retire) {
82
+ try {
83
+ result.retired = (await seeder.retire()).length;
84
+ } catch (err) {
85
+ result.failed.push({
86
+ key: "(retire)",
87
+ error: err instanceof Error ? err.message : String(err),
88
+ });
89
+ }
90
+ }
91
+
80
92
  if (!opts?.quiet) {
81
93
  console.log(
82
94
  `[seed:${result.kind}] created=${result.created} updated=${result.updated} ` +
83
95
  `unchanged=${result.skippedUnchanged} preserved=${result.skippedUserModified} ` +
84
- `failed=${result.failed.length}`,
96
+ `retired=${result.retired} failed=${result.failed.length}`,
85
97
  );
86
98
  for (const f of result.failed) {
87
99
  console.error(`[seed:${result.kind}] FAILED ${f.key}: ${f.error}`);
@@ -36,3 +36,8 @@ export async function recordSeedState(
36
36
  [kind, key, seededHash, new Date().toISOString()],
37
37
  );
38
38
  }
39
+
40
+ /** Forget `(kind, key)`, e.g. after the seeder retires the entity it seeded. */
41
+ export async function deleteSeedState(kind: string, key: string): Promise<void> {
42
+ await getDbClient().run("DELETE FROM seed_state WHERE kind = ? AND key = ?", [kind, key]);
43
+ }
@@ -53,6 +53,11 @@ export interface Seeder<TItem extends SeedItem = SeedItem> {
53
53
  upstreamHash(item: TItem): string | null | Promise<string | null>;
54
54
  /** Create or update the upstream entity so it matches the source definition. */
55
55
  apply(item: TItem, action: "create" | "update", opts?: SeederRunOptions): void | Promise<void>;
56
+ /**
57
+ * Optional: remove upstream entities for keys the source no longer ships.
58
+ * Must leave user-modified entities in place. Returns the removed keys.
59
+ */
60
+ retire?(): Promise<string[]>;
56
61
  }
57
62
 
58
63
  export type SeederResult = {
@@ -61,5 +66,6 @@ export type SeederResult = {
61
66
  updated: number;
62
67
  skippedUnchanged: number;
63
68
  skippedUserModified: number;
69
+ retired: number;
64
70
  failed: Array<{ key: string; error: string }>;
65
71
  };
@@ -19,6 +19,7 @@
19
19
  * admin route (`POST /api/pricing`) — we don't overwrite seed rows.
20
20
  */
21
21
 
22
+ import { openAICompatibleProviderIds } from "@desplega/model-catalog";
22
23
  import type { PricingProvider, PricingTokenClass } from "../types";
23
24
  import { getDb } from "./db";
24
25
  import {
@@ -98,8 +99,9 @@ const ANTHROPIC_SHORTNAME_TO_MODELSDEV: Record<string, string> = {
98
99
  const ANTHROPIC_SHORTNAME_PRICING_FALLBACKS: Record<string, ModelsDevCostBlock> = {
99
100
  // https://platform.claude.com/docs/en/about-claude/pricing (verified 2026-09-22)
100
101
  "claude-opus-5-5": { input: 4, output: 20, cache_read: 0.2, cache_write: 5 },
101
- // https://platform.claude.com/docs/en/about-claude/pricing (verified 2026-09-28)
102
- "claude-sonnet-5-5": { input: 2, output: 10, cache_read: 0.2, cache_write: 2.5 },
102
+ // https://platform.claude.com/docs/en/about-claude/pricing (verified 2026-10-15):
103
+ // cache hits on Sonnet 5.5 cost 0.05x input, not the usual 0.1x.
104
+ "claude-sonnet-5-5": { input: 2, output: 10, cache_read: 0.1, cache_write: 2.5 },
103
105
  "claude-fable-5-1": { input: 10, output: 50, cache_read: 0.25, cache_write: 12.5 },
104
106
  "claude-mythos-5-1": { input: 10, output: 50, cache_read: 0.25, cache_write: 12.5 },
105
107
  };
@@ -367,6 +369,21 @@ export function buildModelsDevSeedRows(cache: ModelsDevCache): PricingSeedRow[]
367
369
  }
368
370
  }
369
371
 
372
+ // ---- OpenAI-compatible registry providers ($0 flat-rate subscriptions) --
373
+ // pi and opencode run these as `<provider>/<id>`. The models.dev `nan`
374
+ // section and the registry-synthesized `helmcode` section
375
+ // (`withRegistryCatalogSections`) carry cost {0, 0}, so the sessions price
376
+ // as `pricing-table` at $0 instead of `unpriced`. The provider prefix stays
377
+ // in the key: `nan/qwen3.6` and `helmcode/qwen3.6` are separate rows.
378
+ for (const provider of openAICompatibleProviderIds()) {
379
+ for (const [id, model] of Object.entries(cache[provider]?.models ?? {})) {
380
+ if (!model?.cost) continue;
381
+ const key = `${provider}/${id}`;
382
+ add(key, projectCostBlock("pi", key, model.cost));
383
+ add(key, projectCostBlock("opencode", key, model.cost));
384
+ }
385
+ }
386
+
370
387
  return collapseNormalizedIds(sourced);
371
388
  }
372
389
 
@@ -38,7 +38,7 @@ const CONTRADICTORY_RE =
38
38
  /\b(do not|don't|never)\b[\s\S]{0,240}\b(always|must|required)\b|\b(always|must|required)\b[\s\S]{0,240}\b(do not|don't|never)\b/i;
39
39
 
40
40
  const CODE_REGISTRY_EVENTS =
41
- "agentmail.email.followup agentmail.email.mapped_lead agentmail.email.mapped_worker agentmail.email.no_agent agentmail.email.unmapped common.command_suggestions.github_comment_issue common.command_suggestions.github_comment_pr common.command_suggestions.github_issue common.command_suggestions.github_pr common.command_suggestions.gitlab_issue common.command_suggestions.gitlab_mr common.delegation_instruction common.delegation_instruction.gitlab github.check_run.failed github.check_suite.failed github.comment.mentioned github.issue.assigned github.issue.labeled github.issue.mentioned github.pull_request.assigned github.pull_request.closed github.pull_request.labeled github.pull_request.mentioned github.pull_request.review_requested github.pull_request.review_submitted github.pull_request.synchronize github.workflow_run.failed gitlab.comment.mentioned gitlab.issue.assigned gitlab.merge_request.opened gitlab.pipeline.failed heartbeat.boot-triage heartbeat.checklist jira.issue.assigned jira.issue.commented jira.issue.followup kapso.message.received linear.issue.assigned linear.issue.followup linear.issue.reassigned slack.assistant.greeting slack.assistant.offline slack.assistant.suggested_prompts slack.message.attachment_failed slack.message.thread_context system.agent.communication system.agent.lead system.agent.memory system.agent.memory.remote system.agent.outputs system.agent.outputs.no_agent_fs system.agent.repository system.agent.role system.agent.secrets system.agent.slack system.agent.tools_skills system.agent.worker system.agent.worker.remote system.agent.workspace system.agent.workspace.remote system.session.lead system.session.lead.managed system.session.worker system.session.worker.managed system.session.worker.remote task.budget.refused task.requester.profile task.worker.completed task.worker.failed".split(
41
+ "agentmail.email.followup agentmail.email.mapped_lead agentmail.email.mapped_worker agentmail.email.no_agent agentmail.email.unmapped chat.message.thread_context common.command_suggestions.github_comment_issue common.command_suggestions.github_comment_pr common.command_suggestions.github_issue common.command_suggestions.github_pr common.command_suggestions.gitlab_issue common.command_suggestions.gitlab_mr common.delegation_instruction common.delegation_instruction.gitlab github.check_run.failed github.check_suite.failed github.comment.mentioned github.issue.assigned github.issue.labeled github.issue.mentioned github.pull_request.assigned github.pull_request.closed github.pull_request.labeled github.pull_request.mentioned github.pull_request.review_requested github.pull_request.review_submitted github.pull_request.synchronize github.workflow_run.failed gitlab.comment.mentioned gitlab.issue.assigned gitlab.merge_request.opened gitlab.pipeline.failed heartbeat.boot-triage heartbeat.checklist jira.issue.assigned jira.issue.commented jira.issue.followup kapso.message.received linear.issue.assigned linear.issue.followup linear.issue.reassigned slack.assistant.greeting slack.assistant.offline slack.assistant.suggested_prompts slack.message.attachment_failed slack.message.thread_context system.agent.communication system.agent.lead system.agent.memory system.agent.memory.remote system.agent.outputs system.agent.outputs.no_agent_fs system.agent.repository system.agent.role system.agent.secrets system.agent.slack system.agent.tools_skills system.agent.worker system.agent.worker.remote system.agent.workspace system.agent.workspace.remote system.session.lead system.session.lead.managed system.session.worker system.session.worker.managed system.session.worker.remote task.budget.refused task.requester.profile task.worker.completed task.worker.failed".split(
42
42
  " ",
43
43
  );
44
44
 
@@ -38,7 +38,7 @@ const CONTRADICTORY_RE =
38
38
  /\b(do not|don't|never)\b[\s\S]{0,240}\b(always|must|required)\b|\b(always|must|required)\b[\s\S]{0,240}\b(do not|don't|never)\b/i;
39
39
 
40
40
  const CODE_REGISTRY_EVENTS =
41
- "agentmail.email.followup agentmail.email.mapped_lead agentmail.email.mapped_worker agentmail.email.no_agent agentmail.email.unmapped common.command_suggestions.github_comment_issue common.command_suggestions.github_comment_pr common.command_suggestions.github_issue common.command_suggestions.github_pr common.command_suggestions.gitlab_issue common.command_suggestions.gitlab_mr common.delegation_instruction common.delegation_instruction.gitlab github.check_run.failed github.check_suite.failed github.comment.mentioned github.issue.assigned github.issue.labeled github.issue.mentioned github.pull_request.assigned github.pull_request.closed github.pull_request.labeled github.pull_request.mentioned github.pull_request.review_requested github.pull_request.review_submitted github.pull_request.synchronize github.workflow_run.failed gitlab.comment.mentioned gitlab.issue.assigned gitlab.merge_request.opened gitlab.pipeline.failed heartbeat.boot-triage heartbeat.checklist jira.issue.assigned jira.issue.commented jira.issue.followup kapso.message.received linear.issue.assigned linear.issue.followup linear.issue.reassigned slack.assistant.greeting slack.assistant.offline slack.assistant.suggested_prompts slack.message.attachment_failed slack.message.thread_context system.agent.communication system.agent.lead system.agent.memory system.agent.memory.remote system.agent.outputs system.agent.outputs.no_agent_fs system.agent.repository system.agent.role system.agent.secrets system.agent.slack system.agent.tools_skills system.agent.worker system.agent.worker.remote system.agent.workspace system.agent.workspace.remote system.session.lead system.session.lead.managed system.session.worker system.session.worker.managed system.session.worker.remote task.budget.refused task.requester.profile task.worker.completed task.worker.failed".split(
41
+ "agentmail.email.followup agentmail.email.mapped_lead agentmail.email.mapped_worker agentmail.email.no_agent agentmail.email.unmapped chat.message.thread_context common.command_suggestions.github_comment_issue common.command_suggestions.github_comment_pr common.command_suggestions.github_issue common.command_suggestions.github_pr common.command_suggestions.gitlab_issue common.command_suggestions.gitlab_mr common.delegation_instruction common.delegation_instruction.gitlab github.check_run.failed github.check_suite.failed github.comment.mentioned github.issue.assigned github.issue.labeled github.issue.mentioned github.pull_request.assigned github.pull_request.closed github.pull_request.labeled github.pull_request.mentioned github.pull_request.review_requested github.pull_request.review_submitted github.pull_request.synchronize github.workflow_run.failed gitlab.comment.mentioned gitlab.issue.assigned gitlab.merge_request.opened gitlab.pipeline.failed heartbeat.boot-triage heartbeat.checklist jira.issue.assigned jira.issue.commented jira.issue.followup kapso.message.received linear.issue.assigned linear.issue.followup linear.issue.reassigned slack.assistant.greeting slack.assistant.offline slack.assistant.suggested_prompts slack.message.attachment_failed slack.message.thread_context system.agent.communication system.agent.lead system.agent.memory system.agent.memory.remote system.agent.outputs system.agent.outputs.no_agent_fs system.agent.repository system.agent.role system.agent.secrets system.agent.slack system.agent.tools_skills system.agent.worker system.agent.worker.remote system.agent.workspace system.agent.workspace.remote system.session.lead system.session.lead.managed system.session.worker system.session.worker.managed system.session.worker.remote task.budget.refused task.requester.profile task.worker.completed task.worker.failed".split(
42
42
  " ",
43
43
  );
44
44
 
@@ -1,22 +1,4 @@
1
1
  {
2
- "artifacts": [
3
- {
4
- "path": "examples/approval-flow.ts",
5
- "content": "import { createArtifactServer } from \"../../src/artifact-sdk\";\nimport { Hono } from \"hono\";\n\nconst app = new Hono();\napp.get(\"/\", (c) =>\n c.html(`\n<!DOCTYPE html>\n<html>\n<head><title>Approval Required</title></head>\n<body>\n <h1>PR #42 — Review Required</h1>\n <p>Agent wants to merge this PR. Please review.</p>\n <button id=\"approve\">Approve</button>\n <button id=\"reject\">Reject</button>\n <script src=\"/@swarm/sdk.js\"></script>\n <script>\n const swarm = new SwarmSDK();\n document.getElementById('approve').onclick = async () => {\n await swarm.createTask({ task: 'Merge PR #42 — human approved' });\n document.body.innerHTML = '<h1>Approved! Task created.</h1>';\n };\n document.getElementById('reject').onclick = async () => {\n await swarm.createTask({ task: 'PR #42 rejected by human — needs changes' });\n document.body.innerHTML = '<h1>Rejected. Agent notified.</h1>';\n };\n </script>\n</body>\n</html>\n`),\n);\n\nconst server = createArtifactServer({ name: \"approval-pr-42\", app });\nawait server.start();\nconsole.log(`Approval artifact at: ${server.url}`);\n"
6
- },
7
- {
8
- "path": "examples/hono-dashboard.ts",
9
- "content": "import { createArtifactServer } from \"../../src/artifact-sdk\";\nimport { Hono } from \"hono\";\n\nconst app = new Hono();\n\napp.get(\"/\", (c) =>\n c.html(`\n<!DOCTYPE html>\n<html>\n<head><title>Dashboard</title></head>\n<body>\n <h1>Agent Dashboard</h1>\n <div id=\"agents\"></div>\n <script src=\"/@swarm/sdk.js\"></script>\n <script>\n const swarm = new SwarmSDK();\n swarm.getSwarm().then(agents => {\n document.getElementById('agents').innerHTML =\n '<ul>' + agents.map(a => '<li>' + a.name + ' (' + a.status + ')</li>').join('') + '</ul>';\n });\n </script>\n</body>\n</html>\n`),\n);\n\napp.get(\"/api/status\", (c) => c.json({ status: \"ok\", timestamp: Date.now() }));\n\nconst server = createArtifactServer({ name: \"dashboard\", app });\nawait server.start();\nconsole.log(`Dashboard live at: ${server.url}`);\n"
10
- },
11
- {
12
- "path": "examples/multi-artifact.ts",
13
- "content": "import { createArtifactServer } from \"../../src/artifact-sdk\";\nimport { Hono } from \"hono\";\n\n// Artifact 1: Status page\nconst statusApp = new Hono();\nstatusApp.get(\"/\", (c) => c.html(\"<h1>Status: All systems operational</h1>\"));\n\n// Artifact 2: Data viewer\nconst dataApp = new Hono();\ndataApp.get(\"/\", (c) => c.json({ items: [1, 2, 3], generated: new Date().toISOString() }));\n\nconst status = createArtifactServer({ name: \"status\", app: statusApp });\nconst data = createArtifactServer({ name: \"data\", app: dataApp });\n\nawait status.start();\nawait data.start();\n\nconsole.log(`Status page: ${status.url}`);\nconsole.log(`Data viewer: ${data.url}`);\nconsole.log(`Both artifacts running on different ports (${status.port}, ${data.port})`);\n"
14
- },
15
- {
16
- "path": "examples/static-report.sh",
17
- "content": "#!/bin/bash\n# Serve a static HTML report as an artifact\nARTIFACT_DIR=\"/workspace/personal/artifacts/my-report\"\nmkdir -p \"$ARTIFACT_DIR\"\n\ncat > \"$ARTIFACT_DIR/index.html\" << 'HTML'\n<!DOCTYPE html>\n<html>\n<head><title>Agent Report</title></head>\n<body>\n <h1>Analysis Report</h1>\n <p>Generated by agent</p>\n</body>\n</html>\nHTML\n\nagent-swarm artifact serve \"$ARTIFACT_DIR\" --name \"my-report\"\n"
18
- }
19
- ],
20
2
  "brainstorming": [
21
3
  {
22
4
  "path": "template.md",
@@ -77,6 +59,16 @@
77
59
  "content": "---\ndate: [ISO format]\nauthor: [Name]\ntopic: \"[Feature/Bug/Scenario being validated]\"\ntags: [qa, relevant-tags]\nstatus: [in-progress|pass|fail|blocked]\nsource_plan: [optional — path to plan that motivated this QA]\nsource_verification: [optional — path to verification report]\nrelated_pr: [optional — PR URL or number]\nenvironment: [optional — local|staging|production]\nlast_updated: [YYYY-MM-DD]\nlast_updated_by: [Name]\n---\n\n# [Feature/Scenario] — QA Report\n\n## Context\n[What is being validated and why. Link to plan, PR, or issue.]\n\n## Scope\n### In Scope\n- [What this QA covers]\n\n### Out of Scope\n- [What this QA does NOT cover]\n\n## Test Cases\n\n### TC-1: [Test Case Name]\n**Steps:**\n1. [Step 1]\n2. [Step 2]\n\n**Expected Result:** [What should happen]\n**Actual Result:** [What actually happened]\n**Status:** pass | fail | blocked | skipped\n\n### TC-2: [Test Case Name]\n[Same structure]\n\n## Edge Cases & Exploratory Testing\n- [Findings from exploratory testing]\n\n## Evidence\n\n### Screenshots\n- ![Description](path-or-url)\n\n### Videos\n- [Video: Description](url)\n\n### Logs & Output\n```\n[Relevant log output]\n```\n\n### External Links\n- [Sentry Issue](url)\n- [Grafana Dashboard](url)\n- [CI/CD Run](url)\n- [PR](url)\n\n## Issues Found\n- [ ] [Issue description — severity: critical|major|minor]\n\n## Verdict\n**Status**: PASS | FAIL | BLOCKED\n**Summary**: [1-2 sentence summary of QA outcome]\n\n## Appendix\n\n- **Plan**: `thoughts/.../plans/YYYY-MM-DD-topic.md`\n- **Verification**: `thoughts/.../plans/YYYY-MM-DD-verification-report.md`\n- **Related documents**: [links]\n- **Notes**: [follow-up QA, derail items, environment caveats]\n"
78
60
  }
79
61
  ],
62
+ "qa-loom": [
63
+ {
64
+ "path": "build-loom.mjs",
65
+ "content": "// build-loom.mjs: steps.json + raw recording -> narrated qa-loom.mp4 (+ qa-loom.ass, work/).\n// Usage: node build-loom.mjs <steps.json> <raw.webm> <outDir> (bun works too)\n// Env: ELEVENLABS_API_KEY (required), SPEED=1.33, VOICE=21m00Tcm4TlvDq8ikWAM,\n// MODEL=eleven_turbo_v2_5, FONT=\"DejaVu Sans\"\n// Needs: Node 18+ (global fetch, top-level await) and ffmpeg built with libass (`ass` filter).\nimport { execFileSync } from \"node:child_process\";\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\n\nconst [stepsPath, raw, outArg] = process.argv.slice(2);\nif (!stepsPath || !raw || !outArg) {\n throw new Error(\"usage: node build-loom.mjs <steps.json> <raw.webm> <outDir>\");\n}\nconst out = resolve(outArg);\nconst SPEED = Number(process.env.SPEED ?? 1.33);\nconst VOICE = process.env.VOICE ?? \"21m00Tcm4TlvDq8ikWAM\";\nconst MODEL = process.env.MODEL ?? \"eleven_turbo_v2_5\";\nconst FONT = process.env.FONT ?? \"DejaVu Sans\";\nconst KEY = process.env.ELEVENLABS_API_KEY;\nif (!KEY) throw new Error(\"ELEVENLABS_API_KEY is not set\");\nmkdirSync(join(out, \"work\"), { recursive: true });\nconst W = (f) => join(out, \"work\", f);\n\n// -loglevel error keeps ffmpeg's banner and progress output out of the logs.\nconst ff = (...args) => {\n try {\n execFileSync(\"ffmpeg\", [\"-y\", \"-hide_banner\", \"-loglevel\", \"error\", ...args], {\n stdio: [\"ignore\", \"ignore\", \"pipe\"],\n });\n } catch (e) {\n throw new Error(`ffmpeg failed: ${String(e.stderr).slice(0, 400)}`);\n }\n};\nconst ENC = [\n \"-c:v\",\n \"libx264\",\n \"-preset\",\n \"veryfast\",\n \"-crf\",\n \"23\",\n \"-pix_fmt\",\n \"yuv420p\",\n \"-r\",\n \"30\",\n \"-video_track_timescale\",\n \"15360\",\n];\n// Keep the aspect ratio: letterbox or pillarbox into 1920x1080, never stretch (mobile viewports).\nconst FIT =\n \"scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:color=white\";\n\nconst { steps } = JSON.parse(readFileSync(stepsPath, \"utf8\"));\nconst concat = [];\nconst cues = [];\nconst audio = [];\nlet T = 0; // running output timeline, in seconds\n\nfor (const [i, s] of steps.entries()) {\n const visualAt = s.visualAt ?? s.start;\n const segLen = (s.end - s.start) / SPEED;\n const v = (visualAt - s.start) / SPEED; // voice and subtitles start when the screen renders\n\n // 1. TTS with character timestamps, one request per step. Cached per narration text,\n // so a re-run after a style tweak costs no ElevenLabs characters.\n const mp3 = W(`audio-${i + 1}.mp3`);\n const alPath = W(`audio-${i + 1}.json`);\n let cached = existsSync(alPath) ? JSON.parse(readFileSync(alPath, \"utf8\")) : null;\n if (cached?.text !== s.narration) {\n const res = await fetch(\n `https://api.elevenlabs.io/v1/text-to-speech/${VOICE}/with-timestamps`,\n {\n method: \"POST\",\n headers: { \"xi-api-key\": KEY, \"content-type\": \"application/json\" },\n body: JSON.stringify({ text: s.narration, model_id: MODEL }),\n },\n );\n if (!res.ok) throw new Error(`ElevenLabs ${res.status}: ${(await res.text()).slice(0, 200)}`);\n const tts = await res.json();\n // audio_base64 is large: write it straight to disk, never print it.\n writeFileSync(mp3, Buffer.from(tts.audio_base64, \"base64\"));\n cached = { text: s.narration, alignment: tts.alignment };\n writeFileSync(alPath, JSON.stringify(cached));\n }\n const al = cached.alignment;\n const aDur = al.character_end_times_seconds.at(-1);\n\n // 2. Sped-up segment. Freeze the last frame when the narration outlasts it.\n const seg = W(`seg-${i + 1}.mp4`);\n ff(\n \"-ss\",\n String(s.start),\n \"-t\",\n String(s.end - s.start),\n \"-i\",\n raw,\n \"-an\",\n \"-vf\",\n `setpts=(PTS-STARTPTS)/${SPEED},${FIT},fps=30`,\n ...ENC,\n seg,\n );\n concat.push(seg);\n const need = v + aDur + 0.35;\n let len = segLen;\n if (need > segLen) {\n const png = W(`last-${i + 1}.png`);\n ff(\"-sseof\", \"-0.2\", \"-i\", seg, \"-update\", \"1\", \"-frames:v\", \"1\", png);\n const pad = W(`pad-${i + 1}.mp4`);\n const padLen = (need - segLen).toFixed(3);\n ff(\"-loop\", \"1\", \"-framerate\", \"30\", \"-t\", padLen, \"-i\", png, \"-vf\", FIT, ...ENC, pad);\n concat.push(pad);\n len = need;\n }\n audio.push({ mp3, delayMs: Math.round((T + v) * 1000) });\n\n // 3. Subtitle cues from the same alignment, so voice and subtitles cannot drift.\n // At most 6 words per cue, break on punctuation, merge orphans under 3 words.\n const words = [];\n let cur = null;\n al.characters.forEach((ch, k) => {\n if (/\\s/.test(ch)) {\n if (cur) words.push(cur);\n cur = null;\n return;\n }\n if (!cur) cur = { text: \"\", t0: al.character_start_times_seconds[k] };\n cur.text += ch;\n cur.t1 = al.character_end_times_seconds[k];\n });\n if (cur) words.push(cur);\n const groups = [];\n let g = [];\n for (const w of words) {\n g.push(w);\n if (g.length >= 6 || /[.,;:!?]$/.test(w.text)) {\n groups.push(g);\n g = [];\n }\n }\n if (g.length) groups.push(g);\n for (let k = groups.length - 1; k > 0; k--) {\n if (groups[k].length < 3) {\n groups[k - 1].push(...groups[k]);\n groups.splice(k, 1);\n }\n }\n for (const grp of groups) {\n cues.push({\n t0: T + v + grp[0].t0,\n t1: T + v + grp.at(-1).t1 + 0.1,\n text: grp.map((w) => w.text).join(\" \"),\n });\n }\n T += len;\n}\n\n// 4. Concat with the concat demuxer. The concat filter in one big filtergraph can\n// truncate the video stream.\nwriteFileSync(W(\"concat.txt\"), concat.map((f) => `file '${f}'`).join(\"\\n\"));\nff(\"-f\", \"concat\", \"-safe\", \"0\", \"-i\", W(\"concat.txt\"), \"-c\", \"copy\", W(\"video.mp4\"));\n\n// 5. ASS subtitles: one small line, bottom centre, dark box. libass ignores the\n// BackColour alpha with BorderStyle=3, so the box is solid #303030.\nconst ts = (t) => {\n const cs = Math.round(t * 100);\n const h = Math.floor(cs / 360000);\n const m = String(Math.floor(cs / 6000) % 60).padStart(2, \"0\");\n const sec = String(Math.floor(cs / 100) % 60).padStart(2, \"0\");\n return `${h}:${m}:${sec}.${String(cs % 100).padStart(2, \"0\")}`;\n};\nfor (let k = 0; k < cues.length - 1; k++) cues[k].t1 = Math.min(cues[k].t1, cues[k + 1].t0);\nconst dialogue = cues\n .map((c) => `Dialogue: 0,${ts(c.t0)},${ts(c.t1)},Default,,0,0,0,,${c.text}`)\n .join(\"\\n\");\nconst ass = `[Script Info]\nScriptType: v4.00+\nPlayResX: 1920\nPlayResY: 1080\n\n[V4+ Styles]\nFormat: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding\nStyle: Default,${FONT},38,&H00FFFFFF,&H00FFFFFF,&H303030,&H00000000,0,0,0,0,100,100,0,0,3,2,0,2,40,40,30,1\n\n[Events]\nFormat: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n${dialogue}\n`;\nconst assPath = join(out, \"qa-loom.ass\");\nwriteFileSync(assPath, ass);\n\n// 6. Mux: delay each step's audio to where its screen renders, mix, burn subtitles.\n// normalize=0 stops amix from dividing the volume by the number of inputs.\nconst inputs = audio.flatMap((a) => [\"-i\", a.mp3]);\nconst delays = audio.map((a, k) => `[${k + 1}:a]adelay=${a.delayMs}:all=1[a${k}]`).join(\";\");\nconst labels = audio.map((_, k) => `[a${k}]`).join(\"\");\nconst mix = `${labels}amix=inputs=${audio.length}:normalize=0:duration=longest[a]`;\nff(\n \"-i\",\n W(\"video.mp4\"),\n ...inputs,\n \"-filter_complex\",\n `${delays};${mix};[0:v]ass=${assPath}[v]`,\n \"-map\",\n \"[v]\",\n \"-map\",\n \"[a]\",\n ...ENC,\n \"-c:a\",\n \"aac\",\n \"-b:a\",\n \"128k\",\n \"-movflags\",\n \"+faststart\",\n \"-t\",\n T.toFixed(3),\n join(out, \"qa-loom.mp4\"),\n);\nconst chars = steps.reduce((n, s) => n + s.narration.length, 0);\nconsole.log(\n JSON.stringify({ durationS: +T.toFixed(2), steps: steps.length, cues: cues.length, chars }),\n);\n"
66
+ },
67
+ {
68
+ "path": "run-qa.sh",
69
+ "content": "#!/usr/bin/env bash\n# run-qa.sh: record a UI QA pass with agent-browser and write steps.json.\n# Every step offset is in seconds from the moment recording starts.\n#\n# Usage: BASE_URL=http://localhost:5274 OUT=./loom bash run-qa.sh\n# Needs: agent-browser (with `record`), jq, GNU date (%N). No bc: time math runs in jq.\n#\n# Replace the two example steps with the steps that prove your change.\nset -euo pipefail\nBASE_URL=${BASE_URL:-http://localhost:5274}\nOUT=${OUT:-./loom}\nmkdir -p \"$OUT\"\nLOG=\"$OUT/steps.ndjson\"\n: >\"$LOG\"\n\nnow() { date +%s.%N; }\n# act: call right before the action fires. It marks the step start.\nact() { STEP_START=$(now); }\n# step <action> <observed> <verdict> <narration>: call AFTER the resulting screen has\n# rendered (after an `agent-browser wait <selector>`). That moment becomes visualAt.\nstep() {\n jq -nc --arg a \"$1\" --arg o \"$2\" --arg v \"$3\" --arg n \"$4\" \\\n --arg t0 \"$T0\" --arg s \"$STEP_START\" --arg va \"$(now)\" \\\n '{action:$a, observed:$o, verdict:$v, narration:$n,\n start:(($s|tonumber)-($t0|tonumber)), visualAt:(($va|tonumber)-($t0|tonumber))}' >>\"$LOG\"\n}\n\n# Log in and seed data BEFORE recording starts.\nagent-browser set viewport 1920 1080 >/dev/null\nagent-browser open \"$BASE_URL\" >/dev/null\nagent-browser wait 1500 >/dev/null\nagent-browser record start \"$OUT/raw.webm\" --cursor --fps 30 >/dev/null\nT0=$(now)\n\nact\nagent-browser reload >/dev/null\nagent-browser wait h1 >/dev/null\nstep \"Load home page\" \"Heading and nav render\" pass \"The home page loads with its heading and nav.\"\nagent-browser wait 800 >/dev/null # dwell so the viewer sees the result before the next action\n\nact\nagent-browser open \"$BASE_URL/settings\" >/dev/null\nagent-browser wait h1 >/dev/null\nstep \"Open settings\" \"Settings page renders\" pass \"The settings page opens.\"\nagent-browser wait 800 >/dev/null\n\nEND=$(jq -n --arg a \"$(now)\" --arg b \"$T0\" '($a|tonumber)-($b|tonumber)')\nagent-browser record stop >/dev/null\n\n# Each step ends where the next one starts; the last one ends at record stop.\njq -s --argjson end \"$END\" '\n {steps: ([to_entries[] | .value + {idx: (.key + 1)}]\n | . as $s\n | [range(0; length) as $i\n | $s[$i] + {end: (if $i + 1 < ($s | length) then $s[$i + 1].start else $end end)}])}\n' \"$LOG\" >\"$OUT/steps.json\"\njq -c '.steps[] | {idx, start, visualAt, end, verdict}' \"$OUT/steps.json\"\n"
70
+ }
71
+ ],
80
72
  "questioning": [
81
73
  {
82
74
  "path": "template.md",