@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,327 @@
|
|
|
1
|
+
// Migration 220 must not change what an agent runs. For each config shape the
|
|
2
|
+
// test resolves the task the way production does (the real `/api/poll` claim,
|
|
3
|
+
// then the worker's real `fetchRepoConfig` + `fetchResolvedEnv` against this
|
|
4
|
+
// DB) and compares the final model and effort with and without the migrated
|
|
5
|
+
// default profile.
|
|
6
|
+
|
|
7
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
8
|
+
import { readFileSync } from "node:fs";
|
|
9
|
+
import { unlink } from "node:fs/promises";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import {
|
|
12
|
+
closeDb,
|
|
13
|
+
createAgent,
|
|
14
|
+
createSwarmRepo,
|
|
15
|
+
createTaskExtended,
|
|
16
|
+
getDb,
|
|
17
|
+
getDbClient,
|
|
18
|
+
getResolvedConfig,
|
|
19
|
+
getSwarmRepos,
|
|
20
|
+
initDb,
|
|
21
|
+
upsertSwarmConfig,
|
|
22
|
+
} from "../be/db";
|
|
23
|
+
import { getAgentDefaultProfileName } from "../be/db/model-profiles";
|
|
24
|
+
import { fetchRepoConfig, fetchResolvedEnv } from "../commands/runner";
|
|
25
|
+
import { handlePoll } from "../http/poll";
|
|
26
|
+
|
|
27
|
+
const TEST_DB_PATH = "./test-model-profile-backcompat.sqlite";
|
|
28
|
+
const REPO = "desplega-ai/backcompat-repo";
|
|
29
|
+
|
|
30
|
+
async function removeDbFiles(path: string): Promise<void> {
|
|
31
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
32
|
+
try {
|
|
33
|
+
await unlink(path + suffix);
|
|
34
|
+
} catch (error) {
|
|
35
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The part of migration 220 that turns agent overrides into profiles. */
|
|
41
|
+
function agentOverrideMigrationSql(): string {
|
|
42
|
+
const sql = readFileSync(
|
|
43
|
+
join(import.meta.dir, "../be/migrations/220_model_profiles.sql"),
|
|
44
|
+
"utf8",
|
|
45
|
+
);
|
|
46
|
+
return sql.slice(sql.indexOf("CREATE TEMP TABLE _model_profile_overrides"));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let server: ReturnType<typeof Bun.serve>;
|
|
50
|
+
let apiUrl = "";
|
|
51
|
+
|
|
52
|
+
beforeAll(async () => {
|
|
53
|
+
await removeDbFiles(TEST_DB_PATH);
|
|
54
|
+
initDb(TEST_DB_PATH);
|
|
55
|
+
// The two endpoints the worker reads its model config from, backed by this DB.
|
|
56
|
+
server = Bun.serve({
|
|
57
|
+
port: 0,
|
|
58
|
+
async fetch(req) {
|
|
59
|
+
const url = new URL(req.url);
|
|
60
|
+
if (url.pathname === "/api/config/resolved") {
|
|
61
|
+
const configs = await getResolvedConfig(
|
|
62
|
+
url.searchParams.get("agentId") ?? undefined,
|
|
63
|
+
url.searchParams.get("repoId") ?? undefined,
|
|
64
|
+
);
|
|
65
|
+
return Response.json({ configs });
|
|
66
|
+
}
|
|
67
|
+
if (url.pathname === "/api/repos") {
|
|
68
|
+
return Response.json({
|
|
69
|
+
repos: await getSwarmRepos({ name: url.searchParams.get("name") ?? undefined }),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return new Response("Not found", { status: 404 });
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
apiUrl = server.url.toString().replace(/\/$/, "");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
afterAll(async () => {
|
|
79
|
+
server.stop(true);
|
|
80
|
+
closeDb();
|
|
81
|
+
await removeDbFiles(TEST_DB_PATH);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
beforeEach(async () => {
|
|
85
|
+
const client = getDbClient();
|
|
86
|
+
// References first: migration 220's triggers refuse deleting a named profile.
|
|
87
|
+
await client.run("DELETE FROM agent_tasks");
|
|
88
|
+
await client.run("DELETE FROM swarm_config");
|
|
89
|
+
await client.run("DELETE FROM swarm_repos");
|
|
90
|
+
await client.run("DELETE FROM agents");
|
|
91
|
+
await client.run("DELETE FROM model_profiles");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
type Trigger = { task: { resolvedModel?: string; effort?: string } };
|
|
95
|
+
|
|
96
|
+
async function claim(agentId: string): Promise<Trigger["task"]> {
|
|
97
|
+
let body = "";
|
|
98
|
+
const req = {
|
|
99
|
+
method: "GET",
|
|
100
|
+
url: "/api/poll",
|
|
101
|
+
headers: { "x-agent-id": agentId },
|
|
102
|
+
} as unknown as Parameters<typeof handlePoll>[0];
|
|
103
|
+
const res = {
|
|
104
|
+
setHeader() {},
|
|
105
|
+
writeHead() {},
|
|
106
|
+
end(chunk?: string) {
|
|
107
|
+
body = chunk ?? "";
|
|
108
|
+
},
|
|
109
|
+
} as unknown as Parameters<typeof handlePoll>[1];
|
|
110
|
+
await handlePoll(req, res, ["api", "poll"], new URLSearchParams(), agentId);
|
|
111
|
+
const trigger = (JSON.parse(body) as { trigger: Trigger | null }).trigger;
|
|
112
|
+
if (!trigger) throw new Error("nothing claimed");
|
|
113
|
+
return trigger.task;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Claim one task and return what the worker would start: runner.ts
|
|
118
|
+
* spawnProviderProcess takes the claim's `resolvedModel`, else the env's
|
|
119
|
+
* MODEL_OVERRIDE, and the trigger's `effort`, else REASONING_EFFORT_OVERRIDE.
|
|
120
|
+
* The env is the real `fetchResolvedEnv` (repo > agent > global, a withheld
|
|
121
|
+
* row removes its key).
|
|
122
|
+
*/
|
|
123
|
+
async function workerSelection(
|
|
124
|
+
agentId: string,
|
|
125
|
+
vcsRepo?: string,
|
|
126
|
+
profile?: string,
|
|
127
|
+
): Promise<{ model: string | null; effort: string | null }> {
|
|
128
|
+
await createTaskExtended("work", { agentId, vcsRepo, profile });
|
|
129
|
+
const trigger = await claim(agentId);
|
|
130
|
+
const repo = vcsRepo ? await fetchRepoConfig(apiUrl, "key", vcsRepo, true) : null;
|
|
131
|
+
const { env } = await fetchResolvedEnv(apiUrl, "key", agentId, {}, trigger.resolvedModel, {
|
|
132
|
+
repoId: repo?.id,
|
|
133
|
+
provider: "codex",
|
|
134
|
+
});
|
|
135
|
+
await getDbClient().run("UPDATE agent_tasks SET status = 'completed'");
|
|
136
|
+
return {
|
|
137
|
+
model: trigger.resolvedModel || env.MODEL_OVERRIDE || null,
|
|
138
|
+
effort: trigger.effort || env.REASONING_EFFORT_OVERRIDE || null,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function setRow(
|
|
143
|
+
scope: "global" | "agent" | "repo",
|
|
144
|
+
scopeId: string | null,
|
|
145
|
+
key: string,
|
|
146
|
+
value: string,
|
|
147
|
+
exportToEnv = true,
|
|
148
|
+
): Promise<void> {
|
|
149
|
+
await upsertSwarmConfig({ scope, scopeId, key, value, exportToEnv });
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function codexAgent(name: string): Promise<string> {
|
|
153
|
+
const agent = await createAgent({
|
|
154
|
+
name,
|
|
155
|
+
isLead: false,
|
|
156
|
+
status: "idle",
|
|
157
|
+
harnessProvider: "codex",
|
|
158
|
+
});
|
|
159
|
+
return agent.id;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** What the agent runs before migration 220 and after it. */
|
|
163
|
+
async function beforeAndAfterMigration(agentId: string, vcsRepo?: string) {
|
|
164
|
+
const before = await workerSelection(agentId, vcsRepo);
|
|
165
|
+
getDb().exec(agentOverrideMigrationSql());
|
|
166
|
+
const after = await workerSelection(agentId, vcsRepo);
|
|
167
|
+
return { before, after };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
describe("migration 220 keeps exportToEnv = 0 overrides off", () => {
|
|
171
|
+
test("a withheld effort stays absent while the model stays enabled", async () => {
|
|
172
|
+
const id = await codexAgent("bc-effort-withheld");
|
|
173
|
+
await setRow("global", null, "REASONING_EFFORT_OVERRIDE", "medium");
|
|
174
|
+
await setRow("agent", id, "MODEL_OVERRIDE", "gpt-6.1-sol");
|
|
175
|
+
await setRow("agent", id, "REASONING_EFFORT_OVERRIDE", "xhigh", false);
|
|
176
|
+
|
|
177
|
+
const { before, after } = await beforeAndAfterMigration(id);
|
|
178
|
+
expect(before).toEqual({ model: "gpt-6.1-sol", effort: null });
|
|
179
|
+
expect(after).toEqual(before);
|
|
180
|
+
expect(await getAgentDefaultProfileName(id)).toBe("codex-gpt-6.1-sol");
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("a withheld model makes no default", async () => {
|
|
184
|
+
const id = await codexAgent("bc-model-withheld");
|
|
185
|
+
await setRow("agent", id, "MODEL_OVERRIDE", "gpt-6.1-sol", false);
|
|
186
|
+
await setRow("agent", id, "REASONING_EFFORT_OVERRIDE", "xhigh");
|
|
187
|
+
|
|
188
|
+
const { before, after } = await beforeAndAfterMigration(id);
|
|
189
|
+
expect(before).toEqual({ model: null, effort: "xhigh" });
|
|
190
|
+
expect(after).toEqual(before);
|
|
191
|
+
expect(await getAgentDefaultProfileName(id)).toBeNull();
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test("both withheld: no default, nothing applied", async () => {
|
|
195
|
+
const id = await codexAgent("bc-both-withheld");
|
|
196
|
+
await setRow("agent", id, "MODEL_OVERRIDE", "gpt-6.1-sol", false);
|
|
197
|
+
await setRow("agent", id, "REASONING_EFFORT_OVERRIDE", "xhigh", false);
|
|
198
|
+
|
|
199
|
+
const { before, after } = await beforeAndAfterMigration(id);
|
|
200
|
+
expect(before).toEqual({ model: null, effort: null });
|
|
201
|
+
expect(after).toEqual(before);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test("control: both exported still become the default", async () => {
|
|
205
|
+
const id = await codexAgent("bc-both-exported");
|
|
206
|
+
await setRow("agent", id, "MODEL_OVERRIDE", "gpt-6.1-sol");
|
|
207
|
+
await setRow("agent", id, "REASONING_EFFORT_OVERRIDE", "xhigh");
|
|
208
|
+
|
|
209
|
+
const { before, after } = await beforeAndAfterMigration(id);
|
|
210
|
+
expect(before).toEqual({ model: "gpt-6.1-sol", effort: "xhigh" });
|
|
211
|
+
expect(after).toEqual(before);
|
|
212
|
+
expect(await getAgentDefaultProfileName(id)).toBe("codex-gpt-6.1-sol-xhigh");
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
describe("withholding an override later drops the default", () => {
|
|
217
|
+
for (const key of ["MODEL_OVERRIDE", "REASONING_EFFORT_OVERRIDE"] as const) {
|
|
218
|
+
test(`same-value ${key} with exportToEnv false clears it, and the worker runs as without it`, async () => {
|
|
219
|
+
const id = await codexAgent(`bc-toggle-${key}`);
|
|
220
|
+
await setRow("agent", id, "MODEL_OVERRIDE", "gpt-6.1-sol");
|
|
221
|
+
await setRow("agent", id, "REASONING_EFFORT_OVERRIDE", "xhigh");
|
|
222
|
+
getDb().exec(agentOverrideMigrationSql());
|
|
223
|
+
expect(await getAgentDefaultProfileName(id)).toBe("codex-gpt-6.1-sol-xhigh");
|
|
224
|
+
|
|
225
|
+
// Control: re-saving the same value with the same flag is not a change.
|
|
226
|
+
await setRow("agent", id, key, key === "MODEL_OVERRIDE" ? "gpt-6.1-sol" : "xhigh");
|
|
227
|
+
expect(await getAgentDefaultProfileName(id)).toBe("codex-gpt-6.1-sol-xhigh");
|
|
228
|
+
|
|
229
|
+
await upsertSwarmConfig({
|
|
230
|
+
scope: "agent",
|
|
231
|
+
scopeId: id,
|
|
232
|
+
key,
|
|
233
|
+
value: key === "MODEL_OVERRIDE" ? "gpt-6.1-sol" : "xhigh",
|
|
234
|
+
exportToEnv: false,
|
|
235
|
+
});
|
|
236
|
+
expect(await getAgentDefaultProfileName(id)).toBeNull();
|
|
237
|
+
expect(await workerSelection(id)).toEqual(
|
|
238
|
+
key === "MODEL_OVERRIDE"
|
|
239
|
+
? { model: null, effort: "xhigh" }
|
|
240
|
+
: { model: "gpt-6.1-sol", effort: null },
|
|
241
|
+
);
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
test("exporting a withheld override again also clears a default set since", async () => {
|
|
246
|
+
const id = await codexAgent("bc-reexport");
|
|
247
|
+
await setRow("agent", id, "MODEL_OVERRIDE", "gpt-6.1-sol");
|
|
248
|
+
await setRow("agent", id, "REASONING_EFFORT_OVERRIDE", "xhigh", false);
|
|
249
|
+
getDb().exec(agentOverrideMigrationSql());
|
|
250
|
+
expect(await getAgentDefaultProfileName(id)).toBe("codex-gpt-6.1-sol");
|
|
251
|
+
|
|
252
|
+
await setRow("agent", id, "REASONING_EFFORT_OVERRIDE", "xhigh", true);
|
|
253
|
+
expect(await getAgentDefaultProfileName(id)).toBeNull();
|
|
254
|
+
expect(await workerSelection(id)).toEqual({ model: "gpt-6.1-sol", effort: "xhigh" });
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
describe("repo-scoped overrides still beat a migrated agent default", () => {
|
|
259
|
+
async function agentAndRepo(name: string): Promise<{ id: string; repoId: string }> {
|
|
260
|
+
const id = await codexAgent(name);
|
|
261
|
+
await setRow("agent", id, "MODEL_OVERRIDE", "gpt-6.1-sol");
|
|
262
|
+
await setRow("agent", id, "REASONING_EFFORT_OVERRIDE", "xhigh");
|
|
263
|
+
const repo = await createSwarmRepo({
|
|
264
|
+
url: `https://github.com/${REPO}`,
|
|
265
|
+
name: "backcompat-repo",
|
|
266
|
+
});
|
|
267
|
+
return { id, repoId: repo.id };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
test("repo model and effort: the task runs the repo's, as before", async () => {
|
|
271
|
+
const { id, repoId } = await agentAndRepo("bc-repo-both");
|
|
272
|
+
await setRow("repo", repoId, "MODEL_OVERRIDE", "gpt-5.6-luna");
|
|
273
|
+
await setRow("repo", repoId, "REASONING_EFFORT_OVERRIDE", "low");
|
|
274
|
+
|
|
275
|
+
const { before, after } = await beforeAndAfterMigration(id, REPO);
|
|
276
|
+
expect(before).toEqual({ model: "gpt-5.6-luna", effort: "low" });
|
|
277
|
+
expect(after).toEqual(before);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
test("repo effort only: the agent's model with the repo's effort", async () => {
|
|
281
|
+
const { id, repoId } = await agentAndRepo("bc-repo-effort");
|
|
282
|
+
await setRow("repo", repoId, "REASONING_EFFORT_OVERRIDE", "low");
|
|
283
|
+
|
|
284
|
+
const { before, after } = await beforeAndAfterMigration(id, REPO);
|
|
285
|
+
expect(before).toEqual({ model: "gpt-6.1-sol", effort: "low" });
|
|
286
|
+
expect(after).toEqual(before);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
test("repo model only: the repo's model with the agent's effort", async () => {
|
|
290
|
+
const { id, repoId } = await agentAndRepo("bc-repo-model");
|
|
291
|
+
await setRow("repo", repoId, "MODEL_OVERRIDE", "gpt-5.6-luna");
|
|
292
|
+
|
|
293
|
+
const { before, after } = await beforeAndAfterMigration(id, REPO);
|
|
294
|
+
expect(before).toEqual({ model: "gpt-5.6-luna", effort: "xhigh" });
|
|
295
|
+
expect(after).toEqual(before);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test("withheld repo rows remove the agent's values, as before", async () => {
|
|
299
|
+
const { id, repoId } = await agentAndRepo("bc-repo-withheld");
|
|
300
|
+
await setRow("repo", repoId, "MODEL_OVERRIDE", "gpt-5.6-luna", false);
|
|
301
|
+
await setRow("repo", repoId, "REASONING_EFFORT_OVERRIDE", "low", false);
|
|
302
|
+
|
|
303
|
+
const { before, after } = await beforeAndAfterMigration(id, REPO);
|
|
304
|
+
expect(before).toEqual({ model: null, effort: null });
|
|
305
|
+
expect(after).toEqual(before);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
test("control: a task outside the repo, or a repo with no override, runs the default", async () => {
|
|
309
|
+
const { id } = await agentAndRepo("bc-repo-none");
|
|
310
|
+
const { before, after } = await beforeAndAfterMigration(id, REPO);
|
|
311
|
+
expect(before).toEqual({ model: "gpt-6.1-sol", effort: "xhigh" });
|
|
312
|
+
expect(after).toEqual(before);
|
|
313
|
+
expect(await workerSelection(id)).toEqual(before);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
test("a task's own profile still beats repo config", async () => {
|
|
317
|
+
const { id, repoId } = await agentAndRepo("bc-repo-task-profile");
|
|
318
|
+
await setRow("repo", repoId, "MODEL_OVERRIDE", "gpt-5.6-luna");
|
|
319
|
+
await setRow("repo", repoId, "REASONING_EFFORT_OVERRIDE", "low");
|
|
320
|
+
getDb().exec(agentOverrideMigrationSql());
|
|
321
|
+
|
|
322
|
+
expect(await workerSelection(id, REPO, "codex-gpt-6.1-sol-xhigh")).toEqual({
|
|
323
|
+
model: "gpt-6.1-sol",
|
|
324
|
+
effort: "xhigh",
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
});
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
// Model profiles on the OpenAI-compatible providers pi and opencode run
|
|
2
|
+
// (NaN `nan/*`, Helmcode `helmcode/*`, Vercel AI Gateway `vercel/<vendor>/*`):
|
|
3
|
+
// validation over HTTP, the harness filter, claim, the credential the worker
|
|
4
|
+
// then picks for the claimed model, and migration 220's MODEL_OVERRIDE
|
|
5
|
+
// conversion.
|
|
6
|
+
|
|
7
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
8
|
+
import { readFileSync } from "node:fs";
|
|
9
|
+
import { unlink } from "node:fs/promises";
|
|
10
|
+
import { createServer, type Server } from "node:http";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import {
|
|
13
|
+
closeDb,
|
|
14
|
+
createAgent,
|
|
15
|
+
createTaskExtended,
|
|
16
|
+
getDb,
|
|
17
|
+
getDbClient,
|
|
18
|
+
initDb,
|
|
19
|
+
upsertSwarmConfig,
|
|
20
|
+
} from "../be/db";
|
|
21
|
+
import { getAgentDefaultProfileName } from "../be/db/model-profiles";
|
|
22
|
+
import { fetchResolvedEnv } from "../commands/runner";
|
|
23
|
+
import { handleModelProfiles } from "../http/model-profiles";
|
|
24
|
+
import { handlePoll } from "../http/poll";
|
|
25
|
+
import { ModelProfileNameSchema } from "../types";
|
|
26
|
+
import { setRequestAuth } from "../utils/request-auth-context";
|
|
27
|
+
import { listenOnFreePort } from "./test-net";
|
|
28
|
+
|
|
29
|
+
const TEST_DB_PATH = "./test-model-profile-providers.sqlite";
|
|
30
|
+
|
|
31
|
+
/** One catalog model per provider, the key the worker must pick for it, and a level it supports. */
|
|
32
|
+
const PROVIDERS = [
|
|
33
|
+
{ provider: "nan", model: "nan/glm5.3", keyType: "NAN_API_KEY", effort: "high" },
|
|
34
|
+
{
|
|
35
|
+
provider: "helmcode",
|
|
36
|
+
model: "helmcode/deepseek-v4-flash",
|
|
37
|
+
keyType: "HELMCODE_API_KEY",
|
|
38
|
+
effort: "off",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
provider: "vercel",
|
|
42
|
+
model: "vercel/anthropic/claude-sonnet-4.6",
|
|
43
|
+
keyType: "AI_GATEWAY_API_KEY",
|
|
44
|
+
effort: "medium",
|
|
45
|
+
},
|
|
46
|
+
] as const;
|
|
47
|
+
|
|
48
|
+
const HARNESSES = ["pi", "opencode"] as const;
|
|
49
|
+
|
|
50
|
+
async function removeDbFiles(path: string): Promise<void> {
|
|
51
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
52
|
+
try {
|
|
53
|
+
await unlink(path + suffix);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let server: Server;
|
|
61
|
+
let baseUrl = "";
|
|
62
|
+
let configServer: ReturnType<typeof Bun.serve>;
|
|
63
|
+
let configUrl = "";
|
|
64
|
+
|
|
65
|
+
beforeAll(async () => {
|
|
66
|
+
await removeDbFiles(TEST_DB_PATH);
|
|
67
|
+
initDb(TEST_DB_PATH);
|
|
68
|
+
server = createServer((req, res) => {
|
|
69
|
+
setRequestAuth(req, { kind: "operator", fingerprint: "test" });
|
|
70
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
71
|
+
const segments = url.pathname.split("/").filter(Boolean);
|
|
72
|
+
void (async () => {
|
|
73
|
+
if (await handleModelProfiles(req, res, segments, url.searchParams, undefined)) return;
|
|
74
|
+
res.statusCode = 404;
|
|
75
|
+
res.end("{}");
|
|
76
|
+
})();
|
|
77
|
+
});
|
|
78
|
+
baseUrl = `http://localhost:${await listenOnFreePort(server)}`;
|
|
79
|
+
// Every provider key is configured, so the pick shows which one the model routes to.
|
|
80
|
+
configServer = Bun.serve({
|
|
81
|
+
port: 0,
|
|
82
|
+
fetch(req) {
|
|
83
|
+
if (new URL(req.url).pathname === "/api/config/resolved") {
|
|
84
|
+
return Response.json({
|
|
85
|
+
configs: [
|
|
86
|
+
{ key: "OPENROUTER_API_KEY", value: "fixture-openrouter" },
|
|
87
|
+
{ key: "NAN_API_KEY", value: "fixture-nan" },
|
|
88
|
+
{ key: "HELMCODE_API_KEY", value: "fixture-helmcode" },
|
|
89
|
+
{ key: "AI_GATEWAY_API_KEY", value: "fixture-gateway" },
|
|
90
|
+
],
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
return new Response("Not found", { status: 404 });
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
configUrl = configServer.url.toString().replace(/\/$/, "");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
afterAll(async () => {
|
|
100
|
+
server.close();
|
|
101
|
+
configServer.stop(true);
|
|
102
|
+
closeDb();
|
|
103
|
+
await removeDbFiles(TEST_DB_PATH);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
beforeEach(async () => {
|
|
107
|
+
const client = getDbClient();
|
|
108
|
+
await client.run("DELETE FROM agent_tasks");
|
|
109
|
+
await client.run("DELETE FROM swarm_config");
|
|
110
|
+
await client.run("DELETE FROM agents");
|
|
111
|
+
await client.run("DELETE FROM model_profiles");
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
function post(body: Record<string, unknown>): Promise<Response> {
|
|
115
|
+
return fetch(`${baseUrl}/api/model-profiles`, {
|
|
116
|
+
method: "POST",
|
|
117
|
+
headers: { "content-type": "application/json" },
|
|
118
|
+
body: JSON.stringify(body),
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function errorOf(response: Response): Promise<string> {
|
|
123
|
+
return ((await response.json()) as { error: string }).error;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
type ClaimedTask = { resolvedModel?: string; modelSource?: string; effort?: string };
|
|
127
|
+
|
|
128
|
+
async function claim(agentId: string): Promise<ClaimedTask> {
|
|
129
|
+
let body = "";
|
|
130
|
+
const req = {
|
|
131
|
+
method: "GET",
|
|
132
|
+
url: "/api/poll",
|
|
133
|
+
headers: { "x-agent-id": agentId },
|
|
134
|
+
} as unknown as Parameters<typeof handlePoll>[0];
|
|
135
|
+
const res = {
|
|
136
|
+
setHeader() {},
|
|
137
|
+
writeHead() {},
|
|
138
|
+
end(chunk?: string) {
|
|
139
|
+
body = chunk ?? "";
|
|
140
|
+
},
|
|
141
|
+
} as unknown as Parameters<typeof handlePoll>[1];
|
|
142
|
+
await handlePoll(req, res, ["api", "poll"], new URLSearchParams(), agentId);
|
|
143
|
+
const trigger = (JSON.parse(body) as { trigger: { task: ClaimedTask } | null }).trigger;
|
|
144
|
+
if (!trigger) throw new Error("nothing claimed");
|
|
145
|
+
return trigger.task;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
describe("a profile accepts every provider pi and opencode run", () => {
|
|
149
|
+
for (const harness of HARNESSES) {
|
|
150
|
+
for (const { provider, model, effort } of PROVIDERS) {
|
|
151
|
+
test(`${harness} + ${provider}: created with a supported effort and listed under its harness`, async () => {
|
|
152
|
+
const name = `${harness}-${provider}`;
|
|
153
|
+
const created = await post({ name, harness, model, effort });
|
|
154
|
+
expect(created.status).toBe(201);
|
|
155
|
+
expect(await created.json()).toMatchObject({ name, harness, model, effort });
|
|
156
|
+
|
|
157
|
+
const listed = await fetch(`${baseUrl}/api/model-profiles?harness=${harness}`);
|
|
158
|
+
const { profiles } = (await listed.json()) as { profiles: { name: string }[] };
|
|
159
|
+
expect(profiles.map((p) => p.name)).toEqual([name]);
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
test("an id the provider's catalog lacks is 400 unless allowCustomModel", async () => {
|
|
165
|
+
for (const model of ["nan/no-such-model", "helmcode/no-such-model", "vercel/acme/no-such"]) {
|
|
166
|
+
const refused = await post({ name: "pi-unknown", harness: "pi", model });
|
|
167
|
+
expect(refused.status).toBe(400);
|
|
168
|
+
expect(await errorOf(refused)).toContain("not in the model catalog");
|
|
169
|
+
}
|
|
170
|
+
const custom = await post({
|
|
171
|
+
name: "pi-gateway-custom",
|
|
172
|
+
harness: "pi",
|
|
173
|
+
model: "vercel/acme/fresh-launch",
|
|
174
|
+
allowCustomModel: true,
|
|
175
|
+
});
|
|
176
|
+
expect(custom.status).toBe(201);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("a provider model on a harness that cannot run it is 400", async () => {
|
|
180
|
+
for (const harness of ["claude", "codex"]) {
|
|
181
|
+
for (const { model } of PROVIDERS) {
|
|
182
|
+
const refused = await post({ name: `${harness}-x`, harness, model });
|
|
183
|
+
expect(refused.status).toBe(400);
|
|
184
|
+
expect(await errorOf(refused)).toContain(`does not run on the ${harness} harness`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test("an effort the model does not list is 400", async () => {
|
|
190
|
+
const refused = await post({
|
|
191
|
+
name: "pi-nan-xhigh",
|
|
192
|
+
harness: "pi",
|
|
193
|
+
model: "nan/glm5.3",
|
|
194
|
+
effort: "xhigh",
|
|
195
|
+
});
|
|
196
|
+
expect(refused.status).toBe(400);
|
|
197
|
+
expect(await errorOf(refused)).toContain("not supported");
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
describe("claim hands the provider model to the worker, which picks that provider's key", () => {
|
|
202
|
+
for (const harness of HARNESSES) {
|
|
203
|
+
for (const { provider, model, keyType, effort } of PROVIDERS) {
|
|
204
|
+
test(`${harness} + ${provider}`, async () => {
|
|
205
|
+
const name = `${harness}-${provider}`;
|
|
206
|
+
expect((await post({ name, harness, model, effort })).status).toBe(201);
|
|
207
|
+
const agent = await createAgent({
|
|
208
|
+
name: `w-${name}`,
|
|
209
|
+
isLead: false,
|
|
210
|
+
status: "idle",
|
|
211
|
+
harnessProvider: harness,
|
|
212
|
+
});
|
|
213
|
+
await createTaskExtended("work", { agentId: agent.id, profile: name });
|
|
214
|
+
|
|
215
|
+
const task = await claim(agent.id);
|
|
216
|
+
expect(task).toMatchObject({ resolvedModel: model, modelSource: "profile:task", effort });
|
|
217
|
+
|
|
218
|
+
const { credentialSelections } = await fetchResolvedEnv(
|
|
219
|
+
configUrl,
|
|
220
|
+
"key",
|
|
221
|
+
agent.id,
|
|
222
|
+
{},
|
|
223
|
+
task.resolvedModel,
|
|
224
|
+
{ provider: harness },
|
|
225
|
+
);
|
|
226
|
+
expect(credentialSelections.map((s) => s.keyType)).toEqual([keyType]);
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
describe("migration 220 converts provider MODEL_OVERRIDEs", () => {
|
|
233
|
+
function agentOverrideMigrationSql(): string {
|
|
234
|
+
const sql = readFileSync(
|
|
235
|
+
join(import.meta.dir, "../be/migrations/220_model_profiles.sql"),
|
|
236
|
+
"utf8",
|
|
237
|
+
);
|
|
238
|
+
return sql.slice(sql.indexOf("CREATE TEMP TABLE _model_profile_overrides"));
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
test("each becomes a valid, harness-scoped default the next claim runs", async () => {
|
|
242
|
+
const cases = [
|
|
243
|
+
{ harness: "pi", model: "nan/glm5.3", effort: "high", name: "pi-nan-glm5.3-high" },
|
|
244
|
+
{
|
|
245
|
+
harness: "opencode",
|
|
246
|
+
model: "helmcode/deepseek-v4-flash",
|
|
247
|
+
effort: null,
|
|
248
|
+
name: "opencode-helmcode-deepseek-v4-flash",
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
harness: "pi",
|
|
252
|
+
model: "vercel/anthropic/claude-sonnet-4.6",
|
|
253
|
+
effort: "medium",
|
|
254
|
+
name: "pi-vercel-anthropic-claude-sonnet-4.6-medium",
|
|
255
|
+
},
|
|
256
|
+
] as const;
|
|
257
|
+
const agents: string[] = [];
|
|
258
|
+
for (const c of cases) {
|
|
259
|
+
const agent = await createAgent({
|
|
260
|
+
name: `m-${c.name}`,
|
|
261
|
+
isLead: false,
|
|
262
|
+
status: "idle",
|
|
263
|
+
harnessProvider: c.harness,
|
|
264
|
+
});
|
|
265
|
+
agents.push(agent.id);
|
|
266
|
+
await upsertSwarmConfig({
|
|
267
|
+
scope: "agent",
|
|
268
|
+
scopeId: agent.id,
|
|
269
|
+
key: "MODEL_OVERRIDE",
|
|
270
|
+
value: c.model,
|
|
271
|
+
});
|
|
272
|
+
if (c.effort) {
|
|
273
|
+
await upsertSwarmConfig({
|
|
274
|
+
scope: "agent",
|
|
275
|
+
scopeId: agent.id,
|
|
276
|
+
key: "REASONING_EFFORT_OVERRIDE",
|
|
277
|
+
value: c.effort,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
getDb().exec(agentOverrideMigrationSql());
|
|
283
|
+
|
|
284
|
+
for (const [i, c] of cases.entries()) {
|
|
285
|
+
const agentId = agents[i] as string;
|
|
286
|
+
expect(await getAgentDefaultProfileName(agentId)).toBe(c.name);
|
|
287
|
+
// The generated name passes the API's name rule, so the profile stays addressable.
|
|
288
|
+
expect(ModelProfileNameSchema.safeParse(c.name).success).toBe(true);
|
|
289
|
+
const fetched = await fetch(`${baseUrl}/api/model-profiles/${c.name}`);
|
|
290
|
+
expect(await fetched.json()).toMatchObject({
|
|
291
|
+
name: c.name,
|
|
292
|
+
harness: c.harness,
|
|
293
|
+
model: c.model,
|
|
294
|
+
effort: c.effort,
|
|
295
|
+
});
|
|
296
|
+
await createTaskExtended("work", { agentId });
|
|
297
|
+
const task = await claim(agentId);
|
|
298
|
+
expect(task).toMatchObject({ resolvedModel: c.model, modelSource: "profile:agent-default" });
|
|
299
|
+
if (c.effort) expect(task.effort).toBe(c.effort);
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
});
|