@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,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The provider key splice and the `helmcode` block, run through the real
|
|
3
|
+
* `@opencode/sdk` embedded host against a fake OpenAI-compatible server.
|
|
4
|
+
*
|
|
5
|
+
* `applyProviderKeySplice` builds the provider entry; the test only repoints
|
|
6
|
+
* `options.baseURL` at a local `Bun.serve({ port: 0 })` that answers
|
|
7
|
+
* `/v1/chat/completions`, then checks the request OpenCode sent: the picked
|
|
8
|
+
* pool member as the bearer (not the comma pool sitting in `process.env`) and
|
|
9
|
+
* the bare model id. The model catalog is bundled in the SDK, and no request
|
|
10
|
+
* leaves the machine. The control test pins why `env: []` is needed: without
|
|
11
|
+
* it, the pool in `process.env` beats `options.apiKey`.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
15
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
16
|
+
import { tmpdir } from "node:os";
|
|
17
|
+
import { join } from "node:path";
|
|
18
|
+
import { OpenCode, type OpenCodeEvent } from "@opencode/sdk";
|
|
19
|
+
import { applyProviderKeySplice, type OpencodeConfig } from "../providers/opencode-adapter";
|
|
20
|
+
|
|
21
|
+
const TEST_TIMEOUT_MS = 60_000;
|
|
22
|
+
|
|
23
|
+
const PICKED = "sk-helmcode-picked-live-0123456789";
|
|
24
|
+
const POOL_IN_PROCESS_ENV = "sk-helmcode-wrong-aaaaaaaaaa,sk-helmcode-wrong-bbbbbbbbbb";
|
|
25
|
+
|
|
26
|
+
type SeenRequest = { path: string; authorization: string | null; model?: string };
|
|
27
|
+
|
|
28
|
+
let server: ReturnType<typeof Bun.serve>;
|
|
29
|
+
let directory: string;
|
|
30
|
+
const seen: SeenRequest[] = [];
|
|
31
|
+
let prevHelmcodeKey: string | undefined;
|
|
32
|
+
|
|
33
|
+
/** One streamed (or plain) assistant turn that says "pong". */
|
|
34
|
+
function completion(model: string, stream: boolean): Response {
|
|
35
|
+
const base = { id: "chatcmpl-test", created: 0, model };
|
|
36
|
+
const usage = { prompt_tokens: 5, completion_tokens: 1, total_tokens: 6 };
|
|
37
|
+
if (!stream) {
|
|
38
|
+
return Response.json({
|
|
39
|
+
...base,
|
|
40
|
+
object: "chat.completion",
|
|
41
|
+
choices: [
|
|
42
|
+
{
|
|
43
|
+
index: 0,
|
|
44
|
+
message: { role: "assistant", content: "pong" },
|
|
45
|
+
finish_reason: "stop",
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
usage,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
const chunk = (body: Record<string, unknown>) =>
|
|
52
|
+
`data: ${JSON.stringify({ ...base, object: "chat.completion.chunk", ...body })}\n\n`;
|
|
53
|
+
const sse =
|
|
54
|
+
chunk({
|
|
55
|
+
choices: [{ index: 0, delta: { role: "assistant", content: "pong" }, finish_reason: null }],
|
|
56
|
+
}) +
|
|
57
|
+
chunk({ choices: [{ index: 0, delta: {}, finish_reason: "stop" }], usage }) +
|
|
58
|
+
"data: [DONE]\n\n";
|
|
59
|
+
return new Response(sse, { headers: { "content-type": "text/event-stream" } });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
beforeAll(() => {
|
|
63
|
+
directory = mkdtempSync(join(tmpdir(), "opencode-helmcode-sdk-"));
|
|
64
|
+
server = Bun.serve({
|
|
65
|
+
port: 0,
|
|
66
|
+
async fetch(req) {
|
|
67
|
+
const url = new URL(req.url);
|
|
68
|
+
const body = req.method === "POST" ? ((await req.json().catch(() => ({}))) as any) : {};
|
|
69
|
+
seen.push({
|
|
70
|
+
path: url.pathname,
|
|
71
|
+
authorization: req.headers.get("authorization"),
|
|
72
|
+
model: body?.model,
|
|
73
|
+
});
|
|
74
|
+
if (url.pathname.endsWith("/chat/completions")) {
|
|
75
|
+
return completion(String(body?.model ?? ""), body?.stream === true);
|
|
76
|
+
}
|
|
77
|
+
return new Response("not found", { status: 404 });
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
// The pool a production entrypoint exports raw; the splice must beat it.
|
|
81
|
+
prevHelmcodeKey = process.env.HELMCODE_API_KEY;
|
|
82
|
+
process.env.HELMCODE_API_KEY = POOL_IN_PROCESS_ENV;
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
afterAll(() => {
|
|
86
|
+
server.stop(true);
|
|
87
|
+
rmSync(directory, { recursive: true, force: true });
|
|
88
|
+
if (prevHelmcodeKey === undefined) delete process.env.HELMCODE_API_KEY;
|
|
89
|
+
else process.env.HELMCODE_API_KEY = prevHelmcodeKey;
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
async function runOnePrompt(config: OpencodeConfig): Promise<string> {
|
|
93
|
+
const host = await OpenCode.create({
|
|
94
|
+
config: { content: JSON.stringify(config) },
|
|
95
|
+
plugins: [],
|
|
96
|
+
log: { level: "error", emit: () => {} },
|
|
97
|
+
});
|
|
98
|
+
try {
|
|
99
|
+
const session = await host.sessions.create({ location: { directory } });
|
|
100
|
+
const done = (async () => {
|
|
101
|
+
for await (const event of host.events.subscribe() as AsyncIterable<OpenCodeEvent>) {
|
|
102
|
+
const data = (event as { data?: { sessionID?: string; error?: unknown } }).data;
|
|
103
|
+
if (data?.sessionID !== session.id) continue;
|
|
104
|
+
if (event.type === "session.execution.succeeded") return "succeeded";
|
|
105
|
+
if (event.type === "session.execution.failed") {
|
|
106
|
+
return `failed: ${JSON.stringify(data.error)}`;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return "stream ended";
|
|
110
|
+
})();
|
|
111
|
+
await host.sessions.prompt({ sessionID: session.id, text: "say pong", delivery: "queue" });
|
|
112
|
+
return await done;
|
|
113
|
+
} finally {
|
|
114
|
+
await host.close();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
describe("provider key splice through the real OpenCode host", () => {
|
|
119
|
+
test(
|
|
120
|
+
"sends the picked key and the bare model id to the provider's baseURL",
|
|
121
|
+
async () => {
|
|
122
|
+
seen.length = 0;
|
|
123
|
+
const model = "helmcode/glm5.3";
|
|
124
|
+
const config: OpencodeConfig = { model };
|
|
125
|
+
const spliced = applyProviderKeySplice(config, model, { HELMCODE_API_KEY: PICKED });
|
|
126
|
+
expect(spliced?.name).toBe("HELMCODE_API_KEY");
|
|
127
|
+
const helmcode = config.provider?.helmcode;
|
|
128
|
+
if (!helmcode?.options) throw new Error("helmcode block missing");
|
|
129
|
+
helmcode.options.baseURL = `http://127.0.0.1:${server.port}/v1`;
|
|
130
|
+
|
|
131
|
+
const outcome = await runOnePrompt(config);
|
|
132
|
+
|
|
133
|
+
const chats = seen.filter((r) => r.path === "/v1/chat/completions");
|
|
134
|
+
expect(chats.length).toBeGreaterThan(0);
|
|
135
|
+
for (const chat of chats) {
|
|
136
|
+
expect(chat.authorization).toBe(`Bearer ${PICKED}`);
|
|
137
|
+
expect(chat.model).toBe("glm5.3");
|
|
138
|
+
}
|
|
139
|
+
expect(outcome).toBe("succeeded");
|
|
140
|
+
},
|
|
141
|
+
TEST_TIMEOUT_MS,
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
test(
|
|
145
|
+
"nan (built-in provider): env [] plus the picked key beats a pool in process.env",
|
|
146
|
+
async () => {
|
|
147
|
+
const prevNan = process.env.NAN_API_KEY;
|
|
148
|
+
process.env.NAN_API_KEY = POOL_IN_PROCESS_ENV;
|
|
149
|
+
seen.length = 0;
|
|
150
|
+
try {
|
|
151
|
+
const model = "nan/glm5.3-flash";
|
|
152
|
+
const config: OpencodeConfig = { model };
|
|
153
|
+
const spliced = applyProviderKeySplice(config, model, { NAN_API_KEY: PICKED });
|
|
154
|
+
expect(spliced?.name).toBe("NAN_API_KEY");
|
|
155
|
+
const nan = config.provider?.nan;
|
|
156
|
+
if (!nan?.options) throw new Error("nan block missing");
|
|
157
|
+
nan.options.baseURL = `http://127.0.0.1:${server.port}/v1`;
|
|
158
|
+
|
|
159
|
+
const outcome = await runOnePrompt(config);
|
|
160
|
+
|
|
161
|
+
const chats = seen.filter((r) => r.path === "/v1/chat/completions");
|
|
162
|
+
expect(chats.length).toBeGreaterThan(0);
|
|
163
|
+
for (const chat of chats) {
|
|
164
|
+
expect(chat.authorization).toBe(`Bearer ${PICKED}`);
|
|
165
|
+
expect(chat.model).toBe("glm5.3-flash");
|
|
166
|
+
}
|
|
167
|
+
expect(outcome).toBe("succeeded");
|
|
168
|
+
} finally {
|
|
169
|
+
if (prevNan === undefined) delete process.env.NAN_API_KEY;
|
|
170
|
+
else process.env.NAN_API_KEY = prevNan;
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
TEST_TIMEOUT_MS,
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
test(
|
|
177
|
+
"nan control: without the splice the pool in process.env reaches the provider",
|
|
178
|
+
async () => {
|
|
179
|
+
const prevNan = process.env.NAN_API_KEY;
|
|
180
|
+
process.env.NAN_API_KEY = POOL_IN_PROCESS_ENV;
|
|
181
|
+
seen.length = 0;
|
|
182
|
+
try {
|
|
183
|
+
const model = "nan/glm5.3-flash";
|
|
184
|
+
const config: OpencodeConfig = {
|
|
185
|
+
model,
|
|
186
|
+
provider: {
|
|
187
|
+
nan: { options: { apiKey: PICKED, baseURL: `http://127.0.0.1:${server.port}/v1` } },
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
await runOnePrompt(config);
|
|
191
|
+
const chats = seen.filter((r) => r.path === "/v1/chat/completions");
|
|
192
|
+
expect(chats.length).toBeGreaterThan(0);
|
|
193
|
+
expect(chats[0]?.authorization).not.toBe(`Bearer ${PICKED}`);
|
|
194
|
+
} finally {
|
|
195
|
+
if (prevNan === undefined) delete process.env.NAN_API_KEY;
|
|
196
|
+
else process.env.NAN_API_KEY = prevNan;
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
TEST_TIMEOUT_MS,
|
|
200
|
+
);
|
|
201
|
+
});
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import {
|
|
2
|
+
afterAll,
|
|
3
|
+
afterEach,
|
|
4
|
+
beforeAll,
|
|
5
|
+
beforeEach,
|
|
6
|
+
describe,
|
|
7
|
+
expect,
|
|
8
|
+
spyOn,
|
|
9
|
+
test,
|
|
10
|
+
} from "bun:test";
|
|
11
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
12
|
+
import { tmpdir } from "node:os";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { OpenCode } from "@opencode/sdk";
|
|
15
|
+
import { fetchResolvedEnv } from "../commands/runner";
|
|
16
|
+
import { createOpencodeSwarmPlugin } from "../providers/opencode-swarm-plugin";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* OpenCode runs in-process, so its shell tool would spawn with the worker's
|
|
20
|
+
* process.env. The swarm plugin swaps in the env the runner resolved for the
|
|
21
|
+
* task: a row admitted after boot (exportToEnv flipped on) must reach the
|
|
22
|
+
* shell, and a row revoked after boot (flipped off) must leave it, even though
|
|
23
|
+
* process.env still holds the boot value.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
type ConfigRow = { key: string; value: string; isSecret: boolean; exportToEnv: boolean };
|
|
27
|
+
|
|
28
|
+
const SHELL_TIMEOUT_MS = 15_000;
|
|
29
|
+
|
|
30
|
+
let server: ReturnType<typeof Bun.serve>;
|
|
31
|
+
let apiUrl = "";
|
|
32
|
+
let rows: ConfigRow[] = [];
|
|
33
|
+
let directory = "";
|
|
34
|
+
|
|
35
|
+
beforeAll(() => {
|
|
36
|
+
server = Bun.serve({
|
|
37
|
+
port: 0,
|
|
38
|
+
fetch: () => Response.json({ configs: rows }),
|
|
39
|
+
});
|
|
40
|
+
apiUrl = `http://localhost:${server.port}`;
|
|
41
|
+
directory = mkdtempSync(join(tmpdir(), "opencode-shell-env-"));
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
afterAll(() => {
|
|
45
|
+
server.stop(true);
|
|
46
|
+
rmSync(directory, { recursive: true, force: true });
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
type Host = Awaited<ReturnType<typeof OpenCode.create>>;
|
|
50
|
+
|
|
51
|
+
async function hostWithEnv(env: Record<string, string | undefined>): Promise<Host> {
|
|
52
|
+
const plugin = createOpencodeSwarmPlugin({
|
|
53
|
+
apiUrl: "http://127.0.0.1:9",
|
|
54
|
+
apiKey: "",
|
|
55
|
+
agentId: "agent-1",
|
|
56
|
+
taskId: "",
|
|
57
|
+
isLead: false,
|
|
58
|
+
env,
|
|
59
|
+
});
|
|
60
|
+
return OpenCode.create({ plugins: [plugin], log: { level: "error", emit: () => {} } });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Runs one command as a session shell call, the path the agent's shell tool
|
|
65
|
+
* takes (`host.shell.create` skips plugin hooks), and returns its output.
|
|
66
|
+
*/
|
|
67
|
+
async function shell(host: Host, command: string): Promise<string> {
|
|
68
|
+
const session = await host.sessions.create({ location: { directory } });
|
|
69
|
+
const outFile = join(directory, `out-${crypto.randomUUID()}.txt`);
|
|
70
|
+
await host.sessions.shell({
|
|
71
|
+
sessionID: session.id,
|
|
72
|
+
command: `{ ${command}; } > ${outFile}.tmp 2>&1; mv ${outFile}.tmp ${outFile}`,
|
|
73
|
+
});
|
|
74
|
+
const deadline = Date.now() + SHELL_TIMEOUT_MS;
|
|
75
|
+
while (!(await Bun.file(outFile).exists())) {
|
|
76
|
+
if (Date.now() > deadline) throw new Error("shell did not exit");
|
|
77
|
+
await Bun.sleep(25);
|
|
78
|
+
}
|
|
79
|
+
return (await Bun.file(outFile).text()).trim();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
describe("OpenCode shell env (real OpenCode host)", () => {
|
|
83
|
+
const envKeys = ["SHELL_REVOKED_CANARY", "SHELL_ADMITTED_CANARY"] as const;
|
|
84
|
+
const saved: Record<string, string | undefined> = {};
|
|
85
|
+
let log: ReturnType<typeof spyOn>;
|
|
86
|
+
|
|
87
|
+
beforeEach(() => {
|
|
88
|
+
for (const key of envKeys) saved[key] = process.env[key];
|
|
89
|
+
log = spyOn(console, "log").mockImplementation(() => {});
|
|
90
|
+
});
|
|
91
|
+
afterEach(() => {
|
|
92
|
+
log.mockRestore();
|
|
93
|
+
for (const key of envKeys) {
|
|
94
|
+
if (saved[key] === undefined) delete process.env[key];
|
|
95
|
+
else process.env[key] = saved[key];
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test(
|
|
100
|
+
"admission and revocation after boot reach the shell",
|
|
101
|
+
async () => {
|
|
102
|
+
process.env.SHELL_REVOKED_CANARY = "boot-revoked";
|
|
103
|
+
delete process.env.SHELL_ADMITTED_CANARY;
|
|
104
|
+
rows = [
|
|
105
|
+
{ key: "SHELL_REVOKED_CANARY", value: "row-revoked", isSecret: true, exportToEnv: false },
|
|
106
|
+
{ key: "SHELL_ADMITTED_CANARY", value: "row-admitted", isSecret: true, exportToEnv: true },
|
|
107
|
+
];
|
|
108
|
+
const { env } = await fetchResolvedEnv(apiUrl, "key", "agent-1", process.env);
|
|
109
|
+
const host = await hostWithEnv(env);
|
|
110
|
+
try {
|
|
111
|
+
const out = await shell(
|
|
112
|
+
host,
|
|
113
|
+
'echo "admitted=$SHELL_ADMITTED_CANARY revoked=$SHELL_REVOKED_CANARY term=$OPENCODE_TERMINAL."; tr "\\0" "\\n" < /proc/$$/environ | grep -c "^SHELL_REVOKED_CANARY=" || true',
|
|
114
|
+
);
|
|
115
|
+
expect(out).toContain("admitted=row-admitted revoked= term=1.");
|
|
116
|
+
expect(out.split("\n").at(-1)).toBe("0");
|
|
117
|
+
} finally {
|
|
118
|
+
await host.close();
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
SHELL_TIMEOUT_MS * 2,
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
test(
|
|
125
|
+
"concurrent hosts keep their own env",
|
|
126
|
+
async () => {
|
|
127
|
+
const [first, second] = await Promise.all([
|
|
128
|
+
hostWithEnv({ ...process.env, SHELL_ADMITTED_CANARY: "first" }),
|
|
129
|
+
hostWithEnv({ ...process.env, SHELL_ADMITTED_CANARY: undefined }),
|
|
130
|
+
]);
|
|
131
|
+
try {
|
|
132
|
+
const [a, b] = await Promise.all([
|
|
133
|
+
shell(first, 'echo "v=$SHELL_ADMITTED_CANARY"'),
|
|
134
|
+
shell(second, 'echo "v=$SHELL_ADMITTED_CANARY"'),
|
|
135
|
+
]);
|
|
136
|
+
expect(a).toBe("v=first");
|
|
137
|
+
expect(b).toBe("v=");
|
|
138
|
+
expect(process.env.SHELL_ADMITTED_CANARY).toBeUndefined();
|
|
139
|
+
} finally {
|
|
140
|
+
await Promise.all([first.close(), second.close()]);
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
SHELL_TIMEOUT_MS * 2,
|
|
144
|
+
);
|
|
145
|
+
});
|
|
@@ -2,16 +2,17 @@
|
|
|
2
2
|
* OPENROUTER_BASE_URL gateway routing — unit tests.
|
|
3
3
|
*
|
|
4
4
|
* Covers the shared resolver (`src/utils/openrouter-base-url.ts`), the pi
|
|
5
|
-
* models.json
|
|
6
|
-
* its composition through pi-coding-agent's ModelRuntime, and
|
|
5
|
+
* per-session models.json composer (`writeSessionModelsJson`) for every shared
|
|
6
|
+
* starting state, its composition through pi-coding-agent's ModelRuntime, and
|
|
7
|
+
* attribution host gating.
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
|
-
import {
|
|
10
|
-
import { mkdtempSync, rmSync } from "node:fs";
|
|
10
|
+
import { describe, expect, test } from "bun:test";
|
|
11
|
+
import { mkdtempSync, readdirSync, readFileSync, rmSync } from "node:fs";
|
|
11
12
|
import { tmpdir } from "node:os";
|
|
12
13
|
import { join } from "node:path";
|
|
13
14
|
import { ModelRuntime } from "@earendil-works/pi-coding-agent";
|
|
14
|
-
import {
|
|
15
|
+
import { writeSessionModelsJson } from "../providers/pi-mono-adapter";
|
|
15
16
|
import {
|
|
16
17
|
DEFAULT_OPENROUTER_BASE_URL,
|
|
17
18
|
getOpenRouterAttributionHeaders,
|
|
@@ -47,162 +48,111 @@ describe("getOpenRouterBaseUrl", () => {
|
|
|
47
48
|
});
|
|
48
49
|
});
|
|
49
50
|
|
|
50
|
-
describe("
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
51
|
+
describe("writeSessionModelsJson — OpenRouter gateway (pi)", () => {
|
|
52
|
+
const MARKER = ".agent-swarm-openrouter-override.json";
|
|
53
|
+
const LEGACY = "https://legacy-gateway.example/v1";
|
|
54
|
+
const USER = "https://user.example/v1";
|
|
55
|
+
const userOpenRouter = { baseUrl: USER, compat: { supportsTools: true } };
|
|
56
|
+
const custom = { baseUrl: "https://custom.example/v1", api: "openai-completions" };
|
|
57
|
+
|
|
58
|
+
/** Starting states of the shared agent dir; the marker ones are left by earlier versions. */
|
|
59
|
+
const states: Record<string, Record<string, string>> = {
|
|
60
|
+
"no models.json": {},
|
|
61
|
+
"hand-authored baseUrl": {
|
|
62
|
+
"models.json": JSON.stringify({ providers: { openrouter: userOpenRouter, custom } }),
|
|
63
|
+
},
|
|
64
|
+
"legacy override over a user baseUrl": {
|
|
65
|
+
"models.json": JSON.stringify({
|
|
66
|
+
providers: { openrouter: { ...userOpenRouter, baseUrl: LEGACY }, custom },
|
|
67
|
+
}),
|
|
68
|
+
[MARKER]: JSON.stringify({ baseUrl: LEGACY, previous: USER }),
|
|
69
|
+
},
|
|
70
|
+
"legacy override alone": {
|
|
71
|
+
"models.json": JSON.stringify({ providers: { openrouter: { baseUrl: LEGACY } } }),
|
|
72
|
+
[MARKER]: JSON.stringify({ baseUrl: LEGACY }),
|
|
73
|
+
},
|
|
74
|
+
"legacy marker, baseUrl edited by hand since": {
|
|
75
|
+
"models.json": JSON.stringify({ providers: { openrouter: { baseUrl: USER } } }),
|
|
76
|
+
[MARKER]: JSON.stringify({ baseUrl: LEGACY }),
|
|
77
|
+
},
|
|
78
|
+
"corrupt models.json": { "models.json": "{not json" },
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// The openrouter block the session's runtime loads, per state, for the
|
|
82
|
+
// default gateway and for OPENROUTER_BASE_URL=GATEWAY. `null`: no copy, and
|
|
83
|
+
// the session loads the shared file as it is.
|
|
84
|
+
const expected: Record<string, [defaultGateway: unknown, gateway: unknown]> = {
|
|
85
|
+
"no models.json": [null, { baseUrl: GATEWAY }],
|
|
86
|
+
"hand-authored baseUrl": [null, { ...userOpenRouter, baseUrl: GATEWAY }],
|
|
87
|
+
"legacy override over a user baseUrl": [
|
|
88
|
+
userOpenRouter,
|
|
89
|
+
{ ...userOpenRouter, baseUrl: GATEWAY },
|
|
90
|
+
],
|
|
91
|
+
"legacy override alone": [undefined, { baseUrl: GATEWAY }],
|
|
92
|
+
"legacy marker, baseUrl edited by hand since": [{ baseUrl: USER }, { baseUrl: GATEWAY }],
|
|
93
|
+
"corrupt models.json": [null, { baseUrl: GATEWAY }],
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
function snapshot(dir: string): Record<string, string> {
|
|
97
|
+
return Object.fromEntries(
|
|
98
|
+
readdirSync(dir)
|
|
99
|
+
.sort()
|
|
100
|
+
.map((name) => [name, readFileSync(join(dir, name), "utf-8")]),
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
for (const [state, files] of Object.entries(states)) {
|
|
105
|
+
for (const [i, env] of [{}, { OPENROUTER_BASE_URL: GATEWAY }].entries()) {
|
|
106
|
+
const label = i === 0 ? "default gateway" : "session gateway";
|
|
107
|
+
test(`${state}, ${label}: composed privately, shared dir untouched`, async () => {
|
|
108
|
+
const dir = mkdtempSync(join(tmpdir(), "pi-or-session-"));
|
|
109
|
+
try {
|
|
110
|
+
for (const [name, text] of Object.entries(files)) await Bun.write(join(dir, name), text);
|
|
111
|
+
const before = snapshot(dir);
|
|
112
|
+
const session = await writeSessionModelsJson(dir, env);
|
|
113
|
+
try {
|
|
114
|
+
const want = expected[state]?.[i];
|
|
115
|
+
if (want === null) {
|
|
116
|
+
expect(session.modelsPath).toBeUndefined();
|
|
117
|
+
} else {
|
|
118
|
+
expect(session.modelsPath).toBeDefined();
|
|
119
|
+
expect(session.modelsPath?.startsWith(dir)).toBe(false);
|
|
120
|
+
const copy = JSON.parse(readFileSync(session.modelsPath as string, "utf-8"));
|
|
121
|
+
expect(copy.providers?.openrouter).toEqual(want);
|
|
122
|
+
if (files["models.json"]?.includes("custom.example")) {
|
|
123
|
+
expect(copy.providers.custom).toEqual(custom);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
} finally {
|
|
127
|
+
await session.dispose();
|
|
128
|
+
}
|
|
129
|
+
expect(snapshot(dir)).toEqual(before);
|
|
130
|
+
} finally {
|
|
131
|
+
rmSync(dir, { recursive: true, force: true });
|
|
132
|
+
}
|
|
117
133
|
});
|
|
118
|
-
} finally {
|
|
119
|
-
rmSync(dir, { recursive: true, force: true });
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
test("revert leaves a user-edited baseUrl alone (marker mismatch) and drops the marker", async () => {
|
|
124
|
-
const dir = mkdtempSync(join(tmpdir(), "pi-or-user-edit-"));
|
|
125
|
-
try {
|
|
126
|
-
await ensureOpenRouterModelsOverride(dir, { OPENROUTER_BASE_URL: GATEWAY });
|
|
127
|
-
// User hand-edits the file after we wrote it.
|
|
128
|
-
await Bun.write(
|
|
129
|
-
join(dir, "models.json"),
|
|
130
|
-
JSON.stringify({ providers: { openrouter: { baseUrl: "https://mine.example/v1" } } }),
|
|
131
|
-
);
|
|
132
|
-
await ensureOpenRouterModelsOverride(dir, {});
|
|
133
|
-
const kept = JSON.parse(await Bun.file(join(dir, "models.json")).text());
|
|
134
|
-
expect(kept.providers.openrouter.baseUrl).toBe("https://mine.example/v1");
|
|
135
|
-
expect(await Bun.file(join(dir, ".agent-swarm-openrouter-override.json")).exists()).toBe(
|
|
136
|
-
false,
|
|
137
|
-
);
|
|
138
|
-
} finally {
|
|
139
|
-
rmSync(dir, { recursive: true, force: true });
|
|
140
|
-
}
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
test("default env never touches a hand-authored models.json (no marker)", async () => {
|
|
144
|
-
const dir = mkdtempSync(join(tmpdir(), "pi-or-hands-off-"));
|
|
145
|
-
try {
|
|
146
|
-
const userConfig = {
|
|
147
|
-
providers: { openrouter: { baseUrl: "https://mine.example/v1" } },
|
|
148
|
-
};
|
|
149
|
-
await Bun.write(join(dir, "models.json"), JSON.stringify(userConfig));
|
|
150
|
-
await ensureOpenRouterModelsOverride(dir, {});
|
|
151
|
-
expect(JSON.parse(await Bun.file(join(dir, "models.json")).text())).toEqual(userConfig);
|
|
152
|
-
} finally {
|
|
153
|
-
rmSync(dir, { recursive: true, force: true });
|
|
154
134
|
}
|
|
155
|
-
}
|
|
135
|
+
}
|
|
156
136
|
|
|
157
|
-
test("
|
|
158
|
-
|
|
137
|
+
test("ModelRuntime composes the override onto built-in OpenRouter models", async () => {
|
|
138
|
+
// The whole point of the lever: built-in model IDs resolved through the
|
|
139
|
+
// runtime's composed store carry the gateway baseUrl.
|
|
140
|
+
const dir = mkdtempSync(join(tmpdir(), "pi-or-runtime-"));
|
|
141
|
+
const session = await writeSessionModelsJson(dir, { OPENROUTER_BASE_URL: GATEWAY });
|
|
159
142
|
try {
|
|
160
|
-
await
|
|
161
|
-
join(dir, "
|
|
162
|
-
|
|
163
|
-
providers: {
|
|
164
|
-
openrouter: { baseUrl: "https://old.example/v1", compat: { supportsTools: true } },
|
|
165
|
-
custom: { baseUrl: "https://custom.example/v1", api: "openai-completions" },
|
|
166
|
-
},
|
|
167
|
-
}),
|
|
168
|
-
);
|
|
169
|
-
await ensureOpenRouterModelsOverride(dir, { OPENROUTER_BASE_URL: GATEWAY });
|
|
170
|
-
const written = JSON.parse(await Bun.file(join(dir, "models.json")).text());
|
|
171
|
-
expect(written.providers.openrouter.baseUrl).toBe(GATEWAY);
|
|
172
|
-
expect(written.providers.openrouter.compat).toEqual({ supportsTools: true });
|
|
173
|
-
expect(written.providers.custom).toEqual({
|
|
174
|
-
baseUrl: "https://custom.example/v1",
|
|
175
|
-
api: "openai-completions",
|
|
143
|
+
const runtime = await ModelRuntime.create({
|
|
144
|
+
authPath: join(dir, "auth.json"),
|
|
145
|
+
modelsPath: session.modelsPath,
|
|
176
146
|
});
|
|
147
|
+
expect(runtime.getError()).toBeUndefined();
|
|
148
|
+
const model = runtime.getModel("openrouter", "anthropic/claude-sonnet-5");
|
|
149
|
+
expect(model?.baseUrl).toBe(GATEWAY);
|
|
177
150
|
} finally {
|
|
151
|
+
await session.dispose();
|
|
178
152
|
rmSync(dir, { recursive: true, force: true });
|
|
179
153
|
}
|
|
180
154
|
});
|
|
181
155
|
|
|
182
|
-
test("overwrites a corrupt models.json with just the override", async () => {
|
|
183
|
-
const dir = mkdtempSync(join(tmpdir(), "pi-or-corrupt-"));
|
|
184
|
-
try {
|
|
185
|
-
await Bun.write(join(dir, "models.json"), "{not json");
|
|
186
|
-
await ensureOpenRouterModelsOverride(dir, { OPENROUTER_BASE_URL: GATEWAY });
|
|
187
|
-
const written = JSON.parse(await Bun.file(join(dir, "models.json")).text());
|
|
188
|
-
expect(written).toEqual({ providers: { openrouter: { baseUrl: GATEWAY } } });
|
|
189
|
-
} finally {
|
|
190
|
-
rmSync(dir, { recursive: true, force: true });
|
|
191
|
-
}
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
test("ModelRuntime composes the override onto built-in OpenRouter models", async () => {
|
|
195
|
-
// The whole point of the lever: built-in model IDs resolved through the
|
|
196
|
-
// runtime's composed store carry the gateway baseUrl.
|
|
197
|
-
const runtime = await ModelRuntime.create({
|
|
198
|
-
authPath: join(agentDir, "auth.json"),
|
|
199
|
-
modelsPath: join(agentDir, "models.json"),
|
|
200
|
-
});
|
|
201
|
-
expect(runtime.getError()).toBeUndefined();
|
|
202
|
-
const model = runtime.getModel("openrouter", "anthropic/claude-sonnet-5");
|
|
203
|
-
expect(model?.baseUrl).toBe(GATEWAY);
|
|
204
|
-
});
|
|
205
|
-
|
|
206
156
|
test("ModelRuntime keeps the default baseUrl when no models.json exists", async () => {
|
|
207
157
|
const dir = mkdtempSync(join(tmpdir(), "pi-or-default-"));
|
|
208
158
|
try {
|
|
@@ -15,7 +15,7 @@ import { unlink } from "node:fs/promises";
|
|
|
15
15
|
import net from "node:net";
|
|
16
16
|
import { runInNewContext } from "node:vm";
|
|
17
17
|
import type { Subprocess } from "bun";
|
|
18
|
-
import { BROWSER_SDK_JS } from "../
|
|
18
|
+
import { BROWSER_SDK_JS } from "../pages/browser-sdk";
|
|
19
19
|
import { signPageSession, verifyPageSession } from "../utils/page-session";
|
|
20
20
|
import { getFreePort, SERVER_BOOT_HOOK_TIMEOUT_MS, waitForServer } from "./test-net";
|
|
21
21
|
|