@desplega.ai/agent-swarm 1.165.0 → 1.167.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 (296) hide show
  1. package/dist/acp-adapter-zpvy6321.js +34 -0
  2. package/dist/{actions-jna0b3wm.js → actions-fq2zrwtf.js} +30 -34
  3. package/dist/{amp-adapter-0eve81pz.js → amp-adapter-5gbk7501.js} +7 -7
  4. package/dist/{anthropic-messages-hst62ws9.js → anthropic-messages-13yn502s.js} +4 -4
  5. package/dist/{app-75hm5cfa.js → app-nctetn1e.js} +11 -11
  6. package/dist/{assistant-yzd51j33.js → assistant-0aq8gjvj.js} +31 -35
  7. package/dist/{azure-openai-responses-mh8tvf78.js → azure-openai-responses-9st34pfz.js} +5 -5
  8. package/dist/{boot-reembed-z158xq32.js → boot-reembed-1ze5853n.js} +14 -16
  9. package/dist/{boot-reembed-2ex2mcsh.js → boot-reembed-fmp5ww69.js} +15 -16
  10. package/dist/boot-scrub-sweep-cbp8xd4e.js +212 -0
  11. package/dist/{boot-seeds-2bmnh18p.js → boot-seeds-22tx8a53.js} +13 -12
  12. package/dist/{claude-adapter-jbj1mycz.js → claude-adapter-0jhgeh1d.js} +25 -25
  13. package/dist/{claude-managed-adapter-17ggz09t.js → claude-managed-adapter-tzwxy803.js} +5 -5
  14. package/dist/{claude-managed-setup-ptm1nzm2.js → claude-managed-setup-nrhfx5b0.js} +5 -5
  15. package/dist/{claude-sdk-session-9re1g7r2.js → claude-sdk-session-wh77rh83.js} +2632 -2614
  16. package/dist/{cli-zsh093hk.js → cli-014vns2y.js} +63 -1
  17. package/dist/{cli-qvfk83fy.js → cli-13xwcqvj.js} +2 -2
  18. package/dist/{cli-8gpbwy09.js → cli-1yrcfa5s.js} +5 -5
  19. package/dist/{cli-xtgtyafv.js → cli-577bxkf6.js} +212 -451
  20. package/dist/{cli-ma3zvh39.js → cli-5qkcdv8c.js} +6118 -5874
  21. package/dist/{cli-cwxhbf43.js → cli-5t5my0h8.js} +85 -2
  22. package/dist/{cli-zvckv750.js → cli-69egj654.js} +2 -2
  23. package/dist/{cli-9xb89xca.js → cli-6bpvv8h4.js} +9 -9
  24. package/dist/{cli-90v942q1.js → cli-6qxmjpem.js} +8 -8
  25. package/dist/{cli-9866bd07.js → cli-6vfz44sr.js} +1 -1
  26. package/dist/{cli-32bca7nr.js → cli-7avy4v8w.js} +1 -1
  27. package/dist/{cli-xb3c6aat.js → cli-7mhy3ths.js} +21 -18
  28. package/dist/{cli-q38n4bq0.js → cli-95zs84qw.js} +3 -3
  29. package/dist/cli-a35hzdve.js +33 -0
  30. package/dist/{cli-x3x0qwky.js → cli-bxqez1p0.js} +51 -23
  31. package/dist/{cli-prt9emct.js → cli-c2tpw9ck.js} +3 -3
  32. package/dist/{cli-0n4fasz0.js → cli-c8r99akr.js} +16 -16
  33. package/dist/{cli-5anb0ev3.js → cli-cexctgn5.js} +89 -26
  34. package/dist/{cli-bns5c8k9.js → cli-cf2vgn9d.js} +105 -91
  35. package/dist/{cli-ng97fjq7.js → cli-cmxkbzzs.js} +33 -7
  36. package/dist/{cli-nexwh8zg.js → cli-g7k7zbcz.js} +12 -5
  37. package/dist/{cli-mmzwwptd.js → cli-hf5s9pgf.js} +1 -1
  38. package/dist/{cli-nvvfd8bq.js → cli-jbzbr36n.js} +4091 -3793
  39. package/dist/{cli-zn2v3t8v.js → cli-jvmtxqtc.js} +27 -15
  40. package/dist/{cli-pkke6geq.js → cli-km4x66j0.js} +83 -65
  41. package/dist/cli-kw0jcfje.js +516 -0
  42. package/dist/{cli-28smfvbn.js → cli-m7vmeepg.js} +32 -19
  43. package/dist/{cli-hpryr6em.js → cli-mb75wpxf.js} +1 -1
  44. package/dist/{cli-qdv8kgz7.js → cli-mkdsy1nq.js} +14018 -3333
  45. package/dist/{cli-b0f70z24.js → cli-mn8qkd8v.js} +1 -1
  46. package/dist/{cli-420bdgw0.js → cli-mv2520nm.js} +1 -1
  47. package/dist/{cli-n9ehc28t.js → cli-mxry91sa.js} +31 -10
  48. package/dist/{cli-wfajtmf7.js → cli-mxvnnv5q.js} +596 -13
  49. package/dist/cli-nkqjj42q.js +104 -0
  50. package/dist/{cli-e7t4snpa.js → cli-p8rt8spp.js} +5 -5
  51. package/dist/{cli-gh5yqzpe.js → cli-pkj1w46r.js} +12 -7
  52. package/dist/cli-q1vnz8wv.js +96 -0
  53. package/dist/{cli-fj86xr3c.js → cli-qd97v6z2.js} +2 -2
  54. package/dist/{cli-ba66wyhy.js → cli-qzmzwegq.js} +60 -20
  55. package/dist/{cli-5b2fjjzh.js → cli-r7eeddkq.js} +14 -9
  56. package/dist/{cli-8ssjez7e.js → cli-rhy7z1rp.js} +22 -11
  57. package/dist/{cli-4mevd1hf.js → cli-s9y81s7j.js} +5 -5
  58. package/dist/{cli-59jn5twa.js → cli-tc5zec7v.js} +7 -7
  59. package/dist/{cli-ttbsse3f.js → cli-tmhsxhb8.js} +1 -1
  60. package/dist/{cli-k6xx3pve.js → cli-tx2zvn23.js} +4 -4
  61. package/dist/{cli-pjxnr1jn.js → cli-ty4bzkqr.js} +5 -5
  62. package/dist/{cli-nfygw434.js → cli-v1pwxreq.js} +55 -52
  63. package/dist/{cli-ftry7975.js → cli-v1w7pbd8.js} +3 -2
  64. package/dist/{cli-10zmp1p7.js → cli-vr5avem2.js} +27 -32
  65. package/dist/{cli-bzr0ftht.js → cli-wnb9b2g3.js} +19 -0
  66. package/dist/cli-wt7ccy4r.js +2654 -0
  67. package/dist/{cli-50eg13rj.js → cli-wydz3feg.js} +549 -321
  68. package/dist/{cli-krqm5f11.js → cli-xezn9nkh.js} +16 -13
  69. package/dist/{cli-8kt0bmgx.js → cli-xf713zz0.js} +2 -2
  70. package/dist/{cli-x7afk5wm.js → cli-xvc3q9br.js} +1 -1
  71. package/dist/{cli-9s3h836g.js → cli-xvdgasgy.js} +1 -1
  72. package/dist/{cli-j0pdrmwg.js → cli-y0kjpfm9.js} +3 -3
  73. package/dist/{cli-6zcfw0x7.js → cli-y195hv1a.js} +1 -1
  74. package/dist/{cli-amwhs8h2.js → cli-ykt8f7va.js} +22 -9
  75. package/dist/{cli-1kzfvd1z.js → cli-ymmc1zxg.js} +15 -11
  76. package/dist/{cli-xnzrzets.js → cli-ypgba1a8.js} +2 -2
  77. package/dist/{cli-bdv39kgj.js → cli-z1dqhc9y.js} +31 -20
  78. package/dist/{cli-fw6vdcq5.js → cli-zp67qg5x.js} +34 -1
  79. package/dist/{cli-qbksc292.js → cli-zy39d433.js} +2 -0
  80. package/dist/cli.js +36 -32
  81. package/dist/{cloudflare-workers-ai-system-one-tt484t61.js → cloudflare-workers-ai-system-one-sta45jf9.js} +7 -5
  82. package/dist/{codex-adapter-3qnxshje.js → codex-adapter-cgesnjkr.js} +20 -20
  83. package/dist/{codex-login-brkxhhdg.js → codex-login-g1cyt5zm.js} +2 -2
  84. package/dist/{codex-session-runner-612wnapp.js → codex-session-runner-8kd7ddhr.js} +20 -20
  85. package/dist/{commands-2ybfcxkj.js → commands-h6zaaxym.js} +11 -10
  86. package/dist/{cursor-adapter-p3jgdehv.js → cursor-adapter-v8mm6bp5.js} +7 -7
  87. package/dist/{db-f4ans0jj.js → db-qg3myn53.js} +13 -10
  88. package/dist/{devin-adapter-dhzt01ff.js → devin-adapter-bffjj5n4.js} +2 -2
  89. package/dist/{dispatcher-pk182apd.js → dispatcher-7vnwj9qw.js} +28 -32
  90. package/dist/{dsh-adapter-7twaeps1.js → dsh-adapter-tz6hfp5r.js} +7 -7
  91. package/dist/{e2b-y8t3jzyz.js → e2b-nd50eccg.js} +1 -1
  92. package/dist/{easter-egg-3d-mtpnw06s.js → easter-egg-3d-66zxervv.js} +2 -2
  93. package/dist/{event-bus-w7ac4n3c.js → event-bus-vnfthafg.js} +2 -2
  94. package/dist/{execute-s4ccjq3n.js → execute-h4jgevyw.js} +61 -11
  95. package/dist/{google-generative-ai-d6nphg3q.js → google-generative-ai-b8zxrpax.js} +5 -5
  96. package/dist/{google-vertex-cpshkref.js → google-vertex-nrdd697t.js} +5 -5
  97. package/dist/grok-adapter-jdt2dz2s.js +39 -0
  98. package/dist/{handlers-g2wnr4t9.js → handlers-2qtkpncr.js} +31 -35
  99. package/dist/{hook-f7jt9vnm.js → hook-51yfcgsv.js} +48 -39
  100. package/dist/{hook-bbdpnkb4.js → hook-qnbdgwvd.js} +15 -17
  101. package/dist/{http-w40q4w94.js → http-jsmtag08.js} +536 -231
  102. package/dist/{index-056j5kzn.js → index-2t9kesn1.js} +13 -12
  103. package/dist/{index-cj1r9q83.js → index-bt8wkv1w.js} +29 -33
  104. package/dist/{index-7n70jwbb.js → index-cx1sb9bq.js} +42 -35
  105. package/dist/{index-9bfssf54.js → index-ekywyv2v.js} +28 -32
  106. package/dist/{index-fdk1t4d6.js → index-n0rxa4ca.js} +11962 -9900
  107. package/dist/{index-k8xc083e.js → index-t4g06w93.js} +28 -32
  108. package/dist/{keepalive-1tj10tf4.js → keepalive-4aezx4bj.js} +16 -12
  109. package/dist/{lead-st0bg7v4.js → lead-r44bkpm5.js} +52 -56
  110. package/dist/{llama-cpp-classify-tgj35k8d.js → llama-cpp-classify-6erb7wwv.js} +3 -1
  111. package/dist/{maintenance-nz71zats.js → maintenance-grz5jrzh.js} +12 -13
  112. package/dist/{mistral-conversations-8445ddjn.js → mistral-conversations-059y51zt.js} +4 -4
  113. package/dist/{node-f0ykebbv.js → node-qm9eb6z0.js} +25 -7
  114. package/dist/{oauth-refresh-sweep-aefzwvvq.js → oauth-refresh-sweep-wm4ctjfj.js} +15 -11
  115. package/dist/{onboard-kgpr9mbn.js → onboard-v60zgqf0.js} +2 -2
  116. package/dist/{openai-codex-responses-9ynm88zy.js → openai-codex-responses-jzxkta9b.js} +8 -7
  117. package/dist/{openai-completions-nhqwjmaw.js → openai-completions-663yncfq.js} +4 -4
  118. package/dist/openai-decisions-vj9nkpf8.js +162 -0
  119. package/dist/{openai-responses-xx1c4kgm.js → openai-responses-m3g43rrt.js} +5 -5
  120. package/dist/{opencode-adapter-0c8e70dk.js → opencode-adapter-yjyqynzf.js} +8 -8
  121. package/dist/{openrouter-images-8yx3487g.js → openrouter-images-kr60n6b9.js} +1 -1
  122. package/dist/{otel-impl-x8zp4y5a.js → otel-impl-ghh4dyf8.js} +2 -2
  123. package/dist/{pi-messages-vxdt8v7j.js → pi-messages-xszk613n.js} +1 -1
  124. package/dist/{pi-mono-adapter-t280ah5c.js → pi-mono-adapter-0eg8kw2c.js} +69 -52
  125. package/dist/{pricing-refresh-9e0q7cwk.js → pricing-refresh-mjrqvnvw.js} +12 -11
  126. package/dist/{rbac-roles-790j3ga8.js → rbac-roles-axv4d74x.js} +11 -10
  127. package/dist/{rbac-roles-kcp6zge2.js → rbac-roles-fd5tskj9.js} +11 -10
  128. package/dist/{render-v2-tch4nefj.js → render-v2-k50j33qd.js} +11 -11
  129. package/dist/{runtime-v2t1r79e.js → runtime-h9ha1mq6.js} +4 -4
  130. package/dist/{setup-rcm303ns.js → setup-4syh0pd2.js} +2 -2
  131. package/dist/{typesafe-system-one-datfr82p.js → typesafe-system-one-e988vty7.js} +7 -5
  132. package/dist/{virtual-modules-wm8mxdxx.js → virtual-modules-ftr9kwe8.js} +11 -11
  133. package/dist/{work-objects-ctg7t70w.js → work-objects-jcdbg8rr.js} +32 -36
  134. package/dist/{worker-5dp0xm55.js → worker-x8j8c9sx.js} +52 -56
  135. package/dist/{x-v3yw66zs.js → x-ma4gnfgy.js} +2 -2
  136. package/openapi.json +5 -2
  137. package/package.json +15 -11
  138. package/src/be/automation-preflight.ts +9 -4
  139. package/src/be/boot-scrub-sweep.ts +302 -0
  140. package/src/be/crypto/key-bootstrap.ts +1 -1
  141. package/src/be/db/approvals.ts +15 -1
  142. package/src/be/db/codex-oauth-device-flows.ts +40 -0
  143. package/src/be/db/config-secrets.ts +31 -0
  144. package/src/be/db/runtime.ts +4 -0
  145. package/src/be/db/script-runs.ts +15 -0
  146. package/src/be/db/tasks/write.ts +1 -1
  147. package/src/be/db-queries/mcp-oauth.ts +5 -0
  148. package/src/be/db-queries/oauth.ts +77 -0
  149. package/src/be/db.ts +162 -59
  150. package/src/be/events.ts +17 -1
  151. package/src/be/memory/index-content.ts +2 -2
  152. package/src/be/memory/providers/openai-embedding.ts +7 -2
  153. package/src/be/memory/providers/sqlite-store.ts +63 -11
  154. package/src/be/memory/raters/llm-summarizer.ts +7 -2
  155. package/src/be/memory/types.ts +14 -0
  156. package/src/be/migrations/201_workflow_replay_columns.sql +10 -0
  157. package/src/be/migrations/202_codex_oauth_device_flows.sql +24 -0
  158. package/src/be/modelsdev-cache.json +61 -0
  159. package/src/be/scripts/db.ts +28 -4
  160. package/src/be/scripts/source-secrets.ts +37 -0
  161. package/src/be/scrub-json.ts +32 -0
  162. package/src/be/sealed-json.ts +59 -0
  163. package/src/be/secret-registry.ts +131 -0
  164. package/src/be/seed-pricing.ts +84 -53
  165. package/src/be/swarm-config-guard.ts +13 -0
  166. package/src/be/workflow-replay.ts +77 -0
  167. package/src/commands/provider-credentials.ts +28 -5
  168. package/src/commands/runner.ts +7 -12
  169. package/src/heartbeat/heartbeat.ts +91 -19
  170. package/src/hooks/hook.ts +72 -27
  171. package/src/http/codex-oauth-device.ts +8 -10
  172. package/src/http/db-query-bounded.ts +3 -0
  173. package/src/http/db-query-shared.ts +107 -15
  174. package/src/http/db-query.ts +10 -2
  175. package/src/http/index.ts +23 -6
  176. package/src/http/mcp.ts +5 -0
  177. package/src/http/onboarding.ts +2 -0
  178. package/src/http/script-runs.ts +9 -1
  179. package/src/http/scripts.ts +15 -0
  180. package/src/http/workflows.ts +3 -2
  181. package/src/oauth/mcp-wrapper.ts +1 -1
  182. package/src/providers/acp-adapter.ts +25 -11
  183. package/src/providers/acp-target-catalog.ts +35 -1
  184. package/src/providers/acp-targets.ts +78 -7
  185. package/src/providers/amp-adapter.ts +1 -1
  186. package/src/providers/claude-adapter.ts +26 -0
  187. package/src/providers/codex-adapter.ts +30 -3
  188. package/src/providers/codex-oauth/auth-json.ts +21 -4
  189. package/src/providers/codex-oauth/types.ts +2 -0
  190. package/src/providers/cursor-adapter.ts +1 -1
  191. package/src/providers/dsh-adapter.ts +10 -1
  192. package/src/providers/grok-adapter.ts +18 -2
  193. package/src/providers/opencode-adapter.ts +21 -5
  194. package/src/providers/pi-mono-extension.ts +56 -25
  195. package/src/providers/pricing-sources.md +2 -0
  196. package/src/script-workflows/supervisor.ts +11 -2
  197. package/src/scripts-runtime/eval-harness.ts +2 -12
  198. package/src/scripts-runtime/executors/quickjs-bundle.ts +152 -0
  199. package/src/scripts-runtime/executors/quickjs-prelude.ts +285 -0
  200. package/src/scripts-runtime/executors/quickjs-runner.ts +580 -0
  201. package/src/scripts-runtime/executors/quickjs-worker.ts +81 -0
  202. package/src/scripts-runtime/executors/quickjs.ts +196 -0
  203. package/src/scripts-runtime/executors/registry.ts +2 -0
  204. package/src/scripts-runtime/executors/sourcemap.ts +79 -0
  205. package/src/scripts-runtime/script-error-hints.ts +14 -0
  206. package/src/server.ts +66 -4
  207. package/src/tests/acp-adapter.test.ts +277 -12
  208. package/src/tests/boot-scrub-sweep.test.ts +390 -0
  209. package/src/tests/check-text-columns.test.ts +145 -0
  210. package/src/tests/ci-unit-test-report.test.ts +73 -0
  211. package/src/tests/claude-default-route.test.ts +75 -0
  212. package/src/tests/claude-hook-output.test.ts +233 -0
  213. package/src/tests/claude-sdk-process.fixture.ts +13 -2
  214. package/src/tests/codex-adapter-pool-auth-revalidate.test.ts +4 -2
  215. package/src/tests/codex-adapter.test.ts +166 -26
  216. package/src/tests/codex-oauth-auth-timestamps.test.ts +76 -0
  217. package/src/tests/codex-oauth-device.test.ts +23 -37
  218. package/src/tests/codex-oauth.test.ts +4 -1
  219. package/src/tests/context-window.test.ts +2 -2
  220. package/src/tests/credential-check.test.ts +51 -1
  221. package/src/tests/db-query-denylist.test.ts +202 -0
  222. package/src/tests/db-task-read-contract.test.ts +1 -0
  223. package/src/tests/db-task-write-contract.test.ts +1 -0
  224. package/src/tests/dsh-adapter.test.ts +23 -1
  225. package/src/tests/enabled-tools.test.ts +269 -0
  226. package/src/tests/entrypoint-acp-target-binary.test.ts +31 -1
  227. package/src/tests/entrypoint-codex-oauth-seed.test.ts +12 -0
  228. package/src/tests/events-scrub.test.ts +88 -0
  229. package/src/tests/fixtures/secret-corpus.ts +320 -0
  230. package/src/tests/grok-adapter.test.ts +13 -0
  231. package/src/tests/haiku-5-5-pricing.test.ts +38 -0
  232. package/src/tests/heartbeat-checklist.test.ts +158 -1
  233. package/src/tests/onboarding.test.ts +44 -0
  234. package/src/tests/opencode-adapter.test.ts +66 -3
  235. package/src/tests/opencode-plugin.test.ts +37 -0
  236. package/src/tests/openrouter-attribution-call-sites.test.ts +204 -0
  237. package/src/tests/openrouter-base-url.test.ts +30 -39
  238. package/src/tests/pi-mono-extension.test.ts +161 -2
  239. package/src/tests/pricing-refresh.test.ts +70 -1
  240. package/src/tests/reasoning-effort.test.ts +3 -1
  241. package/src/tests/script-connections.test.ts +5 -0
  242. package/src/tests/script-executor-conformance.test.ts +3 -0
  243. package/src/tests/script-executor-quickjs.test.ts +442 -0
  244. package/src/tests/script-executor-registry.test.ts +6 -1
  245. package/src/tests/script-runs-http.test.ts +88 -1
  246. package/src/tests/script-runs-scrub.test.ts +275 -0
  247. package/src/tests/scripts-http.test.ts +82 -1
  248. package/src/tests/scrubbed-text-12e.test.ts +289 -0
  249. package/src/tests/sealed-key-guard.test.ts +166 -0
  250. package/src/tests/secret-canary-sweep.test.ts +958 -0
  251. package/src/tests/secret-registry.test.ts +355 -0
  252. package/src/tests/secret-rules.test.ts +230 -0
  253. package/src/tests/secret-scrubber.test.ts +3 -2
  254. package/src/tests/telemetry-trigger.test.ts +12 -1
  255. package/src/tests/workflow-llm.test.ts +14 -0
  256. package/src/tests/workflow-replay.test.ts +518 -0
  257. package/src/tests/workflow-scrub.test.ts +242 -0
  258. package/src/tests/workflow-system-one-decision-providers.test.ts +33 -0
  259. package/src/tools/db-query.ts +1 -1
  260. package/src/tools/inject-learning.ts +1 -1
  261. package/src/tools/request-human-input.ts +7 -2
  262. package/src/tools/workflows/get-workflow-run.ts +3 -2
  263. package/src/types.ts +1 -1
  264. package/src/utils/enabled-tools.ts +27 -0
  265. package/src/utils/internal-ai/complete-structured.ts +11 -3
  266. package/src/utils/openrouter-base-url.ts +23 -0
  267. package/src/utils/secret-rules.generated.ts +2055 -0
  268. package/src/utils/secret-scrubber.ts +363 -29
  269. package/src/workflows/engine.ts +39 -4
  270. package/src/workflows/executors/raw-llm.ts +1 -0
  271. package/src/workflows/executors/system-one-decision.ts +2 -0
  272. package/src/workflows/executors/validate.ts +1 -0
  273. package/src/workflows/executors/workflow-llm.ts +9 -2
  274. package/src/workflows/recovery.ts +10 -1
  275. package/src/workflows/resume.ts +62 -3
  276. package/src/workflows/retry-poller.ts +3 -0
  277. package/templates/skills/agent-browser/SKILL.md +2 -0
  278. package/templates/skills/agent-browser/content.md +2 -0
  279. package/templates/skills/code-quality/content.md +1 -0
  280. package/templates/skills/heartbeat-runbook/content.md +1 -1
  281. package/templates/skills/work-on-task/SKILL.md +4 -0
  282. package/templates/skills/work-on-task/content.md +4 -0
  283. package/templates/skills/workflow-iterate/content.md +9 -0
  284. package/dist/acp-adapter-w6zws6me.js +0 -23
  285. package/dist/boot-scrub-logs-246yn076.js +0 -99
  286. package/dist/cli-3a4m1aws.js +0 -572
  287. package/dist/cli-3dpj7atn.js +0 -11
  288. package/dist/cli-a87b04k8.js +0 -951
  289. package/dist/cli-cvk6yzw5.js +0 -40
  290. package/dist/cli-gvcgmx69.js +0 -183
  291. package/dist/cli-h1spv8v4.js +0 -427
  292. package/dist/cli-jqt9hrnz.js +0 -168
  293. package/dist/cli-yhmwmv0g.js +0 -9281
  294. package/dist/grok-adapter-yt9pab3k.js +0 -29
  295. package/src/be/boot-scrub-logs.ts +0 -132
  296. package/dist/{codex-hook-d2rkvf39.js → codex-hook-15dgc4cb.js} +1 -1
package/src/hooks/hook.ts CHANGED
@@ -168,12 +168,25 @@ export async function postHookProfileUpdate({
168
168
  }
169
169
 
170
170
  /**
171
- * Hook response for blocking actions
171
+ * Hook JSON responses. Claude Code parses stdout as JSON only when the whole
172
+ * stdout is one JSON object, so these must be the only stdout of the event.
172
173
  * See: https://code.claude.com/docs/en/hooks
173
174
  */
174
- interface HookBlockResponse {
175
- decision: "block";
176
- reason: string;
175
+ type HookBlockResponse =
176
+ | {
177
+ hookSpecificOutput: {
178
+ hookEventName: "PreToolUse";
179
+ permissionDecision: "deny";
180
+ permissionDecisionReason: string;
181
+ };
182
+ }
183
+ | { decision: "block"; reason: string };
184
+
185
+ interface HookContextResponse {
186
+ hookSpecificOutput: {
187
+ hookEventName: "PreToolUse" | "PostToolUse";
188
+ additionalContext: string;
189
+ };
177
190
  }
178
191
 
179
192
  /**
@@ -850,12 +863,33 @@ export async function handleHook(): Promise<void> {
850
863
 
851
864
  /**
852
865
  * Output a blocking response to stop Claude from continuing.
853
- * This is used when a task has been cancelled.
866
+ * Must be the only stdout of the event: any plain line before it makes
867
+ * Claude Code read the whole stdout as text and run the tool anyway.
854
868
  */
855
869
  const outputBlockResponse = (reason: string): void => {
856
- const response: HookBlockResponse = {
857
- decision: "block",
858
- reason,
870
+ const response: HookBlockResponse =
871
+ msg.hook_event_name === "PreToolUse"
872
+ ? {
873
+ hookSpecificOutput: {
874
+ hookEventName: "PreToolUse",
875
+ permissionDecision: "deny",
876
+ permissionDecisionReason: reason,
877
+ },
878
+ }
879
+ : { decision: "block", reason };
880
+ console.log(JSON.stringify(response));
881
+ };
882
+
883
+ /**
884
+ * Output context for the model on a tool event. Plain stdout on PreToolUse
885
+ * and PostToolUse never reaches the model; additionalContext does.
886
+ */
887
+ const outputToolContext = (
888
+ hookEventName: "PreToolUse" | "PostToolUse",
889
+ additionalContext: string,
890
+ ): void => {
891
+ const response: HookContextResponse = {
892
+ hookSpecificOutput: { hookEventName, additionalContext },
859
893
  };
860
894
  console.log(JSON.stringify(response));
861
895
  };
@@ -988,8 +1022,23 @@ export async function handleHook(): Promise<void> {
988
1022
  // Get current agent info
989
1023
  const agentInfo = await getAgentInfo();
990
1024
 
991
- // Always output agent status with system tray
992
- if (agentInfo) {
1025
+ // A block must be the only stdout of the event, so a cancelled task is
1026
+ // checked before the status line goes out.
1027
+ if (
1028
+ msg.hook_event_name === "UserPromptSubmit" &&
1029
+ agentInfo &&
1030
+ !agentInfo.isLead &&
1031
+ agentInfo.status === "busy"
1032
+ ) {
1033
+ if (await checkAndBlockIfCancelled(true)) {
1034
+ return;
1035
+ }
1036
+ }
1037
+
1038
+ // Output agent status with system tray. Skipped on tool events: their plain
1039
+ // stdout never reaches the model and would break a JSON decision.
1040
+ const isToolEvent = msg.hook_event_name === "PreToolUse" || msg.hook_event_name === "PostToolUse";
1041
+ if (agentInfo && !isToolEvent) {
993
1042
  // Base status line
994
1043
  console.log(
995
1044
  `You are registered as ${agentInfo.isLead ? "lead" : "worker"} agent "${agentInfo.name}" (ID: ${agentInfo.id}, status: ${agentInfo.status}).`,
@@ -1004,7 +1053,7 @@ export async function handleHook(): Promise<void> {
1004
1053
  }
1005
1054
  } else if (
1006
1055
  shouldShowRegistrationNudge({
1007
- agentInfoPresent: false,
1056
+ agentInfoPresent: Boolean(agentInfo),
1008
1057
  eventType: msg.hook_event_name,
1009
1058
  hasAgentIdHeader: hasAgentIdHeader(),
1010
1059
  })
@@ -1131,8 +1180,7 @@ export async function handleHook(): Promise<void> {
1131
1180
  }
1132
1181
 
1133
1182
  // Block `gh pr create|edit` when the body for a public repo leaks internal
1134
- // identifiers. Exit code 2 blocks the call even though the status line
1135
- // above already went to stdout, and stderr reaches the model.
1183
+ // identifiers. Exit code 2 blocks the call and stderr reaches the model.
1136
1184
  if (msg.tool_name === "Bash") {
1137
1185
  const prBodyBlock = await guardGhPrBody(msg.tool_input, msg.cwd ?? process.cwd());
1138
1186
  if (prBodyBlock) {
@@ -1142,6 +1190,9 @@ export async function handleHook(): Promise<void> {
1142
1190
  }
1143
1191
  }
1144
1192
 
1193
+ // Emitted last, so a later block stays the only stdout of the event.
1194
+ let loopWarning: string | undefined;
1195
+
1145
1196
  // Tool loop detection (workers only, when processing a task)
1146
1197
  if (agentInfo && !agentInfo.isLead && agentInfo.status === "busy") {
1147
1198
  const loopTaskFile = await readTaskFile();
@@ -1162,7 +1213,7 @@ export async function handleHook(): Promise<void> {
1162
1213
  }
1163
1214
 
1164
1215
  if (loopResult.severity === "warning" && loopResult.reason) {
1165
- console.log(`Warning: ${loopResult.reason}`);
1216
+ loopWarning = `Warning: ${loopResult.reason}`;
1166
1217
  }
1167
1218
  }
1168
1219
  }
@@ -1179,6 +1230,9 @@ export async function handleHook(): Promise<void> {
1179
1230
  }
1180
1231
  }
1181
1232
 
1233
+ if (loopWarning) {
1234
+ outputToolContext("PreToolUse", loopWarning);
1235
+ }
1182
1236
  break;
1183
1237
  }
1184
1238
 
@@ -1200,7 +1254,8 @@ export async function handleHook(): Promise<void> {
1200
1254
  method: "PUT",
1201
1255
  headers: mcpConfig!.headers,
1202
1256
  }).catch((e) => {
1203
- console.debug("Failed to update agent activity timestamp:", e);
1257
+ // stderr: stdout on this event is reserved for one JSON object.
1258
+ console.error("Failed to update agent activity timestamp:", e);
1204
1259
  });
1205
1260
  }
1206
1261
 
@@ -1270,7 +1325,8 @@ export async function handleHook(): Promise<void> {
1270
1325
  if (msg.tool_name?.endsWith("send-task")) {
1271
1326
  const maybeTaskId = (msg.tool_response as { task?: { id?: string } })?.task?.id;
1272
1327
 
1273
- console.log(
1328
+ outputToolContext(
1329
+ "PostToolUse",
1274
1330
  `Task sent successfully.${maybeTaskId ? ` Task ID: ${maybeTaskId}.` : ""} Monitor progress using the get-task-details tool periodically.`,
1275
1331
  );
1276
1332
  }
@@ -1278,17 +1334,6 @@ export async function handleHook(): Promise<void> {
1278
1334
  }
1279
1335
  break;
1280
1336
 
1281
- case "UserPromptSubmit": {
1282
- // For worker agents, check if their task has been cancelled
1283
- // This catches cancellations at the start of a new iteration
1284
- if (agentInfo && !agentInfo.isLead && agentInfo.status === "busy") {
1285
- if (await checkAndBlockIfCancelled(true)) {
1286
- return; // Exit early
1287
- }
1288
- }
1289
- break;
1290
- }
1291
-
1292
1337
  case "Stop":
1293
1338
  // Clean up any artifact tunnels managed by PM2
1294
1339
  try {
@@ -1,7 +1,8 @@
1
1
  import type { IncomingMessage, ServerResponse } from "node:http";
2
2
  import { z } from "zod";
3
3
  import { decryptSecret, encryptSecret, getEncryptionKey } from "../be/crypto";
4
- import { getDbClient, getKv, upsertKv, upsertSwarmConfig } from "../be/db";
4
+ import { getDbClient, upsertSwarmConfig } from "../be/db";
5
+ import { getCodexDeviceFlow, upsertCodexDeviceFlow } from "../be/db/codex-oauth-device-flows";
5
6
  import { updateOnboardingAiFromCodexDevice } from "../be/onboarding";
6
7
  import {
7
8
  CODEX_DEVICE_VERIFICATION_URL,
@@ -17,7 +18,6 @@ import { ensureConfigAdmin } from "./config";
17
18
  import { route } from "./route-def";
18
19
  import { jsonError } from "./utils";
19
20
 
20
- const FLOW_NAMESPACE = "codex-oauth-device";
21
21
  const FLOW_TTL_MS = 15 * 60 * 1_000;
22
22
  const POLL_LEASE_MS = 45_000;
23
23
 
@@ -85,10 +85,10 @@ function registerFlowSecrets(state: FlowState): void {
85
85
  }
86
86
 
87
87
  async function readFlow(flowId: string): Promise<FlowState | null> {
88
- const entry = await getKv(FLOW_NAMESPACE, flowId);
89
- if (!entry || typeof entry.value !== "string") return null;
88
+ const entry = await getCodexDeviceFlow(flowId);
89
+ if (!entry) return null;
90
90
  try {
91
- const plaintext = decryptSecret(entry.value, getEncryptionKey());
91
+ const plaintext = decryptSecret(entry.state, getEncryptionKey());
92
92
  const parsed = FlowStateSchema.safeParse(JSON.parse(plaintext));
93
93
  if (!parsed.success) return null;
94
94
  registerFlowSecrets(parsed.data);
@@ -99,11 +99,9 @@ async function readFlow(flowId: string): Promise<FlowState | null> {
99
99
  }
100
100
 
101
101
  async function writeFlow(flowId: string, state: FlowState): Promise<void> {
102
- await upsertKv({
103
- namespace: FLOW_NAMESPACE,
104
- key: flowId,
105
- value: encryptSecret(JSON.stringify(state), getEncryptionKey()),
106
- valueType: "string",
102
+ await upsertCodexDeviceFlow({
103
+ flowId,
104
+ state: encryptSecret(JSON.stringify(state), getEncryptionKey()),
107
105
  expiresAt: state.expiresAt,
108
106
  });
109
107
  }
@@ -37,6 +37,7 @@
37
37
  import { getDb, resolveSqliteVecExtensionPath } from "../be/db";
38
38
  import type { DbQueryResult } from "./db-query-shared";
39
39
  import {
40
+ assertNoDeniedTables,
40
41
  assertSingleStatement,
41
42
  DbQueryConcurrencyCapError,
42
43
  DbQueryTimeoutError,
@@ -203,6 +204,8 @@ export async function executeReadOnlyQueryBounded(
203
204
  maxRows?: number,
204
205
  ): Promise<DbQueryResult> {
205
206
  assertSingleStatement(sql);
207
+ // The child opens its own connection, so the parent checks the program first.
208
+ assertNoDeniedTables(sql);
206
209
  acquireBoundedQuerySlot();
207
210
 
208
211
  try {
@@ -56,6 +56,111 @@ export function assertSingleStatement(sql: string): void {
56
56
  }
57
57
  }
58
58
 
59
+ /**
60
+ * Tables whose rows are credentials: OAuth tokens and client secrets, PKCE
61
+ * verifiers, API and session token hashes, encrypted script-API bearers, and
62
+ * encrypted Codex device-login state.
63
+ * `db-query` is open to every authenticated agent, so it refuses any query
64
+ * that reads one of these, however the table is reached (alias, CTE, view,
65
+ * subquery, join or covering index). `swarm_config` stays readable: its
66
+ * secret values are ciphertext and agents query its non-secret keys.
67
+ */
68
+ export const DB_QUERY_DENIED_TABLES: ReadonlySet<string> = new Set([
69
+ "oauth_authorizations",
70
+ "oauth_apps",
71
+ "oauth_pending",
72
+ "user_tokens",
73
+ "session_tokens",
74
+ "script_apis",
75
+ "codex_oauth_device_flows",
76
+ ]);
77
+
78
+ /** Thrown when a query would read a table in {@link DB_QUERY_DENIED_TABLES}. */
79
+ export class DbQueryDeniedTableError extends Error {
80
+ constructor(readonly tables: string[]) {
81
+ super(
82
+ `db-query cannot read ${tables.join(", ")}: ${tables.length === 1 ? "this table holds" : "these tables hold"} credentials. ` +
83
+ "Use the dedicated OAuth, token or config tools instead.",
84
+ );
85
+ this.name = "DbQueryDeniedTableError";
86
+ }
87
+ }
88
+
89
+ /** Bytecode opcodes whose p2 is the root page of the b-tree they open. */
90
+ const CURSOR_OPEN_OPCODES = new Set(["OpenRead", "ReopenIdx", "OpenWrite"]);
91
+
92
+ /**
93
+ * Open a read-only connection to the swarm database. User SQL is never
94
+ * prepared on the writable application connection. In-memory databases have
95
+ * no file to reopen, so they get a read-only snapshot instead.
96
+ */
97
+ function openReadOnlyReader(): Database {
98
+ const database = getDb();
99
+ const reader =
100
+ !database.filename || database.filename === ":memory:"
101
+ ? Database.deserialize(database.serialize(), { readonly: true })
102
+ : new Database(database.filename, { readonly: true });
103
+ const vecExtensionPath = resolveSqliteVecExtensionPath();
104
+ if (vecExtensionPath) {
105
+ try {
106
+ reader.loadExtension(vecExtensionPath);
107
+ } catch {
108
+ // Match the bounded path: only vec queries require the extension.
109
+ }
110
+ }
111
+ return reader;
112
+ }
113
+
114
+ /**
115
+ * Reject `sql` if it reads a denied table. Works on the compiled program, not
116
+ * the SQL text: `EXPLAIN` lists every b-tree the statement opens by root page,
117
+ * and `sqlite_master` maps root pages (tables and their indexes) back to a
118
+ * table name. Views, CTEs and subqueries are already expanded in the program,
119
+ * so no SQL parsing is needed. A user `EXPLAIN ...` returns bytecode, never
120
+ * row data, so it is allowed as is.
121
+ */
122
+ export function assertNoDeniedTables(sql: string, reader?: Database): void {
123
+ const statement = stripTrailingSemicolon(sql);
124
+ if (/^explain\b/i.test(stripLeadingComments(statement))) return;
125
+
126
+ const db = reader ?? openReadOnlyReader();
127
+ try {
128
+ const rootPages = new Map<number, string>();
129
+ for (const row of db
130
+ .query("SELECT rootpage, tbl_name FROM main.sqlite_master WHERE rootpage > 0")
131
+ .all() as Array<{ rootpage: number; tbl_name: string }>) {
132
+ rootPages.set(row.rootpage, row.tbl_name.toLowerCase());
133
+ }
134
+
135
+ using program = db.prepare(`EXPLAIN ${statement}`);
136
+ const denied = new Set<string>();
137
+ for (const op of program.all() as Array<{ opcode: string; p2: number; p3: number }>) {
138
+ // p3 is the schema index: 0 = main. Credential tables only live there.
139
+ if (!CURSOR_OPEN_OPCODES.has(op.opcode) || op.p3 !== 0) continue;
140
+ const table = rootPages.get(op.p2);
141
+ if (table && DB_QUERY_DENIED_TABLES.has(table)) denied.add(table);
142
+ }
143
+ if (denied.size > 0) throw new DbQueryDeniedTableError([...denied].sort());
144
+ } finally {
145
+ if (!reader) db.close();
146
+ }
147
+ }
148
+
149
+ function stripLeadingComments(sql: string): string {
150
+ let rest = sql.trimStart();
151
+ for (;;) {
152
+ if (rest.startsWith("--")) {
153
+ const end = rest.indexOf("\n");
154
+ rest = end === -1 ? "" : rest.slice(end + 1).trimStart();
155
+ } else if (rest.startsWith("/*")) {
156
+ const end = rest.indexOf("*/");
157
+ rest = end === -1 ? "" : rest.slice(end + 2).trimStart();
158
+ } else {
159
+ return rest;
160
+ }
161
+ }
162
+ }
163
+
59
164
  /**
60
165
  * Execute a read-only SQL query against the swarm database, synchronously,
61
166
  * on the caller's own thread.
@@ -72,22 +177,9 @@ export function executeReadOnlyQuery(
72
177
  maxRows?: number,
73
178
  ): DbQueryResult {
74
179
  assertSingleStatement(sql);
75
- const database = getDb();
76
- // Never prepare user SQL on the writable application connection. In-memory
77
- // databases have no file to reopen, so query a read-only snapshot instead.
78
- const reader =
79
- !database.filename || database.filename === ":memory:"
80
- ? Database.deserialize(database.serialize(), { readonly: true })
81
- : new Database(database.filename, { readonly: true });
180
+ const reader = openReadOnlyReader();
82
181
  try {
83
- const vecExtensionPath = resolveSqliteVecExtensionPath();
84
- if (vecExtensionPath) {
85
- try {
86
- reader.loadExtension(vecExtensionPath);
87
- } catch {
88
- // Match the bounded path: only vec queries require the extension.
89
- }
90
- }
182
+ assertNoDeniedTables(sql, reader);
91
183
  using stmt = reader.prepare(sql);
92
184
 
93
185
  // Require a result set; SQLite readonly mode is the write barrier.
@@ -1,5 +1,6 @@
1
1
  import type { IncomingMessage, ServerResponse } from "node:http";
2
2
  import { z } from "zod";
3
+ import { scrubObject } from "../utils/secret-scrubber";
3
4
  import { executeReadOnlyQueryBounded } from "./db-query-bounded";
4
5
  import {
5
6
  assertSingleStatement,
@@ -18,7 +19,10 @@ import { json, jsonError } from "./utils";
18
19
 
19
20
  export type { DbQueryResult } from "./db-query-shared";
20
21
  export {
22
+ assertNoDeniedTables,
21
23
  assertSingleStatement,
24
+ DB_QUERY_DENIED_TABLES,
25
+ DbQueryDeniedTableError,
22
26
  executeReadOnlyQuery,
23
27
  getDbQueryHttpBudgetMs,
24
28
  getDbQueryHttpMaxRows,
@@ -104,7 +108,9 @@ const dbQueryRoute = route({
104
108
  rowLimit: z.number().nullable(),
105
109
  }),
106
110
  },
107
- 400: { description: "Invalid or disallowed SQL" },
111
+ 400: {
112
+ description: "Invalid or disallowed SQL, including queries that read credential tables",
113
+ },
108
114
  408: { description: "Query exceeded its wall-clock budget and was terminated" },
109
115
  429: { description: "Too many concurrent bounded db-query executions; retry shortly" },
110
116
  },
@@ -131,7 +137,9 @@ export async function handleDbQuery(
131
137
  getDbQueryHttpBudgetMs(),
132
138
  getDbQueryHttpMaxRows(),
133
139
  );
134
- dbQueryRoute.respond(res, 200, result);
140
+ // Rows can hold secrets pasted into free text (task output, logs, events).
141
+ // The MCP tool gets this from the registrar; the HTTP route does it here.
142
+ dbQueryRoute.respond(res, 200, scrubObject(result));
135
143
  } catch (err: unknown) {
136
144
  if (err instanceof DbQueryConcurrencyCapError) {
137
145
  // Machine-readable code + Retry-After so a caller can back off instead
package/src/http/index.ts CHANGED
@@ -26,6 +26,7 @@ import {
26
26
  stopAuditWriter,
27
27
  } from "../be/rbac-audit";
28
28
  import { startScratchScriptGc, stopScratchScriptGc } from "../be/scripts/retention";
29
+ import { loadSecretRegistry } from "../be/secret-registry";
29
30
  import { seedLegacyCapabilitiesConfig } from "../be/seed-capabilities";
30
31
  import {
31
32
  loadEnabledExtensions,
@@ -589,6 +590,22 @@ try {
589
590
  }
590
591
  warnIfCorsAllowsAnyOrigin();
591
592
 
593
+ // Register every stored secret (config secrets of all scopes, OAuth and MCP
594
+ // OAuth credentials, script API tokens) with the scrubber before listen, so
595
+ // egress and the boot retro-sweep below can redact values not touched since
596
+ // the last restart. Non-fatal: a decrypt failure only loses redaction coverage.
597
+ try {
598
+ const loaded = await loadSecretRegistry();
599
+ console.log(
600
+ `[secret-registry] registered config=${loaded.config} oauth=${loaded.oauth} scriptApi=${loaded.scriptApi} failed=${loaded.failed}`,
601
+ );
602
+ } catch (err) {
603
+ console.error(
604
+ "[secret-registry] load failed (non-fatal):",
605
+ scrubSecrets(err instanceof Error ? err.message : String(err)),
606
+ );
607
+ }
608
+
592
609
  // Upgrade seed: explicit CAPABILITIES env values that predate capability
593
610
  // gating get the previously always-registered groups backfilled into a
594
611
  // global swarm_config row (operator-editable; skipped when a row exists).
@@ -809,13 +826,13 @@ httpServer
809
826
  console.error("[boot-reembed-scripts] startup backfill failed (non-fatal):", err);
810
827
  });
811
828
 
812
- // One-time scrub: retroactively redact any session_logs rows containing
813
- // sensitive patterns that pre-date the defense-in-depth scrub layer.
814
- // Idempotent, tracked via seed_state.
815
- import("../be/boot-scrub-logs")
816
- .then(({ runBootScrubLogs }) => runBootScrubLogs())
829
+ // Versioned retro-sweep: once per SCRUBBER_RULES_VERSION, redact stored
830
+ // rows (logs, tasks, memory, events, workflow steps) that pre-date the
831
+ // current scrubber rules. Idempotent and resumable, tracked via seed_state.
832
+ import("../be/boot-scrub-sweep")
833
+ .then(({ runBootScrubSweep }) => runBootScrubSweep())
817
834
  .catch((err) => {
818
- console.error("[boot-scrub-logs] startup scrub failed (non-fatal):", err);
835
+ console.error("[boot-scrub-sweep] startup scrub failed (non-fatal):", err);
819
836
  });
820
837
  })
821
838
  .on("error", (err) => {
package/src/http/mcp.ts CHANGED
@@ -244,6 +244,11 @@ export async function handleMcp(
244
244
  }
245
245
  const server = await createServer({
246
246
  preloadedTools,
247
+ // Agent row, then global row, then deployment env. A blank agent row
248
+ // resolves to "unset" for that agent.
249
+ enabledTools:
250
+ configs.find((config) => config.key === "SWARM_ENABLED_TOOLS")?.value ??
251
+ process.env.SWARM_ENABLED_TOOLS,
247
252
  scriptsOnly: resolveScriptsOnlyMode({
248
253
  env: process.env.SCRIPTS_ONLY_MCP,
249
254
  configValue,
@@ -21,6 +21,7 @@ import {
21
21
  } from "../be/onboarding";
22
22
  import { assertUrlSafe, publicEndpointSsrfOptions } from "../oauth/mcp-wrapper";
23
23
  import { ProviderNameSchema } from "../types";
24
+ import { getOpenRouterAttributionHeaders } from "../utils/openrouter-base-url";
24
25
  import { ensureConfigAdmin } from "./config";
25
26
  import { scheduleIntegrationsReload } from "./core";
26
27
  import { route } from "./route-def";
@@ -255,6 +256,7 @@ async function handleMemoryProbe(body: OnboardingMemoryRequest): Promise<Onboard
255
256
  const client = new OpenAI({
256
257
  baseURL: endpoint.toString(),
257
258
  apiKey,
259
+ defaultHeaders: getOpenRouterAttributionHeaders(endpoint.toString()),
258
260
  timeout: 15_000,
259
261
  maxRetries: 0,
260
262
  fetchOptions: { redirect: "manual" },
@@ -23,6 +23,7 @@ import {
23
23
  upsertScriptRunJournalStep,
24
24
  } from "../be/db";
25
25
  import { explicitModelErrorForAgent } from "../be/model-validation";
26
+ import { refuseSourceWithSecrets } from "../be/scripts/source-secrets";
26
27
  import { lintWorkflowLabels } from "../script-workflows/label-lint";
27
28
  import { scriptRunMaxAgentTasks, scriptRunMaxSteps } from "../script-workflows/limits";
28
29
  import {
@@ -40,6 +41,7 @@ import {
40
41
  TERMINAL_SCRIPT_RUN_STATUSES,
41
42
  } from "../types";
42
43
  import { getAppUrl, getMcpBaseUrl } from "../utils/constants";
44
+ import { scrubSecrets } from "../utils/secret-scrubber";
43
45
  import {
44
46
  executeRawLlm,
45
47
  RawLlmConfigSchema,
@@ -369,6 +371,12 @@ export async function handleScriptRuns(
369
371
  const agent = await requireAgent(res, agentId);
370
372
  if (!agent) return true;
371
373
 
374
+ const secretRefusal = refuseSourceWithSecrets(parsed.body.source);
375
+ if (secretRefusal) {
376
+ json(res, secretRefusal, 400);
377
+ return true;
378
+ }
379
+
372
380
  const lint = lintWorkflowLabels(parsed.body.source);
373
381
  if (!lint.ok) {
374
382
  json(
@@ -545,7 +553,7 @@ export async function handleScriptRuns(
545
553
  config: parsed.body.config ?? {},
546
554
  status: parsed.body.status,
547
555
  result: parsed.body.result,
548
- error: parsed.body.error,
556
+ error: parsed.body.error === undefined ? undefined : scrubSecrets(parsed.body.error),
549
557
  durationMs: parsed.body.durationMs,
550
558
  });
551
559
  return assertRunWithinLimits(run.id);
@@ -32,6 +32,7 @@ import {
32
32
  } from "../be/scripts/db";
33
33
  import { searchScripts } from "../be/scripts/embeddings";
34
34
  import { extractArgsJsonSchema } from "../be/scripts/extract-schema";
35
+ import { refuseSourceWithSecrets } from "../be/scripts/source-secrets";
35
36
  import {
36
37
  scriptSdkTypesWithGeneratedApis,
37
38
  scriptStdlibTypesWithGeneratedApis,
@@ -649,6 +650,12 @@ export async function handleScripts(
649
650
  }
650
651
  }
651
652
 
653
+ const secretRefusal = refuseSourceWithSecrets(parsed.body.source);
654
+ if (secretRefusal) {
655
+ json(res, secretRefusal, 400);
656
+ return true;
657
+ }
658
+
652
659
  const typecheck = await typecheckScript(parsed.body.source, { agentId: agent.id });
653
660
  if (!typecheck.ok) {
654
661
  json(
@@ -716,6 +723,14 @@ export async function handleScripts(
716
723
  req.headers["x-runtime-instance-id"],
717
724
  );
718
725
 
726
+ // Inline source is saved as a scratch script and recorded on the run row:
727
+ // refuse an embedded secret before it executes or persists.
728
+ const secretRefusal = parsed.body.source ? refuseSourceWithSecrets(parsed.body.source) : null;
729
+ if (secretRefusal) {
730
+ json(res, secretRefusal, 400);
731
+ return true;
732
+ }
733
+
719
734
  let source = parsed.body.source;
720
735
  let fsMode = parsed.body.fsMode;
721
736
  let namedScript: ScriptRecord | null = null;
@@ -901,13 +901,14 @@ export async function handleWorkflows(
901
901
  if (getWorkflowRunRoute.match(req.method, pathSegments)) {
902
902
  const parsed = await getWorkflowRunRoute.parse(req, res, pathSegments, queryParams);
903
903
  if (!parsed) return true;
904
- const run = await getWorkflowRun(parsed.params.id);
904
+ // Display view: the scrubbed context and step outputs, never replay state.
905
+ const run = await getWorkflowRun(parsed.params.id, "display");
905
906
  if (!run) {
906
907
  res.writeHead(404);
907
908
  res.end();
908
909
  return true;
909
910
  }
910
- const steps = await getWorkflowRunStepsByRunId(parsed.params.id);
911
+ const steps = await getWorkflowRunStepsByRunId(parsed.params.id, "display");
911
912
  getWorkflowRunRoute.respond(res, 200, { run, steps });
912
913
  return true;
913
914
  }
@@ -372,7 +372,7 @@ function formUrlEncodeComponent(value: string): string {
372
372
  * at each call site means a new sensitive field cannot forget one shape.
373
373
  */
374
374
  function redactSentCredentials(text: string, sent: Array<string | null | undefined>): string {
375
- let out = scrubSecrets(text);
375
+ let out: string = scrubSecrets(text);
376
376
  for (const value of sent) {
377
377
  if (!value) continue;
378
378
  for (const shape of new Set([value, formUrlEncodeComponent(value)])) {
@@ -410,17 +410,29 @@ export class ACPAdapter implements ProviderAdapter {
410
410
  async createSession(config: ProviderSessionConfig): Promise<ProviderSession> {
411
411
  const target = this.options.target ?? resolveAcpTarget(config);
412
412
  await target.writeSystemPromptArtifact(config);
413
- const command = target.command(config);
414
- const proc = registerProcessGroup(
415
- Bun.spawn(command, {
416
- cwd: config.cwd,
417
- detached: detachedProcessGroup,
418
- env: target.env(config),
419
- stdin: "pipe",
420
- stdout: "pipe",
421
- stderr: "pipe",
422
- }),
423
- );
413
+ const cleanupArtifact = async () => {
414
+ try {
415
+ await target.cleanupSystemPromptArtifact?.(config);
416
+ } catch (err) {
417
+ console.warn(`\x1b[33m[acp]\x1b[0m System prompt cleanup failed: ${formatError(err)}`);
418
+ }
419
+ };
420
+ let proc: Bun.Subprocess<"pipe", "pipe", "pipe">;
421
+ try {
422
+ proc = registerProcessGroup(
423
+ Bun.spawn(target.command(config), {
424
+ cwd: config.cwd,
425
+ detached: detachedProcessGroup,
426
+ env: target.env(config),
427
+ stdin: "pipe",
428
+ stdout: "pipe",
429
+ stderr: "pipe",
430
+ }),
431
+ );
432
+ } catch (err) {
433
+ await cleanupArtifact();
434
+ throw err;
435
+ }
424
436
 
425
437
  let session: ACPSession | null = null;
426
438
  const preSessionEvents: ProviderEvent[] = [];
@@ -499,6 +511,7 @@ export class ACPAdapter implements ProviderAdapter {
499
511
  ephemeralToken.tokenId,
500
512
  );
501
513
  for (const event of preSessionEvents) session.emitFromAcp(event);
514
+ void session.waitForCompletion().finally(cleanupArtifact);
502
515
  return session;
503
516
  } catch (err) {
504
517
  // Revoke the ephemeral token before re-throwing if ACPSession has not yet
@@ -508,6 +521,7 @@ export class ACPAdapter implements ProviderAdapter {
508
521
  void revokeAcpSessionToken(config.apiUrl, config.apiKey, ephemeralToken.tokenId);
509
522
  }
510
523
  await terminateProcessGroup(proc.pid);
524
+ await cleanupArtifact();
511
525
  throw new Error(
512
526
  `ACP target failed during startup: ${describeTargetError(target, scrubSecrets(formatError(err)))}`,
513
527
  );