@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.
- package/README.md +1 -1
- package/dist/acp-adapter-yzfxn8vj.js +23 -0
- package/dist/{actions-hp2h32dx.js → actions-qz3m692w.js} +24 -28
- package/dist/{amp-adapter-zfd7djyx.js → amp-adapter-j1xbmsqz.js} +7 -7
- package/dist/{app-w2d26h4f.js → app-43vrdmv8.js} +9 -10
- package/dist/{assistant-45pc4en7.js → assistant-ertffmka.js} +25 -29
- package/dist/{boot-reembed-k1q2s4fc.js → boot-reembed-998jtpdg.js} +12 -15
- package/dist/{boot-reembed-6rhcpsjf.js → boot-reembed-s7k7c6v2.js} +13 -15
- package/dist/boot-scrub-sweep-nepsk4t1.js +211 -0
- package/dist/{boot-seeds-ex93edx7.js → boot-seeds-vvzs1zhk.js} +12 -12
- package/dist/{claude-adapter-hcxd0szm.js → claude-adapter-2zvmekrp.js} +15 -17
- package/dist/{claude-managed-adapter-fz7xps2r.js → claude-managed-adapter-s49wbk8t.js} +5 -5
- package/dist/{claude-managed-setup-xx6f15h9.js → claude-managed-setup-cvhqq8qn.js} +4 -4
- package/dist/{claude-sdk-session-6nb9raem.js → claude-sdk-session-kkbxtw5q.js} +2624 -2606
- package/dist/{cli-etbrt8x1.js → cli-0nhdwxd5.js} +4 -4
- package/dist/{cli-jeefgppq.js → cli-0qjcsxgz.js} +7 -7
- package/dist/{cli-6behs2bt.js → cli-32bca7nr.js} +1 -1
- package/dist/{cli-gt8v96qx.js → cli-3ddybbbx.js} +4 -4
- package/dist/{acp-adapter-rxvek4ym.js → cli-3qn4r334.js} +317 -35
- package/dist/{cli-1d3w4xm3.js → cli-4r1sjfc4.js} +14 -6
- package/dist/{cli-691f2rxm.js → cli-66zh26sp.js} +68 -16
- package/dist/{cli-et8wdyqh.js → cli-68r1j4sg.js} +9 -9
- package/dist/{cli-xb7kyanp.js → cli-8kgvmkak.js} +3 -3
- package/dist/{cli-fdpkw8vk.js → cli-8t229cmr.js} +8 -8
- package/dist/{cli-gc54qc0p.js → cli-aqcey741.js} +1 -1
- package/dist/{cli-4q4cmr4t.js → cli-cpfmqz3n.js} +8 -8
- package/dist/{cli-a9bw0xxt.js → cli-df56tqdw.js} +997 -408
- package/dist/{cli-n7h1kqpf.js → cli-dmtrxqhg.js} +6 -1
- package/dist/{cli-xfb7333f.js → cli-dw2q9f7g.js} +5 -5
- package/dist/{cli-4drstbb9.js → cli-ecknp7p1.js} +81 -23
- package/dist/{cli-v0pvvhfq.js → cli-efnd2kce.js} +36 -1
- package/dist/{cli-1wetf34q.js → cli-f2n17zhp.js} +1 -1
- package/dist/{cli-2c8eky5y.js → cli-feh3dkah.js} +1 -1
- package/dist/cli-fn697bnt.js +126 -0
- package/dist/{cli-v5pr2fgt.js → cli-gt8azyet.js} +3 -3
- package/dist/{cli-dbszf3rw.js → cli-h3x613ze.js} +4 -4
- package/dist/{cli-nwhb8y2e.js → cli-hf5s9pgf.js} +1 -1
- package/dist/cli-j2jbr8af.js +516 -0
- package/dist/{cli-agjzafns.js → cli-jhm6hzjn.js} +593 -10
- package/dist/{cli-j0tmc908.js → cli-jvj6a0cv.js} +484 -477
- package/dist/{cli-xxz89c2n.js → cli-mb75wpxf.js} +1 -1
- package/dist/{cli-07s39g1n.js → cli-mv2520nm.js} +1 -1
- package/dist/{cli-v90vfwyb.js → cli-mwegs4af.js} +5 -5
- package/dist/{cli-bz1gbac9.js → cli-p4ztb80h.js} +2 -2
- package/dist/{cli-1wd1n2ya.js → cli-rfphkzh5.js} +1 -1
- package/dist/{cli-976r1zqx.js → cli-rggdq8wq.js} +5 -5
- package/dist/{cli-3ajw5v5v.js → cli-rsy933yz.js} +6 -6
- package/dist/{cli-yj790e19.js → cli-tadfgd0t.js} +8 -1
- package/dist/{cli-cn53e7vc.js → cli-ttbsse3f.js} +6 -1
- package/dist/{cli-dyqsrqcf.js → cli-v2a8x45d.js} +4 -4
- package/dist/{cli-wbvnn6tk.js → cli-v6vv7dy8.js} +91 -9
- package/dist/{cli-z2ap2c0c.js → cli-vzc73wet.js} +90 -55
- package/dist/cli-wt7ccy4r.js +2654 -0
- package/dist/{cli-4cn19jvn.js → cli-x7kpr72a.js} +3 -3
- package/dist/{cli-003785v9.js → cli-xvc3q9br.js} +1 -1
- package/dist/{cli-v0e7jpr5.js → cli-xvdgasgy.js} +13 -4
- package/dist/{cli-asqeg4ve.js → cli-y59h2pk9.js} +2 -2
- package/dist/{cli-0wn3ypqv.js → cli-yshaazs1.js} +14121 -3478
- package/dist/{cli-wg8yjqtw.js → cli-ysnyhws9.js} +16 -3
- package/dist/cli-zp67qg5x.js +92 -0
- package/dist/{cli-g3qgrzck.js → cli-zsh093hk.js} +71 -3
- package/dist/{cli-ma3zvh39.js → cli-zy7vwsne.js} +6066 -5829
- package/dist/cli.js +79 -26
- package/dist/{codex-adapter-pcxdn10v.js → codex-adapter-dw1wxqt8.js} +10 -10
- package/dist/{codex-hook-b1fb7c2k.js → codex-hook-d2rkvf39.js} +1 -1
- package/dist/{codex-login-059nm145.js → codex-login-terbdsqq.js} +2 -2
- package/dist/{codex-session-runner-xzwwddp8.js → codex-session-runner-3cgb8y1g.js} +10 -10
- package/dist/{commands-98at8mkn.js → commands-mhxam6z2.js} +9 -9
- package/dist/{cursor-adapter-dkg6qnkp.js → cursor-adapter-2qda3st7.js} +6 -6
- package/dist/{db-01g8wtvw.js → db-sjks418k.js} +11 -9
- package/dist/{devin-adapter-qmttt2fz.js → devin-adapter-bffjj5n4.js} +2 -2
- package/dist/{dispatcher-nn2k1wxd.js → dispatcher-1es759rn.js} +22 -26
- package/dist/{dsh-adapter-w4d4vwdq.js → dsh-adapter-bek4cyw9.js} +6 -6
- package/dist/{e2b-9e4g2hvt.js → e2b-nd50eccg.js} +1 -1
- package/dist/{event-bus-z7v2t26e.js → event-bus-vnfthafg.js} +2 -2
- package/dist/grok-adapter-ar9cw9jg.js +29 -0
- package/dist/{handlers-b0kqsbg8.js → handlers-1gkbhktr.js} +25 -29
- package/dist/{hook-sap7v1aj.js → hook-ds2zxq9v.js} +16 -18
- package/dist/{hook-pz50gqep.js → hook-vxa3tcsy.js} +10 -12
- package/dist/{http-ncr8rmd0.js → http-3h21z9md.js} +679 -227
- package/dist/{index-c4aqcyz1.js → index-9b65j1m5.js} +11 -11
- package/dist/{index-r46s01nq.js → index-bn6cy8ps.js} +22 -26
- package/dist/{index-38kqdt3e.js → index-dkfxpkmm.js} +23 -27
- package/dist/{index-630kj5vd.js → index-f8xnqrye.js} +55 -43
- package/dist/{index-fdk1t4d6.js → index-n0rxa4ca.js} +11962 -9900
- package/dist/{index-9nrj7797.js → index-n99fycq7.js} +22 -26
- package/dist/{keepalive-31613r2h.js → keepalive-3xspa9jw.js} +14 -11
- package/dist/{lead-mx59tr5s.js → lead-6qphc124.js} +43 -43
- package/dist/{maintenance-55ff39ca.js → maintenance-0teem1bb.js} +10 -12
- package/dist/{node-f0ykebbv.js → node-9sg7ajby.js} +25 -7
- package/dist/{oauth-refresh-sweep-anma954w.js → oauth-refresh-sweep-sh9q25xp.js} +13 -10
- package/dist/{onboard-b2fy8ftb.js → onboard-54qmnqxe.js} +4 -3
- package/dist/{opencode-adapter-p1s92tmy.js → opencode-adapter-wjfa2wvd.js} +6 -6
- package/dist/{otel-impl-9brvt7xv.js → otel-impl-ycqzp2a1.js} +2 -2
- package/dist/{pi-mono-adapter-kz4rrwvd.js → pi-mono-adapter-e77e9zst.js} +6 -6
- package/dist/{pricing-refresh-7w8mbmva.js → pricing-refresh-atnkngqs.js} +10 -10
- package/dist/{rbac-roles-ejp7qwvb.js → rbac-roles-9pjphh9q.js} +10 -10
- package/dist/{rbac-roles-nhk4r7bc.js → rbac-roles-j78wdksm.js} +10 -10
- package/dist/{render-v2-2gzt8skq.js → render-v2-pxjs49fx.js} +9 -10
- package/dist/{setup-5et8t412.js → setup-ymej0xtx.js} +2 -2
- package/dist/{work-objects-tb0er7qa.js → work-objects-3nh4ymzb.js} +26 -30
- package/dist/{worker-62cv075d.js → worker-qa9fkz5s.js} +43 -43
- package/dist/{x-vbxb31jr.js → x-ma4gnfgy.js} +2 -2
- package/openapi.json +457 -31
- package/package.json +14 -6
- package/plugin/commands/create-pr.md +7 -1
- package/plugin/pi-skills/create-pr/SKILL.md +7 -1
- package/src/agentmail/app.ts +5 -4
- package/src/be/automation-preflight.ts +11 -9
- package/src/be/boot-scrub-sweep.ts +302 -0
- package/src/be/connector-codes.ts +81 -0
- package/src/be/crypto/key-bootstrap.ts +1 -1
- package/src/be/db/approvals.ts +15 -1
- package/src/be/db/codex-oauth-device-flows.ts +40 -0
- package/src/be/db/config-secrets.ts +31 -0
- package/src/be/db/runtime.ts +4 -0
- package/src/be/db/script-runs.ts +15 -0
- package/src/be/db/tasks/write.ts +1 -1
- package/src/be/db-queries/mcp-oauth.ts +5 -0
- package/src/be/db-queries/oauth.ts +77 -0
- package/src/be/db.ts +162 -59
- package/src/be/events.ts +17 -1
- package/src/be/memory/index-content.ts +2 -2
- package/src/be/memory/providers/sqlite-store.ts +63 -11
- package/src/be/memory/types.ts +14 -0
- package/src/be/migrations/197_connector_codes.sql +23 -0
- package/src/be/migrations/198_permission_audit_guest.sql +33 -0
- package/src/be/migrations/200_backfill_auto_memory_expiry.sql +20 -0
- package/src/be/migrations/201_workflow_replay_columns.sql +10 -0
- package/src/be/migrations/202_codex_oauth_device_flows.sql +24 -0
- package/src/be/model-tier-resolution.ts +2 -1
- package/src/be/model-validation.ts +1 -1
- package/src/be/pricing-normalize.ts +3 -0
- package/src/be/rbac-audit.ts +2 -1
- package/src/be/scripts/db.ts +28 -4
- package/src/be/scripts/source-secrets.ts +37 -0
- package/src/be/scripts/typecheck.ts +23 -2
- package/src/be/scrub-json.ts +32 -0
- package/src/be/sealed-json.ts +59 -0
- package/src/be/secret-registry.ts +131 -0
- package/src/be/seed-pricing.ts +14 -0
- package/src/be/users.ts +7 -1
- package/src/be/workflow-replay.ts +77 -0
- package/src/cli.tsx +7 -0
- package/src/commands/provider-credentials.ts +46 -8
- package/src/commands/runner.ts +2 -10
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/github/app.ts +4 -4
- package/src/heartbeat/heartbeat.ts +20 -8
- package/src/hooks/pr-body-guard.ts +1 -1
- package/src/http/agents.ts +1 -0
- package/src/http/approval-requests.ts +1 -0
- package/src/http/apps.ts +3 -0
- package/src/http/auth.ts +4 -1
- package/src/http/codex-oauth-device.ts +8 -10
- package/src/http/codex-oauth-keep-warm.ts +2 -2
- package/src/http/config-values.ts +59 -0
- package/src/http/config.ts +5 -0
- package/src/http/db-query-bounded.ts +3 -0
- package/src/http/db-query-shared.ts +107 -15
- package/src/http/db-query.ts +10 -2
- package/src/http/extensions.ts +1 -0
- package/src/http/index.ts +23 -6
- package/src/http/integrations.ts +1 -38
- package/src/http/ip-rate-limit.ts +82 -0
- package/src/http/kv.ts +38 -4
- package/src/http/mcp-servers.ts +1 -0
- package/src/http/memory.ts +9 -0
- package/src/http/models-catalog.ts +2 -0
- package/src/http/operator-actor.ts +4 -0
- package/src/http/page-proxy.ts +22 -0
- package/src/http/pages.ts +4 -2
- package/src/http/realtime.ts +1 -1
- package/src/http/request-principal.ts +14 -1
- package/src/http/script-connections.ts +8 -0
- package/src/http/script-runs.ts +12 -4
- package/src/http/scripts.ts +15 -0
- package/src/http/session-cost-recompute.ts +3 -2
- package/src/http/tasks.ts +9 -0
- package/src/http/users.ts +226 -0
- package/src/http/workflows.ts +3 -2
- package/src/http.ts +1 -0
- package/src/integrations/kapso/client.ts +3 -1
- package/src/jira/app.ts +2 -2
- package/src/kv-view.ts +324 -0
- package/src/linear/app.ts +2 -2
- package/src/oauth/mcp-wrapper.ts +1 -1
- package/src/prompts/memories.ts +42 -1
- package/src/providers/acp-adapter.ts +85 -24
- package/src/providers/acp-target-catalog.ts +54 -1
- package/src/providers/acp-targets.ts +358 -4
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/cursor-adapter.ts +1 -1
- package/src/providers/grok-adapter.ts +186 -0
- package/src/providers/index.ts +5 -1
- package/src/providers/pricing-sources.md +6 -0
- package/src/providers/reasoning-effort.ts +17 -1
- package/src/providers/types.ts +2 -1
- package/src/rbac/can.ts +4 -3
- package/src/rbac/legacy-policy.ts +3 -3
- package/src/rbac/types.ts +6 -0
- package/src/script-workflows/supervisor.ts +11 -2
- package/src/scripts-runtime/eval-harness.ts +2 -12
- package/src/scripts-runtime/executors/quickjs-bundle.ts +152 -0
- package/src/scripts-runtime/executors/quickjs-prelude.ts +285 -0
- package/src/scripts-runtime/executors/quickjs-runner.ts +580 -0
- package/src/scripts-runtime/executors/quickjs-worker.ts +81 -0
- package/src/scripts-runtime/executors/quickjs.ts +196 -0
- package/src/scripts-runtime/executors/registry.ts +2 -0
- package/src/scripts-runtime/executors/sourcemap.ts +79 -0
- package/src/scripts-runtime/script-error-hints.ts +14 -0
- package/src/scripts-runtime/swarm-sdk.ts +9 -2
- package/src/scripts-runtime/types/stdlib.d.ts +27 -8
- package/src/scripts-runtime/types/swarm-extension.d.ts +3 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +27 -8
- package/src/server-user.ts +2 -20
- package/src/server.ts +3 -1
- package/src/slack/app.ts +4 -0
- package/src/slack/config.ts +3 -1
- package/src/slack/egress-scrub.ts +87 -0
- package/src/tests/acp-adapter.test.ts +191 -2
- package/src/tests/boot-scrub-sweep.test.ts +390 -0
- package/src/tests/budget-user-scope.test.ts +7 -21
- package/src/tests/check-pr-body.test.ts +149 -6
- package/src/tests/check-text-columns.test.ts +145 -0
- package/src/tests/claude-adapter-binary.test.ts +1 -1
- package/src/tests/claude-sdk-process.fixture.ts +13 -2
- package/src/tests/codex-oauth-device.test.ts +23 -37
- package/src/tests/codex-oauth-keep-warm.test.ts +29 -0
- package/src/tests/console-scrub.test.ts +141 -0
- package/src/tests/credential-check.test.ts +90 -0
- package/src/tests/db-query-denylist.test.ts +202 -0
- package/src/tests/db-task-read-contract.test.ts +1 -0
- package/src/tests/db-task-write-contract.test.ts +1 -0
- package/src/tests/docs-index-footprint.test.js +3 -3
- package/src/tests/entrypoint-acp-target-binary.test.ts +44 -1
- package/src/tests/events-scrub.test.ts +88 -0
- package/src/tests/explicit-model-validation.test.ts +81 -0
- package/src/tests/extensions-pre-task.test.ts +33 -1
- package/src/tests/fixtures/grok/acp-updates-recorded.jsonl +53 -0
- package/src/tests/fixtures/grok/prompt-response-meta.json +36 -0
- package/src/tests/fixtures/secret-corpus.ts +320 -0
- package/src/tests/grok-adapter.test.ts +647 -0
- package/src/tests/heartbeat-checklist.test.ts +44 -0
- package/src/tests/integration-disable-flags.test.ts +156 -0
- package/src/tests/kv-http.test.ts +34 -0
- package/src/tests/kv-tool.test.ts +274 -1
- package/src/tests/kv-view.test.ts +147 -0
- package/src/tests/mcp-user-route.test.ts +257 -15
- package/src/tests/memory-injection-outcome.test.ts +143 -0
- package/src/tests/model-tier-resolution.test.ts +76 -3
- package/src/tests/page-guest-session.test.ts +424 -0
- package/src/tests/page-proxy.test.ts +1 -1
- package/src/tests/page-viewer-auth.test.ts +22 -1
- package/src/tests/pr-body-leaks.test.ts +157 -2
- package/src/tests/preload.ts +10 -0
- package/src/tests/provider-registration.test.ts +3 -1
- package/src/tests/rbac-audit.test.ts +32 -0
- package/src/tests/rbac-mcp-admission-e2e.test.ts +4 -62
- package/src/tests/script-connections.test.ts +9 -0
- package/src/tests/script-executor-conformance.test.ts +3 -0
- package/src/tests/script-executor-quickjs.test.ts +442 -0
- package/src/tests/script-executor-registry.test.ts +6 -1
- package/src/tests/script-runs-http.test.ts +126 -1
- package/src/tests/script-runs-scrub.test.ts +275 -0
- package/src/tests/scripts-http.test.ts +82 -1
- package/src/tests/scripts-mcp-e2e.test.ts +42 -6
- package/src/tests/scripts-runtime.test.ts +37 -0
- package/src/tests/scrubbed-text-12e.test.ts +289 -0
- package/src/tests/sealed-key-guard.test.ts +166 -0
- package/src/tests/secret-canary-sweep.test.ts +958 -0
- package/src/tests/secret-registry.test.ts +355 -0
- package/src/tests/secret-rules.test.ts +230 -0
- package/src/tests/secret-scrubber.test.ts +317 -1
- package/src/tests/session-costs-golden.test.ts +17 -0
- package/src/tests/slack-egress-scrub.test.ts +216 -0
- package/src/tests/swarm-tool-result-gate.test.ts +143 -30
- package/src/tests/task-tools-ctx.test.ts +3 -0
- package/src/tests/user-token-routes.test.ts +342 -3
- package/src/tests/workflow-replay.test.ts +518 -0
- package/src/tests/workflow-scrub.test.ts +242 -0
- package/src/tests/workflow-system-one-decision-providers.test.ts +737 -0
- package/src/tools/db-query.ts +1 -1
- package/src/tools/inject-learning.ts +1 -1
- package/src/tools/kv/kv-get.ts +136 -6
- package/src/tools/send-task.ts +61 -17
- package/src/tools/utils.ts +186 -43
- package/src/tools/workflows/get-workflow-run.ts +3 -2
- package/src/types.ts +16 -1
- package/src/utils/console-scrub-install.ts +5 -0
- package/src/utils/console-scrub.ts +57 -0
- package/src/utils/credentials.ts +8 -0
- package/src/utils/page-session.ts +6 -2
- package/src/utils/pr-body-leaks.ts +149 -10
- package/src/utils/request-auth-context.ts +2 -0
- package/src/utils/secret-rules.generated.ts +2055 -0
- package/src/utils/secret-scrubber.ts +662 -36
- package/src/workflows/engine.ts +39 -4
- package/src/workflows/executors/notify.ts +6 -1
- package/src/workflows/executors/system-one-decision.ts +31 -12
- package/src/workflows/executors/system-one-openai-wire.ts +168 -0
- package/src/workflows/executors/system-one-providers.ts +112 -11
- package/src/workflows/executors/system-one-wire.ts +79 -0
- package/src/workflows/recovery.ts +10 -1
- package/src/workflows/resume.ts +62 -3
- package/src/workflows/retry-poller.ts +3 -0
- package/templates/ai-toolbox.manifest.json +2 -2
- package/templates/skills/agent-browser/SKILL.md +5 -5
- package/templates/skills/agent-browser/config.json +1 -1
- package/templates/skills/agent-browser/content.md +4 -4
- package/templates/skills/artifacts/content.md +17 -7
- package/templates/skills/jira-interaction/config.json +1 -1
- package/templates/skills/jira-interaction/content.md +56 -205
- package/templates/skills/linear-interaction/config.json +1 -1
- package/templates/skills/linear-interaction/content.md +31 -50
- package/templates/skills/pages/content.md +8 -4
- package/templates/skills/qa/content.md +2 -2
- package/templates/skills/workflow-iterate/content.md +4 -4
- package/tsconfig.json +3 -3
- package/dist/boot-scrub-logs-t8xg7wd7.js +0 -99
- package/dist/cli-0a7r5331.js +0 -168
- package/dist/cli-93rxw9yd.js +0 -951
- package/dist/cli-cvk6yzw5.js +0 -40
- package/dist/cli-dgnj0zcc.js +0 -40
- package/dist/cli-qt5wzq6v.js +0 -256
- package/dist/cli-v7fqehqd.js +0 -9281
- package/dist/cli-vbr859rz.js +0 -572
- 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 {
|
|
3
|
-
import
|
|
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
|
-
|
|
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:
|
|
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.
|
|
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.
|
|
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
|
+
}
|
package/src/providers/index.ts
CHANGED
|
@@ -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 =
|
|
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;
|