@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
@@ -1,13 +1,42 @@
1
+ import { mkdir, mkdtemp, rm } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
1
3
  import { join } from "node:path";
2
- import { type AcpTarget, getAcpTargetCatalogEntry, isAcpTarget } from "./acp-target-catalog";
3
- import type { ProviderSessionConfig } from "./types";
4
+ import { normalizeGrokModel } from "@desplega/model-catalog";
5
+ import { clampContextPercent, getContextWindowSize } from "../utils/context-window";
6
+ import {
7
+ ACP_TARGET_IDS,
8
+ type AcpTarget,
9
+ getAcpTargetCatalogEntry,
10
+ isAcpTarget,
11
+ } from "./acp-target-catalog";
12
+ import { applyReasoningEffort } from "./reasoning-effort";
13
+ import type { CostData, CostModelUsage, ProviderEvent, ProviderSessionConfig } from "./types";
4
14
 
5
15
  export interface AcpTargetProfile {
6
- readonly target: AcpTarget;
16
+ /** An operator-selectable ACP target, or a first-class harness built on the ACP client. */
17
+ readonly target: AcpTarget | "grok";
7
18
  command(config: ProviderSessionConfig): string[];
8
19
  env(config: ProviderSessionConfig): Record<string, string>;
9
20
  configuredOptions(config: ProviderSessionConfig): Record<string, string | boolean>;
10
21
  writeSystemPromptArtifact(config: ProviderSessionConfig): Promise<void>;
22
+ /** Removes what writeSystemPromptArtifact wrote. Called once the session ends or fails to start. */
23
+ cleanupSystemPromptArtifact?(config: ProviderSessionConfig): Promise<void>;
24
+ /** Target-specific `session/new` `_meta`. Targets without one send none. */
25
+ sessionMeta?(config: ProviderSessionConfig): Record<string, unknown> | undefined;
26
+ /** A clearer message for a target error whose own text misleads, else undefined. */
27
+ describeError?(message: string): string | undefined;
28
+ /** Rewrites a translated event for display, e.g. unwrapping a tool proxy. */
29
+ rewriteEvent?(event: ProviderEvent): ProviderEvent;
30
+ /** Usage, cost and the model that ran, from the `session/prompt` response `_meta`. */
31
+ promptCost?(
32
+ meta: Record<string, unknown> | null | undefined,
33
+ model: string,
34
+ ): Partial<CostData> | undefined;
35
+ /** A context snapshot from the `session/prompt` response `_meta`, for targets without `usage_update`. */
36
+ promptContext?(
37
+ meta: Record<string, unknown> | null | undefined,
38
+ model: string,
39
+ ): Extract<ProviderEvent, { type: "context_usage" }> | undefined;
11
40
  }
12
41
 
13
42
  export class AcpTargetResolutionError extends Error {
@@ -181,16 +210,341 @@ const opencodeTargetProfile: AcpTargetProfile = {
181
210
  async writeSystemPromptArtifact() {},
182
211
  };
183
212
 
213
+ // The prompt file is per session and lives outside the task cwd, so it never
214
+ // lands in the repo tree. `env()` runs after `writeSystemPromptArtifact()` and
215
+ // reads the path back from here.
216
+ const geminiSystemPromptPaths = new WeakMap<ProviderSessionConfig, string>();
217
+
218
+ const geminiTargetProfile: AcpTargetProfile = {
219
+ target: "gemini",
220
+ command() {
221
+ const entry = getAcpTargetCatalogEntry("gemini");
222
+ return [entry.command!, ...(entry.args ?? [])];
223
+ },
224
+ env(config) {
225
+ const env = baseTargetEnv(config);
226
+ const entry = getAcpTargetCatalogEntry("gemini");
227
+ copyEnvKeys(config, env, entry.envKeys);
228
+ // A worker has no interactive trust prompt; the task cwd is ours.
229
+ env.GEMINI_CLI_TRUST_WORKSPACE = "true";
230
+ // Startup fallback when the ACP `model` option is not advertised.
231
+ if (config.model.trim()) env.GEMINI_MODEL = config.model.trim();
232
+ const systemPromptPath = geminiSystemPromptPaths.get(config);
233
+ if (systemPromptPath) env.GEMINI_SYSTEM_MD = systemPromptPath;
234
+ return env;
235
+ },
236
+ configuredOptions,
237
+ async writeSystemPromptArtifact(config) {
238
+ // GEMINI_SYSTEM_MD replaces Gemini CLI's built-in prompt, so an empty
239
+ // swarm prompt keeps the built-in one instead of blanking it.
240
+ if (!config.systemPrompt?.trim()) return;
241
+ const directory = await mkdtemp(join(tmpdir(), "swarm-acp-gemini-"));
242
+ const path = join(directory, "system.md");
243
+ await Bun.write(path, config.systemPrompt);
244
+ geminiSystemPromptPaths.set(config, path);
245
+ },
246
+ };
247
+
248
+ /**
249
+ * Copilot CLI has no system-prompt flag. Under `--acp` it reads custom
250
+ * instructions from the git root, the cwd, and every directory listed in
251
+ * COPILOT_CUSTOM_INSTRUCTIONS_DIRS. Verified against 1.0.93: from those extra
252
+ * directories it loads `.github/instructions/*.instructions.md`, but not
253
+ * `AGENTS.md`. So the swarm prompt goes into a per-task directory outside the
254
+ * repo, and the repo's own AGENTS.md is never touched.
255
+ */
256
+ export function copilotInstructionsDir(config: ProviderSessionConfig): string {
257
+ const key = new Bun.CryptoHasher("sha256")
258
+ .update(`${config.agentId}:${config.taskId}:${config.cwd}`)
259
+ .digest("hex")
260
+ .slice(0, 16);
261
+ return join(tmpdir(), "agent-swarm-copilot-instructions", key);
262
+ }
263
+
264
+ export const COPILOT_INSTRUCTIONS_FILE = join(
265
+ ".github",
266
+ "instructions",
267
+ "agent-swarm.instructions.md",
268
+ );
269
+
270
+ const copilotTargetProfile: AcpTargetProfile = {
271
+ target: "copilot",
272
+ command() {
273
+ const entry = getAcpTargetCatalogEntry("copilot");
274
+ return [entry.command!, ...(entry.args ?? [])];
275
+ },
276
+ env(config) {
277
+ const env = baseTargetEnv(config);
278
+ const entry = getAcpTargetCatalogEntry("copilot");
279
+ copyEnvKeys(config, env, entry.envKeys);
280
+ if (config.model.trim()) env.COPILOT_MODEL = config.model.trim();
281
+ // The image pins the CLI; a self-update would run an unpinned binary.
282
+ env.COPILOT_AUTO_UPDATE = "false";
283
+ const operatorDirs = readEnv(config, "COPILOT_CUSTOM_INSTRUCTIONS_DIRS")?.trim();
284
+ env.COPILOT_CUSTOM_INSTRUCTIONS_DIRS = [copilotInstructionsDir(config), operatorDirs]
285
+ .filter(Boolean)
286
+ .join(",");
287
+ return env;
288
+ },
289
+ configuredOptions,
290
+ async writeSystemPromptArtifact(config) {
291
+ const dir = copilotInstructionsDir(config);
292
+ await rm(dir, { recursive: true, force: true });
293
+ if (!config.systemPrompt) return;
294
+ await mkdir(join(dir, ".github", "instructions"), { recursive: true });
295
+ await Bun.write(
296
+ join(dir, COPILOT_INSTRUCTIONS_FILE),
297
+ `---\napplyTo: "**"\n---\n\n${config.systemPrompt}\n`,
298
+ );
299
+ },
300
+ async cleanupSystemPromptArtifact(config) {
301
+ await rm(copilotInstructionsDir(config), { recursive: true, force: true });
302
+ },
303
+ };
304
+
305
+ /**
306
+ * Grok CLI env keys passed through to `grok agent stdio`. `GROK_HOME` is the
307
+ * per-session state dir the grok adapter creates; the rest switch off the
308
+ * Claude/Cursor compat surfaces that would otherwise load the worker's own
309
+ * Claude hooks, MCP servers, CLAUDE.md and rules into a Grok session.
310
+ * Claude skills stay on so swarm skills remain invocable.
311
+ */
312
+ export const GROK_ISOLATION_ENV: Readonly<Record<string, string>> = {
313
+ GROK_DISABLE_AUTOUPDATER: "1",
314
+ GROK_TELEMETRY_ENABLED: "0",
315
+ GROK_CLAUDE_HOOKS_ENABLED: "false",
316
+ GROK_CLAUDE_MCPS_ENABLED: "false",
317
+ GROK_CLAUDE_AGENTS_ENABLED: "false",
318
+ GROK_CLAUDE_RULES_ENABLED: "false",
319
+ GROK_CURSOR_HOOKS_ENABLED: "false",
320
+ GROK_CURSOR_MCPS_ENABLED: "false",
321
+ GROK_CURSOR_AGENTS_ENABLED: "false",
322
+ GROK_CURSOR_RULES_ENABLED: "false",
323
+ GROK_CURSOR_SKILLS_ENABLED: "false",
324
+ };
325
+
326
+ /**
327
+ * The Grok CLI's endpoint overrides for the xAI route (docs.x.ai settings
328
+ * reference): `GROK_MODELS_BASE_URL` points inference and the model list at
329
+ * any OpenAI-compatible gateway, authenticated with XAI_API_KEY.
330
+ */
331
+ export const GROK_XAI_ENDPOINT_KEYS = [
332
+ "GROK_MODELS_BASE_URL",
333
+ "GROK_MODELS_LIST_URL",
334
+ "GROK_XAI_API_BASE_URL",
335
+ ] as const;
336
+
337
+ /** `openrouter/<vendor>/<id>`: an OpenRouter model the grok adapter registers in `config.toml`. */
338
+ export function isGrokOpenRouterModel(model: string): boolean {
339
+ return model.trim().startsWith("openrouter/");
340
+ }
341
+
342
+ /**
343
+ * Grok exposes MCP tools through its `use_tool` proxy (on-demand tool search),
344
+ * so every swarm call arrives as `use_tool {tool_name: "swarm__get-swarm",
345
+ * tool_input}`. Unwrap it to the `mcp__<server>__<tool>` name and its own
346
+ * input, the shape every other harness logs. Grok's in-progress updates
347
+ * repeat the input tagged with a `variant` (`UseTool`, `SearchTool`); the
348
+ * dashboard merges update input into the call, so the tag is dropped and a
349
+ * `UseTool` update carries only the inner input.
350
+ */
351
+ function unwrapGrokToolProxy(event: ProviderEvent): ProviderEvent {
352
+ if (event.type === "tool_start" && event.toolName === "use_tool") {
353
+ const args = event.args as { tool_name?: unknown; tool_input?: unknown } | null;
354
+ if (typeof args?.tool_name !== "string" || !args.tool_name) return event;
355
+ return { ...event, toolName: `mcp__${args.tool_name}`, args: args.tool_input ?? {} };
356
+ }
357
+ if (event.type !== "custom" || event.name !== "acp_tool_call_update") return event;
358
+ const data = event.data as { rawInput?: unknown } | null;
359
+ const raw = data?.rawInput as Record<string, unknown> | null | undefined;
360
+ if (!raw || typeof raw !== "object" || typeof raw.variant !== "string") return event;
361
+ const { variant, ...rest } = raw;
362
+ const rawInput = variant === "UseTool" ? (rest.tool_input ?? {}) : rest;
363
+ return { ...event, data: { ...(data as Record<string, unknown>), rawInput } };
364
+ }
365
+
366
+ function finiteCount(value: unknown): number | undefined {
367
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined;
368
+ }
369
+
370
+ /** xAI bills in ticks of 1e-10 USD (`costUsdTicks`). */
371
+ const XAI_USD_TICKS = 10_000_000_000;
372
+
373
+ /**
374
+ * The swarm id of a model Grok reports (`_meta.modelId`, a `modelUsage` key)
375
+ * in a session started on `sessionModel`. An OpenRouter session only reaches
376
+ * OpenRouter, so its ids keep the `openrouter/` namespace pricing and context
377
+ * lookups need; an xAI session's ids are bare xAI ids.
378
+ */
379
+ export function grokReportedModel(reported: string, sessionModel: string): string {
380
+ const id = normalizeGrokModel(reported);
381
+ if (!isGrokOpenRouterModel(sessionModel) || isGrokOpenRouterModel(id)) return id;
382
+ return `openrouter/${id}`;
383
+ }
384
+
385
+ /** The model Grok says ran the prompt's last call, else the session's model. */
386
+ function grokFinalModel(meta: Record<string, unknown> | null | undefined, model: string): string {
387
+ const reported = typeof meta?.modelId === "string" ? meta.modelId.trim() : "";
388
+ return reported ? grokReportedModel(reported, model) : normalizeGrokModel(model);
389
+ }
390
+
391
+ /**
392
+ * Grok's usage counts in the swarm's shape: its `inputTokens` include cache
393
+ * reads and its `outputTokens` exclude reasoning, so both are converted to the
394
+ * swarm's disjoint input and reasoning-inclusive output.
395
+ */
396
+ function grokUsageCounts(usage: Record<string, unknown>) {
397
+ const input = finiteCount(usage.inputTokens) ?? 0;
398
+ const cacheRead = finiteCount(usage.cachedReadTokens) ?? 0;
399
+ const reasoning = finiteCount(usage.reasoningTokens) ?? 0;
400
+ const ticks = finiteCount(usage.costUsdTicks);
401
+ return {
402
+ inputTokens: Math.max(0, input - cacheRead),
403
+ cacheReadTokens: cacheRead,
404
+ cacheWriteTokens: finiteCount(usage.cacheCreationTokens) ?? 0,
405
+ outputTokens: (finiteCount(usage.outputTokens) ?? 0) + reasoning,
406
+ reasoningOutputTokens: reasoning,
407
+ costUsd: ticks ? ticks / XAI_USD_TICKS : undefined,
408
+ };
409
+ }
410
+
411
+ /** `_meta.usage.modelUsage` as per-model rows, keyed by the swarm model id. */
412
+ function grokModelUsage(usage: Record<string, unknown>, model: string): CostModelUsage[] {
413
+ const byModel = usage.modelUsage;
414
+ if (!byModel || typeof byModel !== "object" || Array.isArray(byModel)) return [];
415
+ const rows: CostModelUsage[] = [];
416
+ for (const [reported, entry] of Object.entries(byModel as Record<string, unknown>)) {
417
+ if (!reported.trim() || !entry || typeof entry !== "object") continue;
418
+ const {
419
+ costUsd,
420
+ reasoningOutputTokens: _,
421
+ ...counts
422
+ } = grokUsageCounts(entry as Record<string, unknown>);
423
+ rows.push({
424
+ model: grokReportedModel(reported, model),
425
+ ...counts,
426
+ ...(costUsd !== undefined ? { harnessCostUsd: costUsd } : {}),
427
+ });
428
+ }
429
+ return rows;
430
+ }
431
+
432
+ /**
433
+ * Grok answers `session/prompt` with `usage: null` and puts the prompt's
434
+ * cumulative usage in `_meta.usage`, split per model in `modelUsage` (a side
435
+ * model or a fallback shows up as its own entry). `_meta.modelId` names the
436
+ * model that ran, which can differ from the one requested. `costUsdTicks` is
437
+ * what xAI billed; a BYOK model (OpenRouter) reports none and is priced from
438
+ * the table, per model.
439
+ */
440
+ export function grokPromptCost(
441
+ meta: Record<string, unknown> | null | undefined,
442
+ model: string,
443
+ ): Partial<CostData> | undefined {
444
+ const usage = meta?.usage as Record<string, unknown> | undefined;
445
+ if (!usage || typeof usage !== "object") return undefined;
446
+ const { costUsd, ...counts } = grokUsageCounts(usage);
447
+ const turns = finiteCount(usage.numTurns) ?? finiteCount(usage.modelCalls);
448
+ const models = grokModelUsage(usage, model);
449
+ return {
450
+ model: grokFinalModel(meta, model),
451
+ ...counts,
452
+ ...(models.length ? { models } : {}),
453
+ ...(costUsd !== undefined ? { totalCostUsd: costUsd } : {}),
454
+ ...(turns ? { numTurns: turns } : {}),
455
+ };
456
+ }
457
+
458
+ /**
459
+ * Grok sends no `usage_update`. The prompt response's top-level `_meta`
460
+ * describes the last model call: `totalTokens` is its input (cache reads
461
+ * included), output and reasoning, i.e. how full the context was at the end,
462
+ * and `modelId` is the model whose window that is.
463
+ */
464
+ export function grokPromptContext(
465
+ meta: Record<string, unknown> | null | undefined,
466
+ model: string,
467
+ ): Extract<ProviderEvent, { type: "context_usage" }> | undefined {
468
+ const used = finiteCount(meta?.totalTokens);
469
+ if (!used) return undefined;
470
+ const id = grokFinalModel(meta, model);
471
+ const total = getContextWindowSize(isGrokOpenRouterModel(id) ? id : `xai/${id}`);
472
+ return {
473
+ type: "context_usage",
474
+ contextUsedTokens: used,
475
+ contextTotalTokens: total,
476
+ contextPercent: clampContextPercent(used, total),
477
+ outputTokens: finiteCount(meta?.outputTokens) ?? null,
478
+ contextFormula: "harness-reported",
479
+ };
480
+ }
481
+
482
+ /** `-32000 Authentication required` (ACP) or the CLI's "Not signed in" text. */
483
+ const GROK_AUTH_ERROR_RE = /authentication required|not signed in/i;
484
+
485
+ export const grokTargetProfile: AcpTargetProfile = {
486
+ target: "grok",
487
+ command(config) {
488
+ const binary = readEnv(config, "GROK_BINARY")?.trim() || "grok";
489
+ // `--no-leader` keeps each session on its own agent process (the default
490
+ // config may enable a shared leader socket). Flags go before `stdio`.
491
+ const args = [binary, "agent", "--no-leader", "--always-approve"];
492
+ if (config.model.trim()) args.push("--model", config.model.trim());
493
+ const effort = config.reasoningEffort
494
+ ? applyReasoningEffort("grok", config.model.trim(), config.reasoningEffort)
495
+ : null;
496
+ if (effort?.kind === "grok-effort") args.push("--reasoning-effort", effort.reasoningEffort);
497
+ args.push("stdio");
498
+ return args;
499
+ },
500
+ env(config) {
501
+ const env = baseTargetEnv(config);
502
+ // Each route gets only its own key: an OpenRouter session never sees
503
+ // XAI_API_KEY, so no side model (web search, summaries) bills xAI.
504
+ if (isGrokOpenRouterModel(config.model)) {
505
+ copyEnvKeys(config, env, ["OPENROUTER_API_KEY", "GROK_HOME"]);
506
+ } else {
507
+ copyEnvKeys(config, env, ["XAI_API_KEY", "GROK_HOME", ...GROK_XAI_ENDPOINT_KEYS]);
508
+ }
509
+ Object.assign(env, GROK_ISOLATION_ENV);
510
+ return env;
511
+ },
512
+ // Model and effort ride on the command line, which the CLI accepts before auth.
513
+ configuredOptions() {
514
+ return {};
515
+ },
516
+ async writeSystemPromptArtifact() {},
517
+ sessionMeta(config) {
518
+ // `rules` appends to Grok's own system prompt; `systemPromptOverride`
519
+ // would replace it, tool instructions included.
520
+ return {
521
+ ...(config.systemPrompt?.trim() ? { rules: config.systemPrompt } : {}),
522
+ yoloMode: true,
523
+ };
524
+ },
525
+ rewriteEvent: unwrapGrokToolProxy,
526
+ promptCost: grokPromptCost,
527
+ promptContext: grokPromptContext,
528
+ describeError(message) {
529
+ if (!GROK_AUTH_ERROR_RE.test(message)) return undefined;
530
+ return `Grok rejected the credentials (XAI_API_KEY or OPENROUTER_API_KEY invalid or missing): ${message}`;
531
+ },
532
+ };
533
+
184
534
  export function resolveAcpTarget(config: ProviderSessionConfig): AcpTargetProfile {
185
535
  const target = readEnv(config, "ACP_TARGET") ?? "custom";
186
536
  if (!isAcpTarget(target)) {
187
537
  throw new AcpTargetResolutionError(
188
- `Unsupported ACP target "${target}". Supported targets: opencode, custom.`,
538
+ `Unsupported ACP target "${target}". Supported targets: ${ACP_TARGET_IDS.join(", ")}.`,
189
539
  );
190
540
  }
191
541
  switch (target) {
192
542
  case "opencode":
193
543
  return opencodeTargetProfile;
544
+ case "gemini":
545
+ return geminiTargetProfile;
546
+ case "copilot":
547
+ return copilotTargetProfile;
194
548
  case "custom":
195
549
  return customTargetProfile;
196
550
  }
@@ -44,7 +44,7 @@ import type {
44
44
  * default; the image installs exactly this version and the per-task settings
45
45
  * set `amp.updates.mode: "disabled"`. Bump it deliberately, with a live run.
46
46
  */
47
- export const AMP_PACKAGE = "@ampcode/cli@0.0.1791316840-g709a2a";
47
+ export const AMP_PACKAGE = "@ampcode/cli@0.0.1791403257-g05ffa9";
48
48
 
49
49
  /** MCP server name. Amp exposes its tools as `agent_swarm` / `store_progress` (dashes become underscores). */
50
50
  const AMP_MCP_SERVER_NAME = "agent-swarm";
@@ -35,7 +35,7 @@ import type {
35
35
  } from "./types";
36
36
 
37
37
  /** Pinned in package.json and Dockerfile.worker; bump all three together. */
38
- export const CURSOR_SDK_VERSION = "1.0.36";
38
+ export const CURSOR_SDK_VERSION = "1.0.37";
39
39
 
40
40
  /** MCP server name; Cursor reports its tools as `mcp` calls with this `providerIdentifier`. */
41
41
  const CURSOR_MCP_SERVER_NAME = "agent-swarm";
@@ -0,0 +1,186 @@
1
+ // Bun has no temp-dir or recursive-delete API; file reads and writes use Bun.file/Bun.write.
2
+ import { mkdtemp, rm } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { normalizeGrokModel } from "@desplega/model-catalog";
6
+ import { getOpenRouterBaseUrl } from "../utils/openrouter-base-url";
7
+ import { registerVolatileSecret } from "../utils/secret-scrubber";
8
+ import { ACPAdapter } from "./acp-adapter";
9
+ import { grokTargetProfile, isGrokOpenRouterModel } from "./acp-targets";
10
+ import type {
11
+ CredStatus,
12
+ ProviderAdapter,
13
+ ProviderSession,
14
+ ProviderSessionConfig,
15
+ ProviderTraits,
16
+ } from "./types";
17
+
18
+ /**
19
+ * The xAI Grok CLI (`@xai-official/grok`) on the swarm's ACP client:
20
+ * `grok agent --no-leader stdio` is an ACP server, so spawn, swarm MCP
21
+ * injection, permissions, event translation and cost all come from
22
+ * {@link ACPAdapter}. This adapter adds the per-session `GROK_HOME`.
23
+ *
24
+ * Auth is `XAI_API_KEY` for xAI models, or `OPENROUTER_API_KEY` for an
25
+ * `openrouter/<id>` model, which runs as an OpenAI-compatible BYOK model. A
26
+ * SuperGrok OAuth pool needs the CLI's `auth.json` refresh behaviour measured
27
+ * first (issue #1952 follow-up).
28
+ */
29
+ export function checkGrokCredentials(env: Record<string, string | undefined>): CredStatus {
30
+ return env.XAI_API_KEY?.trim() || env.OPENROUTER_API_KEY?.trim()
31
+ ? { ready: true, missing: [], satisfiedBy: "env" }
32
+ : {
33
+ ready: false,
34
+ missing: ["XAI_API_KEY", "OPENROUTER_API_KEY"],
35
+ hint: "Set XAI_API_KEY (console.x.ai) or OPENROUTER_API_KEY (openrouter/<id> models) for grok.",
36
+ };
37
+ }
38
+
39
+ /**
40
+ * Claude Code plugins installed for the worker's own harness. Grok discovers
41
+ * `~/.claude/plugins` and loads their MCP servers even with Claude MCP compat
42
+ * off, so each one is listed in `[plugins].disabled`.
43
+ */
44
+ export async function installedClaudePluginNames(home: string | undefined): Promise<string[]> {
45
+ if (!home) return [];
46
+ try {
47
+ const parsed = (await Bun.file(
48
+ join(home, ".claude", "plugins", "installed_plugins.json"),
49
+ ).json()) as { plugins?: Record<string, unknown> };
50
+ const ids = Object.keys(parsed.plugins ?? {});
51
+ // Keys are `<plugin>@<marketplace>`; Grok matches on the plugin name.
52
+ return [...new Set(ids.map((id) => id.split("@")[0] ?? "").filter(Boolean))];
53
+ } catch {
54
+ return [];
55
+ }
56
+ }
57
+
58
+ /** An OpenAI-compatible model the session registers under `[model."<id>"]`. */
59
+ export interface GrokCustomModel {
60
+ /** The swarm model string, which is also the id passed to `--model`. */
61
+ id: string;
62
+ /** The id sent to the endpoint. */
63
+ model: string;
64
+ baseUrl: string;
65
+ /** Env var holding the endpoint's API key. */
66
+ envKey: string;
67
+ }
68
+
69
+ /** `openrouter/<vendor>/<id>` -> a `[model."openrouter/<vendor>/<id>"]` block on OpenRouter. */
70
+ export function grokOpenRouterModel(
71
+ model: string,
72
+ env: Record<string, string | undefined>,
73
+ ): GrokCustomModel | null {
74
+ const id = model.trim();
75
+ if (!isGrokOpenRouterModel(id)) return null;
76
+ return {
77
+ id,
78
+ model: id.slice("openrouter/".length),
79
+ baseUrl: getOpenRouterBaseUrl(env),
80
+ envKey: "OPENROUTER_API_KEY",
81
+ };
82
+ }
83
+
84
+ /** `$GROK_HOME/config.toml` for one swarm session. */
85
+ export function buildGrokConfigToml(
86
+ disabledPlugins: readonly string[],
87
+ customModel: GrokCustomModel | null = null,
88
+ ): string {
89
+ const list = disabledPlugins.map((name) => JSON.stringify(name)).join(", ");
90
+ const modelBlock = customModel
91
+ ? [
92
+ "",
93
+ `[model.${JSON.stringify(customModel.id)}]`,
94
+ `model = ${JSON.stringify(customModel.model)}`,
95
+ `base_url = ${JSON.stringify(customModel.baseUrl)}`,
96
+ `env_key = ${JSON.stringify(customModel.envKey)}`,
97
+ `api_backend = "chat_completions"`,
98
+ ]
99
+ : [];
100
+ return [
101
+ "[cli]",
102
+ "auto_update = false",
103
+ "",
104
+ "[compat.codex]",
105
+ "hooks = false",
106
+ "skills = false",
107
+ "",
108
+ "[plugins]",
109
+ `disabled = [${list}]`,
110
+ ...modelBlock,
111
+ "",
112
+ ].join("\n");
113
+ }
114
+
115
+ /**
116
+ * `$GROK_HOME/requirements.toml`: a policy pin that skips every hook outside
117
+ * managed policy at dispatch (plugin hooks and the Claude/Cursor compat
118
+ * files included), so the worker's own Claude hooks never fire inside Grok.
119
+ * `grok inspect` reports it as "Hooks outside managed policy disabled".
120
+ */
121
+ export const GROK_REQUIREMENTS_TOML = "allow_managed_hooks_only = true\n";
122
+
123
+ export class GrokAdapter implements ProviderAdapter {
124
+ readonly name = "grok";
125
+
126
+ readonly traits: ProviderTraits = {
127
+ hasMcp: true,
128
+ hasToolSearch: false,
129
+ // Claude skills compat stays on, so Grok lists ~/.claude/skills itself.
130
+ nativeSkillDiscovery: true,
131
+ hasLocalEnvironment: true,
132
+ steerModes: [],
133
+ };
134
+
135
+ private readonly acp = new ACPAdapter({ providerName: "grok", target: grokTargetProfile });
136
+
137
+ async createSession(sessionConfig: ProviderSessionConfig): Promise<ProviderSession> {
138
+ // `xai/<id>` is the catalog's spelling of a bare xAI id; the CLI takes the bare one.
139
+ const config = { ...sessionConfig, model: normalizeGrokModel(sessionConfig.model) };
140
+ const env = { ...process.env, ...config.env };
141
+ const customModel = grokOpenRouterModel(config.model, env);
142
+ const keyName = customModel ? "OPENROUTER_API_KEY" : "XAI_API_KEY";
143
+ const apiKey = env[keyName]?.trim();
144
+ if (!apiKey) {
145
+ throw new Error(
146
+ customModel
147
+ ? `grok requires OPENROUTER_API_KEY for ${customModel.id}`
148
+ : "grok requires XAI_API_KEY",
149
+ );
150
+ }
151
+ registerVolatileSecret(apiKey, keyName);
152
+
153
+ // A fresh GROK_HOME per session: no shared auth.json, sessions or leader
154
+ // socket between tasks, and the config below applies to this run only.
155
+ const grokHome = await mkdtemp(join(tmpdir(), "swarm-grok-"));
156
+ try {
157
+ await Bun.write(
158
+ join(grokHome, "config.toml"),
159
+ buildGrokConfigToml(await installedClaudePluginNames(env.HOME), customModel),
160
+ { mode: 0o600 },
161
+ );
162
+ await Bun.write(join(grokHome, "requirements.toml"), GROK_REQUIREMENTS_TOML, {
163
+ mode: 0o600,
164
+ });
165
+ const session = await this.acp.createSession({
166
+ ...config,
167
+ env: { ...config.env, [keyName]: apiKey, GROK_HOME: grokHome },
168
+ });
169
+ void session
170
+ .waitForCompletion()
171
+ .finally(() => rm(grokHome, { recursive: true, force: true }));
172
+ return session;
173
+ } catch (error) {
174
+ await rm(grokHome, { recursive: true, force: true });
175
+ throw error;
176
+ }
177
+ }
178
+
179
+ async canResume(_sessionId: string): Promise<boolean> {
180
+ return false;
181
+ }
182
+
183
+ formatCommand(commandName: string): string {
184
+ return `/${commandName}`;
185
+ }
186
+ }
@@ -59,13 +59,17 @@ export async function createProviderAdapter(provider: string): Promise<ProviderA
59
59
  const { CursorAdapter } = await import("./cursor-adapter");
60
60
  return new CursorAdapter();
61
61
  }
62
+ case "grok": {
63
+ const { GrokAdapter } = await import("./grok-adapter");
64
+ return new GrokAdapter();
65
+ }
62
66
  case "acp": {
63
67
  const { ACPAdapter } = await import("./acp-adapter");
64
68
  return new ACPAdapter();
65
69
  }
66
70
  default:
67
71
  throw new Error(
68
- `Unknown HARNESS_PROVIDER: "${provider}". Supported: claude, pi, codex, devin, claude-managed, opencode, acp, dsh, cursor, amp`,
72
+ `Unknown HARNESS_PROVIDER: "${provider}". Supported: claude, pi, codex, devin, claude-managed, opencode, acp, dsh, cursor, amp, grok`,
69
73
  );
70
74
  }
71
75
  }
@@ -69,6 +69,12 @@ rate by hand should also update this file.
69
69
  When the adapter reports what Amp billed (`amp threads usage`, only when
70
70
  every request was billed through Amp), that harness cost wins and the
71
71
  rows above only fill the per-model breakdown.
72
+ - xAI models (models.dev `xai` section, bare ids such as `grok-4.6`) → rows
73
+ under `provider='grok'`. Base rates only; the `context_over_200k` tier is
74
+ not projected (no provider projects context tiers yet). The adapter sends
75
+ what xAI billed (`_meta.usage.costUsdTicks`), which wins over these rows
76
+ as for amp. OpenRouter models also project under `provider='grok'` for
77
+ `openrouter/<id>` models, which report no USD.
72
78
  - OpenAI, Anthropic, Google and xAI models (bare vendor ids) → rows under
73
79
  `provider='cursor'`. Cursor bills the vendor's API rates and reports the
74
80
  vendor's own id. The adapter subtracts cache reads and writes from SDK
@@ -17,6 +17,7 @@ import {
17
17
  claudeCatalogModelId,
18
18
  cursorCatalogRef,
19
19
  dshCatalogRef,
20
+ grokCatalogRef,
20
21
  REASONING_EFFORT_LEVELS,
21
22
  type ReasoningEffortLevel,
22
23
  reasoningLevelsFor,
@@ -28,7 +29,15 @@ export { REASONING_EFFORT_LEVELS };
28
29
  export type ReasoningEffort = ReasoningEffortLevel;
29
30
 
30
31
  /** The local harnesses this feature covers (Devin / claude-managed / ACP are out of scope). */
31
- export type ReasoningHarness = "claude" | "codex" | "pi" | "opencode" | "dsh" | "cursor" | "amp";
32
+ export type ReasoningHarness =
33
+ | "claude"
34
+ | "codex"
35
+ | "pi"
36
+ | "opencode"
37
+ | "dsh"
38
+ | "cursor"
39
+ | "amp"
40
+ | "grok";
32
41
 
33
42
  export interface ReasoningCapability {
34
43
  supported: boolean;
@@ -55,6 +64,7 @@ export type ReasoningEffortApplication =
55
64
  | { kind: "dsh-effort"; reasoningEffort: ReasoningEffort }
56
65
  | { kind: "amp-effort"; reasoningEffort: ReasoningEffort }
57
66
  | { kind: "cursor-effort"; reasoningEffort: ReasoningEffort }
67
+ | { kind: "grok-effort"; reasoningEffort: ReasoningEffort }
58
68
  | { kind: "noop" };
59
69
 
60
70
  // --- Capability lookup --------------------------------------------------------
@@ -90,6 +100,8 @@ function lookupModel(
90
100
  ({ providerId, modelId } = dshCatalogRef(model));
91
101
  } else if (harness === "cursor") {
92
102
  ({ providerId, modelId } = cursorCatalogRef(model));
103
+ } else if (harness === "grok") {
104
+ ({ providerId, modelId } = grokCatalogRef(model));
93
105
  } else {
94
106
  ({ providerId, modelId } = splitProviderModel(model));
95
107
  if (!providerId) return undefined;
@@ -223,6 +235,10 @@ export function applyReasoningEffort(
223
235
  // (`reasoning`, `reasoning_effort`, `effort`); the adapter maps the
224
236
  // level onto the live model list (see `src/providers/cursor-adapter.ts`).
225
237
  return { kind: "cursor-effort", reasoningEffort: level };
238
+ case "grok":
239
+ // Grok's `--reasoning-effort` takes the normalized names as-is; see
240
+ // `src/providers/grok-adapter.ts`.
241
+ return { kind: "grok-effort", reasoningEffort: level };
226
242
  default: {
227
243
  const _exhaustive: never = harness;
228
244
  return _exhaustive;