@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
|
@@ -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");
|
|
@@ -10,6 +11,12 @@ const TELEMETRY_PATH = join(import.meta.dir, "..", "telemetry.ts");
|
|
|
10
11
|
// leaves the process), and print the environment the event carries.
|
|
11
12
|
const BOOT_SCRIPT = `
|
|
12
13
|
const t = await import(${JSON.stringify(TELEMETRY_PATH)});
|
|
14
|
+
const { mkdtempSync, rmSync } = await import("node:fs");
|
|
15
|
+
const { tmpdir } = await import("node:os");
|
|
16
|
+
const { pathToFileURL } = await import("node:url");
|
|
17
|
+
const root = mkdtempSync(tmpdir() + "/telemetry-package-");
|
|
18
|
+
t.getTelemetryEnvironment(pathToFileURL(root + "/"));
|
|
19
|
+
rmSync(root, { recursive: true, force: true });
|
|
13
20
|
let body;
|
|
14
21
|
globalThis.fetch = async (_url, init) => {
|
|
15
22
|
body = JSON.parse(init.body);
|
|
@@ -86,6 +93,60 @@ describe("withHarnessTelemetryEnv", () => {
|
|
|
86
93
|
});
|
|
87
94
|
});
|
|
88
95
|
|
|
96
|
+
test("worker telemetry receives and clears company profile through resolved config", async () => {
|
|
97
|
+
const profile = {
|
|
98
|
+
SWARM_ORG_WEBSITE: "acme.com",
|
|
99
|
+
SWARM_ORG_TEAM_SIZE: "2-10",
|
|
100
|
+
SWARM_ORG_USE_CASE: "ops",
|
|
101
|
+
SWARM_ORG_ACQUISITION_SOURCE: "github",
|
|
102
|
+
};
|
|
103
|
+
const saved = Object.fromEntries(
|
|
104
|
+
[...Object.keys(profile), "ANONYMIZED_TELEMETRY"].map((key) => [key, process.env[key]]),
|
|
105
|
+
);
|
|
106
|
+
const originalFetch = globalThis.fetch;
|
|
107
|
+
const sent: Array<{ context: Record<string, unknown> }> = [];
|
|
108
|
+
try {
|
|
109
|
+
process.env.ANONYMIZED_TELEMETRY = "true";
|
|
110
|
+
_resetTelemetryStateForTests();
|
|
111
|
+
globalThis.fetch = (async (_url: string, init?: { body?: string }) => {
|
|
112
|
+
sent.push(JSON.parse(init?.body ?? "{}"));
|
|
113
|
+
return new Response(null, { status: 204 });
|
|
114
|
+
}) as typeof fetch;
|
|
115
|
+
applyResolvedEnvToProcessEnv(profile);
|
|
116
|
+
const config: Record<string, string> = {
|
|
117
|
+
telemetry_installation_id: "install_worker_profile",
|
|
118
|
+
telemetry_org_id: "org_0123456789abcdef",
|
|
119
|
+
telemetry_org_domain: "acme.com",
|
|
120
|
+
};
|
|
121
|
+
await initTelemetry(
|
|
122
|
+
"worker",
|
|
123
|
+
(key) => config[key],
|
|
124
|
+
() => {},
|
|
125
|
+
);
|
|
126
|
+
track({ event: "session.started", properties: { agentId: crypto.randomUUID() } });
|
|
127
|
+
expect(sent[0]?.context).toMatchObject({
|
|
128
|
+
org_website: "acme.com",
|
|
129
|
+
org_domain: "acme.com",
|
|
130
|
+
team_size: "2-10",
|
|
131
|
+
use_case: "ops",
|
|
132
|
+
acquisition_source: "github",
|
|
133
|
+
});
|
|
134
|
+
applyResolvedEnvToProcessEnv(Object.fromEntries(Object.keys(profile).map((key) => [key, ""])));
|
|
135
|
+
track({ event: "session.started", properties: { agentId: crypto.randomUUID() } });
|
|
136
|
+
expect(sent[1]?.context).not.toHaveProperty("org_website");
|
|
137
|
+
expect(sent[1]?.context).not.toHaveProperty("team_size");
|
|
138
|
+
expect(sent[1]?.context).not.toHaveProperty("use_case");
|
|
139
|
+
expect(sent[1]?.context.acquisition_source).toBeNull();
|
|
140
|
+
} finally {
|
|
141
|
+
for (const [key, value] of Object.entries(saved)) {
|
|
142
|
+
if (value === undefined) delete process.env[key];
|
|
143
|
+
else process.env[key] = value;
|
|
144
|
+
}
|
|
145
|
+
globalThis.fetch = originalFetch;
|
|
146
|
+
_resetTelemetryStateForTests();
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
89
150
|
describe("API boot telemetry environment", () => {
|
|
90
151
|
test(
|
|
91
152
|
"an unprefixed API boot from an agent session emits test",
|
package/src/tests/status.test.ts
CHANGED
|
@@ -115,7 +115,6 @@ const ENV_KEYS_TO_RESET = [
|
|
|
115
115
|
"GOOGLE_APPLICATION_CREDENTIALS",
|
|
116
116
|
"AGENTMAIL_API_KEY",
|
|
117
117
|
"AGENTMAIL_DISABLE",
|
|
118
|
-
"SWARM_VERIFY_TTL_MS",
|
|
119
118
|
"ANONYMIZED_TELEMETRY",
|
|
120
119
|
];
|
|
121
120
|
|
|
@@ -314,7 +313,7 @@ describe("setup milestones", () => {
|
|
|
314
313
|
expect(getMilestone(payload, "harness").state).toBe("verified");
|
|
315
314
|
});
|
|
316
315
|
|
|
317
|
-
test("provider rollup counts every agent with a
|
|
316
|
+
test("provider rollup counts every agent with a passing report in verifiedWorkers", async () => {
|
|
318
317
|
const verified = await createAgent({
|
|
319
318
|
name: "w-verified",
|
|
320
319
|
isLead: false,
|
|
@@ -376,7 +375,7 @@ describe("setup milestones", () => {
|
|
|
376
375
|
|
|
377
376
|
// ─── Multi-provider fleet rollup ─────────────────────────────────────────
|
|
378
377
|
describe("harness — multi-provider fleet aggregate", () => {
|
|
379
|
-
test("`verified` when every provider in the fleet has a
|
|
378
|
+
test("`verified` when every provider in the fleet has a passing live test", async () => {
|
|
380
379
|
const a = await createAgent({
|
|
381
380
|
name: "claude-w",
|
|
382
381
|
isLead: false,
|
|
@@ -423,8 +422,30 @@ describe("setup milestones", () => {
|
|
|
423
422
|
|
|
424
423
|
const m = getMilestone(await buildStatusPayload(), "harness");
|
|
425
424
|
expect(m.state).toBe("configured");
|
|
426
|
-
|
|
427
|
-
expect(m.hint).
|
|
425
|
+
// Only the provider that needs attention is listed.
|
|
426
|
+
expect(m.hint).toBe("codex: not live-tested");
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
test("an all-verified fleet carries no hint, however old the passing tests are", async () => {
|
|
430
|
+
const a = await createAgent({
|
|
431
|
+
name: "claude-w",
|
|
432
|
+
isLead: false,
|
|
433
|
+
status: "idle",
|
|
434
|
+
capabilities: [],
|
|
435
|
+
});
|
|
436
|
+
const b = await createAgent({
|
|
437
|
+
name: "codex-w",
|
|
438
|
+
isLead: false,
|
|
439
|
+
status: "idle",
|
|
440
|
+
capabilities: [],
|
|
441
|
+
});
|
|
442
|
+
const old = { ok: true, error: null, latency_ms: 12, testedAt: Date.now() - 6 * 3_600_000 };
|
|
443
|
+
await seedCredStatus(a.id, "claude", { ready: true, satisfiedBy: "env", liveTest: old });
|
|
444
|
+
await seedCredStatus(b.id, "codex", { ready: true, satisfiedBy: "file", liveTest: old });
|
|
445
|
+
|
|
446
|
+
const m = getMilestone(await buildStatusPayload(), "harness");
|
|
447
|
+
expect(m.state).toBe("verified");
|
|
448
|
+
expect(m.hint).toBeUndefined();
|
|
428
449
|
});
|
|
429
450
|
|
|
430
451
|
test("`unverified` when any provider in the fleet reports blocked credentials", async () => {
|
|
@@ -1062,6 +1083,135 @@ describe("computeHealth (Phase 2)", () => {
|
|
|
1062
1083
|
expect(payload.health).toBe("degraded");
|
|
1063
1084
|
});
|
|
1064
1085
|
|
|
1086
|
+
test("`ok` when live workers' last passing live test is hours old", async () => {
|
|
1087
|
+
const lead = await createAgent({
|
|
1088
|
+
name: "lead-old",
|
|
1089
|
+
isLead: true,
|
|
1090
|
+
status: "idle",
|
|
1091
|
+
capabilities: [],
|
|
1092
|
+
});
|
|
1093
|
+
const worker = await createAgent({
|
|
1094
|
+
name: "worker-old",
|
|
1095
|
+
isLead: false,
|
|
1096
|
+
status: "idle",
|
|
1097
|
+
capabilities: [],
|
|
1098
|
+
});
|
|
1099
|
+
await updateAgentActivity(lead.id);
|
|
1100
|
+
await updateAgentActivity(worker.id);
|
|
1101
|
+
const oldPass = { ok: true, error: null, latency_ms: 12, testedAt: Date.now() - 6 * 3_600_000 };
|
|
1102
|
+
await seedCredStatus(lead.id, "claude", { ready: true, satisfiedBy: "env", liveTest: oldPass });
|
|
1103
|
+
await seedCredStatus(worker.id, "claude", {
|
|
1104
|
+
ready: true,
|
|
1105
|
+
satisfiedBy: "env",
|
|
1106
|
+
liveTest: oldPass,
|
|
1107
|
+
});
|
|
1108
|
+
|
|
1109
|
+
const payload = await buildStatusPayload();
|
|
1110
|
+
expect(getMilestone(payload, "workers").state).toBe("verified");
|
|
1111
|
+
expect(getMilestone(payload, "harness").state).toBe("verified");
|
|
1112
|
+
expect(payload.health).toBe("ok");
|
|
1113
|
+
});
|
|
1114
|
+
|
|
1115
|
+
test("`degraded` when the provider's latest live test failed, even after an older pass", async () => {
|
|
1116
|
+
const passed = await createAgent({
|
|
1117
|
+
name: "worker-pass",
|
|
1118
|
+
isLead: false,
|
|
1119
|
+
status: "idle",
|
|
1120
|
+
capabilities: [],
|
|
1121
|
+
});
|
|
1122
|
+
const failed = await createAgent({
|
|
1123
|
+
name: "worker-fail",
|
|
1124
|
+
isLead: false,
|
|
1125
|
+
status: "idle",
|
|
1126
|
+
capabilities: [],
|
|
1127
|
+
});
|
|
1128
|
+
await updateAgentActivity(passed.id);
|
|
1129
|
+
await updateAgentActivity(failed.id);
|
|
1130
|
+
await seedCredStatus(passed.id, "claude", {
|
|
1131
|
+
ready: true,
|
|
1132
|
+
satisfiedBy: "env",
|
|
1133
|
+
liveTest: { ok: true, error: null, latency_ms: 12, testedAt: Date.now() - 60_000 },
|
|
1134
|
+
});
|
|
1135
|
+
await seedCredStatus(failed.id, "claude", {
|
|
1136
|
+
ready: true,
|
|
1137
|
+
satisfiedBy: "env",
|
|
1138
|
+
liveTest: { ok: false, error: "HTTP 401", latency_ms: 12, testedAt: Date.now() },
|
|
1139
|
+
});
|
|
1140
|
+
|
|
1141
|
+
const payload = await buildStatusPayload();
|
|
1142
|
+
const harness = getMilestone(payload, "harness");
|
|
1143
|
+
expect(harness.state).toBe("configured");
|
|
1144
|
+
expect(harness.hint).toBe("claude: live test failed");
|
|
1145
|
+
expect(payload.health).toBe("degraded");
|
|
1146
|
+
});
|
|
1147
|
+
|
|
1148
|
+
test("an offline agent's newer failed live test does not override an online pass", async () => {
|
|
1149
|
+
const online = await createAgent({
|
|
1150
|
+
name: "worker-online",
|
|
1151
|
+
isLead: false,
|
|
1152
|
+
status: "idle",
|
|
1153
|
+
capabilities: [],
|
|
1154
|
+
});
|
|
1155
|
+
const offline = await createAgent({
|
|
1156
|
+
name: "worker-offline",
|
|
1157
|
+
isLead: false,
|
|
1158
|
+
status: "offline",
|
|
1159
|
+
capabilities: [],
|
|
1160
|
+
});
|
|
1161
|
+
await updateAgentActivity(online.id);
|
|
1162
|
+
await seedCredStatus(online.id, "claude", {
|
|
1163
|
+
ready: true,
|
|
1164
|
+
satisfiedBy: "env",
|
|
1165
|
+
liveTest: { ok: true, error: null, latency_ms: 12, testedAt: Date.now() - 6 * 3_600_000 },
|
|
1166
|
+
});
|
|
1167
|
+
await seedCredStatus(offline.id, "claude", {
|
|
1168
|
+
ready: true,
|
|
1169
|
+
satisfiedBy: "env",
|
|
1170
|
+
liveTest: { ok: false, error: "HTTP 401", latency_ms: 12, testedAt: Date.now() },
|
|
1171
|
+
});
|
|
1172
|
+
|
|
1173
|
+
expect(await rollupCredStatusForProvider("claude")).toMatchObject({
|
|
1174
|
+
state: "verified",
|
|
1175
|
+
workers: 1,
|
|
1176
|
+
reports: 1,
|
|
1177
|
+
});
|
|
1178
|
+
const harness = getMilestone(await buildStatusPayload(), "harness");
|
|
1179
|
+
expect(harness.state).toBe("verified");
|
|
1180
|
+
expect(harness.hint).toBeUndefined();
|
|
1181
|
+
});
|
|
1182
|
+
|
|
1183
|
+
test("a provider reported only by offline agents does not degrade the harness", async () => {
|
|
1184
|
+
const online = await createAgent({
|
|
1185
|
+
name: "claude-online",
|
|
1186
|
+
isLead: false,
|
|
1187
|
+
status: "idle",
|
|
1188
|
+
capabilities: [],
|
|
1189
|
+
});
|
|
1190
|
+
const offline = await createAgent({
|
|
1191
|
+
name: "codex-offline",
|
|
1192
|
+
isLead: false,
|
|
1193
|
+
status: "offline",
|
|
1194
|
+
capabilities: [],
|
|
1195
|
+
});
|
|
1196
|
+
await updateAgentActivity(online.id);
|
|
1197
|
+
await seedCredStatus(online.id, "claude", {
|
|
1198
|
+
ready: true,
|
|
1199
|
+
satisfiedBy: "env",
|
|
1200
|
+
liveTest: { ok: true, error: null, latency_ms: 12, testedAt: Date.now() },
|
|
1201
|
+
});
|
|
1202
|
+
await seedCredStatus(offline.id, "codex", {
|
|
1203
|
+
ready: false,
|
|
1204
|
+
missing: ["OPENAI_API_KEY"],
|
|
1205
|
+
satisfiedBy: null,
|
|
1206
|
+
liveTest: { ok: false, error: "HTTP 401", latency_ms: 12, testedAt: Date.now() },
|
|
1207
|
+
});
|
|
1208
|
+
|
|
1209
|
+
const harness = getMilestone(await buildStatusPayload(), "harness");
|
|
1210
|
+
expect(harness.state).toBe("verified");
|
|
1211
|
+
expect(harness.hint).toBeUndefined();
|
|
1212
|
+
expect((harness.providers ?? []).map((p) => p.provider)).toEqual(["claude"]);
|
|
1213
|
+
});
|
|
1214
|
+
|
|
1065
1215
|
test("`ok` when workers are `configured` (heartbeat drift is a runtime concern, not setup health)", async () => {
|
|
1066
1216
|
// Workers in `configured` state means agents exist but haven't posted a
|
|
1067
1217
|
// heartbeat in the last 5 minutes. This is surfaced on /agents and the
|
|
@@ -1166,9 +1316,8 @@ describe("worker-reported live test drives harness.state", () => {
|
|
|
1166
1316
|
expect(getMilestone(await buildStatusPayload(), "harness").state).toBe("verified");
|
|
1167
1317
|
});
|
|
1168
1318
|
|
|
1169
|
-
test("
|
|
1319
|
+
test("an old passing live test keeps harness `verified`", async () => {
|
|
1170
1320
|
process.env.HARNESS_PROVIDER = "claude";
|
|
1171
|
-
process.env.SWARM_VERIFY_TTL_MS = "1000"; // 1s — anything older is stale
|
|
1172
1321
|
const a = await createAgent({ name: "w-stl", isLead: false, status: "idle", capabilities: [] });
|
|
1173
1322
|
await seedCredStatus(a.id, "claude", {
|
|
1174
1323
|
ready: true,
|
|
@@ -1176,10 +1325,10 @@ describe("worker-reported live test drives harness.state", () => {
|
|
|
1176
1325
|
ok: true,
|
|
1177
1326
|
error: null,
|
|
1178
1327
|
latency_ms: 80,
|
|
1179
|
-
testedAt: Date.now() -
|
|
1328
|
+
testedAt: Date.now() - 24 * 3_600_000, // a day ago: workers only re-test on change
|
|
1180
1329
|
},
|
|
1181
1330
|
});
|
|
1182
|
-
expect(getMilestone(await buildStatusPayload(), "harness").state).toBe("
|
|
1331
|
+
expect(getMilestone(await buildStatusPayload(), "harness").state).toBe("verified");
|
|
1183
1332
|
});
|
|
1184
1333
|
|
|
1185
1334
|
test("a failed live test still leaves harness `configured` if presence is ready", async () => {
|
|
@@ -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,
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
2
|
+
import { unlink } from "node:fs/promises";
|
|
3
|
+
import {
|
|
4
|
+
closeDb,
|
|
5
|
+
createAgent,
|
|
6
|
+
createTaskExtended,
|
|
7
|
+
getDbClient,
|
|
8
|
+
getTaskLineage,
|
|
9
|
+
initDb,
|
|
10
|
+
TASK_LINEAGE_LIMITS,
|
|
11
|
+
} from "../be/db";
|
|
12
|
+
|
|
13
|
+
const TEST_DB_PATH = "./test-task-lineage.sqlite";
|
|
14
|
+
|
|
15
|
+
async function removeDb() {
|
|
16
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
17
|
+
try {
|
|
18
|
+
await unlink(`${TEST_DB_PATH}${suffix}`);
|
|
19
|
+
} catch {}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe("getTaskLineage", () => {
|
|
24
|
+
let agentId: string;
|
|
25
|
+
|
|
26
|
+
beforeAll(async () => {
|
|
27
|
+
await removeDb();
|
|
28
|
+
initDb(TEST_DB_PATH);
|
|
29
|
+
const agent = await createAgent({
|
|
30
|
+
id: crypto.randomUUID(),
|
|
31
|
+
name: "Lineage Test Agent",
|
|
32
|
+
isLead: false,
|
|
33
|
+
status: "idle",
|
|
34
|
+
});
|
|
35
|
+
agentId = agent.id;
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
/** A straight parentTaskId chain, root first. */
|
|
39
|
+
async function createChain(label: string, length: number) {
|
|
40
|
+
const chain = [await createTaskExtended(`${label} 0`, { agentId })];
|
|
41
|
+
for (let i = 1; i < length; i++) {
|
|
42
|
+
chain.push(
|
|
43
|
+
await createTaskExtended(`${label} ${i}`, { agentId, parentTaskId: chain[i - 1]!.id }),
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
return chain;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
afterAll(async () => {
|
|
50
|
+
closeDb();
|
|
51
|
+
await removeDb();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("unknown task: null", async () => {
|
|
55
|
+
expect(await getTaskLineage("no-such-task")).toBeNull();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("from a grandchild: walks up to the root, then the whole tree", async () => {
|
|
59
|
+
const root = await createTaskExtended("root", { agentId });
|
|
60
|
+
const childA = await createTaskExtended("child a", { agentId, parentTaskId: root.id });
|
|
61
|
+
const childB = await createTaskExtended("child b", { agentId, parentTaskId: root.id });
|
|
62
|
+
const grandchild = await createTaskExtended("grandchild", {
|
|
63
|
+
agentId,
|
|
64
|
+
parentTaskId: childA.id,
|
|
65
|
+
});
|
|
66
|
+
const greatGrandchild = await createTaskExtended("great-grandchild", {
|
|
67
|
+
agentId,
|
|
68
|
+
parentTaskId: grandchild.id,
|
|
69
|
+
});
|
|
70
|
+
const unrelated = await createTaskExtended("unrelated", { agentId });
|
|
71
|
+
|
|
72
|
+
const lineage = await getTaskLineage(grandchild.id);
|
|
73
|
+
expect(lineage).not.toBeNull();
|
|
74
|
+
expect(lineage!.rootTaskId).toBe(root.id);
|
|
75
|
+
expect(lineage!.truncated).toBe(false);
|
|
76
|
+
expect(lineage!.tasks[0]!.id).toBe(root.id);
|
|
77
|
+
const ids = new Set(lineage!.tasks.map((t) => t.id));
|
|
78
|
+
expect(ids).toEqual(
|
|
79
|
+
new Set([root.id, childA.id, childB.id, grandchild.id, greatGrandchild.id]),
|
|
80
|
+
);
|
|
81
|
+
expect(ids.has(unrelated.id)).toBe(false);
|
|
82
|
+
expect(lineage!.tasks.find((t) => t.id === childA.id)!.parentTaskId).toBe(root.id);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("a node cap keeps the path to the task and its own subtree, and says it cut", async () => {
|
|
86
|
+
const root = await createTaskExtended("wide root", { agentId });
|
|
87
|
+
const children = [];
|
|
88
|
+
for (let i = 0; i < 6; i++) {
|
|
89
|
+
children.push(await createTaskExtended(`fan-out ${i}`, { agentId, parentTaskId: root.id }));
|
|
90
|
+
}
|
|
91
|
+
const last = children[children.length - 1]!;
|
|
92
|
+
const leaf = await createTaskExtended("leaf under the last child", {
|
|
93
|
+
agentId,
|
|
94
|
+
parentTaskId: last.id,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const lineage = await getTaskLineage(last.id, { maxNodes: 3 });
|
|
98
|
+
expect(lineage!.truncated).toBe(true);
|
|
99
|
+
const ids = new Set(lineage!.tasks.map((t) => t.id));
|
|
100
|
+
expect(ids.has(root.id)).toBe(true);
|
|
101
|
+
expect(ids.has(last.id)).toBe(true);
|
|
102
|
+
expect(ids.has(leaf.id)).toBe(true);
|
|
103
|
+
expect(lineage!.tasks.length).toBeLessThanOrEqual(2 * 3 + 2);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("a depth cap stops the walk down", async () => {
|
|
107
|
+
const root = await createTaskExtended("deep root", { agentId });
|
|
108
|
+
let parent = root;
|
|
109
|
+
for (let i = 0; i < 5; i++) {
|
|
110
|
+
parent = await createTaskExtended(`level ${i + 1}`, { agentId, parentTaskId: parent.id });
|
|
111
|
+
}
|
|
112
|
+
const lineage = await getTaskLineage(root.id, { maxDepth: 2 });
|
|
113
|
+
expect(lineage!.tasks).toHaveLength(3);
|
|
114
|
+
expect(lineage!.truncated).toBe(true);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("a chain past the default depth cap says it cut", async () => {
|
|
118
|
+
const chain = await createChain("default-depth", 32);
|
|
119
|
+
const lineage = await getTaskLineage(chain[0]!.id);
|
|
120
|
+
expect(lineage!.tasks).toHaveLength(TASK_LINEAGE_LIMITS.maxDepth + 1);
|
|
121
|
+
expect(lineage!.truncated).toBe(true);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("a chain that ends exactly at the depth cap is complete", async () => {
|
|
125
|
+
const chain = await createChain("exact-depth", 3);
|
|
126
|
+
const lineage = await getTaskLineage(chain[0]!.id, { maxDepth: 2 });
|
|
127
|
+
expect(lineage!.tasks).toHaveLength(3);
|
|
128
|
+
expect(lineage!.truncated).toBe(false);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("every ancestor of the task survives a node cap of 1", async () => {
|
|
132
|
+
const chain = await createChain("path-node-cap", 5);
|
|
133
|
+
const leaf = chain[chain.length - 1]!;
|
|
134
|
+
const lineage = await getTaskLineage(leaf.id, { maxNodes: 1 });
|
|
135
|
+
expect(new Set(lineage!.tasks.map((t) => t.id))).toEqual(new Set(chain.map((t) => t.id)));
|
|
136
|
+
// Nothing in the tree is left out, so the result is complete.
|
|
137
|
+
expect(lineage!.truncated).toBe(false);
|
|
138
|
+
const ids = new Set(lineage!.tasks.map((t) => t.id));
|
|
139
|
+
for (const task of lineage!.tasks) {
|
|
140
|
+
if (task.id !== lineage!.rootTaskId) expect(ids.has(task.parentTaskId!)).toBe(true);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("a deep focused path survives both caps, and a cut sibling still flags", async () => {
|
|
145
|
+
const chain = await createChain("deep-focus", 8);
|
|
146
|
+
const sibling = await createTaskExtended("off-path sibling", {
|
|
147
|
+
agentId,
|
|
148
|
+
parentTaskId: chain[1]!.id,
|
|
149
|
+
});
|
|
150
|
+
const leaf = chain[chain.length - 1]!;
|
|
151
|
+
const lineage = await getTaskLineage(leaf.id, { maxNodes: 1, maxDepth: 1 });
|
|
152
|
+
const ids = new Set(lineage!.tasks.map((t) => t.id));
|
|
153
|
+
for (const task of chain) expect(ids.has(task.id)).toBe(true);
|
|
154
|
+
expect(ids.has(sibling.id)).toBe(false);
|
|
155
|
+
expect(lineage!.truncated).toBe(true);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test("a fan-out exactly at the node cap is complete; one more child cuts", async () => {
|
|
159
|
+
const root = await createTaskExtended("cap root", { agentId });
|
|
160
|
+
for (let i = 0; i < 3; i++) {
|
|
161
|
+
await createTaskExtended(`cap child ${i}`, { agentId, parentTaskId: root.id });
|
|
162
|
+
}
|
|
163
|
+
const atCap = await getTaskLineage(root.id, { maxNodes: 4 });
|
|
164
|
+
expect(atCap!.tasks).toHaveLength(4);
|
|
165
|
+
expect(atCap!.truncated).toBe(false);
|
|
166
|
+
const overCap = await getTaskLineage(root.id, { maxNodes: 3 });
|
|
167
|
+
expect(overCap!.tasks).toHaveLength(3);
|
|
168
|
+
expect(overCap!.truncated).toBe(true);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("a parentTaskId cycle ends at the ancestor cap instead of looping", async () => {
|
|
172
|
+
const a = await createTaskExtended("cycle a", { agentId });
|
|
173
|
+
const b = await createTaskExtended("cycle b", { agentId, parentTaskId: a.id });
|
|
174
|
+
await getDbClient().run("UPDATE agent_tasks SET parentTaskId = ? WHERE id = ?", [b.id, a.id]);
|
|
175
|
+
|
|
176
|
+
const lineage = await getTaskLineage(a.id, { maxAncestors: 5, maxDepth: 4 });
|
|
177
|
+
expect(lineage!.truncated).toBe(true);
|
|
178
|
+
expect(new Set(lineage!.tasks.map((t) => t.id))).toEqual(new Set([a.id, b.id]));
|
|
179
|
+
});
|
|
180
|
+
});
|