@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
|
@@ -128,6 +128,61 @@ describe("slack-update", () => {
|
|
|
128
128
|
});
|
|
129
129
|
});
|
|
130
130
|
|
|
131
|
+
test("splits a long message into sections under Slack's cap", async () => {
|
|
132
|
+
const message = Array.from(
|
|
133
|
+
{ length: 120 },
|
|
134
|
+
(_, index) => `line ${String(index).padStart(3, "0")} radar item with some words`,
|
|
135
|
+
)
|
|
136
|
+
.join("\n")
|
|
137
|
+
.slice(0, 3946);
|
|
138
|
+
const tool = buildTool();
|
|
139
|
+
const result = await tool.handler(
|
|
140
|
+
{ channelId: "C1", messageTs: "1783411554.596189", message },
|
|
141
|
+
meta(leadAgentId),
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
expect(result.structuredContent.success).toBe(true);
|
|
145
|
+
const payload = mockChatUpdate.mock.calls[0][0] as {
|
|
146
|
+
text: string;
|
|
147
|
+
blocks: { type: string; text: { type: string; text: string } }[];
|
|
148
|
+
};
|
|
149
|
+
expect(payload.text).toBe(message);
|
|
150
|
+
expect(payload.blocks.length).toBeGreaterThanOrEqual(2);
|
|
151
|
+
for (const block of payload.blocks) {
|
|
152
|
+
expect(block.type).toBe("section");
|
|
153
|
+
expect(block.text.text.length).toBeLessThanOrEqual(2900);
|
|
154
|
+
}
|
|
155
|
+
const joined = payload.blocks.map((block) => block.text.text).join("");
|
|
156
|
+
expect(joined.replace(/\s/g, "")).toBe(message.replace(/\s/g, ""));
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("keeps a 2,931-char message with an emoji at 2,900 in one intact section", async () => {
|
|
160
|
+
const message = `${"x".repeat(2899)}😀${"y".repeat(30)}`;
|
|
161
|
+
expect(message.length).toBe(2931);
|
|
162
|
+
const tool = buildTool();
|
|
163
|
+
const result = await tool.handler(
|
|
164
|
+
{ channelId: "C1", messageTs: "1783411554.596189", message },
|
|
165
|
+
meta(leadAgentId),
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
expect(result.structuredContent.success).toBe(true);
|
|
169
|
+
expect(mockChatUpdate.mock.calls[0][0].blocks).toEqual([
|
|
170
|
+
{ type: "section", text: { type: "mrkdwn", text: message } },
|
|
171
|
+
]);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("keeps a short message in exactly one section", async () => {
|
|
175
|
+
const tool = buildTool();
|
|
176
|
+
await tool.handler(
|
|
177
|
+
{ channelId: "C1", messageTs: "1783411554.596189", message: "short" },
|
|
178
|
+
meta(leadAgentId),
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
expect(mockChatUpdate.mock.calls[0][0]).toMatchObject({
|
|
182
|
+
blocks: [{ type: "section", text: { type: "mrkdwn", text: "short" } }],
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
131
186
|
test.each([
|
|
132
187
|
["message_not_found", "No message found at that timestamp in this channel."],
|
|
133
188
|
[
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
import { unlink } from "node:fs/promises";
|
|
3
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
import { closeDb, createAgent, getSlackMessageByChannelTs, initDb } from "../be/db";
|
|
5
|
+
import { isSlackUserId } from "../slack/dm";
|
|
6
|
+
import { registerSlackPostTool } from "../tools/slack-post";
|
|
7
|
+
import { registerSlackReadTool } from "../tools/slack-read";
|
|
8
|
+
import { registerSlackStartThreadTool } from "../tools/slack-start-thread";
|
|
9
|
+
|
|
10
|
+
const TEST_DB_PATH = "./test-slack-user-dm.sqlite";
|
|
11
|
+
const USER_ID = "U08USERDM";
|
|
12
|
+
// The bot's own IM with USER_ID: readable, writable, delivers message.im events.
|
|
13
|
+
const BOT_DM = "D0BOTDM01";
|
|
14
|
+
// Where Slack puts a bot post addressed to the bare user id: the bot is not a member.
|
|
15
|
+
const FOREIGN_DM = "D0FOREIGN";
|
|
16
|
+
|
|
17
|
+
function slackError(code: string): Error {
|
|
18
|
+
return Object.assign(new Error(`An API error occurred: ${code}`), {
|
|
19
|
+
data: { ok: false, error: code },
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let tsCounter = 0;
|
|
24
|
+
const postMessage = mock(async (args: { channel: string; thread_ts?: string }) => {
|
|
25
|
+
if (args.channel === FOREIGN_DM) throw slackError("channel_not_found");
|
|
26
|
+
const ts = `500.${++tsCounter}`;
|
|
27
|
+
return { ok: true, ts, channel: args.channel === USER_ID ? FOREIGN_DM : args.channel };
|
|
28
|
+
});
|
|
29
|
+
const conversationsOpen = mock(async (args: { users: string }) => {
|
|
30
|
+
if (args.users !== USER_ID) throw slackError("user_not_found");
|
|
31
|
+
return { ok: true, channel: { id: BOT_DM } };
|
|
32
|
+
});
|
|
33
|
+
const conversationsReplies = mock(async (args: { channel: string; ts: string }) => {
|
|
34
|
+
if (args.channel !== BOT_DM) throw slackError("channel_not_found");
|
|
35
|
+
return { ok: true, messages: [{ user: USER_ID, text: "reply from human", ts: args.ts }] };
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
mock.module("../slack/app", () => ({
|
|
39
|
+
getSlackApp: () => ({
|
|
40
|
+
client: {
|
|
41
|
+
chat: { postMessage },
|
|
42
|
+
conversations: { open: conversationsOpen, replies: conversationsReplies },
|
|
43
|
+
auth: { test: async () => ({ ok: true, user_id: "UBOT" }) },
|
|
44
|
+
users: { info: async () => ({ ok: true, user: { real_name: "Human" } }) },
|
|
45
|
+
},
|
|
46
|
+
}),
|
|
47
|
+
}));
|
|
48
|
+
|
|
49
|
+
type ToolResult = {
|
|
50
|
+
structuredContent: {
|
|
51
|
+
success: boolean;
|
|
52
|
+
message: string;
|
|
53
|
+
channelId?: string;
|
|
54
|
+
ts?: string;
|
|
55
|
+
messages?: Array<{ text: string }>;
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
type RegisteredTool = { handler: (args: unknown, extra: unknown) => Promise<ToolResult> };
|
|
59
|
+
|
|
60
|
+
function buildTools() {
|
|
61
|
+
const server = new McpServer({ name: "slack-user-dm-test", version: "1.0.0" });
|
|
62
|
+
registerSlackPostTool(server);
|
|
63
|
+
registerSlackReadTool(server);
|
|
64
|
+
registerSlackStartThreadTool(server);
|
|
65
|
+
return (server as unknown as { _registeredTools: Record<string, RegisteredTool> })
|
|
66
|
+
._registeredTools;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function meta(agentId: string) {
|
|
70
|
+
return { sessionId: "s1", requestInfo: { headers: { "x-agent-id": agentId } } };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function removeDbFiles() {
|
|
74
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
75
|
+
try {
|
|
76
|
+
await unlink(`${TEST_DB_PATH}${suffix}`);
|
|
77
|
+
} catch {}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let leadId: string;
|
|
82
|
+
|
|
83
|
+
beforeAll(async () => {
|
|
84
|
+
await removeDbFiles();
|
|
85
|
+
initDb(TEST_DB_PATH);
|
|
86
|
+
leadId = (await createAgent({ name: "Lead DM", isLead: true, status: "idle" })).id;
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
beforeEach(() => {
|
|
90
|
+
postMessage.mockClear();
|
|
91
|
+
conversationsOpen.mockClear();
|
|
92
|
+
conversationsReplies.mockClear();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
afterAll(async () => {
|
|
96
|
+
closeDb();
|
|
97
|
+
await removeDbFiles();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe("Slack tools addressed with a user id", () => {
|
|
101
|
+
test("isSlackUserId matches user ids only", () => {
|
|
102
|
+
expect(isSlackUserId("U08NR6QD6CS")).toBe(true);
|
|
103
|
+
expect(isSlackUserId("W012ABCDEF")).toBe(true);
|
|
104
|
+
expect(isSlackUserId("D0A4T383FMF")).toBe(false);
|
|
105
|
+
expect(isSlackUserId("C0AR967K0KZ")).toBe(false);
|
|
106
|
+
expect(isSlackUserId("G01PRIVATE")).toBe(false);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("slack-start-thread opens the bot DM and returns an id that reads and writes work on", async () => {
|
|
110
|
+
const tools = buildTools();
|
|
111
|
+
|
|
112
|
+
const started = await tools["slack-start-thread"]!.handler(
|
|
113
|
+
{ channelId: USER_ID, message: "hello" },
|
|
114
|
+
meta(leadId),
|
|
115
|
+
);
|
|
116
|
+
expect(started.structuredContent.success).toBe(true);
|
|
117
|
+
expect(conversationsOpen).toHaveBeenCalledWith({ users: USER_ID });
|
|
118
|
+
expect(postMessage.mock.calls[0]![0].channel).toBe(BOT_DM);
|
|
119
|
+
expect(started.structuredContent.channelId).toBe(BOT_DM);
|
|
120
|
+
const threadTs = started.structuredContent.ts!;
|
|
121
|
+
// The recorded message points at the bot DM, so inbound replies match it.
|
|
122
|
+
expect((await getSlackMessageByChannelTs(BOT_DM, threadTs))?.threadTs).toBe(threadTs);
|
|
123
|
+
|
|
124
|
+
const posted = await tools["slack-post"]!.handler(
|
|
125
|
+
{ channelId: BOT_DM, threadTs, message: "follow-up" },
|
|
126
|
+
meta(leadId),
|
|
127
|
+
);
|
|
128
|
+
expect(posted.structuredContent.success).toBe(true);
|
|
129
|
+
expect(postMessage.mock.calls[1]![0]).toMatchObject({ channel: BOT_DM, thread_ts: threadTs });
|
|
130
|
+
|
|
131
|
+
const read = await tools["slack-read"]!.handler({ channelId: BOT_DM, threadTs }, meta(leadId));
|
|
132
|
+
expect(read.structuredContent.success).toBe(true);
|
|
133
|
+
expect(read.structuredContent.messages?.[0]?.text).toContain("reply from human");
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("slack-post and slack-read with the user id resolve to the same bot DM", async () => {
|
|
137
|
+
const tools = buildTools();
|
|
138
|
+
|
|
139
|
+
const posted = await tools["slack-post"]!.handler(
|
|
140
|
+
{ channelId: USER_ID, threadTs: "500.1", message: "by user id" },
|
|
141
|
+
meta(leadId),
|
|
142
|
+
);
|
|
143
|
+
expect(posted.structuredContent.success).toBe(true);
|
|
144
|
+
expect(postMessage.mock.calls[0]![0]).toMatchObject({ channel: BOT_DM, thread_ts: "500.1" });
|
|
145
|
+
|
|
146
|
+
const read = await tools["slack-read"]!.handler(
|
|
147
|
+
{ channelId: USER_ID, threadTs: "500.1" },
|
|
148
|
+
meta(leadId),
|
|
149
|
+
);
|
|
150
|
+
expect(read.structuredContent.success).toBe(true);
|
|
151
|
+
expect(conversationsReplies.mock.calls[0]![0].channel).toBe(BOT_DM);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("conversation ids pass through without opening a DM", async () => {
|
|
155
|
+
const tools = buildTools();
|
|
156
|
+
|
|
157
|
+
const started = await tools["slack-start-thread"]!.handler(
|
|
158
|
+
{ channelId: "C0CHANNEL1", message: "channel post" },
|
|
159
|
+
meta(leadId),
|
|
160
|
+
);
|
|
161
|
+
expect(started.structuredContent.channelId).toBe("C0CHANNEL1");
|
|
162
|
+
expect(postMessage.mock.calls[0]![0].channel).toBe("C0CHANNEL1");
|
|
163
|
+
expect(conversationsOpen).not.toHaveBeenCalled();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("a user id Slack cannot open a DM with fails without posting", async () => {
|
|
167
|
+
const tools = buildTools();
|
|
168
|
+
|
|
169
|
+
const started = await tools["slack-start-thread"]!.handler(
|
|
170
|
+
{ channelId: "U0NOBODY1", message: "hello" },
|
|
171
|
+
meta(leadId),
|
|
172
|
+
);
|
|
173
|
+
expect(started.structuredContent.success).toBe(false);
|
|
174
|
+
expect(started.structuredContent.message).toContain("user_not_found");
|
|
175
|
+
expect(postMessage).not.toHaveBeenCalled();
|
|
176
|
+
});
|
|
177
|
+
});
|
|
@@ -288,7 +288,7 @@ describe("finalizeSwarmToolResult", () => {
|
|
|
288
288
|
expect((nonEmpty.structuredContent as { nudge?: string }).nudge).toBeUndefined();
|
|
289
289
|
});
|
|
290
290
|
|
|
291
|
-
test("NUDGES map: app-get steers to
|
|
291
|
+
test("NUDGES map: app-get steers to queries and actions only when the app has them", async () => {
|
|
292
292
|
const withSurface = await finalizeSwarmToolResult("app-get", {
|
|
293
293
|
ok: true,
|
|
294
294
|
message: 'App "PM Inbox" (a1).',
|
|
@@ -302,9 +302,7 @@ describe("finalizeSwarmToolResult", () => {
|
|
|
302
302
|
},
|
|
303
303
|
},
|
|
304
304
|
});
|
|
305
|
-
expect((withSurface.structuredContent as { nudge?: string }).nudge).toContain(
|
|
306
|
-
"callable surface",
|
|
307
|
-
);
|
|
305
|
+
expect((withSurface.structuredContent as { nudge?: string }).nudge).toContain("app-action-run");
|
|
308
306
|
|
|
309
307
|
const bare = await finalizeSwarmToolResult("app-get", {
|
|
310
308
|
ok: true,
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
2
|
import { unlink } from "node:fs/promises";
|
|
3
|
-
import { closeDb, createUser, initDb } from "../be/db";
|
|
3
|
+
import { closeDb, createUser, getDbClient, initDb, updateUser, upsertSwarmConfig } from "../be/db";
|
|
4
4
|
import { attachRole, detachRole } from "../be/rbac-roles";
|
|
5
|
+
import { loadGlobalConfigsIntoEnv } from "../http/core";
|
|
5
6
|
import {
|
|
6
7
|
_getOrgIdForTests,
|
|
7
8
|
_resetTelemetryStateForTests,
|
|
8
9
|
initTelemetry,
|
|
10
|
+
setTelemetryFunctionResolver,
|
|
9
11
|
setTelemetryOrgDomain,
|
|
10
12
|
track,
|
|
11
13
|
} from "../telemetry";
|
|
@@ -19,12 +21,22 @@ import {
|
|
|
19
21
|
validOrgDomain,
|
|
20
22
|
validOrgId,
|
|
21
23
|
} from "../telemetry-context";
|
|
22
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
_resetTelemetryIdentityForTests,
|
|
26
|
+
cachedUserFunction,
|
|
27
|
+
computeOrgDomain,
|
|
28
|
+
primeUserRole,
|
|
29
|
+
} from "../telemetry-identity";
|
|
23
30
|
|
|
24
31
|
// initTelemetry no-ops when ANONYMIZED_TELEMETRY=false, and the CI env may set it.
|
|
25
32
|
process.env.ANONYMIZED_TELEMETRY = "true";
|
|
26
33
|
|
|
27
34
|
const TEST_DB_PATH = "./test-telemetry-context.sqlite";
|
|
35
|
+
const PROFILE_KEYS = [
|
|
36
|
+
"SWARM_ORG_TEAM_SIZE",
|
|
37
|
+
"SWARM_ORG_USE_CASE",
|
|
38
|
+
"SWARM_ORG_ACQUISITION_SOURCE",
|
|
39
|
+
] as const;
|
|
28
40
|
|
|
29
41
|
async function removeTestDb(): Promise<void> {
|
|
30
42
|
for (const suffix of ["", "-wal", "-shm"]) {
|
|
@@ -116,6 +128,30 @@ describe("telemetry context helpers", () => {
|
|
|
116
128
|
expect(mapTriggerSurface(null)).toBe("other");
|
|
117
129
|
});
|
|
118
130
|
|
|
131
|
+
test("buildContext omits invalid profile values and a function without an actor", () => {
|
|
132
|
+
const base = {
|
|
133
|
+
orgId: "org_0123456789abcdef",
|
|
134
|
+
installationId: "install_test",
|
|
135
|
+
isCloud: false,
|
|
136
|
+
isE2b: false,
|
|
137
|
+
swarmVersion: "1.2.3",
|
|
138
|
+
};
|
|
139
|
+
const context = buildContext({
|
|
140
|
+
...base,
|
|
141
|
+
teamSize: "invalid",
|
|
142
|
+
useCase: "invalid",
|
|
143
|
+
acquisitionSource: "invalid",
|
|
144
|
+
userFunction: "founder",
|
|
145
|
+
});
|
|
146
|
+
expect(context).not.toHaveProperty("team_size");
|
|
147
|
+
expect(context).not.toHaveProperty("use_case");
|
|
148
|
+
expect(context).not.toHaveProperty("user_function");
|
|
149
|
+
expect(context.acquisition_source).toBeNull();
|
|
150
|
+
expect(
|
|
151
|
+
buildContext({ ...base, actor: { userId: "u-1" }, userFunction: "administrator" }),
|
|
152
|
+
).not.toHaveProperty("user_function");
|
|
153
|
+
});
|
|
154
|
+
|
|
119
155
|
test("normalizeProperties keeps an event alive when one optional value is unsendable", () => {
|
|
120
156
|
const out = normalizeProperties("task.completed", {
|
|
121
157
|
taskId: "t-1",
|
|
@@ -248,6 +284,19 @@ describe("telemetry envelope", () => {
|
|
|
248
284
|
}
|
|
249
285
|
});
|
|
250
286
|
|
|
287
|
+
test("track resolves the actor's job function without changing the RBAC role", async () => {
|
|
288
|
+
const config = memoryConfig({ telemetry_installation_id: "install_function" });
|
|
289
|
+
await initTelemetry("api-server", config.get, config.set, { generateIfMissing: true });
|
|
290
|
+
setTelemetryFunctionResolver(() => "founder");
|
|
291
|
+
track({
|
|
292
|
+
event: "workflow.deleted",
|
|
293
|
+
properties: { workflowId: "wf-1" },
|
|
294
|
+
actor: { userId: "u-1", role: "member" },
|
|
295
|
+
});
|
|
296
|
+
await tick();
|
|
297
|
+
expect(sent[0]?.context).toMatchObject({ user_function: "founder", user_role: "member" });
|
|
298
|
+
});
|
|
299
|
+
|
|
251
300
|
test("an org with no ID yet sends nothing instead of a half-identified event", async () => {
|
|
252
301
|
// A worker on an old server: installation ID known, no org ID stored.
|
|
253
302
|
const config = memoryConfig({ telemetry_installation_id: "install_noorg" });
|
|
@@ -270,18 +319,95 @@ describe("telemetry envelope", () => {
|
|
|
270
319
|
});
|
|
271
320
|
|
|
272
321
|
describe("org email domain", () => {
|
|
322
|
+
const savedProfile = Object.fromEntries(PROFILE_KEYS.map((key) => [key, process.env[key]]));
|
|
323
|
+
|
|
273
324
|
beforeEach(async () => {
|
|
274
325
|
closeDb();
|
|
275
326
|
await removeTestDb();
|
|
276
327
|
initDb(TEST_DB_PATH);
|
|
277
328
|
_resetTelemetryIdentityForTests();
|
|
329
|
+
for (const key of PROFILE_KEYS) delete process.env[key];
|
|
278
330
|
});
|
|
279
331
|
|
|
280
332
|
afterEach(async () => {
|
|
333
|
+
for (const key of PROFILE_KEYS) {
|
|
334
|
+
if (savedProfile[key] === undefined) delete process.env[key];
|
|
335
|
+
else process.env[key] = savedProfile[key];
|
|
336
|
+
}
|
|
281
337
|
closeDb();
|
|
282
338
|
await removeTestDb();
|
|
283
339
|
});
|
|
284
340
|
|
|
341
|
+
test("stored company profile reaches context after reload and clears without restart", async () => {
|
|
342
|
+
const values = ["11-50", "features", "hacker_news"];
|
|
343
|
+
for (const [index, key] of PROFILE_KEYS.entries()) {
|
|
344
|
+
await upsertSwarmConfig({ scope: "global", key, value: values[index] as string });
|
|
345
|
+
}
|
|
346
|
+
await loadGlobalConfigsIntoEnv(true);
|
|
347
|
+
const originalFetch = globalThis.fetch;
|
|
348
|
+
const sent: Array<{ context: Record<string, unknown> }> = [];
|
|
349
|
+
globalThis.fetch = (async (_url: string, init?: { body?: string }) => {
|
|
350
|
+
if (init?.body) sent.push(JSON.parse(init.body));
|
|
351
|
+
return new Response(null, { status: 204 });
|
|
352
|
+
}) as typeof fetch;
|
|
353
|
+
_resetTelemetryStateForTests();
|
|
354
|
+
try {
|
|
355
|
+
const config = memoryConfig({
|
|
356
|
+
telemetry_installation_id: "install_profile",
|
|
357
|
+
telemetry_org_id: "org_0123456789abcdef",
|
|
358
|
+
});
|
|
359
|
+
await initTelemetry("api-server", config.get, config.set);
|
|
360
|
+
track({ event: "server.started", properties: { port: 3013 } });
|
|
361
|
+
expect(sent).toHaveLength(1);
|
|
362
|
+
expect(sent[0]?.context).toMatchObject({
|
|
363
|
+
team_size: "11-50",
|
|
364
|
+
use_case: "features",
|
|
365
|
+
acquisition_source: "hacker_news",
|
|
366
|
+
});
|
|
367
|
+
for (const key of PROFILE_KEYS) {
|
|
368
|
+
await upsertSwarmConfig({ scope: "global", key, value: "" });
|
|
369
|
+
}
|
|
370
|
+
await loadGlobalConfigsIntoEnv(true);
|
|
371
|
+
track({ event: "server.started", properties: { port: 3013 } });
|
|
372
|
+
expect(sent).toHaveLength(2);
|
|
373
|
+
expect(sent[1]?.context).not.toHaveProperty("team_size");
|
|
374
|
+
expect(sent[1]?.context).not.toHaveProperty("use_case");
|
|
375
|
+
expect(sent[1]?.context.acquisition_source).toBeNull();
|
|
376
|
+
} finally {
|
|
377
|
+
globalThis.fetch = originalFetch;
|
|
378
|
+
_resetTelemetryStateForTests();
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
test("job function uses the role cache and invalidates after a committed user update", async () => {
|
|
383
|
+
const user = await createUser({
|
|
384
|
+
name: "Founder",
|
|
385
|
+
role: "admin",
|
|
386
|
+
metadata: { jobFunction: "founder" },
|
|
387
|
+
});
|
|
388
|
+
await primeUserRole(user.id);
|
|
389
|
+
expect(cachedUserFunction(user.id)).toBe("founder");
|
|
390
|
+
await getDbClient().transaction(async () => {
|
|
391
|
+
await updateUser(user.id, { metadata: { jobFunction: "engineer" } });
|
|
392
|
+
expect(cachedUserFunction(user.id)).toBe("founder");
|
|
393
|
+
});
|
|
394
|
+
await primeUserRole(user.id);
|
|
395
|
+
expect(cachedUserFunction(user.id)).toBe("engineer");
|
|
396
|
+
await updateUser(user.id, { metadata: { jobFunction: "a free-text legacy role" } });
|
|
397
|
+
await primeUserRole(user.id);
|
|
398
|
+
expect(cachedUserFunction(user.id)).toBeUndefined();
|
|
399
|
+
const context = buildContext({
|
|
400
|
+
orgId: "org_0123456789abcdef",
|
|
401
|
+
installationId: "install_profile",
|
|
402
|
+
isCloud: false,
|
|
403
|
+
isE2b: false,
|
|
404
|
+
swarmVersion: "1.2.3",
|
|
405
|
+
actor: { userId: user.id },
|
|
406
|
+
userFunction: cachedUserFunction(user.id),
|
|
407
|
+
});
|
|
408
|
+
expect(context).not.toHaveProperty("user_function");
|
|
409
|
+
});
|
|
410
|
+
|
|
285
411
|
test("is undefined with no users", async () => {
|
|
286
412
|
expect(await computeOrgDomain()).toBeUndefined();
|
|
287
413
|
});
|
|
@@ -5,6 +5,7 @@ import { isPublicRoute } from "../http/route-def";
|
|
|
5
5
|
import { handleTelemetryUi } from "../http/telemetry-ui";
|
|
6
6
|
import { getPathSegments, parseQueryParams } from "../http/utils";
|
|
7
7
|
import { _resetTelemetryStateForTests, initTelemetry } from "../telemetry";
|
|
8
|
+
import catalog from "../telemetry-contract/catalog.json";
|
|
8
9
|
import { UI_EVENT_NAMES, validateUiEvent } from "../telemetry-ui-events";
|
|
9
10
|
import type { HttpRequestAuth } from "../utils/request-auth-context";
|
|
10
11
|
import { setRequestAuth } from "../utils/request-auth-context";
|
|
@@ -118,6 +119,128 @@ describe("POST /api/telemetry/ui-event", () => {
|
|
|
118
119
|
expect(sent).toHaveLength(0);
|
|
119
120
|
});
|
|
120
121
|
|
|
122
|
+
test.each([
|
|
123
|
+
{ event: "identity.lookup", properties: { matched: true } },
|
|
124
|
+
...["identity_modal", "setup"].map((surface) => ({
|
|
125
|
+
event: "identity.user_created",
|
|
126
|
+
properties: { surface, email_kind: "work", has_function: true, has_company: true },
|
|
127
|
+
})),
|
|
128
|
+
{
|
|
129
|
+
event: "fleet_benchmark.unlocked",
|
|
130
|
+
properties: { fields: ["website", "team_size", "use_case"] },
|
|
131
|
+
},
|
|
132
|
+
...["compose", "docker", "kubernetes"].map((target) => ({
|
|
133
|
+
event: "setup.worker_command_copied",
|
|
134
|
+
properties: { target, harness: "codex" },
|
|
135
|
+
})),
|
|
136
|
+
...[
|
|
137
|
+
"prompt",
|
|
138
|
+
"setup",
|
|
139
|
+
"settings",
|
|
140
|
+
"org_name_popup",
|
|
141
|
+
"feedback",
|
|
142
|
+
"fleet_benchmark",
|
|
143
|
+
"telemetry_banner",
|
|
144
|
+
"identity_modal",
|
|
145
|
+
].map((surface) => ({
|
|
146
|
+
event: "org.profile_updated",
|
|
147
|
+
properties: {
|
|
148
|
+
fields: ["name", "website", "team_size", "use_case", "acquisition_source"],
|
|
149
|
+
surface,
|
|
150
|
+
},
|
|
151
|
+
})),
|
|
152
|
+
{
|
|
153
|
+
event: "popup.submitted",
|
|
154
|
+
properties: {
|
|
155
|
+
popup: "feedback",
|
|
156
|
+
has_message: false,
|
|
157
|
+
has_contact: false,
|
|
158
|
+
answered: 3,
|
|
159
|
+
perks: ["used_by", "slack_connect"],
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
...["notification.viewed", "notification.dismissed", "notification.submitted"].flatMap(
|
|
163
|
+
(event) =>
|
|
164
|
+
["task_failed", "first_workflow", "default"].map((moment) => ({
|
|
165
|
+
event,
|
|
166
|
+
properties: {
|
|
167
|
+
key: "slack_connect_invite",
|
|
168
|
+
moment,
|
|
169
|
+
...(event === "notification.submitted" ? { user_function: "engineer" } : {}),
|
|
170
|
+
},
|
|
171
|
+
})),
|
|
172
|
+
),
|
|
173
|
+
])("accepts the company-profile catalog event $event with $properties", async ({
|
|
174
|
+
event,
|
|
175
|
+
properties,
|
|
176
|
+
}) => {
|
|
177
|
+
const result = await post({ event, properties });
|
|
178
|
+
await flush();
|
|
179
|
+
expect(result.statusCode).toBe(204);
|
|
180
|
+
expect(sent).toHaveLength(1);
|
|
181
|
+
expect(sent[0]).toMatchObject({ event, source: "ui", properties });
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test.each([
|
|
185
|
+
{
|
|
186
|
+
event: "identity.user_created",
|
|
187
|
+
properties: {
|
|
188
|
+
surface: "invalid",
|
|
189
|
+
email_kind: "work",
|
|
190
|
+
has_function: true,
|
|
191
|
+
has_company: false,
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
{ event: "setup.worker_command_copied", properties: { target: "invalid", harness: "codex" } },
|
|
195
|
+
{ event: "org.profile_updated", properties: { fields: ["team_size"], surface: "invalid" } },
|
|
196
|
+
{ event: "org.profile_updated", properties: { fields: ["invalid"], surface: "settings" } },
|
|
197
|
+
{ event: "fleet_benchmark.unlocked", properties: { fields: ["invalid"] } },
|
|
198
|
+
{
|
|
199
|
+
event: "popup.submitted",
|
|
200
|
+
properties: { popup: "feedback", has_message: false, has_contact: false, perks: ["invalid"] },
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
event: "notification.submitted",
|
|
204
|
+
properties: { key: "slack_connect_invite", moment: "invalid" },
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
event: "notification.submitted",
|
|
208
|
+
properties: { key: "slack_connect_invite", user_function: "invalid" },
|
|
209
|
+
},
|
|
210
|
+
])("rejects a bad enum in $event with $properties", async ({ event, properties }) => {
|
|
211
|
+
const result = await post({ event, properties });
|
|
212
|
+
expect(result.statusCode).toBe(400);
|
|
213
|
+
expect(sent).toHaveLength(0);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test("array item references enforce the referenced enum", async () => {
|
|
217
|
+
const entry = catalog.events.find((entry) => entry.event === "fleet_benchmark.unlocked")!;
|
|
218
|
+
const fields = entry.properties.fields!;
|
|
219
|
+
const original = fields.items;
|
|
220
|
+
// Exercise a referenced item definition without changing the vendored catalog.
|
|
221
|
+
Object.assign(fields, { items: { ref: "use_case" } });
|
|
222
|
+
try {
|
|
223
|
+
expect(
|
|
224
|
+
(
|
|
225
|
+
await post({
|
|
226
|
+
event: "fleet_benchmark.unlocked",
|
|
227
|
+
properties: { fields: ["features"] },
|
|
228
|
+
})
|
|
229
|
+
).statusCode,
|
|
230
|
+
).toBe(204);
|
|
231
|
+
sent = [];
|
|
232
|
+
const result = await post({
|
|
233
|
+
event: "fleet_benchmark.unlocked",
|
|
234
|
+
properties: { fields: ["invalid"] },
|
|
235
|
+
});
|
|
236
|
+
expect(result.statusCode).toBe(400);
|
|
237
|
+
expect(result.body).toContain("items must be one of");
|
|
238
|
+
expect(sent).toHaveLength(0);
|
|
239
|
+
} finally {
|
|
240
|
+
fields.items = original;
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
|
|
121
244
|
test("an unknown event name gets 400 and sends nothing", async () => {
|
|
122
245
|
const result = await post({ event: "task.created", properties: {} });
|
|
123
246
|
await flush();
|
|
@@ -376,9 +376,10 @@ describe("Tool Annotations & Classification", () => {
|
|
|
376
376
|
// (this suite forces ALL_CAPABILITIES, including default-disabled ones).
|
|
377
377
|
// Includes 11 skill tools, 7 MCP server tools, reusable script tools, and the
|
|
378
378
|
// native Kapso/WhatsApp tools (register/unregister number + send/reply message),
|
|
379
|
-
//
|
|
379
|
+
// the six Chat SDK tools (chat-* + register/unregister-chat-channel),
|
|
380
|
+
// and the seven app row tools plus app-action-run.
|
|
380
381
|
expect(count).toBeGreaterThanOrEqual(45);
|
|
381
|
-
expect(count).toBeLessThanOrEqual(
|
|
382
|
+
expect(count).toBeLessThanOrEqual(169);
|
|
382
383
|
});
|
|
383
384
|
|
|
384
385
|
test("core tools are fewer than deferred tools", () => {
|