@desplega.ai/agent-swarm 2.3.0 → 2.4.1
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 -0
- package/dist/{acp-adapter-v85a2bqy.js → acp-adapter-zvzr538d.js} +9 -9
- package/dist/{actions-zgcxfspj.js → actions-jhtqdw69.js} +21 -19
- package/dist/{amp-adapter-jw9xfz42.js → amp-adapter-bd84rhs2.js} +4 -4
- package/dist/{app-4e1s4675.js → app-kreq1gwg.js} +3 -3
- package/dist/{assistant-v238n9dk.js → assistant-3k22sbsq.js} +23 -21
- package/dist/{boot-reembed-p666cw5n.js → boot-reembed-e53x6b9v.js} +1 -1
- package/dist/{boot-reembed-w6pm64j4.js → boot-reembed-f6z923ny.js} +1 -1
- package/dist/{boot-scrub-sweep-jcyjagn3.js → boot-scrub-sweep-0538vsb7.js} +4 -4
- package/dist/{boot-seeds-gbedmamp.js → boot-seeds-6wxqkfk2.js} +4 -4
- package/dist/{claude-adapter-q7wspzt8.js → claude-adapter-gfts5044.js} +9 -9
- package/dist/{claude-managed-adapter-n7ahjyx6.js → claude-managed-adapter-h2y889ge.js} +3 -3
- package/dist/{claude-managed-setup-wza784fb.js → claude-managed-setup-h00hsgtz.js} +2 -2
- package/dist/{claude-sdk-session-yf400ptr.js → claude-sdk-session-56besmfz.js} +13 -13
- package/dist/{cli-65q70v2v.js → cli-0bh477zn.js} +33 -6
- package/dist/{cli-4ync7m60.js → cli-0ebey9hg.js} +1 -1
- package/dist/{cli-665j3feb.js → cli-0xk505e8.js} +1 -1
- package/dist/{cli-7gh7020n.js → cli-1dmcq2eh.js} +1 -1
- package/dist/{cli-gzvb3d4a.js → cli-1jx0srjt.js} +3 -3
- package/dist/{cli-zj4d6afs.js → cli-1pa3rz69.js} +21 -6
- package/dist/{cli-7ayprxp7.js → cli-2dfbv8qr.js} +8 -0
- package/dist/{cli-30m5q8mc.js → cli-3kh4kpqy.js} +6 -6
- package/dist/{cli-qrwp40st.js → cli-3p8fqcnh.js} +2 -2
- package/dist/{cli-y3dvz6ge.js → cli-3r448dqn.js} +3 -3
- package/dist/{cli-a17tzmn0.js → cli-3tnpj2bh.js} +7 -7
- package/dist/{cli-ea7chd26.js → cli-471qk6s2.js} +93 -46
- package/dist/{cli-a49vpbf3.js → cli-5kqnemen.js} +1 -1
- package/dist/{cli-0rh20bp3.js → cli-6ncjvx55.js} +3 -3
- package/dist/{cli-fb6t92v8.js → cli-6x9ma5w5.js} +1 -1
- package/dist/{cli-k28p6xh7.js → cli-7225gwj7.js} +2 -2
- package/dist/{cli-8cvvytad.js → cli-8nyhf7za.js} +3 -3
- package/dist/{cli-e09hj50q.js → cli-8wkb09q5.js} +11 -11
- package/dist/{cli-j0v75y13.js → cli-a0kcty51.js} +4 -4
- package/dist/{cli-zes4tsbb.js → cli-a4gvwttf.js} +4 -4
- package/dist/{cli-jhp7w1fy.js → cli-a5ebc95n.js} +2 -2
- package/dist/{cli-5d1m1qca.js → cli-ash6xxk7.js} +2 -2
- package/dist/{cli-fp4xbw07.js → cli-axh29tyr.js} +14 -12
- package/dist/{cli-p22zdvnt.js → cli-bn0rewsy.js} +3 -3
- package/dist/{cli-hz64bcnj.js → cli-bn5kevnf.js} +11 -373
- package/dist/{cli-8jtrekpy.js → cli-d2mb5s8a.js} +7 -7
- package/dist/{cli-c5sbmw1v.js → cli-d82btycs.js} +94 -15
- package/dist/{cli-vjfpshn3.js → cli-ejs3m1sx.js} +1 -1
- package/dist/{cli-yv0vgscc.js → cli-fpmsqq59.js} +18 -2
- package/dist/cli-fz9kewbh.js +382 -0
- package/dist/{cli-4s1kqrqg.js → cli-g0hgtz4p.js} +6 -6
- package/dist/{cli-hn3pftj2.js → cli-gf0kfg81.js} +7 -7
- package/dist/{cli-d43xqzbe.js → cli-hqhzg1qq.js} +2 -2
- package/dist/{cli-azs9bhc5.js → cli-j4rqdf2n.js} +2 -2
- package/dist/{cli-wtg6edvd.js → cli-jh0qcrga.js} +1 -1
- package/dist/{cli-kqypty34.js → cli-k9z4zd4p.js} +6 -5
- package/dist/{cli-jhmjwqcg.js → cli-kmqgmcnb.js} +47 -14
- package/dist/{cli-8qsx817b.js → cli-kr2pmfqx.js} +583 -769
- package/dist/{cli-4g629sgr.js → cli-m2qgh5sv.js} +20 -1
- package/dist/{cli-9e9kjqsj.js → cli-mfe7f7b5.js} +2 -2
- package/dist/{cli-x3en6ct3.js → cli-nwyw3zhg.js} +1 -1
- package/dist/{cli-6vcyw6v7.js → cli-nx462e4v.js} +5 -3
- package/dist/{cli-tr24eeny.js → cli-p0rakbrk.js} +9 -9
- package/dist/{cli-4hmaheb6.js → cli-p6argxh9.js} +9 -9
- package/dist/{cli-3avfh5az.js → cli-pemywhfq.js} +2 -2
- package/dist/{cli-etvn5ej2.js → cli-pnp2j5zg.js} +2 -2
- package/dist/{cli-wk8ccrz6.js → cli-pw7v69vp.js} +2 -2
- package/dist/{cli-ka01m5rb.js → cli-rdq7tzwm.js} +17 -14
- package/dist/{cli-re4wkbzx.js → cli-rqqsfz30.js} +677 -104
- package/dist/{cli-xgn4rwyv.js → cli-s48yvzb8.js} +4 -4
- package/dist/{cli-zv4vy2b0.js → cli-s95dknxh.js} +1 -1
- package/dist/{cli-g6p7c2my.js → cli-sjyetj21.js} +2 -2
- package/dist/{cli-rdr166ht.js → cli-smvcw9z5.js} +1 -1
- package/dist/{cli-fhvqt2fp.js → cli-sv5gb0yb.js} +1 -1
- package/dist/{cli-yv3n83x8.js → cli-syhq378v.js} +2 -2
- package/dist/{cli-q1812jbw.js → cli-tbhwrxpt.js} +9 -9
- package/dist/{cli-w7z94e6c.js → cli-tx00c0h6.js} +4 -2
- package/dist/{cli-dxwh4j32.js → cli-vy0fay6k.js} +46 -37
- package/dist/{cli-ptkpaqfy.js → cli-wt645v7q.js} +1 -1
- package/dist/{cli-ppapy34w.js → cli-wxbn3yfx.js} +2 -2
- package/dist/{cli-cby329ny.js → cli-x5mq1fyq.js} +3 -3
- package/dist/{cli-rc3r0bnh.js → cli-xbzvet96.js} +6 -6
- package/dist/{cli-4mkvwgzr.js → cli-xf234ws8.js} +1 -1
- package/dist/{cli-6p7ddg72.js → cli-xftrvxag.js} +5 -5
- package/dist/cli-z071fb95.js +848 -0
- package/dist/{cli-1v8w782f.js → cli-z1ew8vbq.js} +14 -7
- package/dist/{cli-9td5ss65.js → cli-zfg9enh9.js} +3 -3
- package/dist/{cli-yxqag76b.js → cli-zw88zjba.js} +1 -1
- package/dist/cli.js +19 -19
- package/dist/{codex-adapter-nhjj2veb.js → codex-adapter-p6rmm7vv.js} +6 -6
- package/dist/{codex-hook-x6tm6y5m.js → codex-hook-x7nejyrr.js} +8 -5
- package/dist/{codex-login-35p4sk85.js → codex-login-m819c41r.js} +1 -1
- package/dist/{codex-session-runner-6hggrjbx.js → codex-session-runner-8vd5t9t0.js} +7 -7
- package/dist/{commands-mrw1ph2w.js → commands-7ems2p13.js} +2 -2
- package/dist/{controller-pt1zam5w.js → controller-m263fg43.js} +4 -4
- package/dist/{controllers-connect-ymw5541d.js → controllers-connect-bg33ae53.js} +28 -24
- package/dist/{cursor-adapter-cf4zh5x0.js → cursor-adapter-cz6nm6mj.js} +3 -3
- package/dist/{db-hy80219q.js → db-w505dwa8.js} +2 -2
- package/dist/{db-hhpy6m4x.js → db-ys2rbdft.js} +3 -2
- package/dist/{devin-adapter-7egzmt0k.js → devin-adapter-zgqadvnm.js} +1 -1
- package/dist/{dispatcher-6pdjtkr1.js → dispatcher-0s0mckqr.js} +20 -18
- package/dist/{docker-njavd3fb.js → docker-fkgtr75t.js} +2 -2
- package/dist/{dsh-adapter-fbx06re2.js → dsh-adapter-3n3net33.js} +3 -3
- package/dist/{e2b-5r6a5jeb.js → e2b-7am6tmq6.js} +2 -2
- package/dist/{e2b-pw1nvtmh.js → e2b-thdcxtjd.js} +2 -2
- package/dist/{easter-egg-3d-6hg2r942.js → easter-egg-3d-5cgynaja.js} +1 -1
- package/dist/{event-bus-101fenkh.js → event-bus-9vpk8gmq.js} +1 -1
- package/dist/{execute-bcqc69a5.js → execute-t4e6m3rw.js} +1 -1
- package/dist/{external-channel-j6vb6ya1.js → external-channel-5b23fv8z.js} +2 -2
- package/dist/{google-generative-ai-phpmwxvg.js → google-generative-ai-bmq8gkf3.js} +1 -1
- package/dist/{google-vertex-es9ybgdy.js → google-vertex-b1rvp52s.js} +1 -1
- package/dist/{grok-adapter-1193sprp.js → grok-adapter-q9fghe5q.js} +10 -10
- package/dist/handlers-mqrym347.js +39 -0
- package/dist/{heartbeat-h1w1ktce.js → heartbeat-tww6yx1w.js} +23 -21
- package/dist/{hook-ejja4bpe.js → hook-dx0pkvar.js} +16 -9
- package/dist/{hook-skrbja18.js → hook-pvmh9b6m.js} +6 -6
- package/dist/{http-mzh41mfj.js → http-k2ejbt05.js} +975 -292
- package/dist/{index-6rmnn89d.js → index-2z710v4x.js} +10 -10
- package/dist/{index-xmmkyx82.js → index-dgz61nff.js} +20 -18
- package/dist/{index-wgsw9xcz.js → index-g6tbb6ve.js} +1 -1
- package/dist/index-h2ek08d6.js +40 -0
- package/dist/index-pqfwh9nn.js +41 -0
- package/dist/{index-epr2gnbw.js → index-x5aktmp8.js} +1 -1
- package/dist/{index-x0q2a4f9.js → index-ysd45kgc.js} +1 -1
- package/dist/{index-g909y089.js → index-zgvqzjsw.js} +8 -8
- package/dist/{keepalive-4djvbm2j.js → keepalive-nhvpw10z.js} +4 -4
- package/dist/lead-jb6sf69z.js +78 -0
- package/dist/{maintenance-f3xhnjsm.js → maintenance-agakc519.js} +1 -1
- package/dist/{oauth-refresh-sweep-fpd6x6rw.js → oauth-refresh-sweep-4dcbhtn0.js} +3 -3
- package/dist/{onboard-196tjgyn.js → onboard-4n2kwa4v.js} +3 -3
- package/dist/{opencode-adapter-eeesgfqk.js → opencode-adapter-3s7ax02e.js} +7 -7
- package/dist/{otel-impl-g8fh4c21.js → otel-impl-tfa12080.js} +2 -2
- package/dist/{pi-mono-adapter-zasp3ze3.js → pi-mono-adapter-ytp7gz8b.js} +24 -13
- package/dist/{pricing-refresh-sdcf5yaz.js → pricing-refresh-3cqx3f3x.js} +3 -3
- package/dist/{rbac-roles-bsp5657v.js → rbac-roles-9fae5rnv.js} +5 -3
- package/dist/{rbac-roles-2x4g1wb5.js → rbac-roles-y6zbfzve.js} +2 -2
- package/dist/{render-v2-0pyb39bb.js → render-v2-emawjvc4.js} +3 -3
- package/dist/{resume-a86tkj6r.js → resume-zhgts7h7.js} +10 -8
- package/dist/{runtime-ar8310ba.js → runtime-xe3aqy4x.js} +1 -1
- package/dist/session-access-cm823e06.js +25 -0
- package/dist/{setup-ypbzcapd.js → setup-tkp99w1e.js} +1 -1
- package/dist/{virtual-modules-wzk6jf3a.js → virtual-modules-4r3zkc20.js} +2 -2
- package/dist/{work-objects-yjp8de12.js → work-objects-f722h263.js} +23 -21
- package/dist/worker-p7f336rq.js +78 -0
- package/dist/{x-8ewt8gxn.js → x-y45mthwn.js} +2 -2
- package/openapi.json +1086 -305
- package/package.json +2 -2
- package/src/apps/action-caller.ts +6 -0
- package/src/apps/action-runner.ts +146 -0
- package/src/apps/row-store.ts +4 -2
- package/src/apps/script-types.ts +17 -4
- package/src/be/db/active-sessions.ts +5 -0
- package/src/be/db/session-visibility.ts +29 -0
- package/src/be/db/tasks/read.ts +61 -3
- package/src/be/db/tasks/write.ts +15 -6
- package/src/be/db.ts +106 -37
- package/src/be/events.ts +11 -0
- package/src/be/fleet-benchmark-stats.ts +10 -5
- package/src/be/migrations/230_session_shares.sql +23 -0
- package/src/be/models-catalog.ts +4 -0
- package/src/be/onboarding.ts +6 -0
- package/src/be/rbac-roles.ts +2 -0
- package/src/be/scripts/typecheck.ts +13 -0
- package/src/be/session-shares.ts +244 -0
- package/src/be/swarm-config-guard.ts +33 -3
- package/src/be/telemetry-enable.ts +1 -1
- package/src/commands/provider-credentials.ts +12 -2
- package/src/commands/runner.ts +14 -0
- package/src/heartbeat/heartbeat.ts +2 -2
- package/src/hooks/codex-hook.ts +6 -2
- package/src/hooks/hook.ts +7 -0
- package/src/hooks/pr-body-guard.ts +38 -11
- package/src/hooks/pr-room-guard.ts +88 -0
- package/src/http/active-sessions.ts +13 -2
- package/src/http/agents.ts +11 -3
- package/src/http/all-routes.ts +1 -0
- package/src/http/api-keys.ts +3 -1
- package/src/http/approval-requests.ts +2 -1
- package/src/http/apps.ts +118 -190
- package/src/http/config.ts +3 -0
- package/src/http/context.ts +2 -0
- package/src/http/core.ts +39 -2
- package/src/http/events.ts +6 -2
- package/src/http/favorite-owner.ts +1 -1
- package/src/http/fleet-benchmark.ts +79 -30
- package/src/http/fs.ts +38 -3
- package/src/http/global-config.ts +7 -0
- package/src/http/index.ts +3 -1
- package/src/http/memory.ts +3 -1
- package/src/http/ranking.ts +351 -0
- package/src/http/realtime.ts +26 -21
- package/src/http/route-def.ts +77 -11
- package/src/http/script-runs.ts +2 -1
- package/src/http/scripts.ts +8 -0
- package/src/http/session-access.ts +179 -0
- package/src/http/session-data.ts +10 -2
- package/src/http/sessions.ts +212 -12
- package/src/http/stats.ts +7 -3
- package/src/http/tasks.ts +85 -8
- package/src/http/telemetry-ui.ts +1 -1
- package/src/http/used-by.ts +64 -20
- package/src/http/users.ts +2 -2
- package/src/prompts/session-templates.ts +3 -1
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/pi-mono-adapter.ts +15 -0
- package/src/providers/reasoning-effort.ts +2 -1
- package/src/providers/types.ts +2 -0
- package/src/ranking/telemetry-ranking.ts +119 -0
- package/src/ranking/usage.ts +57 -0
- package/src/rbac/admission.ts +22 -7
- package/src/rbac/legacy-policy.ts +2 -0
- package/src/rbac/permissions.ts +9 -0
- package/src/realtime/auth.ts +69 -0
- package/src/realtime/tickets.ts +4 -1
- package/src/realtime/transport.ts +77 -9
- package/src/scripts-runtime/sdk-allowlist.ts +7 -0
- package/src/scripts-runtime/types/stdlib.d.ts +31 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +31 -0
- package/src/server.ts +4 -0
- package/src/slack/blocks.ts +18 -0
- package/src/slack/dm.ts +29 -0
- package/src/telemetry-context.ts +19 -1
- package/src/telemetry-contract/EVENTS.md +45 -8
- package/src/telemetry-contract/SOURCE.json +38 -4
- package/src/telemetry-contract/catalog.json +294 -5
- package/src/telemetry-contract/ranking/fixtures/expected.json +30 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/bad-category.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/bad-claim.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/blank-description.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/blank-name.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/description-too-long.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/domain-uppercase.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/domain-with-scheme.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/domain-with-www.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/http-source-url.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/missing-description.json +10 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/missing-version.json +10 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/name-not-string.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/name-too-long.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/not-object.json +1 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/null.json +1 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/other-with-subcategory.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/oversize.json +1 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/short-claim.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/subcategory-other-category.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/trailing-data.json +1 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/unknown-field.json +12 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/unsupported-version.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/updated-at-not-date.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/updated-at-offset.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/invalid/wrong-case-field.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/valid/category-without-subcategory.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/valid/full.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/valid/minimal.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/valid/no-claim.json +10 -0
- package/src/telemetry-contract/ranking/fixtures/valid/padded-text.json +11 -0
- package/src/telemetry-contract/ranking/fixtures/valid/unicode-name.json +11 -0
- package/src/telemetry-contract/ranking/listing.v1.schema.json +61 -0
- package/src/telemetry-contract/ranking/taxonomy.json +8 -0
- package/src/telemetry-contract/ranking-taxonomy.gen.ts +11 -0
- package/src/telemetry-contract/types.gen.ts +963 -58
- package/src/telemetry-identity.ts +47 -9
- package/src/telemetry-snapshot.ts +9 -1
- package/src/telemetry-ui-events.ts +9 -2
- package/src/telemetry.ts +40 -5
- package/src/tests/app-action-run-tool.test.ts +178 -0
- package/src/tests/app-row-tools.test.ts +360 -0
- package/src/tests/apps-rbac.test.ts +189 -2
- package/src/tests/apps-script-types.test.ts +16 -3
- package/src/tests/credential-check.test.ts +38 -0
- package/src/tests/db-extraction-contract.test.ts +1 -0
- package/src/tests/db-task-read-contract.test.ts +1 -0
- package/src/tests/docs-index-footprint.test.js +2 -2
- package/src/tests/fleet-benchmark.test.ts +122 -2
- package/src/tests/http-users.test.ts +62 -1
- package/src/tests/model-catalog-table.test.ts +63 -0
- package/src/tests/model-profiles-api.test.ts +19 -7
- package/src/tests/models-catalog.test.ts +50 -0
- package/src/tests/onboarding.test.ts +21 -0
- package/src/tests/org-identity.test.ts +44 -0
- package/src/tests/org-website.test.ts +62 -0
- package/src/tests/pi-mono-adapter.test.ts +23 -0
- package/src/tests/pr-body-leaks.test.ts +4 -0
- package/src/tests/pr-room-guard.test.ts +242 -0
- package/src/tests/ranking-listing.test.ts +97 -0
- package/src/tests/ranking-route.test.ts +321 -0
- package/src/tests/ranking-usage.test.ts +50 -0
- package/src/tests/rbac-admission-e2e.test.ts +8 -0
- package/src/tests/rbac-admission.test.ts +38 -0
- package/src/tests/rbac-charact-http.test.ts +4 -3
- package/src/tests/rbac-engine.test.ts +2 -0
- package/src/tests/rbac-roles.test.ts +2 -0
- package/src/tests/realtime-room-surfaces.test.ts +3 -1
- package/src/tests/realtime-session-join-race.test.ts +142 -0
- package/src/tests/realtime-tickets.test.ts +24 -17
- package/src/tests/realtime-transport.test.ts +91 -0
- package/src/tests/reasoning-effort.test.ts +39 -3
- package/src/tests/runner-harness-telemetry-env.test.ts +56 -1
- package/src/tests/session-route-posture.test.ts +639 -0
- package/src/tests/session-sharing-routes.test.ts +282 -0
- package/src/tests/session-sharing.test.ts +526 -0
- package/src/tests/sessions-detail-batch.test.ts +7 -1
- package/src/tests/slack-tool-blocks.test.ts +101 -0
- package/src/tests/slack-update.test.ts +55 -0
- package/src/tests/slack-user-dm.test.ts +177 -0
- package/src/tests/swarm-tool-result-gate.test.ts +2 -4
- package/src/tests/telemetry-context.test.ts +128 -2
- package/src/tests/telemetry-ui-route.test.ts +123 -0
- package/src/tests/tool-annotations.test.ts +3 -2
- package/src/tests/used-by-route.test.ts +153 -9
- package/src/tests/workflow-agent-task-retry.test.ts +130 -4
- package/src/tests/workflow-wait-recovery.test.ts +116 -4
- package/src/tools/app-action-run.ts +63 -0
- package/src/tools/app-rows.ts +234 -0
- package/src/tools/model-catalog/index.ts +1 -1
- package/src/tools/slack-post.ts +18 -12
- package/src/tools/slack-read.ts +13 -1
- package/src/tools/slack-reply.ts +7 -10
- package/src/tools/slack-start-thread.ts +18 -11
- package/src/tools/slack-update.ts +2 -9
- package/src/tools/tool-config.ts +8 -0
- package/src/tools/utils.ts +1 -1
- package/src/tools/workflows/retry-workflow-run.ts +1 -1
- package/src/types.ts +52 -0
- package/src/utils/company-profile.ts +24 -0
- package/src/utils/org-identity.ts +33 -0
- package/src/utils/org-website.ts +58 -21
- package/src/workflows/recovery.ts +10 -22
- package/src/workflows/resume.ts +16 -0
- package/src/workflows/task-step-routing.ts +47 -7
- package/templates/skills/apps/SKILL.md +58 -2
- package/templates/skills/apps/content.md +58 -2
- package/dist/handlers-tb3xgjnm.js +0 -37
- package/dist/index-nyfcdpky.js +0 -39
- package/dist/index-vjn3wbzf.js +0 -38
- package/dist/lead-61kqp604.js +0 -76
- package/dist/worker-4ptnn1ge.js +0 -76
- package/dist/{cli-n5mrsg0f.js → cli-60cgh1rv.js} +3 -3
|
@@ -236,6 +236,26 @@ describe("checkPiMonoCredentials", () => {
|
|
|
236
236
|
).toBe(false);
|
|
237
237
|
});
|
|
238
238
|
|
|
239
|
+
test("strict: MODEL_OVERRIDE=opencode-go/... requires OPENCODE_API_KEY", async () => {
|
|
240
|
+
const env = { MODEL_OVERRIDE: "opencode-go/deepseek-v4.1-flash" };
|
|
241
|
+
expect(
|
|
242
|
+
(
|
|
243
|
+
await checkPiMonoCredentials(
|
|
244
|
+
{ ...env, OPENCODE_API_KEY: "x" },
|
|
245
|
+
{ homeDir: HOME, fs: noFiles },
|
|
246
|
+
)
|
|
247
|
+
).ready,
|
|
248
|
+
).toBe(true);
|
|
249
|
+
expect(
|
|
250
|
+
(
|
|
251
|
+
await checkPiMonoCredentials(
|
|
252
|
+
{ ...env, ANTHROPIC_API_KEY: "x" },
|
|
253
|
+
{ homeDir: HOME, fs: noFiles },
|
|
254
|
+
)
|
|
255
|
+
).ready,
|
|
256
|
+
).toBe(false);
|
|
257
|
+
});
|
|
258
|
+
|
|
239
259
|
test("strict: MODEL_OVERRIDE=google/... accepts GEMINI_API_KEY", async () => {
|
|
240
260
|
const env = {
|
|
241
261
|
MODEL_OVERRIDE: "google/gemini-3-flash-preview",
|
|
@@ -1078,6 +1098,24 @@ describe("isCredCheckDisabled", () => {
|
|
|
1078
1098
|
describe("buildCredStatusReport", () => {
|
|
1079
1099
|
const HOME = "/home/worker";
|
|
1080
1100
|
|
|
1101
|
+
test("pi reports the opencode-go ids its pi-ai resolves", async () => {
|
|
1102
|
+
const snap = await buildCredStatusReport(
|
|
1103
|
+
"pi",
|
|
1104
|
+
{ HOME },
|
|
1105
|
+
{ homeDir: HOME, fs: fsWith(new Set([`${HOME}/.pi/agent/auth.json`])) },
|
|
1106
|
+
"boot",
|
|
1107
|
+
);
|
|
1108
|
+
expect(snap.opencodeGo?.models).toContain("deepseek-v4.1-flash");
|
|
1109
|
+
expect(snap.opencodeGo?.models).not.toContain("deepseek-v4-flash");
|
|
1110
|
+
const other = await buildCredStatusReport(
|
|
1111
|
+
"opencode",
|
|
1112
|
+
{ HOME },
|
|
1113
|
+
{ homeDir: HOME, fs: fsWith(new Set([`${HOME}/.local/share/opencode/auth.json`])) },
|
|
1114
|
+
"boot",
|
|
1115
|
+
);
|
|
1116
|
+
expect(other.opencodeGo).toBeNull();
|
|
1117
|
+
});
|
|
1118
|
+
|
|
1081
1119
|
for (const [provider, authPath] of [
|
|
1082
1120
|
["pi", ".pi/agent/auth.json"],
|
|
1083
1121
|
["opencode", ".local/share/opencode/auth.json"],
|
|
@@ -15,6 +15,7 @@ test("facade exposes repository bindings without internal exports", () => {
|
|
|
15
15
|
"rowToAgentTaskSummary",
|
|
16
16
|
"configureTaskReadDependencies",
|
|
17
17
|
"NEVER_STARTED_TASK_STATUSES",
|
|
18
|
+
"readableSessionCondition",
|
|
18
19
|
].includes(name)
|
|
19
20
|
) {
|
|
20
21
|
expect(name in db).toBe(false);
|
|
@@ -103,8 +103,8 @@ describe("docs API index footprint", () => {
|
|
|
103
103
|
expect(spec.paths["/api/agents/{id}/runtime"]?.get).toBeDefined();
|
|
104
104
|
|
|
105
105
|
const generatedKeys = await generatedOperationKeys();
|
|
106
|
-
expect(operationKeys).toHaveLength(
|
|
107
|
-
expect(generatedKeys).toHaveLength(
|
|
106
|
+
expect(operationKeys).toHaveLength(436);
|
|
107
|
+
expect(generatedKeys).toHaveLength(436);
|
|
108
108
|
expect(new Set(generatedKeys)).toEqual(new Set(operationKeys));
|
|
109
109
|
});
|
|
110
110
|
|
|
@@ -209,11 +209,37 @@ describe("GET /api/fleet-benchmark", () => {
|
|
|
209
209
|
}
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
-
test("telemetry off:
|
|
212
|
+
test("telemetry off: local 30-day counts without an ingest anchor or proxy call", async () => {
|
|
213
213
|
process.env.ANONYMIZED_TELEMETRY = "false";
|
|
214
|
+
await db.getDbClient().run("DELETE FROM swarm_config WHERE key = 'telemetry_first_ingest'");
|
|
215
|
+
await addTasks(9, "completed", 3);
|
|
216
|
+
await addTasks(1, "failed", 3);
|
|
217
|
+
await addTasks(5, "pending", 3);
|
|
218
|
+
await addTasks(40, "completed", 45);
|
|
219
|
+
await setProfile();
|
|
214
220
|
const response = await buildFleetBenchmarkResponse(NOW);
|
|
215
221
|
expect(response.state).toBe("telemetry_off");
|
|
216
|
-
expect(response.local).
|
|
222
|
+
expect(response.local).toEqual({
|
|
223
|
+
tasksPerWeek: 3.5,
|
|
224
|
+
taskSuccessRate: 0.9,
|
|
225
|
+
finishedTasks: 10,
|
|
226
|
+
agents: 3,
|
|
227
|
+
bucket: "2-5",
|
|
228
|
+
});
|
|
229
|
+
expect(response.level).toBe("full");
|
|
230
|
+
expect(response.peerGroup).toBeNull();
|
|
231
|
+
expect(response.comparisons).toEqual([]);
|
|
232
|
+
expect(fetched).toEqual([]);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test("telemetry off on a fresh swarm returns zero counts", async () => {
|
|
236
|
+
process.env.ANONYMIZED_TELEMETRY = "false";
|
|
237
|
+
await db.getDbClient().run("DELETE FROM swarm_config WHERE key = 'telemetry_first_ingest'");
|
|
238
|
+
expect(await buildFleetBenchmarkResponse(NOW)).toMatchObject({
|
|
239
|
+
state: "telemetry_off",
|
|
240
|
+
level: "basic",
|
|
241
|
+
local: { tasksPerWeek: 0, taskSuccessRate: null, finishedTasks: 0 },
|
|
242
|
+
});
|
|
217
243
|
expect(fetched).toEqual([]);
|
|
218
244
|
});
|
|
219
245
|
|
|
@@ -227,6 +253,19 @@ describe("GET /api/fleet-benchmark", () => {
|
|
|
227
253
|
expect(fetched).toEqual([]);
|
|
228
254
|
});
|
|
229
255
|
|
|
256
|
+
test("an empty stored name overrides the deployment name", async () => {
|
|
257
|
+
const saved = process.env.SWARM_ORG_NAME;
|
|
258
|
+
process.env.SWARM_ORG_NAME = "Deployment name";
|
|
259
|
+
try {
|
|
260
|
+
await db.upsertSwarmConfig({ scope: "global", key: "SWARM_ORG_NAME", value: "" });
|
|
261
|
+
expect((await buildFleetBenchmarkResponse(NOW)).state).toBe("no_org_name");
|
|
262
|
+
expect(fetched).toEqual([]);
|
|
263
|
+
} finally {
|
|
264
|
+
if (saved === undefined) delete process.env.SWARM_ORG_NAME;
|
|
265
|
+
else process.env.SWARM_ORG_NAME = saved;
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
|
|
230
269
|
test("fewer than 10 finished tasks: local numbers only", async () => {
|
|
231
270
|
await addTasks(9, "completed", 3);
|
|
232
271
|
await addTasks(5, "pending", 3);
|
|
@@ -242,6 +281,8 @@ describe("GET /api/fleet-benchmark", () => {
|
|
|
242
281
|
await addTasks(40, "completed", 45); // Outside the window.
|
|
243
282
|
const response = await buildFleetBenchmarkResponse(NOW);
|
|
244
283
|
expect(response.state).toBe("ready");
|
|
284
|
+
expect(response.level).toBe("basic");
|
|
285
|
+
expect(response.peerGroup).toBeNull();
|
|
245
286
|
expect(response.local).toEqual({
|
|
246
287
|
tasksPerWeek: 14,
|
|
247
288
|
taskSuccessRate: 0.95,
|
|
@@ -259,6 +300,85 @@ describe("GET /api/fleet-benchmark", () => {
|
|
|
259
300
|
expect(fetched).toHaveLength(1);
|
|
260
301
|
});
|
|
261
302
|
|
|
303
|
+
async function setProfile(teamSize = "2-10", useCase = "features"): Promise<void> {
|
|
304
|
+
await db.upsertSwarmConfig({ scope: "global", key: "SWARM_ORG_WEBSITE", value: "acme.com" });
|
|
305
|
+
await db.upsertSwarmConfig({ scope: "global", key: "SWARM_ORG_TEAM_SIZE", value: teamSize });
|
|
306
|
+
await db.upsertSwarmConfig({ scope: "global", key: "SWARM_ORG_USE_CASE", value: useCase });
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
test("full profile requests peers and returns the proxy group", async () => {
|
|
310
|
+
await addTasks(10, "completed", 1);
|
|
311
|
+
await setProfile();
|
|
312
|
+
proxyBody = {
|
|
313
|
+
...FLEET,
|
|
314
|
+
peer_group: { team_size: "2-10", use_case: "features", orgs: 6 },
|
|
315
|
+
};
|
|
316
|
+
const response = await buildFleetBenchmarkResponse(NOW);
|
|
317
|
+
expect(response).toMatchObject({
|
|
318
|
+
state: "ready",
|
|
319
|
+
level: "full",
|
|
320
|
+
peerGroup: { teamSize: "2-10", useCase: "features", orgs: 6 },
|
|
321
|
+
});
|
|
322
|
+
expect(fetched).toEqual([
|
|
323
|
+
"http://proxy.test/v1/fleet/benchmarks?window=30d&team_size=2-10&use_case=features",
|
|
324
|
+
]);
|
|
325
|
+
await buildFleetBenchmarkResponse(NOW);
|
|
326
|
+
expect(fetched).toHaveLength(1);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
test("full profile tolerates absent or null peer_group from the proxy", async () => {
|
|
330
|
+
await addTasks(10, "completed", 1);
|
|
331
|
+
await setProfile();
|
|
332
|
+
for (const peer_group of [undefined, null]) {
|
|
333
|
+
proxyBody = { ...FLEET, peer_group };
|
|
334
|
+
const route = await freshRoute();
|
|
335
|
+
const response = await route.buildFleetBenchmarkResponse(NOW);
|
|
336
|
+
expect(response).toMatchObject({ state: "ready", level: "full", peerGroup: null });
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
test("cache follows profile changes and cleared fields return to basic", async () => {
|
|
341
|
+
await addTasks(10, "completed", 1);
|
|
342
|
+
await buildFleetBenchmarkResponse(NOW);
|
|
343
|
+
await setProfile();
|
|
344
|
+
await buildFleetBenchmarkResponse(NOW);
|
|
345
|
+
await setProfile("11-50", "bugs");
|
|
346
|
+
await buildFleetBenchmarkResponse(NOW);
|
|
347
|
+
await setProfile("11-50", "review");
|
|
348
|
+
await buildFleetBenchmarkResponse(NOW);
|
|
349
|
+
expect(fetched).toHaveLength(4);
|
|
350
|
+
expect(fetched[2]).toContain("team_size=11-50&use_case=bugs");
|
|
351
|
+
expect(fetched[3]).toContain("team_size=11-50&use_case=review");
|
|
352
|
+
await db.upsertSwarmConfig({ scope: "global", key: "SWARM_ORG_WEBSITE", value: "" });
|
|
353
|
+
const response = await buildFleetBenchmarkResponse(NOW);
|
|
354
|
+
expect(response.level).toBe("basic");
|
|
355
|
+
expect(fetched.at(-1)).toBe("http://proxy.test/v1/fleet/benchmarks?window=30d");
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
test("partial profile stays basic and sends no peer filters", async () => {
|
|
359
|
+
await addTasks(10, "completed", 1);
|
|
360
|
+
for (const key of ["SWARM_ORG_WEBSITE", "SWARM_ORG_TEAM_SIZE", "SWARM_ORG_USE_CASE"]) {
|
|
361
|
+
await setProfile();
|
|
362
|
+
await db.upsertSwarmConfig({ scope: "global", key, value: "" });
|
|
363
|
+
const route = await freshRoute();
|
|
364
|
+
expect((await route.buildFleetBenchmarkResponse(NOW)).level).toBe("basic");
|
|
365
|
+
expect(fetched.at(-1)).toBe("http://proxy.test/v1/fleet/benchmarks?window=30d");
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
test("enabling telemetry never reuses local-only counts for comparison", async () => {
|
|
370
|
+
await addTasks(10, "completed", 1);
|
|
371
|
+
await db.getDbClient().run("DELETE FROM swarm_config WHERE key = 'telemetry_first_ingest'");
|
|
372
|
+
process.env.ANONYMIZED_TELEMETRY = "false";
|
|
373
|
+
expect((await buildFleetBenchmarkResponse(NOW)).local?.finishedTasks).toBe(10);
|
|
374
|
+
process.env.ANONYMIZED_TELEMETRY = "true";
|
|
375
|
+
expect(await buildFleetBenchmarkResponse(NOW)).toMatchObject({
|
|
376
|
+
state: "unavailable",
|
|
377
|
+
local: null,
|
|
378
|
+
});
|
|
379
|
+
expect(fetched).toEqual([]);
|
|
380
|
+
});
|
|
381
|
+
|
|
262
382
|
async function mintInstallId(daysAgo: number): Promise<void> {
|
|
263
383
|
await db
|
|
264
384
|
.getDbClient()
|
|
@@ -160,12 +160,39 @@ describe("GET /api/users", () => {
|
|
|
160
160
|
});
|
|
161
161
|
|
|
162
162
|
describe("POST /api/users", () => {
|
|
163
|
+
test("email is optional, and valid email addresses are accepted", async () => {
|
|
164
|
+
for (const email of [undefined, "taras+swarm@example.com"]) {
|
|
165
|
+
const response = await authedFetch("/api/users", {
|
|
166
|
+
method: "POST",
|
|
167
|
+
body: JSON.stringify({ name: "Optional email", email, role: "engineer" }),
|
|
168
|
+
});
|
|
169
|
+
expect(response.status).toBe(200);
|
|
170
|
+
const { user } = await response.json();
|
|
171
|
+
expect(user.email ?? null).toBe(email ?? null);
|
|
172
|
+
expect(user.role).toBe("engineer");
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test("invalid email is rejected before a user is created", async () => {
|
|
177
|
+
for (const email of ["", "not-an-email", "tester@dev", null]) {
|
|
178
|
+
const response = await authedFetch("/api/users", {
|
|
179
|
+
method: "POST",
|
|
180
|
+
body: JSON.stringify({ name: "Invalid email", email }),
|
|
181
|
+
});
|
|
182
|
+
expect(response.status).toBe(400);
|
|
183
|
+
expect(await response.text()).toContain("email");
|
|
184
|
+
}
|
|
185
|
+
expect(await getDbClient().get<{ n: number }>("SELECT COUNT(*) AS n FROM users")).toEqual({
|
|
186
|
+
n: 0,
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
163
190
|
test("creates user + links identities + budget event, all tagged op:<fp>", async () => {
|
|
164
191
|
const r = await authedFetch("/api/users", {
|
|
165
192
|
method: "POST",
|
|
166
193
|
body: JSON.stringify({
|
|
167
194
|
name: "Tester",
|
|
168
|
-
email: "tester@dev",
|
|
195
|
+
email: "tester@dev.example",
|
|
169
196
|
dailyBudgetUsd: 5,
|
|
170
197
|
identities: [
|
|
171
198
|
{ kind: "slack", externalId: "U_QA1" },
|
|
@@ -197,6 +224,40 @@ describe("POST /api/users", () => {
|
|
|
197
224
|
});
|
|
198
225
|
|
|
199
226
|
describe("PATCH /api/users/:id", () => {
|
|
227
|
+
test("validates email when present and permits profile updates without email", async () => {
|
|
228
|
+
const user = await createUser({ name: "Email validation", email: "old@example.com" });
|
|
229
|
+
for (const email of ["", "invalid", "tester@dev"]) {
|
|
230
|
+
const response = await authedFetch(`/api/users/${user.id}`, {
|
|
231
|
+
method: "PATCH",
|
|
232
|
+
body: JSON.stringify({ email }),
|
|
233
|
+
});
|
|
234
|
+
expect(response.status).toBe(400);
|
|
235
|
+
expect(await response.text()).toContain("email");
|
|
236
|
+
}
|
|
237
|
+
const unchanged = await authedFetch(`/api/users/${user.id}`);
|
|
238
|
+
expect((await unchanged.json()).user.email).toBe("old@example.com");
|
|
239
|
+
for (const patch of [{ role: "founder" }, { email: "new+swarm@example.com" }]) {
|
|
240
|
+
const response = await authedFetch(`/api/users/${user.id}`, {
|
|
241
|
+
method: "PATCH",
|
|
242
|
+
body: JSON.stringify(patch),
|
|
243
|
+
});
|
|
244
|
+
expect(response.status).toBe(200);
|
|
245
|
+
expect((await response.json()).user).toMatchObject(patch);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test("null clears the primary email", async () => {
|
|
250
|
+
const user = await createUser({ name: "Clear email", email: "clear@example.com" });
|
|
251
|
+
const response = await authedFetch(`/api/users/${user.id}`, {
|
|
252
|
+
method: "PATCH",
|
|
253
|
+
body: JSON.stringify({ email: null }),
|
|
254
|
+
});
|
|
255
|
+
expect(response.status).toBe(200);
|
|
256
|
+
expect((await response.json()).user.email ?? null).toBeNull();
|
|
257
|
+
const reloaded = await authedFetch(`/api/users/${user.id}`);
|
|
258
|
+
expect((await reloaded.json()).user.email ?? null).toBeNull();
|
|
259
|
+
});
|
|
260
|
+
|
|
200
261
|
test("dailyBudgetUsd mirrors into user-scoped budgets and null removes the mirror", async () => {
|
|
201
262
|
const create = await authedFetch("/api/users", {
|
|
202
263
|
method: "POST",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { afterAll, afterEach, beforeAll, describe, expect, test } from "bun:test";
|
|
2
2
|
import { unlink } from "node:fs/promises";
|
|
3
|
+
import { reasoningLevelsForModel } from "@desplega/model-catalog";
|
|
3
4
|
import { closeDb, getActivePricingRow, getDbClient, initDb } from "../be/db";
|
|
4
5
|
import {
|
|
5
6
|
listModelCatalogOverlay,
|
|
@@ -10,6 +11,7 @@ import {
|
|
|
10
11
|
import { getModelsCatalog, resetModelsCatalogForTests } from "../be/models-catalog";
|
|
11
12
|
import type { ModelsDevCache } from "../be/modelsdev-cache";
|
|
12
13
|
import { MODEL_CATALOG_FRESH_MS, refreshModelCatalog } from "../be/pricing-refresh";
|
|
14
|
+
import { getContextWindowSize } from "../utils/context-window";
|
|
13
15
|
|
|
14
16
|
const TEST_DB_PATH = "./test-model-catalog-table.sqlite";
|
|
15
17
|
|
|
@@ -212,6 +214,67 @@ describe("persistent model catalog", () => {
|
|
|
212
214
|
expect(model?.name).toBe("GPT Base");
|
|
213
215
|
});
|
|
214
216
|
|
|
217
|
+
test("opencode-go lands in the catalog, context windows and effort gating", async () => {
|
|
218
|
+
const cache: ModelsDevCache = {
|
|
219
|
+
"opencode-go": {
|
|
220
|
+
id: "opencode-go",
|
|
221
|
+
name: "OpenCode Go",
|
|
222
|
+
models: {
|
|
223
|
+
"deepseek-v4.1-flash": {
|
|
224
|
+
id: "deepseek-v4.1-flash",
|
|
225
|
+
name: "DeepSeek V4.1 Flash",
|
|
226
|
+
limit: { context: 1_000_000 },
|
|
227
|
+
reasoning: true,
|
|
228
|
+
reasoning_options: [{ type: "effort", values: ["low", "high", "max"] }],
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
await refreshModelCatalog({ force: true, now: 1_000, fetchImpl: ok(cache, '"og-v1"') });
|
|
234
|
+
|
|
235
|
+
const catalog = await loadModelsCatalog();
|
|
236
|
+
expect(catalog.providers["opencode-go"]?.models["deepseek-v4.1-flash"]?.limit?.context).toBe(
|
|
237
|
+
1_000_000,
|
|
238
|
+
);
|
|
239
|
+
// Before the projection covered opencode-go this fell back to 200k.
|
|
240
|
+
expect(getContextWindowSize("opencode-go/deepseek-v4.1-flash")).toBe(1_000_000);
|
|
241
|
+
// opencode keeps the model's own `max`; the catalog now carries the facts.
|
|
242
|
+
expect(
|
|
243
|
+
reasoningLevelsForModel("opencode", "opencode-go/deepseek-v4.1-flash", catalog.providers),
|
|
244
|
+
).toEqual(["low", "high", "max"]);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test("an opencode-go overlay expires once upstream carries the projected facts", async () => {
|
|
248
|
+
const cache: ModelsDevCache = {
|
|
249
|
+
"opencode-go": {
|
|
250
|
+
id: "opencode-go",
|
|
251
|
+
name: "OpenCode Go",
|
|
252
|
+
models: {
|
|
253
|
+
"deepseek-v4.1-flash": {
|
|
254
|
+
id: "deepseek-v4.1-flash",
|
|
255
|
+
limit: { context: 1_000_000 },
|
|
256
|
+
reasoning: true,
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
await refreshModelCatalog({ force: true, now: 1_000, fetchImpl: ok(cache, '"og-v1"') });
|
|
262
|
+
await upsertModelCatalogOverlay({
|
|
263
|
+
provider: "opencode-go",
|
|
264
|
+
modelId: "deepseek-v4.1-flash",
|
|
265
|
+
contextWindow: 1_000_000,
|
|
266
|
+
reason: "hand-verified until models.dev caught up",
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
await refreshModelCatalog({ force: true, now: 2_000, fetchImpl: ok(cache, '"og-v2"') });
|
|
270
|
+
|
|
271
|
+
expect((await listModelCatalogOverlay()).map((o) => `${o.provider}/${o.modelId}`)).toEqual([]);
|
|
272
|
+
const model = (await loadModelsCatalog()).providers["opencode-go"]?.models[
|
|
273
|
+
"deepseek-v4.1-flash"
|
|
274
|
+
];
|
|
275
|
+
expect(model?.limit?.context).toBe(1_000_000);
|
|
276
|
+
});
|
|
277
|
+
|
|
215
278
|
test("overlay auto-expires once upstream matches", async () => {
|
|
216
279
|
await refreshModelCatalog({ force: true, now: 1_000, fetchImpl: ok(payload(), '"v1"') });
|
|
217
280
|
await upsertModelCatalogOverlay({
|
|
@@ -228,14 +228,26 @@ describe("profile CRUD", () => {
|
|
|
228
228
|
(await call("PATCH", "/api/model-profiles/oc-off", { description: "kept" })).status,
|
|
229
229
|
).toBe(200);
|
|
230
230
|
|
|
231
|
+
// opencode passes the model's own declared efforts through, `max` included.
|
|
232
|
+
expect(
|
|
233
|
+
(await call("POST", "/api/model-profiles", { ...body, name: "oc-max", effort: "max" }))
|
|
234
|
+
.status,
|
|
235
|
+
).toBe(201);
|
|
236
|
+
|
|
231
237
|
// Control: a level this model does not list is still refused, on both verbs.
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
effort: "max"
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
expect(
|
|
238
|
+
// `openai/gpt-5.1-codex` declares low|medium|high, so it accepts no `max`.
|
|
239
|
+
const noMax = { harness: "opencode", model: "openai/gpt-5.1-codex" };
|
|
240
|
+
expect(
|
|
241
|
+
(await call("POST", "/api/model-profiles", { ...noMax, name: "oc-nomax", effort: "max" }))
|
|
242
|
+
.status,
|
|
243
|
+
).toBe(400);
|
|
244
|
+
expect(
|
|
245
|
+
(await call("POST", "/api/model-profiles", { ...noMax, name: "oc-capped", effort: "low" }))
|
|
246
|
+
.status,
|
|
247
|
+
).toBe(201);
|
|
248
|
+
expect((await call("PATCH", "/api/model-profiles/oc-capped", { effort: "max" })).status).toBe(
|
|
249
|
+
400,
|
|
250
|
+
);
|
|
239
251
|
});
|
|
240
252
|
|
|
241
253
|
test("a tier seed and a profile in use cannot be deleted", async () => {
|
|
@@ -74,6 +74,33 @@ describe("buildModelsCatalog", () => {
|
|
|
74
74
|
});
|
|
75
75
|
expect(catalog.openai?.models["gpt-keyed"]?.id).toBe("gpt-keyed");
|
|
76
76
|
});
|
|
77
|
+
|
|
78
|
+
test("projects opencode-go (OpenCode Go) like its sibling opencode", () => {
|
|
79
|
+
const catalog = buildModelsCatalog({
|
|
80
|
+
requesty: { models: { ignored: { id: "ignored" } } },
|
|
81
|
+
"opencode-go": {
|
|
82
|
+
id: "opencode-go",
|
|
83
|
+
name: "OpenCode Go",
|
|
84
|
+
models: {
|
|
85
|
+
"deepseek-v4.1-flash": {
|
|
86
|
+
id: "deepseek-v4.1-flash",
|
|
87
|
+
name: "DeepSeek V4.1 Flash",
|
|
88
|
+
limit: { context: 1_000_000 },
|
|
89
|
+
reasoning: true,
|
|
90
|
+
reasoning_options: [{ type: "effort", values: ["low", "high", "max"] }],
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
// A non-picker provider is dropped; opencode-go is kept.
|
|
96
|
+
expect(Object.keys(catalog)).toEqual(["opencode-go"]);
|
|
97
|
+
expect(catalog["opencode-go"]?.models["deepseek-v4.1-flash"]).toMatchObject({
|
|
98
|
+
id: "deepseek-v4.1-flash",
|
|
99
|
+
limit: { context: 1_000_000 },
|
|
100
|
+
reasoning: true,
|
|
101
|
+
reasoning_options: [{ type: "effort", values: ["low", "high", "max"] }],
|
|
102
|
+
});
|
|
103
|
+
});
|
|
77
104
|
});
|
|
78
105
|
|
|
79
106
|
describe("getModelsCatalog", () => {
|
|
@@ -85,6 +112,11 @@ describe("getModelsCatalog", () => {
|
|
|
85
112
|
expect(Object.keys(result.providers.openrouter?.models ?? {}).length).toBeGreaterThan(0);
|
|
86
113
|
expect(Object.keys(result.providers.anthropic?.models ?? {}).length).toBeGreaterThan(0);
|
|
87
114
|
expect(Object.keys(result.providers.opencode?.models ?? {}).length).toBeGreaterThan(0);
|
|
115
|
+
// OpenCode Go is a catalog provider too (its sibling `opencode` is Zen).
|
|
116
|
+
expect(result.providers["opencode-go"]?.name).toBe("OpenCode Go");
|
|
117
|
+
expect(result.providers["opencode-go"]?.models["deepseek-v4-flash"]?.limit?.context).toBe(
|
|
118
|
+
1_000_000,
|
|
119
|
+
);
|
|
88
120
|
expect(result.providers.nan?.models["glm5.3-flash"]).toBeDefined();
|
|
89
121
|
expect(Object.keys(result.providers.helmcode?.models ?? {}).sort()).toEqual([
|
|
90
122
|
"deepseek-v4-flash",
|
|
@@ -159,3 +191,21 @@ describe("nan and helmcode sections", () => {
|
|
|
159
191
|
expect(getContextWindowSize("nan/qwen3.6")).toBe(262_144);
|
|
160
192
|
});
|
|
161
193
|
});
|
|
194
|
+
|
|
195
|
+
describe("opencode-go section", () => {
|
|
196
|
+
test("the snapshot gives opencode-go ids their context window, not the 200k fallback", () => {
|
|
197
|
+
expect(getContextWindowSize("opencode-go/deepseek-v4-flash")).toBe(1_000_000);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test("opencode and pi keep the model's own max for an opencode-go id", () => {
|
|
201
|
+
const catalog = getModelsCatalog().providers;
|
|
202
|
+
// `deepseek-v4-flash` declares low|high|max and both harnesses pass it through.
|
|
203
|
+
for (const harness of ["opencode", "pi"] as const) {
|
|
204
|
+
expect(reasoningLevelsForModel(harness, "opencode-go/deepseek-v4-flash", catalog)).toEqual([
|
|
205
|
+
"low",
|
|
206
|
+
"high",
|
|
207
|
+
"max",
|
|
208
|
+
]);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
});
|
|
@@ -184,6 +184,27 @@ describe("onboarding state", () => {
|
|
|
184
184
|
expect(body.state.version).toBe(1);
|
|
185
185
|
});
|
|
186
186
|
|
|
187
|
+
test("a stored org name completes the name step, the old default does not", async () => {
|
|
188
|
+
const nameStep = async () =>
|
|
189
|
+
((await (await request("/api/onboarding")).json()) as { state: OnboardingState }).state.steps
|
|
190
|
+
.name;
|
|
191
|
+
const previous = process.env.SWARM_ORG_NAME;
|
|
192
|
+
try {
|
|
193
|
+
delete process.env.SWARM_ORG_NAME;
|
|
194
|
+
expect((await nameStep()).status).toBe("todo");
|
|
195
|
+
process.env.SWARM_ORG_NAME = " Your Swarm ";
|
|
196
|
+
expect((await nameStep()).status).toBe("todo");
|
|
197
|
+
process.env.SWARM_ORG_NAME = "Acme";
|
|
198
|
+
expect(await nameStep()).toMatchObject({ status: "done", method: "custom_name" });
|
|
199
|
+
// Clearing the name later never undoes a done step.
|
|
200
|
+
delete process.env.SWARM_ORG_NAME;
|
|
201
|
+
expect((await nameStep()).status).toBe("done");
|
|
202
|
+
} finally {
|
|
203
|
+
if (previous === undefined) delete process.env.SWARM_ORG_NAME;
|
|
204
|
+
else process.env.SWARM_ORG_NAME = previous;
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
|
|
187
208
|
test("concurrent first GETs create state and started telemetry once", async () => {
|
|
188
209
|
const responses = await Promise.all(
|
|
189
210
|
Array.from({ length: 8 }, () => request("/api/onboarding")),
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { orgIdentityStatus } from "../utils/org-identity";
|
|
3
|
+
|
|
4
|
+
describe("orgIdentityStatus", () => {
|
|
5
|
+
test.each([
|
|
6
|
+
["empty", {}, ["SWARM_ORG_NAME", "SWARM_ORG_WEBSITE"]],
|
|
7
|
+
[
|
|
8
|
+
"whitespace",
|
|
9
|
+
{ SWARM_ORG_NAME: " ", SWARM_ORG_WEBSITE: " " },
|
|
10
|
+
["SWARM_ORG_NAME", "SWARM_ORG_WEBSITE"],
|
|
11
|
+
],
|
|
12
|
+
[
|
|
13
|
+
"the catalog default",
|
|
14
|
+
{ SWARM_ORG_NAME: "Swarm", SWARM_ORG_WEBSITE: "acme.com" },
|
|
15
|
+
["SWARM_ORG_NAME"],
|
|
16
|
+
],
|
|
17
|
+
[
|
|
18
|
+
"the Slack fallback",
|
|
19
|
+
{ SWARM_ORG_NAME: " your swarm ", SWARM_ORG_WEBSITE: "acme.com" },
|
|
20
|
+
["SWARM_ORG_NAME"],
|
|
21
|
+
],
|
|
22
|
+
[
|
|
23
|
+
"an invalid website",
|
|
24
|
+
{ SWARM_ORG_NAME: "Acme", SWARM_ORG_WEBSITE: "localhost" },
|
|
25
|
+
["SWARM_ORG_WEBSITE"],
|
|
26
|
+
],
|
|
27
|
+
["both set", { SWARM_ORG_NAME: " Acme ", SWARM_ORG_WEBSITE: "https://www.Acme.com/" }, []],
|
|
28
|
+
] as const)("%s", (_label, src, missing) => {
|
|
29
|
+
const status = orgIdentityStatus(src);
|
|
30
|
+
expect(status.missing).toEqual([...missing]);
|
|
31
|
+
expect(status.ok).toBe(missing.length === 0);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("returns the trimmed name and the normalized website", () => {
|
|
35
|
+
expect(
|
|
36
|
+
orgIdentityStatus({ SWARM_ORG_NAME: " Acme ", SWARM_ORG_WEBSITE: "https://www.Acme.com/" }),
|
|
37
|
+
).toEqual({
|
|
38
|
+
name: "Acme",
|
|
39
|
+
website: "acme.com",
|
|
40
|
+
missing: [],
|
|
41
|
+
ok: true,
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
import { validateConfigValue } from "../be/swarm-config-guard";
|
|
3
3
|
import { buildContext } from "../telemetry-context";
|
|
4
|
+
import {
|
|
5
|
+
COMPANY_ACQUISITION_SOURCES,
|
|
6
|
+
COMPANY_TEAM_SIZES,
|
|
7
|
+
COMPANY_USE_CASES,
|
|
8
|
+
} from "../utils/company-profile";
|
|
4
9
|
import {
|
|
5
10
|
isFilledOrgLogoUrl,
|
|
6
11
|
normalizeOrgWebsite,
|
|
@@ -75,6 +80,48 @@ describe("websiteSuggestion", () => {
|
|
|
75
80
|
expect(websiteSuggestion("gmail.com")).toBeNull();
|
|
76
81
|
expect(websiteSuggestion(null)).toBeNull();
|
|
77
82
|
});
|
|
83
|
+
|
|
84
|
+
test("never suggests a regional or single-domain free-mail provider", () => {
|
|
85
|
+
for (const domain of [
|
|
86
|
+
"hotmail.es",
|
|
87
|
+
"yahoo.co.uk",
|
|
88
|
+
"outlook.fr",
|
|
89
|
+
"web.de",
|
|
90
|
+
"gmx.net",
|
|
91
|
+
"pm.me",
|
|
92
|
+
"mail.ru",
|
|
93
|
+
"live.co.uk",
|
|
94
|
+
"live.nl",
|
|
95
|
+
"yandex.com.tr",
|
|
96
|
+
"yandex.kz",
|
|
97
|
+
"icloud.com",
|
|
98
|
+
"me.com",
|
|
99
|
+
"proton.me",
|
|
100
|
+
"protonmail.ch",
|
|
101
|
+
"aol.de",
|
|
102
|
+
"qq.com",
|
|
103
|
+
"163.com",
|
|
104
|
+
"naver.com",
|
|
105
|
+
"Hotmail.CO.UK",
|
|
106
|
+
"mail.yahoo.com",
|
|
107
|
+
]) {
|
|
108
|
+
expect(websiteSuggestion(domain)).toBeNull();
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("keeps company domains that only look like a provider", () => {
|
|
113
|
+
for (const domain of [
|
|
114
|
+
"acme.co.uk",
|
|
115
|
+
"acme.com.br",
|
|
116
|
+
"mailchimp.com",
|
|
117
|
+
"live.acme.com",
|
|
118
|
+
"yahoo-labs.dev",
|
|
119
|
+
"proton.ai",
|
|
120
|
+
"web.dev",
|
|
121
|
+
]) {
|
|
122
|
+
expect(websiteSuggestion(domain)).toBe(domain);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
78
125
|
});
|
|
79
126
|
|
|
80
127
|
describe("SWARM_ORG_WEBSITE config guard", () => {
|
|
@@ -95,6 +142,21 @@ describe("SWARM_ORG_WEBSITE config guard", () => {
|
|
|
95
142
|
});
|
|
96
143
|
});
|
|
97
144
|
|
|
145
|
+
describe("company profile config guards", () => {
|
|
146
|
+
for (const [key, values] of [
|
|
147
|
+
["SWARM_ORG_TEAM_SIZE", COMPANY_TEAM_SIZES],
|
|
148
|
+
["SWARM_ORG_USE_CASE", COMPANY_USE_CASES],
|
|
149
|
+
["SWARM_ORG_ACQUISITION_SOURCE", COMPANY_ACQUISITION_SOURCES],
|
|
150
|
+
] as const) {
|
|
151
|
+
test(`${key} accepts its enum or empty, and rejects other values`, () => {
|
|
152
|
+
for (const value of ["", ...values]) expect(validateConfigValue(key, value)).toBeNull();
|
|
153
|
+
for (const value of ["unknown", "OTHER", "free text", null, 1, true]) {
|
|
154
|
+
expect(validateConfigValue(key, value)).toContain(key);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
98
160
|
describe("org_website in the telemetry context", () => {
|
|
99
161
|
const input = {
|
|
100
162
|
orgId: "org_0123456789abcdef",
|