@desplega.ai/agent-swarm 2.4.0 → 2.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{acp-adapter-kedstxx6.js → acp-adapter-dv7hmx4r.js} +9 -9
- package/dist/{actions-vget6r1k.js → actions-51nc1gny.js} +21 -21
- package/dist/{amp-adapter-xeb5mktv.js → amp-adapter-abp5tdx8.js} +4 -4
- package/dist/{app-mqap8t8m.js → app-9h14qvr6.js} +3 -3
- package/dist/{assistant-h0gv97mv.js → assistant-wv1gz3mt.js} +22 -22
- package/dist/{boot-reembed-qr8a894z.js → boot-reembed-6yn53ms9.js} +1 -1
- package/dist/{boot-reembed-rj85jjhw.js → boot-reembed-7fcka4z5.js} +1 -1
- package/dist/{boot-scrub-sweep-eyfvn6v9.js → boot-scrub-sweep-pm1580ax.js} +4 -4
- package/dist/{boot-seeds-xs9a8dx1.js → boot-seeds-g5hfc1x8.js} +4 -4
- package/dist/{claude-adapter-bm23cye7.js → claude-adapter-gf3kxhf0.js} +9 -9
- package/dist/{claude-managed-adapter-1mwrsm9d.js → claude-managed-adapter-h2y889ge.js} +2 -2
- package/dist/{claude-managed-setup-gkfgks4j.js → claude-managed-setup-h00hsgtz.js} +2 -2
- package/dist/{claude-sdk-session-apb4ypz3.js → claude-sdk-session-s2k1qd8y.js} +13 -13
- package/dist/{cli-tj4j2ts4.js → cli-02qm7src.js} +8 -8
- package/dist/{cli-1cevz46c.js → cli-0qvtffe9.js} +6 -6
- package/dist/{cli-avmqhatn.js → cli-10hhg0km.js} +8 -8
- package/dist/{cli-7gh7020n.js → cli-1dmcq2eh.js} +1 -1
- package/dist/{cli-kz116kvw.js → cli-239yjk27.js} +7 -7
- package/dist/{cli-7ayprxp7.js → cli-2dfbv8qr.js} +8 -0
- package/dist/{cli-a49vpbf3.js → cli-5kqnemen.js} +1 -1
- package/dist/{cli-x719j3vs.js → cli-5rk36sjf.js} +442 -205
- package/dist/{cli-fb6t92v8.js → cli-6x9ma5w5.js} +1 -1
- package/dist/{cli-c8fwpgqb.js → cli-7225gwj7.js} +2 -2
- package/dist/{cli-5s5hx57y.js → cli-7awf1fp8.js} +1 -1
- package/dist/{cli-bn26xbcs.js → cli-8nyhf7za.js} +1 -1
- package/dist/{cli-ywyrffff.js → cli-8sxqsbbq.js} +1 -1
- package/dist/{cli-rsd7ga7v.js → cli-8zwe9mpk.js} +11 -11
- package/dist/{cli-y83zk5sb.js → cli-a0kcty51.js} +4 -4
- package/dist/{cli-8b9wvjps.js → cli-a4gvwttf.js} +4 -4
- package/dist/{cli-jhp7w1fy.js → cli-a5ebc95n.js} +2 -2
- package/dist/{cli-mvg2hf8p.js → cli-ahtpy9kf.js} +14 -7
- package/dist/{cli-x1p5dhq2.js → cli-bcj753sq.js} +3 -3
- package/dist/{cli-p22zdvnt.js → cli-bn0rewsy.js} +3 -3
- package/dist/{cli-p1m2b6gc.js → cli-d2mb5s8a.js} +7 -7
- package/dist/{cli-rg7ntypm.js → cli-d3qqdy4c.js} +456 -94
- package/dist/{cli-c5sbmw1v.js → cli-d82btycs.js} +94 -15
- package/dist/{cli-02vhhssk.js → cli-efaqv1c7.js} +5 -5
- package/dist/{cli-vjfpshn3.js → cli-ejs3m1sx.js} +1 -1
- package/dist/{cli-kk2r80n5.js → cli-faypxz7q.js} +4 -4
- package/dist/{cli-a1esmmee.js → cli-fpmsqq59.js} +7 -2
- package/dist/{cli-wrv09v7q.js → cli-ftyypbg5.js} +1 -1
- package/dist/{cli-zvze9rbm.js → cli-fyzjmseb.js} +3 -3
- package/dist/{cli-8rwvksbc.js → cli-g0hgtz4p.js} +5 -5
- package/dist/{cli-nrrg0fvy.js → cli-h38rjv87.js} +133 -12
- package/dist/{cli-tg9ksbn8.js → cli-h93c9vb3.js} +2 -2
- package/dist/{cli-eqgfs202.js → cli-hqhzg1qq.js} +2 -2
- package/dist/{cli-bhwa8rzc.js → cli-hyfw2j1e.js} +13 -12
- package/dist/{cli-3wjs22x3.js → cli-j4rqdf2n.js} +2 -2
- package/dist/{cli-6ftrrwm1.js → cli-n85dqg6d.js} +5 -5
- package/dist/{cli-8cxkw1f5.js → cli-nmqdc2t3.js} +2 -2
- package/dist/{cli-4fhyq711.js → cli-nn47d7e8.js} +13 -13
- package/dist/{cli-x3en6ct3.js → cli-nwyw3zhg.js} +1 -1
- package/dist/{cli-3avfh5az.js → cli-pemywhfq.js} +2 -2
- package/dist/{cli-xfe63zx3.js → cli-pkht5hap.js} +2 -2
- package/dist/{cli-sppc0cmw.js → cli-pnp2j5zg.js} +2 -2
- package/dist/{cli-wk8ccrz6.js → cli-pw7v69vp.js} +2 -2
- package/dist/{cli-tgvpmnwz.js → cli-r99kc9yq.js} +5 -5
- package/dist/{cli-bfc3k1x5.js → cli-rdyw3mct.js} +9 -9
- package/dist/{cli-b80a1100.js → cli-s6vr7r2d.js} +2 -2
- package/dist/{cli-zv4vy2b0.js → cli-s95dknxh.js} +1 -1
- package/dist/{cli-ttaz6c7m.js → cli-saw9a6f6.js} +6 -5
- package/dist/{cli-mz1ezzzh.js → cli-shvfedvm.js} +3 -3
- package/dist/{cli-rdr166ht.js → cli-smvcw9z5.js} +1 -1
- package/dist/{cli-2rwet3r7.js → cli-sv5gb0yb.js} +1 -1
- package/dist/{cli-5ff2sjyt.js → cli-t30yj95q.js} +2 -2
- package/dist/{cli-ptkpaqfy.js → cli-wt645v7q.js} +1 -1
- package/dist/{cli-f3nx1h8z.js → cli-wxbn3yfx.js} +2 -2
- package/dist/{cli-wyrr38xm.js → cli-x5mq1fyq.js} +1 -1
- package/dist/{cli-ccn0eybf.js → cli-xbzvet96.js} +6 -6
- package/dist/{cli-4mkvwgzr.js → cli-xf234ws8.js} +1 -1
- package/dist/{cli-nme7wery.js → cli-y2x52gs2.js} +46 -13
- package/dist/{cli-ffbrkh1r.js → cli-y8n5k515.js} +46 -38
- package/dist/{cli-ecshf504.js → cli-z4wd2ftv.js} +7 -7
- package/dist/{cli-dsgj3e7a.js → cli-zfg9enh9.js} +2 -2
- package/dist/{cli-yxqag76b.js → cli-zw88zjba.js} +1 -1
- package/dist/cli.js +20 -20
- package/dist/{codex-adapter-pp2gq8gr.js → codex-adapter-ttygw382.js} +6 -6
- package/dist/{codex-hook-qwzccd64.js → codex-hook-x7nejyrr.js} +7 -4
- package/dist/{codex-login-35p4sk85.js → codex-login-m819c41r.js} +1 -1
- package/dist/{codex-session-runner-0vzn09t8.js → codex-session-runner-60b58vv3.js} +7 -7
- package/dist/{commands-psqhkttb.js → commands-edmdwa4x.js} +2 -2
- package/dist/{controller-t20vz2k8.js → controller-m263fg43.js} +4 -4
- package/dist/{controllers-connect-gkxmqw9d.js → controllers-connect-5fym8sw1.js} +23 -23
- package/dist/{cursor-adapter-3v542qe5.js → cursor-adapter-cz6nm6mj.js} +3 -3
- package/dist/{db-nc1rd869.js → db-2y0mgstc.js} +3 -3
- package/dist/{db-2xbyenjr.js → db-nn0hcmx5.js} +6 -2
- package/dist/{devin-adapter-7egzmt0k.js → devin-adapter-zgqadvnm.js} +1 -1
- package/dist/{dispatcher-ewceb8ts.js → dispatcher-0a9wwjcx.js} +20 -20
- package/dist/{docker-njavd3fb.js → docker-fkgtr75t.js} +2 -2
- package/dist/{dsh-adapter-75xwh78t.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-xsc47jwf.js → easter-egg-3d-5cgynaja.js} +1 -1
- package/dist/{event-bus-101fenkh.js → event-bus-9vpk8gmq.js} +1 -1
- package/dist/{execute-gzmw1mnw.js → execute-t4e6m3rw.js} +1 -1
- package/dist/{external-channel-a5dsmxn4.js → external-channel-zhvt95n3.js} +2 -2
- package/dist/{grok-adapter-vj88xr23.js → grok-adapter-qnr1hp2y.js} +10 -10
- package/dist/handlers-dkcarkd0.js +39 -0
- package/dist/{heartbeat-t2m9r3wa.js → heartbeat-gz97r7ac.js} +23 -23
- package/dist/{hook-xa677f8r.js → hook-1mmdgjqs.js} +16 -9
- package/dist/{hook-8femx1bg.js → hook-x8rthca4.js} +6 -6
- package/dist/{http-xpmmq028.js → http-r8f85xr9.js} +274 -160
- package/dist/index-5jecgp3n.js +41 -0
- package/dist/index-aex0eba9.js +40 -0
- package/dist/{index-syztqdt6.js → index-g6tbb6ve.js} +1 -1
- package/dist/{index-cdzyfrsp.js → index-smxyk04s.js} +1 -1
- package/dist/{index-dg2tmj99.js → index-vzvfqzfj.js} +20 -20
- package/dist/{index-s7ckdajq.js → index-y7rbcrc1.js} +10 -10
- package/dist/{keepalive-3nr4st3r.js → keepalive-64c0f34c.js} +4 -4
- package/dist/lead-b1da0f97.js +78 -0
- package/dist/{maintenance-verwpe2c.js → maintenance-mnxpar12.js} +1 -1
- package/dist/{oauth-refresh-sweep-9q2t0y3q.js → oauth-refresh-sweep-36hs2v4h.js} +3 -3
- package/dist/{onboard-aytqedmq.js → onboard-xes8de61.js} +3 -3
- package/dist/{opencode-adapter-f4hryjny.js → opencode-adapter-3s7ax02e.js} +7 -7
- package/dist/{otel-impl-hckw4wt1.js → otel-impl-fzc57kcq.js} +2 -2
- package/dist/{pi-mono-adapter-hxby0ce1.js → pi-mono-adapter-ytp7gz8b.js} +22 -11
- package/dist/{pricing-refresh-5nvme3sm.js → pricing-refresh-1nek2w7b.js} +3 -3
- package/dist/{rbac-roles-vbpy21mg.js → rbac-roles-sqefb5hw.js} +2 -2
- package/dist/{rbac-roles-pd8c26m1.js → rbac-roles-ttyfaep2.js} +1 -1
- package/dist/{render-v2-7nqgvk2q.js → render-v2-vb527jwn.js} +3 -3
- package/dist/{resume-b6djcj3c.js → resume-jcdzg1bn.js} +10 -8
- package/dist/{runtime-txrxc9za.js → runtime-xe3aqy4x.js} +1 -1
- package/dist/{session-access-fc3f9y12.js → session-access-zsp4wsp0.js} +4 -4
- package/dist/{setup-n39szm6b.js → setup-1nb2zqd3.js} +1 -1
- package/dist/{virtual-modules-wyp2k6z3.js → virtual-modules-4r3zkc20.js} +2 -2
- package/dist/{work-objects-jem2fre0.js → work-objects-61w08fwr.js} +23 -23
- package/dist/worker-c1e68hpa.js +78 -0
- package/dist/{x-8ewt8gxn.js → x-y45mthwn.js} +2 -2
- package/openapi.json +491 -52
- package/package.json +3 -3
- package/src/apps/action-runner.ts +146 -0
- package/src/apps/catalog.generated.json +3 -2
- package/src/apps/definition.ts +152 -4
- package/src/apps/row-store.ts +23 -9
- package/src/apps/schema-migrate.ts +14 -6
- package/src/apps/script-types.ts +2 -1
- package/src/apps/sync.ts +20 -19
- package/src/be/db/tasks/read.ts +112 -0
- package/src/be/db.ts +16 -4
- package/src/be/fleet-benchmark-stats.ts +10 -5
- package/src/be/models-catalog.ts +4 -0
- package/src/be/onboarding.ts +6 -0
- package/src/be/scripts/typecheck.ts +13 -0
- package/src/be/swarm-config-guard.ts +25 -1
- package/src/be/telemetry-enable.ts +1 -1
- package/src/commands/provider-credentials.ts +12 -2
- package/src/commands/runner.ts +3 -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/agents.ts +1 -0
- package/src/http/apps.ts +99 -183
- package/src/http/fleet-benchmark.ts +79 -30
- package/src/http/global-config.ts +7 -0
- package/src/http/ranking.ts +24 -40
- package/src/http/status.ts +49 -43
- package/src/http/tasks.ts +49 -1
- package/src/http/telemetry-ui.ts +1 -1
- package/src/http/used-by.ts +55 -19
- package/src/http/users.ts +2 -2
- package/src/providers/acp-targets.ts +1 -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 +5 -33
- 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/telemetry-context.ts +19 -1
- package/src/telemetry-contract/EVENTS.md +42 -8
- package/src/telemetry-contract/SOURCE.json +6 -5
- package/src/telemetry-contract/catalog.json +225 -5
- package/src/telemetry-contract/ranking/fixtures/valid/no-claim.json +10 -0
- package/src/telemetry-contract/ranking/listing.v1.schema.json +2 -2
- package/src/telemetry-contract/types.gen.ts +225 -27
- 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 +29 -6
- package/src/tests/app-action-run-tool.test.ts +178 -0
- package/src/tests/app-row-tools.test.ts +385 -0
- package/src/tests/apps-json-column.test.ts +611 -0
- package/src/tests/apps-script-types.test.ts +2 -0
- package/src/tests/apps-sync-engine.test.ts +42 -0
- package/src/tests/check-pr-body.test.ts +0 -23
- package/src/tests/credential-check.test.ts +38 -0
- package/src/tests/db-task-read-contract.test.ts +4 -0
- package/src/tests/db-task-write-contract.test.ts +2 -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-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 +8 -23
- package/src/tests/ranking-route.test.ts +49 -47
- package/src/tests/reasoning-effort.test.ts +39 -3
- package/src/tests/runner-harness-telemetry-env.test.ts +62 -1
- package/src/tests/status.test.ts +158 -9
- package/src/tests/swarm-tool-result-gate.test.ts +2 -4
- package/src/tests/task-lineage.test.ts +180 -0
- package/src/tests/telemetry-context.test.ts +128 -2
- package/src/tests/telemetry-init.test.ts +62 -5
- 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 +144 -10
- package/src/tests/user-token-rest-auth.test.ts +38 -0
- 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 +239 -0
- package/src/tools/model-catalog/index.ts +1 -1
- 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 +17 -0
- package/src/utils/company-profile.ts +24 -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 +44 -4
- package/templates/skills/apps/content.md +44 -4
- package/dist/handlers-zbs90bny.js +0 -39
- package/dist/index-8pwc1jc8.js +0 -40
- package/dist/index-nxa6me0q.js +0 -41
- package/dist/lead-td8h2sfn.js +0 -78
- package/dist/worker-wr6xgfr2.js +0 -78
- package/src/tests/check-ranking-entry.test.ts +0 -146
|
@@ -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
|
});
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { mkdirSync, mkdtempSync, rmSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
2
6
|
import pkg from "../../package.json";
|
|
3
7
|
import {
|
|
4
8
|
_getInstallationIdForTests,
|
|
@@ -14,6 +18,7 @@ import {
|
|
|
14
18
|
_resolveIntegrationProvider,
|
|
15
19
|
_resolveIntegrationType,
|
|
16
20
|
emitIntegrationConnected,
|
|
21
|
+
getTelemetryEnvironment,
|
|
17
22
|
initTelemetry,
|
|
18
23
|
telemetry,
|
|
19
24
|
track,
|
|
@@ -686,8 +691,11 @@ describe("initTelemetry", () => {
|
|
|
686
691
|
const originalFetch = globalThis.fetch;
|
|
687
692
|
const originalNodeEnv = process.env.NODE_ENV;
|
|
688
693
|
let captured: Record<string, unknown> | null = null;
|
|
694
|
+
let packageRoot: URL;
|
|
689
695
|
|
|
690
696
|
beforeEach(() => {
|
|
697
|
+
packageRoot = pathToFileURL(`${mkdtempSync(join(tmpdir(), "telemetry-env-"))}/`);
|
|
698
|
+
process.env.NODE_ENV = "production";
|
|
691
699
|
captured = null;
|
|
692
700
|
globalThis.fetch = (async (_url: string, init?: { body?: string }) => {
|
|
693
701
|
captured = init?.body ? JSON.parse(init.body) : null;
|
|
@@ -697,14 +705,59 @@ describe("initTelemetry", () => {
|
|
|
697
705
|
});
|
|
698
706
|
|
|
699
707
|
afterEach(() => {
|
|
708
|
+
rmSync(packageRoot, { recursive: true, force: true });
|
|
700
709
|
globalThis.fetch = originalFetch;
|
|
701
710
|
delete process.env.DESPLEGA_TELEMETRY_ENV;
|
|
702
711
|
if (originalNodeEnv === undefined) delete process.env.NODE_ENV;
|
|
703
712
|
else process.env.NODE_ENV = originalNodeEnv;
|
|
704
713
|
});
|
|
705
714
|
|
|
715
|
+
test("track detects the real checkout through the default package root with an unprimed cache", async () => {
|
|
716
|
+
await initTelemetry(
|
|
717
|
+
"api-server",
|
|
718
|
+
async (key) => (key === "telemetry_installation_id" ? "install_checkout_env" : undefined),
|
|
719
|
+
async () => {},
|
|
720
|
+
{ generateIfMissing: true },
|
|
721
|
+
);
|
|
722
|
+
|
|
723
|
+
track({ event: "test.event", properties: {} });
|
|
724
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
725
|
+
|
|
726
|
+
const metadata = (captured as { metadata: Record<string, unknown> }).metadata;
|
|
727
|
+
expect(metadata.environment).toBe("development");
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
test("a .git directory marks a checkout as development", () => {
|
|
731
|
+
mkdirSync(new URL(".git", packageRoot));
|
|
732
|
+
expect(getTelemetryEnvironment(packageRoot)).toBe("development");
|
|
733
|
+
rmSync(new URL(".git", packageRoot), { recursive: true });
|
|
734
|
+
expect(getTelemetryEnvironment(packageRoot)).toBe("development");
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
test("a .git file marks a worktree as development", async () => {
|
|
738
|
+
await Bun.write(new URL(".git", packageRoot), "gitdir: /some/worktree");
|
|
739
|
+
expect(getTelemetryEnvironment(packageRoot)).toBe("development");
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
test("caches the checkout check across events", () => {
|
|
743
|
+
expect(getTelemetryEnvironment(packageRoot)).toBe("production");
|
|
744
|
+
mkdirSync(new URL(".git", packageRoot));
|
|
745
|
+
expect(getTelemetryEnvironment(packageRoot)).toBe("production");
|
|
746
|
+
process.env.DESPLEGA_TELEMETRY_ENV = "staging";
|
|
747
|
+
expect(getTelemetryEnvironment(packageRoot)).toBe("staging");
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
test("an unusable package root falls back to production without throwing", () => {
|
|
751
|
+
expect(getTelemetryEnvironment(new URL("data:text/plain,unavailable"))).toBe("production");
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
test("compiled binary paths default to production without throwing", () => {
|
|
755
|
+
expect(getTelemetryEnvironment(new URL("file:///$bunfs/root/"))).toBe("production");
|
|
756
|
+
});
|
|
757
|
+
|
|
706
758
|
test("defaults to production even when NODE_ENV is development", async () => {
|
|
707
759
|
process.env.NODE_ENV = "development";
|
|
760
|
+
expect(getTelemetryEnvironment(packageRoot)).toBe("production");
|
|
708
761
|
await initTelemetry(
|
|
709
762
|
"api-server",
|
|
710
763
|
async (key) => (key === "telemetry_installation_id" ? "install_default_env" : undefined),
|
|
@@ -719,9 +772,11 @@ describe("initTelemetry", () => {
|
|
|
719
772
|
expect(metadata.environment).toBe("production");
|
|
720
773
|
});
|
|
721
774
|
|
|
722
|
-
test("uses DESPLEGA_TELEMETRY_ENV
|
|
723
|
-
|
|
724
|
-
process.env.
|
|
775
|
+
test("uses DESPLEGA_TELEMETRY_ENV over a git checkout and NODE_ENV=test", async () => {
|
|
776
|
+
mkdirSync(new URL(".git", packageRoot));
|
|
777
|
+
process.env.NODE_ENV = "test";
|
|
778
|
+
process.env.DESPLEGA_TELEMETRY_ENV = "staging";
|
|
779
|
+
expect(getTelemetryEnvironment(packageRoot)).toBe("staging");
|
|
725
780
|
await initTelemetry(
|
|
726
781
|
"api-server",
|
|
727
782
|
async (key) => (key === "telemetry_installation_id" ? "install_explicit_env" : undefined),
|
|
@@ -733,11 +788,13 @@ describe("initTelemetry", () => {
|
|
|
733
788
|
await new Promise((r) => setTimeout(r, 0));
|
|
734
789
|
|
|
735
790
|
const metadata = (captured as { metadata: Record<string, unknown> }).metadata;
|
|
736
|
-
expect(metadata.environment).toBe("
|
|
791
|
+
expect(metadata.environment).toBe("staging");
|
|
737
792
|
});
|
|
738
793
|
|
|
739
|
-
test("preserves NODE_ENV=test when telemetry env is unset", async () => {
|
|
794
|
+
test("preserves NODE_ENV=test over a git checkout when telemetry env is unset", async () => {
|
|
795
|
+
mkdirSync(new URL(".git", packageRoot));
|
|
740
796
|
process.env.NODE_ENV = "test";
|
|
797
|
+
expect(getTelemetryEnvironment(packageRoot)).toBe("test");
|
|
741
798
|
await initTelemetry(
|
|
742
799
|
"api-server",
|
|
743
800
|
async (key) => (key === "telemetry_installation_id" ? "install_test_env" : undefined),
|
|
@@ -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", () => {
|