@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.
Files changed (239) hide show
  1. package/README.md +1 -1
  2. package/dist/{acp-adapter-kedstxx6.js → acp-adapter-dv7hmx4r.js} +9 -9
  3. package/dist/{actions-vget6r1k.js → actions-51nc1gny.js} +21 -21
  4. package/dist/{amp-adapter-xeb5mktv.js → amp-adapter-abp5tdx8.js} +4 -4
  5. package/dist/{app-mqap8t8m.js → app-9h14qvr6.js} +3 -3
  6. package/dist/{assistant-h0gv97mv.js → assistant-wv1gz3mt.js} +22 -22
  7. package/dist/{boot-reembed-qr8a894z.js → boot-reembed-6yn53ms9.js} +1 -1
  8. package/dist/{boot-reembed-rj85jjhw.js → boot-reembed-7fcka4z5.js} +1 -1
  9. package/dist/{boot-scrub-sweep-eyfvn6v9.js → boot-scrub-sweep-pm1580ax.js} +4 -4
  10. package/dist/{boot-seeds-xs9a8dx1.js → boot-seeds-g5hfc1x8.js} +4 -4
  11. package/dist/{claude-adapter-bm23cye7.js → claude-adapter-gf3kxhf0.js} +9 -9
  12. package/dist/{claude-managed-adapter-1mwrsm9d.js → claude-managed-adapter-h2y889ge.js} +2 -2
  13. package/dist/{claude-managed-setup-gkfgks4j.js → claude-managed-setup-h00hsgtz.js} +2 -2
  14. package/dist/{claude-sdk-session-apb4ypz3.js → claude-sdk-session-s2k1qd8y.js} +13 -13
  15. package/dist/{cli-tj4j2ts4.js → cli-02qm7src.js} +8 -8
  16. package/dist/{cli-1cevz46c.js → cli-0qvtffe9.js} +6 -6
  17. package/dist/{cli-avmqhatn.js → cli-10hhg0km.js} +8 -8
  18. package/dist/{cli-7gh7020n.js → cli-1dmcq2eh.js} +1 -1
  19. package/dist/{cli-kz116kvw.js → cli-239yjk27.js} +7 -7
  20. package/dist/{cli-7ayprxp7.js → cli-2dfbv8qr.js} +8 -0
  21. package/dist/{cli-a49vpbf3.js → cli-5kqnemen.js} +1 -1
  22. package/dist/{cli-x719j3vs.js → cli-5rk36sjf.js} +442 -205
  23. package/dist/{cli-fb6t92v8.js → cli-6x9ma5w5.js} +1 -1
  24. package/dist/{cli-c8fwpgqb.js → cli-7225gwj7.js} +2 -2
  25. package/dist/{cli-5s5hx57y.js → cli-7awf1fp8.js} +1 -1
  26. package/dist/{cli-bn26xbcs.js → cli-8nyhf7za.js} +1 -1
  27. package/dist/{cli-ywyrffff.js → cli-8sxqsbbq.js} +1 -1
  28. package/dist/{cli-rsd7ga7v.js → cli-8zwe9mpk.js} +11 -11
  29. package/dist/{cli-y83zk5sb.js → cli-a0kcty51.js} +4 -4
  30. package/dist/{cli-8b9wvjps.js → cli-a4gvwttf.js} +4 -4
  31. package/dist/{cli-jhp7w1fy.js → cli-a5ebc95n.js} +2 -2
  32. package/dist/{cli-mvg2hf8p.js → cli-ahtpy9kf.js} +14 -7
  33. package/dist/{cli-x1p5dhq2.js → cli-bcj753sq.js} +3 -3
  34. package/dist/{cli-p22zdvnt.js → cli-bn0rewsy.js} +3 -3
  35. package/dist/{cli-p1m2b6gc.js → cli-d2mb5s8a.js} +7 -7
  36. package/dist/{cli-rg7ntypm.js → cli-d3qqdy4c.js} +456 -94
  37. package/dist/{cli-c5sbmw1v.js → cli-d82btycs.js} +94 -15
  38. package/dist/{cli-02vhhssk.js → cli-efaqv1c7.js} +5 -5
  39. package/dist/{cli-vjfpshn3.js → cli-ejs3m1sx.js} +1 -1
  40. package/dist/{cli-kk2r80n5.js → cli-faypxz7q.js} +4 -4
  41. package/dist/{cli-a1esmmee.js → cli-fpmsqq59.js} +7 -2
  42. package/dist/{cli-wrv09v7q.js → cli-ftyypbg5.js} +1 -1
  43. package/dist/{cli-zvze9rbm.js → cli-fyzjmseb.js} +3 -3
  44. package/dist/{cli-8rwvksbc.js → cli-g0hgtz4p.js} +5 -5
  45. package/dist/{cli-nrrg0fvy.js → cli-h38rjv87.js} +133 -12
  46. package/dist/{cli-tg9ksbn8.js → cli-h93c9vb3.js} +2 -2
  47. package/dist/{cli-eqgfs202.js → cli-hqhzg1qq.js} +2 -2
  48. package/dist/{cli-bhwa8rzc.js → cli-hyfw2j1e.js} +13 -12
  49. package/dist/{cli-3wjs22x3.js → cli-j4rqdf2n.js} +2 -2
  50. package/dist/{cli-6ftrrwm1.js → cli-n85dqg6d.js} +5 -5
  51. package/dist/{cli-8cxkw1f5.js → cli-nmqdc2t3.js} +2 -2
  52. package/dist/{cli-4fhyq711.js → cli-nn47d7e8.js} +13 -13
  53. package/dist/{cli-x3en6ct3.js → cli-nwyw3zhg.js} +1 -1
  54. package/dist/{cli-3avfh5az.js → cli-pemywhfq.js} +2 -2
  55. package/dist/{cli-xfe63zx3.js → cli-pkht5hap.js} +2 -2
  56. package/dist/{cli-sppc0cmw.js → cli-pnp2j5zg.js} +2 -2
  57. package/dist/{cli-wk8ccrz6.js → cli-pw7v69vp.js} +2 -2
  58. package/dist/{cli-tgvpmnwz.js → cli-r99kc9yq.js} +5 -5
  59. package/dist/{cli-bfc3k1x5.js → cli-rdyw3mct.js} +9 -9
  60. package/dist/{cli-b80a1100.js → cli-s6vr7r2d.js} +2 -2
  61. package/dist/{cli-zv4vy2b0.js → cli-s95dknxh.js} +1 -1
  62. package/dist/{cli-ttaz6c7m.js → cli-saw9a6f6.js} +6 -5
  63. package/dist/{cli-mz1ezzzh.js → cli-shvfedvm.js} +3 -3
  64. package/dist/{cli-rdr166ht.js → cli-smvcw9z5.js} +1 -1
  65. package/dist/{cli-2rwet3r7.js → cli-sv5gb0yb.js} +1 -1
  66. package/dist/{cli-5ff2sjyt.js → cli-t30yj95q.js} +2 -2
  67. package/dist/{cli-ptkpaqfy.js → cli-wt645v7q.js} +1 -1
  68. package/dist/{cli-f3nx1h8z.js → cli-wxbn3yfx.js} +2 -2
  69. package/dist/{cli-wyrr38xm.js → cli-x5mq1fyq.js} +1 -1
  70. package/dist/{cli-ccn0eybf.js → cli-xbzvet96.js} +6 -6
  71. package/dist/{cli-4mkvwgzr.js → cli-xf234ws8.js} +1 -1
  72. package/dist/{cli-nme7wery.js → cli-y2x52gs2.js} +46 -13
  73. package/dist/{cli-ffbrkh1r.js → cli-y8n5k515.js} +46 -38
  74. package/dist/{cli-ecshf504.js → cli-z4wd2ftv.js} +7 -7
  75. package/dist/{cli-dsgj3e7a.js → cli-zfg9enh9.js} +2 -2
  76. package/dist/{cli-yxqag76b.js → cli-zw88zjba.js} +1 -1
  77. package/dist/cli.js +20 -20
  78. package/dist/{codex-adapter-pp2gq8gr.js → codex-adapter-ttygw382.js} +6 -6
  79. package/dist/{codex-hook-qwzccd64.js → codex-hook-x7nejyrr.js} +7 -4
  80. package/dist/{codex-login-35p4sk85.js → codex-login-m819c41r.js} +1 -1
  81. package/dist/{codex-session-runner-0vzn09t8.js → codex-session-runner-60b58vv3.js} +7 -7
  82. package/dist/{commands-psqhkttb.js → commands-edmdwa4x.js} +2 -2
  83. package/dist/{controller-t20vz2k8.js → controller-m263fg43.js} +4 -4
  84. package/dist/{controllers-connect-gkxmqw9d.js → controllers-connect-5fym8sw1.js} +23 -23
  85. package/dist/{cursor-adapter-3v542qe5.js → cursor-adapter-cz6nm6mj.js} +3 -3
  86. package/dist/{db-nc1rd869.js → db-2y0mgstc.js} +3 -3
  87. package/dist/{db-2xbyenjr.js → db-nn0hcmx5.js} +6 -2
  88. package/dist/{devin-adapter-7egzmt0k.js → devin-adapter-zgqadvnm.js} +1 -1
  89. package/dist/{dispatcher-ewceb8ts.js → dispatcher-0a9wwjcx.js} +20 -20
  90. package/dist/{docker-njavd3fb.js → docker-fkgtr75t.js} +2 -2
  91. package/dist/{dsh-adapter-75xwh78t.js → dsh-adapter-3n3net33.js} +3 -3
  92. package/dist/{e2b-5r6a5jeb.js → e2b-7am6tmq6.js} +2 -2
  93. package/dist/{e2b-pw1nvtmh.js → e2b-thdcxtjd.js} +2 -2
  94. package/dist/{easter-egg-3d-xsc47jwf.js → easter-egg-3d-5cgynaja.js} +1 -1
  95. package/dist/{event-bus-101fenkh.js → event-bus-9vpk8gmq.js} +1 -1
  96. package/dist/{execute-gzmw1mnw.js → execute-t4e6m3rw.js} +1 -1
  97. package/dist/{external-channel-a5dsmxn4.js → external-channel-zhvt95n3.js} +2 -2
  98. package/dist/{grok-adapter-vj88xr23.js → grok-adapter-qnr1hp2y.js} +10 -10
  99. package/dist/handlers-dkcarkd0.js +39 -0
  100. package/dist/{heartbeat-t2m9r3wa.js → heartbeat-gz97r7ac.js} +23 -23
  101. package/dist/{hook-xa677f8r.js → hook-1mmdgjqs.js} +16 -9
  102. package/dist/{hook-8femx1bg.js → hook-x8rthca4.js} +6 -6
  103. package/dist/{http-xpmmq028.js → http-r8f85xr9.js} +274 -160
  104. package/dist/index-5jecgp3n.js +41 -0
  105. package/dist/index-aex0eba9.js +40 -0
  106. package/dist/{index-syztqdt6.js → index-g6tbb6ve.js} +1 -1
  107. package/dist/{index-cdzyfrsp.js → index-smxyk04s.js} +1 -1
  108. package/dist/{index-dg2tmj99.js → index-vzvfqzfj.js} +20 -20
  109. package/dist/{index-s7ckdajq.js → index-y7rbcrc1.js} +10 -10
  110. package/dist/{keepalive-3nr4st3r.js → keepalive-64c0f34c.js} +4 -4
  111. package/dist/lead-b1da0f97.js +78 -0
  112. package/dist/{maintenance-verwpe2c.js → maintenance-mnxpar12.js} +1 -1
  113. package/dist/{oauth-refresh-sweep-9q2t0y3q.js → oauth-refresh-sweep-36hs2v4h.js} +3 -3
  114. package/dist/{onboard-aytqedmq.js → onboard-xes8de61.js} +3 -3
  115. package/dist/{opencode-adapter-f4hryjny.js → opencode-adapter-3s7ax02e.js} +7 -7
  116. package/dist/{otel-impl-hckw4wt1.js → otel-impl-fzc57kcq.js} +2 -2
  117. package/dist/{pi-mono-adapter-hxby0ce1.js → pi-mono-adapter-ytp7gz8b.js} +22 -11
  118. package/dist/{pricing-refresh-5nvme3sm.js → pricing-refresh-1nek2w7b.js} +3 -3
  119. package/dist/{rbac-roles-vbpy21mg.js → rbac-roles-sqefb5hw.js} +2 -2
  120. package/dist/{rbac-roles-pd8c26m1.js → rbac-roles-ttyfaep2.js} +1 -1
  121. package/dist/{render-v2-7nqgvk2q.js → render-v2-vb527jwn.js} +3 -3
  122. package/dist/{resume-b6djcj3c.js → resume-jcdzg1bn.js} +10 -8
  123. package/dist/{runtime-txrxc9za.js → runtime-xe3aqy4x.js} +1 -1
  124. package/dist/{session-access-fc3f9y12.js → session-access-zsp4wsp0.js} +4 -4
  125. package/dist/{setup-n39szm6b.js → setup-1nb2zqd3.js} +1 -1
  126. package/dist/{virtual-modules-wyp2k6z3.js → virtual-modules-4r3zkc20.js} +2 -2
  127. package/dist/{work-objects-jem2fre0.js → work-objects-61w08fwr.js} +23 -23
  128. package/dist/worker-c1e68hpa.js +78 -0
  129. package/dist/{x-8ewt8gxn.js → x-y45mthwn.js} +2 -2
  130. package/openapi.json +491 -52
  131. package/package.json +3 -3
  132. package/src/apps/action-runner.ts +146 -0
  133. package/src/apps/catalog.generated.json +3 -2
  134. package/src/apps/definition.ts +152 -4
  135. package/src/apps/row-store.ts +23 -9
  136. package/src/apps/schema-migrate.ts +14 -6
  137. package/src/apps/script-types.ts +2 -1
  138. package/src/apps/sync.ts +20 -19
  139. package/src/be/db/tasks/read.ts +112 -0
  140. package/src/be/db.ts +16 -4
  141. package/src/be/fleet-benchmark-stats.ts +10 -5
  142. package/src/be/models-catalog.ts +4 -0
  143. package/src/be/onboarding.ts +6 -0
  144. package/src/be/scripts/typecheck.ts +13 -0
  145. package/src/be/swarm-config-guard.ts +25 -1
  146. package/src/be/telemetry-enable.ts +1 -1
  147. package/src/commands/provider-credentials.ts +12 -2
  148. package/src/commands/runner.ts +3 -0
  149. package/src/heartbeat/heartbeat.ts +2 -2
  150. package/src/hooks/codex-hook.ts +6 -2
  151. package/src/hooks/hook.ts +7 -0
  152. package/src/hooks/pr-body-guard.ts +38 -11
  153. package/src/hooks/pr-room-guard.ts +88 -0
  154. package/src/http/agents.ts +1 -0
  155. package/src/http/apps.ts +99 -183
  156. package/src/http/fleet-benchmark.ts +79 -30
  157. package/src/http/global-config.ts +7 -0
  158. package/src/http/ranking.ts +24 -40
  159. package/src/http/status.ts +49 -43
  160. package/src/http/tasks.ts +49 -1
  161. package/src/http/telemetry-ui.ts +1 -1
  162. package/src/http/used-by.ts +55 -19
  163. package/src/http/users.ts +2 -2
  164. package/src/providers/acp-targets.ts +1 -1
  165. package/src/providers/amp-adapter.ts +1 -1
  166. package/src/providers/pi-mono-adapter.ts +15 -0
  167. package/src/providers/reasoning-effort.ts +2 -1
  168. package/src/providers/types.ts +2 -0
  169. package/src/ranking/telemetry-ranking.ts +5 -33
  170. package/src/scripts-runtime/sdk-allowlist.ts +7 -0
  171. package/src/scripts-runtime/types/stdlib.d.ts +31 -0
  172. package/src/scripts-runtime/types/swarm-sdk.d.ts +31 -0
  173. package/src/server.ts +4 -0
  174. package/src/telemetry-context.ts +19 -1
  175. package/src/telemetry-contract/EVENTS.md +42 -8
  176. package/src/telemetry-contract/SOURCE.json +6 -5
  177. package/src/telemetry-contract/catalog.json +225 -5
  178. package/src/telemetry-contract/ranking/fixtures/valid/no-claim.json +10 -0
  179. package/src/telemetry-contract/ranking/listing.v1.schema.json +2 -2
  180. package/src/telemetry-contract/types.gen.ts +225 -27
  181. package/src/telemetry-identity.ts +47 -9
  182. package/src/telemetry-snapshot.ts +9 -1
  183. package/src/telemetry-ui-events.ts +9 -2
  184. package/src/telemetry.ts +29 -6
  185. package/src/tests/app-action-run-tool.test.ts +178 -0
  186. package/src/tests/app-row-tools.test.ts +385 -0
  187. package/src/tests/apps-json-column.test.ts +611 -0
  188. package/src/tests/apps-script-types.test.ts +2 -0
  189. package/src/tests/apps-sync-engine.test.ts +42 -0
  190. package/src/tests/check-pr-body.test.ts +0 -23
  191. package/src/tests/credential-check.test.ts +38 -0
  192. package/src/tests/db-task-read-contract.test.ts +4 -0
  193. package/src/tests/db-task-write-contract.test.ts +2 -0
  194. package/src/tests/docs-index-footprint.test.js +2 -2
  195. package/src/tests/fleet-benchmark.test.ts +122 -2
  196. package/src/tests/http-users.test.ts +62 -1
  197. package/src/tests/model-catalog-table.test.ts +63 -0
  198. package/src/tests/model-profiles-api.test.ts +19 -7
  199. package/src/tests/models-catalog.test.ts +50 -0
  200. package/src/tests/onboarding.test.ts +21 -0
  201. package/src/tests/org-website.test.ts +62 -0
  202. package/src/tests/pi-mono-adapter.test.ts +23 -0
  203. package/src/tests/pr-body-leaks.test.ts +4 -0
  204. package/src/tests/pr-room-guard.test.ts +242 -0
  205. package/src/tests/ranking-listing.test.ts +8 -23
  206. package/src/tests/ranking-route.test.ts +49 -47
  207. package/src/tests/reasoning-effort.test.ts +39 -3
  208. package/src/tests/runner-harness-telemetry-env.test.ts +62 -1
  209. package/src/tests/status.test.ts +158 -9
  210. package/src/tests/swarm-tool-result-gate.test.ts +2 -4
  211. package/src/tests/task-lineage.test.ts +180 -0
  212. package/src/tests/telemetry-context.test.ts +128 -2
  213. package/src/tests/telemetry-init.test.ts +62 -5
  214. package/src/tests/telemetry-ui-route.test.ts +123 -0
  215. package/src/tests/tool-annotations.test.ts +3 -2
  216. package/src/tests/used-by-route.test.ts +144 -10
  217. package/src/tests/user-token-rest-auth.test.ts +38 -0
  218. package/src/tests/workflow-agent-task-retry.test.ts +130 -4
  219. package/src/tests/workflow-wait-recovery.test.ts +116 -4
  220. package/src/tools/app-action-run.ts +63 -0
  221. package/src/tools/app-rows.ts +239 -0
  222. package/src/tools/model-catalog/index.ts +1 -1
  223. package/src/tools/tool-config.ts +8 -0
  224. package/src/tools/utils.ts +1 -1
  225. package/src/tools/workflows/retry-workflow-run.ts +1 -1
  226. package/src/types.ts +17 -0
  227. package/src/utils/company-profile.ts +24 -0
  228. package/src/utils/org-website.ts +58 -21
  229. package/src/workflows/recovery.ts +10 -22
  230. package/src/workflows/resume.ts +16 -0
  231. package/src/workflows/task-step-routing.ts +47 -7
  232. package/templates/skills/apps/SKILL.md +44 -4
  233. package/templates/skills/apps/content.md +44 -4
  234. package/dist/handlers-zbs90bny.js +0 -39
  235. package/dist/index-8pwc1jc8.js +0 -40
  236. package/dist/index-nxa6me0q.js +0 -41
  237. package/dist/lead-td8h2sfn.js +0 -78
  238. package/dist/worker-wr6xgfr2.js +0 -78
  239. 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
- { harness: "pi", model: "openrouter/deepseek/deepseek-v4.1-flash", levels: ["low", "high"] },
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
- levels: ["low", "high"],
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",
@@ -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 fresh passing report in verifiedWorkers", async () => {
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 fresh passing live test", async () => {
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
- expect(m.hint).toContain("claude");
427
- expect(m.hint).toContain("codex");
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("a stale live test (older than SWARM_VERIFY_TTL_MS) drops to `configured`", async () => {
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() - 60_000, // 60s ago, well beyond TTL
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("configured");
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 the callable surface only when one exists", async () => {
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
+ });