@desplega.ai/agent-swarm 1.173.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{acp-adapter-ccm9xrep.js → acp-adapter-sj2g0m84.js} +17 -16
- package/dist/{actions-sgeyv08k.js → actions-fegk44gr.js} +39 -34
- package/dist/{amp-adapter-gpxp3kc9.js → amp-adapter-ad8ft89x.js} +8 -7
- package/dist/{app-km9qnv2f.js → app-kfvgbvzy.js} +18 -14
- package/dist/{assistant-5r8tcvvd.js → assistant-z7zdy9ja.js} +40 -35
- package/dist/{boot-reembed-swwzdgd7.js → boot-reembed-5c2z0mbe.js} +10 -9
- package/dist/{boot-reembed-16rv9843.js → boot-reembed-j8xa181e.js} +10 -9
- package/dist/{boot-scrub-sweep-zkr6vkqb.js → boot-scrub-sweep-hnkn3rgt.js} +11 -10
- package/dist/{boot-seeds-d8sp49t2.js → boot-seeds-kwjq4d2z.js} +14 -13
- package/dist/{claude-adapter-yzzzh8jp.js → claude-adapter-9vmvpvgd.js} +19 -18
- package/dist/{claude-managed-adapter-xgxhqrt2.js → claude-managed-adapter-k9qae05j.js} +7 -5
- package/dist/{claude-managed-setup-bnmjarjc.js → claude-managed-setup-q54zx5q2.js} +6 -4
- package/dist/{claude-sdk-session-4whxere4.js → claude-sdk-session-9jye6cxr.js} +11881 -11472
- package/dist/{cli-94b48jhk.js → cli-03gedhng.js} +16 -14
- package/dist/{cli-1w0vj3gw.js → cli-1e4b6bzh.js} +7 -7
- package/dist/{cli-d603qf86.js → cli-29jc5q7w.js} +10 -8
- package/dist/cli-2m1ktfzz.js +17795 -0
- package/dist/{cli-x119kdba.js → cli-3dj8k4vy.js} +1 -1
- package/dist/{cli-srqq3gc5.js → cli-3fbmmawm.js} +5 -5
- package/dist/{cli-gmdajfw5.js → cli-4jxm2qtz.js} +4987 -1708
- package/dist/{cli-tadfgd0t.js → cli-5hkm7whv.js} +33 -4
- package/dist/{cli-42qhyvwx.js → cli-5t4f29ez.js} +3 -3
- package/dist/{cli-7a0sde2x.js → cli-6cagzc96.js} +9 -0
- package/dist/cli-6cvfxs8r.js +47 -0
- package/dist/{cli-0kxf81jj.js → cli-6jyj59nb.js} +2 -2
- package/dist/{cli-w1apjecb.js → cli-6razj6t4.js} +2 -2
- package/dist/{cli-anzdk0z4.js → cli-74ahz61x.js} +2 -2
- package/dist/{cli-m89t9jdf.js → cli-7x0g4sn1.js} +15416 -16024
- package/dist/{cli-yatxqynm.js → cli-9ha6hs2d.js} +1 -290
- package/dist/{cli-81r1wjyv.js → cli-9nfx44eh.js} +1 -1
- package/dist/cli-9traj3dn.js +638 -0
- package/dist/{cli-smcrrmhy.js → cli-agzebbd5.js} +9 -9
- package/dist/{cli-na2pxe4f.js → cli-apx87jne.js} +2 -2
- package/dist/{cli-rfz2ve55.js → cli-bzszjf2p.js} +2855 -862
- package/dist/{cli-c5km5znr.js → cli-c8r3kzzb.js} +1 -1
- package/dist/{cli-hsem16hb.js → cli-cgtksawx.js} +7 -4
- package/dist/{cli-db3m6zdt.js → cli-d2qr7aj4.js} +1 -1
- package/dist/{cli-rfs4vddv.js → cli-d5f9ed5c.js} +5 -5
- package/dist/cli-e3g5mbkz.js +776 -0
- package/dist/{cli-7jykv7ef.js → cli-e6mpjhed.js} +1 -1
- package/dist/{cli-7a93cv8c.js → cli-ee9gtz70.js} +98 -23
- package/dist/{cli-3nxdmjrc.js → cli-eejsgvm4.js} +4 -4
- package/dist/{cli-fmhj1ehj.js → cli-ef1hg1wv.js} +470 -51
- package/dist/cli-f527ce74.js +781 -0
- package/dist/{cli-ppy4nnnh.js → cli-f7kr7svz.js} +2 -2
- package/dist/cli-hjwmhcg1.js +10622 -0
- package/dist/{cli-2bw3w47j.js → cli-hnhtk6cj.js} +1 -1
- package/dist/{cli-ywm1azg9.js → cli-hnmhtzvh.js} +6 -4
- package/dist/{cli-h7k0xtry.js → cli-hvqbtna4.js} +4 -1256
- package/dist/{cli-s8z6x4b4.js → cli-j0pfptdp.js} +218 -102
- package/dist/cli-jta2eyzc.js +619 -0
- package/dist/{cli-5er5gjbm.js → cli-k382tshb.js} +10 -11
- package/dist/{cli-v3zhnxsb.js → cli-k3dp5tpf.js} +2 -2
- package/dist/{cli-wd0hz6ah.js → cli-kbrt8gqa.js} +9 -91
- package/dist/cli-kceah1eh.js +92 -0
- package/dist/{cli-ne7smj1b.js → cli-ke896fcf.js} +10 -10
- package/dist/{cli-xc7p7s3g.js → cli-kt6zqg0e.js} +6 -6
- package/dist/{cli-wrh08zjd.js → cli-m50t4309.js} +5 -18
- package/dist/cli-n2wq4j7m.js +6985 -0
- package/dist/{cli-bv2etw2y.js → cli-n3hzg89b.js} +46 -15
- package/dist/{cli-tmhsxhb8.js → cli-n5vwg842.js} +23 -6
- package/dist/{cli-qm1448f9.js → cli-ne236afy.js} +8 -8
- package/dist/{cli-d161g69q.js → cli-p1x85d2t.js} +50 -2
- package/dist/{cli-7pz3b7x5.js → cli-przpmdgr.js} +25 -0
- package/dist/{cli-0a7mm3zp.js → cli-q0ke1yww.js} +25 -21
- package/dist/{cli-e3pmyt39.js → cli-q50942rs.js} +1 -1
- package/dist/{cli-r44t9wqc.js → cli-r106725k.js} +2 -2
- package/dist/{cli-phcqp1b9.js → cli-r2pnvj8g.js} +2 -2
- package/dist/{cli-jpc4r224.js → cli-r62a35ea.js} +19 -3
- package/dist/{cli-cd0hv9nh.js → cli-ra0xzggj.js} +2 -2
- package/dist/cli-rhs9pz33.js +294 -0
- package/dist/{cli-r4fqfpwf.js → cli-s2sp957d.js} +50 -27
- package/dist/{cli-1x7da0de.js → cli-s4setq91.js} +8 -8
- package/dist/{cli-6thk4jz3.js → cli-se8w6wny.js} +6 -6
- package/dist/cli-skxvvpap.js +10140 -0
- package/dist/{cli-014vns2y.js → cli-stfs0k5s.js} +208 -525
- package/dist/{cli-0p3enk35.js → cli-tqaazemc.js} +9 -1
- package/dist/{cli-nwrdx840.js → cli-tr4sxp68.js} +3 -3
- package/dist/{cli-r0byd61h.js → cli-tsde1hdr.js} +15 -15
- package/dist/{cli-b07hr3bt.js → cli-txqb9s38.js} +2 -2
- package/dist/{cli-6r2f7edk.js → cli-v1yb0rqb.js} +6 -6
- package/dist/{cli-saqyage0.js → cli-vatyhshy.js} +179 -27
- package/dist/{cli-5wpw4rab.js → cli-vdach5a9.js} +5 -5
- package/dist/{cli-7avy4v8w.js → cli-w1q7h2r2.js} +5 -3
- package/dist/{cli-rcp5aqdw.js → cli-w7m81tb7.js} +1 -1
- package/dist/{cli-c3rk1v0x.js → cli-wayw93nt.js} +3 -3
- package/dist/cli-wmgrkscj.js +23254 -0
- package/dist/{cli-9xf7fdtp.js → cli-z5jjpaxw.js} +29 -6
- package/dist/{cli-451ks6aw.js → cli-zkpks3r3.js} +1 -1
- package/dist/cli.js +29 -45
- package/dist/{codex-adapter-r3dn4fp5.js → codex-adapter-y81sssk0.js} +15 -14
- package/dist/{codex-login-841kv5fk.js → codex-login-f2rcfpp9.js} +3 -2
- package/dist/{codex-session-runner-fwt793h4.js → codex-session-runner-8vx4j6a9.js} +15 -14
- package/dist/{commands-z8fq9qna.js → commands-kcpvpcrr.js} +11 -10
- package/dist/{controller-hq5ggpcp.js → controller-ywrmt6yd.js} +5 -4
- package/dist/{controllers-connect-3mf2btyj.js → controllers-connect-kwpvj3rf.js} +42 -37
- package/dist/{cursor-adapter-q8pgv5qa.js → cursor-adapter-cehv1qns.js} +7 -6
- package/dist/{db-yyhqqrbf.js → db-k8ankj20.js} +12 -11
- package/dist/{db-qfpq4bt7.js → db-ww5d7jq3.js} +28 -9
- package/dist/{devin-adapter-xgtj680j.js → devin-adapter-x1022qb1.js} +3 -2
- package/dist/{dispatcher-x0x7qfcq.js → dispatcher-e8gq5m68.js} +38 -33
- package/dist/{docker-f503k9p0.js → docker-ff6y1190.js} +3 -2
- package/dist/{dsh-adapter-34etvb5m.js → dsh-adapter-3epjm3af.js} +7 -6
- package/dist/{e2b-66xra4az.js → e2b-8ygwhg0f.js} +4 -3
- package/dist/{e2b-xwhyj8cw.js → e2b-swhnpw3n.js} +4 -3
- package/dist/{event-bus-vjtv82gm.js → event-bus-6axnngdj.js} +3 -2
- package/dist/{external-channel-h1fjecwe.js → external-channel-h3359cwn.js} +11 -10
- package/dist/{google-generative-ai-szdxbpqg.js → google-generative-ai-g8db7x0f.js} +4 -2
- package/dist/{google-vertex-qzw3p36d.js → google-vertex-r2692q0r.js} +4 -2
- package/dist/{grok-adapter-e7mr287e.js → grok-adapter-0d4x7gkx.js} +18 -17
- package/dist/{handlers-znb9n7d4.js → handlers-an1gzv21.js} +41 -36
- package/dist/{heartbeat-bm4qkn8d.js → heartbeat-6dakeb61.js} +43 -38
- package/dist/{hook-254r4mza.js → hook-6v6cv6d5.js} +11 -23
- package/dist/{hook-xaa3q80s.js → hook-sa5xye4y.js} +12 -11
- package/dist/{http-3dgrgp8y.js → http-r7f3eky0.js} +1499 -251
- package/dist/index-52j1aja4.js +3465 -0
- package/dist/{index-fx3jqq1e.js → index-7ep4gzdk.js} +38 -33
- package/dist/index-9gw58n4k.js +193 -0
- package/dist/index-9j87mcm8.js +56305 -0
- package/dist/index-bsmcresk.js +79424 -0
- package/dist/index-mh85qhec.js +475 -0
- package/dist/{index-g2byveez.js → index-rmpw9a8q.js} +38 -33
- package/dist/{index-2ybk7khy.js → index-s2wxgzr0.js} +468 -683
- package/dist/{index-bdsqa799.js → index-v1v6n7hg.js} +43 -38
- package/dist/index-we27kd31.js +12810 -0
- package/dist/{index-jd7sejm2.js → index-zk96aq1k.js} +10 -9
- package/dist/{keepalive-6ywhpja1.js → keepalive-be9vwy5h.js} +12 -11
- package/dist/{lead-cw45n21s.js → lead-0861cwrf.js} +61 -56
- package/dist/{maintenance-ss1a1ge9.js → maintenance-mzzgc8bn.js} +10 -9
- package/dist/{node-fkajaq96.js → node-mn1zs7an.js} +2 -1
- package/dist/{oauth-refresh-sweep-3c6f8vyy.js → oauth-refresh-sweep-cpdnfwdj.js} +11 -10
- package/dist/{onboard-8hzdhc1k.js → onboard-fh8qs4r3.js} +11 -3
- package/dist/{opencode-adapter-qbchbcfz.js → opencode-adapter-nm42wwj8.js} +18 -13
- package/dist/{otel-impl-e6mnvc4v.js → otel-impl-x6c1c9g4.js} +242 -868
- package/dist/{pi-mono-adapter-3c9g9snh.js → pi-mono-adapter-9n2ya240.js} +244 -106
- package/dist/{pricing-refresh-gedkjdw0.js → pricing-refresh-qb0rv0ap.js} +12 -11
- package/dist/{rbac-roles-dt4vh1yy.js → rbac-roles-1gzdwbh1.js} +11 -10
- package/dist/{rbac-roles-eszdg6pm.js → rbac-roles-3mraey52.js} +12 -11
- package/dist/{render-v2-907cy6sa.js → render-v2-agpa45zz.js} +18 -14
- package/dist/{resume-42stcaz0.js → resume-x0ydy9s9.js} +25 -21
- package/dist/{runtime-qfry88v2.js → runtime-sa2vr16g.js} +2 -2
- package/dist/{setup-adbc1c10.js → setup-as9ga0d6.js} +2 -2
- package/dist/{virtual-modules-rtmt494q.js → virtual-modules-9gkxqrap.js} +6 -5
- package/dist/{work-objects-wpj8ck95.js → work-objects-y5g1y7ev.js} +41 -36
- package/dist/{worker-bzdra9rf.js → worker-860c3e38.js} +61 -56
- package/dist/{x-qfmygqqv.js → x-m477fr0h.js} +3 -2
- package/openapi.json +523 -344
- package/package.json +10 -11
- package/src/be/config-refs.ts +60 -0
- package/src/be/db/page-versions.ts +15 -0
- package/src/be/db/runtime.ts +35 -8
- package/src/be/db/skill-name-resolution.ts +36 -0
- package/src/be/db/tasks/read.ts +230 -0
- package/src/be/db/tasks/write.ts +7 -0
- package/src/be/db.ts +52 -7
- package/src/be/migrations/215_swarm_config_export_to_env.sql +34 -0
- package/src/be/migrations/216_task_resolved_effort.sql +5 -0
- package/src/be/migrations/217_chat_sdk_state.sql +104 -0
- package/src/be/model-catalog-store.ts +5 -1
- package/src/be/model-tier-resolution.ts +12 -1
- package/src/be/model-validation.ts +5 -2
- package/src/be/models-catalog.ts +26 -1
- package/src/be/modelsdev-cache.json +198 -63
- package/src/be/modelsdev-cache.ts +9 -1
- package/src/be/script-credential-broker.ts +39 -2
- package/src/be/scripts/run-saved.ts +1 -0
- package/src/be/scripts/typecheck.ts +10 -8
- package/src/be/seed/runner.ts +13 -1
- package/src/be/seed/state-db.ts +5 -0
- package/src/be/seed/types.ts +6 -0
- package/src/be/seed-pricing.ts +19 -2
- package/src/be/seed-scripts/catalog/ops-catalog-audit.inline.ts +1 -1
- package/src/be/seed-scripts/catalog/ops-catalog-audit.ts +1 -1
- package/src/be/seed-skills/bundled-files.generated.json +0 -18
- package/src/be/seed-skills/index.ts +104 -3
- package/src/be/swarm-config-guard.ts +52 -0
- package/src/cli.tsx +0 -16
- package/src/commands/onboard/non-interactive.ts +4 -2
- package/src/commands/provider-credentials.ts +52 -6
- package/src/commands/runner.ts +190 -54
- package/src/e2b/env.ts +5 -0
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/extensions/contract.ts +45 -0
- package/src/extensions/dispatcher.ts +7 -0
- package/src/extensions/post-bridge.ts +40 -0
- package/src/github/handlers.ts +11 -4
- package/src/heartbeat/templates.ts +8 -5
- package/src/hooks/hook.ts +0 -26
- package/src/http/all-routes.ts +0 -1
- package/src/http/config.ts +36 -17
- package/src/http/core.ts +4 -0
- package/src/http/index.ts +14 -2
- package/src/http/integrations.ts +88 -1
- package/src/http/mcp.ts +15 -5
- package/src/http/page-proxy.ts +2 -4
- package/src/http/pages-public.ts +3 -3
- package/src/http/pages.ts +3 -13
- package/src/http/poll.ts +28 -68
- package/src/http/requested-by.ts +54 -0
- package/src/http/scripts.ts +1 -0
- package/src/http/stats.ts +1 -30
- package/src/http/tasks.ts +61 -5
- package/src/http/utils.ts +14 -0
- package/src/http/webhooks.ts +159 -1
- package/src/http/x.ts +1 -0
- package/src/integrations/chat-sdk/adapters.ts +129 -0
- package/src/integrations/chat-sdk/attachments.ts +232 -0
- package/src/integrations/chat-sdk/background.ts +36 -0
- package/src/integrations/chat-sdk/commands.ts +194 -0
- package/src/integrations/chat-sdk/config.ts +198 -0
- package/src/integrations/chat-sdk/discord-api.ts +63 -0
- package/src/integrations/chat-sdk/discord-gateway.ts +185 -0
- package/src/integrations/chat-sdk/handlers.ts +28 -0
- package/src/integrations/chat-sdk/inbound.ts +426 -0
- package/src/integrations/chat-sdk/limits.ts +48 -0
- package/src/integrations/chat-sdk/logger.ts +81 -0
- package/src/integrations/chat-sdk/outbound.ts +123 -0
- package/src/integrations/chat-sdk/relay.ts +172 -0
- package/src/integrations/chat-sdk/router.ts +169 -0
- package/src/integrations/chat-sdk/runtime.ts +351 -0
- package/src/integrations/chat-sdk/schema.ts +245 -0
- package/src/integrations/chat-sdk/state.ts +243 -0
- package/src/integrations/chat-sdk/status.ts +244 -0
- package/src/integrations/chat-sdk/steering.ts +58 -0
- package/src/integrations/chat-sdk/teams-origin.ts +77 -0
- package/src/integrations/chat-sdk/telegram-api.ts +56 -0
- package/src/integrations/chat-sdk/templates.ts +16 -0
- package/src/integrations/chat-sdk/test-hooks.ts +37 -0
- package/src/integrations/chat-sdk/thread-buffer.ts +100 -0
- package/src/integrations/chat-sdk/typing.ts +65 -0
- package/src/pages/version.ts +4 -5
- package/src/prompts/base-prompt.ts +111 -45
- package/src/providers/acp-target-catalog.ts +6 -0
- package/src/providers/acp-targets.ts +55 -2
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/claude-adapter.ts +1 -1
- package/src/providers/claude-flag-settings.ts +17 -5
- package/src/providers/claude-managed-adapter.ts +1 -1
- package/src/providers/claude-session-events.ts +6 -1
- package/src/providers/dsh-adapter.ts +1 -1
- package/src/providers/opencode-adapter.ts +243 -26
- package/src/providers/opencode-swarm-plugin.ts +31 -0
- package/src/providers/pi-mono-adapter.ts +398 -153
- package/src/providers/pricing-sources.md +33 -2
- package/src/providers/reasoning-effort.ts +27 -0
- package/src/providers/types.ts +6 -2
- package/src/rbac/legacy-policy.ts +8 -2
- package/src/rbac/permissions.ts +21 -0
- package/src/scheduler/scheduler.ts +4 -2
- package/src/scripts-runtime/loader.ts +19 -7
- package/src/scripts-runtime/sdk-allowlist.ts +8 -6
- package/src/scripts-runtime/swarm-sdk.ts +0 -11
- package/src/scripts-runtime/types/stdlib.d.ts +27 -15
- package/src/scripts-runtime/types/swarm-extension.d.ts +63 -1
- package/src/scripts-runtime/types/swarm-sdk.d.ts +27 -15
- package/src/server.ts +52 -18
- package/src/slack/event-dedup.ts +1 -1
- package/src/tasks/additive-buffer.ts +4 -2
- package/src/tasks/context-key.ts +60 -1
- package/src/tasks/model-pin.ts +67 -0
- package/src/tasks/worker-follow-up.ts +4 -31
- package/src/tests/acp-adapter.test.ts +42 -0
- package/src/tests/api-drain-worker-e2e.test.ts +1 -6
- package/src/tests/base-prompt.test.ts +58 -54
- package/src/tests/chat-sdk-commands.test.ts +191 -0
- package/src/tests/chat-sdk-config.test.ts +334 -0
- package/src/tests/chat-sdk-discord-api.test.ts +68 -0
- package/src/tests/chat-sdk-discord-gateway.test.ts +252 -0
- package/src/tests/chat-sdk-discord-webhook.test.ts +126 -0
- package/src/tests/chat-sdk-fields-sync.test.ts +50 -0
- package/src/tests/chat-sdk-harness.ts +146 -0
- package/src/tests/chat-sdk-inbound.test.ts +363 -0
- package/src/tests/chat-sdk-relay.test.ts +470 -0
- package/src/tests/chat-sdk-reload.test.ts +128 -0
- package/src/tests/chat-sdk-shutdown.test.ts +220 -0
- package/src/tests/chat-sdk-slack.test.ts +268 -0
- package/src/tests/chat-sdk-state.test.ts +163 -0
- package/src/tests/chat-sdk-status-route.test.ts +382 -0
- package/src/tests/chat-sdk-teams.test.ts +277 -0
- package/src/tests/chat-sdk-tools.test.ts +520 -0
- package/src/tests/chat-sdk-typing.test.ts +120 -0
- package/src/tests/chat-sdk-webhook-telegram.test.ts +200 -0
- package/src/tests/claude-adapter-binary.test.ts +9 -4
- package/src/tests/claude-flag-settings.test.ts +11 -3
- package/src/tests/claude-managed-adapter.test.ts +1 -1
- package/src/tests/claude-sdk-lifecycle.test.ts +9 -3
- package/src/tests/claude-thinking-tokens.test.ts +57 -0
- package/src/tests/claude-worker-parity.test.ts +2 -7
- package/src/tests/config-env-export-chat-sdk.test.ts +11 -0
- package/src/tests/config-refs.test.ts +66 -0
- package/src/tests/context-key.test.ts +19 -0
- package/src/tests/cors-allowlist.test.ts +25 -0
- package/src/tests/create-page-tool.test.ts +18 -0
- package/src/tests/credential-check.test.ts +139 -0
- package/src/tests/credentials.test.ts +21 -0
- package/src/tests/db-runtime.test.ts +61 -0
- package/src/tests/db-task-read-contract.test.ts +20 -0
- package/src/tests/db-task-write-contract.test.ts +10 -0
- package/src/tests/docs-index-footprint.test.js +3 -3
- package/src/tests/dsh-adapter.test.ts +1 -1
- package/src/tests/enabled-capabilities.test.ts +20 -0
- package/src/tests/entrypoint-api-readiness.test.ts +14 -8
- package/src/tests/entrypoint-config-env-export.test.ts +5 -2
- package/src/tests/entrypoint-export-to-env.test.ts +78 -0
- package/src/tests/explicit-model-validation.test.ts +105 -1
- package/src/tests/extensions-example-route-chat-channel.test.ts +154 -0
- package/src/tests/extensions-post-bridge.test.ts +51 -0
- package/src/tests/fetch-resolved-env.test.ts +128 -0
- package/src/tests/fixtures/chat-sdk/telegram-private-text.json +10 -0
- package/src/tests/fixtures/extensions/route-chat-channel-to-agent/hooks.ts +27 -0
- package/src/tests/fixtures/extensions/route-chat-channel-to-agent/manifest.json +9 -0
- package/src/tests/fixtures/openai-compatible-registry-extension.child.ts +146 -0
- package/src/tests/fixtures/runner-legacy-resume-prompt.ts +122 -0
- package/src/tests/github-handlers.test.ts +35 -0
- package/src/tests/harness-provider-resolution.test.ts +12 -0
- package/src/tests/heartbeat-checklist.test.ts +3 -2
- package/src/tests/hook-runtime-close.test.ts +0 -2
- package/src/tests/http-api-integration.test.ts +0 -25
- package/src/tests/migration-046-budgets.test.ts +17 -16
- package/src/tests/migration-217-chat-sdk-state.test.ts +113 -0
- package/src/tests/model-tier-resolution.test.ts +93 -0
- package/src/tests/models-catalog.test.ts +42 -1
- package/src/tests/openai-compatible-providers.test.ts +134 -0
- package/src/tests/openai-compatible-registry-extension.test.ts +42 -0
- package/src/tests/opencode-adapter.test.ts +216 -2
- package/src/tests/opencode-helmcode-sdk.test.ts +201 -0
- package/src/tests/opencode-shell-env.test.ts +145 -0
- package/src/tests/openrouter-base-url.test.ts +101 -151
- package/src/tests/page-proxy.test.ts +1 -1
- package/src/tests/pages-browser-sdk.test.ts +76 -0
- package/src/tests/pages-feedback.test.ts +1 -1
- package/src/tests/paused-tasks-requested-by.test.ts +102 -0
- package/src/tests/pi-custom-provider-fake-server.test.ts +442 -0
- package/src/tests/pi-mono-adapter.test.ts +385 -5
- package/src/tests/pi-shell-env.test.ts +170 -0
- package/src/tests/prompt-template-remaining.test.ts +35 -0
- package/src/tests/rbac-engine.test.ts +11 -3
- package/src/tests/runner-exit-after-tasks.test.ts +0 -4
- package/src/tests/runner-legacy-resume-prompt.test.ts +64 -0
- package/src/tests/script-connections.test.ts +2 -0
- package/src/tests/scripts-http.test.ts +201 -1
- package/src/tests/scripts-only-gating.test.ts +2 -3
- package/src/tests/scripts-runtime.test.ts +58 -40
- package/src/tests/secret-scrubber.test.ts +86 -0
- package/src/tests/seed-skills-bundled-files.test.ts +45 -53
- package/src/tests/seed-skills-retire.test.ts +182 -0
- package/src/tests/send-task-requester-gate.test.ts +58 -0
- package/src/tests/session-cost-recompute-unit.test.ts +46 -0
- package/src/tests/skill-get-name-resolution.test.ts +137 -0
- package/src/tests/sonnet-5-5-registry.test.ts +9 -1
- package/src/tests/swarm-config-export-to-env.test.ts +267 -0
- package/src/tests/swarm-config-reserved-keys.test.ts +16 -0
- package/src/tests/swarm-diff.test.ts +1 -1
- package/src/tests/task-model-effort-rerun.test.ts +164 -0
- package/src/tests/task-session-prompts.test.ts +132 -0
- package/src/tests/task-tool-manifest.test.ts +14 -0
- package/src/tests/task-tools-ctx.test.ts +2 -0
- package/src/tests/tool-annotations.test.ts +6 -6
- package/src/tests/ui-logs-parser.test.ts +43 -0
- package/src/tests/used-by-route.test.ts +6 -0
- package/src/tests/vercel-ai-gateway.test.ts +149 -0
- package/src/tests/workflow-swarm-script.test.ts +38 -0
- package/src/tests/workflow-wait-filter.test.ts +5 -3
- package/src/tools/chat-access.ts +41 -0
- package/src/tools/chat-open-dm.ts +59 -0
- package/src/tools/chat-post.ts +74 -0
- package/src/tools/chat-read-thread.ts +100 -0
- package/src/tools/chat-reply.ts +75 -0
- package/src/tools/create-page.ts +3 -13
- package/src/tools/model-catalog/index.ts +2 -1
- package/src/tools/register-chat-channel.ts +139 -0
- package/src/tools/send-task.ts +24 -0
- package/src/tools/skills/skill-get.ts +17 -9
- package/src/tools/swarm-config/get-config.ts +2 -1
- package/src/tools/swarm-config/list-config.ts +2 -1
- package/src/tools/swarm-config/set-config.ts +16 -3
- package/src/tools/tool-config.ts +8 -6
- package/src/types.ts +12 -2
- package/src/utils/config-env-export.ts +74 -0
- package/src/utils/credentials.test.ts +92 -0
- package/src/utils/credentials.ts +52 -4
- package/src/utils/harness-provider.ts +15 -6
- package/src/utils/runtime-model-catalog.ts +11 -1
- package/src/utils/secret-scrubber.ts +71 -2
- package/src/utils/task-tool-manifest.ts +19 -2
- package/src/workflows/executors/swarm-script.ts +1 -0
- package/templates/skills/agent-browser/SKILL.md +1 -1
- package/templates/skills/agent-browser/content.md +1 -1
- package/templates/skills/apps/SKILL.md +8 -1
- package/templates/skills/apps/content.md +8 -1
- package/templates/skills/chat-sdk/config.json +14 -0
- package/templates/skills/chat-sdk/content.md +84 -0
- package/templates/skills/composio-gmail/content.md +7 -0
- package/templates/skills/composio-google-docs/content.md +1 -1
- package/templates/skills/email-domain-health/config.json +14 -0
- package/templates/skills/email-domain-health/content.md +131 -0
- package/templates/skills/kv-storage/content.md +2 -2
- package/templates/skills/pages/content.md +8 -10
- package/templates/skills/swarm-extensions/content.md +2 -0
- package/templates/skills/swarm-scripts/SKILL.md +4 -2
- package/templates/skills/swarm-scripts/config.json +1 -1
- package/templates/skills/swarm-scripts/content.md +4 -2
- package/templates/skills/workflow-iterate/content.md +38 -0
- package/dist/artifact-eczsknc3.js +0 -3843
- package/dist/cli-bd957dgb.js +0 -153
- package/dist/cli-q51yprfq.js +0 -1104
- package/dist/cli-t1ze64h3.js +0 -811
- package/dist/cli-tcc4wd5d.js +0 -56471
- package/dist/index-9ch1n761.js +0 -1102
- package/src/artifact-sdk/index.ts +0 -2
- package/src/artifact-sdk/localtunnel.d.ts +0 -20
- package/src/artifact-sdk/port.ts +0 -12
- package/src/artifact-sdk/server.ts +0 -183
- package/src/artifact-sdk/tunnel.ts +0 -19
- package/src/commands/artifact.ts +0 -249
- package/src/http/ecosystem.ts +0 -77
- package/src/tests/artifact-commands.test.ts +0 -92
- package/src/tests/artifact-sdk.test.ts +0 -793
- package/src/tests/register-service-security.test.ts +0 -194
- package/src/tools/list-services.ts +0 -107
- package/src/tools/register-service.ts +0 -177
- package/src/tools/unregister-service.ts +0 -73
- package/src/tools/update-service-status.ts +0 -96
- package/templates/skills/artifacts/config.json +0 -14
- package/templates/skills/artifacts/content.md +0 -344
- package/templates/skills/artifacts/files/examples/approval-flow.ts +0 -34
- package/templates/skills/artifacts/files/examples/hono-dashboard.ts +0 -31
- package/templates/skills/artifacts/files/examples/multi-artifact.ts +0 -20
- package/templates/skills/artifacts/files/examples/static-report.sh +0 -17
- /package/src/{artifact-sdk → pages}/browser-sdk.ts +0 -0
- /package/src/{artifact-sdk → pages}/feedback-overlay.ts +0 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Multi-file (bundled) seeded skills.
|
|
3
3
|
*
|
|
4
|
-
* Regression coverage for the collision that shipped
|
|
4
|
+
* Regression coverage for the collision that shipped the since-retired
|
|
5
|
+
* `artifacts` skill twice — once
|
|
5
6
|
* baked into the worker image with an `examples/` directory, once seeded from
|
|
6
7
|
* `templates/skills/` as a simple skill. The DB copy won at runtime, dropped a
|
|
7
8
|
* `.swarm-managed` marker, and the next reconcile pass deleted the baked
|
|
@@ -58,17 +59,9 @@ describe("seeded skills with bundled files", () => {
|
|
|
58
59
|
await removeDbFiles(TEST_DB_PATH);
|
|
59
60
|
});
|
|
60
61
|
|
|
61
|
-
test("embedded manifest exposes bundled files for
|
|
62
|
+
test("embedded manifest exposes bundled files for pages", async () => {
|
|
62
63
|
const skills = await loadSeedSkills();
|
|
63
64
|
|
|
64
|
-
const artifacts = skills.find((skill) => skill.name === "artifacts");
|
|
65
|
-
expect(artifacts?.files.map((file) => file.path).sort()).toEqual([
|
|
66
|
-
"examples/approval-flow.ts",
|
|
67
|
-
"examples/hono-dashboard.ts",
|
|
68
|
-
"examples/multi-artifact.ts",
|
|
69
|
-
"examples/static-report.sh",
|
|
70
|
-
]);
|
|
71
|
-
|
|
72
65
|
const pages = skills.find((skill) => skill.name === "pages");
|
|
73
66
|
expect(pages?.files.map((file) => file.path).sort()).toEqual([
|
|
74
67
|
"examples/annotated-pr.html",
|
|
@@ -84,7 +77,7 @@ describe("seeded skills with bundled files", () => {
|
|
|
84
77
|
const embedded = await loadSeedSkills();
|
|
85
78
|
const fromDisk = await loadSeedSkills(TEMPLATES_DIR);
|
|
86
79
|
|
|
87
|
-
for (const name of ["
|
|
80
|
+
for (const name of ["pages", "script-builder"]) {
|
|
88
81
|
const a = embedded.find((skill) => skill.name === name);
|
|
89
82
|
const b = fromDisk.find((skill) => skill.name === name);
|
|
90
83
|
expect(a?.files).toEqual(b?.files ?? []);
|
|
@@ -107,18 +100,17 @@ describe("seeded skills with bundled files", () => {
|
|
|
107
100
|
});
|
|
108
101
|
|
|
109
102
|
test("seeding writes skill_files rows and marks the skill complex", async () => {
|
|
110
|
-
const
|
|
111
|
-
expect(
|
|
112
|
-
if (!
|
|
103
|
+
const pages = await getSkillByName("pages", "swarm");
|
|
104
|
+
expect(pages).toBeTruthy();
|
|
105
|
+
if (!pages) return;
|
|
113
106
|
|
|
114
|
-
expect(
|
|
107
|
+
expect(pages.isComplex).toBe(true);
|
|
115
108
|
|
|
116
|
-
const files = await getSkillFiles(
|
|
109
|
+
const files = await getSkillFiles(pages.id);
|
|
117
110
|
expect(files.map((file) => file.path).sort()).toEqual([
|
|
118
|
-
"examples/
|
|
119
|
-
"examples/
|
|
120
|
-
"examples/
|
|
121
|
-
"examples/static-report.sh",
|
|
111
|
+
"examples/annotated-pr.html",
|
|
112
|
+
"examples/multiplayer-board.html",
|
|
113
|
+
"examples/report-page.html",
|
|
122
114
|
]);
|
|
123
115
|
expect(files.every((file) => file.content.length > 0)).toBe(true);
|
|
124
116
|
});
|
|
@@ -131,24 +123,24 @@ describe("seeded skills with bundled files", () => {
|
|
|
131
123
|
});
|
|
132
124
|
|
|
133
125
|
test("a bundled-file edit registers as drift and is preserved", async () => {
|
|
134
|
-
const item = (await skillsSeeder.items()).find((candidate) => candidate.key === "
|
|
126
|
+
const item = (await skillsSeeder.items()).find((candidate) => candidate.key === "pages");
|
|
135
127
|
expect(item).toBeDefined();
|
|
136
128
|
if (!item) return;
|
|
137
129
|
|
|
138
130
|
// Freshly seeded and untouched: upstream hashes to exactly the source.
|
|
139
131
|
expect(await skillsSeeder.upstreamHash(item)).toBe(item.contentHash);
|
|
140
132
|
|
|
141
|
-
const
|
|
142
|
-
if (!
|
|
143
|
-
const original = (await getSkillFiles(
|
|
144
|
-
(file) => file.path === "examples/
|
|
133
|
+
const pages = await getSkillByName("pages", "swarm");
|
|
134
|
+
if (!pages) throw new Error("pages skill missing");
|
|
135
|
+
const original = (await getSkillFiles(pages.id)).find(
|
|
136
|
+
(file) => file.path === "examples/report-page.html",
|
|
145
137
|
);
|
|
146
138
|
if (!original) throw new Error("bundled example missing");
|
|
147
139
|
|
|
148
140
|
// Simulate a user editing a bundled file through the skills API.
|
|
149
|
-
await upsertSkillFile(
|
|
141
|
+
await upsertSkillFile(pages.id, {
|
|
150
142
|
path: original.path,
|
|
151
|
-
content: `${original.content}\n
|
|
143
|
+
content: `${original.content}\n<!-- edited by a user -->`,
|
|
152
144
|
});
|
|
153
145
|
|
|
154
146
|
// The edit must move the upstream hash — otherwise the seeder would think
|
|
@@ -159,11 +151,11 @@ describe("seeded skills with bundled files", () => {
|
|
|
159
151
|
expect(result.failed).toEqual([]);
|
|
160
152
|
expect(result.skippedUserModified).toBeGreaterThan(0);
|
|
161
153
|
|
|
162
|
-
const after = (await getSkillFiles(
|
|
163
|
-
expect(after?.content).toContain("
|
|
154
|
+
const after = (await getSkillFiles(pages.id)).find((file) => file.path === original.path);
|
|
155
|
+
expect(after?.content).toContain("<!-- edited by a user -->");
|
|
164
156
|
|
|
165
157
|
// Restore so later tests see a pristine skill.
|
|
166
|
-
await upsertSkillFile(
|
|
158
|
+
await upsertSkillFile(pages.id, { path: original.path, content: original.content });
|
|
167
159
|
});
|
|
168
160
|
|
|
169
161
|
test("a userInvocable flip registers as drift and is preserved", async () => {
|
|
@@ -195,24 +187,24 @@ describe("seeded skills with bundled files", () => {
|
|
|
195
187
|
// format (no bundled-file section). The new seeder must still recognise it
|
|
196
188
|
// as pristine and update it — not misread it as user-modified and skip it
|
|
197
189
|
// forever, which would mean the merged content never reaches production.
|
|
198
|
-
const
|
|
199
|
-
if (!
|
|
190
|
+
const pages = await getSkillByName("pages", "swarm");
|
|
191
|
+
if (!pages) throw new Error("pages skill missing");
|
|
200
192
|
|
|
201
|
-
const legacyContent = "---\nname:
|
|
202
|
-
await updateSkill(
|
|
203
|
-
for (const file of await getSkillFiles(
|
|
204
|
-
await deleteSkillFile(
|
|
193
|
+
const legacyContent = "---\nname: pages\ndescription: old\n---\n\nold body\n";
|
|
194
|
+
await updateSkill(pages.id, { content: legacyContent, isComplex: false });
|
|
195
|
+
for (const file of await getSkillFiles(pages.id)) {
|
|
196
|
+
await deleteSkillFile(pages.id, file.path);
|
|
205
197
|
}
|
|
206
198
|
|
|
207
199
|
// Old-format hash: base only, no file section. The flag mirrors the seeded
|
|
208
|
-
// config so the test does not pin
|
|
200
|
+
// config so the test does not pin pages' systemDefault value.
|
|
209
201
|
const legacyHash = computeContentHash(
|
|
210
|
-
`${legacyContent}\n\n# seed:systemDefault=${
|
|
202
|
+
`${legacyContent}\n\n# seed:systemDefault=${pages.systemDefault ? 1 : 0}\n`,
|
|
211
203
|
);
|
|
212
|
-
await recordSeedState("skill", "
|
|
204
|
+
await recordSeedState("skill", "pages", legacyHash);
|
|
213
205
|
|
|
214
|
-
const item = (await skillsSeeder.items()).find((candidate) => candidate.key === "
|
|
215
|
-
if (!item) throw new Error("
|
|
206
|
+
const item = (await skillsSeeder.items()).find((candidate) => candidate.key === "pages");
|
|
207
|
+
if (!item) throw new Error("pages seed item missing");
|
|
216
208
|
|
|
217
209
|
// Pristine: upstream (file-less) still hashes in the old format.
|
|
218
210
|
expect(await skillsSeeder.upstreamHash(item)).toBe(legacyHash);
|
|
@@ -221,10 +213,10 @@ describe("seeded skills with bundled files", () => {
|
|
|
221
213
|
expect(result.failed).toEqual([]);
|
|
222
214
|
expect(result.skippedUserModified).toBe(0);
|
|
223
215
|
|
|
224
|
-
const upgraded = await getSkillByName("
|
|
216
|
+
const upgraded = await getSkillByName("pages", "swarm");
|
|
225
217
|
expect(upgraded?.isComplex).toBe(true);
|
|
226
218
|
expect(upgraded?.content).not.toBe(legacyContent);
|
|
227
|
-
expect((await getSkillFiles(upgraded?.id ?? "")).length).toBe(
|
|
219
|
+
expect((await getSkillFiles(upgraded?.id ?? "")).length).toBe(3);
|
|
228
220
|
|
|
229
221
|
// And it settles: the next run is a clean no-op.
|
|
230
222
|
const second = await runSeeder(skillsSeeder, { quiet: true });
|
|
@@ -273,18 +265,18 @@ describe("seeded skills with bundled files", () => {
|
|
|
273
265
|
});
|
|
274
266
|
|
|
275
267
|
test("bundled files survive repeated filesystem syncs (the reconcile bug)", async () => {
|
|
276
|
-
const
|
|
277
|
-
expect(
|
|
278
|
-
if (!
|
|
268
|
+
const pages = await getSkillByName("pages", "swarm");
|
|
269
|
+
expect(pages).toBeTruthy();
|
|
270
|
+
if (!pages) return;
|
|
279
271
|
|
|
280
272
|
const entry: SkillFsEntry = {
|
|
281
|
-
id:
|
|
282
|
-
name:
|
|
283
|
-
content:
|
|
284
|
-
isComplex:
|
|
273
|
+
id: pages.id,
|
|
274
|
+
name: pages.name,
|
|
275
|
+
content: pages.content,
|
|
276
|
+
isComplex: pages.isComplex,
|
|
285
277
|
isEnabled: true,
|
|
286
278
|
isActive: true,
|
|
287
|
-
files: (await getSkillFiles(
|
|
279
|
+
files: (await getSkillFiles(pages.id)).map((file) => ({
|
|
288
280
|
path: file.path,
|
|
289
281
|
content: file.content,
|
|
290
282
|
isBinary: file.isBinary,
|
|
@@ -297,9 +289,9 @@ describe("seeded skills with bundled files", () => {
|
|
|
297
289
|
FAKE_HOME,
|
|
298
290
|
".claude",
|
|
299
291
|
"skills",
|
|
300
|
-
"
|
|
292
|
+
"pages",
|
|
301
293
|
"examples",
|
|
302
|
-
"
|
|
294
|
+
"report-page.html",
|
|
303
295
|
);
|
|
304
296
|
expect(existsSync(examplePath)).toBe(true);
|
|
305
297
|
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retiring a seeded skill whose template was removed.
|
|
3
|
+
*
|
|
4
|
+
* Dropping `templates/skills/<name>/` alone leaves the last seeded row live on
|
|
5
|
+
* every existing install. `retireSeedSkills` deletes it, but only while the
|
|
6
|
+
* seeder still owns it: a recorded `seed_state` hash that the live copy
|
|
7
|
+
* matches and every metadata column still holds what the seeder wrote.
|
|
8
|
+
* Unrecorded and user-edited skills stay, including metadata-only edits.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
12
|
+
import { unlink } from "node:fs/promises";
|
|
13
|
+
import {
|
|
14
|
+
closeDb,
|
|
15
|
+
computeContentHash,
|
|
16
|
+
createAgent,
|
|
17
|
+
createSkill,
|
|
18
|
+
getAgentSkills,
|
|
19
|
+
getSkillByName,
|
|
20
|
+
getSkillFiles,
|
|
21
|
+
initDb,
|
|
22
|
+
installSkill,
|
|
23
|
+
updateSkill,
|
|
24
|
+
upsertSkillFiles,
|
|
25
|
+
} from "../be/db";
|
|
26
|
+
import { getSeedState, recordSeedState, runSeeder } from "../be/seed";
|
|
27
|
+
import { RETIRED_SEED_SKILLS, retireSeedSkills, skillsSeeder } from "../be/seed-skills";
|
|
28
|
+
|
|
29
|
+
const TEST_DB_PATH = `./test-seed-skills-retire-${process.pid}.sqlite`;
|
|
30
|
+
|
|
31
|
+
async function removeDbFiles(path: string): Promise<void> {
|
|
32
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
33
|
+
await unlink(path + suffix).catch(() => {});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const BUNDLED_FILE = { path: "examples/serve.ts", content: "pm2 start serve.ts\n" };
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Seed a swarm skill the way the seeder does (frontmatter description equal to
|
|
41
|
+
* the description column, isComplex set from the file count) and record the
|
|
42
|
+
* hash it would have written.
|
|
43
|
+
*/
|
|
44
|
+
async function seedLegacySkill(name: string, withFile = false): Promise<string> {
|
|
45
|
+
const content = `---\nname: ${name}\ndescription: old\n---\n\nRun pm2 start.\n`;
|
|
46
|
+
const skill = await createSkill({
|
|
47
|
+
name,
|
|
48
|
+
description: "old",
|
|
49
|
+
content,
|
|
50
|
+
type: "personal",
|
|
51
|
+
scope: "swarm",
|
|
52
|
+
ownerAgentId: undefined,
|
|
53
|
+
systemDefault: false,
|
|
54
|
+
isComplex: withFile,
|
|
55
|
+
});
|
|
56
|
+
let hashed = `${content}\n\n# seed:systemDefault=0\n`;
|
|
57
|
+
if (withFile) {
|
|
58
|
+
await upsertSkillFiles(skill.id, [BUNDLED_FILE]);
|
|
59
|
+
hashed += `# seed:files\n${BUNDLED_FILE.path}\n${BUNDLED_FILE.content}\n`;
|
|
60
|
+
}
|
|
61
|
+
await recordSeedState("skill", name, computeContentHash(hashed));
|
|
62
|
+
return skill.id;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
describe("retiring seeded skills", () => {
|
|
66
|
+
let agentId: string;
|
|
67
|
+
|
|
68
|
+
beforeAll(async () => {
|
|
69
|
+
await removeDbFiles(TEST_DB_PATH);
|
|
70
|
+
initDb(TEST_DB_PATH);
|
|
71
|
+
agentId = (
|
|
72
|
+
await createAgent({ name: "retire-test", isLead: false, status: "idle", capabilities: [] })
|
|
73
|
+
).id;
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
afterAll(async () => {
|
|
77
|
+
closeDb();
|
|
78
|
+
await removeDbFiles(TEST_DB_PATH);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("artifacts is retired and no longer seeded", async () => {
|
|
82
|
+
expect(RETIRED_SEED_SKILLS).toContain("artifacts");
|
|
83
|
+
const keys = (await skillsSeeder.items()).map((item) => item.key);
|
|
84
|
+
expect(keys).not.toContain("artifacts");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("the seeder run deletes a pristine retired skill and its seed state", async () => {
|
|
88
|
+
const id = await seedLegacySkill("artifacts", true);
|
|
89
|
+
await installSkill(agentId, id);
|
|
90
|
+
|
|
91
|
+
const result = await runSeeder(skillsSeeder, { quiet: true });
|
|
92
|
+
expect(result.failed).toEqual([]);
|
|
93
|
+
expect(result.retired).toBe(1);
|
|
94
|
+
expect(await getSkillByName("artifacts", "swarm")).toBeNull();
|
|
95
|
+
expect(await getSkillFiles(id)).toEqual([]);
|
|
96
|
+
expect((await getAgentSkills(agentId, false)).map((s) => s.id)).not.toContain(id);
|
|
97
|
+
expect(await getSeedState("skill", "artifacts")).toBeNull();
|
|
98
|
+
|
|
99
|
+
// Settled: nothing left to retire on the next boot.
|
|
100
|
+
expect((await runSeeder(skillsSeeder, { quiet: true })).retired).toBe(0);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("the seeder run keeps a retired skill with metadata-only edits, with its installs and files", async () => {
|
|
104
|
+
const id = await seedLegacySkill("artifacts", true);
|
|
105
|
+
await installSkill(agentId, id);
|
|
106
|
+
await updateSkill(id, {
|
|
107
|
+
allowedTools: "Read",
|
|
108
|
+
model: "user-selected-model",
|
|
109
|
+
description: "my edited description",
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const result = await runSeeder(skillsSeeder, { quiet: true });
|
|
113
|
+
expect(result.failed).toEqual([]);
|
|
114
|
+
expect(result.retired).toBe(0);
|
|
115
|
+
|
|
116
|
+
const kept = await getSkillByName("artifacts", "swarm");
|
|
117
|
+
expect(kept?.id).toBe(id);
|
|
118
|
+
expect(kept?.model).toBe("user-selected-model");
|
|
119
|
+
expect((await getSkillFiles(id)).map((file) => file.path)).toEqual([BUNDLED_FILE.path]);
|
|
120
|
+
expect((await getAgentSkills(agentId, false)).map((s) => s.id)).toContain(id);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Every column updateSkill can write outside the seed hash. Each one alone
|
|
124
|
+
// marks the row as user-edited.
|
|
125
|
+
const METADATA_EDITS: [string, Parameters<typeof updateSkill>[1]][] = [
|
|
126
|
+
["description", { description: "mine" }],
|
|
127
|
+
["allowedTools", { allowedTools: "Read" }],
|
|
128
|
+
["model", { model: "opus" }],
|
|
129
|
+
["effort", { effort: "high" }],
|
|
130
|
+
["context", { context: "fork" }],
|
|
131
|
+
["agent", { agent: "reviewer" }],
|
|
132
|
+
["disableModelInvocation", { disableModelInvocation: true }],
|
|
133
|
+
["isEnabled", { isEnabled: false }],
|
|
134
|
+
["isComplex", { isComplex: true }],
|
|
135
|
+
["sourceUrl", { sourceUrl: "https://example.com/SKILL.md" }],
|
|
136
|
+
["sourceRepo", { sourceRepo: "me/skills" }],
|
|
137
|
+
["sourcePath", { sourcePath: "skills/artifacts" }],
|
|
138
|
+
["sourceBranch", { sourceBranch: "dev" }],
|
|
139
|
+
["sourceHash", { sourceHash: "abc123" }],
|
|
140
|
+
["lastFetchedAt", { lastFetchedAt: "2026-01-01T00:00:00.000Z" }],
|
|
141
|
+
];
|
|
142
|
+
|
|
143
|
+
test.each(METADATA_EDITS)("a retired skill with an edited %s is kept", async (field, edit) => {
|
|
144
|
+
const name = `zz-retired-meta-${field.toLowerCase()}`;
|
|
145
|
+
const id = await seedLegacySkill(name);
|
|
146
|
+
await updateSkill(id, edit);
|
|
147
|
+
|
|
148
|
+
expect(await retireSeedSkills([name])).toEqual([]);
|
|
149
|
+
expect(await getSkillByName(name, "swarm")).not.toBeNull();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("a pristine retired skill touched by a no-op update is still retired", async () => {
|
|
153
|
+
const id = await seedLegacySkill("zz-retired-noop");
|
|
154
|
+
await updateSkill(id, {});
|
|
155
|
+
|
|
156
|
+
expect(await retireSeedSkills(["zz-retired-noop"])).toEqual(["zz-retired-noop"]);
|
|
157
|
+
expect(await getSkillByName("zz-retired-noop", "swarm")).toBeNull();
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("a retired skill edited since it was seeded is kept", async () => {
|
|
161
|
+
const id = await seedLegacySkill("zz-retired-edited");
|
|
162
|
+
await updateSkill(id, { content: "---\nname: zz-retired-edited\n---\n\nmine\n" });
|
|
163
|
+
|
|
164
|
+
expect(await retireSeedSkills(["zz-retired-edited"])).toEqual([]);
|
|
165
|
+
expect(await getSkillByName("zz-retired-edited", "swarm")).not.toBeNull();
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test("a same-name skill the seeder never recorded is kept", async () => {
|
|
169
|
+
await createSkill({
|
|
170
|
+
name: "zz-retired-unrecorded",
|
|
171
|
+
description: "user authored",
|
|
172
|
+
content: "---\nname: zz-retired-unrecorded\n---\n\nmine\n",
|
|
173
|
+
type: "personal",
|
|
174
|
+
scope: "swarm",
|
|
175
|
+
ownerAgentId: undefined,
|
|
176
|
+
systemDefault: false,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
expect(await retireSeedSkills(["zz-retired-unrecorded"])).toEqual([]);
|
|
180
|
+
expect(await getSkillByName("zz-retired-unrecorded", "swarm")).not.toBeNull();
|
|
181
|
+
});
|
|
182
|
+
});
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* A task's requester drives attribution, cost reporting and admission. A non-lead agent may omit
|
|
3
3
|
* `requestedByUserId` or pass the requester of its own current task. Only a lead, the operator or
|
|
4
4
|
* a user may name any other registered user. Covers the send-task tool and POST /api/tasks.
|
|
5
|
+
* POST /api/tasks also refuses an agent a foreign parent or a chat thread it does not own.
|
|
5
6
|
*/
|
|
6
7
|
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
7
8
|
import { unlink } from "node:fs/promises";
|
|
@@ -18,6 +19,7 @@ import {
|
|
|
18
19
|
initDb,
|
|
19
20
|
} from "../be/db";
|
|
20
21
|
import { handleTasks } from "../http/tasks";
|
|
22
|
+
import { chatSdkContextKey } from "../tasks/context-key";
|
|
21
23
|
import { registerSendTaskTool } from "../tools/send-task";
|
|
22
24
|
import { type HttpRequestAuth, setRequestAuth } from "../utils/request-auth-context";
|
|
23
25
|
|
|
@@ -258,3 +260,59 @@ describe("POST /api/tasks with an X-Agent-ID on the swarm key", () => {
|
|
|
258
260
|
expect(lead.body.requestedByUserId).toBe(userBId);
|
|
259
261
|
});
|
|
260
262
|
});
|
|
263
|
+
|
|
264
|
+
describe("POST /api/tasks parent and chat-thread binding for an agent caller", () => {
|
|
265
|
+
test("a worker cannot attach a child to a task it neither owns nor created", async () => {
|
|
266
|
+
const foreign = await createTaskExtended("lead chat task (http)", {
|
|
267
|
+
agentId: LEAD_ID,
|
|
268
|
+
source: "chat",
|
|
269
|
+
contextKey: chatSdkContextKey("telegram:552222"),
|
|
270
|
+
});
|
|
271
|
+
const before = await taskCount();
|
|
272
|
+
const result = await postTask(
|
|
273
|
+
{ agentId: WORKER_ID },
|
|
274
|
+
{ parentTaskId: foreign.id, agentId: WORKER_ID, routingReason: "skill" },
|
|
275
|
+
);
|
|
276
|
+
expect(result.status).toBe(403);
|
|
277
|
+
expect(await taskCount()).toBe(before);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
test("a worker cannot bind a new task to a chat thread by contextKey", async () => {
|
|
281
|
+
const before = await taskCount();
|
|
282
|
+
const result = await postTask(
|
|
283
|
+
{ agentId: WORKER_ID },
|
|
284
|
+
{
|
|
285
|
+
contextKey: chatSdkContextKey("telegram:552222"),
|
|
286
|
+
agentId: WORKER_ID,
|
|
287
|
+
routingReason: "skill",
|
|
288
|
+
},
|
|
289
|
+
);
|
|
290
|
+
expect(result.status).toBe(403);
|
|
291
|
+
expect(await taskCount()).toBe(before);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
test("a worker may attach under its own task, and the operator and a lead keep access", async () => {
|
|
295
|
+
const own = await createTaskExtended("worker chat task (http)", {
|
|
296
|
+
agentId: WORKER_ID,
|
|
297
|
+
source: "chat",
|
|
298
|
+
contextKey: chatSdkContextKey("telegram:551111"),
|
|
299
|
+
});
|
|
300
|
+
const child = await postTask(
|
|
301
|
+
{ agentId: WORKER_ID },
|
|
302
|
+
{ parentTaskId: own.id, agentId: OTHER_WORKER_ID, routingReason: "skill" },
|
|
303
|
+
);
|
|
304
|
+
expect(child.status).toBe(201);
|
|
305
|
+
|
|
306
|
+
const foreign = await createTaskExtended("lead chat task (http, open)", {
|
|
307
|
+
agentId: LEAD_ID,
|
|
308
|
+
source: "chat",
|
|
309
|
+
contextKey: chatSdkContextKey("telegram:553333"),
|
|
310
|
+
});
|
|
311
|
+
expect((await postTask({}, { parentTaskId: foreign.id })).status).toBe(201);
|
|
312
|
+
expect((await postTask({ agentId: LEAD_ID }, { parentTaskId: foreign.id })).status).toBe(201);
|
|
313
|
+
expect(
|
|
314
|
+
(await postTask({ agentId: LEAD_ID }, { contextKey: chatSdkContextKey("telegram:554444") }))
|
|
315
|
+
.status,
|
|
316
|
+
).toBe(201);
|
|
317
|
+
});
|
|
318
|
+
});
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
// behavior that is uneconomical to express through a server round-trip.
|
|
4
4
|
|
|
5
5
|
import { describe, expect, test } from "bun:test";
|
|
6
|
+
import { loadModelsDevCache } from "../be/modelsdev-cache";
|
|
7
|
+
import { buildModelsDevSeedRows } from "../be/seed-pricing";
|
|
6
8
|
import { recomputeSessionCost } from "../http/session-cost-recompute";
|
|
7
9
|
import type { PricingProvider, PricingTokenClass } from "../types";
|
|
8
10
|
|
|
@@ -215,3 +217,47 @@ describe("recomputeSessionCost — pure branch behavior", () => {
|
|
|
215
217
|
expect(result.costSource).toBe("pricing-table");
|
|
216
218
|
});
|
|
217
219
|
});
|
|
220
|
+
|
|
221
|
+
describe("nan and helmcode sessions price at $0 from the seeded table", () => {
|
|
222
|
+
// Rows projected from the vendored snapshot, exactly as the boot seeder and
|
|
223
|
+
// the runtime refresh insert them.
|
|
224
|
+
const cache = loadModelsDevCache();
|
|
225
|
+
const rows = cache ? buildModelsDevSeedRows(cache) : [];
|
|
226
|
+
const book: RateBook = Object.fromEntries(
|
|
227
|
+
rows.map((row) => [`${row.provider}:${row.model}:${row.tokenClass}`, row.pricePerMillionUsd]),
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
test("pi on nan/glm5.3-flash and opencode on helmcode/qwen3.6 record pricing-table, total 0", async () => {
|
|
231
|
+
for (const [provider, model] of [
|
|
232
|
+
["pi", "nan/glm5.3-flash"],
|
|
233
|
+
["opencode", "helmcode/qwen3.6"],
|
|
234
|
+
] as const) {
|
|
235
|
+
const result = await recomputeSessionCost(
|
|
236
|
+
{
|
|
237
|
+
...BASE,
|
|
238
|
+
provider,
|
|
239
|
+
model,
|
|
240
|
+
harnessCostUsd: 0,
|
|
241
|
+
inputTokens: 50_000,
|
|
242
|
+
outputTokens: 2_000,
|
|
243
|
+
cacheReadTokens: 10_000,
|
|
244
|
+
},
|
|
245
|
+
lookupFrom(book),
|
|
246
|
+
);
|
|
247
|
+
expect(result.costSource).toBe("pricing-table");
|
|
248
|
+
expect(result.totalCostUsd).toBe(0);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
test("nan/qwen3.6 and helmcode/qwen3.6 are separate pricing keys", () => {
|
|
253
|
+
for (const harness of ["pi", "opencode"]) {
|
|
254
|
+
const keys = new Set(
|
|
255
|
+
rows
|
|
256
|
+
.filter((row) => row.provider === harness && row.model.endsWith("/qwen3.6"))
|
|
257
|
+
.map((row) => row.model),
|
|
258
|
+
);
|
|
259
|
+
expect(keys.has("nan/qwen3.6")).toBe(true);
|
|
260
|
+
expect(keys.has("helmcode/qwen3.6")).toBe(true);
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
});
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
2
|
+
import { unlink } from "node:fs/promises";
|
|
3
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
import { closeDb, createAgent, createSkill, initDb } from "../be/db";
|
|
5
|
+
import { skillsSeeder } from "../be/seed-skills";
|
|
6
|
+
import { registerSkillGetTool } from "../tools/skills/skill-get";
|
|
7
|
+
|
|
8
|
+
const TEST_DB_PATH = "./test-skill-get-name-resolution.sqlite";
|
|
9
|
+
|
|
10
|
+
const CALLER_ID = "aaaa0000-0000-4000-8000-000000000031";
|
|
11
|
+
const OTHER_ID = "bbbb0000-0000-4000-8000-000000000032";
|
|
12
|
+
const THIRD_ID = "cccc0000-0000-4000-8000-000000000033";
|
|
13
|
+
|
|
14
|
+
type StructuredContent = {
|
|
15
|
+
success: boolean;
|
|
16
|
+
message: string;
|
|
17
|
+
skill?: { id: string; ownerAgentId: string | null };
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
async function callSkillGet(
|
|
21
|
+
server: McpServer,
|
|
22
|
+
callerAgentId: string | undefined,
|
|
23
|
+
args: Record<string, unknown>,
|
|
24
|
+
): Promise<StructuredContent> {
|
|
25
|
+
// biome-ignore lint/complexity/noBannedTypes: accessing internal MCP SDK type for test
|
|
26
|
+
const tools = (server as unknown as { _registeredTools: Record<string, { handler: Function }> })
|
|
27
|
+
._registeredTools;
|
|
28
|
+
const result = await tools["skill-get"].handler(args, {
|
|
29
|
+
sessionId: "test-session",
|
|
30
|
+
requestInfo: { headers: { "x-agent-id": callerAgentId ?? "" } },
|
|
31
|
+
});
|
|
32
|
+
return (result as { structuredContent: StructuredContent }).structuredContent;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function swarmSkill(name: string, ownerAgentId: string | null) {
|
|
36
|
+
return createSkill({
|
|
37
|
+
name,
|
|
38
|
+
description: `${name} description`,
|
|
39
|
+
content: `---\nname: ${name}\ndescription: ${name} description\n---\n\nBody.`,
|
|
40
|
+
type: "personal",
|
|
41
|
+
scope: "swarm",
|
|
42
|
+
ownerAgentId: ownerAgentId ?? undefined,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
describe("skill-get swarm name resolution", () => {
|
|
47
|
+
let server: McpServer;
|
|
48
|
+
|
|
49
|
+
beforeAll(async () => {
|
|
50
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
51
|
+
try {
|
|
52
|
+
await unlink(TEST_DB_PATH + suffix);
|
|
53
|
+
} catch {
|
|
54
|
+
// File doesn't exist
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
closeDb();
|
|
58
|
+
initDb(TEST_DB_PATH);
|
|
59
|
+
for (const [id, name] of [
|
|
60
|
+
[CALLER_ID, "Caller"],
|
|
61
|
+
[OTHER_ID, "Other"],
|
|
62
|
+
[THIRD_ID, "Third"],
|
|
63
|
+
] as const) {
|
|
64
|
+
await createAgent({ id, name, isLead: false, status: "idle" });
|
|
65
|
+
}
|
|
66
|
+
server = new McpServer({ name: "test-skill-get-name-resolution", version: "1.0.0" });
|
|
67
|
+
registerSkillGetTool(server);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
afterAll(async () => {
|
|
71
|
+
closeDb();
|
|
72
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
73
|
+
try {
|
|
74
|
+
await unlink(TEST_DB_PATH + suffix);
|
|
75
|
+
} catch {
|
|
76
|
+
// ignore
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("an owned-only swarm skill resolves by name", async () => {
|
|
82
|
+
const owned = await swarmSkill("owned-only-skill", OTHER_ID);
|
|
83
|
+
|
|
84
|
+
const withCaller = await callSkillGet(server, CALLER_ID, { name: "owned-only-skill" });
|
|
85
|
+
expect(withCaller.success).toBe(true);
|
|
86
|
+
expect(withCaller.skill?.id).toBe(owned.id);
|
|
87
|
+
|
|
88
|
+
const anonymous = await callSkillGet(server, undefined, { name: "owned-only-skill" });
|
|
89
|
+
expect(anonymous.success).toBe(true);
|
|
90
|
+
expect(anonymous.skill?.id).toBe(owned.id);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("the ownerless row beats owned rows, including the caller's own", async () => {
|
|
94
|
+
// Live shape: qa-loom has an ownerless row and an agent-owned twin.
|
|
95
|
+
const owned = await swarmSkill("shadowed-skill", CALLER_ID);
|
|
96
|
+
const ownerless = await swarmSkill("shadowed-skill", null);
|
|
97
|
+
expect(owned.createdAt <= ownerless.createdAt).toBe(true);
|
|
98
|
+
|
|
99
|
+
const result = await callSkillGet(server, CALLER_ID, { name: "shadowed-skill" });
|
|
100
|
+
expect(result.success).toBe(true);
|
|
101
|
+
expect(result.skill?.id).toBe(ownerless.id);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("the caller's own row beats another agent's row", async () => {
|
|
105
|
+
await swarmSkill("two-owner-skill", OTHER_ID);
|
|
106
|
+
const mine = await swarmSkill("two-owner-skill", CALLER_ID);
|
|
107
|
+
|
|
108
|
+
const result = await callSkillGet(server, CALLER_ID, { name: "two-owner-skill" });
|
|
109
|
+
expect(result.success).toBe(true);
|
|
110
|
+
expect(result.skill?.id).toBe(mine.id);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("two foreign-owned rows return an error naming both ids", async () => {
|
|
114
|
+
const first = await swarmSkill("foreign-pair-skill", OTHER_ID);
|
|
115
|
+
const second = await swarmSkill("foreign-pair-skill", THIRD_ID);
|
|
116
|
+
|
|
117
|
+
const result = await callSkillGet(server, CALLER_ID, { name: "foreign-pair-skill" });
|
|
118
|
+
expect(result.success).toBe(false);
|
|
119
|
+
expect(result.message).toContain(first.id);
|
|
120
|
+
expect(result.message).toContain(second.id);
|
|
121
|
+
expect(result.message).toContain("skillId");
|
|
122
|
+
|
|
123
|
+
const byId = await callSkillGet(server, CALLER_ID, { skillId: second.id });
|
|
124
|
+
expect(byId.success).toBe(true);
|
|
125
|
+
expect(byId.skill?.id).toBe(second.id);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("the seed path still matches ownerless rows only", async () => {
|
|
129
|
+
await swarmSkill("seed-owned-only", OTHER_ID);
|
|
130
|
+
|
|
131
|
+
const item = { key: "seed-owned-only", contentHash: "" } as never;
|
|
132
|
+
expect(await skillsSeeder.upstreamHash(item)).toBeNull();
|
|
133
|
+
|
|
134
|
+
await swarmSkill("seed-owned-only", null);
|
|
135
|
+
expect(await skillsSeeder.upstreamHash(item)).not.toBeNull();
|
|
136
|
+
});
|
|
137
|
+
});
|
|
@@ -23,10 +23,11 @@ describe("Sonnet 5.5 registry", () => {
|
|
|
23
23
|
.filter((row) => row.provider === provider && row.model === model)
|
|
24
24
|
.map((row) => [row.tokenClass, row.pricePerMillionUsd]),
|
|
25
25
|
);
|
|
26
|
+
// Sonnet 5.5 cache hits cost 0.05x input, not the usual 0.1x.
|
|
26
27
|
expect(rates).toEqual({
|
|
27
28
|
input: 2,
|
|
28
29
|
output: 10,
|
|
29
|
-
cached_input: 0.
|
|
30
|
+
cached_input: 0.1,
|
|
30
31
|
cache_write: 2.5,
|
|
31
32
|
cache_write_1h: 4,
|
|
32
33
|
});
|
|
@@ -41,4 +42,11 @@ describe("Sonnet 5.5 registry", () => {
|
|
|
41
42
|
).toBe(2);
|
|
42
43
|
}
|
|
43
44
|
});
|
|
45
|
+
|
|
46
|
+
test("the shortname fallback seeds the same cache-read rate when the snapshot lacks the model", () => {
|
|
47
|
+
const row = buildModelsDevSeedRows({ anthropic: { models: {} } }).find(
|
|
48
|
+
(r) => r.provider === "claude" && r.model === "sonnet" && r.tokenClass === "cached_input",
|
|
49
|
+
);
|
|
50
|
+
expect(row?.pricePerMillionUsd).toBe(0.1);
|
|
51
|
+
});
|
|
44
52
|
});
|