@desplega.ai/agent-swarm 1.173.0 → 2.1.0

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 (511) hide show
  1. package/README.md +1 -1
  2. package/dist/{acp-adapter-ccm9xrep.js → acp-adapter-kr4abrsk.js} +17 -16
  3. package/dist/{actions-sgeyv08k.js → actions-eh4g6w2r.js} +39 -34
  4. package/dist/{amp-adapter-gpxp3kc9.js → amp-adapter-7h7wdt8q.js} +8 -7
  5. package/dist/{app-km9qnv2f.js → app-7k8s9w5p.js} +18 -14
  6. package/dist/{assistant-5r8tcvvd.js → assistant-wswt2y7p.js} +40 -35
  7. package/dist/{boot-reembed-16rv9843.js → boot-reembed-9apm9qw6.js} +10 -9
  8. package/dist/{boot-reembed-swwzdgd7.js → boot-reembed-whzbdthj.js} +10 -9
  9. package/dist/{boot-scrub-sweep-zkr6vkqb.js → boot-scrub-sweep-ndzmvmgt.js} +11 -10
  10. package/dist/{boot-seeds-d8sp49t2.js → boot-seeds-e9sta0yt.js} +16 -13
  11. package/dist/{claude-adapter-yzzzh8jp.js → claude-adapter-4mqd3r6r.js} +19 -18
  12. package/dist/{claude-managed-adapter-xgxhqrt2.js → claude-managed-adapter-9wswb52m.js} +7 -5
  13. package/dist/{claude-managed-setup-bnmjarjc.js → claude-managed-setup-tpe1effx.js} +6 -4
  14. package/dist/{claude-sdk-session-4whxere4.js → claude-sdk-session-q49ghmh9.js} +12402 -11850
  15. package/dist/{cli-ne7smj1b.js → cli-01xvj1rq.js} +10 -10
  16. package/dist/{cli-nwrdx840.js → cli-035vekcw.js} +3 -3
  17. package/dist/{cli-9xf7fdtp.js → cli-0pd8hzhc.js} +29 -6
  18. package/dist/{cli-tmhsxhb8.js → cli-0veb92a3.js} +28 -6
  19. package/dist/{cli-c3rk1v0x.js → cli-10jesr25.js} +3 -3
  20. package/dist/{cli-6thk4jz3.js → cli-1y7yywmj.js} +6 -6
  21. package/dist/{cli-6r2f7edk.js → cli-20krdhz5.js} +47 -19
  22. package/dist/{cli-rfs4vddv.js → cli-29rja109.js} +39 -20
  23. package/dist/cli-2m1ktfzz.js +17795 -0
  24. package/dist/{cli-na2pxe4f.js → cli-2pwa0q4d.js} +15 -4
  25. package/dist/{cli-7a93cv8c.js → cli-4mengw46.js} +441 -43
  26. package/dist/{cli-94b48jhk.js → cli-5twdke5x.js} +16 -14
  27. package/dist/{cli-7a0sde2x.js → cli-6cagzc96.js} +9 -0
  28. package/dist/cli-6cvfxs8r.js +47 -0
  29. package/dist/{cli-w1apjecb.js → cli-711vwbxn.js} +2 -2
  30. package/dist/{cli-r4fqfpwf.js → cli-78vv7446.js} +50 -27
  31. package/dist/{cli-srqq3gc5.js → cli-7n6s5man.js} +5 -5
  32. package/dist/{cli-m89t9jdf.js → cli-7x0g4sn1.js} +15416 -16024
  33. package/dist/{cli-2bw3w47j.js → cli-8wwxw8a3.js} +1 -1
  34. package/dist/{cli-yatxqynm.js → cli-9ha6hs2d.js} +1 -290
  35. package/dist/{cli-ywm1azg9.js → cli-9jzdj90z.js} +8 -4
  36. package/dist/cli-9traj3dn.js +638 -0
  37. package/dist/{cli-smcrrmhy.js → cli-agzebbd5.js} +9 -9
  38. package/dist/{cli-c5km5znr.js → cli-c8r3kzzb.js} +1 -1
  39. package/dist/cli-cvv9k0hh.js +10143 -0
  40. package/dist/{cli-anzdk0z4.js → cli-d9286m9h.js} +2 -2
  41. package/dist/cli-e3g5mbkz.js +776 -0
  42. package/dist/cli-f527ce74.js +781 -0
  43. package/dist/{cli-gmdajfw5.js → cli-gxzkg1de.js} +5020 -1729
  44. package/dist/{cli-451ks6aw.js → cli-h1arqn31.js} +1 -1
  45. package/dist/cli-hjwmhcg1.js +10622 -0
  46. package/dist/{cli-jpc4r224.js → cli-hqr88ew6.js} +19 -3
  47. package/dist/{cli-h7k0xtry.js → cli-hvqbtna4.js} +4 -1256
  48. package/dist/{cli-cd0hv9nh.js → cli-j67mh2vg.js} +2 -2
  49. package/dist/cli-jta2eyzc.js +619 -0
  50. package/dist/{cli-wd0hz6ah.js → cli-kbrt8gqa.js} +9 -91
  51. package/dist/cli-kceah1eh.js +92 -0
  52. package/dist/{cli-d603qf86.js → cli-ksphgh48.js} +10 -8
  53. package/dist/{cli-3nxdmjrc.js → cli-mgz4nbgg.js} +4 -4
  54. package/dist/{cli-saqyage0.js → cli-mz5as5ha.js} +179 -27
  55. package/dist/cli-n2wq4j7m.js +6985 -0
  56. package/dist/{cli-ppy4nnnh.js → cli-n8ndw8j7.js} +2 -2
  57. package/dist/{cli-1w0vj3gw.js → cli-naqzr17j.js} +7 -7
  58. package/dist/{cli-0a7mm3zp.js → cli-nm1p8jfr.js} +25 -21
  59. package/dist/{cli-wrh08zjd.js → cli-p3zbc1rw.js} +5 -18
  60. package/dist/{cli-hsem16hb.js → cli-pe05f8yk.js} +29 -6
  61. package/dist/{cli-5wpw4rab.js → cli-qmzsbkvy.js} +5 -5
  62. package/dist/{cli-bv2etw2y.js → cli-qnm4e9e4.js} +99 -590
  63. package/dist/{cli-1x7da0de.js → cli-qqy4q48b.js} +8 -8
  64. package/dist/{cli-r44t9wqc.js → cli-r106725k.js} +2 -2
  65. package/dist/{cli-5er5gjbm.js → cli-r35rw5de.js} +15 -15
  66. package/dist/{cli-v3zhnxsb.js → cli-r610p9bs.js} +2 -2
  67. package/dist/{cli-7jykv7ef.js → cli-rc6pf5kv.js} +1 -1
  68. package/dist/{cli-x119kdba.js → cli-rdxm77k3.js} +1 -1
  69. package/dist/cli-rhs9pz33.js +294 -0
  70. package/dist/{cli-0p3enk35.js → cli-s69nfyeh.js} +9 -1
  71. package/dist/{cli-0kxf81jj.js → cli-sawv63e8.js} +2 -2
  72. package/dist/{cli-e3pmyt39.js → cli-sg19gbf0.js} +1 -1
  73. package/dist/{cli-d161g69q.js → cli-shk7r5cs.js} +57 -6
  74. package/dist/{cli-7pz3b7x5.js → cli-sksetb2q.js} +36 -1
  75. package/dist/{cli-014vns2y.js → cli-stfs0k5s.js} +208 -525
  76. package/dist/{cli-db3m6zdt.js → cli-t02z59f2.js} +1 -1
  77. package/dist/{cli-b07hr3bt.js → cli-txqb9s38.js} +2 -2
  78. package/dist/{cli-s8z6x4b4.js → cli-v7g551te.js} +225 -113
  79. package/dist/{cli-rfz2ve55.js → cli-vgqzp4em.js} +3341 -955
  80. package/dist/{cli-7avy4v8w.js → cli-w1q7h2r2.js} +5 -3
  81. package/dist/{cli-rcp5aqdw.js → cli-w7m81tb7.js} +1 -1
  82. package/dist/cli-wfnemr1t.js +86 -0
  83. package/dist/cli-wmgrkscj.js +23254 -0
  84. package/dist/{cli-qm1448f9.js → cli-wms6x9db.js} +8 -8
  85. package/dist/{cli-tadfgd0t.js → cli-x1r545x8.js} +33 -4
  86. package/dist/{cli-xc7p7s3g.js → cli-xczsgshn.js} +6 -6
  87. package/dist/{cli-fmhj1ehj.js → cli-y3167wt1.js} +882 -271
  88. package/dist/{cli-r0byd61h.js → cli-yjmqpxt3.js} +17 -16
  89. package/dist/{cli-81r1wjyv.js → cli-z780ec7c.js} +1 -1
  90. package/dist/{cli-phcqp1b9.js → cli-zm9t2vde.js} +2 -2
  91. package/dist/cli.js +34 -49
  92. package/dist/{codex-adapter-r3dn4fp5.js → codex-adapter-4z16v9n0.js} +15 -14
  93. package/dist/{codex-login-841kv5fk.js → codex-login-pkepy2j1.js} +3 -2
  94. package/dist/{codex-session-runner-fwt793h4.js → codex-session-runner-rvms9y2x.js} +15 -14
  95. package/dist/{commands-z8fq9qna.js → commands-2a9rtyat.js} +11 -10
  96. package/dist/{controller-hq5ggpcp.js → controller-3k3madbn.js} +5 -4
  97. package/dist/{controllers-connect-3mf2btyj.js → controllers-connect-v9yn8zb9.js} +42 -37
  98. package/dist/{cursor-adapter-q8pgv5qa.js → cursor-adapter-98gbhyzn.js} +7 -6
  99. package/dist/{db-qfpq4bt7.js → db-eywwsnjw.js} +30 -9
  100. package/dist/{db-yyhqqrbf.js → db-ncypk9np.js} +12 -11
  101. package/dist/{devin-adapter-xgtj680j.js → devin-adapter-zeq8b9gz.js} +3 -2
  102. package/dist/{dispatcher-x0x7qfcq.js → dispatcher-w94kc575.js} +38 -33
  103. package/dist/{docker-f503k9p0.js → docker-b1e4cqab.js} +3 -2
  104. package/dist/{dsh-adapter-34etvb5m.js → dsh-adapter-47ztej36.js} +7 -6
  105. package/dist/{e2b-66xra4az.js → e2b-bbt8ey45.js} +4 -3
  106. package/dist/{e2b-xwhyj8cw.js → e2b-dp804sen.js} +4 -3
  107. package/dist/{event-bus-vjtv82gm.js → event-bus-j2jdg3qb.js} +3 -2
  108. package/dist/{external-channel-h1fjecwe.js → external-channel-zrkg8ar9.js} +11 -10
  109. package/dist/{google-generative-ai-szdxbpqg.js → google-generative-ai-g8db7x0f.js} +4 -2
  110. package/dist/{google-vertex-qzw3p36d.js → google-vertex-r2692q0r.js} +4 -2
  111. package/dist/{grok-adapter-e7mr287e.js → grok-adapter-s9ncp9vy.js} +18 -17
  112. package/dist/{handlers-znb9n7d4.js → handlers-kqftvjgw.js} +41 -36
  113. package/dist/{heartbeat-bm4qkn8d.js → heartbeat-9q96p8v9.js} +43 -38
  114. package/dist/{hook-254r4mza.js → hook-56qe10dp.js} +11 -23
  115. package/dist/{hook-xaa3q80s.js → hook-yy6rw42k.js} +12 -11
  116. package/dist/{http-3dgrgp8y.js → http-r2dnb8f8.js} +2411 -275
  117. package/dist/{index-fx3jqq1e.js → index-3gtmkhc6.js} +38 -33
  118. package/dist/{index-bdsqa799.js → index-3k5hkz7r.js} +43 -38
  119. package/dist/index-52j1aja4.js +3465 -0
  120. package/dist/{index-g2byveez.js → index-5e101240.js} +38 -33
  121. package/dist/{index-2ybk7khy.js → index-5wyfjc2c.js} +845 -683
  122. package/dist/index-9gw58n4k.js +193 -0
  123. package/dist/index-9j87mcm8.js +56305 -0
  124. package/dist/index-bsmcresk.js +79424 -0
  125. package/dist/index-mh85qhec.js +475 -0
  126. package/dist/{index-jd7sejm2.js → index-vxvhxxbn.js} +10 -9
  127. package/dist/index-we27kd31.js +12810 -0
  128. package/dist/{keepalive-6ywhpja1.js → keepalive-psj6j54c.js} +12 -11
  129. package/dist/{lead-cw45n21s.js → lead-hajz4688.js} +61 -56
  130. package/dist/{maintenance-ss1a1ge9.js → maintenance-ps02sb5m.js} +10 -9
  131. package/dist/{node-fkajaq96.js → node-mwb5r697.js} +2 -1
  132. package/dist/{oauth-refresh-sweep-3c6f8vyy.js → oauth-refresh-sweep-0284h93b.js} +11 -10
  133. package/dist/{onboard-8hzdhc1k.js → onboard-rbf6bg01.js} +11 -3
  134. package/dist/{opencode-adapter-qbchbcfz.js → opencode-adapter-13dcatgq.js} +18 -13
  135. package/dist/{otel-impl-e6mnvc4v.js → otel-impl-zvqcbhp7.js} +242 -868
  136. package/dist/{pi-mono-adapter-3c9g9snh.js → pi-mono-adapter-xxy47q4v.js} +244 -106
  137. package/dist/{pricing-refresh-gedkjdw0.js → pricing-refresh-kefx9v3t.js} +12 -11
  138. package/dist/{rbac-roles-dt4vh1yy.js → rbac-roles-3m36rxm2.js} +11 -10
  139. package/dist/{rbac-roles-eszdg6pm.js → rbac-roles-z76xv0hq.js} +12 -11
  140. package/dist/{render-v2-907cy6sa.js → render-v2-wz73bs25.js} +18 -14
  141. package/dist/{resume-42stcaz0.js → resume-k4wzge8w.js} +25 -21
  142. package/dist/{runtime-qfry88v2.js → runtime-sa2vr16g.js} +2 -2
  143. package/dist/{setup-adbc1c10.js → setup-2fp2pypp.js} +2 -2
  144. package/dist/{virtual-modules-rtmt494q.js → virtual-modules-9gkxqrap.js} +6 -5
  145. package/dist/{work-objects-wpj8ck95.js → work-objects-6k0ywz3m.js} +41 -36
  146. package/dist/{worker-bzdra9rf.js → worker-5v3rxsdh.js} +61 -56
  147. package/dist/{x-qfmygqqv.js → x-4mtdeym1.js} +3 -2
  148. package/openapi.json +1279 -329
  149. package/package.json +15 -15
  150. package/src/agentmail/handlers.ts +21 -11
  151. package/src/apps/definition.ts +16 -0
  152. package/src/azure-devops/api.ts +52 -2
  153. package/src/azure-devops/boards-sync.ts +50 -20
  154. package/src/azure-devops/index.ts +1 -0
  155. package/src/azure-devops/outbound.ts +95 -0
  156. package/src/be/boot-seeds.ts +11 -0
  157. package/src/be/config-refs.ts +60 -0
  158. package/src/be/db/model-profiles.ts +315 -0
  159. package/src/be/db/page-versions.ts +15 -0
  160. package/src/be/db/runtime.ts +35 -8
  161. package/src/be/db/skill-name-resolution.ts +36 -0
  162. package/src/be/db/tasks/read.ts +237 -0
  163. package/src/be/db/tasks/write.ts +7 -0
  164. package/src/be/db.ts +87 -36
  165. package/src/be/migrations/215_swarm_config_export_to_env.sql +34 -0
  166. package/src/be/migrations/216_task_resolved_effort.sql +5 -0
  167. package/src/be/migrations/217_chat_sdk_state.sql +104 -0
  168. package/src/be/migrations/219_relabel_pi_context_compactions.sql +22 -0
  169. package/src/be/migrations/220_model_profiles.sql +137 -0
  170. package/src/be/model-catalog-store.ts +5 -1
  171. package/src/be/model-tier-resolution.ts +155 -10
  172. package/src/be/model-validation.ts +12 -2
  173. package/src/be/models-catalog.ts +26 -1
  174. package/src/be/modelsdev-cache.json +198 -63
  175. package/src/be/modelsdev-cache.ts +9 -1
  176. package/src/be/script-credential-broker.ts +39 -2
  177. package/src/be/scripts/run-saved.ts +1 -0
  178. package/src/be/scripts/typecheck.ts +22 -8
  179. package/src/be/secret-registry.ts +6 -2
  180. package/src/be/seed/runner.ts +13 -1
  181. package/src/be/seed/state-db.ts +5 -0
  182. package/src/be/seed/types.ts +6 -0
  183. package/src/be/seed-model-profiles.ts +41 -0
  184. package/src/be/seed-pricing.ts +19 -2
  185. package/src/be/seed-scripts/catalog/heartbeat-jev-gate.ts +368 -0
  186. package/src/be/seed-scripts/catalog/ops-catalog-audit.inline.ts +1 -1
  187. package/src/be/seed-scripts/catalog/ops-catalog-audit.ts +1 -1
  188. package/src/be/seed-scripts/index.ts +9 -0
  189. package/src/be/seed-skills/bundled-files.generated.json +0 -18
  190. package/src/be/seed-skills/index.ts +104 -3
  191. package/src/be/swarm-config-guard.ts +53 -0
  192. package/src/cli.tsx +0 -16
  193. package/src/commands/onboard/non-interactive.ts +4 -2
  194. package/src/commands/provider-credentials.ts +52 -6
  195. package/src/commands/runner.ts +193 -71
  196. package/src/e2b/env.ts +5 -0
  197. package/src/extensions/catalog.generated.json +17 -0
  198. package/src/extensions/contract-types.generated.ts +1 -1
  199. package/src/extensions/contract.ts +45 -0
  200. package/src/extensions/dispatcher.ts +7 -0
  201. package/src/extensions/loader.ts +10 -5
  202. package/src/extensions/post-bridge.ts +40 -0
  203. package/src/github/handlers.ts +11 -4
  204. package/src/heartbeat/heartbeat.ts +2 -1
  205. package/src/heartbeat/templates.ts +8 -5
  206. package/src/hooks/hook.ts +0 -26
  207. package/src/http/agents.ts +49 -2
  208. package/src/http/all-routes.ts +1 -1
  209. package/src/http/apps.ts +30 -3
  210. package/src/http/config.ts +62 -19
  211. package/src/http/core.ts +8 -0
  212. package/src/http/index.ts +25 -3
  213. package/src/http/integrations.ts +88 -1
  214. package/src/http/mcp.ts +15 -5
  215. package/src/http/model-profiles.ts +303 -0
  216. package/src/http/page-proxy.ts +2 -4
  217. package/src/http/pages-public.ts +13 -7
  218. package/src/http/pages.ts +3 -13
  219. package/src/http/poll.ts +42 -71
  220. package/src/http/requested-by.ts +54 -0
  221. package/src/http/script-runs.ts +1 -1
  222. package/src/http/scripts.ts +26 -2
  223. package/src/http/stats.ts +1 -30
  224. package/src/http/tasks.ts +79 -5
  225. package/src/http/utils.ts +14 -0
  226. package/src/http/webhooks.ts +160 -3
  227. package/src/http/x.ts +1 -0
  228. package/src/integrations/chat-sdk/adapters.ts +129 -0
  229. package/src/integrations/chat-sdk/attachments.ts +232 -0
  230. package/src/integrations/chat-sdk/background.ts +36 -0
  231. package/src/integrations/chat-sdk/commands.ts +194 -0
  232. package/src/integrations/chat-sdk/config.ts +198 -0
  233. package/src/integrations/chat-sdk/discord-api.ts +63 -0
  234. package/src/integrations/chat-sdk/discord-gateway.ts +185 -0
  235. package/src/integrations/chat-sdk/handlers.ts +28 -0
  236. package/src/integrations/chat-sdk/inbound.ts +426 -0
  237. package/src/integrations/chat-sdk/limits.ts +48 -0
  238. package/src/integrations/chat-sdk/logger.ts +81 -0
  239. package/src/integrations/chat-sdk/outbound.ts +123 -0
  240. package/src/integrations/chat-sdk/relay.ts +172 -0
  241. package/src/integrations/chat-sdk/router.ts +169 -0
  242. package/src/integrations/chat-sdk/runtime.ts +351 -0
  243. package/src/integrations/chat-sdk/schema.ts +245 -0
  244. package/src/integrations/chat-sdk/state.ts +243 -0
  245. package/src/integrations/chat-sdk/status.ts +244 -0
  246. package/src/integrations/chat-sdk/steering.ts +58 -0
  247. package/src/integrations/chat-sdk/teams-origin.ts +77 -0
  248. package/src/integrations/chat-sdk/telegram-api.ts +56 -0
  249. package/src/integrations/chat-sdk/templates.ts +16 -0
  250. package/src/integrations/chat-sdk/test-hooks.ts +37 -0
  251. package/src/integrations/chat-sdk/thread-buffer.ts +100 -0
  252. package/src/integrations/chat-sdk/typing.ts +65 -0
  253. package/src/{artifact-sdk → pages}/feedback-overlay.ts +65 -6
  254. package/src/pages/version.ts +4 -5
  255. package/src/prompts/base-prompt.ts +111 -45
  256. package/src/providers/acp-target-catalog.ts +6 -0
  257. package/src/providers/acp-targets.ts +56 -3
  258. package/src/providers/amp-adapter.ts +1 -1
  259. package/src/providers/claude-adapter.ts +1 -1
  260. package/src/providers/claude-flag-settings.ts +17 -5
  261. package/src/providers/claude-managed-adapter.ts +1 -1
  262. package/src/providers/claude-session-events.ts +6 -1
  263. package/src/providers/dsh-adapter.ts +1 -1
  264. package/src/providers/opencode-adapter.ts +243 -26
  265. package/src/providers/opencode-swarm-plugin.ts +31 -0
  266. package/src/providers/pi-mono-adapter.ts +398 -153
  267. package/src/providers/pi-mono-extension.ts +17 -3
  268. package/src/providers/pricing-sources.md +33 -2
  269. package/src/providers/reasoning-effort.ts +37 -0
  270. package/src/providers/types.ts +6 -2
  271. package/src/rbac/legacy-policy.ts +16 -2
  272. package/src/rbac/permissions.ts +26 -0
  273. package/src/scheduler/deferred-task-waits.ts +18 -5
  274. package/src/scheduler/scheduler.ts +11 -3
  275. package/src/scripts-runtime/executors/quickjs-prelude.ts +75 -6
  276. package/src/scripts-runtime/executors/quickjs-runner.ts +85 -8
  277. package/src/scripts-runtime/executors/quickjs-web-shims.ts +1041 -0
  278. package/src/scripts-runtime/extract-args-schema.ts +9 -8
  279. package/src/scripts-runtime/loader.ts +19 -7
  280. package/src/scripts-runtime/quickjs-compat.ts +464 -0
  281. package/src/scripts-runtime/sdk-allowlist.ts +12 -6
  282. package/src/scripts-runtime/swarm-sdk.ts +0 -11
  283. package/src/scripts-runtime/types/stdlib.d.ts +39 -15
  284. package/src/scripts-runtime/types/swarm-extension.d.ts +66 -1
  285. package/src/scripts-runtime/types/swarm-sdk.d.ts +39 -15
  286. package/src/server.ts +62 -19
  287. package/src/slack/blocks.ts +4 -2
  288. package/src/slack/event-dedup.ts +1 -1
  289. package/src/slack/reaction-shortcode.ts +14 -0
  290. package/src/slack/render-v2.ts +12 -7
  291. package/src/slack/responses.ts +5 -4
  292. package/src/slack/thread-buffer.ts +41 -22
  293. package/src/tasks/additive-buffer.ts +4 -2
  294. package/src/tasks/context-key.ts +60 -1
  295. package/src/tasks/model-pin.ts +67 -0
  296. package/src/tasks/worker-follow-up.ts +15 -33
  297. package/src/tests/acp-adapter.test.ts +42 -0
  298. package/src/tests/agentmail-filters.test.ts +47 -0
  299. package/src/tests/api-drain-worker-e2e.test.ts +1 -6
  300. package/src/tests/apps-spike2.test.ts +35 -0
  301. package/src/tests/azure-devops-outbound.test.ts +420 -0
  302. package/src/tests/base-prompt.test.ts +58 -54
  303. package/src/tests/chat-sdk-commands.test.ts +191 -0
  304. package/src/tests/chat-sdk-config.test.ts +334 -0
  305. package/src/tests/chat-sdk-discord-api.test.ts +68 -0
  306. package/src/tests/chat-sdk-discord-gateway.test.ts +252 -0
  307. package/src/tests/chat-sdk-discord-webhook.test.ts +126 -0
  308. package/src/tests/chat-sdk-fields-sync.test.ts +50 -0
  309. package/src/tests/chat-sdk-harness.ts +146 -0
  310. package/src/tests/chat-sdk-inbound.test.ts +363 -0
  311. package/src/tests/chat-sdk-relay.test.ts +470 -0
  312. package/src/tests/chat-sdk-reload.test.ts +128 -0
  313. package/src/tests/chat-sdk-shutdown.test.ts +220 -0
  314. package/src/tests/chat-sdk-slack.test.ts +268 -0
  315. package/src/tests/chat-sdk-state.test.ts +163 -0
  316. package/src/tests/chat-sdk-status-route.test.ts +382 -0
  317. package/src/tests/chat-sdk-teams.test.ts +277 -0
  318. package/src/tests/chat-sdk-tools.test.ts +520 -0
  319. package/src/tests/chat-sdk-typing.test.ts +120 -0
  320. package/src/tests/chat-sdk-webhook-telegram.test.ts +200 -0
  321. package/src/tests/claude-adapter-binary.test.ts +9 -4
  322. package/src/tests/claude-flag-settings.test.ts +11 -3
  323. package/src/tests/claude-managed-adapter.test.ts +1 -1
  324. package/src/tests/claude-sdk-lifecycle.test.ts +9 -3
  325. package/src/tests/claude-thinking-tokens.test.ts +57 -0
  326. package/src/tests/claude-worker-parity.test.ts +2 -7
  327. package/src/tests/config-env-export-chat-sdk.test.ts +11 -0
  328. package/src/tests/config-refs.test.ts +66 -0
  329. package/src/tests/context-key.test.ts +19 -0
  330. package/src/tests/cors-allowlist.test.ts +25 -0
  331. package/src/tests/create-page-tool.test.ts +18 -0
  332. package/src/tests/credential-check.test.ts +139 -0
  333. package/src/tests/credentials.test.ts +21 -0
  334. package/src/tests/db-runtime.test.ts +61 -0
  335. package/src/tests/db-task-read-contract.test.ts +22 -0
  336. package/src/tests/db-task-write-contract.test.ts +11 -0
  337. package/src/tests/deferred-task-wake.test.ts +78 -1
  338. package/src/tests/docs-index-footprint.test.js +3 -3
  339. package/src/tests/dsh-adapter.test.ts +1 -1
  340. package/src/tests/enabled-capabilities.test.ts +20 -0
  341. package/src/tests/entrypoint-api-readiness.test.ts +14 -8
  342. package/src/tests/entrypoint-config-env-export.test.ts +5 -2
  343. package/src/tests/entrypoint-export-to-env.test.ts +78 -0
  344. package/src/tests/env-flag.test.ts +1 -0
  345. package/src/tests/explicit-model-validation.test.ts +105 -1
  346. package/src/tests/extensions-catalog.test.ts +1 -0
  347. package/src/tests/extensions-example-comms-adherence-gate.test.ts +413 -0
  348. package/src/tests/extensions-example-route-chat-channel.test.ts +154 -0
  349. package/src/tests/extensions-loader.test.ts +15 -0
  350. package/src/tests/extensions-post-bridge.test.ts +51 -0
  351. package/src/tests/fetch-resolved-env.test.ts +128 -0
  352. package/src/tests/fixtures/chat-sdk/telegram-private-text.json +10 -0
  353. package/src/tests/fixtures/extensions/route-chat-channel-to-agent/hooks.ts +27 -0
  354. package/src/tests/fixtures/extensions/route-chat-channel-to-agent/manifest.json +9 -0
  355. package/src/tests/fixtures/openai-compatible-registry-extension.child.ts +146 -0
  356. package/src/tests/fixtures/runner-legacy-resume-prompt.ts +122 -0
  357. package/src/tests/github-handlers.test.ts +35 -0
  358. package/src/tests/harness-provider-resolution.test.ts +12 -0
  359. package/src/tests/heartbeat-checklist.test.ts +3 -2
  360. package/src/tests/heartbeat-jev-gate-credentials.test.ts +168 -0
  361. package/src/tests/heartbeat-jev-gate.test.ts +385 -0
  362. package/src/tests/hook-runtime-close.test.ts +0 -2
  363. package/src/tests/http-api-integration.test.ts +0 -25
  364. package/src/tests/migration-046-budgets.test.ts +17 -16
  365. package/src/tests/migration-217-chat-sdk-state.test.ts +113 -0
  366. package/src/tests/model-profile-backcompat.test.ts +327 -0
  367. package/src/tests/model-profile-providers.test.ts +302 -0
  368. package/src/tests/model-profile-runtime-secrets.test.ts +223 -0
  369. package/src/tests/model-profiles-api.test.ts +614 -0
  370. package/src/tests/model-tier-resolution.test.ts +93 -0
  371. package/src/tests/models-catalog.test.ts +42 -1
  372. package/src/tests/openai-compatible-providers.test.ts +134 -0
  373. package/src/tests/openai-compatible-registry-extension.test.ts +42 -0
  374. package/src/tests/opencode-adapter.test.ts +216 -2
  375. package/src/tests/opencode-helmcode-sdk.test.ts +201 -0
  376. package/src/tests/opencode-shell-env.test.ts +145 -0
  377. package/src/tests/openrouter-base-url.test.ts +101 -151
  378. package/src/tests/page-proxy.test.ts +1 -1
  379. package/src/tests/pages-browser-sdk.test.ts +76 -0
  380. package/src/tests/pages-feedback-keys.test.ts +121 -0
  381. package/src/tests/pages-feedback.test.ts +4 -1
  382. package/src/tests/paused-tasks-requested-by.test.ts +102 -0
  383. package/src/tests/pi-context-compaction.test.ts +193 -0
  384. package/src/tests/pi-custom-provider-fake-server.test.ts +442 -0
  385. package/src/tests/pi-mono-adapter.test.ts +385 -5
  386. package/src/tests/pi-shell-env.test.ts +170 -0
  387. package/src/tests/profile-claim.test.ts +269 -0
  388. package/src/tests/prompt-template-remaining.test.ts +35 -0
  389. package/src/tests/quickjs-compat.test.ts +213 -0
  390. package/src/tests/rbac-engine.test.ts +32 -3
  391. package/src/tests/runner-exit-after-tasks.test.ts +0 -4
  392. package/src/tests/runner-legacy-resume-prompt.test.ts +64 -0
  393. package/src/tests/script-connections.test.ts +6 -0
  394. package/src/tests/script-executor-conformance.test.ts +252 -1
  395. package/src/tests/script-executor-quickjs.test.ts +96 -0
  396. package/src/tests/script-runs-http.test.ts +89 -0
  397. package/src/tests/scripts-http.test.ts +217 -1
  398. package/src/tests/scripts-only-gating.test.ts +2 -3
  399. package/src/tests/scripts-runtime.test.ts +58 -40
  400. package/src/tests/secret-scrubber.test.ts +96 -0
  401. package/src/tests/seed-model-profiles.test.ts +174 -0
  402. package/src/tests/seed-scripts.test.ts +2 -2
  403. package/src/tests/seed-skills-bundled-files.test.ts +45 -53
  404. package/src/tests/seed-skills-retire.test.ts +182 -0
  405. package/src/tests/send-task-requester-gate.test.ts +58 -0
  406. package/src/tests/session-cost-recompute-unit.test.ts +46 -0
  407. package/src/tests/skill-get-name-resolution.test.ts +137 -0
  408. package/src/tests/skill-invocations.test.ts +4 -2
  409. package/src/tests/slack-blocks.test.ts +36 -0
  410. package/src/tests/slack-inline-output.test.ts +14 -0
  411. package/src/tests/slack-reaction-config.test.ts +20 -0
  412. package/src/tests/slack-render-v2.test.ts +37 -0
  413. package/src/tests/slack-thread-buffer.test.ts +98 -1
  414. package/src/tests/sonnet-5-5-registry.test.ts +9 -1
  415. package/src/tests/swarm-config-export-to-env.test.ts +267 -0
  416. package/src/tests/swarm-config-reserved-keys.test.ts +16 -0
  417. package/src/tests/swarm-diff.test.ts +1 -1
  418. package/src/tests/task-model-effort-rerun.test.ts +164 -0
  419. package/src/tests/task-session-prompts.test.ts +132 -0
  420. package/src/tests/task-tool-manifest.test.ts +14 -0
  421. package/src/tests/task-tools-ctx.test.ts +2 -0
  422. package/src/tests/tool-annotations.test.ts +6 -6
  423. package/src/tests/ui-logs-parser.test.ts +43 -0
  424. package/src/tests/used-by-route.test.ts +6 -0
  425. package/src/tests/vercel-ai-gateway.test.ts +149 -0
  426. package/src/tests/workflow-llm.test.ts +61 -0
  427. package/src/tests/workflow-swarm-script.test.ts +38 -0
  428. package/src/tests/workflow-wait-filter.test.ts +5 -3
  429. package/src/tools/chat-access.ts +41 -0
  430. package/src/tools/chat-open-dm.ts +59 -0
  431. package/src/tools/chat-post.ts +74 -0
  432. package/src/tools/chat-read-thread.ts +100 -0
  433. package/src/tools/chat-reply.ts +75 -0
  434. package/src/tools/create-page.ts +3 -13
  435. package/src/tools/model-catalog/index.ts +2 -1
  436. package/src/tools/model-profiles/list-model-profiles.ts +54 -0
  437. package/src/tools/register-chat-channel.ts +139 -0
  438. package/src/tools/request-human-input.ts +11 -3
  439. package/src/tools/script-apis.ts +5 -1
  440. package/src/tools/script-upsert.ts +4 -1
  441. package/src/tools/send-task.ts +48 -1
  442. package/src/tools/skills/skill-get.ts +17 -9
  443. package/src/tools/swarm-config/get-config.ts +2 -1
  444. package/src/tools/swarm-config/list-config.ts +2 -1
  445. package/src/tools/swarm-config/set-config.ts +16 -3
  446. package/src/tools/system-one-decide.ts +104 -0
  447. package/src/tools/task-action.ts +22 -1
  448. package/src/tools/tool-config.ts +14 -6
  449. package/src/types.ts +48 -3
  450. package/src/utils/config-env-export.ts +74 -0
  451. package/src/utils/credentials.test.ts +92 -0
  452. package/src/utils/credentials.ts +52 -4
  453. package/src/utils/harness-provider.ts +15 -6
  454. package/src/utils/runtime-model-catalog.ts +11 -1
  455. package/src/utils/secret-scrubber.ts +86 -10
  456. package/src/utils/task-tool-manifest.ts +19 -2
  457. package/src/utils/vcs-repo-match.ts +29 -0
  458. package/src/workflows/executors/raw-llm.ts +8 -3
  459. package/src/workflows/executors/swarm-script.ts +1 -0
  460. package/src/workflows/executors/system-one-decision.ts +21 -11
  461. package/src/workflows/executors/workflow-llm.ts +22 -0
  462. package/src/workflows/index.ts +21 -12
  463. package/templates/extensions/README.md +1 -0
  464. package/templates/extensions/comms-adherence-gate/README.md +88 -0
  465. package/templates/extensions/comms-adherence-gate/hooks.ts +489 -0
  466. package/templates/extensions/comms-adherence-gate/manifest.json +8 -0
  467. package/templates/skills/agent-browser/SKILL.md +1 -1
  468. package/templates/skills/agent-browser/content.md +1 -1
  469. package/templates/skills/apps/SKILL.md +8 -1
  470. package/templates/skills/apps/content.md +8 -1
  471. package/templates/skills/chat-sdk/config.json +14 -0
  472. package/templates/skills/chat-sdk/content.md +84 -0
  473. package/templates/skills/composio-gmail/content.md +7 -0
  474. package/templates/skills/composio-google-docs/content.md +1 -1
  475. package/templates/skills/email-domain-health/config.json +14 -0
  476. package/templates/skills/email-domain-health/content.md +131 -0
  477. package/templates/skills/kv-storage/content.md +2 -2
  478. package/templates/skills/pages/content.md +8 -10
  479. package/templates/skills/swarm-extensions/content.md +2 -0
  480. package/templates/skills/swarm-scripts/SKILL.md +4 -2
  481. package/templates/skills/swarm-scripts/config.json +1 -1
  482. package/templates/skills/swarm-scripts/content.md +4 -2
  483. package/templates/skills/workflow-iterate/content.md +38 -0
  484. package/dist/artifact-eczsknc3.js +0 -3843
  485. package/dist/cli-42qhyvwx.js +0 -33
  486. package/dist/cli-bd957dgb.js +0 -153
  487. package/dist/cli-q51yprfq.js +0 -1104
  488. package/dist/cli-t1ze64h3.js +0 -811
  489. package/dist/cli-tcc4wd5d.js +0 -56471
  490. package/dist/index-9ch1n761.js +0 -1102
  491. package/src/artifact-sdk/index.ts +0 -2
  492. package/src/artifact-sdk/localtunnel.d.ts +0 -20
  493. package/src/artifact-sdk/port.ts +0 -12
  494. package/src/artifact-sdk/server.ts +0 -183
  495. package/src/artifact-sdk/tunnel.ts +0 -19
  496. package/src/commands/artifact.ts +0 -249
  497. package/src/http/ecosystem.ts +0 -77
  498. package/src/tests/artifact-commands.test.ts +0 -92
  499. package/src/tests/artifact-sdk.test.ts +0 -793
  500. package/src/tests/register-service-security.test.ts +0 -194
  501. package/src/tools/list-services.ts +0 -107
  502. package/src/tools/register-service.ts +0 -177
  503. package/src/tools/unregister-service.ts +0 -73
  504. package/src/tools/update-service-status.ts +0 -96
  505. package/templates/skills/artifacts/config.json +0 -14
  506. package/templates/skills/artifacts/content.md +0 -344
  507. package/templates/skills/artifacts/files/examples/approval-flow.ts +0 -34
  508. package/templates/skills/artifacts/files/examples/hono-dashboard.ts +0 -31
  509. package/templates/skills/artifacts/files/examples/multi-artifact.ts +0 -20
  510. package/templates/skills/artifacts/files/examples/static-report.sh +0 -17
  511. /package/src/{artifact-sdk → pages}/browser-sdk.ts +0 -0
@@ -0,0 +1,78 @@
1
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
2
+ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { CHILD_PROCESS_TEST_BUDGET_MS, expectChildOk, runChild } from "./test-proc";
6
+
7
+ /**
8
+ * Runs the real swarm_config_env block from docker-entrypoint.sh (extracted
9
+ * between its BEGIN/END markers) against a /api/config/resolved fixture, the
10
+ * same way the entrypoint sources it, and reads the resulting env.
11
+ */
12
+
13
+ const entrypoint = readFileSync("docker-entrypoint.sh", "utf8");
14
+ const block = entrypoint.slice(
15
+ entrypoint.indexOf("# BEGIN swarm_config_env"),
16
+ entrypoint.indexOf("# END swarm_config_env"),
17
+ );
18
+
19
+ let dir = "";
20
+
21
+ beforeAll(() => {
22
+ dir = mkdtempSync(join(tmpdir(), "entrypoint-export-"));
23
+ });
24
+ afterAll(() => {
25
+ rmSync(dir, { recursive: true, force: true });
26
+ });
27
+
28
+ async function exportedEnv(configs: unknown[], containerEnv: Record<string, string> = {}) {
29
+ const fixture = join(dir, `${crypto.randomUUID()}.json`);
30
+ writeFileSync(fixture, JSON.stringify({ configs }));
31
+ const script = `${block}
32
+ swarm_config_env_lines "$1" > "$1.env"
33
+ set -a; . "$1.env"; set +a
34
+ for withheld_key in $(swarm_config_withheld_keys "$1"); do unset "$withheld_key"; done
35
+ env
36
+ `;
37
+ const result = await runChild(["bash", "-c", script, "bash", fixture], {
38
+ env: { PATH: process.env.PATH ?? "/usr/bin:/bin", ...containerEnv },
39
+ timeoutMs: CHILD_PROCESS_TEST_BUDGET_MS,
40
+ });
41
+ expectChildOk(result, "entrypoint export block");
42
+ return Object.fromEntries(
43
+ result.stdout
44
+ .split("\n")
45
+ .filter((line) => line.includes("="))
46
+ .map((line) => [line.slice(0, line.indexOf("=")), line.slice(line.indexOf("=") + 1)]),
47
+ );
48
+ }
49
+
50
+ describe("docker-entrypoint.sh swarm_config_env", () => {
51
+ test("markers are present", () => {
52
+ expect(block).toContain("swarm_config_env_lines()");
53
+ expect(block).toContain("swarm_config_withheld_keys()");
54
+ });
55
+
56
+ test("exports exportToEnv rows and drops withheld ones, including container copies", async () => {
57
+ const env = await exportedEnv(
58
+ [
59
+ { key: "GITHUB_TOKEN", value: "row-github", isSecret: true, exportToEnv: true },
60
+ { key: "VERCEL_TOKEN", value: "row-vercel", isSecret: true, exportToEnv: false },
61
+ { key: "QUOTED", value: "it's $HOME", isSecret: false, exportToEnv: true },
62
+ // A pre-215 API sends no flag: the row keeps exporting.
63
+ { key: "LEGACY", value: "row-legacy", isSecret: true },
64
+ { key: "CF-Access-Client-Id", value: "x", isSecret: true, exportToEnv: true },
65
+ { key: "codex_oauth_1", value: "x", isSecret: true, exportToEnv: true },
66
+ { key: "HARNESS_PROVIDER", value: "pi", isSecret: false, exportToEnv: true },
67
+ ],
68
+ { VERCEL_TOKEN: "container-vercel" },
69
+ );
70
+ expect(env.GITHUB_TOKEN).toBe("row-github");
71
+ expect(env.QUOTED).toBe("it's $HOME");
72
+ expect(env.LEGACY).toBe("row-legacy");
73
+ expect(env.VERCEL_TOKEN).toBeUndefined();
74
+ expect(env.codex_oauth_1).toBeUndefined();
75
+ expect(env.HARNESS_PROVIDER).toBeUndefined();
76
+ expect(Object.values(env)).not.toContain("row-vercel");
77
+ });
78
+ });
@@ -76,6 +76,7 @@ describe("swarm-config-guard: Configuration-page value validation", () => {
76
76
  "SLACK_REACTION_STEERED",
77
77
  "SLACK_REACTION_COMPLETED",
78
78
  "SLACK_REACTION_FAILED",
79
+ "SLACK_STATUS_ICON_COMPLETED",
79
80
  ];
80
81
  for (const key of keys) {
81
82
  expect(validateConfigValue(key, "thumbsup")).toBeNull();
@@ -542,7 +542,12 @@ describe("harness compatibility at every create entry point", () => {
542
542
  });
543
543
 
544
544
  test("send-task: the parent auto-route target is judged", async () => {
545
- const parent = await createTaskExtended("codex parent", { agentId: codexId, source: "mcp" });
545
+ // The worker created the parent, so it may continue it (task.parent.attach).
546
+ const parent = await createTaskExtended("codex parent", {
547
+ agentId: codexId,
548
+ creatorAgentId: workerId,
549
+ source: "mcp",
550
+ });
546
551
  const refused = await sendTaskHandler(ownerCtx({ agentId: workerId }), {
547
552
  task: "child of a codex task",
548
553
  parentTaskId: parent.id,
@@ -662,3 +667,102 @@ describe("harness compatibility at every create entry point", () => {
662
667
  expect(String(res.body.error)).toContain("does not run on the codex harness");
663
668
  });
664
669
  });
670
+
671
+ describe("nan and helmcode catalog models", () => {
672
+ let piId = "";
673
+ let opencodeId = "";
674
+
675
+ beforeAll(async () => {
676
+ piId = (
677
+ await createAgent({
678
+ name: "validation-pi",
679
+ isLead: false,
680
+ status: "idle",
681
+ harnessProvider: "pi",
682
+ })
683
+ ).id;
684
+ opencodeId = (
685
+ await createAgent({
686
+ name: "validation-opencode",
687
+ isLead: false,
688
+ status: "idle",
689
+ harnessProvider: "opencode",
690
+ })
691
+ ).id;
692
+ });
693
+
694
+ const ids = [
695
+ "nan/glm5.3-flash",
696
+ "nan/glm5.3",
697
+ "nan/mimo-v2.6-flash",
698
+ "helmcode/glm5.3",
699
+ "helmcode/qwen3.6",
700
+ "helmcode/deepseek-v4-flash",
701
+ ];
702
+
703
+ test("pi and opencode accept the ids without allowCustomModel", async () => {
704
+ for (const harnessProvider of ["pi", "opencode"]) {
705
+ for (const model of ids) {
706
+ expect(await explicitModelError({ model, harnessProvider })).toBeNull();
707
+ }
708
+ }
709
+ });
710
+
711
+ test("claude and codex reject them with the harness mismatch, even with allowCustomModel", async () => {
712
+ for (const harnessProvider of ["claude", "codex"]) {
713
+ for (const allowCustomModel of [false, true]) {
714
+ for (const model of ["nan/glm5.3-flash", "helmcode/glm5.3"]) {
715
+ expect(await explicitModelError({ model, harnessProvider, allowCustomModel })).toContain(
716
+ `does not run on the ${harnessProvider} harness`,
717
+ );
718
+ }
719
+ }
720
+ }
721
+ });
722
+
723
+ test("a misspelt helmcode id is unknown", async () => {
724
+ expect(
725
+ await explicitModelError({ model: "helmcode/glm-5.3", harnessProvider: "pi" }),
726
+ ).toContain("Unknown model");
727
+ });
728
+
729
+ test("send-task: pi and opencode agents take the ids, a claude agent gets the mismatch", async () => {
730
+ const sender = ownerCtx({ agentId: leadId });
731
+ const args = {
732
+ task: "nan route",
733
+ offerMode: false,
734
+ allowDuplicate: false,
735
+ routingReason: "human_pinned" as const,
736
+ routingNote: "pinned to check the nan/helmcode catalog ids",
737
+ };
738
+ for (const agentId of [piId, opencodeId]) {
739
+ for (const model of ["nan/mimo-v2.6-flash", "helmcode/glm5.3"]) {
740
+ const accepted = await sendTaskHandler(sender, {
741
+ ...args,
742
+ task: `nan route ${model} to ${agentId}`,
743
+ agentId,
744
+ model,
745
+ });
746
+ expect({ ok: accepted.ok, message: accepted.message }).toEqual({
747
+ ok: true,
748
+ message: expect.any(String),
749
+ });
750
+ }
751
+ }
752
+ const refused = await sendTaskHandler(sender, {
753
+ ...args,
754
+ agentId: workerId,
755
+ model: "helmcode/glm5.3",
756
+ });
757
+ expect(refused.ok).toBe(false);
758
+ expect(refused.message).toContain("does not run on the claude harness");
759
+ });
760
+
761
+ test("PATCH /api/agents/:id/runtime stores a helmcode model on a pi agent", async () => {
762
+ const res = await api("PATCH", `/api/agents/${piId}/runtime`, {
763
+ harness_provider: "pi",
764
+ model: "helmcode/glm5.3",
765
+ });
766
+ expect(res.status).toBe(200);
767
+ });
768
+ });
@@ -37,6 +37,7 @@ describe("predefined extension catalog", () => {
37
37
  async () => {
38
38
  const catalog = getExtensionCatalog();
39
39
  expect(Object.keys(catalog).sort()).toEqual([
40
+ "comms-adherence-gate",
40
41
  "deploy-awareness",
41
42
  "github-sender-allowlist",
42
43
  "notify-on-complete",
@@ -0,0 +1,413 @@
1
+ import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, mock, test } from "bun:test";
2
+ import * as contractRuntime from "../extensions/contract-runtime";
3
+
4
+ // Unit tests for templates/extensions/comms-adherence-gate/hooks.ts. Every Jev call goes
5
+ // to a mocked fetch and the DB read to a mocked ctx.swarm.db_query; nothing reaches the
6
+ // network.
7
+
8
+ // `swarm-extension` is a bare import the extension loader shims to contract-runtime.
9
+ mock.module("swarm-extension", () => contractRuntime);
10
+
11
+ type Handler = (event: any, ctx: any) => Promise<any>;
12
+ let handler: Handler;
13
+ let mod: any;
14
+
15
+ const ENV_KEYS = ["TYPESAFE_API_KEY", "OPENROUTER_API_KEY"];
16
+ const savedEnv: Record<string, string | undefined> = {};
17
+
18
+ beforeAll(async () => {
19
+ for (const k of ENV_KEYS) {
20
+ savedEnv[k] = process.env[k];
21
+ delete process.env[k];
22
+ }
23
+ mod = await import("../../templates/extensions/comms-adherence-gate/hooks.ts");
24
+ mod.default({
25
+ on: (name: string, h: Handler) => {
26
+ if (name === "pre.tool.call") handler = h;
27
+ },
28
+ });
29
+ });
30
+
31
+ afterAll(() => {
32
+ for (const k of ENV_KEYS) {
33
+ if (savedEnv[k] === undefined) delete process.env[k];
34
+ else process.env[k] = savedEnv[k];
35
+ }
36
+ });
37
+
38
+ const TASK_ID = "11111111-2222-3333-4444-555555555555";
39
+ const PREFS = { verbosity: "very terse", tone: "direct, plain", language: "English" };
40
+ const TASK_TEXT =
41
+ "<thread_context>\n<@U1|Alice>: earlier chatter about deploys\n</thread_context>\n\nwhich option did you pick?";
42
+
43
+ const realFetch = globalThis.fetch;
44
+ let jevCalls: Array<{ url: string; body: any; auth: string }> = [];
45
+
46
+ const mockJev = (reply: (body: any) => Response | Promise<Response>) => {
47
+ globalThis.fetch = (async (url: any, init: any) => {
48
+ const body = JSON.parse(String(init?.body ?? "{}"));
49
+ jevCalls.push({ url: String(url), body, auth: String(init?.headers?.Authorization ?? "") });
50
+ return reply(body);
51
+ }) as typeof fetch;
52
+ };
53
+ /** Answer every question with the same score, or per-question scores. */
54
+ const jevScores = (scores: number | Record<string, number>) =>
55
+ mockJev((body) => {
56
+ const answers: Record<string, unknown> = {};
57
+ for (const id of Object.keys(body.questions ?? {})) {
58
+ const s = typeof scores === "number" ? scores : (scores[id] ?? 1);
59
+ answers[id] = { type: "noul", noul: s };
60
+ }
61
+ return Response.json({ model: "jev-1.13.0", answers });
62
+ });
63
+
64
+ type Row = Record<string, unknown> | null;
65
+ const taskRow = (over: Record<string, unknown> = {}): Row => ({
66
+ source: "slack",
67
+ slackThreadTs: "1700000000.000100",
68
+ agentId: "agent-1",
69
+ requestedByUserId: "user-1",
70
+ comms: JSON.stringify(PREFS),
71
+ name: "Alice",
72
+ askHead: TASK_TEXT.slice(0, 400),
73
+ askTail: TASK_TEXT.slice(-1400),
74
+ ...over,
75
+ });
76
+
77
+ const makeCtx = (
78
+ config: Record<string, unknown> = {},
79
+ opts: {
80
+ row?: Row;
81
+ key?: string;
82
+ openrouterKey?: string;
83
+ dbThrows?: boolean;
84
+ /** The caller's agents.isLead; null means no agents row. */
85
+ callerIsLead?: boolean | null;
86
+ } = {},
87
+ ) => {
88
+ const kv: Record<string, unknown> = {};
89
+ const counters: Record<string, number> = {};
90
+ const queries: any[] = [];
91
+ const setEnv = (name: string, value: string) => {
92
+ if (value) process.env[name] = value;
93
+ else delete process.env[name];
94
+ };
95
+ setEnv("TYPESAFE_API_KEY", opts.key ?? "tsk-test");
96
+ setEnv("OPENROUTER_API_KEY", opts.openrouterKey ?? "");
97
+ const row = opts.row === undefined ? taskRow() : opts.row;
98
+ const swarm = {
99
+ db_query: async (args: any) => {
100
+ queries.push(args);
101
+ if (opts.dbThrows) throw new Error("db down");
102
+ if (String(args.sql).includes("FROM agents")) {
103
+ const lead = opts.callerIsLead ?? false;
104
+ return {
105
+ success: true,
106
+ data: { rows: opts.callerIsLead === null ? [] : [{ isLead: lead ? 1 : 0 }] },
107
+ };
108
+ }
109
+ return { success: true, data: { rows: row ? [row] : [] } };
110
+ },
111
+ kv_set: async ({ key, value }: any) => {
112
+ kv[key] = value;
113
+ return { success: true };
114
+ },
115
+ kv_get: async ({ key }: any) =>
116
+ key in counters ? { success: true, data: { value: counters[key] } } : { success: false },
117
+ kv_incr: async ({ key }: any) => {
118
+ counters[key] = (counters[key] ?? 0) + 1;
119
+ return { success: true };
120
+ },
121
+ };
122
+ const warns: unknown[] = [];
123
+ return {
124
+ ctx: {
125
+ swarm,
126
+ config,
127
+ state: {},
128
+ log: { debug() {}, info() {}, warn: (...a: unknown[]) => warns.push(a), error() {} },
129
+ signal: new AbortController().signal,
130
+ },
131
+ kv,
132
+ counters,
133
+ queries,
134
+ warns,
135
+ };
136
+ };
137
+
138
+ const completion = (output: string, extra: Record<string, unknown> = {}) => ({
139
+ tool: "store-progress",
140
+ args: { taskId: TASK_ID, status: "completed", output, ...extra },
141
+ requestInfo: { callOrigin: "mcp", agentId: "agent-1" },
142
+ });
143
+
144
+ beforeEach(() => {
145
+ jevCalls = [];
146
+ mod.resetKeyCache();
147
+ });
148
+ afterEach(() => {
149
+ globalThis.fetch = realFetch;
150
+ });
151
+
152
+ describe("defaults", () => {
153
+ test("a new install defaults to shadow", () => {
154
+ expect(mod.cfgOf(undefined)).toMatchObject({
155
+ mode: "shadow",
156
+ threshold: 0.5,
157
+ timeoutMs: 4000,
158
+ maxBlocksPerTask: 1,
159
+ provider: "auto",
160
+ });
161
+ });
162
+
163
+ test("an invalid config falls back to defaults", () => {
164
+ expect(mod.cfgOf({ mode: "loud" }).mode).toBe("shadow");
165
+ });
166
+ });
167
+
168
+ describe("latestAsk", () => {
169
+ test("takes the text after the last thread context", () => {
170
+ expect(mod.latestAsk(TASK_TEXT)).toBe("which option did you pick?");
171
+ });
172
+
173
+ test("strips the thread follow-up header", () => {
174
+ const text =
175
+ "<thread_context>\nx\n</thread_context>\n\n[Thread follow-up — 1 message(s) buffered]\n\ntell me a long joke";
176
+ expect(mod.latestAsk(text)).toBe("tell me a long joke");
177
+ });
178
+
179
+ test("falls back to the end of the context when nothing follows it", () => {
180
+ expect(mod.latestAsk("<thread_context>\nplease explain in detail\n</thread_context>")).toBe(
181
+ "<thread_context>\nplease explain in detail",
182
+ );
183
+ });
184
+ });
185
+
186
+ describe("pass", () => {
187
+ test("scores at or above threshold pass and are recorded", async () => {
188
+ jevScores(0.9);
189
+ const { ctx, kv, counters, queries } = makeCtx({ mode: "enforce" });
190
+ const result = await handler(completion("Picked B: it keeps the migration reversible."), ctx);
191
+ expect(result).toBeUndefined();
192
+ expect(counters).toMatchObject({ total: 1, pass: 1 });
193
+ expect(counters.fail).toBeUndefined();
194
+ expect(kv.last).toMatchObject({ taskId: TASK_ID, failed: [], provider: "typesafe" });
195
+ // The task id is bound, never interpolated into the SQL.
196
+ expect(queries[0].params).toEqual([TASK_ID]);
197
+ expect(queries[0].sql).not.toContain(TASK_ID);
198
+ const call = jevCalls[0]!;
199
+ expect(call.url).toBe("https://api.typesafe.ai/v1/systemone");
200
+ expect(call.auth).toBe("Bearer tsk-test");
201
+ expect(Object.keys(call.body.questions).sort()).toEqual(["language", "tone", "verbosity"]);
202
+ expect(call.body.state.latest_request).toBe("which option did you pick?");
203
+ });
204
+
205
+ test("non-Slack tasks, requesters without prefs, non-completions and other agents' tasks skip Jev", async () => {
206
+ jevScores(0.1);
207
+ const cases: Array<[any, Row]> = [
208
+ [completion("x"), taskRow({ source: "mcp", slackThreadTs: null })],
209
+ [completion("x"), taskRow({ comms: null })],
210
+ [completion("x"), taskRow({ comms: JSON.stringify({ timezone: "Europe/Madrid" }) })],
211
+ [
212
+ { ...completion("x"), args: { taskId: TASK_ID, status: "in_progress", progress: "hi" } },
213
+ taskRow(),
214
+ ],
215
+ [{ ...completion("x"), tool: "send-task" }, taskRow()],
216
+ [{ ...completion("x"), requestInfo: { callOrigin: "extension" } }, taskRow()],
217
+ // Another agent's task, or a caller with no agent id.
218
+ [completion("x"), taskRow({ agentId: "agent-2" })],
219
+ [{ ...completion("x"), requestInfo: { callOrigin: "mcp" } }, taskRow()],
220
+ [completion("x", { taskId: "not-a-uuid" }), taskRow()],
221
+ ];
222
+ for (const [event, row] of cases) {
223
+ const { ctx } = makeCtx({ mode: "enforce" }, { row });
224
+ expect(await handler(event, ctx)).toBeUndefined();
225
+ }
226
+ expect(jevCalls).toHaveLength(0);
227
+ });
228
+
229
+ test("a non-lead worker completing another agent's task causes zero judge egress", async () => {
230
+ jevScores(0.1);
231
+ for (const callerIsLead of [false, null]) {
232
+ const { ctx, queries } = makeCtx(
233
+ { mode: "enforce" },
234
+ { row: taskRow({ agentId: "agent-2" }), callerIsLead },
235
+ );
236
+ expect(await handler(completion("x"), ctx)).toBeUndefined();
237
+ // Lead status is read for the caller from the agents row.
238
+ expect(queries[1]).toMatchObject({ params: ["agent-1"] });
239
+ }
240
+ expect(jevCalls).toHaveLength(0);
241
+ });
242
+
243
+ test("a lead completing a worker-owned task reaches the judge", async () => {
244
+ jevScores(0.1);
245
+ const { ctx, counters } = makeCtx(
246
+ { mode: "enforce" },
247
+ { row: taskRow({ agentId: "agent-2" }), callerIsLead: true },
248
+ );
249
+ expect(await handler(completion("x"), ctx)).toMatchObject({ action: "block" });
250
+ expect(jevCalls).toHaveLength(1);
251
+ expect(counters).toMatchObject({ total: 1, fail: 1, blocked: 1 });
252
+ });
253
+
254
+ test("a completion on an unassigned task reaches the judge", async () => {
255
+ jevScores(0.1);
256
+ const { ctx, counters, queries } = makeCtx(
257
+ { mode: "enforce" },
258
+ { row: taskRow({ agentId: null }) },
259
+ );
260
+ expect(await handler(completion("x"), ctx)).toMatchObject({ action: "block" });
261
+ expect(jevCalls).toHaveLength(1);
262
+ expect(counters).toMatchObject({ total: 1, fail: 1, blocked: 1 });
263
+ // Owner and unassigned cases never need the caller's agents row.
264
+ expect(queries).toHaveLength(1);
265
+ });
266
+
267
+ test("mode off never reads the task", async () => {
268
+ jevScores(0.1);
269
+ const { ctx, queries } = makeCtx({ mode: "off" });
270
+ expect(await handler(completion("x"), ctx)).toBeUndefined();
271
+ expect(queries).toHaveLength(0);
272
+ });
273
+ });
274
+
275
+ describe("block", () => {
276
+ test("enforce blocks a failing output once per task, then lets it through", async () => {
277
+ jevScores({ verbosity: 0.2, tone: 0.9, language: 0.95 });
278
+ const { ctx, kv, counters } = makeCtx({ mode: "enforce" });
279
+ const first = await handler(completion("Great question! Let me walk you through..."), ctx);
280
+ expect(first).toMatchObject({ action: "block" });
281
+ expect(first.reason).toContain("Alice's stated preferences");
282
+ expect(first.reason).toContain("verbosity (0.20 vs 0.5)");
283
+ expect(first.reason).toContain("very terse");
284
+ expect(counters).toMatchObject({ total: 1, fail: 1, blocked: 1, "dim:verbosity": 1 });
285
+ expect(counters[`blocks:${TASK_ID}`]).toBe(1);
286
+ expect(Object.values(kv).some((v: any) => v?.action === "blocked")).toBe(true);
287
+
288
+ const second = await handler(completion("Still long."), ctx);
289
+ expect(second).toBeUndefined();
290
+ expect(counters).toMatchObject({ total: 2, fail: 2, blocked: 1, escaped: 1 });
291
+ });
292
+
293
+ test("enforce with the default threshold passes a 0.6 score", async () => {
294
+ jevScores({ verbosity: 0.6, tone: 0.9, language: 0.95 });
295
+ const { ctx, counters } = makeCtx({ mode: "enforce" });
296
+ expect(await handler(completion("Picked B."), ctx)).toBeUndefined();
297
+ expect(counters).toMatchObject({ total: 1, pass: 1 });
298
+ expect(counters.blocked).toBeUndefined();
299
+ });
300
+
301
+ test("enforce with threshold 0.75 blocks a 0.6 score", async () => {
302
+ jevScores({ verbosity: 0.6, tone: 0.9, language: 0.95 });
303
+ const { ctx, counters } = makeCtx({ mode: "enforce", threshold: 0.75 });
304
+ const result = await handler(completion("Picked B."), ctx);
305
+ expect(result).toMatchObject({ action: "block" });
306
+ expect(result.reason).toContain("verbosity (0.60 vs 0.75)");
307
+ expect(counters).toMatchObject({ total: 1, fail: 1, blocked: 1 });
308
+ });
309
+
310
+ test("shadow records the failure and never blocks", async () => {
311
+ jevScores(0.1);
312
+ const { ctx, counters } = makeCtx();
313
+ expect(await handler(completion("x"), ctx)).toBeUndefined();
314
+ expect(counters).toMatchObject({ fail: 1, shadowed: 1 });
315
+ expect(counters.blocked).toBeUndefined();
316
+ });
317
+
318
+ test("openrouter carries the same request with its own model", async () => {
319
+ jevScores(0.1);
320
+ const { ctx } = makeCtx({ mode: "enforce" }, { key: "", openrouterKey: "or-test" });
321
+ const result = await handler(completion("x"), ctx);
322
+ expect(result).toMatchObject({ action: "block" });
323
+ expect(jevCalls[0]!.url).toBe("https://openrouter.ai/api/alpha/decisions");
324
+ expect(jevCalls[0]!.auth).toBe("Bearer or-test");
325
+ expect(jevCalls[0]!.body.model).toBe("typesafe/jev-1.13");
326
+ });
327
+ });
328
+
329
+ describe("fail open", () => {
330
+ const expectFailOpen = async (ctxBundle: ReturnType<typeof makeCtx>, pattern: RegExp) => {
331
+ const result = await handler(completion("x"), ctxBundle.ctx);
332
+ expect(result).toBeUndefined();
333
+ expect(ctxBundle.counters.error).toBe(1);
334
+ expect(String((ctxBundle.kv["error:last"] as any)?.error)).toMatch(pattern);
335
+ expect(ctxBundle.counters.blocked).toBeUndefined();
336
+ };
337
+
338
+ test("no key", async () => {
339
+ jevScores(0.1);
340
+ await expectFailOpen(
341
+ makeCtx({ mode: "enforce" }, { key: "" }),
342
+ /no TYPESAFE_API_KEY or OPENROUTER_API_KEY/,
343
+ );
344
+ expect(jevCalls).toHaveLength(0);
345
+ });
346
+
347
+ test("a redacted key counts as missing", async () => {
348
+ jevScores(0.1);
349
+ await expectFailOpen(
350
+ makeCtx({ mode: "enforce", provider: "typesafe" }, { key: "[REDACTED:TYPESAFE_API_KEY]" }),
351
+ /no TYPESAFE_API_KEY/,
352
+ );
353
+ });
354
+
355
+ test("HTTP error", async () => {
356
+ mockJev(() => new Response("nope", { status: 503 }));
357
+ await expectFailOpen(makeCtx({ mode: "enforce" }), /typesafe 503/);
358
+ });
359
+
360
+ test("network error", async () => {
361
+ mockJev(() => {
362
+ throw new Error("ECONNRESET");
363
+ });
364
+ await expectFailOpen(makeCtx({ mode: "enforce" }), /ECONNRESET/);
365
+ });
366
+
367
+ test("malformed answer", async () => {
368
+ mockJev(() => Response.json({ answers: { verbosity: { noul: "high" } } }));
369
+ await expectFailOpen(makeCtx({ mode: "enforce" }), /no scores in response/);
370
+ });
371
+
372
+ test("timeout", async () => {
373
+ // Honors the abort signal like real fetch; never answers on its own.
374
+ globalThis.fetch = ((_url: any, init: any) =>
375
+ new Promise<Response>((_, reject) => {
376
+ init?.signal?.addEventListener("abort", () => reject(new Error("aborted by signal")));
377
+ })) as typeof fetch;
378
+ await expectFailOpen(makeCtx({ mode: "enforce", timeoutMs: 10 }), /aborted by signal/);
379
+ });
380
+
381
+ test("DB read failure", async () => {
382
+ jevScores(0.1);
383
+ await expectFailOpen(makeCtx({ mode: "enforce" }, { dbThrows: true }), /db down/);
384
+ expect(jevCalls).toHaveLength(0);
385
+ });
386
+ });
387
+
388
+ describe("self-test", () => {
389
+ test("records both controls without judging real traffic", async () => {
390
+ mockJev((body) =>
391
+ Response.json({
392
+ answers: {
393
+ verbosity: { noul: String(body.state.agent_message).startsWith("Great") ? 0.1 : 0.9 },
394
+ },
395
+ }),
396
+ );
397
+ const { ctx, kv, queries, counters } = makeCtx();
398
+ const event = {
399
+ tool: "store-progress",
400
+ args: { taskId: TASK_ID, status: "in_progress", progress: "comms-gate-selftest now" },
401
+ requestInfo: { callOrigin: "mcp" },
402
+ };
403
+ expect(await handler(event, ctx)).toBeUndefined();
404
+ expect(kv["selftest:last"]).toMatchObject({
405
+ positiveControlBadScore: 0.1,
406
+ negativeControlGoodScore: 0.9,
407
+ controlsOk: true,
408
+ provider: "typesafe",
409
+ });
410
+ expect(queries).toHaveLength(0);
411
+ expect(counters.total).toBeUndefined();
412
+ });
413
+ });