@desplega.ai/agent-swarm 1.173.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{acp-adapter-ccm9xrep.js → acp-adapter-sj2g0m84.js} +17 -16
- package/dist/{actions-sgeyv08k.js → actions-fegk44gr.js} +39 -34
- package/dist/{amp-adapter-gpxp3kc9.js → amp-adapter-ad8ft89x.js} +8 -7
- package/dist/{app-km9qnv2f.js → app-kfvgbvzy.js} +18 -14
- package/dist/{assistant-5r8tcvvd.js → assistant-z7zdy9ja.js} +40 -35
- package/dist/{boot-reembed-swwzdgd7.js → boot-reembed-5c2z0mbe.js} +10 -9
- package/dist/{boot-reembed-16rv9843.js → boot-reembed-j8xa181e.js} +10 -9
- package/dist/{boot-scrub-sweep-zkr6vkqb.js → boot-scrub-sweep-hnkn3rgt.js} +11 -10
- package/dist/{boot-seeds-d8sp49t2.js → boot-seeds-kwjq4d2z.js} +14 -13
- package/dist/{claude-adapter-yzzzh8jp.js → claude-adapter-9vmvpvgd.js} +19 -18
- package/dist/{claude-managed-adapter-xgxhqrt2.js → claude-managed-adapter-k9qae05j.js} +7 -5
- package/dist/{claude-managed-setup-bnmjarjc.js → claude-managed-setup-q54zx5q2.js} +6 -4
- package/dist/{claude-sdk-session-4whxere4.js → claude-sdk-session-9jye6cxr.js} +11881 -11472
- package/dist/{cli-94b48jhk.js → cli-03gedhng.js} +16 -14
- package/dist/{cli-1w0vj3gw.js → cli-1e4b6bzh.js} +7 -7
- package/dist/{cli-d603qf86.js → cli-29jc5q7w.js} +10 -8
- package/dist/cli-2m1ktfzz.js +17795 -0
- package/dist/{cli-x119kdba.js → cli-3dj8k4vy.js} +1 -1
- package/dist/{cli-srqq3gc5.js → cli-3fbmmawm.js} +5 -5
- package/dist/{cli-gmdajfw5.js → cli-4jxm2qtz.js} +4987 -1708
- package/dist/{cli-tadfgd0t.js → cli-5hkm7whv.js} +33 -4
- package/dist/{cli-42qhyvwx.js → cli-5t4f29ez.js} +3 -3
- package/dist/{cli-7a0sde2x.js → cli-6cagzc96.js} +9 -0
- package/dist/cli-6cvfxs8r.js +47 -0
- package/dist/{cli-0kxf81jj.js → cli-6jyj59nb.js} +2 -2
- package/dist/{cli-w1apjecb.js → cli-6razj6t4.js} +2 -2
- package/dist/{cli-anzdk0z4.js → cli-74ahz61x.js} +2 -2
- package/dist/{cli-m89t9jdf.js → cli-7x0g4sn1.js} +15416 -16024
- package/dist/{cli-yatxqynm.js → cli-9ha6hs2d.js} +1 -290
- package/dist/{cli-81r1wjyv.js → cli-9nfx44eh.js} +1 -1
- package/dist/cli-9traj3dn.js +638 -0
- package/dist/{cli-smcrrmhy.js → cli-agzebbd5.js} +9 -9
- package/dist/{cli-na2pxe4f.js → cli-apx87jne.js} +2 -2
- package/dist/{cli-rfz2ve55.js → cli-bzszjf2p.js} +2855 -862
- package/dist/{cli-c5km5znr.js → cli-c8r3kzzb.js} +1 -1
- package/dist/{cli-hsem16hb.js → cli-cgtksawx.js} +7 -4
- package/dist/{cli-db3m6zdt.js → cli-d2qr7aj4.js} +1 -1
- package/dist/{cli-rfs4vddv.js → cli-d5f9ed5c.js} +5 -5
- package/dist/cli-e3g5mbkz.js +776 -0
- package/dist/{cli-7jykv7ef.js → cli-e6mpjhed.js} +1 -1
- package/dist/{cli-7a93cv8c.js → cli-ee9gtz70.js} +98 -23
- package/dist/{cli-3nxdmjrc.js → cli-eejsgvm4.js} +4 -4
- package/dist/{cli-fmhj1ehj.js → cli-ef1hg1wv.js} +470 -51
- package/dist/cli-f527ce74.js +781 -0
- package/dist/{cli-ppy4nnnh.js → cli-f7kr7svz.js} +2 -2
- package/dist/cli-hjwmhcg1.js +10622 -0
- package/dist/{cli-2bw3w47j.js → cli-hnhtk6cj.js} +1 -1
- package/dist/{cli-ywm1azg9.js → cli-hnmhtzvh.js} +6 -4
- package/dist/{cli-h7k0xtry.js → cli-hvqbtna4.js} +4 -1256
- package/dist/{cli-s8z6x4b4.js → cli-j0pfptdp.js} +218 -102
- package/dist/cli-jta2eyzc.js +619 -0
- package/dist/{cli-5er5gjbm.js → cli-k382tshb.js} +10 -11
- package/dist/{cli-v3zhnxsb.js → cli-k3dp5tpf.js} +2 -2
- package/dist/{cli-wd0hz6ah.js → cli-kbrt8gqa.js} +9 -91
- package/dist/cli-kceah1eh.js +92 -0
- package/dist/{cli-ne7smj1b.js → cli-ke896fcf.js} +10 -10
- package/dist/{cli-xc7p7s3g.js → cli-kt6zqg0e.js} +6 -6
- package/dist/{cli-wrh08zjd.js → cli-m50t4309.js} +5 -18
- package/dist/cli-n2wq4j7m.js +6985 -0
- package/dist/{cli-bv2etw2y.js → cli-n3hzg89b.js} +46 -15
- package/dist/{cli-tmhsxhb8.js → cli-n5vwg842.js} +23 -6
- package/dist/{cli-qm1448f9.js → cli-ne236afy.js} +8 -8
- package/dist/{cli-d161g69q.js → cli-p1x85d2t.js} +50 -2
- package/dist/{cli-7pz3b7x5.js → cli-przpmdgr.js} +25 -0
- package/dist/{cli-0a7mm3zp.js → cli-q0ke1yww.js} +25 -21
- package/dist/{cli-e3pmyt39.js → cli-q50942rs.js} +1 -1
- package/dist/{cli-r44t9wqc.js → cli-r106725k.js} +2 -2
- package/dist/{cli-phcqp1b9.js → cli-r2pnvj8g.js} +2 -2
- package/dist/{cli-jpc4r224.js → cli-r62a35ea.js} +19 -3
- package/dist/{cli-cd0hv9nh.js → cli-ra0xzggj.js} +2 -2
- package/dist/cli-rhs9pz33.js +294 -0
- package/dist/{cli-r4fqfpwf.js → cli-s2sp957d.js} +50 -27
- package/dist/{cli-1x7da0de.js → cli-s4setq91.js} +8 -8
- package/dist/{cli-6thk4jz3.js → cli-se8w6wny.js} +6 -6
- package/dist/cli-skxvvpap.js +10140 -0
- package/dist/{cli-014vns2y.js → cli-stfs0k5s.js} +208 -525
- package/dist/{cli-0p3enk35.js → cli-tqaazemc.js} +9 -1
- package/dist/{cli-nwrdx840.js → cli-tr4sxp68.js} +3 -3
- package/dist/{cli-r0byd61h.js → cli-tsde1hdr.js} +15 -15
- package/dist/{cli-b07hr3bt.js → cli-txqb9s38.js} +2 -2
- package/dist/{cli-6r2f7edk.js → cli-v1yb0rqb.js} +6 -6
- package/dist/{cli-saqyage0.js → cli-vatyhshy.js} +179 -27
- package/dist/{cli-5wpw4rab.js → cli-vdach5a9.js} +5 -5
- package/dist/{cli-7avy4v8w.js → cli-w1q7h2r2.js} +5 -3
- package/dist/{cli-rcp5aqdw.js → cli-w7m81tb7.js} +1 -1
- package/dist/{cli-c3rk1v0x.js → cli-wayw93nt.js} +3 -3
- package/dist/cli-wmgrkscj.js +23254 -0
- package/dist/{cli-9xf7fdtp.js → cli-z5jjpaxw.js} +29 -6
- package/dist/{cli-451ks6aw.js → cli-zkpks3r3.js} +1 -1
- package/dist/cli.js +29 -45
- package/dist/{codex-adapter-r3dn4fp5.js → codex-adapter-y81sssk0.js} +15 -14
- package/dist/{codex-login-841kv5fk.js → codex-login-f2rcfpp9.js} +3 -2
- package/dist/{codex-session-runner-fwt793h4.js → codex-session-runner-8vx4j6a9.js} +15 -14
- package/dist/{commands-z8fq9qna.js → commands-kcpvpcrr.js} +11 -10
- package/dist/{controller-hq5ggpcp.js → controller-ywrmt6yd.js} +5 -4
- package/dist/{controllers-connect-3mf2btyj.js → controllers-connect-kwpvj3rf.js} +42 -37
- package/dist/{cursor-adapter-q8pgv5qa.js → cursor-adapter-cehv1qns.js} +7 -6
- package/dist/{db-yyhqqrbf.js → db-k8ankj20.js} +12 -11
- package/dist/{db-qfpq4bt7.js → db-ww5d7jq3.js} +28 -9
- package/dist/{devin-adapter-xgtj680j.js → devin-adapter-x1022qb1.js} +3 -2
- package/dist/{dispatcher-x0x7qfcq.js → dispatcher-e8gq5m68.js} +38 -33
- package/dist/{docker-f503k9p0.js → docker-ff6y1190.js} +3 -2
- package/dist/{dsh-adapter-34etvb5m.js → dsh-adapter-3epjm3af.js} +7 -6
- package/dist/{e2b-66xra4az.js → e2b-8ygwhg0f.js} +4 -3
- package/dist/{e2b-xwhyj8cw.js → e2b-swhnpw3n.js} +4 -3
- package/dist/{event-bus-vjtv82gm.js → event-bus-6axnngdj.js} +3 -2
- package/dist/{external-channel-h1fjecwe.js → external-channel-h3359cwn.js} +11 -10
- package/dist/{google-generative-ai-szdxbpqg.js → google-generative-ai-g8db7x0f.js} +4 -2
- package/dist/{google-vertex-qzw3p36d.js → google-vertex-r2692q0r.js} +4 -2
- package/dist/{grok-adapter-e7mr287e.js → grok-adapter-0d4x7gkx.js} +18 -17
- package/dist/{handlers-znb9n7d4.js → handlers-an1gzv21.js} +41 -36
- package/dist/{heartbeat-bm4qkn8d.js → heartbeat-6dakeb61.js} +43 -38
- package/dist/{hook-254r4mza.js → hook-6v6cv6d5.js} +11 -23
- package/dist/{hook-xaa3q80s.js → hook-sa5xye4y.js} +12 -11
- package/dist/{http-3dgrgp8y.js → http-r7f3eky0.js} +1499 -251
- package/dist/index-52j1aja4.js +3465 -0
- package/dist/{index-fx3jqq1e.js → index-7ep4gzdk.js} +38 -33
- package/dist/index-9gw58n4k.js +193 -0
- package/dist/index-9j87mcm8.js +56305 -0
- package/dist/index-bsmcresk.js +79424 -0
- package/dist/index-mh85qhec.js +475 -0
- package/dist/{index-g2byveez.js → index-rmpw9a8q.js} +38 -33
- package/dist/{index-2ybk7khy.js → index-s2wxgzr0.js} +468 -683
- package/dist/{index-bdsqa799.js → index-v1v6n7hg.js} +43 -38
- package/dist/index-we27kd31.js +12810 -0
- package/dist/{index-jd7sejm2.js → index-zk96aq1k.js} +10 -9
- package/dist/{keepalive-6ywhpja1.js → keepalive-be9vwy5h.js} +12 -11
- package/dist/{lead-cw45n21s.js → lead-0861cwrf.js} +61 -56
- package/dist/{maintenance-ss1a1ge9.js → maintenance-mzzgc8bn.js} +10 -9
- package/dist/{node-fkajaq96.js → node-mn1zs7an.js} +2 -1
- package/dist/{oauth-refresh-sweep-3c6f8vyy.js → oauth-refresh-sweep-cpdnfwdj.js} +11 -10
- package/dist/{onboard-8hzdhc1k.js → onboard-fh8qs4r3.js} +11 -3
- package/dist/{opencode-adapter-qbchbcfz.js → opencode-adapter-nm42wwj8.js} +18 -13
- package/dist/{otel-impl-e6mnvc4v.js → otel-impl-x6c1c9g4.js} +242 -868
- package/dist/{pi-mono-adapter-3c9g9snh.js → pi-mono-adapter-9n2ya240.js} +244 -106
- package/dist/{pricing-refresh-gedkjdw0.js → pricing-refresh-qb0rv0ap.js} +12 -11
- package/dist/{rbac-roles-dt4vh1yy.js → rbac-roles-1gzdwbh1.js} +11 -10
- package/dist/{rbac-roles-eszdg6pm.js → rbac-roles-3mraey52.js} +12 -11
- package/dist/{render-v2-907cy6sa.js → render-v2-agpa45zz.js} +18 -14
- package/dist/{resume-42stcaz0.js → resume-x0ydy9s9.js} +25 -21
- package/dist/{runtime-qfry88v2.js → runtime-sa2vr16g.js} +2 -2
- package/dist/{setup-adbc1c10.js → setup-as9ga0d6.js} +2 -2
- package/dist/{virtual-modules-rtmt494q.js → virtual-modules-9gkxqrap.js} +6 -5
- package/dist/{work-objects-wpj8ck95.js → work-objects-y5g1y7ev.js} +41 -36
- package/dist/{worker-bzdra9rf.js → worker-860c3e38.js} +61 -56
- package/dist/{x-qfmygqqv.js → x-m477fr0h.js} +3 -2
- package/openapi.json +523 -344
- package/package.json +10 -11
- package/src/be/config-refs.ts +60 -0
- package/src/be/db/page-versions.ts +15 -0
- package/src/be/db/runtime.ts +35 -8
- package/src/be/db/skill-name-resolution.ts +36 -0
- package/src/be/db/tasks/read.ts +230 -0
- package/src/be/db/tasks/write.ts +7 -0
- package/src/be/db.ts +52 -7
- package/src/be/migrations/215_swarm_config_export_to_env.sql +34 -0
- package/src/be/migrations/216_task_resolved_effort.sql +5 -0
- package/src/be/migrations/217_chat_sdk_state.sql +104 -0
- package/src/be/model-catalog-store.ts +5 -1
- package/src/be/model-tier-resolution.ts +12 -1
- package/src/be/model-validation.ts +5 -2
- package/src/be/models-catalog.ts +26 -1
- package/src/be/modelsdev-cache.json +198 -63
- package/src/be/modelsdev-cache.ts +9 -1
- package/src/be/script-credential-broker.ts +39 -2
- package/src/be/scripts/run-saved.ts +1 -0
- package/src/be/scripts/typecheck.ts +10 -8
- package/src/be/seed/runner.ts +13 -1
- package/src/be/seed/state-db.ts +5 -0
- package/src/be/seed/types.ts +6 -0
- package/src/be/seed-pricing.ts +19 -2
- package/src/be/seed-scripts/catalog/ops-catalog-audit.inline.ts +1 -1
- package/src/be/seed-scripts/catalog/ops-catalog-audit.ts +1 -1
- package/src/be/seed-skills/bundled-files.generated.json +0 -18
- package/src/be/seed-skills/index.ts +104 -3
- package/src/be/swarm-config-guard.ts +52 -0
- package/src/cli.tsx +0 -16
- package/src/commands/onboard/non-interactive.ts +4 -2
- package/src/commands/provider-credentials.ts +52 -6
- package/src/commands/runner.ts +190 -54
- package/src/e2b/env.ts +5 -0
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/extensions/contract.ts +45 -0
- package/src/extensions/dispatcher.ts +7 -0
- package/src/extensions/post-bridge.ts +40 -0
- package/src/github/handlers.ts +11 -4
- package/src/heartbeat/templates.ts +8 -5
- package/src/hooks/hook.ts +0 -26
- package/src/http/all-routes.ts +0 -1
- package/src/http/config.ts +36 -17
- package/src/http/core.ts +4 -0
- package/src/http/index.ts +14 -2
- package/src/http/integrations.ts +88 -1
- package/src/http/mcp.ts +15 -5
- package/src/http/page-proxy.ts +2 -4
- package/src/http/pages-public.ts +3 -3
- package/src/http/pages.ts +3 -13
- package/src/http/poll.ts +28 -68
- package/src/http/requested-by.ts +54 -0
- package/src/http/scripts.ts +1 -0
- package/src/http/stats.ts +1 -30
- package/src/http/tasks.ts +61 -5
- package/src/http/utils.ts +14 -0
- package/src/http/webhooks.ts +159 -1
- package/src/http/x.ts +1 -0
- package/src/integrations/chat-sdk/adapters.ts +129 -0
- package/src/integrations/chat-sdk/attachments.ts +232 -0
- package/src/integrations/chat-sdk/background.ts +36 -0
- package/src/integrations/chat-sdk/commands.ts +194 -0
- package/src/integrations/chat-sdk/config.ts +198 -0
- package/src/integrations/chat-sdk/discord-api.ts +63 -0
- package/src/integrations/chat-sdk/discord-gateway.ts +185 -0
- package/src/integrations/chat-sdk/handlers.ts +28 -0
- package/src/integrations/chat-sdk/inbound.ts +426 -0
- package/src/integrations/chat-sdk/limits.ts +48 -0
- package/src/integrations/chat-sdk/logger.ts +81 -0
- package/src/integrations/chat-sdk/outbound.ts +123 -0
- package/src/integrations/chat-sdk/relay.ts +172 -0
- package/src/integrations/chat-sdk/router.ts +169 -0
- package/src/integrations/chat-sdk/runtime.ts +351 -0
- package/src/integrations/chat-sdk/schema.ts +245 -0
- package/src/integrations/chat-sdk/state.ts +243 -0
- package/src/integrations/chat-sdk/status.ts +244 -0
- package/src/integrations/chat-sdk/steering.ts +58 -0
- package/src/integrations/chat-sdk/teams-origin.ts +77 -0
- package/src/integrations/chat-sdk/telegram-api.ts +56 -0
- package/src/integrations/chat-sdk/templates.ts +16 -0
- package/src/integrations/chat-sdk/test-hooks.ts +37 -0
- package/src/integrations/chat-sdk/thread-buffer.ts +100 -0
- package/src/integrations/chat-sdk/typing.ts +65 -0
- package/src/pages/version.ts +4 -5
- package/src/prompts/base-prompt.ts +111 -45
- package/src/providers/acp-target-catalog.ts +6 -0
- package/src/providers/acp-targets.ts +55 -2
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/claude-adapter.ts +1 -1
- package/src/providers/claude-flag-settings.ts +17 -5
- package/src/providers/claude-managed-adapter.ts +1 -1
- package/src/providers/claude-session-events.ts +6 -1
- package/src/providers/dsh-adapter.ts +1 -1
- package/src/providers/opencode-adapter.ts +243 -26
- package/src/providers/opencode-swarm-plugin.ts +31 -0
- package/src/providers/pi-mono-adapter.ts +398 -153
- package/src/providers/pricing-sources.md +33 -2
- package/src/providers/reasoning-effort.ts +27 -0
- package/src/providers/types.ts +6 -2
- package/src/rbac/legacy-policy.ts +8 -2
- package/src/rbac/permissions.ts +21 -0
- package/src/scheduler/scheduler.ts +4 -2
- package/src/scripts-runtime/loader.ts +19 -7
- package/src/scripts-runtime/sdk-allowlist.ts +8 -6
- package/src/scripts-runtime/swarm-sdk.ts +0 -11
- package/src/scripts-runtime/types/stdlib.d.ts +27 -15
- package/src/scripts-runtime/types/swarm-extension.d.ts +63 -1
- package/src/scripts-runtime/types/swarm-sdk.d.ts +27 -15
- package/src/server.ts +52 -18
- package/src/slack/event-dedup.ts +1 -1
- package/src/tasks/additive-buffer.ts +4 -2
- package/src/tasks/context-key.ts +60 -1
- package/src/tasks/model-pin.ts +67 -0
- package/src/tasks/worker-follow-up.ts +4 -31
- package/src/tests/acp-adapter.test.ts +42 -0
- package/src/tests/api-drain-worker-e2e.test.ts +1 -6
- package/src/tests/base-prompt.test.ts +58 -54
- package/src/tests/chat-sdk-commands.test.ts +191 -0
- package/src/tests/chat-sdk-config.test.ts +334 -0
- package/src/tests/chat-sdk-discord-api.test.ts +68 -0
- package/src/tests/chat-sdk-discord-gateway.test.ts +252 -0
- package/src/tests/chat-sdk-discord-webhook.test.ts +126 -0
- package/src/tests/chat-sdk-fields-sync.test.ts +50 -0
- package/src/tests/chat-sdk-harness.ts +146 -0
- package/src/tests/chat-sdk-inbound.test.ts +363 -0
- package/src/tests/chat-sdk-relay.test.ts +470 -0
- package/src/tests/chat-sdk-reload.test.ts +128 -0
- package/src/tests/chat-sdk-shutdown.test.ts +220 -0
- package/src/tests/chat-sdk-slack.test.ts +268 -0
- package/src/tests/chat-sdk-state.test.ts +163 -0
- package/src/tests/chat-sdk-status-route.test.ts +382 -0
- package/src/tests/chat-sdk-teams.test.ts +277 -0
- package/src/tests/chat-sdk-tools.test.ts +520 -0
- package/src/tests/chat-sdk-typing.test.ts +120 -0
- package/src/tests/chat-sdk-webhook-telegram.test.ts +200 -0
- package/src/tests/claude-adapter-binary.test.ts +9 -4
- package/src/tests/claude-flag-settings.test.ts +11 -3
- package/src/tests/claude-managed-adapter.test.ts +1 -1
- package/src/tests/claude-sdk-lifecycle.test.ts +9 -3
- package/src/tests/claude-thinking-tokens.test.ts +57 -0
- package/src/tests/claude-worker-parity.test.ts +2 -7
- package/src/tests/config-env-export-chat-sdk.test.ts +11 -0
- package/src/tests/config-refs.test.ts +66 -0
- package/src/tests/context-key.test.ts +19 -0
- package/src/tests/cors-allowlist.test.ts +25 -0
- package/src/tests/create-page-tool.test.ts +18 -0
- package/src/tests/credential-check.test.ts +139 -0
- package/src/tests/credentials.test.ts +21 -0
- package/src/tests/db-runtime.test.ts +61 -0
- package/src/tests/db-task-read-contract.test.ts +20 -0
- package/src/tests/db-task-write-contract.test.ts +10 -0
- package/src/tests/docs-index-footprint.test.js +3 -3
- package/src/tests/dsh-adapter.test.ts +1 -1
- package/src/tests/enabled-capabilities.test.ts +20 -0
- package/src/tests/entrypoint-api-readiness.test.ts +14 -8
- package/src/tests/entrypoint-config-env-export.test.ts +5 -2
- package/src/tests/entrypoint-export-to-env.test.ts +78 -0
- package/src/tests/explicit-model-validation.test.ts +105 -1
- package/src/tests/extensions-example-route-chat-channel.test.ts +154 -0
- package/src/tests/extensions-post-bridge.test.ts +51 -0
- package/src/tests/fetch-resolved-env.test.ts +128 -0
- package/src/tests/fixtures/chat-sdk/telegram-private-text.json +10 -0
- package/src/tests/fixtures/extensions/route-chat-channel-to-agent/hooks.ts +27 -0
- package/src/tests/fixtures/extensions/route-chat-channel-to-agent/manifest.json +9 -0
- package/src/tests/fixtures/openai-compatible-registry-extension.child.ts +146 -0
- package/src/tests/fixtures/runner-legacy-resume-prompt.ts +122 -0
- package/src/tests/github-handlers.test.ts +35 -0
- package/src/tests/harness-provider-resolution.test.ts +12 -0
- package/src/tests/heartbeat-checklist.test.ts +3 -2
- package/src/tests/hook-runtime-close.test.ts +0 -2
- package/src/tests/http-api-integration.test.ts +0 -25
- package/src/tests/migration-046-budgets.test.ts +17 -16
- package/src/tests/migration-217-chat-sdk-state.test.ts +113 -0
- package/src/tests/model-tier-resolution.test.ts +93 -0
- package/src/tests/models-catalog.test.ts +42 -1
- package/src/tests/openai-compatible-providers.test.ts +134 -0
- package/src/tests/openai-compatible-registry-extension.test.ts +42 -0
- package/src/tests/opencode-adapter.test.ts +216 -2
- package/src/tests/opencode-helmcode-sdk.test.ts +201 -0
- package/src/tests/opencode-shell-env.test.ts +145 -0
- package/src/tests/openrouter-base-url.test.ts +101 -151
- package/src/tests/page-proxy.test.ts +1 -1
- package/src/tests/pages-browser-sdk.test.ts +76 -0
- package/src/tests/pages-feedback.test.ts +1 -1
- package/src/tests/paused-tasks-requested-by.test.ts +102 -0
- package/src/tests/pi-custom-provider-fake-server.test.ts +442 -0
- package/src/tests/pi-mono-adapter.test.ts +385 -5
- package/src/tests/pi-shell-env.test.ts +170 -0
- package/src/tests/prompt-template-remaining.test.ts +35 -0
- package/src/tests/rbac-engine.test.ts +11 -3
- package/src/tests/runner-exit-after-tasks.test.ts +0 -4
- package/src/tests/runner-legacy-resume-prompt.test.ts +64 -0
- package/src/tests/script-connections.test.ts +2 -0
- package/src/tests/scripts-http.test.ts +201 -1
- package/src/tests/scripts-only-gating.test.ts +2 -3
- package/src/tests/scripts-runtime.test.ts +58 -40
- package/src/tests/secret-scrubber.test.ts +86 -0
- package/src/tests/seed-skills-bundled-files.test.ts +45 -53
- package/src/tests/seed-skills-retire.test.ts +182 -0
- package/src/tests/send-task-requester-gate.test.ts +58 -0
- package/src/tests/session-cost-recompute-unit.test.ts +46 -0
- package/src/tests/skill-get-name-resolution.test.ts +137 -0
- package/src/tests/sonnet-5-5-registry.test.ts +9 -1
- package/src/tests/swarm-config-export-to-env.test.ts +267 -0
- package/src/tests/swarm-config-reserved-keys.test.ts +16 -0
- package/src/tests/swarm-diff.test.ts +1 -1
- package/src/tests/task-model-effort-rerun.test.ts +164 -0
- package/src/tests/task-session-prompts.test.ts +132 -0
- package/src/tests/task-tool-manifest.test.ts +14 -0
- package/src/tests/task-tools-ctx.test.ts +2 -0
- package/src/tests/tool-annotations.test.ts +6 -6
- package/src/tests/ui-logs-parser.test.ts +43 -0
- package/src/tests/used-by-route.test.ts +6 -0
- package/src/tests/vercel-ai-gateway.test.ts +149 -0
- package/src/tests/workflow-swarm-script.test.ts +38 -0
- package/src/tests/workflow-wait-filter.test.ts +5 -3
- package/src/tools/chat-access.ts +41 -0
- package/src/tools/chat-open-dm.ts +59 -0
- package/src/tools/chat-post.ts +74 -0
- package/src/tools/chat-read-thread.ts +100 -0
- package/src/tools/chat-reply.ts +75 -0
- package/src/tools/create-page.ts +3 -13
- package/src/tools/model-catalog/index.ts +2 -1
- package/src/tools/register-chat-channel.ts +139 -0
- package/src/tools/send-task.ts +24 -0
- package/src/tools/skills/skill-get.ts +17 -9
- package/src/tools/swarm-config/get-config.ts +2 -1
- package/src/tools/swarm-config/list-config.ts +2 -1
- package/src/tools/swarm-config/set-config.ts +16 -3
- package/src/tools/tool-config.ts +8 -6
- package/src/types.ts +12 -2
- package/src/utils/config-env-export.ts +74 -0
- package/src/utils/credentials.test.ts +92 -0
- package/src/utils/credentials.ts +52 -4
- package/src/utils/harness-provider.ts +15 -6
- package/src/utils/runtime-model-catalog.ts +11 -1
- package/src/utils/secret-scrubber.ts +71 -2
- package/src/utils/task-tool-manifest.ts +19 -2
- package/src/workflows/executors/swarm-script.ts +1 -0
- package/templates/skills/agent-browser/SKILL.md +1 -1
- package/templates/skills/agent-browser/content.md +1 -1
- package/templates/skills/apps/SKILL.md +8 -1
- package/templates/skills/apps/content.md +8 -1
- package/templates/skills/chat-sdk/config.json +14 -0
- package/templates/skills/chat-sdk/content.md +84 -0
- package/templates/skills/composio-gmail/content.md +7 -0
- package/templates/skills/composio-google-docs/content.md +1 -1
- package/templates/skills/email-domain-health/config.json +14 -0
- package/templates/skills/email-domain-health/content.md +131 -0
- package/templates/skills/kv-storage/content.md +2 -2
- package/templates/skills/pages/content.md +8 -10
- package/templates/skills/swarm-extensions/content.md +2 -0
- package/templates/skills/swarm-scripts/SKILL.md +4 -2
- package/templates/skills/swarm-scripts/config.json +1 -1
- package/templates/skills/swarm-scripts/content.md +4 -2
- package/templates/skills/workflow-iterate/content.md +38 -0
- package/dist/artifact-eczsknc3.js +0 -3843
- package/dist/cli-bd957dgb.js +0 -153
- package/dist/cli-q51yprfq.js +0 -1104
- package/dist/cli-t1ze64h3.js +0 -811
- package/dist/cli-tcc4wd5d.js +0 -56471
- package/dist/index-9ch1n761.js +0 -1102
- package/src/artifact-sdk/index.ts +0 -2
- package/src/artifact-sdk/localtunnel.d.ts +0 -20
- package/src/artifact-sdk/port.ts +0 -12
- package/src/artifact-sdk/server.ts +0 -183
- package/src/artifact-sdk/tunnel.ts +0 -19
- package/src/commands/artifact.ts +0 -249
- package/src/http/ecosystem.ts +0 -77
- package/src/tests/artifact-commands.test.ts +0 -92
- package/src/tests/artifact-sdk.test.ts +0 -793
- package/src/tests/register-service-security.test.ts +0 -194
- package/src/tools/list-services.ts +0 -107
- package/src/tools/register-service.ts +0 -177
- package/src/tools/unregister-service.ts +0 -73
- package/src/tools/update-service-status.ts +0 -96
- package/templates/skills/artifacts/config.json +0 -14
- package/templates/skills/artifacts/content.md +0 -344
- package/templates/skills/artifacts/files/examples/approval-flow.ts +0 -34
- package/templates/skills/artifacts/files/examples/hono-dashboard.ts +0 -31
- package/templates/skills/artifacts/files/examples/multi-artifact.ts +0 -20
- package/templates/skills/artifacts/files/examples/static-report.sh +0 -17
- /package/src/{artifact-sdk → pages}/browser-sdk.ts +0 -0
- /package/src/{artifact-sdk → pages}/feedback-overlay.ts +0 -0
package/src/tools/create-page.ts
CHANGED
|
@@ -21,7 +21,8 @@ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
21
21
|
import * as z from "zod";
|
|
22
22
|
import { authorizeAssetKeyWrite } from "@/be/asset-key-auth";
|
|
23
23
|
import { resolveTaskAuditUserId } from "@/be/audit-user";
|
|
24
|
-
import { createPage, getPage, getPageBySlug,
|
|
24
|
+
import { createPage, getPage, getPageBySlug, updatePage } from "@/be/db";
|
|
25
|
+
import { getPageEditCounter } from "@/be/db/page-versions";
|
|
25
26
|
import { snapshotPage } from "@/pages/version";
|
|
26
27
|
import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
|
|
27
28
|
import { AssetKeySchema, PageAuthModeSchema, PageContentTypeSchema } from "@/types";
|
|
@@ -45,17 +46,6 @@ function getAppBaseUrl(): string {
|
|
|
45
46
|
return getAppUrl();
|
|
46
47
|
}
|
|
47
48
|
|
|
48
|
-
/**
|
|
49
|
-
* Edit counter for a page — `MAX(page_versions.version) + 1`. Returned to
|
|
50
|
-
* the agent as `version` so they have a monotonic "this page has been
|
|
51
|
-
* edited N times" signal. Mirrors the value returned by
|
|
52
|
-
* `PUT /api/pages/:id` (see src/http/pages.ts:pageEditCounter).
|
|
53
|
-
*/
|
|
54
|
-
async function pageEditCounter(pageId: string): Promise<number> {
|
|
55
|
-
const versions = await getPageVersions(pageId);
|
|
56
|
-
return versions.length > 0 ? versions[0]!.version + 1 : 1;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
49
|
export const registerCreatePageTool = (server: McpServer) => {
|
|
60
50
|
createToolRegistrar(server)(
|
|
61
51
|
"create_page",
|
|
@@ -217,7 +207,7 @@ export const registerCreatePageTool = (server: McpServer) => {
|
|
|
217
207
|
|
|
218
208
|
const apiUrl = `${getApiBaseUrl()}/p/${id}`;
|
|
219
209
|
const appUrl = `${getAppBaseUrl()}/pages/${id}`;
|
|
220
|
-
const version = await
|
|
210
|
+
const version = await getPageEditCounter(id);
|
|
221
211
|
|
|
222
212
|
return toolOk(`Page "${input.title}" saved (slug=${finalSlug}, version=${version}).`, {
|
|
223
213
|
details: `API: ${apiUrl}\n App: ${appUrl}`,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { openAICompatibleProviderIds } from "@desplega/model-catalog";
|
|
1
2
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
3
|
import * as z from "zod";
|
|
3
4
|
import { getAgentById } from "@/be/db";
|
|
@@ -86,7 +87,7 @@ export const registerModelCatalogOverlayUpsertTool = (server: McpServer) => {
|
|
|
86
87
|
.string()
|
|
87
88
|
.min(1)
|
|
88
89
|
.describe(
|
|
89
|
-
|
|
90
|
+
`models.dev provider id: anthropic, openai, openrouter, amazon-bedrock, opencode, ${openAICompatibleProviderIds().join(", ")}.`,
|
|
90
91
|
),
|
|
91
92
|
modelId: z
|
|
92
93
|
.string()
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
import { getAgentById, getWorkflow } from "@/be/db";
|
|
4
|
+
import { CHAT_SDK_ADAPTER_NAMES } from "@/integrations/chat-sdk/config";
|
|
5
|
+
import { errorMessage } from "@/integrations/chat-sdk/logger";
|
|
6
|
+
import {
|
|
7
|
+
type ChatChannelMapping,
|
|
8
|
+
chatChannelKey,
|
|
9
|
+
deleteChatChannelMapping,
|
|
10
|
+
putChatChannelMapping,
|
|
11
|
+
} from "@/integrations/chat-sdk/router";
|
|
12
|
+
import { can } from "@/rbac";
|
|
13
|
+
import {
|
|
14
|
+
createToolRegistrar,
|
|
15
|
+
type RequestInfo,
|
|
16
|
+
swarmToolOutputSchema,
|
|
17
|
+
toolErr,
|
|
18
|
+
toolOk,
|
|
19
|
+
} from "@/tools/utils";
|
|
20
|
+
|
|
21
|
+
async function canManageChatChannels(requestInfo: RequestInfo): Promise<boolean> {
|
|
22
|
+
const caller = requestInfo.agentId ? await getAgentById(requestInfo.agentId) : null;
|
|
23
|
+
return can({
|
|
24
|
+
principal: {
|
|
25
|
+
kind: "agent",
|
|
26
|
+
agentId: requestInfo.agentId ?? "",
|
|
27
|
+
isLead: caller?.isLead ?? false,
|
|
28
|
+
},
|
|
29
|
+
verb: "integration.chat.manage",
|
|
30
|
+
resource: { kind: "none" },
|
|
31
|
+
source: "mcp",
|
|
32
|
+
}).allow;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const registerRegisterChatChannelTool = (server: McpServer) => {
|
|
36
|
+
createToolRegistrar(server)(
|
|
37
|
+
"register-chat-channel",
|
|
38
|
+
{
|
|
39
|
+
title: "Route a chat channel to an agent or workflow",
|
|
40
|
+
annotations: { idempotentHint: true },
|
|
41
|
+
description:
|
|
42
|
+
"Lead-only. Route every swarm-addressed message from one Chat SDK channel (a Telegram group, Slack channel, Discord channel or Teams chat) to a fixed agent or workflow instead of the lead. Give exactly one of agentId or workflowId. A swarm#<agentId> token in a message still wins over the mapping. Remove it with unregister-chat-channel.",
|
|
43
|
+
inputSchema: z.object({
|
|
44
|
+
adapter: z.enum(CHAT_SDK_ADAPTER_NAMES).describe("Chat SDK adapter name."),
|
|
45
|
+
channelId: z
|
|
46
|
+
.string()
|
|
47
|
+
.min(1)
|
|
48
|
+
.describe("SDK channel id, such as telegram:-100123 or slack:C123 (prefix optional)."),
|
|
49
|
+
agentId: z.string().optional().describe("Agent that receives the channel's tasks."),
|
|
50
|
+
workflowId: z
|
|
51
|
+
.string()
|
|
52
|
+
.optional()
|
|
53
|
+
.describe("Workflow whose webhook trigger receives the channel's messages."),
|
|
54
|
+
name: z.string().max(200).optional().describe("Display name for the mapping."),
|
|
55
|
+
}),
|
|
56
|
+
outputSchema: swarmToolOutputSchema({
|
|
57
|
+
yourAgentId: z.string().optional(),
|
|
58
|
+
key: z.string().optional(),
|
|
59
|
+
mapping: z
|
|
60
|
+
.looseObject({
|
|
61
|
+
agentId: z.string().optional(),
|
|
62
|
+
workflowId: z.string().optional(),
|
|
63
|
+
name: z.string().optional(),
|
|
64
|
+
createdAt: z.string().optional(),
|
|
65
|
+
})
|
|
66
|
+
.optional(),
|
|
67
|
+
}),
|
|
68
|
+
},
|
|
69
|
+
async ({ adapter, channelId, agentId, workflowId, name }, requestInfo) => {
|
|
70
|
+
const data = { yourAgentId: requestInfo.agentId };
|
|
71
|
+
try {
|
|
72
|
+
if (!(await canManageChatChannels(requestInfo))) {
|
|
73
|
+
return toolErr("Permission denied. Only the lead can register a chat channel.", {
|
|
74
|
+
data,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
if (Boolean(agentId) === Boolean(workflowId)) {
|
|
78
|
+
return toolErr("Give exactly one of agentId or workflowId.", { data });
|
|
79
|
+
}
|
|
80
|
+
if (agentId && !(await getAgentById(agentId))) {
|
|
81
|
+
return toolErr(`Agent ${agentId} not found.`, { data });
|
|
82
|
+
}
|
|
83
|
+
if (workflowId && !(await getWorkflow(workflowId))) {
|
|
84
|
+
return toolErr(`Workflow ${workflowId} not found.`, { data });
|
|
85
|
+
}
|
|
86
|
+
const mapping: ChatChannelMapping = {
|
|
87
|
+
...(agentId ? { agentId } : {}),
|
|
88
|
+
...(workflowId ? { workflowId } : {}),
|
|
89
|
+
...(name ? { name } : {}),
|
|
90
|
+
createdAt: new Date().toISOString(),
|
|
91
|
+
};
|
|
92
|
+
const key = await putChatChannelMapping(adapter, channelId, mapping);
|
|
93
|
+
return toolOk(
|
|
94
|
+
`Registered chat channel ${key} -> ${workflowId ? `workflow ${workflowId}` : `agent ${agentId}`}.`,
|
|
95
|
+
{ data: { ...data, key, mapping } },
|
|
96
|
+
);
|
|
97
|
+
} catch (error) {
|
|
98
|
+
return toolErr(errorMessage(error), { data });
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export const registerUnregisterChatChannelTool = (server: McpServer) => {
|
|
105
|
+
createToolRegistrar(server)(
|
|
106
|
+
"unregister-chat-channel",
|
|
107
|
+
{
|
|
108
|
+
title: "Remove a chat channel route",
|
|
109
|
+
annotations: { idempotentHint: true },
|
|
110
|
+
description:
|
|
111
|
+
"Lead-only. Remove the channel-to-agent or channel-to-workflow route that register-chat-channel stored. Messages from the channel go back to the default route (thread owner, then the lead).",
|
|
112
|
+
inputSchema: z.object({
|
|
113
|
+
adapter: z.enum(CHAT_SDK_ADAPTER_NAMES).describe("Chat SDK adapter name."),
|
|
114
|
+
channelId: z.string().min(1).describe("SDK channel id (prefix optional)."),
|
|
115
|
+
}),
|
|
116
|
+
outputSchema: swarmToolOutputSchema({
|
|
117
|
+
yourAgentId: z.string().optional(),
|
|
118
|
+
key: z.string().optional(),
|
|
119
|
+
}),
|
|
120
|
+
},
|
|
121
|
+
async ({ adapter, channelId }, requestInfo) => {
|
|
122
|
+
const data = { yourAgentId: requestInfo.agentId };
|
|
123
|
+
try {
|
|
124
|
+
if (!(await canManageChatChannels(requestInfo))) {
|
|
125
|
+
return toolErr("Permission denied. Only the lead can unregister a chat channel.", {
|
|
126
|
+
data,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
const key = chatChannelKey(adapter, channelId);
|
|
130
|
+
const deleted = await deleteChatChannelMapping(adapter, channelId);
|
|
131
|
+
return deleted
|
|
132
|
+
? toolOk(`Unregistered chat channel ${key}.`, { data: { ...data, key } })
|
|
133
|
+
: toolErr(`No mapping found for chat channel ${key}.`, { data: { ...data, key } });
|
|
134
|
+
} catch (error) {
|
|
135
|
+
return toolErr(errorMessage(error), { data });
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
);
|
|
139
|
+
};
|
package/src/tools/send-task.ts
CHANGED
|
@@ -378,6 +378,30 @@ export async function sendTaskHandler(
|
|
|
378
378
|
const effectiveParentTask = effectiveParentTaskId
|
|
379
379
|
? await getTaskById(effectiveParentTaskId)
|
|
380
380
|
: null;
|
|
381
|
+
// The child inherits the parent's contextKey (its Slack or chat thread) and requester, and
|
|
382
|
+
// thread-scoped tools trust that binding. An agent can only build on a parent it owns or
|
|
383
|
+
// created; otherwise a worker could mint access to a foreign thread.
|
|
384
|
+
if (ctx.kind === "owner" && creatorAgentId && effectiveParentTask) {
|
|
385
|
+
const caller = await getAgentById(creatorAgentId);
|
|
386
|
+
const decision = can({
|
|
387
|
+
principal: { kind: "agent", agentId: creatorAgentId, isLead: caller?.isLead ?? false },
|
|
388
|
+
verb: "task.parent.attach",
|
|
389
|
+
resource: {
|
|
390
|
+
kind: "task",
|
|
391
|
+
taskId: effectiveParentTask.id,
|
|
392
|
+
requestedByUserId: effectiveParentTask.requestedByUserId,
|
|
393
|
+
creatorAgentId: effectiveParentTask.creatorAgentId,
|
|
394
|
+
agentId: effectiveParentTask.agentId,
|
|
395
|
+
},
|
|
396
|
+
source: "mcp",
|
|
397
|
+
});
|
|
398
|
+
if (!decision.allow) {
|
|
399
|
+
return toolErr(
|
|
400
|
+
`You don't have context for parent task ${effectiveParentTask.id}. Only its assignee, its creator, or a lead can create a child task under it.`,
|
|
401
|
+
{ data: { yourAgentId: creatorAgentId } },
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
381
405
|
if (effectiveParentTask?.routingAffinityInvalid) {
|
|
382
406
|
return toolErr("Cannot continue a task with an invalid routing affinity.", {
|
|
383
407
|
data: { yourAgentId: creatorAgentId },
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import * as z from "zod";
|
|
3
3
|
import { getSkillById, getSkillByName } from "@/be/db";
|
|
4
|
+
import { resolveSwarmSkillByName } from "@/be/db/skill-name-resolution";
|
|
4
5
|
import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
|
|
5
6
|
|
|
6
7
|
export const registerSkillGetTool = (server: McpServer) => {
|
|
@@ -10,7 +11,7 @@ export const registerSkillGetTool = (server: McpServer) => {
|
|
|
10
11
|
title: "Get Skill",
|
|
11
12
|
annotations: { destructiveHint: false },
|
|
12
13
|
description:
|
|
13
|
-
"Get full skill content by ID or name. Name resolution checks agent scope first, then swarm, then global.",
|
|
14
|
+
"Get full skill content by ID or name. Name resolution checks agent scope first, then swarm, then global. In swarm scope the ownerless skill wins, then your own, then the only agent-owned one; when several other agents own that name, it errors with their IDs: pass skillId instead.",
|
|
14
15
|
inputSchema: z.object({
|
|
15
16
|
skillId: z.string().optional().describe("Skill ID"),
|
|
16
17
|
name: z.string().optional().describe("Skill name (resolved with precedence)"),
|
|
@@ -29,15 +30,22 @@ export const registerSkillGetTool = (server: McpServer) => {
|
|
|
29
30
|
|
|
30
31
|
if (args.skillId) {
|
|
31
32
|
skill = await getSkillById(args.skillId);
|
|
32
|
-
} else if (args.name && requestInfo.agentId) {
|
|
33
|
-
// Precedence: agent (personal) → swarm → global
|
|
34
|
-
skill =
|
|
35
|
-
(await getSkillByName(args.name, "agent", requestInfo.agentId)) ||
|
|
36
|
-
(await getSkillByName(args.name, "swarm")) ||
|
|
37
|
-
(await getSkillByName(args.name, "global"));
|
|
38
33
|
} else if (args.name) {
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
// Precedence: agent (personal) → swarm → global
|
|
35
|
+
if (requestInfo.agentId) {
|
|
36
|
+
skill = await getSkillByName(args.name, "agent", requestInfo.agentId);
|
|
37
|
+
}
|
|
38
|
+
if (!skill) {
|
|
39
|
+
const swarm = await resolveSwarmSkillByName(args.name, requestInfo.agentId);
|
|
40
|
+
if (swarm.kind === "ambiguous") {
|
|
41
|
+
return toolErr(
|
|
42
|
+
`Skill name "${args.name}" matches ${swarm.skillIds.length} swarm skills owned by other agents: ${swarm.skillIds.join(", ")}. Call skill-get with skillId instead.`,
|
|
43
|
+
{ data: { yourAgentId: requestInfo.agentId } },
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
if (swarm.kind === "found") skill = await getSkillById(swarm.skillId);
|
|
47
|
+
}
|
|
48
|
+
if (!skill) skill = await getSkillByName(args.name, "global");
|
|
41
49
|
}
|
|
42
50
|
|
|
43
51
|
if (!skill) {
|
|
@@ -19,6 +19,7 @@ const configEntryShape = z.looseObject({
|
|
|
19
19
|
createdAt: z.string().optional(),
|
|
20
20
|
lastUpdatedAt: z.string().optional(),
|
|
21
21
|
encrypted: z.boolean().optional(),
|
|
22
|
+
exportToEnv: z.boolean().optional(),
|
|
22
23
|
});
|
|
23
24
|
|
|
24
25
|
export const registerGetConfigTool = (server: McpServer) => {
|
|
@@ -110,7 +111,7 @@ export const registerGetConfigTool = (server: McpServer) => {
|
|
|
110
111
|
: result
|
|
111
112
|
.map(
|
|
112
113
|
(c) =>
|
|
113
|
-
`- ${c.key}=${c.isSecret && !effectiveIncludeSecrets ? "********" : c.value} (scope: ${c.scope}${c.scopeId ? `, scopeId: ${c.scopeId}` : ""})`,
|
|
114
|
+
`- ${c.key}=${c.isSecret && !effectiveIncludeSecrets ? "********" : c.value} (scope: ${c.scope}${c.scopeId ? `, scopeId: ${c.scopeId}` : ""}${c.exportToEnv ? "" : ", not in agent env"})`,
|
|
114
115
|
)
|
|
115
116
|
.join("\n");
|
|
116
117
|
|
|
@@ -19,6 +19,7 @@ const configEntryShape = z.looseObject({
|
|
|
19
19
|
createdAt: z.string().optional(),
|
|
20
20
|
lastUpdatedAt: z.string().optional(),
|
|
21
21
|
encrypted: z.boolean().optional(),
|
|
22
|
+
exportToEnv: z.boolean().optional(),
|
|
22
23
|
});
|
|
23
24
|
|
|
24
25
|
export const registerListConfigTool = (server: McpServer) => {
|
|
@@ -102,7 +103,7 @@ export const registerListConfigTool = (server: McpServer) => {
|
|
|
102
103
|
: result
|
|
103
104
|
.map(
|
|
104
105
|
(c) =>
|
|
105
|
-
`- [${c.scope}${c.scopeId ? `:${c.scopeId}` : ""}] ${c.key}=${c.isSecret && !effectiveIncludeSecrets ? "********" : c.value}${c.description ? ` — ${c.description}` : ""}`,
|
|
106
|
+
`- [${c.scope}${c.scopeId ? `:${c.scopeId}` : ""}] ${c.key}=${c.isSecret && !effectiveIncludeSecrets ? "********" : c.value}${c.exportToEnv ? "" : " [not in agent env]"}${c.description ? ` — ${c.description}` : ""}`,
|
|
106
107
|
)
|
|
107
108
|
.join("\n");
|
|
108
109
|
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
isReservedConfigKey,
|
|
9
9
|
reservedKeyError,
|
|
10
10
|
validateConfigValue,
|
|
11
|
+
validateExportToEnv,
|
|
11
12
|
} from "@/be/swarm-config-guard";
|
|
12
13
|
import { scheduleIntegrationsReload } from "@/http/core";
|
|
13
14
|
import { can } from "@/rbac";
|
|
@@ -26,6 +27,7 @@ const configEntryShape = z.looseObject({
|
|
|
26
27
|
createdAt: z.string().optional(),
|
|
27
28
|
lastUpdatedAt: z.string().optional(),
|
|
28
29
|
encrypted: z.boolean().optional(),
|
|
30
|
+
exportToEnv: z.boolean().optional(),
|
|
29
31
|
});
|
|
30
32
|
|
|
31
33
|
export const registerSetConfigTool = (server: McpServer) => {
|
|
@@ -34,7 +36,7 @@ export const registerSetConfigTool = (server: McpServer) => {
|
|
|
34
36
|
{
|
|
35
37
|
title: "Set Config",
|
|
36
38
|
description:
|
|
37
|
-
"Set or update a swarm configuration value. Upserts by (scope, scopeId, key). Use scope='global' for server-wide settings, 'agent' for agent-specific, or 'repo' for repo-specific. Set isSecret=true to mask the value in API responses.",
|
|
39
|
+
"Set or update a swarm configuration value. Upserts by (scope, scopeId, key). Use scope='global' for server-wide settings, 'agent' for agent-specific, or 'repo' for repo-specific. Set isSecret=true to mask the value in API responses. Set exportToEnv=false to keep the row out of agent process env (agents then reach it through a connection or script credential binding).",
|
|
38
40
|
annotations: { idempotentHint: true },
|
|
39
41
|
|
|
40
42
|
inputSchema: z.object({
|
|
@@ -63,13 +65,22 @@ export const registerSetConfigTool = (server: McpServer) => {
|
|
|
63
65
|
.string()
|
|
64
66
|
.optional()
|
|
65
67
|
.describe("Optional human-readable description of this config entry."),
|
|
68
|
+
exportToEnv: z
|
|
69
|
+
.boolean()
|
|
70
|
+
.optional()
|
|
71
|
+
.describe(
|
|
72
|
+
"Export this row into agent process env (default true on create, false for a few server-side secrets; omitted on update keeps the stored flag). Rejected as true for keys only the API server reads.",
|
|
73
|
+
),
|
|
66
74
|
}),
|
|
67
75
|
outputSchema: swarmToolOutputSchema({
|
|
68
76
|
yourAgentId: z.string().optional(),
|
|
69
77
|
config: configEntryShape.optional(),
|
|
70
78
|
}),
|
|
71
79
|
},
|
|
72
|
-
async (
|
|
80
|
+
async (
|
|
81
|
+
{ scope, scopeId, key, value, isSecret, envPath, description, exportToEnv },
|
|
82
|
+
requestInfo,
|
|
83
|
+
) => {
|
|
73
84
|
if (!requestInfo.agentId) {
|
|
74
85
|
return toolErr('Agent ID not found. Set the "X-Agent-ID" header.');
|
|
75
86
|
}
|
|
@@ -114,7 +125,8 @@ export const registerSetConfigTool = (server: McpServer) => {
|
|
|
114
125
|
});
|
|
115
126
|
}
|
|
116
127
|
|
|
117
|
-
const validationError =
|
|
128
|
+
const validationError =
|
|
129
|
+
validateConfigValue(key, value) ?? validateExportToEnv(key, exportToEnv);
|
|
118
130
|
if (validationError) {
|
|
119
131
|
return toolErr(validationError, { data: { yourAgentId: requestInfo.agentId } });
|
|
120
132
|
}
|
|
@@ -127,6 +139,7 @@ export const registerSetConfigTool = (server: McpServer) => {
|
|
|
127
139
|
isSecret,
|
|
128
140
|
envPath,
|
|
129
141
|
description,
|
|
142
|
+
exportToEnv,
|
|
130
143
|
});
|
|
131
144
|
|
|
132
145
|
if (scope === "global") {
|
package/src/tools/tool-config.ts
CHANGED
|
@@ -72,12 +72,6 @@ export const DEFERRED_TOOLS = new Set([
|
|
|
72
72
|
"retry-workflow-run",
|
|
73
73
|
"cancel-workflow-run",
|
|
74
74
|
|
|
75
|
-
// Services (4)
|
|
76
|
-
"register-service",
|
|
77
|
-
"unregister-service",
|
|
78
|
-
"list-services",
|
|
79
|
-
"update-service-status",
|
|
80
|
-
|
|
81
75
|
// Config (7)
|
|
82
76
|
"set-config",
|
|
83
77
|
"list-config",
|
|
@@ -127,6 +121,14 @@ export const DEFERRED_TOOLS = new Set([
|
|
|
127
121
|
"send-whatsapp-message",
|
|
128
122
|
"reply-whatsapp-message",
|
|
129
123
|
|
|
124
|
+
// Chat SDK (6)
|
|
125
|
+
"chat-reply",
|
|
126
|
+
"chat-post",
|
|
127
|
+
"chat-open-dm",
|
|
128
|
+
"chat-read-thread",
|
|
129
|
+
"register-chat-channel",
|
|
130
|
+
"unregister-chat-channel",
|
|
131
|
+
|
|
130
132
|
// Tracker (7)
|
|
131
133
|
"tracker-status",
|
|
132
134
|
"get-oauth-access-token",
|
package/src/types.ts
CHANGED
|
@@ -391,6 +391,7 @@ export const AgentTaskSourceSchema = z.enum([
|
|
|
391
391
|
"linear",
|
|
392
392
|
"jira",
|
|
393
393
|
"comb",
|
|
394
|
+
"chat",
|
|
394
395
|
]);
|
|
395
396
|
export type AgentTaskSource = z.infer<typeof AgentTaskSourceSchema>;
|
|
396
397
|
|
|
@@ -446,7 +447,7 @@ export const SteeringStatusSchema = z.enum([
|
|
|
446
447
|
]);
|
|
447
448
|
export type SteeringStatus = z.infer<typeof SteeringStatusSchema>;
|
|
448
449
|
|
|
449
|
-
export const SteeringSourceSchema = z.enum(["ui", "mcp", "script", "slack", "api"]);
|
|
450
|
+
export const SteeringSourceSchema = z.enum(["ui", "mcp", "script", "slack", "api", "chat"]);
|
|
450
451
|
export type SteeringSource = z.infer<typeof SteeringSourceSchema>;
|
|
451
452
|
|
|
452
453
|
export const SteeringMessageSchema = z
|
|
@@ -681,6 +682,10 @@ export const AgentTaskSchema = z
|
|
|
681
682
|
resolvedModel: z.string().optional(),
|
|
682
683
|
modelSource: z.string().optional(),
|
|
683
684
|
modelAlias: z.string().optional(),
|
|
685
|
+
// The effort the session ran at (task effort, else the agent's
|
|
686
|
+
// REASONING_EFFORT_OVERRIDE, dropped when the harness/model cannot take
|
|
687
|
+
// it). Reported at session start; unset when the harness default applied.
|
|
688
|
+
resolvedEffort: ReasoningEffortSchema.optional(),
|
|
684
689
|
|
|
685
690
|
// Schedule linking (optional — set when task was created by a schedule).
|
|
686
691
|
// z.string(): also reachable verbatim from the create-task query param.
|
|
@@ -1458,7 +1463,7 @@ export type ChannelType = z.infer<typeof ChannelTypeSchema>;
|
|
|
1458
1463
|
export type Channel = z.infer<typeof ChannelSchema>;
|
|
1459
1464
|
export type ChannelMessage = z.infer<typeof ChannelMessageSchema>;
|
|
1460
1465
|
|
|
1461
|
-
// Service Types (
|
|
1466
|
+
// Service Types (removed PM2 service registry; kept with the services table)
|
|
1462
1467
|
export const ServiceStatusSchema = z.enum(["starting", "healthy", "unhealthy", "stopped"]);
|
|
1463
1468
|
|
|
1464
1469
|
export const ServiceSchema = z
|
|
@@ -1834,6 +1839,10 @@ export const SwarmConfigSchema = z
|
|
|
1834
1839
|
// auto-encrypted during initDb; if that fails, boot aborts before normal API
|
|
1835
1840
|
// reads occur.
|
|
1836
1841
|
encrypted: z.boolean(),
|
|
1842
|
+
// Whether workers export this row into agent process env. False keeps it
|
|
1843
|
+
// server-side (connections, script credential bindings). Always false for
|
|
1844
|
+
// keys only the API server reads (src/utils/config-env-export.ts).
|
|
1845
|
+
exportToEnv: z.boolean(),
|
|
1837
1846
|
})
|
|
1838
1847
|
.openapi("SwarmConfig");
|
|
1839
1848
|
|
|
@@ -2625,6 +2634,7 @@ export type AgentTaskSummary = Pick<
|
|
|
2625
2634
|
| "modelSource"
|
|
2626
2635
|
| "modelAlias"
|
|
2627
2636
|
| "effort"
|
|
2637
|
+
| "resolvedEffort"
|
|
2628
2638
|
| "provider"
|
|
2629
2639
|
| "requestedByUserId"
|
|
2630
2640
|
| "progress"
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a swarm_config row reaches an agent's process environment.
|
|
3
|
+
*
|
|
4
|
+
* Each row carries an `exportToEnv` flag (migration 215). Workers export a row
|
|
5
|
+
* into the harness env, and so into `env`, `printenv` and `/proc/<pid>/environ`
|
|
6
|
+
* of every agent shell, only when the flag is true. Rows with the flag off stay
|
|
7
|
+
* server-side: agents use them through a connection or a script credential
|
|
8
|
+
* binding instead.
|
|
9
|
+
*
|
|
10
|
+
* Keys below are read only by the API server (integration signing secrets, app
|
|
11
|
+
* private keys, OAuth client secrets, the API's own agent-fs key). They can
|
|
12
|
+
* never be exported: the config API rejects `exportToEnv: true` for them and
|
|
13
|
+
* every reader treats a stored true as false.
|
|
14
|
+
*/
|
|
15
|
+
export const ENV_EXPORT_RESTRICTED_KEYS: ReadonlySet<string> = new Set([
|
|
16
|
+
"SLACK_APP_TOKEN",
|
|
17
|
+
"SLACK_SIGNING_SECRET",
|
|
18
|
+
"GITHUB_APP_PRIVATE_KEY",
|
|
19
|
+
"GITHUB_WEBHOOK_SECRET",
|
|
20
|
+
"GITLAB_WEBHOOK_SECRET",
|
|
21
|
+
"LINEAR_CLIENT_SECRET",
|
|
22
|
+
"LINEAR_SIGNING_SECRET",
|
|
23
|
+
"JIRA_CLIENT_SECRET",
|
|
24
|
+
"JIRA_WEBHOOK_TOKEN",
|
|
25
|
+
"AZURE_DEVOPS_WEBHOOK_SECRET",
|
|
26
|
+
"AGENTMAIL_WEBHOOK_SECRET",
|
|
27
|
+
"API_AGENT_FS_API_KEY",
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Server-side secrets an operator may still export per row. A new row for one
|
|
32
|
+
* of these keys starts with the flag off unless the caller sends
|
|
33
|
+
* `exportToEnv: true`. Migration 215 applies the same default to rows that
|
|
34
|
+
* already existed.
|
|
35
|
+
*/
|
|
36
|
+
export const ENV_EXPORT_DEFAULT_OFF_KEYS: ReadonlySet<string> = new Set([
|
|
37
|
+
"KAPSO_WEBHOOK_HMAC_SECRET",
|
|
38
|
+
"SENTRY_TEST_APP_WEBHOOK_SECRET",
|
|
39
|
+
"TELEMETRY_CH_AUTH",
|
|
40
|
+
"HACK_AGENT_FS_ADMIN_KEY",
|
|
41
|
+
"X_CLIENT_SECRET",
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
/** Script connection secrets (`connection.<slug>.secret`) are brokered server-side. */
|
|
45
|
+
const CONNECTION_SECRET_KEY = /^connection\..+\.secret$/;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Key prefixes whose rows start with the flag off: the API server reads them
|
|
49
|
+
* (Chat SDK bot tokens and adapter JSON), agents never need them in a shell.
|
|
50
|
+
*/
|
|
51
|
+
const ENV_EXPORT_DEFAULT_OFF_PREFIXES = ["CHAT_SDK_"];
|
|
52
|
+
|
|
53
|
+
export function isEnvExportRestrictedKey(key: string): boolean {
|
|
54
|
+
return ENV_EXPORT_RESTRICTED_KEYS.has(key.toUpperCase()) || CONNECTION_SECRET_KEY.test(key);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** The flag a new row gets when the caller does not send one. */
|
|
58
|
+
export function defaultExportToEnv(key: string): boolean {
|
|
59
|
+
const upper = key.toUpperCase();
|
|
60
|
+
return (
|
|
61
|
+
!isEnvExportRestrictedKey(key) &&
|
|
62
|
+
!ENV_EXPORT_DEFAULT_OFF_KEYS.has(upper) &&
|
|
63
|
+
!ENV_EXPORT_DEFAULT_OFF_PREFIXES.some((prefix) => upper.startsWith(prefix))
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function envExportRestrictedError(key: string): string {
|
|
68
|
+
return `Key '${key}' is read only by the API server and can never be exported to agent env; store it with exportToEnv: false.`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** The flag readers act on: a stored true never wins for a restricted key. */
|
|
72
|
+
export function effectiveExportToEnv(key: string, stored: boolean): boolean {
|
|
73
|
+
return stored && !isEnvExportRestrictedKey(key);
|
|
74
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import { OPENAI_COMPATIBLE_PROVIDERS } from "@desplega/model-catalog";
|
|
2
3
|
import {
|
|
3
4
|
CREDENTIAL_POOL_VARS,
|
|
5
|
+
deriveProviderFromKeyType,
|
|
4
6
|
getModelAwareCredentialVars,
|
|
5
7
|
resolveCredentialPools,
|
|
6
8
|
selectRandomCredential,
|
|
@@ -123,6 +125,35 @@ describe("resolveCredentialPools", () => {
|
|
|
123
125
|
expect(["example-oauth1", "example-oauth2"]).toContain(env.CLAUDE_CODE_OAUTH_TOKEN!);
|
|
124
126
|
expect(["example-apikey1", "example-apikey2"]).toContain(env.ANTHROPIC_API_KEY!);
|
|
125
127
|
});
|
|
128
|
+
|
|
129
|
+
it("pools NAN_API_KEY alone for an opencode nan/* model", async () => {
|
|
130
|
+
const env: Record<string, string | undefined> = {
|
|
131
|
+
NAN_API_KEY: "example-nan-a,example-nan-b,example-nan-c",
|
|
132
|
+
OPENROUTER_API_KEY: "example-or-a,example-or-b",
|
|
133
|
+
};
|
|
134
|
+
const selections = await resolveCredentialPools(env, {
|
|
135
|
+
provider: "opencode",
|
|
136
|
+
model: "nan/glm5.3-flash",
|
|
137
|
+
});
|
|
138
|
+
expect(["example-nan-a", "example-nan-b", "example-nan-c"]).toContain(env.NAN_API_KEY!);
|
|
139
|
+
expect(selections).toHaveLength(1);
|
|
140
|
+
expect(selections[0]?.keyType).toBe("NAN_API_KEY");
|
|
141
|
+
expect(selections[0]?.total).toBe(3);
|
|
142
|
+
// OpenRouter is not relevant to a nan/* model and stays untouched.
|
|
143
|
+
expect(env.OPENROUTER_API_KEY).toBe("example-or-a,example-or-b");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("pools HELMCODE_API_KEY alone for a pi helmcode/* model", async () => {
|
|
147
|
+
const env: Record<string, string | undefined> = {
|
|
148
|
+
HELMCODE_API_KEY: "example-helm-a,example-helm-b",
|
|
149
|
+
};
|
|
150
|
+
const selections = await resolveCredentialPools(env, {
|
|
151
|
+
provider: "pi",
|
|
152
|
+
model: "helmcode/glm5.3",
|
|
153
|
+
});
|
|
154
|
+
expect(["example-helm-a", "example-helm-b"]).toContain(env.HELMCODE_API_KEY!);
|
|
155
|
+
expect(selections.map((s) => s.keyType)).toEqual(["HELMCODE_API_KEY"]);
|
|
156
|
+
});
|
|
126
157
|
});
|
|
127
158
|
|
|
128
159
|
describe("validateClaudeCredentials", () => {
|
|
@@ -198,6 +229,23 @@ describe("CREDENTIAL_POOL_VARS", () => {
|
|
|
198
229
|
it("contains expected env var names", () => {
|
|
199
230
|
expect(CREDENTIAL_POOL_VARS).toContain("CLAUDE_CODE_OAUTH_TOKEN");
|
|
200
231
|
expect(CREDENTIAL_POOL_VARS).toContain("ANTHROPIC_API_KEY");
|
|
232
|
+
expect(CREDENTIAL_POOL_VARS).toContain("NAN_API_KEY");
|
|
233
|
+
expect(CREDENTIAL_POOL_VARS).toContain("HELMCODE_API_KEY");
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it("pools only the canonical NaN var; the alias is normalized by the runner", () => {
|
|
237
|
+
expect(OPENAI_COMPATIBLE_PROVIDERS.nan.aliasEnvVars).toEqual(["HELMCODE_API_NAN"]);
|
|
238
|
+
expect(CREDENTIAL_POOL_VARS as readonly string[]).not.toContain("HELMCODE_API_NAN");
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
describe("deriveProviderFromKeyType", () => {
|
|
243
|
+
it("tags NaN and Helmcode keys as pi, like OpenRouter", () => {
|
|
244
|
+
for (const keyType of ["NAN_API_KEY", "HELMCODE_API_NAN", "HELMCODE_API_KEY"]) {
|
|
245
|
+
expect(deriveProviderFromKeyType(keyType)).toBe("pi");
|
|
246
|
+
}
|
|
247
|
+
expect(deriveProviderFromKeyType("OPENROUTER_API_KEY")).toBe("pi");
|
|
248
|
+
expect(deriveProviderFromKeyType("SOMETHING_ELSE")).toBe("claude");
|
|
201
249
|
});
|
|
202
250
|
});
|
|
203
251
|
|
|
@@ -266,4 +314,48 @@ describe("getModelAwareCredentialVars", () => {
|
|
|
266
314
|
const vars = getModelAwareCredentialVars("opencode", "anthropic/claude-sonnet-4-20250514");
|
|
267
315
|
expect(vars).not.toContain("OPENAI_API_KEY");
|
|
268
316
|
});
|
|
317
|
+
|
|
318
|
+
it("routes nan/* and helmcode/* to their own key on pi and opencode", () => {
|
|
319
|
+
for (const provider of ["pi", "opencode"]) {
|
|
320
|
+
expect(getModelAwareCredentialVars(provider, "nan/glm5.3-flash")).toEqual(["NAN_API_KEY"]);
|
|
321
|
+
expect(getModelAwareCredentialVars(provider, "helmcode/glm5.3")).toEqual([
|
|
322
|
+
"HELMCODE_API_KEY",
|
|
323
|
+
]);
|
|
324
|
+
// Only the exact prefix routes: a look-alike is any other provider/model.
|
|
325
|
+
const other = getModelAwareCredentialVars(provider, "other/x");
|
|
326
|
+
expect(getModelAwareCredentialVars(provider, "helmcodex/x")).toEqual(other);
|
|
327
|
+
expect(getModelAwareCredentialVars(provider, "nanx/y")).toEqual(other);
|
|
328
|
+
expect(other).not.toContain("NAN_API_KEY");
|
|
329
|
+
expect(other).not.toContain("HELMCODE_API_KEY");
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it("keeps OpenRouter and bare-id routing byte-identical", () => {
|
|
334
|
+
for (const provider of ["pi", "opencode"]) {
|
|
335
|
+
expect(
|
|
336
|
+
getModelAwareCredentialVars(provider, "openrouter/deepseek/deepseek-v4-flash"),
|
|
337
|
+
).toEqual(["OPENROUTER_API_KEY", "ANTHROPIC_API_KEY"]);
|
|
338
|
+
expect(getModelAwareCredentialVars(provider, "openai/gpt-5")).toEqual([
|
|
339
|
+
"OPENROUTER_API_KEY",
|
|
340
|
+
"ANTHROPIC_API_KEY",
|
|
341
|
+
]);
|
|
342
|
+
expect(getModelAwareCredentialVars(provider, "gpt-4o")).toEqual([
|
|
343
|
+
"OPENROUTER_API_KEY",
|
|
344
|
+
"ANTHROPIC_API_KEY",
|
|
345
|
+
"OPENAI_API_KEY",
|
|
346
|
+
]);
|
|
347
|
+
// A NaN model routed through OpenRouter is still an OpenRouter model.
|
|
348
|
+
expect(getModelAwareCredentialVars(provider, "openrouter/nan/glm5.3")).toEqual([
|
|
349
|
+
"OPENROUTER_API_KEY",
|
|
350
|
+
"ANTHROPIC_API_KEY",
|
|
351
|
+
]);
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it("does not apply the nan/helmcode prefix routing to other harnesses", () => {
|
|
356
|
+
expect(getModelAwareCredentialVars("claude", "nan/glm5.3")).toEqual([
|
|
357
|
+
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
358
|
+
"ANTHROPIC_API_KEY",
|
|
359
|
+
]);
|
|
360
|
+
});
|
|
269
361
|
});
|