@desplega.ai/agent-swarm 1.164.1 → 1.166.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 (328) hide show
  1. package/README.md +1 -1
  2. package/dist/acp-adapter-yzfxn8vj.js +23 -0
  3. package/dist/{actions-hp2h32dx.js → actions-qz3m692w.js} +24 -28
  4. package/dist/{amp-adapter-zfd7djyx.js → amp-adapter-j1xbmsqz.js} +7 -7
  5. package/dist/{app-w2d26h4f.js → app-43vrdmv8.js} +9 -10
  6. package/dist/{assistant-45pc4en7.js → assistant-ertffmka.js} +25 -29
  7. package/dist/{boot-reembed-k1q2s4fc.js → boot-reembed-998jtpdg.js} +12 -15
  8. package/dist/{boot-reembed-6rhcpsjf.js → boot-reembed-s7k7c6v2.js} +13 -15
  9. package/dist/boot-scrub-sweep-nepsk4t1.js +211 -0
  10. package/dist/{boot-seeds-ex93edx7.js → boot-seeds-vvzs1zhk.js} +12 -12
  11. package/dist/{claude-adapter-hcxd0szm.js → claude-adapter-2zvmekrp.js} +15 -17
  12. package/dist/{claude-managed-adapter-fz7xps2r.js → claude-managed-adapter-s49wbk8t.js} +5 -5
  13. package/dist/{claude-managed-setup-xx6f15h9.js → claude-managed-setup-cvhqq8qn.js} +4 -4
  14. package/dist/{claude-sdk-session-6nb9raem.js → claude-sdk-session-kkbxtw5q.js} +2624 -2606
  15. package/dist/{cli-etbrt8x1.js → cli-0nhdwxd5.js} +4 -4
  16. package/dist/{cli-jeefgppq.js → cli-0qjcsxgz.js} +7 -7
  17. package/dist/{cli-6behs2bt.js → cli-32bca7nr.js} +1 -1
  18. package/dist/{cli-gt8v96qx.js → cli-3ddybbbx.js} +4 -4
  19. package/dist/{acp-adapter-rxvek4ym.js → cli-3qn4r334.js} +317 -35
  20. package/dist/{cli-1d3w4xm3.js → cli-4r1sjfc4.js} +14 -6
  21. package/dist/{cli-691f2rxm.js → cli-66zh26sp.js} +68 -16
  22. package/dist/{cli-et8wdyqh.js → cli-68r1j4sg.js} +9 -9
  23. package/dist/{cli-xb7kyanp.js → cli-8kgvmkak.js} +3 -3
  24. package/dist/{cli-fdpkw8vk.js → cli-8t229cmr.js} +8 -8
  25. package/dist/{cli-gc54qc0p.js → cli-aqcey741.js} +1 -1
  26. package/dist/{cli-4q4cmr4t.js → cli-cpfmqz3n.js} +8 -8
  27. package/dist/{cli-a9bw0xxt.js → cli-df56tqdw.js} +997 -408
  28. package/dist/{cli-n7h1kqpf.js → cli-dmtrxqhg.js} +6 -1
  29. package/dist/{cli-xfb7333f.js → cli-dw2q9f7g.js} +5 -5
  30. package/dist/{cli-4drstbb9.js → cli-ecknp7p1.js} +81 -23
  31. package/dist/{cli-v0pvvhfq.js → cli-efnd2kce.js} +36 -1
  32. package/dist/{cli-1wetf34q.js → cli-f2n17zhp.js} +1 -1
  33. package/dist/{cli-2c8eky5y.js → cli-feh3dkah.js} +1 -1
  34. package/dist/cli-fn697bnt.js +126 -0
  35. package/dist/{cli-v5pr2fgt.js → cli-gt8azyet.js} +3 -3
  36. package/dist/{cli-dbszf3rw.js → cli-h3x613ze.js} +4 -4
  37. package/dist/{cli-nwhb8y2e.js → cli-hf5s9pgf.js} +1 -1
  38. package/dist/cli-j2jbr8af.js +516 -0
  39. package/dist/{cli-agjzafns.js → cli-jhm6hzjn.js} +593 -10
  40. package/dist/{cli-j0tmc908.js → cli-jvj6a0cv.js} +484 -477
  41. package/dist/{cli-xxz89c2n.js → cli-mb75wpxf.js} +1 -1
  42. package/dist/{cli-07s39g1n.js → cli-mv2520nm.js} +1 -1
  43. package/dist/{cli-v90vfwyb.js → cli-mwegs4af.js} +5 -5
  44. package/dist/{cli-bz1gbac9.js → cli-p4ztb80h.js} +2 -2
  45. package/dist/{cli-1wd1n2ya.js → cli-rfphkzh5.js} +1 -1
  46. package/dist/{cli-976r1zqx.js → cli-rggdq8wq.js} +5 -5
  47. package/dist/{cli-3ajw5v5v.js → cli-rsy933yz.js} +6 -6
  48. package/dist/{cli-yj790e19.js → cli-tadfgd0t.js} +8 -1
  49. package/dist/{cli-cn53e7vc.js → cli-ttbsse3f.js} +6 -1
  50. package/dist/{cli-dyqsrqcf.js → cli-v2a8x45d.js} +4 -4
  51. package/dist/{cli-wbvnn6tk.js → cli-v6vv7dy8.js} +91 -9
  52. package/dist/{cli-z2ap2c0c.js → cli-vzc73wet.js} +90 -55
  53. package/dist/cli-wt7ccy4r.js +2654 -0
  54. package/dist/{cli-4cn19jvn.js → cli-x7kpr72a.js} +3 -3
  55. package/dist/{cli-003785v9.js → cli-xvc3q9br.js} +1 -1
  56. package/dist/{cli-v0e7jpr5.js → cli-xvdgasgy.js} +13 -4
  57. package/dist/{cli-asqeg4ve.js → cli-y59h2pk9.js} +2 -2
  58. package/dist/{cli-0wn3ypqv.js → cli-yshaazs1.js} +14121 -3478
  59. package/dist/{cli-wg8yjqtw.js → cli-ysnyhws9.js} +16 -3
  60. package/dist/cli-zp67qg5x.js +92 -0
  61. package/dist/{cli-g3qgrzck.js → cli-zsh093hk.js} +71 -3
  62. package/dist/{cli-ma3zvh39.js → cli-zy7vwsne.js} +6066 -5829
  63. package/dist/cli.js +79 -26
  64. package/dist/{codex-adapter-pcxdn10v.js → codex-adapter-dw1wxqt8.js} +10 -10
  65. package/dist/{codex-hook-b1fb7c2k.js → codex-hook-d2rkvf39.js} +1 -1
  66. package/dist/{codex-login-059nm145.js → codex-login-terbdsqq.js} +2 -2
  67. package/dist/{codex-session-runner-xzwwddp8.js → codex-session-runner-3cgb8y1g.js} +10 -10
  68. package/dist/{commands-98at8mkn.js → commands-mhxam6z2.js} +9 -9
  69. package/dist/{cursor-adapter-dkg6qnkp.js → cursor-adapter-2qda3st7.js} +6 -6
  70. package/dist/{db-01g8wtvw.js → db-sjks418k.js} +11 -9
  71. package/dist/{devin-adapter-qmttt2fz.js → devin-adapter-bffjj5n4.js} +2 -2
  72. package/dist/{dispatcher-nn2k1wxd.js → dispatcher-1es759rn.js} +22 -26
  73. package/dist/{dsh-adapter-w4d4vwdq.js → dsh-adapter-bek4cyw9.js} +6 -6
  74. package/dist/{e2b-9e4g2hvt.js → e2b-nd50eccg.js} +1 -1
  75. package/dist/{event-bus-z7v2t26e.js → event-bus-vnfthafg.js} +2 -2
  76. package/dist/grok-adapter-ar9cw9jg.js +29 -0
  77. package/dist/{handlers-b0kqsbg8.js → handlers-1gkbhktr.js} +25 -29
  78. package/dist/{hook-sap7v1aj.js → hook-ds2zxq9v.js} +16 -18
  79. package/dist/{hook-pz50gqep.js → hook-vxa3tcsy.js} +10 -12
  80. package/dist/{http-ncr8rmd0.js → http-3h21z9md.js} +679 -227
  81. package/dist/{index-c4aqcyz1.js → index-9b65j1m5.js} +11 -11
  82. package/dist/{index-r46s01nq.js → index-bn6cy8ps.js} +22 -26
  83. package/dist/{index-38kqdt3e.js → index-dkfxpkmm.js} +23 -27
  84. package/dist/{index-630kj5vd.js → index-f8xnqrye.js} +55 -43
  85. package/dist/{index-fdk1t4d6.js → index-n0rxa4ca.js} +11962 -9900
  86. package/dist/{index-9nrj7797.js → index-n99fycq7.js} +22 -26
  87. package/dist/{keepalive-31613r2h.js → keepalive-3xspa9jw.js} +14 -11
  88. package/dist/{lead-mx59tr5s.js → lead-6qphc124.js} +43 -43
  89. package/dist/{maintenance-55ff39ca.js → maintenance-0teem1bb.js} +10 -12
  90. package/dist/{node-f0ykebbv.js → node-9sg7ajby.js} +25 -7
  91. package/dist/{oauth-refresh-sweep-anma954w.js → oauth-refresh-sweep-sh9q25xp.js} +13 -10
  92. package/dist/{onboard-b2fy8ftb.js → onboard-54qmnqxe.js} +4 -3
  93. package/dist/{opencode-adapter-p1s92tmy.js → opencode-adapter-wjfa2wvd.js} +6 -6
  94. package/dist/{otel-impl-9brvt7xv.js → otel-impl-ycqzp2a1.js} +2 -2
  95. package/dist/{pi-mono-adapter-kz4rrwvd.js → pi-mono-adapter-e77e9zst.js} +6 -6
  96. package/dist/{pricing-refresh-7w8mbmva.js → pricing-refresh-atnkngqs.js} +10 -10
  97. package/dist/{rbac-roles-ejp7qwvb.js → rbac-roles-9pjphh9q.js} +10 -10
  98. package/dist/{rbac-roles-nhk4r7bc.js → rbac-roles-j78wdksm.js} +10 -10
  99. package/dist/{render-v2-2gzt8skq.js → render-v2-pxjs49fx.js} +9 -10
  100. package/dist/{setup-5et8t412.js → setup-ymej0xtx.js} +2 -2
  101. package/dist/{work-objects-tb0er7qa.js → work-objects-3nh4ymzb.js} +26 -30
  102. package/dist/{worker-62cv075d.js → worker-qa9fkz5s.js} +43 -43
  103. package/dist/{x-vbxb31jr.js → x-ma4gnfgy.js} +2 -2
  104. package/openapi.json +457 -31
  105. package/package.json +14 -6
  106. package/plugin/commands/create-pr.md +7 -1
  107. package/plugin/pi-skills/create-pr/SKILL.md +7 -1
  108. package/src/agentmail/app.ts +5 -4
  109. package/src/be/automation-preflight.ts +11 -9
  110. package/src/be/boot-scrub-sweep.ts +302 -0
  111. package/src/be/connector-codes.ts +81 -0
  112. package/src/be/crypto/key-bootstrap.ts +1 -1
  113. package/src/be/db/approvals.ts +15 -1
  114. package/src/be/db/codex-oauth-device-flows.ts +40 -0
  115. package/src/be/db/config-secrets.ts +31 -0
  116. package/src/be/db/runtime.ts +4 -0
  117. package/src/be/db/script-runs.ts +15 -0
  118. package/src/be/db/tasks/write.ts +1 -1
  119. package/src/be/db-queries/mcp-oauth.ts +5 -0
  120. package/src/be/db-queries/oauth.ts +77 -0
  121. package/src/be/db.ts +162 -59
  122. package/src/be/events.ts +17 -1
  123. package/src/be/memory/index-content.ts +2 -2
  124. package/src/be/memory/providers/sqlite-store.ts +63 -11
  125. package/src/be/memory/types.ts +14 -0
  126. package/src/be/migrations/197_connector_codes.sql +23 -0
  127. package/src/be/migrations/198_permission_audit_guest.sql +33 -0
  128. package/src/be/migrations/200_backfill_auto_memory_expiry.sql +20 -0
  129. package/src/be/migrations/201_workflow_replay_columns.sql +10 -0
  130. package/src/be/migrations/202_codex_oauth_device_flows.sql +24 -0
  131. package/src/be/model-tier-resolution.ts +2 -1
  132. package/src/be/model-validation.ts +1 -1
  133. package/src/be/pricing-normalize.ts +3 -0
  134. package/src/be/rbac-audit.ts +2 -1
  135. package/src/be/scripts/db.ts +28 -4
  136. package/src/be/scripts/source-secrets.ts +37 -0
  137. package/src/be/scripts/typecheck.ts +23 -2
  138. package/src/be/scrub-json.ts +32 -0
  139. package/src/be/sealed-json.ts +59 -0
  140. package/src/be/secret-registry.ts +131 -0
  141. package/src/be/seed-pricing.ts +14 -0
  142. package/src/be/users.ts +7 -1
  143. package/src/be/workflow-replay.ts +77 -0
  144. package/src/cli.tsx +7 -0
  145. package/src/commands/provider-credentials.ts +46 -8
  146. package/src/commands/runner.ts +2 -10
  147. package/src/extensions/contract-types.generated.ts +1 -1
  148. package/src/github/app.ts +4 -4
  149. package/src/heartbeat/heartbeat.ts +20 -8
  150. package/src/hooks/pr-body-guard.ts +1 -1
  151. package/src/http/agents.ts +1 -0
  152. package/src/http/approval-requests.ts +1 -0
  153. package/src/http/apps.ts +3 -0
  154. package/src/http/auth.ts +4 -1
  155. package/src/http/codex-oauth-device.ts +8 -10
  156. package/src/http/codex-oauth-keep-warm.ts +2 -2
  157. package/src/http/config-values.ts +59 -0
  158. package/src/http/config.ts +5 -0
  159. package/src/http/db-query-bounded.ts +3 -0
  160. package/src/http/db-query-shared.ts +107 -15
  161. package/src/http/db-query.ts +10 -2
  162. package/src/http/extensions.ts +1 -0
  163. package/src/http/index.ts +23 -6
  164. package/src/http/integrations.ts +1 -38
  165. package/src/http/ip-rate-limit.ts +82 -0
  166. package/src/http/kv.ts +38 -4
  167. package/src/http/mcp-servers.ts +1 -0
  168. package/src/http/memory.ts +9 -0
  169. package/src/http/models-catalog.ts +2 -0
  170. package/src/http/operator-actor.ts +4 -0
  171. package/src/http/page-proxy.ts +22 -0
  172. package/src/http/pages.ts +4 -2
  173. package/src/http/realtime.ts +1 -1
  174. package/src/http/request-principal.ts +14 -1
  175. package/src/http/script-connections.ts +8 -0
  176. package/src/http/script-runs.ts +12 -4
  177. package/src/http/scripts.ts +15 -0
  178. package/src/http/session-cost-recompute.ts +3 -2
  179. package/src/http/tasks.ts +9 -0
  180. package/src/http/users.ts +226 -0
  181. package/src/http/workflows.ts +3 -2
  182. package/src/http.ts +1 -0
  183. package/src/integrations/kapso/client.ts +3 -1
  184. package/src/jira/app.ts +2 -2
  185. package/src/kv-view.ts +324 -0
  186. package/src/linear/app.ts +2 -2
  187. package/src/oauth/mcp-wrapper.ts +1 -1
  188. package/src/prompts/memories.ts +42 -1
  189. package/src/providers/acp-adapter.ts +85 -24
  190. package/src/providers/acp-target-catalog.ts +54 -1
  191. package/src/providers/acp-targets.ts +358 -4
  192. package/src/providers/amp-adapter.ts +1 -1
  193. package/src/providers/cursor-adapter.ts +1 -1
  194. package/src/providers/grok-adapter.ts +186 -0
  195. package/src/providers/index.ts +5 -1
  196. package/src/providers/pricing-sources.md +6 -0
  197. package/src/providers/reasoning-effort.ts +17 -1
  198. package/src/providers/types.ts +2 -1
  199. package/src/rbac/can.ts +4 -3
  200. package/src/rbac/legacy-policy.ts +3 -3
  201. package/src/rbac/types.ts +6 -0
  202. package/src/script-workflows/supervisor.ts +11 -2
  203. package/src/scripts-runtime/eval-harness.ts +2 -12
  204. package/src/scripts-runtime/executors/quickjs-bundle.ts +152 -0
  205. package/src/scripts-runtime/executors/quickjs-prelude.ts +285 -0
  206. package/src/scripts-runtime/executors/quickjs-runner.ts +580 -0
  207. package/src/scripts-runtime/executors/quickjs-worker.ts +81 -0
  208. package/src/scripts-runtime/executors/quickjs.ts +196 -0
  209. package/src/scripts-runtime/executors/registry.ts +2 -0
  210. package/src/scripts-runtime/executors/sourcemap.ts +79 -0
  211. package/src/scripts-runtime/script-error-hints.ts +14 -0
  212. package/src/scripts-runtime/swarm-sdk.ts +9 -2
  213. package/src/scripts-runtime/types/stdlib.d.ts +27 -8
  214. package/src/scripts-runtime/types/swarm-extension.d.ts +3 -0
  215. package/src/scripts-runtime/types/swarm-sdk.d.ts +27 -8
  216. package/src/server-user.ts +2 -20
  217. package/src/server.ts +3 -1
  218. package/src/slack/app.ts +4 -0
  219. package/src/slack/config.ts +3 -1
  220. package/src/slack/egress-scrub.ts +87 -0
  221. package/src/tests/acp-adapter.test.ts +191 -2
  222. package/src/tests/boot-scrub-sweep.test.ts +390 -0
  223. package/src/tests/budget-user-scope.test.ts +7 -21
  224. package/src/tests/check-pr-body.test.ts +149 -6
  225. package/src/tests/check-text-columns.test.ts +145 -0
  226. package/src/tests/claude-adapter-binary.test.ts +1 -1
  227. package/src/tests/claude-sdk-process.fixture.ts +13 -2
  228. package/src/tests/codex-oauth-device.test.ts +23 -37
  229. package/src/tests/codex-oauth-keep-warm.test.ts +29 -0
  230. package/src/tests/console-scrub.test.ts +141 -0
  231. package/src/tests/credential-check.test.ts +90 -0
  232. package/src/tests/db-query-denylist.test.ts +202 -0
  233. package/src/tests/db-task-read-contract.test.ts +1 -0
  234. package/src/tests/db-task-write-contract.test.ts +1 -0
  235. package/src/tests/docs-index-footprint.test.js +3 -3
  236. package/src/tests/entrypoint-acp-target-binary.test.ts +44 -1
  237. package/src/tests/events-scrub.test.ts +88 -0
  238. package/src/tests/explicit-model-validation.test.ts +81 -0
  239. package/src/tests/extensions-pre-task.test.ts +33 -1
  240. package/src/tests/fixtures/grok/acp-updates-recorded.jsonl +53 -0
  241. package/src/tests/fixtures/grok/prompt-response-meta.json +36 -0
  242. package/src/tests/fixtures/secret-corpus.ts +320 -0
  243. package/src/tests/grok-adapter.test.ts +647 -0
  244. package/src/tests/heartbeat-checklist.test.ts +44 -0
  245. package/src/tests/integration-disable-flags.test.ts +156 -0
  246. package/src/tests/kv-http.test.ts +34 -0
  247. package/src/tests/kv-tool.test.ts +274 -1
  248. package/src/tests/kv-view.test.ts +147 -0
  249. package/src/tests/mcp-user-route.test.ts +257 -15
  250. package/src/tests/memory-injection-outcome.test.ts +143 -0
  251. package/src/tests/model-tier-resolution.test.ts +76 -3
  252. package/src/tests/page-guest-session.test.ts +424 -0
  253. package/src/tests/page-proxy.test.ts +1 -1
  254. package/src/tests/page-viewer-auth.test.ts +22 -1
  255. package/src/tests/pr-body-leaks.test.ts +157 -2
  256. package/src/tests/preload.ts +10 -0
  257. package/src/tests/provider-registration.test.ts +3 -1
  258. package/src/tests/rbac-audit.test.ts +32 -0
  259. package/src/tests/rbac-mcp-admission-e2e.test.ts +4 -62
  260. package/src/tests/script-connections.test.ts +9 -0
  261. package/src/tests/script-executor-conformance.test.ts +3 -0
  262. package/src/tests/script-executor-quickjs.test.ts +442 -0
  263. package/src/tests/script-executor-registry.test.ts +6 -1
  264. package/src/tests/script-runs-http.test.ts +126 -1
  265. package/src/tests/script-runs-scrub.test.ts +275 -0
  266. package/src/tests/scripts-http.test.ts +82 -1
  267. package/src/tests/scripts-mcp-e2e.test.ts +42 -6
  268. package/src/tests/scripts-runtime.test.ts +37 -0
  269. package/src/tests/scrubbed-text-12e.test.ts +289 -0
  270. package/src/tests/sealed-key-guard.test.ts +166 -0
  271. package/src/tests/secret-canary-sweep.test.ts +958 -0
  272. package/src/tests/secret-registry.test.ts +355 -0
  273. package/src/tests/secret-rules.test.ts +230 -0
  274. package/src/tests/secret-scrubber.test.ts +317 -1
  275. package/src/tests/session-costs-golden.test.ts +17 -0
  276. package/src/tests/slack-egress-scrub.test.ts +216 -0
  277. package/src/tests/swarm-tool-result-gate.test.ts +143 -30
  278. package/src/tests/task-tools-ctx.test.ts +3 -0
  279. package/src/tests/user-token-routes.test.ts +342 -3
  280. package/src/tests/workflow-replay.test.ts +518 -0
  281. package/src/tests/workflow-scrub.test.ts +242 -0
  282. package/src/tests/workflow-system-one-decision-providers.test.ts +737 -0
  283. package/src/tools/db-query.ts +1 -1
  284. package/src/tools/inject-learning.ts +1 -1
  285. package/src/tools/kv/kv-get.ts +136 -6
  286. package/src/tools/send-task.ts +61 -17
  287. package/src/tools/utils.ts +186 -43
  288. package/src/tools/workflows/get-workflow-run.ts +3 -2
  289. package/src/types.ts +16 -1
  290. package/src/utils/console-scrub-install.ts +5 -0
  291. package/src/utils/console-scrub.ts +57 -0
  292. package/src/utils/credentials.ts +8 -0
  293. package/src/utils/page-session.ts +6 -2
  294. package/src/utils/pr-body-leaks.ts +149 -10
  295. package/src/utils/request-auth-context.ts +2 -0
  296. package/src/utils/secret-rules.generated.ts +2055 -0
  297. package/src/utils/secret-scrubber.ts +662 -36
  298. package/src/workflows/engine.ts +39 -4
  299. package/src/workflows/executors/notify.ts +6 -1
  300. package/src/workflows/executors/system-one-decision.ts +31 -12
  301. package/src/workflows/executors/system-one-openai-wire.ts +168 -0
  302. package/src/workflows/executors/system-one-providers.ts +112 -11
  303. package/src/workflows/executors/system-one-wire.ts +79 -0
  304. package/src/workflows/recovery.ts +10 -1
  305. package/src/workflows/resume.ts +62 -3
  306. package/src/workflows/retry-poller.ts +3 -0
  307. package/templates/ai-toolbox.manifest.json +2 -2
  308. package/templates/skills/agent-browser/SKILL.md +5 -5
  309. package/templates/skills/agent-browser/config.json +1 -1
  310. package/templates/skills/agent-browser/content.md +4 -4
  311. package/templates/skills/artifacts/content.md +17 -7
  312. package/templates/skills/jira-interaction/config.json +1 -1
  313. package/templates/skills/jira-interaction/content.md +56 -205
  314. package/templates/skills/linear-interaction/config.json +1 -1
  315. package/templates/skills/linear-interaction/content.md +31 -50
  316. package/templates/skills/pages/content.md +8 -4
  317. package/templates/skills/qa/content.md +2 -2
  318. package/templates/skills/workflow-iterate/content.md +4 -4
  319. package/tsconfig.json +3 -3
  320. package/dist/boot-scrub-logs-t8xg7wd7.js +0 -99
  321. package/dist/cli-0a7r5331.js +0 -168
  322. package/dist/cli-93rxw9yd.js +0 -951
  323. package/dist/cli-cvk6yzw5.js +0 -40
  324. package/dist/cli-dgnj0zcc.js +0 -40
  325. package/dist/cli-qt5wzq6v.js +0 -256
  326. package/dist/cli-v7fqehqd.js +0 -9281
  327. package/dist/cli-vbr859rz.js +0 -572
  328. package/src/be/boot-scrub-logs.ts +0 -132
@@ -20,6 +20,7 @@ import type {
20
20
  import * as z from "zod";
21
21
  import { sweepExpiredKvPrefix, upsertKv } from "../be/db";
22
22
  import { MCP_OVERFLOW_NAMESPACE, mcpOverflowNamespace } from "../kv-overflow";
23
+ import { joinKvPath, type KvShapeEntry, splitKvPath, summarizeKvShape } from "../kv-view";
23
24
  import { withSpan } from "../otel";
24
25
  import type { PermissionVerb } from "../rbac/permissions";
25
26
  import { SCRIPT_LONG_TIMEOUT_HINT_MS } from "../scripts-runtime/executors/types";
@@ -138,6 +139,8 @@ export type SwarmToolTruncation = {
138
139
  originalBytes: number;
139
140
  limitBytes: number;
140
141
  retrieval: string;
142
+ /** Biggest branches of the stored value; each `path` is a kv-get `path`. */
143
+ shape?: KvShapeEntry[];
141
144
  };
142
145
 
143
146
  export type SwarmToolResult<TData extends SwarmToolData = SwarmToolData> = {
@@ -153,6 +156,12 @@ export type SwarmToolResult<TData extends SwarmToolData = SwarmToolData> = {
153
156
  nudge?: string;
154
157
  /** Ctx-control metadata attached centrally when the full wire result is spilled. */
155
158
  truncation?: SwarmToolTruncation;
159
+ /**
160
+ * Set by ctx-control only. Keeps `details` out of structuredContent when
161
+ * that channel already carries `data` verbatim and the duplicate rendering
162
+ * is what pushes it over the size budget. The text channel keeps `details`.
163
+ */
164
+ omitStructuredDetails?: boolean;
156
165
  /**
157
166
  * Skip the finalize pipeline's secret scrubbing for this result. ONLY for
158
167
  * deliberate credential-reveal branches (oauth-access-token, script-apis
@@ -186,6 +195,16 @@ const swarmToolTruncationSchema = z.looseObject({
186
195
  originalBytes: z.number(),
187
196
  limitBytes: z.number(),
188
197
  retrieval: z.string(),
198
+ shape: z
199
+ .array(
200
+ z.looseObject({
201
+ path: z.string().optional(),
202
+ type: z.string().optional(),
203
+ bytes: z.number().optional(),
204
+ items: z.number().optional(),
205
+ }),
206
+ )
207
+ .optional(),
189
208
  });
190
209
 
191
210
  export const swarmToolEnvelopeShape = {
@@ -341,15 +360,21 @@ export const NUDGES: Record<string, (result: SwarmToolResult) => string | undefi
341
360
  const rendered =
342
361
  typeof entry.value === "string" ? entry.value : (JSON.stringify(entry.value) ?? "");
343
362
  return rendered.length > MCP_RESULT_WIRE_LIMIT_BYTES
344
- ? "Large value — your harness may truncate this result; to filter or aggregate it, process it in a script via ctx.swarm.kv_get instead."
363
+ ? "Large value — your harness may truncate this result; re-call kv-get with path/offset/limit for a bounded slice, or process it in a script via ctx.swarm.kv_get."
345
364
  : undefined;
346
365
  },
347
366
  };
348
367
 
368
+ /**
369
+ * Per-channel budget: every harness reads ONE channel (text or structured,
370
+ * runbook §3), so each channel must fit on its own. See `wireChannelBytes`.
371
+ */
349
372
  export const MCP_RESULT_WIRE_LIMIT_BYTES = 10_000;
350
373
  export { MCP_OVERFLOW_NAMESPACE, mcpOverflowNamespace };
351
374
  export const MCP_OVERFLOW_TTL_MS = 24 * 60 * 60 * 1_000;
352
- const MCP_PROSE_PREVIEW_CHARS = 1_200;
375
+ // Floor for the prose preview while the array path sizes its arrays; the
376
+ // preview then grows into whatever budget the arrays leave.
377
+ const MCP_PROSE_PREVIEW_FLOOR_CHARS = 1_200;
353
378
 
354
379
  type FinalizeContext = {
355
380
  toolName: string;
@@ -391,7 +416,14 @@ function composeWireResult(r: SwarmToolResult): CallToolResult {
391
416
  // Payload LAST: harnesses truncate oversized text from the tail (or keep
392
417
  // head+tail), so message and nudge lead and a cut lands inside the payload
393
418
  // rendering — a truncated JSON prefix still shows its first key values.
394
- const text = [r.message, r.nudge, normalizedDetails ?? dataFallback]
419
+ // A spill pointer follows the bounded preview; the text channel renders it
420
+ // once, the structured channel carries only the `truncation` object.
421
+ const text = [
422
+ r.message,
423
+ r.nudge,
424
+ normalizedDetails ?? dataFallback,
425
+ r.truncation ? renderTruncationPointer(r.truncation) : undefined,
426
+ ]
395
427
  .filter((part): part is string => Boolean(part?.trim()))
396
428
  .join("\n\n");
397
429
  const structuredContent: Record<string, unknown> = {
@@ -399,7 +431,7 @@ function composeWireResult(r: SwarmToolResult): CallToolResult {
399
431
  success: r.ok,
400
432
  message: r.message,
401
433
  };
402
- if (normalizedDetails) structuredContent.details = normalizedDetails;
434
+ if (normalizedDetails && !r.omitStructuredDetails) structuredContent.details = normalizedDetails;
403
435
  if (r.nudge) structuredContent.nudge = r.nudge;
404
436
  if (r.truncation) structuredContent.truncation = r.truncation;
405
437
 
@@ -410,20 +442,121 @@ function composeWireResult(r: SwarmToolResult): CallToolResult {
410
442
  };
411
443
  }
412
444
 
445
+ function renderTruncationPointer(t: SwarmToolTruncation): string {
446
+ const shape = t.shape?.length ? `\nShape: ${renderShape(t.shape)}` : "";
447
+ return `Full value: ${t.fullValueAt} (${t.originalBytes} bytes)${shape}\nRetrieval: ${t.retrieval}`;
448
+ }
449
+
450
+ /**
451
+ * Bytes the model sees from one wire result: the larger of the two channels.
452
+ * pi/opencode/claude-managed read only `content.text`; Codex reads only the
453
+ * JSON-encoded `structuredContent` (runbook §3). Summing both would spill a
454
+ * result at half the payload any single harness actually receives.
455
+ */
456
+ export function wireChannelBytes(wire: {
457
+ content?: ReadonlyArray<{ type: string; text?: string }>;
458
+ structuredContent?: unknown;
459
+ }): number {
460
+ const text = (wire.content ?? [])
461
+ .map((block) => (block.type === "text" ? (block.text ?? "") : ""))
462
+ .join("");
463
+ return Math.max(
464
+ Buffer.byteLength(text, "utf8"),
465
+ Buffer.byteLength(JSON.stringify(wire.structuredContent ?? {}), "utf8"),
466
+ );
467
+ }
468
+
469
+ /** `wireChannelBytes` of the wire result a SwarmToolResult composes to. */
470
+ export function swarmToolResultBytes(r: SwarmToolResult): number {
471
+ return wireChannelBytes(composeWireResult(r));
472
+ }
473
+
474
+ function fitsWireLimit(r: SwarmToolResult): boolean {
475
+ return swarmToolResultBytes(r) <= MCP_RESULT_WIRE_LIMIT_BYTES;
476
+ }
477
+
478
+ /** A prefix of `source` that never ends on a lone high surrogate. */
479
+ function safePrefix(source: string, chars: number): string {
480
+ const end = Math.min(chars, source.length);
481
+ const code = source.charCodeAt(end - 1);
482
+ return code >= 0xd800 && code <= 0xdbff ? source.slice(0, end - 1) : source.slice(0, end);
483
+ }
484
+
485
+ function previewWithMarker(source: string, chars: number): string {
486
+ const preview = safePrefix(source, chars);
487
+ const omitted = source.length - preview.length;
488
+ return omitted > 0 ? `${preview}\n… [truncated ${omitted} chars]` : preview;
489
+ }
490
+
491
+ /**
492
+ * Largest preview of `source` whose rendered result still fits the per-channel
493
+ * budget (binary search on characters). Falls back to the empty preview when
494
+ * nothing fits, so the pointer still goes out.
495
+ */
496
+ function fillPreviewToBudget(
497
+ source: string,
498
+ render: (preview: string) => SwarmToolResult,
499
+ ): SwarmToolResult {
500
+ let low = 0;
501
+ let high = source.length;
502
+ while (low < high) {
503
+ const mid = Math.ceil((low + high) / 2);
504
+ if (fitsWireLimit(render(previewWithMarker(source, mid)))) low = mid;
505
+ else high = mid - 1;
506
+ }
507
+ return render(previewWithMarker(source, low));
508
+ }
509
+
413
510
  function overflowKey(toolName: string, value: string): string {
414
511
  const safeToolName = toolName.replace(/[^a-zA-Z0-9._-]/g, "-").slice(0, 80);
415
512
  const hash = new Bun.CryptoHasher("sha256").update(value).digest("hex");
416
513
  return `v1/${safeToolName}/${hash}`;
417
514
  }
418
515
 
419
- function overflowRetrieval(namespace: string, key: string): string {
516
+ /**
517
+ * Point the model at a targeted fetch: the biggest array in the shape, else the
518
+ * biggest object or string, which kv-get returns as a bounded, pageable slice.
519
+ * A shape of only scalar leaves (a wide object) points at their parent object,
520
+ * since kv-get rejects offset on a scalar.
521
+ */
522
+ function overflowRetrieval(namespace: string, key: string, shape: KvShapeEntry[]): string {
523
+ const target =
524
+ shape.find((entry) => entry.type === "array") ??
525
+ shape.find((entry) => entry.type === "object" || entry.type === "string") ??
526
+ (shape[0] && { path: joinKvPath(splitKvPath(shape[0].path).slice(0, -1)) });
527
+ if (!target) {
528
+ return (
529
+ `kv-get(${JSON.stringify({ namespace, key })}) returns the full value ` +
530
+ `(your harness may truncate it); in a script, ctx.swarm.kv_get keeps it out of your context.`
531
+ );
532
+ }
420
533
  return (
421
- `kv-get(${JSON.stringify({ namespace, key })}) returns the full value ` +
422
- `(your harness may truncate it); to filter or aggregate it instead, ` +
423
- `process it in a script via ctx.swarm.kv_get.`
534
+ `kv-get(${JSON.stringify({ namespace, key, path: target.path, offset: 0 })}) returns a ` +
535
+ `bounded page (nextOffset pages on; any Shape path works; no path = the full value). ` +
536
+ `ctx.swarm.kv_get takes the same args in a script.`
424
537
  );
425
538
  }
426
539
 
540
+ function formatBytes(bytes: number): string {
541
+ return bytes >= 1_024 ? `${(bytes / 1_024).toFixed(1)}KB` : `${bytes}B`;
542
+ }
543
+
544
+ function renderShape(shape: KvShapeEntry[]): string {
545
+ return shape
546
+ .map((entry) => {
547
+ const count =
548
+ entry.items === undefined
549
+ ? ""
550
+ : entry.type === "array"
551
+ ? `[${entry.items}]`
552
+ : entry.type === "object"
553
+ ? `{${entry.items} keys}`
554
+ : ` ${entry.items} chars`;
555
+ return `${entry.path} ${entry.type}${count} ${formatBytes(entry.bytes)}`;
556
+ })
557
+ .join("; ");
558
+ }
559
+
427
560
  function canonicalOverflowPayload(toolName: string, result: SwarmToolResult): string {
428
561
  // `allowSecretEgress` deliberately bypasses the normal wire scrubber. Never
429
562
  // persist that escape hatch: overflow KV is readable by authenticated agents,
@@ -549,15 +682,13 @@ function arrayPreservingOverflowResult(
549
682
  data: SwarmToolData,
550
683
  targets: ArrayTarget[],
551
684
  truncation: SwarmToolTruncation,
552
- pointer: string,
685
+ detailsPreview: string | undefined,
553
686
  ): SwarmToolResult {
554
- const normalizedDetails = result.details?.trim() || undefined;
555
- const details = normalizedDetails
556
- ? `${normalizedDetails.slice(0, MCP_PROSE_PREVIEW_CHARS)}\n… [truncated ${Math.max(
557
- normalizedDetails.length - MCP_PROSE_PREVIEW_CHARS,
558
- 0,
559
- )} chars]\n${pointer}`
560
- : `JSON payload truncated in place:\n${JSON.stringify(data, null, 2)}\n${pointer}`;
687
+ // The structured channel carries the shortened data verbatim, so `details`
688
+ // (tool prose, or the data rendered for text-only harnesses) stays out of
689
+ // it and each channel spends its own budget on payload.
690
+ const details =
691
+ detailsPreview ?? `JSON payload truncated in place:\n${JSON.stringify(data, null, 2)}`;
561
692
  return {
562
693
  ok: result.ok,
563
694
  message: arrayTruncationMessage(result.message, targets),
@@ -565,6 +696,7 @@ function arrayPreservingOverflowResult(
565
696
  data,
566
697
  nudge: result.nudge,
567
698
  truncation,
699
+ omitStructuredDetails: true,
568
700
  };
569
701
  }
570
702
 
@@ -576,7 +708,6 @@ function arrayPreservingOverflowResult(
576
708
  function truncateArraysInPlace(
577
709
  result: SwarmToolResult,
578
710
  truncation: SwarmToolTruncation,
579
- pointer: string,
580
711
  ): SwarmToolResult | undefined {
581
712
  if (!result.data) return undefined;
582
713
  const data = cloneResultData(result.data) as SwarmToolData;
@@ -590,10 +721,15 @@ function truncateArraysInPlace(
590
721
  Buffer.byteLength(JSON.stringify(a.originalItems), "utf8"),
591
722
  );
592
723
 
593
- const render = () => arrayPreservingOverflowResult(result, data, targets, truncation, pointer);
594
- const fits = () =>
595
- Buffer.byteLength(JSON.stringify(composeWireResult(render())), "utf8") <=
596
- MCP_RESULT_WIRE_LIMIT_BYTES;
724
+ // Size the arrays against a fixed prose floor, then let the prose preview
725
+ // grow into whatever budget the arrays leave.
726
+ const prose = result.details?.trim() || undefined;
727
+ const renderWith = (preview: string | undefined) =>
728
+ arrayPreservingOverflowResult(result, data, targets, truncation, preview);
729
+ const render = () =>
730
+ renderWith(prose ? previewWithMarker(prose, MCP_PROSE_PREVIEW_FLOOR_CHARS) : undefined);
731
+ const fits = () => fitsWireLimit(render());
732
+ const finish = () => (prose ? fillPreviewToBudget(prose, renderWith) : render());
597
733
 
598
734
  for (const target of targets) {
599
735
  setArrayLength(target, 0);
@@ -608,10 +744,10 @@ function truncateArraysInPlace(
608
744
  else high = mid - 1;
609
745
  }
610
746
  setArrayLength(target, low);
611
- return render();
747
+ return finish();
612
748
  }
613
749
 
614
- if (fits()) return render();
750
+ if (fits()) return finish();
615
751
 
616
752
  // A large scalar sibling can keep the result oversized even after every
617
753
  // array is emptied. Prefer dropping those non-array branches over dropping
@@ -630,7 +766,7 @@ function truncateArraysInPlace(
630
766
  }
631
767
  setArrayLength(target, low);
632
768
  }
633
- return render();
769
+ return finish();
634
770
  }
635
771
 
636
772
  const ctxControlMiddleware: FinalizeMiddleware = async (result, ctx) => {
@@ -640,21 +776,30 @@ const ctxControlMiddleware: FinalizeMiddleware = async (result, ctx) => {
640
776
  if (ctx.callOrigin === "script-sdk" || ctx.callOrigin === "extension") return result;
641
777
  if (CTX_CONTROL_EXEMPT_TOOLS.has(ctx.toolName)) return result;
642
778
 
643
- const fullWire = composeWireResult(result);
644
- const fullWireJson = JSON.stringify(fullWire);
645
- const fullWireBytes = Buffer.byteLength(fullWireJson, "utf8");
779
+ const fullWireBytes = swarmToolResultBytes(result);
646
780
  if (fullWireBytes <= MCP_RESULT_WIRE_LIMIT_BYTES) {
647
781
  return result;
648
782
  }
649
783
 
784
+ // Lossless first: tools like script-run put the payload in `data` AND a
785
+ // rendering of it in `details`, so structuredContent carries it twice. That
786
+ // channel stays self-sufficient with `data` alone; drop the duplicate there
787
+ // before spilling anything.
788
+ if (result.details?.trim() && result.data && Object.keys(result.data).length > 0) {
789
+ const deduped: SwarmToolResult = { ...result, omitStructuredDetails: true };
790
+ if (fitsWireLimit(deduped)) return deduped;
791
+ }
792
+
793
+ const storedValue = canonicalOverflowPayload(ctx.toolName, result);
794
+ // Paths are relative to the stored value, so each one is a kv-get `path`.
795
+ const shape = summarizeKvShape(JSON.parse(storedValue));
650
796
  let fullValueAt: string;
651
797
  let retrieval: string;
652
798
  if (ctx.agentId) {
653
- const storedValue = canonicalOverflowPayload(ctx.toolName, result);
654
799
  const key = overflowKey(ctx.toolName, storedValue);
655
800
  const namespace = mcpOverflowNamespace(ctx.agentId);
656
801
  fullValueAt = `kv://${namespace}/${key}`;
657
- retrieval = overflowRetrieval(namespace, key);
802
+ retrieval = overflowRetrieval(namespace, key, shape);
658
803
  const expiresAt = Date.now() + MCP_OVERFLOW_TTL_MS;
659
804
 
660
805
  // The public KV write surfaces cap values at 2 MiB, while the SQLite TEXT
@@ -680,28 +825,26 @@ const ctxControlMiddleware: FinalizeMiddleware = async (result, ctx) => {
680
825
  originalBytes: fullWireBytes,
681
826
  limitBytes: MCP_RESULT_WIRE_LIMIT_BYTES,
682
827
  retrieval,
828
+ ...(shape.length > 0 ? { shape } : {}),
683
829
  };
684
- const pointer =
685
- `Full value: ${fullValueAt}\nRetrieval: ${retrieval}\n` +
686
- `Truncation: ${JSON.stringify(truncation)}`;
687
- const arrayPreservingResult = truncateArraysInPlace(result, truncation, pointer);
830
+ const arrayPreservingResult = truncateArraysInPlace(result, truncation);
688
831
  if (arrayPreservingResult) return arrayPreservingResult;
689
832
 
690
- const normalizedDetails = result.details?.trim() || undefined;
691
- const details = normalizedDetails
692
- ? `${normalizedDetails.slice(0, MCP_PROSE_PREVIEW_CHARS)}\n… [truncated ${Math.max(
693
- normalizedDetails.length - MCP_PROSE_PREVIEW_CHARS,
694
- 0,
695
- )} chars]\n${pointer}`
696
- : `JSON payload omitted because the composed result exceeded ${MCP_RESULT_WIRE_LIMIT_BYTES} bytes.\n${pointer}`;
697
-
698
- return {
833
+ const spilled = (details: string): SwarmToolResult => ({
699
834
  ok: result.ok,
700
835
  message: result.message,
701
836
  details,
702
837
  nudge: result.nudge,
703
838
  truncation,
704
- };
839
+ });
840
+ const prose = result.details?.trim() || undefined;
841
+ if (prose) return fillPreviewToBudget(prose, spilled);
842
+ // Data without arrays to shorten: show the head of its JSON rendering as
843
+ // labelled prose. Structured `data` stays omitted, since a cut JSON value
844
+ // there would read as a real (wrong) value.
845
+ const json = result.data ? JSON.stringify(result.data, null, 2) : "";
846
+ if (!json) return spilled("Payload omitted.");
847
+ return fillPreviewToBudget(json, (preview) => spilled(`JSON payload head:\n${preview}`));
705
848
  };
706
849
 
707
850
  // Ordered and security-sensitive: ctx-control runs only after the result and
@@ -54,11 +54,12 @@ export const registerGetWorkflowRunTool = (server: McpServer) => {
54
54
  },
55
55
  async ({ id }) => {
56
56
  try {
57
- const run = await getWorkflowRun(id);
57
+ // Display view: the scrubbed context and step outputs, never replay state.
58
+ const run = await getWorkflowRun(id, "display");
58
59
  if (!run) {
59
60
  return toolErr(`Workflow run not found: ${id}`, { data: { steps: [] } });
60
61
  }
61
- const steps = await getWorkflowRunStepsByRunId(id);
62
+ const steps = await getWorkflowRunStepsByRunId(id, "display");
62
63
  return toolOk(`Run ${id} status: ${run.status}.`, {
63
64
  details: renderSteps(steps),
64
65
  data: { run, steps },
package/src/types.ts CHANGED
@@ -175,6 +175,15 @@ export const DEFAULT_MODEL_TIER_MAP: Record<ProviderName, Record<ModelTier, stri
175
175
  smart: "high",
176
176
  ultra: "ultra",
177
177
  },
178
+ // xAI's text models as `grok models` lists them for an API key, cheapest
179
+ // first: grok-build-0.1 ($1/$2 per 1M), grok-4.3 ($1.25/$2.50), then the
180
+ // grok-4.6/4.7 frontier pair ($2/$6).
181
+ grok: {
182
+ smol: "grok-build-0.1",
183
+ regular: "grok-4.3",
184
+ smart: "grok-4.6",
185
+ ultra: "grok-4.7",
186
+ },
178
187
  // ACP has no portable tier-to-model mapping. Operators may set an explicit
179
188
  // MODEL_OVERRIDE, which the adapter applies through an advertised `model`
180
189
  // config option with a target-specific startup fallback.
@@ -413,6 +422,7 @@ export const ProviderNameSchema = z.enum([
413
422
  "dsh",
414
423
  "amp",
415
424
  "cursor",
425
+ "grok",
416
426
  ]);
417
427
  export type ProviderName = z.infer<typeof ProviderNameSchema>;
418
428
 
@@ -510,6 +520,9 @@ export const PROVIDER_STEER_CAPABILITIES: Record<ProviderName, SteerMode[]> = {
510
520
  // `run.steer()` reaches the in-flight run; a message the SDK reverts to a
511
521
  // follow-up, and every queued one, starts the next run on the same agent.
512
522
  cursor: ["steer", "queue"],
523
+ // Grok runs on the ACP client: one `session/prompt` turn, interrupted only
524
+ // by `session/cancel`. No Grok surface (ACP or headless) exposes a steer.
525
+ grok: [],
513
526
  };
514
527
 
515
528
  export type DevinProviderMeta = {
@@ -532,6 +545,7 @@ export type ProviderMetaMap = {
532
545
  dsh: NoProviderMeta;
533
546
  amp: NoProviderMeta;
534
547
  cursor: NoProviderMeta;
548
+ grok: NoProviderMeta;
535
549
  };
536
550
 
537
551
  export const FollowUpConfigSchema = z
@@ -1307,7 +1321,7 @@ export type AcpSessionConfigOption = z.infer<typeof AcpSessionConfigOptionSchema
1307
1321
 
1308
1322
  export const AgentAcpStatusSchema = z
1309
1323
  .object({
1310
- target: z.enum(["opencode", "custom"]),
1324
+ target: z.enum(["opencode", "gemini", "copilot", "custom"]),
1311
1325
  configOptions: z.array(AcpSessionConfigOptionSchema),
1312
1326
  reportedAt: z.number(),
1313
1327
  })
@@ -3476,6 +3490,7 @@ export const PricingProviderSchema = z.enum([
3476
3490
  "dsh",
3477
3491
  "amp",
3478
3492
  "cursor",
3493
+ "grok",
3479
3494
  ]);
3480
3495
  export type PricingProvider = z.infer<typeof PricingProviderSchema>;
3481
3496
 
@@ -0,0 +1,5 @@
1
+ // Side-effect entry: import first so every later console wrapper and every
2
+ // module-load log line goes through the scrub.
3
+ import { installConsoleScrub } from "./console-scrub";
4
+
5
+ installConsoleScrub();
@@ -0,0 +1,57 @@
1
+ import { format } from "node:util";
2
+ import { scrubSecrets } from "./secret-scrubber";
3
+
4
+ /**
5
+ * Process-wide console scrub: every `console.log/info/warn/error/debug` line
6
+ * is formatted, scrubbed, and handed to the previous method as one string.
7
+ *
8
+ * Install it before anything else wraps the console (the OTel log bridge, the
9
+ * Ink console patch), so every later wrapper sees only scrubbed text.
10
+ */
11
+
12
+ const METHODS = ["log", "info", "warn", "error", "debug"] as const;
13
+ type ConsoleMethod = (typeof METHODS)[number];
14
+
15
+ const SCRUB_FAILED = "[console-scrub] failed to scrub log line";
16
+ const NESTED_SUPPRESSED = "[console-scrub] suppressed a log line written while formatting another";
17
+
18
+ let restore: (() => void) | undefined;
19
+
20
+ export function installConsoleScrub(): void {
21
+ if (restore) return;
22
+ const originals = new Map<ConsoleMethod, (...args: unknown[]) => void>();
23
+ let scrubbing = false;
24
+ for (const method of METHODS) {
25
+ const original = console[method];
26
+ originals.set(method, original);
27
+ console[method] = (...args: unknown[]) => {
28
+ if (args.length === 0) return original.call(console);
29
+ // `format` runs custom inspectors synchronously, and one that logs lands
30
+ // here mid-scrub. Its arguments are unscrubbed and formatting them again
31
+ // could recurse, so fail closed with a fixed line instead.
32
+ if (scrubbing) return original.call(console, NESTED_SUPPRESSED);
33
+ scrubbing = true;
34
+ let line: string;
35
+ try {
36
+ // `format` keeps %s/%d substitution, inspects objects and prints
37
+ // Error stacks, so the scrub sees exactly what would be printed.
38
+ line = scrubSecrets(format(...args));
39
+ } catch {
40
+ // Never fall back to the raw arguments.
41
+ line = SCRUB_FAILED;
42
+ } finally {
43
+ scrubbing = false;
44
+ }
45
+ original.call(console, line);
46
+ };
47
+ }
48
+ restore = () => {
49
+ for (const [method, original] of originals) console[method] = original;
50
+ restore = undefined;
51
+ };
52
+ }
53
+
54
+ /** Restore the console methods captured at install time (tests only). */
55
+ export function uninstallConsoleScrub(): void {
56
+ restore?.();
57
+ }
@@ -13,6 +13,7 @@ export const CREDENTIAL_POOL_VARS = [
13
13
  "DEEPSEEK_API_KEY",
14
14
  "AMP_API_KEY",
15
15
  "CURSOR_API_KEY",
16
+ "XAI_API_KEY",
16
17
  ] as const;
17
18
 
18
19
  /**
@@ -34,6 +35,7 @@ export const PROVIDER_CREDENTIAL_VARS: Record<string, readonly string[]> = {
34
35
  dsh: ["OPENROUTER_API_KEY", "DEEPSEEK_API_KEY"],
35
36
  amp: ["AMP_API_KEY"],
36
37
  cursor: ["CURSOR_API_KEY"],
38
+ grok: ["XAI_API_KEY", "OPENROUTER_API_KEY"],
37
39
  opencode: ["OPENROUTER_API_KEY", "ANTHROPIC_API_KEY", "OPENAI_API_KEY"],
38
40
  };
39
41
 
@@ -69,6 +71,10 @@ export function getModelAwareCredentialVars(provider: string, model?: string): r
69
71
  if (provider === "dsh" && model) {
70
72
  return model.startsWith("openrouter/") ? ["OPENROUTER_API_KEY"] : ["DEEPSEEK_API_KEY"];
71
73
  }
74
+ // grok routes `openrouter/<id>` to OpenRouter and every other model to xAI.
75
+ if (provider === "grok" && model) {
76
+ return model.startsWith("openrouter/") ? ["OPENROUTER_API_KEY"] : ["XAI_API_KEY"];
77
+ }
72
78
  if (!SLASH_MODEL_PROVIDERS.has(provider) || !model) return base;
73
79
  if (model.includes("/")) {
74
80
  return base.filter((v) => v !== "OPENAI_API_KEY");
@@ -100,6 +106,8 @@ export function deriveProviderFromKeyType(keyType: string): string {
100
106
  return "amp";
101
107
  case "CURSOR_API_KEY":
102
108
  return "cursor";
109
+ case "XAI_API_KEY":
110
+ return "grok";
103
111
  case "DEVIN_API_KEY":
104
112
  return "devin";
105
113
  default:
@@ -47,6 +47,8 @@ export interface PageSessionPayload {
47
47
  uid?: string;
48
48
  /** Display name for a user or generated guest session. */
49
49
  name?: string;
50
+ /** Set only when the shared swarm key launched the session. A session with neither this nor `uid` is a guest. */
51
+ op?: true;
50
52
  }
51
53
 
52
54
  /** base64url encode a byte buffer (no padding). */
@@ -212,7 +214,8 @@ export async function verifyPageSession(
212
214
  return null;
213
215
  }
214
216
 
215
- const candidate = payload as { uid?: unknown; name?: unknown };
217
+ const candidate = payload as { uid?: unknown; name?: unknown; op?: unknown };
218
+ if (candidate.op !== undefined && candidate.op !== true) return null;
216
219
  if (candidate.uid !== undefined && typeof candidate.uid !== "string") return null;
217
220
  if (candidate.name !== undefined && typeof candidate.name !== "string") return null;
218
221
 
@@ -286,7 +289,7 @@ const PAGE_SESSION_TTL_SECONDS = 3600;
286
289
  */
287
290
  export async function issuePageSessionCookie(
288
291
  pageId: string,
289
- opts: { dev: boolean; uid?: string; name?: string },
292
+ opts: { dev: boolean; uid?: string; name?: string; op?: true },
290
293
  ): Promise<string> {
291
294
  const exp = Math.floor(Date.now() / 1000) + PAGE_SESSION_TTL_SECONDS;
292
295
  const token = await signPageSession({
@@ -294,6 +297,7 @@ export async function issuePageSessionCookie(
294
297
  exp,
295
298
  ...(opts.uid ? { uid: opts.uid } : {}),
296
299
  ...(opts.name ? { name: opts.name } : {}),
300
+ ...(opts.op ? { op: true as const } : {}),
297
301
  });
298
302
  const attrs = [
299
303
  `page_session=${token}`,