@desplega.ai/agent-swarm 2.4.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 -1
- package/dist/{acp-adapter-kedstxx6.js → acp-adapter-zvzr538d.js} +9 -9
- package/dist/{actions-vget6r1k.js → actions-jhtqdw69.js} +21 -21
- package/dist/{amp-adapter-xeb5mktv.js → amp-adapter-bd84rhs2.js} +4 -4
- package/dist/{app-mqap8t8m.js → app-kreq1gwg.js} +3 -3
- package/dist/{assistant-h0gv97mv.js → assistant-3k22sbsq.js} +22 -22
- package/dist/{boot-reembed-qr8a894z.js → boot-reembed-e53x6b9v.js} +1 -1
- package/dist/{boot-reembed-rj85jjhw.js → boot-reembed-f6z923ny.js} +1 -1
- package/dist/{boot-scrub-sweep-eyfvn6v9.js → boot-scrub-sweep-0538vsb7.js} +4 -4
- package/dist/{boot-seeds-xs9a8dx1.js → boot-seeds-6wxqkfk2.js} +4 -4
- package/dist/{claude-adapter-bm23cye7.js → claude-adapter-gfts5044.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-56besmfz.js} +13 -13
- package/dist/{cli-nrrg0fvy.js → cli-0bh477zn.js} +18 -5
- package/dist/{cli-7gh7020n.js → cli-1dmcq2eh.js} +1 -1
- package/dist/{cli-7ayprxp7.js → cli-2dfbv8qr.js} +8 -0
- package/dist/{cli-1cevz46c.js → cli-3kh4kpqy.js} +6 -6
- package/dist/{cli-tg9ksbn8.js → cli-3p8fqcnh.js} +2 -2
- package/dist/{cli-x1p5dhq2.js → cli-3r448dqn.js} +3 -3
- package/dist/{cli-ecshf504.js → cli-3tnpj2bh.js} +7 -7
- package/dist/{cli-ffbrkh1r.js → cli-471qk6s2.js} +46 -38
- package/dist/{cli-a49vpbf3.js → cli-5kqnemen.js} +1 -1
- package/dist/{cli-zvze9rbm.js → cli-6ncjvx55.js} +3 -3
- package/dist/{cli-fb6t92v8.js → cli-6x9ma5w5.js} +1 -1
- package/dist/{cli-c8fwpgqb.js → cli-7225gwj7.js} +2 -2
- package/dist/{cli-bn26xbcs.js → cli-8nyhf7za.js} +1 -1
- 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-mz1ezzzh.js → cli-ash6xxk7.js} +2 -2
- package/dist/{cli-rsd7ga7v.js → cli-axh29tyr.js} +11 -11
- package/dist/{cli-p22zdvnt.js → cli-bn0rewsy.js} +3 -3
- package/dist/{cli-6ftrrwm1.js → cli-bn5kevnf.js} +5 -5
- package/dist/{cli-p1m2b6gc.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-a1esmmee.js → cli-fpmsqq59.js} +7 -2
- package/dist/{cli-8cxkw1f5.js → cli-fz9kewbh.js} +2 -2
- package/dist/{cli-8rwvksbc.js → cli-g0hgtz4p.js} +5 -5
- package/dist/{cli-kz116kvw.js → cli-gf0kfg81.js} +7 -7
- package/dist/{cli-eqgfs202.js → cli-hqhzg1qq.js} +2 -2
- package/dist/{cli-3wjs22x3.js → cli-j4rqdf2n.js} +2 -2
- package/dist/{cli-ywyrffff.js → cli-jh0qcrga.js} +1 -1
- package/dist/{cli-ttaz6c7m.js → cli-k9z4zd4p.js} +6 -5
- package/dist/{cli-nme7wery.js → cli-kmqgmcnb.js} +46 -13
- package/dist/{cli-x719j3vs.js → cli-kr2pmfqx.js} +392 -168
- package/dist/{cli-wrv09v7q.js → cli-m2qgh5sv.js} +1 -1
- package/dist/{cli-xfe63zx3.js → cli-mfe7f7b5.js} +2 -2
- package/dist/{cli-x3en6ct3.js → cli-nwyw3zhg.js} +1 -1
- package/dist/{cli-5s5hx57y.js → cli-nx462e4v.js} +1 -1
- package/dist/{cli-bfc3k1x5.js → cli-p0rakbrk.js} +9 -9
- package/dist/{cli-avmqhatn.js → cli-p6argxh9.js} +8 -8
- package/dist/{cli-3avfh5az.js → cli-pemywhfq.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-bhwa8rzc.js → cli-rdq7tzwm.js} +13 -12
- package/dist/{cli-rg7ntypm.js → cli-rqqsfz30.js} +379 -87
- package/dist/{cli-kk2r80n5.js → cli-s48yvzb8.js} +4 -4
- package/dist/{cli-zv4vy2b0.js → cli-s95dknxh.js} +1 -1
- package/dist/{cli-b80a1100.js → cli-sjyetj21.js} +2 -2
- 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-syhq378v.js} +2 -2
- package/dist/{cli-tj4j2ts4.js → cli-tbhwrxpt.js} +8 -8
- package/dist/{cli-4fhyq711.js → cli-vy0fay6k.js} +13 -13
- 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-tgvpmnwz.js → cli-xftrvxag.js} +5 -5
- package/dist/{cli-02vhhssk.js → cli-z071fb95.js} +5 -5
- package/dist/{cli-mvg2hf8p.js → cli-z1ew8vbq.js} +14 -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 +19 -19
- package/dist/{codex-adapter-pp2gq8gr.js → codex-adapter-p6rmm7vv.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-8vd5t9t0.js} +7 -7
- package/dist/{commands-psqhkttb.js → commands-7ems2p13.js} +2 -2
- package/dist/{controller-t20vz2k8.js → controller-m263fg43.js} +4 -4
- package/dist/{controllers-connect-gkxmqw9d.js → controllers-connect-bg33ae53.js} +23 -23
- package/dist/{cursor-adapter-3v542qe5.js → cursor-adapter-cz6nm6mj.js} +3 -3
- package/dist/{db-2xbyenjr.js → db-w505dwa8.js} +2 -2
- package/dist/{db-nc1rd869.js → db-ys2rbdft.js} +3 -3
- package/dist/{devin-adapter-7egzmt0k.js → devin-adapter-zgqadvnm.js} +1 -1
- package/dist/{dispatcher-ewceb8ts.js → dispatcher-0s0mckqr.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-5b23fv8z.js} +2 -2
- package/dist/{grok-adapter-vj88xr23.js → grok-adapter-q9fghe5q.js} +10 -10
- package/dist/handlers-mqrym347.js +39 -0
- package/dist/{heartbeat-t2m9r3wa.js → heartbeat-tww6yx1w.js} +23 -23
- package/dist/{hook-xa677f8r.js → hook-dx0pkvar.js} +16 -9
- package/dist/{hook-8femx1bg.js → hook-pvmh9b6m.js} +6 -6
- package/dist/{http-xpmmq028.js → http-k2ejbt05.js} +199 -131
- package/dist/{index-s7ckdajq.js → index-2z710v4x.js} +10 -10
- package/dist/{index-dg2tmj99.js → index-dgz61nff.js} +20 -20
- package/dist/{index-syztqdt6.js → index-g6tbb6ve.js} +1 -1
- package/dist/index-h2ek08d6.js +40 -0
- package/dist/index-pqfwh9nn.js +41 -0
- package/dist/{index-cdzyfrsp.js → index-x5aktmp8.js} +1 -1
- package/dist/{keepalive-3nr4st3r.js → keepalive-nhvpw10z.js} +4 -4
- package/dist/lead-jb6sf69z.js +78 -0
- package/dist/{maintenance-verwpe2c.js → maintenance-agakc519.js} +1 -1
- package/dist/{oauth-refresh-sweep-9q2t0y3q.js → oauth-refresh-sweep-4dcbhtn0.js} +3 -3
- package/dist/{onboard-aytqedmq.js → onboard-4n2kwa4v.js} +3 -3
- package/dist/{opencode-adapter-f4hryjny.js → opencode-adapter-3s7ax02e.js} +7 -7
- package/dist/{otel-impl-hckw4wt1.js → otel-impl-tfa12080.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-3cqx3f3x.js} +3 -3
- package/dist/{rbac-roles-pd8c26m1.js → rbac-roles-9fae5rnv.js} +1 -1
- package/dist/{rbac-roles-vbpy21mg.js → rbac-roles-y6zbfzve.js} +2 -2
- package/dist/{render-v2-7nqgvk2q.js → render-v2-emawjvc4.js} +3 -3
- package/dist/{resume-b6djcj3c.js → resume-zhgts7h7.js} +10 -8
- package/dist/{runtime-txrxc9za.js → runtime-xe3aqy4x.js} +1 -1
- package/dist/{session-access-fc3f9y12.js → session-access-cm823e06.js} +4 -4
- package/dist/{setup-n39szm6b.js → setup-tkp99w1e.js} +1 -1
- package/dist/{virtual-modules-wyp2k6z3.js → virtual-modules-4r3zkc20.js} +2 -2
- package/dist/{work-objects-jem2fre0.js → work-objects-f722h263.js} +23 -23
- package/dist/worker-p7f336rq.js +78 -0
- package/dist/{x-8ewt8gxn.js → x-y45mthwn.js} +2 -2
- package/openapi.json +99 -52
- package/package.json +2 -2
- package/src/apps/action-runner.ts +146 -0
- package/src/apps/row-store.ts +4 -2
- package/src/apps/script-types.ts +1 -1
- package/src/be/db.ts +14 -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 +10 -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 +84 -181
- 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/tasks.ts +2 -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/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 +14 -3
- package/src/tests/app-action-run-tool.test.ts +178 -0
- package/src/tests/app-row-tools.test.ts +360 -0
- package/src/tests/check-pr-body.test.ts +0 -23
- package/src/tests/credential-check.test.ts +38 -0
- 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 +56 -1
- 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 +144 -10
- 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/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 +41 -2
- package/templates/skills/apps/content.md +41 -2
- 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
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { codexPreToolUseBlock } from "../hooks/codex-hook";
|
|
6
|
+
import { gitRemoteOrigin, guardGhPrRoom, type PrRoomGuardDeps } from "../hooks/pr-room-guard";
|
|
7
|
+
import { expectChildOk, runChild } from "./test-proc";
|
|
8
|
+
|
|
9
|
+
const ROOM_LINE = "PR room: prr-abcde23457";
|
|
10
|
+
const BLOCK_REASON =
|
|
11
|
+
'This repo uses PR rooms. Create the room first with the pr-room skill (app action createRoom). Then pass the returned prBody with --body-file. The body must contain the line "PR room: prr-...".';
|
|
12
|
+
|
|
13
|
+
function fakeDeps(overrides: Partial<PrRoomGuardDeps> = {}): PrRoomGuardDeps {
|
|
14
|
+
return {
|
|
15
|
+
env: { PR_ROOM_REPOS: "owner/repo" },
|
|
16
|
+
readFile: async () => "No room marker",
|
|
17
|
+
remoteOrigin: async () => "https://github.com/owner/repo.git",
|
|
18
|
+
...overrides,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const check = (command: string, deps = fakeDeps()) => guardGhPrRoom({ command }, "/w", deps);
|
|
23
|
+
|
|
24
|
+
describe("guardGhPrRoom", () => {
|
|
25
|
+
test.each([undefined, "", " , , "])("allows disabled configuration %p", async (value) => {
|
|
26
|
+
expect(
|
|
27
|
+
await check(
|
|
28
|
+
"gh pr create -R owner/repo -b plain",
|
|
29
|
+
fakeDeps({ env: { PR_ROOM_REPOS: value } }),
|
|
30
|
+
),
|
|
31
|
+
).toBeNull();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("blocks a configured repo without a room marker", async () => {
|
|
35
|
+
expect(await check("gh pr create --repo owner/repo --body plain")).toBe(BLOCK_REASON);
|
|
36
|
+
expect(await check("gh pr create -Rowner/repo")).toBe(BLOCK_REASON);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("allows a marker in the inline body", async () => {
|
|
40
|
+
expect(await check(`gh pr create -R owner/repo --body 'Summary\n${ROOM_LINE}'`)).toBeNull();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("resolves body files relative to cd", async () => {
|
|
44
|
+
const paths: string[] = [];
|
|
45
|
+
const deps = fakeDeps({
|
|
46
|
+
readFile: async (path) => {
|
|
47
|
+
paths.push(path);
|
|
48
|
+
return ROOM_LINE;
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
expect(
|
|
52
|
+
await check("cd /checkout && gh pr create -R owner/repo --body-file body.md", deps),
|
|
53
|
+
).toBeNull();
|
|
54
|
+
expect(paths).toEqual(["/checkout/body.md"]);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("allows a heredoc body with a marker", async () => {
|
|
58
|
+
expect(
|
|
59
|
+
await check(`gh pr create -R owner/repo --body-file - <<'EOF'\n${ROOM_LINE}\nEOF`),
|
|
60
|
+
).toBeNull();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("blocks resolved files and heredocs without a marker", async () => {
|
|
64
|
+
expect(await check("gh pr create -R owner/repo -F body.md")).toBe(BLOCK_REASON);
|
|
65
|
+
expect(await check("gh pr create -R owner/repo -F - <<'EOF'\nplain\nEOF")).toBe(BLOCK_REASON);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("allows another repo without reading its body", async () => {
|
|
69
|
+
expect(await check("gh pr create -R other/repo -b plain")).toBeNull();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test.each([
|
|
73
|
+
"GH_REPO=owner/repo gh pr create -b plain",
|
|
74
|
+
"export GH_REPO=owner/repo && gh pr create -b plain",
|
|
75
|
+
])("checks command targeting: %s", async (command) => {
|
|
76
|
+
expect(await check(command)).toBe(BLOCK_REASON);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("allows an ambiguous target unless every candidate is configured", async () => {
|
|
80
|
+
const otherCheckout = fakeDeps({ remoteOrigin: async () => "https://github.com/other/repo" });
|
|
81
|
+
expect(await check("GH_REPO=owner/repo && gh pr create -b plain", otherCheckout)).toBeNull();
|
|
82
|
+
expect(await check("GH_REPO=owner/repo && gh pr create -b plain")).toBe(BLOCK_REASON);
|
|
83
|
+
expect(await check("gh pr create -R owner/repo -R other/repo -b plain")).toBeNull();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("checks inherited GH_REPO and honors an explicit override", async () => {
|
|
87
|
+
const deps = fakeDeps({ env: { PR_ROOM_REPOS: "owner/repo", GH_REPO: "owner/repo" } });
|
|
88
|
+
expect(await check("gh pr create -b plain", deps)).toBe(BLOCK_REASON);
|
|
89
|
+
expect(await check("gh pr create -R other/repo -b plain", deps)).toBeNull();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("trims configured repos and matches case insensitively", async () => {
|
|
93
|
+
const deps = fakeDeps({ env: { PR_ROOM_REPOS: " other/repo, OWNER/REPO , " } });
|
|
94
|
+
expect(await check("gh -R Owner/Repo pr create -b plain", deps)).toBe(BLOCK_REASON);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("allows edits and unrelated commands", async () => {
|
|
98
|
+
expect(await check("gh pr edit 123 -R owner/repo -b plain")).toBeNull();
|
|
99
|
+
expect(await check("gh pr view 123 -R owner/repo")).toBeNull();
|
|
100
|
+
expect(await guardGhPrRoom(undefined, "/w", fakeDeps())).toBeNull();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test.each([
|
|
104
|
+
"https://github.com/Owner/Repo.git",
|
|
105
|
+
"https://github.com/Owner/Repo",
|
|
106
|
+
"git@github.com:Owner/Repo.git",
|
|
107
|
+
"ssh://git@github.com/Owner/Repo.git",
|
|
108
|
+
])("resolves checkout remote %s", async (remote) => {
|
|
109
|
+
const lookups: string[] = [];
|
|
110
|
+
const deps = fakeDeps({
|
|
111
|
+
remoteOrigin: async (cwd) => {
|
|
112
|
+
lookups.push(cwd);
|
|
113
|
+
return remote;
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
expect(await check("cd /checkout && gh pr create -b plain", deps)).toBe(BLOCK_REASON);
|
|
117
|
+
expect(lookups).toEqual(["/checkout"]);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("allows failed remote lookup and unsupported remote forms", async () => {
|
|
121
|
+
expect(
|
|
122
|
+
await check("gh pr create -b plain", fakeDeps({ remoteOrigin: async () => "local/path" })),
|
|
123
|
+
).toBeNull();
|
|
124
|
+
expect(
|
|
125
|
+
await check(
|
|
126
|
+
"gh pr create -b plain",
|
|
127
|
+
fakeDeps({
|
|
128
|
+
remoteOrigin: async () => {
|
|
129
|
+
throw new Error("no checkout");
|
|
130
|
+
},
|
|
131
|
+
}),
|
|
132
|
+
),
|
|
133
|
+
).toBeNull();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// git exports GIT_DIR to a pre-push hook in a linked worktree. The lookup then reads that repo.
|
|
137
|
+
test.skipIf(process.env.GIT_DIR !== undefined)(
|
|
138
|
+
"allows a checkout remote lookup that does not finish",
|
|
139
|
+
async () => {
|
|
140
|
+
const dir = await mkdtemp(join(tmpdir(), "pr-room-fifo-"));
|
|
141
|
+
try {
|
|
142
|
+
expectChildOk(await runChild(["git", "init", "-q", dir]), "git init");
|
|
143
|
+
await rm(join(dir, ".git", "config"));
|
|
144
|
+
expectChildOk(await runChild(["mkfifo", join(dir, ".git", "config")]), "mkfifo");
|
|
145
|
+
await expect(gitRemoteOrigin(dir, 200)).rejects.toThrow();
|
|
146
|
+
const deps = fakeDeps({ remoteOrigin: (cwd) => gitRemoteOrigin(cwd, 200) });
|
|
147
|
+
expect(await guardGhPrRoom({ command: "gh pr create -b plain" }, dir, deps)).toBeNull();
|
|
148
|
+
} finally {
|
|
149
|
+
await rm(dir, { recursive: true, force: true });
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
test.each([
|
|
155
|
+
'gh pr create -R "$UNKNOWN" -b plain',
|
|
156
|
+
'gh pr create -R owner/repo -b "$UNKNOWN"',
|
|
157
|
+
'gh pr create -R owner/repo -F "$UNKNOWN"',
|
|
158
|
+
"cd - && gh pr create -R owner/repo -b plain",
|
|
159
|
+
'cd "$UNKNOWN" && gh pr create -b plain',
|
|
160
|
+
"read GH_REPO && gh pr create -b plain",
|
|
161
|
+
])("allows unresolved inputs: %s", async (command) => {
|
|
162
|
+
expect(await check(command)).toBeNull();
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("allows unreadable body files", async () => {
|
|
166
|
+
const deps = fakeDeps({
|
|
167
|
+
readFile: async () => {
|
|
168
|
+
throw new Error("unreadable");
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
expect(await check("gh pr create -R owner/repo -F missing.md", deps)).toBeNull();
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("resolves known variables without changing leak guard texts", async () => {
|
|
175
|
+
expect(await check('REPO=owner/repo && gh pr create -R "$REPO" -b plain')).toBe(BLOCK_REASON);
|
|
176
|
+
expect(await check(`BODY='${ROOM_LINE}' && gh pr create -R owner/repo -b "$BODY"`)).toBeNull();
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test.each([
|
|
180
|
+
"cat room.md | gh pr create -R owner/repo -F -",
|
|
181
|
+
"printf plain | gh pr create -R owner/repo -F -",
|
|
182
|
+
"gh pr create -R owner/repo -b $(cat room.md)",
|
|
183
|
+
"gh pr create -R owner/repo --fill",
|
|
184
|
+
"gh pr create -R owner/repo -f",
|
|
185
|
+
"gh pr create -R owner/repo --fill-first",
|
|
186
|
+
"gh pr create -R owner/repo --fill-verbose",
|
|
187
|
+
"gh pr create -R owner/repo -T template.md -b plain",
|
|
188
|
+
"gh pr create -R owner/repo --template=template.md",
|
|
189
|
+
"gh pr create -R owner/repo -dw -b plain",
|
|
190
|
+
"gh pr create -R owner/repo --web",
|
|
191
|
+
"gh pr create -R owner/repo -e",
|
|
192
|
+
"gh pr create -R owner/repo --editor",
|
|
193
|
+
"gh pr create -R owner/repo --recover input.json",
|
|
194
|
+
])("allows a body the guard cannot read: %s", async (command) => {
|
|
195
|
+
expect(await check(command)).toBeNull();
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test.each([
|
|
199
|
+
"PR room: prr-abcde2345",
|
|
200
|
+
"PR room: prr-abcde234578",
|
|
201
|
+
"PR room: prr-ABCDE23457",
|
|
202
|
+
"prefix PR room: prr-abcde23457",
|
|
203
|
+
])("rejects an invalid marker: %s", async (line) => {
|
|
204
|
+
expect(await check(`gh pr create -R owner/repo -b '${line}'`)).toBe(BLOCK_REASON);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test("does not accept a marker only in the title", async () => {
|
|
208
|
+
expect(await check(`gh pr create -R owner/repo -t '${ROOM_LINE}' -b plain`)).toBe(BLOCK_REASON);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test("checks every create invocation", async () => {
|
|
212
|
+
expect(await check(`gh pr edit 1 -b plain && gh pr create -R owner/repo -b plain`)).toBe(
|
|
213
|
+
BLOCK_REASON,
|
|
214
|
+
);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test("Codex runs the room guard after the leak guard, including app-server sessions", async () => {
|
|
218
|
+
const roomDeps = fakeDeps();
|
|
219
|
+
const leakDeps = {
|
|
220
|
+
env: { SWARM_CODEX_APP_SERVER: "1" },
|
|
221
|
+
readFile: roomDeps.readFile,
|
|
222
|
+
repoVisibility: async () => "PUBLIC",
|
|
223
|
+
};
|
|
224
|
+
const msg = {
|
|
225
|
+
hook_event_name: "PreToolUse",
|
|
226
|
+
cwd: "/w",
|
|
227
|
+
tool_input: { command: "gh pr create -R owner/repo -b plain" },
|
|
228
|
+
};
|
|
229
|
+
expect(await codexPreToolUseBlock(msg, leakDeps, roomDeps)).toBe(BLOCK_REASON);
|
|
230
|
+
const leaky = {
|
|
231
|
+
...msg,
|
|
232
|
+
tool_input: {
|
|
233
|
+
command:
|
|
234
|
+
"gh pr create -R owner/repo -b 'https://acme.slack.com/archives/C01234567/p1700000000000000'",
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
expect(await codexPreToolUseBlock(leaky, leakDeps, roomDeps)).toContain("PR body leak check:");
|
|
238
|
+
expect(
|
|
239
|
+
await codexPreToolUseBlock({ ...msg, hook_event_name: "PostToolUse" }, leakDeps, roomDeps),
|
|
240
|
+
).toBeNull();
|
|
241
|
+
});
|
|
242
|
+
});
|
|
@@ -2,12 +2,7 @@ import { describe, expect, test } from "bun:test";
|
|
|
2
2
|
import { readdirSync, readFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { isInternalConfigKey, validateConfigValue } from "../be/swarm-config-guard";
|
|
5
|
-
import {
|
|
6
|
-
buildListing,
|
|
7
|
-
mintRankingClaim,
|
|
8
|
-
parseTelemetryRanking,
|
|
9
|
-
rankingEntryFile,
|
|
10
|
-
} from "../ranking/telemetry-ranking";
|
|
5
|
+
import { buildListing, parseTelemetryRanking } from "../ranking/telemetry-ranking";
|
|
11
6
|
import { RANKING_TAXONOMY } from "../telemetry-contract/ranking-taxonomy.gen";
|
|
12
7
|
import { orgIdentityStatus } from "../utils/org-identity";
|
|
13
8
|
|
|
@@ -41,7 +36,6 @@ describe("telemetry_ranking contract fixtures", () => {
|
|
|
41
36
|
|
|
42
37
|
describe("buildListing", () => {
|
|
43
38
|
const identity = orgIdentityStatus({ SWARM_ORG_NAME: "Acme", SWARM_ORG_WEBSITE: "acme.dev" });
|
|
44
|
-
const claim = mintRankingClaim();
|
|
45
39
|
|
|
46
40
|
test("copies the name and domain from the org identity and blanks to null", () => {
|
|
47
41
|
const listing = buildListing(
|
|
@@ -52,12 +46,10 @@ describe("buildListing", () => {
|
|
|
52
46
|
description: " ",
|
|
53
47
|
sourceCodeUrl: null,
|
|
54
48
|
},
|
|
55
|
-
claim,
|
|
56
49
|
new Date("2026-10-18T10:00:00Z"),
|
|
57
50
|
);
|
|
58
51
|
expect(listing).toEqual({
|
|
59
52
|
v: 1,
|
|
60
|
-
claim,
|
|
61
53
|
name: "Acme",
|
|
62
54
|
domain: "acme.dev",
|
|
63
55
|
category: "engineering",
|
|
@@ -66,29 +58,22 @@ describe("buildListing", () => {
|
|
|
66
58
|
sourceCodeUrl: null,
|
|
67
59
|
updatedAt: "2026-10-18T10:00:00.000Z",
|
|
68
60
|
});
|
|
69
|
-
expect(JSON.parse(rankingEntryFile(listing))).toEqual({
|
|
70
|
-
$schema: "./schema.json",
|
|
71
|
-
domain: "acme.dev",
|
|
72
|
-
claim,
|
|
73
|
-
});
|
|
74
61
|
});
|
|
75
62
|
|
|
76
63
|
test("refuses a missing identity and an http source URL", () => {
|
|
77
64
|
expect(() =>
|
|
78
|
-
buildListing(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
claim,
|
|
82
|
-
),
|
|
65
|
+
buildListing(orgIdentityStatus({ SWARM_ORG_NAME: "Swarm", SWARM_ORG_WEBSITE: "acme.dev" }), {
|
|
66
|
+
category: "other",
|
|
67
|
+
}),
|
|
83
68
|
).toThrow("SWARM_ORG_NAME");
|
|
84
69
|
expect(() =>
|
|
85
|
-
buildListing(identity, { category: "other", sourceCodeUrl: "http://github.com/acme" }
|
|
70
|
+
buildListing(identity, { category: "other", sourceCodeUrl: "http://github.com/acme" }),
|
|
86
71
|
).toThrow();
|
|
87
72
|
});
|
|
88
73
|
|
|
89
|
-
test("
|
|
90
|
-
|
|
91
|
-
expect(
|
|
74
|
+
test("a v2.4.0 listing with a claim still parses", () => {
|
|
75
|
+
const legacy = { ...JSON.parse(read("valid", "full.json")), claim: `rk_${"0".repeat(32)}` };
|
|
76
|
+
expect(parseTelemetryRanking(JSON.stringify(legacy)).claim).toBe(legacy.claim);
|
|
92
77
|
});
|
|
93
78
|
|
|
94
79
|
test("the taxonomy comes from the vendored contract", () => {
|
|
@@ -163,22 +163,15 @@ describe("ranking routes", () => {
|
|
|
163
163
|
test("opt-in stores the listing, turns the setting on and emits it with a matching envelope", async () => {
|
|
164
164
|
const result = await call("POST", "/api/ranking/opt-in", form);
|
|
165
165
|
expect(result.statusCode).toBe(200);
|
|
166
|
-
|
|
167
|
-
expect(
|
|
166
|
+
// Opting in is the whole flow: no claim, no entry file, no PR page.
|
|
167
|
+
expect(Object.keys(result.json).sort()).toEqual(["domain", "listing", "verifyUrl"]);
|
|
168
168
|
expect(result.json.domain).toBe("acme.dev");
|
|
169
|
-
expect(String(result.json.prUrl)).toContain("filename=community%2Franking%2Facme.dev.json");
|
|
170
|
-
expect(JSON.parse(String(result.json.fileJson))).toEqual({
|
|
171
|
-
$schema: "./schema.json",
|
|
172
|
-
domain: "acme.dev",
|
|
173
|
-
claim,
|
|
174
|
-
});
|
|
175
169
|
expect(String(result.json.verifyUrl)).toBe(
|
|
176
|
-
|
|
170
|
+
"https://proxy.desplega.sh/v1/ranking/verify?domain=acme.dev",
|
|
177
171
|
);
|
|
178
172
|
|
|
179
173
|
const stored = parseTelemetryRanking(String(await configValue("telemetry_ranking")));
|
|
180
174
|
expect(stored).toMatchObject({
|
|
181
|
-
claim,
|
|
182
175
|
name: "Acme",
|
|
183
176
|
domain: "acme.dev",
|
|
184
177
|
category: "engineering",
|
|
@@ -186,6 +179,7 @@ describe("ranking routes", () => {
|
|
|
186
179
|
description: "QA",
|
|
187
180
|
sourceCodeUrl: null,
|
|
188
181
|
});
|
|
182
|
+
expect(stored).not.toHaveProperty("claim");
|
|
189
183
|
expect(await configValue("PUBLIC_RANKING_ENABLED")).toBe("true");
|
|
190
184
|
|
|
191
185
|
expect(sent.map((event) => event.event)).toEqual(["ranking.opted_in"]);
|
|
@@ -193,10 +187,6 @@ describe("ranking routes", () => {
|
|
|
193
187
|
expect(listing).toEqual(stored);
|
|
194
188
|
expect(sent[0]?.context).toMatchObject({ org_name: "Acme", org_website: "acme.dev" });
|
|
195
189
|
|
|
196
|
-
// A second opt-in for the same domain keeps the claim.
|
|
197
|
-
const again = await call("POST", "/api/ranking/opt-in", { category: "other" });
|
|
198
|
-
expect(again.json.claim).toBe(claim);
|
|
199
|
-
|
|
200
190
|
const state = await call("GET", "/api/ranking");
|
|
201
191
|
expect(state.json).toMatchObject({
|
|
202
192
|
eligible: true,
|
|
@@ -204,10 +194,12 @@ describe("ranking routes", () => {
|
|
|
204
194
|
missing: [],
|
|
205
195
|
listingMatchesDomain: true,
|
|
206
196
|
});
|
|
197
|
+
expect(state.json).not.toHaveProperty("prUrl");
|
|
198
|
+
expect(state.json).not.toHaveProperty("fileJson");
|
|
207
199
|
});
|
|
208
200
|
|
|
209
|
-
test("only a config admin reads the ranking state
|
|
210
|
-
|
|
201
|
+
test("only a config admin reads the ranking state", async () => {
|
|
202
|
+
await call("POST", "/api/ranking/opt-in", form);
|
|
211
203
|
const worker: HttpRequestAuth = {
|
|
212
204
|
kind: "agent",
|
|
213
205
|
agentId: crypto.randomUUID(),
|
|
@@ -220,42 +212,49 @@ describe("ranking routes", () => {
|
|
|
220
212
|
for (const auth of [worker, guest]) {
|
|
221
213
|
const denied = await call("GET", "/api/ranking", undefined, handleRanking, auth);
|
|
222
214
|
expect(denied.statusCode).toBe(403);
|
|
223
|
-
expect(JSON.stringify(denied.json)).not.toContain(claim);
|
|
224
215
|
}
|
|
225
|
-
|
|
226
|
-
expect(state.statusCode).toBe(200);
|
|
227
|
-
expect((state.json.listing as { claim: string }).claim).toBe(claim);
|
|
216
|
+
expect((await call("GET", "/api/ranking")).statusCode).toBe(200);
|
|
228
217
|
});
|
|
229
218
|
|
|
230
|
-
test("
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
219
|
+
test("a v2.4.0 listing keeps working and its claim is never served or re-sent", async () => {
|
|
220
|
+
const claim = `rk_${"0".repeat(32)}`;
|
|
221
|
+
await upsertSwarmConfig({
|
|
222
|
+
scope: "global",
|
|
223
|
+
key: "telemetry_ranking",
|
|
224
|
+
value: JSON.stringify({
|
|
225
|
+
v: 1,
|
|
226
|
+
claim,
|
|
227
|
+
name: "Acme",
|
|
228
|
+
domain: "acme.dev",
|
|
229
|
+
category: "other",
|
|
230
|
+
subcategory: null,
|
|
231
|
+
description: null,
|
|
232
|
+
sourceCodeUrl: null,
|
|
233
|
+
updatedAt: "2026-10-18T10:00:00.000Z",
|
|
234
|
+
}),
|
|
235
|
+
});
|
|
236
|
+
await upsertSwarmConfig({ scope: "global", key: "PUBLIC_RANKING_ENABLED", value: "true" });
|
|
237
|
+
const state = await call("GET", "/api/ranking");
|
|
238
|
+
expect(state.json).toMatchObject({ enabled: true, listingMatchesDomain: true });
|
|
239
|
+
expect(JSON.stringify(state.json)).not.toContain(claim);
|
|
243
240
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
241
|
+
const put = await call(
|
|
242
|
+
"PUT",
|
|
243
|
+
"/api/config",
|
|
244
|
+
{ scope: "global", key: "SWARM_ORG_NAME", value: "Acme Labs" },
|
|
245
|
+
handleConfig,
|
|
246
|
+
);
|
|
247
|
+
expect(put.statusCode).toBe(200);
|
|
248
|
+
expect(sent.map((event) => event.event)).toEqual(["ranking.opted_in"]);
|
|
249
|
+
expect(String(sent[0]?.properties.listing)).not.toContain("claim");
|
|
247
250
|
});
|
|
248
251
|
|
|
249
|
-
test("opt-out stores false
|
|
252
|
+
test("opt-out stores false and emits ranking.opted_out", async () => {
|
|
250
253
|
await call("POST", "/api/ranking/opt-in", form);
|
|
251
254
|
sent = [];
|
|
252
255
|
const result = await call("POST", "/api/ranking/opt-out");
|
|
253
256
|
expect(result.statusCode).toBe(200);
|
|
254
|
-
expect(result.json).toEqual({
|
|
255
|
-
enabled: false,
|
|
256
|
-
leaveUrl:
|
|
257
|
-
"https://github.com/desplega-ai/agent-swarm/delete/main/community/ranking/acme.dev.json",
|
|
258
|
-
});
|
|
257
|
+
expect(result.json).toEqual({ enabled: false });
|
|
259
258
|
expect(await configValue("PUBLIC_RANKING_ENABLED")).toBe("false");
|
|
260
259
|
expect(sent.map((event) => [event.event, event.properties.domain])).toEqual([
|
|
261
260
|
["ranking.opted_out", "acme.dev"],
|
|
@@ -273,8 +272,8 @@ describe("ranking routes", () => {
|
|
|
273
272
|
expect(String(result.json.error)).toContain("/api/ranking");
|
|
274
273
|
});
|
|
275
274
|
|
|
276
|
-
test("renaming the org re-emits the listing
|
|
277
|
-
|
|
275
|
+
test("renaming the org re-emits the listing", async () => {
|
|
276
|
+
await call("POST", "/api/ranking/opt-in", form);
|
|
278
277
|
sent = [];
|
|
279
278
|
const put = await call(
|
|
280
279
|
"PUT",
|
|
@@ -287,7 +286,6 @@ describe("ranking routes", () => {
|
|
|
287
286
|
const listing = parseTelemetryRanking(String(sent[0]?.properties.listing));
|
|
288
287
|
expect(listing).toMatchObject({
|
|
289
288
|
name: "Acme Labs",
|
|
290
|
-
claim: optIn.json.claim,
|
|
291
289
|
subcategory: "qa-testing",
|
|
292
290
|
});
|
|
293
291
|
// The envelope carries the new name before the config reload reaches process.env.
|
|
@@ -312,8 +310,12 @@ describe("ranking routes", () => {
|
|
|
312
310
|
["ranking.opted_out", "acme.dev"],
|
|
313
311
|
]);
|
|
314
312
|
}
|
|
315
|
-
// After the last case (a new website) the old listing needs a new
|
|
313
|
+
// After the last case (a new website) the old listing needs a new opt-in.
|
|
316
314
|
const state = await call("GET", "/api/ranking");
|
|
317
|
-
expect(state.json).toMatchObject({
|
|
315
|
+
expect(state.json).toMatchObject({
|
|
316
|
+
enabled: false,
|
|
317
|
+
listingMatchesDomain: false,
|
|
318
|
+
verifyUrl: null,
|
|
319
|
+
});
|
|
318
320
|
});
|
|
319
321
|
});
|
|
@@ -212,6 +212,13 @@ describe("applyReasoningEffort — pi-session shape", () => {
|
|
|
212
212
|
sessionOptions: { thinkingLevel: "medium" },
|
|
213
213
|
});
|
|
214
214
|
});
|
|
215
|
+
|
|
216
|
+
test("max passes through for an opencode-go model that declares it", () => {
|
|
217
|
+
expect(applyReasoningEffort("pi", "opencode-go/deepseek-v4-flash", "max")).toEqual({
|
|
218
|
+
kind: "pi-session",
|
|
219
|
+
sessionOptions: { thinkingLevel: "max" },
|
|
220
|
+
});
|
|
221
|
+
});
|
|
215
222
|
});
|
|
216
223
|
|
|
217
224
|
describe("applyReasoningEffort — opencode-options shape", () => {
|
|
@@ -245,6 +252,30 @@ describe("applyReasoningEffort — opencode-options shape", () => {
|
|
|
245
252
|
kind: "noop",
|
|
246
253
|
});
|
|
247
254
|
});
|
|
255
|
+
|
|
256
|
+
test("opencode-go passes max through on its OpenAI-compatible transport", () => {
|
|
257
|
+
// `opencode-go/deepseek-v4-flash` declares low|high|max in the snapshot.
|
|
258
|
+
expect(reasoningCapability("opencode", "opencode-go/deepseek-v4-flash").levels).toEqual([
|
|
259
|
+
"low",
|
|
260
|
+
"high",
|
|
261
|
+
"max",
|
|
262
|
+
]);
|
|
263
|
+
expect(applyReasoningEffort("opencode", "opencode-go/deepseek-v4-flash", "max")).toEqual({
|
|
264
|
+
kind: "opencode-options",
|
|
265
|
+
providerId: "opencode-go",
|
|
266
|
+
modelId: "deepseek-v4-flash",
|
|
267
|
+
options: { reasoningEffort: "max" },
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test("anthropic max uses the effort key opencode's Anthropic transport reads", () => {
|
|
272
|
+
expect(applyReasoningEffort("opencode", "anthropic/claude-opus-5-5", "max")).toEqual({
|
|
273
|
+
kind: "opencode-options",
|
|
274
|
+
providerId: "anthropic",
|
|
275
|
+
modelId: "claude-opus-5-5",
|
|
276
|
+
options: { effort: "max" },
|
|
277
|
+
});
|
|
278
|
+
});
|
|
248
279
|
});
|
|
249
280
|
|
|
250
281
|
// The offered levels, the API's validation and the harness application all read the same
|
|
@@ -266,16 +297,21 @@ describe("harness × model → effort matrix", () => {
|
|
|
266
297
|
},
|
|
267
298
|
{ harness: "codex", model: "gpt-6-astra", levels: ["low", "medium", "high", "xhigh", "max"] },
|
|
268
299
|
{ harness: "codex", model: "gpt-5.1-codex", levels: ["low", "medium", "high"] },
|
|
269
|
-
{
|
|
300
|
+
{
|
|
301
|
+
harness: "pi",
|
|
302
|
+
model: "openrouter/deepseek/deepseek-v4.1-flash",
|
|
303
|
+
levels: ["low", "high", "max"],
|
|
304
|
+
},
|
|
270
305
|
{
|
|
271
306
|
harness: "pi",
|
|
272
307
|
model: "openrouter/anthropic/claude-opus-5.5",
|
|
273
|
-
levels: ["low", "medium", "high", "xhigh"],
|
|
308
|
+
levels: ["low", "medium", "high", "xhigh", "max"],
|
|
274
309
|
},
|
|
275
310
|
{
|
|
276
311
|
harness: "opencode",
|
|
277
312
|
model: "openrouter/deepseek/deepseek-v4.1-flash",
|
|
278
|
-
|
|
313
|
+
// The facts declare max, and opencode passes it through.
|
|
314
|
+
levels: ["low", "high", "max"],
|
|
279
315
|
},
|
|
280
316
|
{ harness: "pi", model: "openrouter/qwen/qwen3-coder-flash", levels: [] },
|
|
281
317
|
{ harness: "claude", model: "totally-custom-model-xyz", levels: [] },
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { withHarnessTelemetryEnv } from "../commands/runner";
|
|
3
|
+
import { applyResolvedEnvToProcessEnv, withHarnessTelemetryEnv } from "../commands/runner";
|
|
4
|
+
import { _resetTelemetryStateForTests, initTelemetry, track } from "../telemetry";
|
|
4
5
|
import { CHILD_PROCESS_TEST_BUDGET_MS, runChild } from "./test-proc";
|
|
5
6
|
|
|
6
7
|
const TELEMETRY_PATH = join(import.meta.dir, "..", "telemetry.ts");
|
|
@@ -86,6 +87,60 @@ describe("withHarnessTelemetryEnv", () => {
|
|
|
86
87
|
});
|
|
87
88
|
});
|
|
88
89
|
|
|
90
|
+
test("worker telemetry receives and clears company profile through resolved config", async () => {
|
|
91
|
+
const profile = {
|
|
92
|
+
SWARM_ORG_WEBSITE: "acme.com",
|
|
93
|
+
SWARM_ORG_TEAM_SIZE: "2-10",
|
|
94
|
+
SWARM_ORG_USE_CASE: "ops",
|
|
95
|
+
SWARM_ORG_ACQUISITION_SOURCE: "github",
|
|
96
|
+
};
|
|
97
|
+
const saved = Object.fromEntries(
|
|
98
|
+
[...Object.keys(profile), "ANONYMIZED_TELEMETRY"].map((key) => [key, process.env[key]]),
|
|
99
|
+
);
|
|
100
|
+
const originalFetch = globalThis.fetch;
|
|
101
|
+
const sent: Array<{ context: Record<string, unknown> }> = [];
|
|
102
|
+
try {
|
|
103
|
+
process.env.ANONYMIZED_TELEMETRY = "true";
|
|
104
|
+
_resetTelemetryStateForTests();
|
|
105
|
+
globalThis.fetch = (async (_url: string, init?: { body?: string }) => {
|
|
106
|
+
sent.push(JSON.parse(init?.body ?? "{}"));
|
|
107
|
+
return new Response(null, { status: 204 });
|
|
108
|
+
}) as typeof fetch;
|
|
109
|
+
applyResolvedEnvToProcessEnv(profile);
|
|
110
|
+
const config: Record<string, string> = {
|
|
111
|
+
telemetry_installation_id: "install_worker_profile",
|
|
112
|
+
telemetry_org_id: "org_0123456789abcdef",
|
|
113
|
+
telemetry_org_domain: "acme.com",
|
|
114
|
+
};
|
|
115
|
+
await initTelemetry(
|
|
116
|
+
"worker",
|
|
117
|
+
(key) => config[key],
|
|
118
|
+
() => {},
|
|
119
|
+
);
|
|
120
|
+
track({ event: "session.started", properties: { agentId: crypto.randomUUID() } });
|
|
121
|
+
expect(sent[0]?.context).toMatchObject({
|
|
122
|
+
org_website: "acme.com",
|
|
123
|
+
org_domain: "acme.com",
|
|
124
|
+
team_size: "2-10",
|
|
125
|
+
use_case: "ops",
|
|
126
|
+
acquisition_source: "github",
|
|
127
|
+
});
|
|
128
|
+
applyResolvedEnvToProcessEnv(Object.fromEntries(Object.keys(profile).map((key) => [key, ""])));
|
|
129
|
+
track({ event: "session.started", properties: { agentId: crypto.randomUUID() } });
|
|
130
|
+
expect(sent[1]?.context).not.toHaveProperty("org_website");
|
|
131
|
+
expect(sent[1]?.context).not.toHaveProperty("team_size");
|
|
132
|
+
expect(sent[1]?.context).not.toHaveProperty("use_case");
|
|
133
|
+
expect(sent[1]?.context.acquisition_source).toBeNull();
|
|
134
|
+
} finally {
|
|
135
|
+
for (const [key, value] of Object.entries(saved)) {
|
|
136
|
+
if (value === undefined) delete process.env[key];
|
|
137
|
+
else process.env[key] = value;
|
|
138
|
+
}
|
|
139
|
+
globalThis.fetch = originalFetch;
|
|
140
|
+
_resetTelemetryStateForTests();
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
89
144
|
describe("API boot telemetry environment", () => {
|
|
90
145
|
test(
|
|
91
146
|
"an unprefixed API boot from an agent session emits test",
|
|
@@ -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,
|