@desplega.ai/agent-swarm 1.173.0 → 2.1.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-kr4abrsk.js} +17 -16
- package/dist/{actions-sgeyv08k.js → actions-eh4g6w2r.js} +39 -34
- package/dist/{amp-adapter-gpxp3kc9.js → amp-adapter-7h7wdt8q.js} +8 -7
- package/dist/{app-km9qnv2f.js → app-7k8s9w5p.js} +18 -14
- package/dist/{assistant-5r8tcvvd.js → assistant-wswt2y7p.js} +40 -35
- package/dist/{boot-reembed-16rv9843.js → boot-reembed-9apm9qw6.js} +10 -9
- package/dist/{boot-reembed-swwzdgd7.js → boot-reembed-whzbdthj.js} +10 -9
- package/dist/{boot-scrub-sweep-zkr6vkqb.js → boot-scrub-sweep-ndzmvmgt.js} +11 -10
- package/dist/{boot-seeds-d8sp49t2.js → boot-seeds-e9sta0yt.js} +16 -13
- package/dist/{claude-adapter-yzzzh8jp.js → claude-adapter-4mqd3r6r.js} +19 -18
- package/dist/{claude-managed-adapter-xgxhqrt2.js → claude-managed-adapter-9wswb52m.js} +7 -5
- package/dist/{claude-managed-setup-bnmjarjc.js → claude-managed-setup-tpe1effx.js} +6 -4
- package/dist/{claude-sdk-session-4whxere4.js → claude-sdk-session-q49ghmh9.js} +12402 -11850
- package/dist/{cli-ne7smj1b.js → cli-01xvj1rq.js} +10 -10
- package/dist/{cli-nwrdx840.js → cli-035vekcw.js} +3 -3
- package/dist/{cli-9xf7fdtp.js → cli-0pd8hzhc.js} +29 -6
- package/dist/{cli-tmhsxhb8.js → cli-0veb92a3.js} +28 -6
- package/dist/{cli-c3rk1v0x.js → cli-10jesr25.js} +3 -3
- package/dist/{cli-6thk4jz3.js → cli-1y7yywmj.js} +6 -6
- package/dist/{cli-6r2f7edk.js → cli-20krdhz5.js} +47 -19
- package/dist/{cli-rfs4vddv.js → cli-29rja109.js} +39 -20
- package/dist/cli-2m1ktfzz.js +17795 -0
- package/dist/{cli-na2pxe4f.js → cli-2pwa0q4d.js} +15 -4
- package/dist/{cli-7a93cv8c.js → cli-4mengw46.js} +441 -43
- package/dist/{cli-94b48jhk.js → cli-5twdke5x.js} +16 -14
- package/dist/{cli-7a0sde2x.js → cli-6cagzc96.js} +9 -0
- package/dist/cli-6cvfxs8r.js +47 -0
- package/dist/{cli-w1apjecb.js → cli-711vwbxn.js} +2 -2
- package/dist/{cli-r4fqfpwf.js → cli-78vv7446.js} +50 -27
- package/dist/{cli-srqq3gc5.js → cli-7n6s5man.js} +5 -5
- package/dist/{cli-m89t9jdf.js → cli-7x0g4sn1.js} +15416 -16024
- package/dist/{cli-2bw3w47j.js → cli-8wwxw8a3.js} +1 -1
- package/dist/{cli-yatxqynm.js → cli-9ha6hs2d.js} +1 -290
- package/dist/{cli-ywm1azg9.js → cli-9jzdj90z.js} +8 -4
- package/dist/cli-9traj3dn.js +638 -0
- package/dist/{cli-smcrrmhy.js → cli-agzebbd5.js} +9 -9
- package/dist/{cli-c5km5znr.js → cli-c8r3kzzb.js} +1 -1
- package/dist/cli-cvv9k0hh.js +10143 -0
- package/dist/{cli-anzdk0z4.js → cli-d9286m9h.js} +2 -2
- package/dist/cli-e3g5mbkz.js +776 -0
- package/dist/cli-f527ce74.js +781 -0
- package/dist/{cli-gmdajfw5.js → cli-gxzkg1de.js} +5020 -1729
- package/dist/{cli-451ks6aw.js → cli-h1arqn31.js} +1 -1
- package/dist/cli-hjwmhcg1.js +10622 -0
- package/dist/{cli-jpc4r224.js → cli-hqr88ew6.js} +19 -3
- package/dist/{cli-h7k0xtry.js → cli-hvqbtna4.js} +4 -1256
- package/dist/{cli-cd0hv9nh.js → cli-j67mh2vg.js} +2 -2
- package/dist/cli-jta2eyzc.js +619 -0
- package/dist/{cli-wd0hz6ah.js → cli-kbrt8gqa.js} +9 -91
- package/dist/cli-kceah1eh.js +92 -0
- package/dist/{cli-d603qf86.js → cli-ksphgh48.js} +10 -8
- package/dist/{cli-3nxdmjrc.js → cli-mgz4nbgg.js} +4 -4
- package/dist/{cli-saqyage0.js → cli-mz5as5ha.js} +179 -27
- package/dist/cli-n2wq4j7m.js +6985 -0
- package/dist/{cli-ppy4nnnh.js → cli-n8ndw8j7.js} +2 -2
- package/dist/{cli-1w0vj3gw.js → cli-naqzr17j.js} +7 -7
- package/dist/{cli-0a7mm3zp.js → cli-nm1p8jfr.js} +25 -21
- package/dist/{cli-wrh08zjd.js → cli-p3zbc1rw.js} +5 -18
- package/dist/{cli-hsem16hb.js → cli-pe05f8yk.js} +29 -6
- package/dist/{cli-5wpw4rab.js → cli-qmzsbkvy.js} +5 -5
- package/dist/{cli-bv2etw2y.js → cli-qnm4e9e4.js} +99 -590
- package/dist/{cli-1x7da0de.js → cli-qqy4q48b.js} +8 -8
- package/dist/{cli-r44t9wqc.js → cli-r106725k.js} +2 -2
- package/dist/{cli-5er5gjbm.js → cli-r35rw5de.js} +15 -15
- package/dist/{cli-v3zhnxsb.js → cli-r610p9bs.js} +2 -2
- package/dist/{cli-7jykv7ef.js → cli-rc6pf5kv.js} +1 -1
- package/dist/{cli-x119kdba.js → cli-rdxm77k3.js} +1 -1
- package/dist/cli-rhs9pz33.js +294 -0
- package/dist/{cli-0p3enk35.js → cli-s69nfyeh.js} +9 -1
- package/dist/{cli-0kxf81jj.js → cli-sawv63e8.js} +2 -2
- package/dist/{cli-e3pmyt39.js → cli-sg19gbf0.js} +1 -1
- package/dist/{cli-d161g69q.js → cli-shk7r5cs.js} +57 -6
- package/dist/{cli-7pz3b7x5.js → cli-sksetb2q.js} +36 -1
- package/dist/{cli-014vns2y.js → cli-stfs0k5s.js} +208 -525
- package/dist/{cli-db3m6zdt.js → cli-t02z59f2.js} +1 -1
- package/dist/{cli-b07hr3bt.js → cli-txqb9s38.js} +2 -2
- package/dist/{cli-s8z6x4b4.js → cli-v7g551te.js} +225 -113
- package/dist/{cli-rfz2ve55.js → cli-vgqzp4em.js} +3341 -955
- package/dist/{cli-7avy4v8w.js → cli-w1q7h2r2.js} +5 -3
- package/dist/{cli-rcp5aqdw.js → cli-w7m81tb7.js} +1 -1
- package/dist/cli-wfnemr1t.js +86 -0
- package/dist/cli-wmgrkscj.js +23254 -0
- package/dist/{cli-qm1448f9.js → cli-wms6x9db.js} +8 -8
- package/dist/{cli-tadfgd0t.js → cli-x1r545x8.js} +33 -4
- package/dist/{cli-xc7p7s3g.js → cli-xczsgshn.js} +6 -6
- package/dist/{cli-fmhj1ehj.js → cli-y3167wt1.js} +882 -271
- package/dist/{cli-r0byd61h.js → cli-yjmqpxt3.js} +17 -16
- package/dist/{cli-81r1wjyv.js → cli-z780ec7c.js} +1 -1
- package/dist/{cli-phcqp1b9.js → cli-zm9t2vde.js} +2 -2
- package/dist/cli.js +34 -49
- package/dist/{codex-adapter-r3dn4fp5.js → codex-adapter-4z16v9n0.js} +15 -14
- package/dist/{codex-login-841kv5fk.js → codex-login-pkepy2j1.js} +3 -2
- package/dist/{codex-session-runner-fwt793h4.js → codex-session-runner-rvms9y2x.js} +15 -14
- package/dist/{commands-z8fq9qna.js → commands-2a9rtyat.js} +11 -10
- package/dist/{controller-hq5ggpcp.js → controller-3k3madbn.js} +5 -4
- package/dist/{controllers-connect-3mf2btyj.js → controllers-connect-v9yn8zb9.js} +42 -37
- package/dist/{cursor-adapter-q8pgv5qa.js → cursor-adapter-98gbhyzn.js} +7 -6
- package/dist/{db-qfpq4bt7.js → db-eywwsnjw.js} +30 -9
- package/dist/{db-yyhqqrbf.js → db-ncypk9np.js} +12 -11
- package/dist/{devin-adapter-xgtj680j.js → devin-adapter-zeq8b9gz.js} +3 -2
- package/dist/{dispatcher-x0x7qfcq.js → dispatcher-w94kc575.js} +38 -33
- package/dist/{docker-f503k9p0.js → docker-b1e4cqab.js} +3 -2
- package/dist/{dsh-adapter-34etvb5m.js → dsh-adapter-47ztej36.js} +7 -6
- package/dist/{e2b-66xra4az.js → e2b-bbt8ey45.js} +4 -3
- package/dist/{e2b-xwhyj8cw.js → e2b-dp804sen.js} +4 -3
- package/dist/{event-bus-vjtv82gm.js → event-bus-j2jdg3qb.js} +3 -2
- package/dist/{external-channel-h1fjecwe.js → external-channel-zrkg8ar9.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-s9ncp9vy.js} +18 -17
- package/dist/{handlers-znb9n7d4.js → handlers-kqftvjgw.js} +41 -36
- package/dist/{heartbeat-bm4qkn8d.js → heartbeat-9q96p8v9.js} +43 -38
- package/dist/{hook-254r4mza.js → hook-56qe10dp.js} +11 -23
- package/dist/{hook-xaa3q80s.js → hook-yy6rw42k.js} +12 -11
- package/dist/{http-3dgrgp8y.js → http-r2dnb8f8.js} +2411 -275
- package/dist/{index-fx3jqq1e.js → index-3gtmkhc6.js} +38 -33
- package/dist/{index-bdsqa799.js → index-3k5hkz7r.js} +43 -38
- package/dist/index-52j1aja4.js +3465 -0
- package/dist/{index-g2byveez.js → index-5e101240.js} +38 -33
- package/dist/{index-2ybk7khy.js → index-5wyfjc2c.js} +845 -683
- 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-jd7sejm2.js → index-vxvhxxbn.js} +10 -9
- package/dist/index-we27kd31.js +12810 -0
- package/dist/{keepalive-6ywhpja1.js → keepalive-psj6j54c.js} +12 -11
- package/dist/{lead-cw45n21s.js → lead-hajz4688.js} +61 -56
- package/dist/{maintenance-ss1a1ge9.js → maintenance-ps02sb5m.js} +10 -9
- package/dist/{node-fkajaq96.js → node-mwb5r697.js} +2 -1
- package/dist/{oauth-refresh-sweep-3c6f8vyy.js → oauth-refresh-sweep-0284h93b.js} +11 -10
- package/dist/{onboard-8hzdhc1k.js → onboard-rbf6bg01.js} +11 -3
- package/dist/{opencode-adapter-qbchbcfz.js → opencode-adapter-13dcatgq.js} +18 -13
- package/dist/{otel-impl-e6mnvc4v.js → otel-impl-zvqcbhp7.js} +242 -868
- package/dist/{pi-mono-adapter-3c9g9snh.js → pi-mono-adapter-xxy47q4v.js} +244 -106
- package/dist/{pricing-refresh-gedkjdw0.js → pricing-refresh-kefx9v3t.js} +12 -11
- package/dist/{rbac-roles-dt4vh1yy.js → rbac-roles-3m36rxm2.js} +11 -10
- package/dist/{rbac-roles-eszdg6pm.js → rbac-roles-z76xv0hq.js} +12 -11
- package/dist/{render-v2-907cy6sa.js → render-v2-wz73bs25.js} +18 -14
- package/dist/{resume-42stcaz0.js → resume-k4wzge8w.js} +25 -21
- package/dist/{runtime-qfry88v2.js → runtime-sa2vr16g.js} +2 -2
- package/dist/{setup-adbc1c10.js → setup-2fp2pypp.js} +2 -2
- package/dist/{virtual-modules-rtmt494q.js → virtual-modules-9gkxqrap.js} +6 -5
- package/dist/{work-objects-wpj8ck95.js → work-objects-6k0ywz3m.js} +41 -36
- package/dist/{worker-bzdra9rf.js → worker-5v3rxsdh.js} +61 -56
- package/dist/{x-qfmygqqv.js → x-4mtdeym1.js} +3 -2
- package/openapi.json +1279 -329
- package/package.json +15 -15
- package/src/agentmail/handlers.ts +21 -11
- package/src/apps/definition.ts +16 -0
- package/src/azure-devops/api.ts +52 -2
- package/src/azure-devops/boards-sync.ts +50 -20
- package/src/azure-devops/index.ts +1 -0
- package/src/azure-devops/outbound.ts +95 -0
- package/src/be/boot-seeds.ts +11 -0
- package/src/be/config-refs.ts +60 -0
- package/src/be/db/model-profiles.ts +315 -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 +237 -0
- package/src/be/db/tasks/write.ts +7 -0
- package/src/be/db.ts +87 -36
- 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/migrations/219_relabel_pi_context_compactions.sql +22 -0
- package/src/be/migrations/220_model_profiles.sql +137 -0
- package/src/be/model-catalog-store.ts +5 -1
- package/src/be/model-tier-resolution.ts +155 -10
- package/src/be/model-validation.ts +12 -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 +22 -8
- package/src/be/secret-registry.ts +6 -2
- 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-model-profiles.ts +41 -0
- package/src/be/seed-pricing.ts +19 -2
- package/src/be/seed-scripts/catalog/heartbeat-jev-gate.ts +368 -0
- 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-scripts/index.ts +9 -0
- 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 +53 -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 +193 -71
- package/src/e2b/env.ts +5 -0
- package/src/extensions/catalog.generated.json +17 -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/loader.ts +10 -5
- package/src/extensions/post-bridge.ts +40 -0
- package/src/github/handlers.ts +11 -4
- package/src/heartbeat/heartbeat.ts +2 -1
- package/src/heartbeat/templates.ts +8 -5
- package/src/hooks/hook.ts +0 -26
- package/src/http/agents.ts +49 -2
- package/src/http/all-routes.ts +1 -1
- package/src/http/apps.ts +30 -3
- package/src/http/config.ts +62 -19
- package/src/http/core.ts +8 -0
- package/src/http/index.ts +25 -3
- package/src/http/integrations.ts +88 -1
- package/src/http/mcp.ts +15 -5
- package/src/http/model-profiles.ts +303 -0
- package/src/http/page-proxy.ts +2 -4
- package/src/http/pages-public.ts +13 -7
- package/src/http/pages.ts +3 -13
- package/src/http/poll.ts +42 -71
- package/src/http/requested-by.ts +54 -0
- package/src/http/script-runs.ts +1 -1
- package/src/http/scripts.ts +26 -2
- package/src/http/stats.ts +1 -30
- package/src/http/tasks.ts +79 -5
- package/src/http/utils.ts +14 -0
- package/src/http/webhooks.ts +160 -3
- 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/{artifact-sdk → pages}/feedback-overlay.ts +65 -6
- 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 +56 -3
- 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/pi-mono-extension.ts +17 -3
- package/src/providers/pricing-sources.md +33 -2
- package/src/providers/reasoning-effort.ts +37 -0
- package/src/providers/types.ts +6 -2
- package/src/rbac/legacy-policy.ts +16 -2
- package/src/rbac/permissions.ts +26 -0
- package/src/scheduler/deferred-task-waits.ts +18 -5
- package/src/scheduler/scheduler.ts +11 -3
- package/src/scripts-runtime/executors/quickjs-prelude.ts +75 -6
- package/src/scripts-runtime/executors/quickjs-runner.ts +85 -8
- package/src/scripts-runtime/executors/quickjs-web-shims.ts +1041 -0
- package/src/scripts-runtime/extract-args-schema.ts +9 -8
- package/src/scripts-runtime/loader.ts +19 -7
- package/src/scripts-runtime/quickjs-compat.ts +464 -0
- package/src/scripts-runtime/sdk-allowlist.ts +12 -6
- package/src/scripts-runtime/swarm-sdk.ts +0 -11
- package/src/scripts-runtime/types/stdlib.d.ts +39 -15
- package/src/scripts-runtime/types/swarm-extension.d.ts +66 -1
- package/src/scripts-runtime/types/swarm-sdk.d.ts +39 -15
- package/src/server.ts +62 -19
- package/src/slack/blocks.ts +4 -2
- package/src/slack/event-dedup.ts +1 -1
- package/src/slack/reaction-shortcode.ts +14 -0
- package/src/slack/render-v2.ts +12 -7
- package/src/slack/responses.ts +5 -4
- package/src/slack/thread-buffer.ts +41 -22
- 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 +15 -33
- package/src/tests/acp-adapter.test.ts +42 -0
- package/src/tests/agentmail-filters.test.ts +47 -0
- package/src/tests/api-drain-worker-e2e.test.ts +1 -6
- package/src/tests/apps-spike2.test.ts +35 -0
- package/src/tests/azure-devops-outbound.test.ts +420 -0
- 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 +22 -0
- package/src/tests/db-task-write-contract.test.ts +11 -0
- package/src/tests/deferred-task-wake.test.ts +78 -1
- 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/env-flag.test.ts +1 -0
- package/src/tests/explicit-model-validation.test.ts +105 -1
- package/src/tests/extensions-catalog.test.ts +1 -0
- package/src/tests/extensions-example-comms-adherence-gate.test.ts +413 -0
- package/src/tests/extensions-example-route-chat-channel.test.ts +154 -0
- package/src/tests/extensions-loader.test.ts +15 -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/heartbeat-jev-gate-credentials.test.ts +168 -0
- package/src/tests/heartbeat-jev-gate.test.ts +385 -0
- 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-profile-backcompat.test.ts +327 -0
- package/src/tests/model-profile-providers.test.ts +302 -0
- package/src/tests/model-profile-runtime-secrets.test.ts +223 -0
- package/src/tests/model-profiles-api.test.ts +614 -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-keys.test.ts +121 -0
- package/src/tests/pages-feedback.test.ts +4 -1
- package/src/tests/paused-tasks-requested-by.test.ts +102 -0
- package/src/tests/pi-context-compaction.test.ts +193 -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/profile-claim.test.ts +269 -0
- package/src/tests/prompt-template-remaining.test.ts +35 -0
- package/src/tests/quickjs-compat.test.ts +213 -0
- package/src/tests/rbac-engine.test.ts +32 -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 +6 -0
- package/src/tests/script-executor-conformance.test.ts +252 -1
- package/src/tests/script-executor-quickjs.test.ts +96 -0
- package/src/tests/script-runs-http.test.ts +89 -0
- package/src/tests/scripts-http.test.ts +217 -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 +96 -0
- package/src/tests/seed-model-profiles.test.ts +174 -0
- package/src/tests/seed-scripts.test.ts +2 -2
- 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/skill-invocations.test.ts +4 -2
- package/src/tests/slack-blocks.test.ts +36 -0
- package/src/tests/slack-inline-output.test.ts +14 -0
- package/src/tests/slack-reaction-config.test.ts +20 -0
- package/src/tests/slack-render-v2.test.ts +37 -0
- package/src/tests/slack-thread-buffer.test.ts +98 -1
- 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-llm.test.ts +61 -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/model-profiles/list-model-profiles.ts +54 -0
- package/src/tools/register-chat-channel.ts +139 -0
- package/src/tools/request-human-input.ts +11 -3
- package/src/tools/script-apis.ts +5 -1
- package/src/tools/script-upsert.ts +4 -1
- package/src/tools/send-task.ts +48 -1
- 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/system-one-decide.ts +104 -0
- package/src/tools/task-action.ts +22 -1
- package/src/tools/tool-config.ts +14 -6
- package/src/types.ts +48 -3
- 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 +86 -10
- package/src/utils/task-tool-manifest.ts +19 -2
- package/src/utils/vcs-repo-match.ts +29 -0
- package/src/workflows/executors/raw-llm.ts +8 -3
- package/src/workflows/executors/swarm-script.ts +1 -0
- package/src/workflows/executors/system-one-decision.ts +21 -11
- package/src/workflows/executors/workflow-llm.ts +22 -0
- package/src/workflows/index.ts +21 -12
- package/templates/extensions/README.md +1 -0
- package/templates/extensions/comms-adherence-gate/README.md +88 -0
- package/templates/extensions/comms-adherence-gate/hooks.ts +489 -0
- package/templates/extensions/comms-adherence-gate/manifest.json +8 -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-42qhyvwx.js +0 -33
- 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
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
// What a task records about the model and effort it ran on, and what a re-run
|
|
2
|
+
// of the same row carries over. Regression for: an agent's model/effort changed
|
|
3
|
+
// in the dashboard, and the next run of a task kept the previous values.
|
|
4
|
+
|
|
5
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
6
|
+
import { unlink } from "node:fs/promises";
|
|
7
|
+
import {
|
|
8
|
+
closeDb,
|
|
9
|
+
createAgent,
|
|
10
|
+
createTaskExtended,
|
|
11
|
+
getTaskById,
|
|
12
|
+
initDb,
|
|
13
|
+
updateTaskClaudeSessionId,
|
|
14
|
+
} from "../be/db";
|
|
15
|
+
import { effectiveReasoningEffort } from "../providers/reasoning-effort";
|
|
16
|
+
import { resumedTaskModelSelection, taskModelPin } from "../tasks/model-pin";
|
|
17
|
+
|
|
18
|
+
const TEST_DB_PATH = "./test-task-model-effort-rerun.sqlite";
|
|
19
|
+
|
|
20
|
+
async function removeDbFiles(path: string): Promise<void> {
|
|
21
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
22
|
+
try {
|
|
23
|
+
await unlink(path + suffix);
|
|
24
|
+
} catch (error) {
|
|
25
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
beforeAll(async () => {
|
|
31
|
+
await removeDbFiles(TEST_DB_PATH);
|
|
32
|
+
initDb(TEST_DB_PATH);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
afterAll(async () => {
|
|
36
|
+
closeDb();
|
|
37
|
+
await removeDbFiles(TEST_DB_PATH);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe("taskModelPin", () => {
|
|
41
|
+
test("a model reported by a previous session is not a pin", () => {
|
|
42
|
+
expect(taskModelPin({ model: "claude-opus-5-5", claudeSessionId: "s1" })).toBeUndefined();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("a model a tier layer chose is not a pin", () => {
|
|
46
|
+
expect(
|
|
47
|
+
taskModelPin({
|
|
48
|
+
model: "claude-opus-5-5",
|
|
49
|
+
modelTier: "smart",
|
|
50
|
+
resolvedModel: "claude-opus-5-5",
|
|
51
|
+
modelSource: "tier-default",
|
|
52
|
+
}),
|
|
53
|
+
).toBeUndefined();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("the creator's model on an unclaimed row is the pin", () => {
|
|
57
|
+
expect(taskModelPin({ model: "claude-sonnet-5-5" })).toBe("claude-sonnet-5-5");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("a claimed pin keeps its alias", () => {
|
|
61
|
+
expect(
|
|
62
|
+
taskModelPin({
|
|
63
|
+
model: "claude-opus-5-5",
|
|
64
|
+
modelSource: "model",
|
|
65
|
+
modelAlias: "latest:anthropic/opus",
|
|
66
|
+
claudeSessionId: "s1",
|
|
67
|
+
}),
|
|
68
|
+
).toBe("latest:anthropic/opus");
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe("resumedTaskModelSelection", () => {
|
|
73
|
+
test("a paused unpinned task resumes without the previous run's model or claim record", () => {
|
|
74
|
+
expect(
|
|
75
|
+
resumedTaskModelSelection({
|
|
76
|
+
model: "claude-opus-5-5",
|
|
77
|
+
resolvedModel: "claude-opus-5-5",
|
|
78
|
+
modelSource: "tier-config",
|
|
79
|
+
modelTier: "smart",
|
|
80
|
+
claudeSessionId: "s1",
|
|
81
|
+
}),
|
|
82
|
+
).toEqual({ model: undefined, resolvedModel: undefined, modelSource: undefined });
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("a paused pinned task resumes on its pin", () => {
|
|
86
|
+
expect(
|
|
87
|
+
resumedTaskModelSelection({
|
|
88
|
+
model: "claude-sonnet-5-5",
|
|
89
|
+
resolvedModel: "claude-sonnet-5-5",
|
|
90
|
+
modelSource: "model",
|
|
91
|
+
claudeSessionId: "s1",
|
|
92
|
+
}),
|
|
93
|
+
).toEqual({
|
|
94
|
+
model: "claude-sonnet-5-5",
|
|
95
|
+
resolvedModel: "claude-sonnet-5-5",
|
|
96
|
+
modelSource: "model",
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe("effectiveReasoningEffort", () => {
|
|
102
|
+
test("unset effort records nothing", () => {
|
|
103
|
+
expect(effectiveReasoningEffort("claude", "claude-opus-5-5", undefined)).toBeNull();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("harnesses without effort support record nothing", () => {
|
|
107
|
+
expect(effectiveReasoningEffort("devin", "", "high")).toBeNull();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("a level the model cannot take is dropped, the same as the adapter does", () => {
|
|
111
|
+
expect(effectiveReasoningEffort("claude", "not-a-reasoning-model", "high")).toBeNull();
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe("session report records the effort", () => {
|
|
116
|
+
test("the effort the session ran at lands on resolvedEffort, and a re-run replaces it", async () => {
|
|
117
|
+
const agent = await createAgent({ name: "effort-rerun", isLead: false, status: "idle" });
|
|
118
|
+
const task = await createTaskExtended("work", { agentId: agent.id });
|
|
119
|
+
|
|
120
|
+
// First run: the agent's REASONING_EFFORT_OVERRIDE was high.
|
|
121
|
+
await updateTaskClaudeSessionId(
|
|
122
|
+
task.id,
|
|
123
|
+
"run-1",
|
|
124
|
+
"claude",
|
|
125
|
+
undefined,
|
|
126
|
+
"claude-opus-5-5",
|
|
127
|
+
undefined,
|
|
128
|
+
undefined,
|
|
129
|
+
"high",
|
|
130
|
+
);
|
|
131
|
+
let row = await getTaskById(task.id);
|
|
132
|
+
expect(row?.resolvedEffort).toBe("high");
|
|
133
|
+
// The requested-effort column stays the creator's value, not a pin.
|
|
134
|
+
expect(row?.effort).toBeUndefined();
|
|
135
|
+
|
|
136
|
+
// Effort changed to low in the dashboard; the next run records low.
|
|
137
|
+
await updateTaskClaudeSessionId(
|
|
138
|
+
task.id,
|
|
139
|
+
"run-2",
|
|
140
|
+
"claude",
|
|
141
|
+
undefined,
|
|
142
|
+
"claude-opus-5-5",
|
|
143
|
+
undefined,
|
|
144
|
+
undefined,
|
|
145
|
+
"low",
|
|
146
|
+
);
|
|
147
|
+
row = await getTaskById(task.id);
|
|
148
|
+
expect(row?.resolvedEffort).toBe("low");
|
|
149
|
+
|
|
150
|
+
// Effort set back to Auto: the harness default applied, nothing to show.
|
|
151
|
+
await updateTaskClaudeSessionId(
|
|
152
|
+
task.id,
|
|
153
|
+
"run-3",
|
|
154
|
+
"claude",
|
|
155
|
+
undefined,
|
|
156
|
+
"claude-opus-5-5",
|
|
157
|
+
undefined,
|
|
158
|
+
undefined,
|
|
159
|
+
null,
|
|
160
|
+
);
|
|
161
|
+
row = await getTaskById(task.id);
|
|
162
|
+
expect(row?.resolvedEffort).toBeUndefined();
|
|
163
|
+
});
|
|
164
|
+
});
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
buildRequesterProfilePrompt,
|
|
4
|
+
buildTaskSessionPrompts,
|
|
5
|
+
type TaskSessionContext,
|
|
6
|
+
} from "../commands/runner";
|
|
7
|
+
import { type BasePromptArgs, getTaskContextPrompt } from "../prompts/base-prompt";
|
|
8
|
+
import type { ProviderName } from "../types";
|
|
9
|
+
|
|
10
|
+
// The provider caches the system prompt as a prefix, so any per-task byte in it
|
|
11
|
+
// re-bills the whole prompt on the first call of every session. These tests pin
|
|
12
|
+
// the split: one agent, different tasks, requesters and repos, same system
|
|
13
|
+
// prompt; the per-task context still reaches the model, in the task prompt.
|
|
14
|
+
|
|
15
|
+
const agentArgs = (
|
|
16
|
+
provider: ProviderName,
|
|
17
|
+
extra: Partial<BasePromptArgs> = {},
|
|
18
|
+
): BasePromptArgs => ({
|
|
19
|
+
role: "worker",
|
|
20
|
+
agentId: "11111111-2222-3333-4444-555555555555",
|
|
21
|
+
name: "Ada",
|
|
22
|
+
provider,
|
|
23
|
+
traits: { hasMcp: true, hasLocalEnvironment: true, nativeSkillDiscovery: provider !== "codex" },
|
|
24
|
+
soulMd: "You are Ada.",
|
|
25
|
+
claudeMd: "# Agent: Ada\n\nAlways run the tests before you push.",
|
|
26
|
+
skillsSummary: [{ name: "commit", description: "Create a commit" }],
|
|
27
|
+
mcpServers: ["linear"],
|
|
28
|
+
...extra,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const sessionA: TaskSessionContext = {
|
|
32
|
+
repoContext: {
|
|
33
|
+
clonePath: "/workspace/personal/repos/alpha",
|
|
34
|
+
claudeMd: "Alpha repo rules.",
|
|
35
|
+
warning: "Alpha is behind origin",
|
|
36
|
+
autoStashes: [{ ref: "stash@{0}", message: "WIP alpha" }],
|
|
37
|
+
guidelines: {
|
|
38
|
+
prChecks: ["bun run lint"],
|
|
39
|
+
mergeChecks: ["CI green"],
|
|
40
|
+
allowMerge: false,
|
|
41
|
+
review: ["Check the db boundary"],
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
slackContext: { channelId: "C0ALPHA", threadTs: "111.222" },
|
|
45
|
+
requestedBy: {
|
|
46
|
+
name: "Taras",
|
|
47
|
+
role: "CTO",
|
|
48
|
+
notes: "Lead with the answer.",
|
|
49
|
+
comms: { verbosity: "terse" },
|
|
50
|
+
},
|
|
51
|
+
cwdWarning: 'Note: The task requested working directory "/nope" but it does not exist.',
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const sessionB: TaskSessionContext = {
|
|
55
|
+
repoContext: { clonePath: "/workspace/personal/repos/beta", guidelines: null },
|
|
56
|
+
requestedBy: { name: "Eze", role: "CEO", comms: { language: "Spanish" } },
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// A heartbeat: no repository, no requester, no working directory.
|
|
60
|
+
const sessionC: TaskSessionContext = {};
|
|
61
|
+
|
|
62
|
+
async function build(args: BasePromptArgs, context: TaskSessionContext, taskPrompt: string) {
|
|
63
|
+
return buildTaskSessionPrompts({
|
|
64
|
+
basePromptArgs: args,
|
|
65
|
+
additionalSystemPrompt: "Operator note: be careful.",
|
|
66
|
+
taskPrompt,
|
|
67
|
+
context,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
describe("buildTaskSessionPrompts", () => {
|
|
72
|
+
for (const provider of ["claude", "codex", "opencode", "pi"] as const) {
|
|
73
|
+
test(`${provider}: the system prompt is byte-identical across tasks, requesters and repos`, async () => {
|
|
74
|
+
const a = await build(agentArgs(provider), sessionA, "/work-on-task aaa\n\nTask: alpha");
|
|
75
|
+
const b = await build(agentArgs(provider), sessionB, "/work-on-task bbb\n\nTask: beta");
|
|
76
|
+
const c = await build(agentArgs(provider), sessionC, "/work-on-task ccc\n\nTask: heartbeat");
|
|
77
|
+
|
|
78
|
+
expect(a.systemPrompt).toBe(b.systemPrompt);
|
|
79
|
+
expect(a.systemPrompt).toBe(c.systemPrompt);
|
|
80
|
+
expect(a.systemPrompt).toEndWith("\n\nOperator note: be careful.");
|
|
81
|
+
for (const perTask of ["## Repository", "## Requester Profile", "Taras", "Eze", "/nope"]) {
|
|
82
|
+
expect(a.systemPrompt).not.toContain(perTask);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
test("a scripts-only worker keeps the system prompt stable across Slack and non-Slack tasks", async () => {
|
|
88
|
+
const keys = ["SLACK_DISABLE", "SLACK_BOT_TOKEN", "SLACK_APP_TOKEN"] as const;
|
|
89
|
+
const saved = keys.map((key) => [key, process.env[key]] as const);
|
|
90
|
+
delete process.env.SLACK_DISABLE;
|
|
91
|
+
process.env.SLACK_BOT_TOKEN = "xoxb-test-token";
|
|
92
|
+
process.env.SLACK_APP_TOKEN = "xapp-test-token";
|
|
93
|
+
try {
|
|
94
|
+
const args = agentArgs("claude", { scriptsOnly: true });
|
|
95
|
+
const a = await build(args, sessionA, "task a");
|
|
96
|
+
const c = await build(args, sessionC, "task c");
|
|
97
|
+
expect(a.systemPrompt).toBe(c.systemPrompt);
|
|
98
|
+
expect(a.systemPrompt).not.toContain("C0ALPHA");
|
|
99
|
+
expect(a.prompt).toContain("## Slack (scripts-only)");
|
|
100
|
+
expect(a.prompt).toContain("C0ALPHA");
|
|
101
|
+
} finally {
|
|
102
|
+
for (const [key, value] of saved) {
|
|
103
|
+
if (value === undefined) delete process.env[key];
|
|
104
|
+
else process.env[key] = value;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("the per-task context reaches the task prompt with the same text, after the task", async () => {
|
|
110
|
+
const args = agentArgs("opencode");
|
|
111
|
+
const taskPrompt = "/work-on-task aaa\n\nTask: alpha";
|
|
112
|
+
const { prompt } = await build(args, sessionA, taskPrompt);
|
|
113
|
+
|
|
114
|
+
const repoBlock = await getTaskContextPrompt({ ...args, repoContext: sessionA.repoContext });
|
|
115
|
+
const profile = await buildRequesterProfilePrompt(sessionA.requestedBy);
|
|
116
|
+
expect(repoBlock).toContain("## Repository");
|
|
117
|
+
expect(repoBlock).toContain("Alpha repo rules.");
|
|
118
|
+
expect(profile).toContain("This task was requested by Taras (CTO).");
|
|
119
|
+
|
|
120
|
+
expect(prompt).toStartWith(`${taskPrompt}\n\n`);
|
|
121
|
+
expect(prompt).toContain(repoBlock);
|
|
122
|
+
expect(prompt).toContain(profile);
|
|
123
|
+
expect(prompt).toEndWith(sessionA.cwdWarning ?? "");
|
|
124
|
+
expect(prompt.indexOf(repoBlock)).toBeLessThan(prompt.indexOf(profile));
|
|
125
|
+
expect(prompt).not.toMatch(/\n{3,}/);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("a task with no per-task context keeps its prompt unchanged", async () => {
|
|
129
|
+
const { prompt } = await build(agentArgs("claude"), sessionC, "/work-on-task ccc");
|
|
130
|
+
expect(prompt).toBe("/work-on-task ccc");
|
|
131
|
+
});
|
|
132
|
+
});
|
|
@@ -27,6 +27,20 @@ describe("task tool manifests", () => {
|
|
|
27
27
|
]);
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
+
test("chat-reply only preloads for a task carrying a chat-sdk contextKey", () => {
|
|
31
|
+
const manifest = parseTaskToolManifest('{"taskTypes":{"review":["chat-reply","script-run"]}}');
|
|
32
|
+
expect(selectTaskTools(manifest, { taskType: "review" })).toEqual(["script-run"]);
|
|
33
|
+
expect(
|
|
34
|
+
selectTaskTools(manifest, { taskType: "review", contextKey: "task:slack:C123:1700.1" }),
|
|
35
|
+
).toEqual(["script-run"]);
|
|
36
|
+
expect(
|
|
37
|
+
selectTaskTools(manifest, {
|
|
38
|
+
taskType: "review",
|
|
39
|
+
contextKey: "task:chat-sdk:telegram:555001",
|
|
40
|
+
}),
|
|
41
|
+
).toEqual(["chat-reply", "script-run"]);
|
|
42
|
+
});
|
|
43
|
+
|
|
30
44
|
test("config writes reject unknown tools, invalid shapes, and oversized selections", () => {
|
|
31
45
|
for (const value of [
|
|
32
46
|
"not json",
|
|
@@ -137,8 +137,10 @@ describe("task tool ctx", () => {
|
|
|
137
137
|
});
|
|
138
138
|
await updateAgentProfile(lead.id, { capabilities: ["merge"] });
|
|
139
139
|
await updateAgentProfile(underprivilegedLead.id, { capabilities: ["typescript"] });
|
|
140
|
+
// The sender created the parent, so it may continue it (task.parent.attach).
|
|
140
141
|
const parent = await createTaskExtended("privileged parent", {
|
|
141
142
|
agentId: lead.id,
|
|
143
|
+
creatorAgentId: sender.id,
|
|
142
144
|
routingAffinity: { capabilities: ["merge"], leadOnly: true },
|
|
143
145
|
});
|
|
144
146
|
|
|
@@ -116,7 +116,6 @@ describe("Tool Annotations & Classification", () => {
|
|
|
116
116
|
"delete-config",
|
|
117
117
|
"delete-workflow",
|
|
118
118
|
"delete-page",
|
|
119
|
-
"unregister-service",
|
|
120
119
|
"slack-delete",
|
|
121
120
|
"slack-archive-channel",
|
|
122
121
|
];
|
|
@@ -135,7 +134,6 @@ describe("Tool Annotations & Classification", () => {
|
|
|
135
134
|
"get-tasks",
|
|
136
135
|
"get-config",
|
|
137
136
|
"list-channels",
|
|
138
|
-
"list-services",
|
|
139
137
|
"list-schedules",
|
|
140
138
|
"list-config",
|
|
141
139
|
"memory-get",
|
|
@@ -350,7 +348,7 @@ describe("Tool Annotations & Classification", () => {
|
|
|
350
348
|
expect(missing).toEqual([]);
|
|
351
349
|
});
|
|
352
350
|
|
|
353
|
-
test("service tools
|
|
351
|
+
test("the removed PM2 service registry tools stay gone", () => {
|
|
354
352
|
const serviceTools = [
|
|
355
353
|
"register-service",
|
|
356
354
|
"unregister-service",
|
|
@@ -358,7 +356,8 @@ describe("Tool Annotations & Classification", () => {
|
|
|
358
356
|
"update-service-status",
|
|
359
357
|
];
|
|
360
358
|
for (const tool of serviceTools) {
|
|
361
|
-
expect(DEFERRED_TOOLS.has(tool)).toBe(
|
|
359
|
+
expect(DEFERRED_TOOLS.has(tool)).toBe(false);
|
|
360
|
+
expect(tools[tool]).toBeUndefined();
|
|
362
361
|
}
|
|
363
362
|
});
|
|
364
363
|
|
|
@@ -376,9 +375,10 @@ describe("Tool Annotations & Classification", () => {
|
|
|
376
375
|
// We expect all tools to be registered when all capabilities are enabled
|
|
377
376
|
// (this suite forces ALL_CAPABILITIES, including default-disabled ones).
|
|
378
377
|
// Includes 11 skill tools, 7 MCP server tools, reusable script tools, and the
|
|
379
|
-
// native Kapso/WhatsApp tools (register/unregister number + send/reply message)
|
|
378
|
+
// native Kapso/WhatsApp tools (register/unregister number + send/reply message),
|
|
379
|
+
// and the six Chat SDK tools (chat-* + register/unregister-chat-channel).
|
|
380
380
|
expect(count).toBeGreaterThanOrEqual(45);
|
|
381
|
-
expect(count).toBeLessThanOrEqual(
|
|
381
|
+
expect(count).toBeLessThanOrEqual(163);
|
|
382
382
|
});
|
|
383
383
|
|
|
384
384
|
test("core tools are fewer than deferred tools", () => {
|
|
@@ -1154,6 +1154,49 @@ describe("ui logs parser", () => {
|
|
|
1154
1154
|
]);
|
|
1155
1155
|
});
|
|
1156
1156
|
|
|
1157
|
+
test("keeps a Claude sub-agent's parent_tool_use_id on its messages", () => {
|
|
1158
|
+
const messages = parseSessionLogs([
|
|
1159
|
+
log("spawn", "claude", 1, {
|
|
1160
|
+
type: "assistant",
|
|
1161
|
+
message: {
|
|
1162
|
+
role: "assistant",
|
|
1163
|
+
content: [{ type: "tool_use", id: "toolu_agent_1", name: "Agent", input: {} }],
|
|
1164
|
+
},
|
|
1165
|
+
}),
|
|
1166
|
+
log("child-call", "claude", 2, {
|
|
1167
|
+
type: "assistant",
|
|
1168
|
+
parent_tool_use_id: "toolu_agent_1",
|
|
1169
|
+
message: {
|
|
1170
|
+
role: "assistant",
|
|
1171
|
+
content: [
|
|
1172
|
+
{ type: "thinking", thinking: "Look at the fixtures." },
|
|
1173
|
+
{ type: "tool_use", id: "toolu_read_1", name: "Read", input: { file_path: "a.ts" } },
|
|
1174
|
+
],
|
|
1175
|
+
},
|
|
1176
|
+
}),
|
|
1177
|
+
log("child-result", "claude", 3, {
|
|
1178
|
+
type: "user",
|
|
1179
|
+
parent_tool_use_id: "toolu_agent_1",
|
|
1180
|
+
message: {
|
|
1181
|
+
role: "user",
|
|
1182
|
+
content: [{ type: "tool_result", tool_use_id: "toolu_read_1", content: "ok" }],
|
|
1183
|
+
},
|
|
1184
|
+
}),
|
|
1185
|
+
log("main-text", "claude", 4, {
|
|
1186
|
+
type: "assistant",
|
|
1187
|
+
parent_tool_use_id: null,
|
|
1188
|
+
message: { role: "assistant", content: [{ type: "text", text: "Done." }] },
|
|
1189
|
+
}),
|
|
1190
|
+
]);
|
|
1191
|
+
|
|
1192
|
+
expect(messages.map((m) => [m.id, m.parentToolUseId])).toEqual([
|
|
1193
|
+
["spawn", undefined],
|
|
1194
|
+
["child-call", "toolu_agent_1"],
|
|
1195
|
+
["child-result", "toolu_agent_1"],
|
|
1196
|
+
["main-text", undefined],
|
|
1197
|
+
]);
|
|
1198
|
+
});
|
|
1199
|
+
|
|
1157
1200
|
test("extracts a completed Claude foreground Agent run from its paired result", () => {
|
|
1158
1201
|
const runs = extractSubagentRuns([
|
|
1159
1202
|
log("spawn", "claude", 1, {
|
|
@@ -61,10 +61,14 @@ const configValue = async (key: string) =>
|
|
|
61
61
|
|
|
62
62
|
describe("POST /api/used-by", () => {
|
|
63
63
|
const originalFetch = globalThis.fetch;
|
|
64
|
+
const savedEnvWebsite = process.env.SWARM_ORG_WEBSITE;
|
|
65
|
+
const savedEnvName = process.env.SWARM_ORG_NAME;
|
|
64
66
|
let proxied: Array<{ url: string; body: Record<string, unknown> }>;
|
|
65
67
|
let proxyStatus: number;
|
|
66
68
|
|
|
67
69
|
beforeAll(async () => {
|
|
70
|
+
delete process.env.SWARM_ORG_WEBSITE;
|
|
71
|
+
delete process.env.SWARM_ORG_NAME;
|
|
68
72
|
await removeDbFiles();
|
|
69
73
|
initDb(TEST_DB_PATH);
|
|
70
74
|
await createAgent({ id: WORKER_ID, name: "Worker", isLead: false, status: "idle" });
|
|
@@ -75,6 +79,8 @@ describe("POST /api/used-by", () => {
|
|
|
75
79
|
});
|
|
76
80
|
|
|
77
81
|
afterAll(async () => {
|
|
82
|
+
if (savedEnvWebsite !== undefined) process.env.SWARM_ORG_WEBSITE = savedEnvWebsite;
|
|
83
|
+
if (savedEnvName !== undefined) process.env.SWARM_ORG_NAME = savedEnvName;
|
|
78
84
|
globalThis.fetch = originalFetch;
|
|
79
85
|
closeDb();
|
|
80
86
|
await removeDbFiles();
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vercel AI Gateway (`vercel/<vendor>/<model>`) on pi and opencode: the full
|
|
3
|
+
* chat catalog, and the one endpoint no env var or hand-written block can move.
|
|
4
|
+
*/
|
|
5
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
6
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
7
|
+
import { tmpdir } from "node:os";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import {
|
|
10
|
+
isAgentChatModel,
|
|
11
|
+
OPENAI_COMPATIBLE_PROVIDERS,
|
|
12
|
+
openAICompatibleBaseUrlVars,
|
|
13
|
+
providerBaseUrl,
|
|
14
|
+
providerForModel,
|
|
15
|
+
withRegistryCatalogSections,
|
|
16
|
+
} from "@desplega/model-catalog";
|
|
17
|
+
import { buildCatalogEntries } from "../be/model-catalog-store";
|
|
18
|
+
import { isKnownCatalogModel } from "../be/model-validation";
|
|
19
|
+
import { buildModelsCatalog } from "../be/models-catalog";
|
|
20
|
+
import { loadModelsDevCache, type ModelsDevCache } from "../be/modelsdev-cache";
|
|
21
|
+
import snapshot from "../be/modelsdev-cache.json";
|
|
22
|
+
import { applyProviderKeySplice, type OpencodeConfig } from "../providers/opencode-adapter";
|
|
23
|
+
import { customProviderModelsBlock, writeSessionModelsJson } from "../providers/pi-mono-adapter";
|
|
24
|
+
|
|
25
|
+
const VERCEL = OPENAI_COMPATIBLE_PROVIDERS.vercel;
|
|
26
|
+
const ENDPOINT = "https://ai-gateway.vercel.sh/v1";
|
|
27
|
+
const WRONG = "https://ai-gateway.vercel.sh";
|
|
28
|
+
const raw = snapshot as unknown as ModelsDevCache;
|
|
29
|
+
const rawVercel = (raw.vercel?.models ?? {}) as Record<
|
|
30
|
+
string,
|
|
31
|
+
Parameters<typeof isAgentChatModel>[0]
|
|
32
|
+
>;
|
|
33
|
+
const chatIds = Object.keys(rawVercel).filter((id) => isAgentChatModel(rawVercel[id] ?? {}));
|
|
34
|
+
|
|
35
|
+
describe("Vercel AI Gateway catalog", () => {
|
|
36
|
+
test("the snapshot carries non-chat rows, so the filter is exercised", () => {
|
|
37
|
+
expect(rawVercel["voyage/voyage-code-3"]).toBeDefined();
|
|
38
|
+
expect(chatIds.length).toBeGreaterThan(200);
|
|
39
|
+
expect(chatIds.length).toBeLessThan(Object.keys(rawVercel).length);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("pi lists every AI Gateway chat model, and no embedding or video model", () => {
|
|
43
|
+
const block = customProviderModelsBlock(VERCEL);
|
|
44
|
+
const ids = block.models.map((m) => m.id as string);
|
|
45
|
+
expect(ids.sort()).toEqual([...chatIds].sort());
|
|
46
|
+
expect(ids).toContain("zai/glm-5.3");
|
|
47
|
+
expect(ids).toContain("moonshotai/kimi-k3");
|
|
48
|
+
expect(ids).toContain("anthropic/claude-sonnet-4.6");
|
|
49
|
+
expect(ids).not.toContain("voyage/voyage-code-3");
|
|
50
|
+
expect(ids).not.toContain("klingai/kling-v3.0-t2v");
|
|
51
|
+
const glm = block.models.find((m) => m.id === "zai/glm-5.3");
|
|
52
|
+
expect(glm).toMatchObject({ contextWindow: 1000000, maxTokens: 1000000, reasoning: true });
|
|
53
|
+
expect(block).toMatchObject({
|
|
54
|
+
baseUrl: ENDPOINT,
|
|
55
|
+
api: "openai-completions",
|
|
56
|
+
apiKey: "$AI_GATEWAY_API_KEY",
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("picker, live and validation catalogs drop non-chat Vercel rows", () => {
|
|
61
|
+
const merged = withRegistryCatalogSections(raw);
|
|
62
|
+
expect(Object.keys(merged.vercel?.models ?? {}).sort()).toEqual([...chatIds].sort());
|
|
63
|
+
// OpenRouter is not a registry provider and keeps its full section.
|
|
64
|
+
expect(Object.keys(merged.openrouter?.models ?? {}).length).toBe(
|
|
65
|
+
Object.keys(raw.openrouter?.models ?? {}).length,
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const live = buildCatalogEntries(raw, 0).filter((e) => e.provider === "vercel");
|
|
69
|
+
expect(live.map((e) => e.modelId).sort()).toEqual([...chatIds].sort());
|
|
70
|
+
|
|
71
|
+
const cache = loadModelsDevCache();
|
|
72
|
+
if (!cache) throw new Error("snapshot missing");
|
|
73
|
+
const catalog = buildModelsCatalog(cache) as Parameters<typeof isKnownCatalogModel>[1];
|
|
74
|
+
expect(isKnownCatalogModel("vercel/zai/glm-5.3", catalog)).toBe(true);
|
|
75
|
+
expect(isKnownCatalogModel("vercel/anthropic/claude-sonnet-4.6", catalog)).toBe(true);
|
|
76
|
+
expect(isKnownCatalogModel("vercel/voyage/voyage-code-3", catalog)).toBe(false);
|
|
77
|
+
expect(providerForModel("vercel/moonshotai/kimi-k3")?.id).toBe("vercel");
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe("Vercel AI Gateway endpoint is fixed", () => {
|
|
82
|
+
const dirs: string[] = [];
|
|
83
|
+
afterEach(async () => {
|
|
84
|
+
await Promise.all(dirs.splice(0).map((d) => rm(d, { recursive: true, force: true })));
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("VERCEL_BASE_URL is never read or forwarded", () => {
|
|
88
|
+
expect(VERCEL.baseUrl).toBe(ENDPOINT);
|
|
89
|
+
expect(providerBaseUrl(VERCEL, { VERCEL_BASE_URL: WRONG })).toBe(ENDPOINT);
|
|
90
|
+
expect(openAICompatibleBaseUrlVars()).not.toContain("VERCEL_BASE_URL");
|
|
91
|
+
expect(openAICompatibleBaseUrlVars()).toContain("NAN_BASE_URL");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("opencode gets the key and no baseURL, even over a configured one", () => {
|
|
95
|
+
const key = crypto.randomUUID();
|
|
96
|
+
const config: OpencodeConfig = {
|
|
97
|
+
model: "vercel/zai/glm-5.3",
|
|
98
|
+
provider: { vercel: { options: { baseURL: WRONG } } },
|
|
99
|
+
};
|
|
100
|
+
const picked = applyProviderKeySplice(config, config.model, {
|
|
101
|
+
AI_GATEWAY_API_KEY: key,
|
|
102
|
+
VERCEL_BASE_URL: WRONG,
|
|
103
|
+
});
|
|
104
|
+
expect(picked?.key).toBe(key);
|
|
105
|
+
expect(config.provider?.vercel).toEqual({ env: [], options: { apiKey: key } });
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("pi gets the Chat Completions endpoint over VERCEL_BASE_URL and a hand-written block", async () => {
|
|
109
|
+
const agentDir = await mkdtemp(join(tmpdir(), "pi-vercel-"));
|
|
110
|
+
dirs.push(agentDir);
|
|
111
|
+
await Bun.write(
|
|
112
|
+
join(agentDir, "models.json"),
|
|
113
|
+
JSON.stringify({
|
|
114
|
+
providers: { vercel: { baseUrl: WRONG, api: "anthropic-messages", headers: { a: "b" } } },
|
|
115
|
+
}),
|
|
116
|
+
);
|
|
117
|
+
const env = { AI_GATEWAY_API_KEY: "k", VERCEL_BASE_URL: WRONG };
|
|
118
|
+
const session = await writeSessionModelsJson(agentDir, env, "vercel/vendor/brand-new-model");
|
|
119
|
+
try {
|
|
120
|
+
expect(session.modelsPath).toBeDefined();
|
|
121
|
+
const written = await Bun.file(session.modelsPath as string).json();
|
|
122
|
+
const block = written.providers.vercel;
|
|
123
|
+
expect(block.baseUrl).toBe(ENDPOINT);
|
|
124
|
+
expect(block.api).toBe("openai-completions");
|
|
125
|
+
expect(block.headers).toEqual({ a: "b" });
|
|
126
|
+
const ids = (block.models as { id: string }[]).map((m) => m.id);
|
|
127
|
+
expect(ids).toContain("zai/glm-5.3");
|
|
128
|
+
// An id the catalog does not know yet still resolves (bare entry).
|
|
129
|
+
expect(ids).toContain("vendor/brand-new-model");
|
|
130
|
+
} finally {
|
|
131
|
+
await session.dispose();
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("pi without a shared block also lands on the endpoint", async () => {
|
|
136
|
+
const agentDir = await mkdtemp(join(tmpdir(), "pi-vercel-"));
|
|
137
|
+
dirs.push(agentDir);
|
|
138
|
+
const session = await writeSessionModelsJson(agentDir, {
|
|
139
|
+
AI_GATEWAY_API_KEY: "k",
|
|
140
|
+
VERCEL_BASE_URL: WRONG,
|
|
141
|
+
});
|
|
142
|
+
try {
|
|
143
|
+
const written = await Bun.file(session.modelsPath as string).json();
|
|
144
|
+
expect(written.providers.vercel.baseUrl).toBe(ENDPOINT);
|
|
145
|
+
} finally {
|
|
146
|
+
await session.dispose();
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
});
|
|
@@ -77,6 +77,67 @@ describe("resolveWorkflowLlmConfig", () => {
|
|
|
77
77
|
expect(config.model).toBe("gpt-5.4");
|
|
78
78
|
});
|
|
79
79
|
|
|
80
|
+
test("routes a registry-provider model to that provider, not OpenRouter", async () => {
|
|
81
|
+
const config = await resolveWorkflowLlmConfig("nan/qwen3.8-flash", {
|
|
82
|
+
OPENROUTER_API_KEY: "sk-or-test",
|
|
83
|
+
NAN_API_KEY: "nan-test",
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
expect(config).toEqual({
|
|
87
|
+
apiKey: "nan-test",
|
|
88
|
+
baseURL: "https://api.nan.builders/v1",
|
|
89
|
+
headers: {},
|
|
90
|
+
model: "qwen3.8-flash",
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("reads a registry provider's alias key and base URL override", async () => {
|
|
95
|
+
const config = await resolveWorkflowLlmConfig("nan/deepseek-v4-flash", {
|
|
96
|
+
HELMCODE_API_NAN: "nan-alias",
|
|
97
|
+
NAN_BASE_URL: "https://nan.example.test/v1/",
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
expect(config.apiKey).toBe("nan-alias");
|
|
101
|
+
expect(config.baseURL).toBe("https://nan.example.test/v1");
|
|
102
|
+
expect(config.model).toBe("deepseek-v4-flash");
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("hands the SDK one key from a canonical NaN key pool", async () => {
|
|
106
|
+
const pool = ["nan-pool-a", "nan-pool-b", "nan-pool-c"];
|
|
107
|
+
for (let i = 0; i < 20; i++) {
|
|
108
|
+
const config = await resolveWorkflowLlmConfig("nan/qwen3.8-flash", {
|
|
109
|
+
NAN_API_KEY: ` ${pool.join(", ")} `,
|
|
110
|
+
});
|
|
111
|
+
expect(pool).toContain(config.apiKey);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("hands the SDK one key from an alias NaN key pool", async () => {
|
|
116
|
+
const pool = ["nan-alias-a", "nan-alias-b"];
|
|
117
|
+
for (let i = 0; i < 20; i++) {
|
|
118
|
+
const config = await resolveWorkflowLlmConfig("nan/deepseek-v4-flash", {
|
|
119
|
+
HELMCODE_API_NAN: pool.join(","),
|
|
120
|
+
});
|
|
121
|
+
expect(pool).toContain(config.apiKey);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("fails a registry-provider model without its key instead of falling back to OpenRouter", async () => {
|
|
126
|
+
await expect(
|
|
127
|
+
resolveWorkflowLlmConfig("nan/qwen3.8-flash", { OPENROUTER_API_KEY: "sk-or-test" }),
|
|
128
|
+
).rejects.toThrow("No NaN credential found for nan/qwen3.8-flash. Set NAN_API_KEY.");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("keeps OpenRouter vendor ids on OpenRouter", async () => {
|
|
132
|
+
const config = await resolveWorkflowLlmConfig("deepseek/deepseek-v4.1-flash", {
|
|
133
|
+
OPENROUTER_API_KEY: "sk-or-test",
|
|
134
|
+
NAN_API_KEY: "nan-test",
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
expect(config.apiKey).toBe("sk-or-test");
|
|
138
|
+
expect(config.model).toBe("deepseek/deepseek-v4.1-flash");
|
|
139
|
+
});
|
|
140
|
+
|
|
80
141
|
test("rejects credential kinds without an OpenAI-compatible endpoint", async () => {
|
|
81
142
|
await expect(
|
|
82
143
|
resolveWorkflowLlmConfig(undefined, { ANTHROPIC_API_KEY: "example-sk-ant-test" }),
|