@desplega.ai/agent-swarm 1.160.0 → 1.161.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 (285) hide show
  1. package/README.md +1 -1
  2. package/dist/{acp-adapter-2mjwcsfh.js → acp-adapter-xx489vvb.js} +163 -49
  3. package/dist/{actions-e43rxskr.js → actions-ykjkn0m9.js} +29 -28
  4. package/dist/{app-mc7xfagn.js → app-0hdqa4k1.js} +11 -10
  5. package/dist/{assistant-sedkzv3b.js → assistant-am2g5g8a.js} +30 -29
  6. package/dist/{azure-openai-responses-nfb21z9a.js → azure-openai-responses-fszbkp0e.js} +1 -1
  7. package/dist/{boot-reembed-cqz9swn5.js → boot-reembed-gt5gjkt4.js} +12 -11
  8. package/dist/{boot-reembed-m3656j2n.js → boot-reembed-xy9t7avg.js} +13 -12
  9. package/dist/{boot-scrub-logs-xbxf5d7w.js → boot-scrub-logs-9x0985y5.js} +10 -9
  10. package/dist/{boot-seeds-gsetbs5y.js → boot-seeds-trv0d4vg.js} +13 -12
  11. package/dist/{claude-adapter-dc6289mc.js → claude-adapter-jb6e1e54.js} +29 -23
  12. package/dist/{claude-managed-adapter-naaw51me.js → claude-managed-adapter-yxkfxg8j.js} +6 -5
  13. package/dist/{claude-managed-setup-eacvchx0.js → claude-managed-setup-d1q9hv2b.js} +25 -26
  14. package/dist/{claude-sdk-session-bqp2xvym.js → claude-sdk-session-zga700s2.js} +7180 -7102
  15. package/dist/{cli-9hsgwz4w.js → cli-0383bp7f.js} +941 -358
  16. package/dist/{cli-br0qdrnp.js → cli-0c7ata9h.js} +15 -15
  17. package/dist/{cli-btfv8y88.js → cli-121ph74b.js} +2 -2
  18. package/dist/{cli-a8dvhzrt.js → cli-17xpf7cm.js} +4 -4
  19. package/dist/{cli-qydf22qq.js → cli-1cz0c18q.js} +59 -20
  20. package/dist/{cli-tpq83z89.js → cli-1er0rhz8.js} +12 -11
  21. package/dist/{cli-4derpgyk.js → cli-2hhfhfq5.js} +6 -1
  22. package/dist/{cli-rykfmgwg.js → cli-2x0e561b.js} +1 -1
  23. package/dist/{cli-0x01kwrd.js → cli-35pj2yva.js} +11 -177
  24. package/dist/{cli-tfky2838.js → cli-3zc55ctx.js} +4 -4
  25. package/dist/{cli-c8a36vcc.js → cli-5p3x4zna.js} +2 -2
  26. package/dist/{cli-0jwe9034.js → cli-7119ja3w.js} +3 -3
  27. package/dist/{cli-7e398j1h.js → cli-798vkbks.js} +2 -2
  28. package/dist/{cli-v7fn5gms.js → cli-82c1pacn.js} +100 -46
  29. package/dist/{cli-f8gy6r2r.js → cli-8g6mc14k.js} +7 -7
  30. package/dist/{cli-ydmcgve1.js → cli-9m03pzga.js} +4 -4
  31. package/dist/cli-9s68w752.js +303 -0
  32. package/dist/{cli-0q3avq3s.js → cli-a8c82p2t.js} +12 -11
  33. package/dist/{cli-xjcywfsf.js → cli-ad4qxm6j.js} +12 -11
  34. package/dist/{cli-ktmyrtpn.js → cli-akfj32vj.js} +29 -7
  35. package/dist/{cli-447dt1h3.js → cli-bh7jed3k.js} +187 -10
  36. package/dist/{cli-0z2v4nhw.js → cli-cat6h73s.js} +1 -1
  37. package/dist/{cli-shkxm1vn.js → cli-cfkrztza.js} +188 -12
  38. package/dist/{cli-jz4w45qt.js → cli-d56tgd45.js} +190 -50
  39. package/dist/cli-dbmwby6a.js +9 -0
  40. package/dist/{cli-vctdqs5h.js → cli-ecfxwnyb.js} +15 -2
  41. package/dist/{cli-10nhj6xq.js → cli-ee8bc46j.js} +12 -12
  42. package/dist/cli-f9w1semm.js +413 -0
  43. package/dist/{cli-6cs6wzxt.js → cli-hgmnf38m.js} +2 -2
  44. package/dist/{cli-25fcq4b4.js → cli-hn04t9db.js} +2 -2
  45. package/dist/{cli-741yb0xx.js → cli-jpf7e1zq.js} +55 -169
  46. package/dist/{cli-kmcpfwn2.js → cli-jyy6pm2f.js} +19 -3
  47. package/dist/{cli-evvtk35y.js → cli-kgd3p0bm.js} +6 -6
  48. package/dist/cli-ma3zvh39.js +10404 -0
  49. package/dist/{cli-r2ap2czm.js → cli-nexqx4ve.js} +6 -0
  50. package/dist/{cli-zpep9q5e.js → cli-nsdxtcbg.js} +17 -15
  51. package/dist/{cli-azhgzetg.js → cli-phfg8vy8.js} +10 -10
  52. package/dist/{cli-mfe5btp9.js → cli-qwsnmjdd.js} +173 -84
  53. package/dist/{cli-jajnsm8q.js → cli-r6fsxc76.js} +18004 -30741
  54. package/dist/{cli-g2whhdrf.js → cli-rtx9dygp.js} +1 -1
  55. package/dist/{cli-12tszpk9.js → cli-sqm3r5ps.js} +81 -15
  56. package/dist/{cli-pm1rst0h.js → cli-t6v2ah3r.js} +27 -12
  57. package/dist/{cli-jyk0ynw2.js → cli-t845n9xk.js} +23 -10
  58. package/dist/{cli-74h9hscv.js → cli-tre4bdgq.js} +1 -1
  59. package/dist/{cli-tmqt7dpj.js → cli-tsk3nz8a.js} +11 -1
  60. package/dist/{cli-phsaj4xq.js → cli-v0pvvhfq.js} +1 -1
  61. package/dist/{cli-d5czme6m.js → cli-vnnz5ncg.js} +4572 -4842
  62. package/dist/{cli-cpvsaqsw.js → cli-w0bwte75.js} +8 -5
  63. package/dist/{cli-tzx2ke1v.js → cli-we0q7t0e.js} +3 -3
  64. package/dist/{cli-r94z9p1z.js → cli-wgbezhw2.js} +3 -0
  65. package/dist/{cli-0wgxmwf6.js → cli-xk79303e.js} +20 -7
  66. package/dist/{cli-ygwm91nt.js → cli-yy6dew39.js} +1 -1
  67. package/dist/{cli-523679eh.js → cli-zb6xbk61.js} +2 -1
  68. package/dist/{cli-jayjpjmb.js → cli-ze1d3wzv.js} +66 -23
  69. package/dist/cli.js +26 -25
  70. package/dist/{codex-adapter-nmxcatww.js → codex-adapter-04v97vep.js} +17 -16
  71. package/dist/{codex-login-w6fz4xbz.js → codex-login-cf7mzsgg.js} +2 -2
  72. package/dist/{codex-session-runner-90aa80ev.js → codex-session-runner-ax69mqq3.js} +17 -16
  73. package/dist/{commands-8wcq1kn7.js → commands-18gy08n1.js} +10 -9
  74. package/dist/{db-j40kc4se.js → db-d2pvjpyt.js} +12 -9
  75. package/dist/{dispatcher-4zqy768d.js → dispatcher-8wbwyzjg.js} +28 -27
  76. package/dist/{dsh-adapter-mpnzgztj.js → dsh-adapter-ysqk9snv.js} +9 -4
  77. package/dist/{e2b-ez07znam.js → e2b-esmt1r16.js} +3 -1
  78. package/dist/{execute-6e7qeyk4.js → execute-cmagrbrz.js} +177 -46
  79. package/dist/{handlers-kjdce1w5.js → handlers-6b69m5kp.js} +31 -30
  80. package/dist/{hook-czx13n12.js → hook-k3mydf2n.js} +14 -13
  81. package/dist/{hook-r2t4bqfg.js → hook-srnkt0hb.js} +14 -13
  82. package/dist/{http-3bbb19be.js → http-hrf80ca1.js} +532 -163
  83. package/dist/{index-fg0dsp6c.js → index-1asbaj62.js} +28 -27
  84. package/dist/{index-kkv84jq9.js → index-45evq4ee.js} +28 -27
  85. package/dist/{index-668me3f0.js → index-9rxdvj6w.js} +29 -28
  86. package/dist/{index-7aj368tw.js → index-k4nj18qs.js} +12 -11
  87. package/dist/{index-zg7vp46x.js → index-xmevk2b9.js} +52 -28
  88. package/dist/{keepalive-yra8dy57.js → keepalive-sfdz6ksp.js} +12 -11
  89. package/dist/{lead-va0pad2c.js → lead-ej91e9hw.js} +46 -45
  90. package/dist/{maintenance-hqewtmyg.js → maintenance-80dnd7ke.js} +13 -12
  91. package/dist/node-f0ykebbv.js +2107 -0
  92. package/dist/node-wyamey5r.js +19 -0
  93. package/dist/{oauth-refresh-sweep-7rdt1m9n.js → oauth-refresh-sweep-m1etk2mx.js} +11 -10
  94. package/dist/{onboard-j6ms6d0x.js → onboard-0q8sdfqc.js} +2 -2
  95. package/dist/{openai-codex-responses-bbancrr0.js → openai-codex-responses-n7n683p2.js} +1 -1
  96. package/dist/{openai-responses-200k1wx4.js → openai-responses-9a3nme9j.js} +1 -1
  97. package/dist/{opencode-adapter-89rabkw2.js → opencode-adapter-4wcgmvgf.js} +11 -8
  98. package/dist/{otel-impl-a792rjz0.js → otel-impl-hnqm9mxq.js} +2 -2
  99. package/dist/pi-logo-animation-e9a0t68z.js +1003 -0
  100. package/dist/{pi-mono-adapter-g7qzvr3v.js → pi-mono-adapter-d6sqm9wr.js} +40 -30
  101. package/dist/{pricing-refresh-wx0tp7cb.js → pricing-refresh-y60z64pm.js} +12 -11
  102. package/dist/{rbac-roles-8tzawacv.js → rbac-roles-8pa0754b.js} +11 -10
  103. package/dist/{render-v2-mm7zswb8.js → render-v2-418s1vj7.js} +11 -10
  104. package/dist/{runtime-zyssgse0.js → runtime-3pknc168.js} +5 -4
  105. package/dist/{setup-8y70ys6t.js → setup-yghdkj4x.js} +2 -2
  106. package/dist/{virtual-modules-6nmk7h3n.js → virtual-modules-jcceevt5.js} +7 -6
  107. package/dist/{work-objects-fg6xkb0n.js → work-objects-j1sz20v9.js} +31 -30
  108. package/dist/{worker-wmwhxsph.js → worker-d4gcvkjt.js} +46 -45
  109. package/dist/{x-n3e0v3xa.js → x-f1gt1pz3.js} +2 -2
  110. package/openapi.json +209 -15
  111. package/package.json +12 -11
  112. package/src/azure-devops/api.ts +121 -0
  113. package/src/azure-devops/auth.ts +78 -0
  114. package/src/azure-devops/handlers.ts +413 -0
  115. package/src/azure-devops/index.ts +27 -0
  116. package/src/azure-devops/templates.ts +108 -0
  117. package/src/azure-devops/types.ts +90 -0
  118. package/src/be/db/tasks/read.ts +1 -1
  119. package/src/be/db/tasks/write.ts +16 -0
  120. package/src/be/db-client.ts +11 -2
  121. package/src/be/db-retention.ts +95 -32
  122. package/src/be/db.ts +2 -1
  123. package/src/be/memory/constants.ts +16 -0
  124. package/src/be/memory/graph-expansion.ts +8 -1
  125. package/src/be/memory/index-content.ts +20 -1
  126. package/src/be/memory/key-guard.ts +93 -0
  127. package/src/be/memory/key-paths.ts +103 -0
  128. package/src/be/memory/providers/sqlite-store.ts +152 -45
  129. package/src/be/memory/raters/llm-summarizer.ts +10 -4
  130. package/src/be/memory/raters/llm.ts +15 -5
  131. package/src/be/memory/raters/store.ts +6 -3
  132. package/src/be/memory/raters/types.ts +11 -0
  133. package/src/be/memory/reranker.ts +38 -7
  134. package/src/be/memory/types.ts +12 -1
  135. package/src/be/migrations/190_memory_rating_model.sql +7 -0
  136. package/src/be/migrations/191_context_versions_previous_index.sql +8 -0
  137. package/src/be/pricing-normalize.ts +4 -2
  138. package/src/be/scripts/typecheck.ts +4 -3
  139. package/src/be/seed-pricing.ts +16 -0
  140. package/src/be/swarm-config-guard.ts +11 -1
  141. package/src/commands/claude-managed-setup.ts +33 -31
  142. package/src/commands/provider-credentials.ts +28 -15
  143. package/src/commands/runner.ts +198 -44
  144. package/src/e2b/env.ts +2 -0
  145. package/src/extensions/catalog.generated.json +17 -0
  146. package/src/extensions/contract-types.generated.ts +1 -1
  147. package/src/github/handlers.ts +10 -74
  148. package/src/github/reactions.ts +119 -138
  149. package/src/github/task-reactions.ts +34 -11
  150. package/src/hooks/hook.ts +1 -1
  151. package/src/http/agents.ts +1 -1
  152. package/src/http/core.ts +12 -0
  153. package/src/http/extensions.ts +51 -27
  154. package/src/http/index.ts +4 -0
  155. package/src/http/mcp-user.ts +8 -13
  156. package/src/http/mcp.ts +19 -12
  157. package/src/http/memory.ts +80 -14
  158. package/src/http/session-data.ts +6 -4
  159. package/src/http/stats.ts +1 -0
  160. package/src/http/tasks.ts +17 -3
  161. package/src/http/users.ts +1 -1
  162. package/src/http/webhooks.ts +120 -2
  163. package/src/providers/claude-adapter.ts +84 -16
  164. package/src/providers/claude-managed-adapter.ts +29 -2
  165. package/src/providers/codex-adapter.ts +1 -1
  166. package/src/providers/codex-skill-resolver.ts +6 -0
  167. package/src/providers/dsh-adapter.ts +203 -6
  168. package/src/providers/opencode-adapter.ts +24 -3
  169. package/src/providers/pi-mono-adapter.ts +31 -5
  170. package/src/providers/pi-mono-extension.ts +1 -1
  171. package/src/providers/pricing-sources.md +5 -1
  172. package/src/providers/reasoning-effort.ts +10 -2
  173. package/src/providers/types.ts +1 -1
  174. package/src/rbac/legacy-policy.ts +2 -0
  175. package/src/rbac/permissions.ts +10 -0
  176. package/src/scripts-runtime/types/stdlib.d.ts +13 -5
  177. package/src/scripts-runtime/types/swarm-extension.d.ts +4 -2
  178. package/src/scripts-runtime/types/swarm-sdk.d.ts +13 -5
  179. package/src/tasks/context-key.ts +38 -1
  180. package/src/tests/azure-devops-auth.test.ts +100 -0
  181. package/src/tests/azure-devops-handlers.test.ts +324 -0
  182. package/src/tests/azure-devops-webhook-malformed.test.ts +96 -0
  183. package/src/tests/claude-default-route.test.ts +260 -0
  184. package/src/tests/claude-managed-adapter.test.ts +47 -0
  185. package/src/tests/claude-managed-setup.test.ts +48 -0
  186. package/src/tests/claude-prompt-body-once.test.ts +117 -0
  187. package/src/tests/claude-routes-e2e.test.ts +316 -0
  188. package/src/tests/context-versions-retention.test.ts +314 -0
  189. package/src/tests/core-auth.test.ts +7 -0
  190. package/src/tests/db-retention.test.ts +14 -14
  191. package/src/tests/db-task-read-contract.test.ts +1 -0
  192. package/src/tests/db-task-write-contract.test.ts +2 -0
  193. package/src/tests/docs-index-footprint.test.js +3 -3
  194. package/src/tests/dsh-adapter.test.ts +133 -1
  195. package/src/tests/dsh-usage.test.ts +153 -0
  196. package/src/tests/extensions-catalog.test.ts +1 -0
  197. package/src/tests/extensions-example-deploy-awareness.test.ts +301 -0
  198. package/src/tests/extensions-http.test.ts +137 -10
  199. package/src/tests/extensions-mcp-tools.test.ts +28 -0
  200. package/src/tests/fetch-resolved-env.test.ts +24 -0
  201. package/src/tests/fixtures/azure-devops/pull-request-commented.json +129 -0
  202. package/src/tests/fixtures/azure-devops/pull-request-created.json +88 -0
  203. package/src/tests/fixtures/fake-claude-routes.ts +229 -0
  204. package/src/tests/github-handlers-reactions.test.ts +486 -0
  205. package/src/tests/github-reactions.test.ts +280 -0
  206. package/src/tests/harness-cli-version.test.ts +62 -0
  207. package/src/tests/http-api-integration.test.ts +21 -0
  208. package/src/tests/http-task-write-ownership.test.ts +24 -1
  209. package/src/tests/internal-ai/complete-structured.test.ts +94 -0
  210. package/src/tests/internal-ai/credentials.test.ts +22 -0
  211. package/src/tests/internal-ai/models.test.ts +36 -2
  212. package/src/tests/internal-ai/schema-parity.test.ts +25 -1
  213. package/src/tests/internal-ai/summarize-session.test.ts +23 -7
  214. package/src/tests/mcp-user-route.test.ts +133 -0
  215. package/src/tests/memory-key-paths.test.ts +1055 -0
  216. package/src/tests/memory-rate-endpoint.test.ts +66 -0
  217. package/src/tests/memory-rater-llm-summarizer.test.ts +2 -1
  218. package/src/tests/memory-rater-llm.test.ts +73 -0
  219. package/src/tests/memory-rater-store.test.ts +31 -0
  220. package/src/tests/memory-reranker.test.ts +40 -0
  221. package/src/tests/opencode-adapter.test.ts +57 -0
  222. package/src/tests/opencode-plugin.test.ts +4 -0
  223. package/src/tests/pi-codemode.test.ts +82 -4
  224. package/src/tests/preload.ts +6 -0
  225. package/src/tests/provider-registration.test.ts +0 -2
  226. package/src/tests/providers/pi-cost.test.ts +190 -4
  227. package/src/tests/rbac-engine.test.ts +6 -1
  228. package/src/tests/runner-slot-reconcile.test.ts +199 -0
  229. package/src/tests/script-connections.test.ts +4 -0
  230. package/src/tests/session-costs-recompute-all-providers.test.ts +23 -0
  231. package/src/tests/task-reactions.test.ts +29 -6
  232. package/src/tests/vcs-azure-devops.test.ts +73 -0
  233. package/src/tests/webhooks-task-blocked.test.ts +23 -0
  234. package/src/tests/workflow-llm.test.ts +14 -0
  235. package/src/tools/context-diff.ts +20 -0
  236. package/src/tools/extension-install.ts +27 -16
  237. package/src/tools/memory-edit.ts +47 -3
  238. package/src/tools/memory-search.ts +13 -1
  239. package/src/tools/memory-store.ts +55 -3
  240. package/src/types.ts +43 -7
  241. package/src/utils/credentials.test.ts +31 -1
  242. package/src/utils/credentials.ts +20 -9
  243. package/src/utils/dsh-usage.ts +53 -0
  244. package/src/utils/harness-cli-version.ts +6 -4
  245. package/src/utils/internal-ai/complete-structured.ts +26 -3
  246. package/src/utils/internal-ai/credentials.ts +23 -9
  247. package/src/utils/internal-ai/index.ts +8 -1
  248. package/src/utils/internal-ai/models.ts +34 -8
  249. package/src/utils/internal-ai/summarize-session.ts +22 -8
  250. package/src/utils/secret-scrubber.ts +8 -0
  251. package/src/vcs/azure-devops.ts +80 -0
  252. package/src/vcs/index.ts +5 -1
  253. package/src/vcs/types.ts +2 -2
  254. package/templates/community/open-prompt-showreel/PLAYBOOK.md +336 -0
  255. package/templates/community/open-prompt-showreel/README.md +33 -0
  256. package/templates/community/open-prompt-showreel/example/tla-races/facts.ts +145 -0
  257. package/templates/community/open-prompt-showreel/example/tla-races/facts.v3.json +140 -0
  258. package/templates/community/open-prompt-showreel/example/tla-races/music.mist-city.json +8 -0
  259. package/templates/community/open-prompt-showreel/example/tla-races/reel.html +805 -0
  260. package/templates/community/open-prompt-showreel/example/tla-races/smoke.sh +48 -0
  261. package/templates/community/open-prompt-showreel/lead-prompt.md +89 -0
  262. package/templates/community/open-prompt-showreel/setup.sh +167 -0
  263. package/templates/community/open-prompt-showreel/skills/motion-design-replication/SKILL.md +85 -0
  264. package/templates/community/open-prompt-showreel/skills/motion-design-video-analysis/SKILL.md +115 -0
  265. package/templates/community/open-prompt-showreel/skills/open-prompt-showreel/SKILL.md +147 -0
  266. package/templates/community/open-prompt-showreel/skills/video-generation/SKILL.md +195 -0
  267. package/templates/community/open-prompt-showreel/tools/fetch-music.sh +65 -0
  268. package/templates/community/open-prompt-showreel/tools/music-analyze.py +69 -0
  269. package/templates/community/open-prompt-showreel/tools/music-current.sh +34 -0
  270. package/templates/community/open-prompt-showreel/tools/music-cut.sh +36 -0
  271. package/templates/community/open-prompt-showreel/tools/music-refine.py +39 -0
  272. package/templates/community/open-prompt-showreel/tools/render.mjs +157 -0
  273. package/templates/community/open-prompt-showreel/tools/sheet.sh +44 -0
  274. package/templates/community/open-prompt-showreel/tools/web-compress.sh +17 -0
  275. package/templates/community/open-prompt-showreel/tools/x-reencode.sh +21 -0
  276. package/templates/extensions/README.md +1 -0
  277. package/templates/extensions/deploy-awareness/README.md +28 -0
  278. package/templates/extensions/deploy-awareness/hooks.ts +152 -0
  279. package/templates/extensions/deploy-awareness/manifest.json +8 -0
  280. package/templates/skills/memory/config.json +2 -2
  281. package/templates/skills/memory/content.md +59 -5
  282. package/templates/skills/swarm-extensions/config.json +1 -1
  283. package/templates/skills/swarm-extensions/content.md +28 -5
  284. package/dist/cli-j490egb5.js +0 -5632
  285. package/dist/{cli-r1mxd1xh.js → cli-yrg7c73k.js} +4 -4
@@ -0,0 +1,260 @@
1
+ /**
2
+ * Issue #1800: a claude worker on a LiteLLM gateway, Microsoft Foundry,
3
+ * Bedrock, or Vertex passes the credential gate with no Anthropic credential,
4
+ * and its gateway key never reaches api.anthropic.com.
5
+ */
6
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
7
+ import {
8
+ buildCredStatusReport,
9
+ checkProviderCredentials,
10
+ validateProviderCredentials,
11
+ } from "../commands/provider-credentials";
12
+ import {
13
+ buildClaudeSessionEnvironment,
14
+ checkClaudeCredentials,
15
+ resolveClaudeBinaryArgv,
16
+ withClaudeRouteEnv,
17
+ } from "../providers/claude-adapter";
18
+ import type { ProviderSessionConfig } from "../providers/types";
19
+ import { resolveCredentialPools, validateClaudeCredentials } from "../utils/credentials";
20
+ import { resolveCredential } from "../utils/internal-ai/credentials";
21
+
22
+ const GATEWAY_URL = "http://litellm.internal:4000";
23
+ const UNTRUSTED_URL = { ANTHROPIC_BASE_URL: "https://untrusted.example" };
24
+ const LITELLM_BEARER = { ANTHROPIC_BASE_URL: GATEWAY_URL, ANTHROPIC_AUTH_TOKEN: "sk-litellm" };
25
+ const LITELLM_API_KEY = { ANTHROPIC_BASE_URL: GATEWAY_URL, ANTHROPIC_API_KEY: "sk-litellm" };
26
+ const FOUNDRY = {
27
+ CLAUDE_CODE_USE_FOUNDRY: "1",
28
+ ANTHROPIC_FOUNDRY_RESOURCE: "my-foundry",
29
+ ANTHROPIC_FOUNDRY_API_KEY: "az-key",
30
+ };
31
+ const BEDROCK = { CLAUDE_CODE_USE_BEDROCK: "1", AWS_REGION: "us-east-1" };
32
+ const VERTEX = {
33
+ CLAUDE_CODE_USE_VERTEX: "1",
34
+ CLOUD_ML_REGION: "us-east5",
35
+ ANTHROPIC_VERTEX_PROJECT_ID: "proj",
36
+ };
37
+ const OAUTH = { CLAUDE_CODE_OAUTH_TOKEN: "sk-ant-oat01-example" };
38
+
39
+ /** Records every request, whichever fetch the code under test reaches for. */
40
+ let requests: string[] = [];
41
+ let realFetch: typeof fetch;
42
+ const recordingFetch = (async (url: string | URL | Request) => {
43
+ requests.push(String(url instanceof Request ? url.url : url));
44
+ return new Response(JSON.stringify({ data: [] }), { status: 200 });
45
+ }) as typeof fetch;
46
+
47
+ beforeEach(() => {
48
+ requests = [];
49
+ realFetch = globalThis.fetch;
50
+ globalThis.fetch = recordingFetch;
51
+ });
52
+
53
+ afterEach(() => {
54
+ globalThis.fetch = realFetch;
55
+ });
56
+
57
+ const anthropicRequests = () => requests.filter((url) => url.includes("api.anthropic.com"));
58
+
59
+ describe("claude credential gate", () => {
60
+ for (const [name, env] of Object.entries({
61
+ "LiteLLM bearer": LITELLM_BEARER,
62
+ "LiteLLM api key": LITELLM_API_KEY,
63
+ Foundry: FOUNDRY,
64
+ Bedrock: BEDROCK,
65
+ Vertex: VERTEX,
66
+ })) {
67
+ test(`${name} reports ready without an Anthropic credential`, async () => {
68
+ expect(checkClaudeCredentials(env)).toMatchObject({ ready: true, missing: [] });
69
+ expect((await checkProviderCredentials("claude", env)).ready).toBe(true);
70
+ });
71
+ }
72
+
73
+ test("no route lists the first-party credentials and mentions the alternatives", () => {
74
+ const status = checkClaudeCredentials({});
75
+ expect(status.ready).toBe(false);
76
+ expect(status.missing).toEqual(["CLAUDE_CODE_OAUTH_TOKEN", "ANTHROPIC_API_KEY"]);
77
+ expect(status.hint).toContain("ANTHROPIC_AUTH_TOKEN");
78
+ expect(status.hint).toContain("CLAUDE_CODE_USE_FOUNDRY");
79
+ });
80
+
81
+ test("an incomplete Foundry route names its missing resource", () => {
82
+ expect(checkClaudeCredentials({ CLAUDE_CODE_USE_FOUNDRY: "1", ...OAUTH })).toMatchObject({
83
+ ready: false,
84
+ missing: ["ANTHROPIC_FOUNDRY_RESOURCE"],
85
+ });
86
+ });
87
+ });
88
+
89
+ describe("claude live check", () => {
90
+ test("gateway keys are checked against the gateway, never api.anthropic.com", async () => {
91
+ for (const env of [LITELLM_BEARER, LITELLM_API_KEY, { ...LITELLM_API_KEY, ...OAUTH }]) {
92
+ const result = await validateProviderCredentials("claude", env);
93
+ expect(result.ok).toBe(true);
94
+ }
95
+ expect(requests).toEqual([
96
+ `${GATEWAY_URL}/v1/models`,
97
+ `${GATEWAY_URL}/v1/models`,
98
+ `${GATEWAY_URL}/v1/models`,
99
+ ]);
100
+ expect(anthropicRequests()).toEqual([]);
101
+ });
102
+
103
+ test("Foundry makes no request and is reported as configured, not verified", async () => {
104
+ const result = await validateProviderCredentials("claude", FOUNDRY);
105
+ expect(result).toMatchObject({ ok: true, skipped: true });
106
+ const report = await buildCredStatusReport("claude", FOUNDRY, {}, "boot");
107
+ expect(report.ready).toBe(true);
108
+ expect(report.liveTest).toBeNull();
109
+ expect(requests).toEqual([]);
110
+ });
111
+
112
+ test("a rejected gateway key fails the live test", async () => {
113
+ const reject = (async () =>
114
+ new Response("bad key", { status: 401 })) as unknown as typeof fetch;
115
+ globalThis.fetch = reject;
116
+ const result = await validateProviderCredentials("claude", LITELLM_BEARER);
117
+ expect(result.ok).toBe(false);
118
+ expect(result.error).toContain("HTTP 401");
119
+ });
120
+
121
+ test("buildCredStatusReport checks the env it was given, not only process.env", async () => {
122
+ const report = await buildCredStatusReport("claude", LITELLM_BEARER, {}, "post_task");
123
+ expect(report.liveTest?.ok).toBe(true);
124
+ expect(requests).toEqual([`${GATEWAY_URL}/v1/models`]);
125
+ });
126
+
127
+ test("an Anthropic API key without a gateway still checks api.anthropic.com", async () => {
128
+ await validateProviderCredentials("claude", { ANTHROPIC_API_KEY: "sk-ant" });
129
+ expect(requests).toEqual(["https://api.anthropic.com/v1/models"]);
130
+ });
131
+ });
132
+
133
+ describe("claude spawn env", () => {
134
+ const config = (env: Record<string, string>) =>
135
+ ({
136
+ taskId: crypto.randomUUID(),
137
+ agentId: crypto.randomUUID(),
138
+ env,
139
+ apiUrl: "http://fixture.invalid",
140
+ apiKey: "example-fixture-swarm-key",
141
+ }) as ProviderSessionConfig;
142
+ const spawnEnv = (env: Record<string, string>) =>
143
+ buildClaudeSessionEnvironment(config(env), "sonnet", "/tmp/fixture-task").env;
144
+ /** The spawned env carries the token under no key, and OAuth is explicitly blank. */
145
+ const expectNoSubscriptionToken = (env: Record<string, string>, token: string) => {
146
+ expect(env.CLAUDE_CODE_OAUTH_TOKEN).toBe("");
147
+ expect(Object.values(env).filter((value) => value?.includes(token))).toEqual([]);
148
+ };
149
+
150
+ test("a gateway route blanks the OAuth token so it cannot reach the gateway", () => {
151
+ const { env } = buildClaudeSessionEnvironment(
152
+ config({ ...LITELLM_BEARER, ...OAUTH }),
153
+ "sonnet",
154
+ "/tmp/fixture-task",
155
+ );
156
+ expect(env.CLAUDE_CODE_OAUTH_TOKEN).toBe("");
157
+ expect(env.ANTHROPIC_AUTH_TOKEN).toBe("sk-litellm");
158
+ expect(env.ANTHROPIC_BASE_URL).toBe(GATEWAY_URL);
159
+ });
160
+
161
+ test("cloud routes blank the OAuth token too", () => {
162
+ for (const route of [FOUNDRY, BEDROCK, VERTEX]) {
163
+ expect(withClaudeRouteEnv({ ...route, ...OAUTH }).CLAUDE_CODE_OAUTH_TOKEN).toBe("");
164
+ }
165
+ });
166
+
167
+ test("the subscription route keeps the OAuth token", () => {
168
+ const { env } = buildClaudeSessionEnvironment(config(OAUTH), "sonnet", "/tmp/fixture-task");
169
+ expect(env.CLAUDE_CODE_OAUTH_TOKEN).toBe(OAUTH.CLAUDE_CODE_OAUTH_TOKEN);
170
+ });
171
+
172
+ test("arbitrary URL + OAuth + no gateway key parks and spawns without the token", async () => {
173
+ const env = { ...UNTRUSTED_URL, ...OAUTH };
174
+ expect(checkClaudeCredentials(env)).toMatchObject({
175
+ ready: false,
176
+ missing: ["ANTHROPIC_AUTH_TOKEN"],
177
+ });
178
+ expect(() => validateClaudeCredentials(env)).toThrow("ANTHROPIC_AUTH_TOKEN");
179
+ expect((await validateProviderCredentials("claude", env)).ok).toBe(false);
180
+ expect((await buildCredStatusReport("claude", env, {}, "boot")).liveTest).toBeNull();
181
+ expect(requests).toEqual([]);
182
+ expectNoSubscriptionToken(spawnEnv(env), OAUTH.CLAUDE_CODE_OAUTH_TOKEN);
183
+ });
184
+
185
+ test("an OAuth token picked from the pool never reaches a keyless gateway", async () => {
186
+ const env: Record<string, string> = {
187
+ ...UNTRUSTED_URL,
188
+ CLAUDE_CODE_OAUTH_TOKEN: "sk-ant-oat01-pool-a,sk-ant-oat01-pool-b",
189
+ };
190
+ await resolveCredentialPools(env, { provider: "claude" });
191
+ expect(env.CLAUDE_CODE_OAUTH_TOKEN).toMatch(/^sk-ant-oat01-pool-[ab]$/);
192
+ expect(checkClaudeCredentials(env).ready).toBe(false);
193
+ expectNoSubscriptionToken(spawnEnv(env), "sk-ant-oat01-pool");
194
+ });
195
+
196
+ test("an OAuth token inherited from process.env is blanked, not revived", () => {
197
+ const previous = process.env.CLAUDE_CODE_OAUTH_TOKEN;
198
+ process.env.CLAUDE_CODE_OAUTH_TOKEN = OAUTH.CLAUDE_CODE_OAUTH_TOKEN;
199
+ try {
200
+ const env = withClaudeRouteEnv({ ...UNTRUSTED_URL, SWARM_USE_CLAUDE_BRIDGE: "true" });
201
+ expect(env.CLAUDE_CODE_OAUTH_TOKEN).toBe("");
202
+ expect(resolveClaudeBinaryArgv(env).useClaudeBridge).toBe(false);
203
+ expectNoSubscriptionToken(
204
+ spawnEnv({ ...UNTRUSTED_URL, SWARM_USE_CLAUDE_BRIDGE: "true" }),
205
+ OAUTH.CLAUDE_CODE_OAUTH_TOKEN,
206
+ );
207
+ } finally {
208
+ if (previous === undefined) delete process.env.CLAUDE_CODE_OAUTH_TOKEN;
209
+ else process.env.CLAUDE_CODE_OAUTH_TOKEN = previous;
210
+ }
211
+ });
212
+
213
+ test("a gateway key cleared after being set does not fall back to OAuth", () => {
214
+ const env: Record<string, string> = { ...UNTRUSTED_URL, ...OAUTH, ANTHROPIC_AUTH_TOKEN: "k" };
215
+ expect(checkClaudeCredentials(env).ready).toBe(true);
216
+ expectNoSubscriptionToken(spawnEnv(env), OAUTH.CLAUDE_CODE_OAUTH_TOKEN);
217
+
218
+ for (const cleared of [
219
+ { ...env, ANTHROPIC_AUTH_TOKEN: "" },
220
+ Object.fromEntries(Object.entries(env).filter(([key]) => key !== "ANTHROPIC_AUTH_TOKEN")),
221
+ ]) {
222
+ expect(checkClaudeCredentials(cleared)).toMatchObject({
223
+ ready: false,
224
+ missing: ["ANTHROPIC_AUTH_TOKEN"],
225
+ });
226
+ expectNoSubscriptionToken(spawnEnv(cleared), OAUTH.CLAUDE_CODE_OAUTH_TOKEN);
227
+ }
228
+ });
229
+
230
+ test("claude-bridge stays off on a gateway route even when process.env has OAuth", () => {
231
+ const env = withClaudeRouteEnv({
232
+ ...LITELLM_BEARER,
233
+ ...OAUTH,
234
+ SWARM_USE_CLAUDE_BRIDGE: "true",
235
+ });
236
+ const argv = resolveClaudeBinaryArgv(env, { ...OAUTH });
237
+ expect(argv.useClaudeBridge).toBe(false);
238
+ });
239
+ });
240
+
241
+ describe("internal-ai credential", () => {
242
+ const opts = (env: NodeJS.ProcessEnv) => ({
243
+ env,
244
+ _getEnvApiKey: () => undefined,
245
+ _getValidCodexOAuth: async () => null,
246
+ _getOAuthApiKey: async () => null,
247
+ _persistCodexOAuth: async () => undefined,
248
+ });
249
+
250
+ test("a gateway's ANTHROPIC_API_KEY is not used for api.anthropic.com calls", async () => {
251
+ expect(await resolveCredential(opts(LITELLM_API_KEY))).toBeNull();
252
+ });
253
+
254
+ test("an ANTHROPIC_API_KEY with a first-party base URL is still used", async () => {
255
+ const cred = await resolveCredential(
256
+ opts({ ANTHROPIC_BASE_URL: "https://api.anthropic.com", ANTHROPIC_API_KEY: "sk-ant" }),
257
+ );
258
+ expect(cred?.kind).toBe("anthropic");
259
+ });
260
+ });
@@ -244,6 +244,53 @@ describe("ClaudeManagedAdapter (Phase 3) — session lifecycle", () => {
244
244
  expect(a[1]?.text).not.toBe(b[1]?.text);
245
245
  });
246
246
 
247
+ test("agent.message with a redacted block keeps a visible placeholder instead of dropping it", async () => {
248
+ const events: Array<Record<string, unknown>> = [
249
+ {
250
+ type: "agent.message",
251
+ id: "evt1",
252
+ processed_at: "2026-01-01T00:00:01Z",
253
+ content: [
254
+ { type: "text", text: "before " },
255
+ { type: "redacted" },
256
+ { type: "text", text: " after" },
257
+ ],
258
+ },
259
+ {
260
+ type: "agent.message",
261
+ id: "evt2",
262
+ processed_at: "2026-01-01T00:00:02Z",
263
+ content: [{ type: "redacted" }],
264
+ },
265
+ {
266
+ type: "session.status_idle",
267
+ id: "evt3",
268
+ processed_at: "2026-01-01T00:00:03Z",
269
+ stop_reason: { type: "end_turn" },
270
+ },
271
+ ];
272
+ const spy = makeFakeClient({
273
+ streamEvents: async function* () {
274
+ for (const e of events) yield e;
275
+ },
276
+ });
277
+ const adapter = new ClaudeManagedAdapter({ client: spy.client });
278
+ const session = await adapter.createSession(
279
+ tConfig({ logFile: join(tmpLogDir, "redacted.log") }),
280
+ );
281
+ const emitted: ProviderEvent[] = [];
282
+ session.onEvent((e) => emitted.push(e));
283
+ await session.waitForCompletion();
284
+
285
+ const messages = emitted.flatMap((e) =>
286
+ e.type === "message" && e.role === "assistant" ? [e.content] : [],
287
+ );
288
+ expect(messages).toEqual([
289
+ "before [content redacted by Anthropic model policy] after",
290
+ "[content redacted by Anthropic model policy]",
291
+ ]);
292
+ });
293
+
247
294
  test("happy path: agent.message → message ProviderEvent, span.model_request_end → cost + context_usage, status_idle → result", async () => {
248
295
  const events: Array<Record<string, unknown>> = [
249
296
  { type: "session.status_running", id: "evt1", processed_at: "2026-01-01T00:00:00Z" },
@@ -193,6 +193,54 @@ describe("runClaudeManagedSetupFlow — happy path", () => {
193
193
  });
194
194
  });
195
195
 
196
+ describe("runClaudeManagedSetupFlow — skill dedupe by display_name", () => {
197
+ function skill(id: string, displayName: string, updatedAt: string) {
198
+ return {
199
+ id,
200
+ display_name: displayName,
201
+ updated_at: updatedAt,
202
+ created_at: updatedAt,
203
+ latest_version_id: `skver_${id}`,
204
+ source: { type: "custom" },
205
+ type: "skill",
206
+ };
207
+ }
208
+
209
+ test("reuses existing skills by display_name and creates only the missing ones", async () => {
210
+ const skillsList = mock(async function* () {
211
+ yield skill("skill_old_worker", "start-worker", "2026-09-01T00:00:00Z");
212
+ // display_name is not unique: the newest duplicate wins.
213
+ yield skill("skill_new_worker", "start-worker", "2026-09-20T00:00:00Z");
214
+ yield skill("skill_stale_worker", "start-worker", "2026-08-01T00:00:00Z");
215
+ });
216
+ const skillsCreate = mock(async (params: { display_name?: string }) => ({
217
+ id: `skill_created_${params.display_name}`,
218
+ }));
219
+ const { client } = makeMockClient();
220
+ client.beta.skills = { create: skillsCreate, list: skillsList } as never;
221
+
222
+ const result = await runClaudeManagedSetupFlow(baseConfig, {
223
+ client: client as any,
224
+ fetchConfig: mock(async () => null),
225
+ upsert: mock(async () => undefined),
226
+ loadSkills: mock(async () => fakeSkillFiles),
227
+ loadSeededSkills: mock(async () => []),
228
+ log: mock(() => undefined),
229
+ });
230
+
231
+ expect(result.skillIds).toEqual(["skill_new_worker", "skill_created_create-pr"]);
232
+ expect(skillsList).toHaveBeenCalledTimes(1);
233
+ // GA shape only: sending the old beta header would make the API answer
234
+ // with `display_title`, and the name index would silently be empty.
235
+ expect(skillsList.mock.calls[0]).toEqual([{ source: "custom" }]);
236
+ expect(skillsCreate).toHaveBeenCalledTimes(1);
237
+ const createArgs = skillsCreate.mock.calls[0]?.[0] as Record<string, unknown>;
238
+ expect(createArgs.display_name).toBe("create-pr");
239
+ expect(createArgs).not.toHaveProperty("display_title");
240
+ expect(createArgs).not.toHaveProperty("betas");
241
+ });
242
+ });
243
+
196
244
  describe("runClaudeManagedSetupFlow — idempotent re-run", () => {
197
245
  test("short-circuits with already-configured when managed_agent_id exists in swarm_config", async () => {
198
246
  const { client, environmentsCreate, agentsCreate } = makeMockClient();
@@ -0,0 +1,117 @@
1
+ /**
2
+ * The claude harness must deliver the task body once in a task's first
3
+ * message. Claude Code's native `/<skill> <args>` expansion puts the args in
4
+ * `<command-args>` and again after `ARGUMENTS:`, so the adapter inlines the
5
+ * runner's skill command instead (`resolveClaudePrompt`).
6
+ */
7
+
8
+ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
9
+ import { cpSync, mkdirSync, rmSync } from "node:fs";
10
+ import { tmpdir } from "node:os";
11
+ import { join } from "node:path";
12
+ import { buildPromptForTrigger, buildResumePrompt } from "../commands/runner";
13
+ import { ClaudeAdapter, resolveClaudePrompt } from "../providers/claude-adapter";
14
+
15
+ const BODY_MARKER = "BODY-MARKER-4c1e";
16
+ const TASK_ID = "00000000-0000-4000-8000-000000000001";
17
+ const BODY = `${BODY_MARKER} Fix the flaky export.\n\nMulti-line body with "quotes" and $ARGUMENTS text.`;
18
+
19
+ let home: string;
20
+ const fmt = (cmd: string) => new ClaudeAdapter().formatCommand(cmd);
21
+
22
+ function countOccurrences(haystack: string, needle: string): number {
23
+ return haystack.split(needle).length - 1;
24
+ }
25
+
26
+ beforeAll(() => {
27
+ home = join(tmpdir(), `claude-prompt-body-once-${crypto.randomUUID()}`);
28
+ for (const skill of ["work-on-task", "review-offered-task"]) {
29
+ const dir = join(home, ".claude", "skills", skill);
30
+ mkdirSync(dir, { recursive: true });
31
+ cpSync(
32
+ join(import.meta.dir, "../../templates/skills", skill, "SKILL.md"),
33
+ join(dir, "SKILL.md"),
34
+ );
35
+ }
36
+ });
37
+
38
+ afterAll(() => {
39
+ rmSync(home, { recursive: true, force: true });
40
+ });
41
+
42
+ beforeEach(async () => {
43
+ // Other test files clear the template registry; re-register the defaults.
44
+ await import(`../commands/templates?t=${Date.now()}`);
45
+ });
46
+
47
+ describe("claude first-message prompt", () => {
48
+ const cases: Array<{ name: string; skill: string; build: () => Promise<string> }> = [
49
+ {
50
+ name: "task_assigned (new tasks, follow-ups, heartbeat checklist)",
51
+ skill: "work-on-task",
52
+ build: () =>
53
+ buildPromptForTrigger(
54
+ { type: "task_assigned", taskId: TASK_ID, task: { id: TASK_ID, task: BODY } },
55
+ "",
56
+ fmt,
57
+ ),
58
+ },
59
+ {
60
+ name: "task_offered",
61
+ skill: "review-offered-task",
62
+ build: () =>
63
+ buildPromptForTrigger(
64
+ { type: "task_offered", taskId: TASK_ID, task: { id: TASK_ID, task: BODY } },
65
+ "",
66
+ fmt,
67
+ ),
68
+ },
69
+ {
70
+ name: "resumed task with progress",
71
+ skill: "work-on-task",
72
+ build: () => buildResumePrompt({ id: TASK_ID, task: BODY, progress: "Read the file" }, fmt),
73
+ },
74
+ {
75
+ name: "resumed task without progress",
76
+ skill: "work-on-task",
77
+ build: () => buildResumePrompt({ id: TASK_ID, task: BODY }, fmt),
78
+ },
79
+ ];
80
+
81
+ for (const { name, skill, build } of cases) {
82
+ test(`${name}: skill inlined, body sent once`, async () => {
83
+ const runnerPrompt = await build();
84
+ // The runner still emits a slash command carrying the body as its args.
85
+ expect(runnerPrompt.startsWith(`/${skill} ${TASK_ID}`)).toBe(true);
86
+
87
+ const prompt = await resolveClaudePrompt(runnerPrompt, home);
88
+
89
+ // No leading slash, so Claude Code does not re-expand (and duplicate) it.
90
+ expect(prompt.startsWith("/")).toBe(false);
91
+ // No leading dash either: `claude -p <prompt>` would parse it as an option.
92
+ expect(prompt.startsWith("-")).toBe(false);
93
+ expect(prompt.startsWith("# ")).toBe(true);
94
+ expect(prompt).not.toContain(`name: ${skill}`);
95
+ expect(prompt).toContain(`User request: ${TASK_ID}`);
96
+ expect(prompt).toContain(TASK_ID);
97
+ expect(countOccurrences(prompt, BODY_MARKER)).toBe(1);
98
+ });
99
+ }
100
+
101
+ test("commands without a SKILL.md pass through for native expansion", async () => {
102
+ const runnerPrompt = `/desplega:research ${TASK_ID}\n\n${BODY}`;
103
+ expect(await resolveClaudePrompt(runnerPrompt, home)).toBe(runnerPrompt);
104
+ });
105
+
106
+ test("a skill that would start the prompt with a dash keeps the native form", async () => {
107
+ const dir = join(home, ".claude", "skills", "dash-skill");
108
+ mkdirSync(dir, { recursive: true });
109
+ await Bun.write(join(dir, "SKILL.md"), "--flag-looking first line\n");
110
+ const runnerPrompt = `/dash-skill ${TASK_ID}\n\n${BODY}`;
111
+ expect(await resolveClaudePrompt(runnerPrompt, home)).toBe(runnerPrompt);
112
+ });
113
+
114
+ test("prompts without a slash command are unchanged", async () => {
115
+ expect(await resolveClaudePrompt(BODY, home)).toBe(BODY);
116
+ });
117
+ });