@desplega.ai/agent-swarm 1.159.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 (415) hide show
  1. package/README.md +2 -2
  2. package/dist/{acp-adapter-qhch0n2v.js → acp-adapter-xx489vvb.js} +286 -75
  3. package/dist/{actions-dtsh1mfb.js → actions-ykjkn0m9.js} +32 -30
  4. package/dist/{anthropic-messages-f2h06k64.js → anthropic-messages-5qx80j9k.js} +99 -14
  5. package/dist/{app-y1b9srva.js → app-0hdqa4k1.js} +13 -11
  6. package/dist/{assistant-31hwy5xj.js → assistant-am2g5g8a.js} +40 -32
  7. package/dist/{azure-openai-responses-0k1mfax5.js → azure-openai-responses-fszbkp0e.js} +3 -3
  8. package/dist/{boot-reembed-48d2qpkt.js → boot-reembed-gt5gjkt4.js} +14 -12
  9. package/dist/{boot-reembed-w0bryhkr.js → boot-reembed-xy9t7avg.js} +15 -13
  10. package/dist/{boot-scrub-logs-t65wbrn7.js → boot-scrub-logs-9x0985y5.js} +12 -10
  11. package/dist/boot-seeds-trv0d4vg.js +29 -0
  12. package/dist/{claude-adapter-1t6611ne.js → claude-adapter-jb6e1e54.js} +32 -26
  13. package/dist/{claude-managed-adapter-werp659j.js → claude-managed-adapter-yxkfxg8j.js} +7 -7
  14. package/dist/{claude-managed-setup-y6rdgbc1.js → claude-managed-setup-d1q9hv2b.js} +26 -27
  15. package/dist/{claude-sdk-session-zhkyk8yw.js → claude-sdk-session-zga700s2.js} +11737 -11256
  16. package/dist/{cli-79aq9cgb.js → cli-0383bp7f.js} +1222 -406
  17. package/dist/{cli-xgpf00sk.js → cli-0c7ata9h.js} +24 -23
  18. package/dist/{cli-qjxy52w7.js → cli-121ph74b.js} +2 -2
  19. package/dist/{cli-20035trh.js → cli-17xpf7cm.js} +14 -7
  20. package/dist/{cli-cqt19wg6.js → cli-1a2j50m0.js} +11 -2
  21. package/dist/{cli-g9df2m37.js → cli-1cz0c18q.js} +249 -200
  22. package/dist/{cli-fjtp3463.js → cli-1er0rhz8.js} +13 -12
  23. package/dist/{cli-ca2w7zv7.js → cli-2hhfhfq5.js} +11 -6
  24. package/dist/{cli-npssxgaz.js → cli-2pdaysab.js} +52 -2
  25. package/dist/{cli-rykfmgwg.js → cli-2x0e561b.js} +1 -1
  26. package/dist/{cli-503ya656.js → cli-35pj2yva.js} +20 -150
  27. package/dist/cli-3qhyh31j.js +166 -0
  28. package/dist/{cli-cygyqj3b.js → cli-3zc55ctx.js} +8 -6
  29. package/dist/{cli-96qd5psa.js → cli-58e8yr0d.js} +1 -1
  30. package/dist/{cli-4aj25szq.js → cli-5nbmpy8w.js} +2 -2
  31. package/dist/{cli-mg24hkt4.js → cli-5p3x4zna.js} +2 -2
  32. package/dist/{cli-vwbbaecp.js → cli-7119ja3w.js} +3 -3
  33. package/dist/{cli-mzkqpxee.js → cli-798vkbks.js} +2 -2
  34. package/dist/{cli-3gvrr8vj.js → cli-82c1pacn.js} +111 -54
  35. package/dist/{cli-hgqfttfc.js → cli-89s4ngw3.js} +1 -1
  36. package/dist/{cli-9b13ggd1.js → cli-89v6kyq7.js} +1 -0
  37. package/dist/{cli-b6thtzac.js → cli-8g6mc14k.js} +7 -7
  38. package/dist/{cli-eg7cdg68.js → cli-9m03pzga.js} +5 -5
  39. package/dist/cli-9s68w752.js +303 -0
  40. package/dist/{cli-48527fpw.js → cli-a8c82p2t.js} +16 -15
  41. package/dist/{cli-5f2hq128.js → cli-ad4qxm6j.js} +12 -11
  42. package/dist/{cli-e4xnc0qp.js → cli-akfj32vj.js} +33 -7
  43. package/dist/{cli-ejpygnqz.js → cli-bh7jed3k.js} +212 -22
  44. package/dist/{cli-0z2v4nhw.js → cli-cat6h73s.js} +1 -1
  45. package/dist/{cli-e0ndxhtk.js → cli-cfkrztza.js} +190 -13
  46. package/dist/{cli-kc3dp84d.js → cli-d56tgd45.js} +216 -59
  47. package/dist/cli-dbmwby6a.js +9 -0
  48. package/dist/{cli-0n2e4x49.js → cli-ecfxwnyb.js} +15 -2
  49. package/dist/{cli-gzyh0j0g.js → cli-ee8bc46j.js} +66 -166
  50. package/dist/{cli-kdnehe0t.js → cli-et8m9xsv.js} +1 -1
  51. package/dist/cli-f9w1semm.js +413 -0
  52. package/dist/cli-gt4hxb8p.js +99 -0
  53. package/dist/{cli-qv4jv8bx.js → cli-hgmnf38m.js} +2 -2
  54. package/dist/{cli-10ym38qc.js → cli-hn04t9db.js} +3 -3
  55. package/dist/{cli-qvnnx4ct.js → cli-jpf7e1zq.js} +105 -243
  56. package/dist/cli-jyy6pm2f.js +333872 -0
  57. package/dist/{cli-dvjpkkjd.js → cli-kgd3p0bm.js} +7 -7
  58. package/dist/cli-ma3zvh39.js +10404 -0
  59. package/dist/{cli-r2ap2czm.js → cli-nexqx4ve.js} +6 -0
  60. package/dist/{cli-j9z8mfx3.js → cli-nsdxtcbg.js} +37 -20
  61. package/dist/{cli-wgjx0q33.js → cli-phfg8vy8.js} +392 -53
  62. package/dist/{cli-rgjprct3.js → cli-ptr8h7kp.js} +19 -1
  63. package/dist/{cli-s8s499xy.js → cli-qbksc292.js} +3 -2
  64. package/dist/{cli-bv0a38v9.js → cli-qwsnmjdd.js} +515 -229
  65. package/dist/{cli-68bmme5p.js → cli-r6fsxc76.js} +40018 -52654
  66. package/dist/{cli-rrqmqr1s.js → cli-rtx9dygp.js} +1 -1
  67. package/dist/{cli-rfezr056.js → cli-sqm3r5ps.js} +83 -15
  68. package/dist/{cli-zw65mab6.js → cli-t6v2ah3r.js} +67 -13
  69. package/dist/{cli-vn0qv9cj.js → cli-t845n9xk.js} +28 -14
  70. package/dist/cli-trc24wk2.js +143 -0
  71. package/dist/{cli-wd9hz40d.js → cli-tre4bdgq.js} +5 -5
  72. package/dist/{cli-9yg65c1j.js → cli-tsk3nz8a.js} +29 -1
  73. package/dist/{cli-phsaj4xq.js → cli-v0pvvhfq.js} +1 -1
  74. package/dist/{cli-hj65wsts.js → cli-vnnz5ncg.js} +4695 -4915
  75. package/dist/{cli-cpvsaqsw.js → cli-w0bwte75.js} +8 -5
  76. package/dist/{cli-z9x0sh1t.js → cli-we0q7t0e.js} +3 -3
  77. package/dist/{cli-r94z9p1z.js → cli-wgbezhw2.js} +3 -0
  78. package/dist/{cli-19jenss3.js → cli-xk79303e.js} +51 -17
  79. package/dist/{cli-31nkw3g3.js → cli-ybgv4kw2.js} +15 -8
  80. package/dist/{cli-ardpzhjm.js → cli-yrg7c73k.js} +13 -6
  81. package/dist/{cli-de1396j2.js → cli-ywybs5as.js} +7 -1
  82. package/dist/{cli-vhjwh6cz.js → cli-yy6dew39.js} +2 -2
  83. package/dist/{cli-bgv70kym.js → cli-zb6xbk61.js} +3 -2
  84. package/dist/{cli-4tvgz99m.js → cli-ze1d3wzv.js} +79 -27
  85. package/dist/cli.js +33 -32
  86. package/dist/{cloudflare-workers-ai-system-one-1sg586qv.js → cloudflare-workers-ai-system-one-k6fbxw9d.js} +2 -2
  87. package/dist/{codex-adapter-jqfqqa21.js → codex-adapter-04v97vep.js} +23 -20
  88. package/dist/{codex-hook-5eys3dya.js → codex-hook-20me6byw.js} +2 -2
  89. package/dist/{codex-login-he9vrn90.js → codex-login-cf7mzsgg.js} +48 -3
  90. package/dist/{codex-session-runner-zd341dsm.js → codex-session-runner-ax69mqq3.js} +21 -20
  91. package/dist/{commands-vfsysz4s.js → commands-18gy08n1.js} +12 -10
  92. package/dist/{db-s3znhvbn.js → db-d2pvjpyt.js} +22 -10
  93. package/dist/{devin-adapter-8vgkwh29.js → devin-adapter-dpsv6hd9.js} +1 -1
  94. package/dist/{dispatcher-5xn0c7rf.js → dispatcher-8wbwyzjg.js} +31 -29
  95. package/dist/{dsh-adapter-894mgfvs.js → dsh-adapter-ysqk9snv.js} +9 -4
  96. package/dist/{e2b-ez07znam.js → e2b-esmt1r16.js} +3 -1
  97. package/dist/{execute-71ysgv35.js → execute-cmagrbrz.js} +235 -52
  98. package/dist/{google-generative-ai-1nfcdphp.js → google-generative-ai-k6n5nbpp.js} +3 -3
  99. package/dist/{google-vertex-1tqcd7b1.js → google-vertex-44xxtcvp.js} +3 -3
  100. package/dist/{handlers-5w4jwycr.js → handlers-6b69m5kp.js} +34 -32
  101. package/dist/{hook-z0hbxj85.js → hook-k3mydf2n.js} +16 -14
  102. package/dist/{hook-853bxrr6.js → hook-srnkt0hb.js} +16 -14
  103. package/dist/{http-akc3wy3m.js → http-hrf80ca1.js} +836 -189
  104. package/dist/{index-ttpbhnjg.js → index-1asbaj62.js} +32 -30
  105. package/dist/{index-cd151qkq.js → index-45evq4ee.js} +31 -29
  106. package/dist/{index-k661dzzr.js → index-9rxdvj6w.js} +32 -30
  107. package/dist/index-k4nj18qs.js +31 -0
  108. package/dist/{index-5sz16ksh.js → index-xmevk2b9.js} +89 -61
  109. package/dist/{keepalive-8p9rpvz5.js → keepalive-sfdz6ksp.js} +14 -12
  110. package/dist/{lead-jp69z5h3.js → lead-ej91e9hw.js} +52 -50
  111. package/dist/{llama-cpp-classify-t2tccams.js → llama-cpp-classify-tgj35k8d.js} +1 -1
  112. package/dist/{maintenance-fwx4ev0k.js → maintenance-80dnd7ke.js} +15 -13
  113. package/dist/{mistral-conversations-bhvwqcnk.js → mistral-conversations-pc3ntnxt.js} +1 -1
  114. package/dist/node-f0ykebbv.js +2107 -0
  115. package/dist/node-wyamey5r.js +19 -0
  116. package/dist/{oauth-refresh-sweep-cwcq1ctx.js → oauth-refresh-sweep-m1etk2mx.js} +13 -11
  117. package/dist/{onboard-2b4q0t84.js → onboard-0q8sdfqc.js} +2 -2
  118. package/dist/{openai-codex-responses-nekthgks.js → openai-codex-responses-n7n683p2.js} +2 -2
  119. package/dist/{openai-completions-hxtzwj0s.js → openai-completions-jpaj5g2b.js} +2 -2
  120. package/dist/{openai-responses-w4r118w7.js → openai-responses-9a3nme9j.js} +3 -3
  121. package/dist/{opencode-adapter-zz7xvjj6.js → opencode-adapter-4wcgmvgf.js} +13 -11
  122. package/dist/{openrouter-images-b6sqqjgn.js → openrouter-images-8yx3487g.js} +1 -1
  123. package/dist/{otel-impl-g8rs4vwr.js → otel-impl-hnqm9mxq.js} +2 -2
  124. package/dist/pi-logo-animation-e9a0t68z.js +1003 -0
  125. package/dist/{pi-mono-adapter-n0me28wt.js → pi-mono-adapter-d6sqm9wr.js} +301 -78
  126. package/dist/{pricing-refresh-0v77385v.js → pricing-refresh-y60z64pm.js} +14 -13
  127. package/dist/quickjs-r1pq2107.wasm +0 -0
  128. package/dist/{rbac-roles-krjyazbj.js → rbac-roles-8pa0754b.js} +13 -11
  129. package/dist/{render-v2-3wjsp9aw.js → render-v2-418s1vj7.js} +13 -11
  130. package/dist/{runtime-ktjysacx.js → runtime-3pknc168.js} +5 -4
  131. package/dist/{setup-yeqryke6.js → setup-yghdkj4x.js} +2 -2
  132. package/dist/{typesafe-system-one-96960jxp.js → typesafe-system-one-2ef30pec.js} +2 -2
  133. package/dist/{virtual-modules-dzdeq1sf.js → virtual-modules-jcceevt5.js} +8 -6
  134. package/dist/{work-objects-3pakpkw0.js → work-objects-j1sz20v9.js} +34 -32
  135. package/dist/{worker-85testgj.js → worker-d4gcvkjt.js} +52 -50
  136. package/dist/{x-n3e0v3xa.js → x-f1gt1pz3.js} +2 -2
  137. package/openapi.json +587 -19
  138. package/package.json +16 -15
  139. package/src/azure-devops/api.ts +121 -0
  140. package/src/azure-devops/auth.ts +78 -0
  141. package/src/azure-devops/handlers.ts +413 -0
  142. package/src/azure-devops/index.ts +27 -0
  143. package/src/azure-devops/templates.ts +108 -0
  144. package/src/azure-devops/types.ts +90 -0
  145. package/src/be/db/api-keys.ts +70 -0
  146. package/src/be/db/tasks/read.ts +1 -1
  147. package/src/be/db/tasks/write.ts +16 -0
  148. package/src/be/db-client.ts +11 -2
  149. package/src/be/db-retention.ts +95 -32
  150. package/src/be/db.ts +187 -24
  151. package/src/be/memory/constants.ts +16 -0
  152. package/src/be/memory/graph-expansion.ts +8 -1
  153. package/src/be/memory/index-content.ts +20 -1
  154. package/src/be/memory/key-guard.ts +93 -0
  155. package/src/be/memory/key-paths.ts +103 -0
  156. package/src/be/memory/providers/sqlite-store.ts +203 -60
  157. package/src/be/memory/raters/llm-summarizer.ts +10 -4
  158. package/src/be/memory/raters/llm.ts +15 -5
  159. package/src/be/memory/raters/store.ts +6 -3
  160. package/src/be/memory/raters/types.ts +11 -0
  161. package/src/be/memory/reranker.ts +38 -7
  162. package/src/be/memory/types.ts +12 -1
  163. package/src/be/migrations/185_api_key_auth_failures.sql +6 -0
  164. package/src/be/migrations/186_api_key_seat_mismatch.sql +8 -0
  165. package/src/be/migrations/190_memory_rating_model.sql +7 -0
  166. package/src/be/migrations/191_context_versions_previous_index.sql +8 -0
  167. package/src/be/model-tier-keys.ts +16 -1
  168. package/src/be/model-tier-resolution.ts +63 -18
  169. package/src/be/model-validation.ts +86 -10
  170. package/src/be/pricing-normalize.ts +4 -2
  171. package/src/be/scripts/typecheck.ts +4 -3
  172. package/src/be/seed/runner.ts +18 -12
  173. package/src/be/seed/types.ts +1 -0
  174. package/src/be/seed-pricing.ts +16 -0
  175. package/src/be/seed-scripts/catalog/task-failure-audit.ts +26 -26
  176. package/src/be/swarm-config-guard.ts +13 -1
  177. package/src/commands/claude-managed-setup.ts +33 -31
  178. package/src/commands/codex-login.ts +67 -0
  179. package/src/commands/credential-outcome-report.ts +191 -0
  180. package/src/commands/provider-credentials.ts +28 -15
  181. package/src/commands/rate-limit-outcome.ts +34 -4
  182. package/src/commands/runner.ts +479 -228
  183. package/src/commands/seat-mismatch-report.ts +46 -0
  184. package/src/e2b/env.ts +2 -0
  185. package/src/extensions/apply-task-create.ts +27 -1
  186. package/src/extensions/catalog.generated.json +17 -0
  187. package/src/extensions/contract-types.generated.ts +1 -1
  188. package/src/github/handlers.ts +10 -74
  189. package/src/github/reactions.ts +119 -138
  190. package/src/github/task-reactions.ts +34 -11
  191. package/src/heartbeat/heartbeat.ts +15 -2
  192. package/src/hooks/hook.ts +1 -1
  193. package/src/http/agents.ts +15 -2
  194. package/src/http/api-keys.ts +140 -2
  195. package/src/http/codex-oauth-keep-warm.ts +3 -5
  196. package/src/http/core.ts +12 -0
  197. package/src/http/extensions.ts +51 -27
  198. package/src/http/index.ts +4 -0
  199. package/src/http/mcp-user.ts +8 -13
  200. package/src/http/mcp.ts +19 -12
  201. package/src/http/memory.ts +91 -18
  202. package/src/http/poll.ts +9 -1
  203. package/src/http/schedules.ts +17 -2
  204. package/src/http/script-runs.ts +12 -0
  205. package/src/http/session-data.ts +6 -4
  206. package/src/http/stats.ts +1 -0
  207. package/src/http/status.ts +15 -1
  208. package/src/http/tasks.ts +80 -8
  209. package/src/http/users.ts +1 -1
  210. package/src/http/webhooks.ts +120 -2
  211. package/src/http/workflows.ts +11 -0
  212. package/src/mcp-client/http-client.ts +5 -0
  213. package/src/memory/recall-query.ts +74 -0
  214. package/src/prompts/base-prompt.ts +15 -4
  215. package/src/prompts/session-templates.ts +22 -1
  216. package/src/providers/acp-adapter.ts +1 -0
  217. package/src/providers/claude-adapter.ts +98 -21
  218. package/src/providers/claude-managed-adapter.ts +30 -2
  219. package/src/providers/claude-sdk-session.ts +3 -1
  220. package/src/providers/codex-adapter.ts +11 -11
  221. package/src/providers/codex-skill-resolver.ts +6 -0
  222. package/src/providers/devin-adapter.ts +1 -0
  223. package/src/providers/dsh-adapter.ts +205 -7
  224. package/src/providers/harness-version.ts +83 -7
  225. package/src/providers/opencode-adapter.ts +77 -4
  226. package/src/providers/pi-binary-root/src/extensions/codemode/worker.ts +11 -0
  227. package/src/providers/pi-codemode-runtime.ts +17 -0
  228. package/src/providers/pi-mono-adapter.ts +387 -83
  229. package/src/providers/pi-mono-extension.ts +17 -2
  230. package/src/providers/pricing-sources.md +5 -1
  231. package/src/providers/reasoning-effort.ts +10 -2
  232. package/src/providers/types.ts +9 -1
  233. package/src/rbac/legacy-policy.ts +20 -0
  234. package/src/rbac/permissions.ts +15 -0
  235. package/src/scripts-runtime/types/stdlib.d.ts +13 -5
  236. package/src/scripts-runtime/types/swarm-extension.d.ts +4 -2
  237. package/src/scripts-runtime/types/swarm-sdk.d.ts +13 -5
  238. package/src/slack/assistant.ts +13 -0
  239. package/src/slack/render-v2.ts +87 -37
  240. package/src/slack/session-status.ts +541 -0
  241. package/src/tasks/context-key.ts +38 -1
  242. package/src/tasks/sibling-awareness.ts +3 -2
  243. package/src/telemetry.ts +5 -4
  244. package/src/templates.d.ts +6 -0
  245. package/src/tests/api-key-auth-failure-route.test.ts +142 -0
  246. package/src/tests/api-key-tracking.test.ts +368 -1
  247. package/src/tests/azure-devops-auth.test.ts +100 -0
  248. package/src/tests/azure-devops-handlers.test.ts +324 -0
  249. package/src/tests/azure-devops-webhook-malformed.test.ts +96 -0
  250. package/src/tests/base-prompt.test.ts +40 -2
  251. package/src/tests/claude-adapter.test.ts +58 -0
  252. package/src/tests/claude-default-route.test.ts +260 -0
  253. package/src/tests/claude-managed-adapter.test.ts +47 -0
  254. package/src/tests/claude-managed-setup.test.ts +48 -0
  255. package/src/tests/claude-prompt-body-once.test.ts +117 -0
  256. package/src/tests/claude-routes-e2e.test.ts +316 -0
  257. package/src/tests/codex-auth-failure.test.ts +52 -0
  258. package/src/tests/codex-login.test.ts +116 -0
  259. package/src/tests/context-versions-retention.test.ts +314 -0
  260. package/src/tests/core-auth.test.ts +7 -0
  261. package/src/tests/credential-exhaustion-policy.test.ts +118 -3
  262. package/src/tests/credential-outcome-report.test.ts +171 -0
  263. package/src/tests/credential-status-api.test.ts +103 -2
  264. package/src/tests/db-retention.test.ts +14 -14
  265. package/src/tests/db-task-read-contract.test.ts +6 -0
  266. package/src/tests/db-task-write-contract.test.ts +533 -1
  267. package/src/tests/docs-index-footprint.test.js +3 -3
  268. package/src/tests/dokploy-deploy.test.ts +315 -0
  269. package/src/tests/dsh-adapter.test.ts +134 -2
  270. package/src/tests/dsh-usage.test.ts +153 -0
  271. package/src/tests/explicit-model-validation.test.ts +253 -2
  272. package/src/tests/extensions-catalog.test.ts +1 -0
  273. package/src/tests/extensions-example-deploy-awareness.test.ts +301 -0
  274. package/src/tests/extensions-http.test.ts +137 -10
  275. package/src/tests/extensions-mcp-tools.test.ts +28 -0
  276. package/src/tests/extensions-pre-task.test.ts +73 -0
  277. package/src/tests/fetch-resolved-env.test.ts +70 -0
  278. package/src/tests/fixtures/azure-devops/pull-request-commented.json +129 -0
  279. package/src/tests/fixtures/azure-devops/pull-request-created.json +88 -0
  280. package/src/tests/fixtures/fake-claude-routes.ts +229 -0
  281. package/src/tests/github-handlers-reactions.test.ts +486 -0
  282. package/src/tests/github-reactions.test.ts +280 -0
  283. package/src/tests/hang-watchdog-worker.ts +47 -0
  284. package/src/tests/hang-watchdog.test.ts +45 -0
  285. package/src/tests/hang-watchdog.ts +30 -0
  286. package/src/tests/harness-cli-version.test.ts +62 -0
  287. package/src/tests/harness-model-guard.test.ts +123 -0
  288. package/src/tests/harness-version.test.ts +70 -3
  289. package/src/tests/http-api-integration.test.ts +21 -0
  290. package/src/tests/http-task-write-ownership.test.ts +195 -0
  291. package/src/tests/internal-ai/complete-structured.test.ts +94 -0
  292. package/src/tests/internal-ai/credentials.test.ts +22 -0
  293. package/src/tests/internal-ai/models.test.ts +36 -2
  294. package/src/tests/internal-ai/schema-parity.test.ts +25 -1
  295. package/src/tests/internal-ai/summarize-session.test.ts +23 -7
  296. package/src/tests/mcp-user-route.test.ts +133 -0
  297. package/src/tests/memory-fts-populate.test.ts +139 -0
  298. package/src/tests/memory-http-recall-gating.test.ts +125 -1
  299. package/src/tests/memory-key-paths.test.ts +1055 -0
  300. package/src/tests/memory-rate-endpoint.test.ts +66 -0
  301. package/src/tests/memory-rater-llm-summarizer.test.ts +2 -1
  302. package/src/tests/memory-rater-llm.test.ts +73 -0
  303. package/src/tests/memory-rater-store.test.ts +31 -0
  304. package/src/tests/memory-reranker.test.ts +40 -0
  305. package/src/tests/model-rate-limit-windows.test.ts +19 -0
  306. package/src/tests/model-tier-resolution.test.ts +92 -0
  307. package/src/tests/opencode-adapter.test.ts +214 -2
  308. package/src/tests/opencode-plugin.test.ts +4 -0
  309. package/src/tests/pi-codemode.test.ts +324 -0
  310. package/src/tests/pi-mono-adapter.test.ts +3 -0
  311. package/src/tests/pi-native-mcp.test.ts +193 -0
  312. package/src/tests/pi-tool-exposure.test.ts +374 -0
  313. package/src/tests/preload.ts +9 -0
  314. package/src/tests/prompt-template-session.test.ts +5 -3
  315. package/src/tests/provider-registration.test.ts +0 -2
  316. package/src/tests/providers/pi-cost.test.ts +190 -4
  317. package/src/tests/rate-limit-event.test.ts +65 -0
  318. package/src/tests/rate-limit-outcome.test.ts +90 -0
  319. package/src/tests/rbac-engine.test.ts +41 -2
  320. package/src/tests/runner-key-completion-outcome.test.ts +112 -0
  321. package/src/tests/runner-key-outcome-late-reset.test.ts +224 -0
  322. package/src/tests/runner-registration-retry.test.ts +149 -0
  323. package/src/tests/runner-slot-reconcile.test.ts +199 -0
  324. package/src/tests/script-connections.test.ts +8 -0
  325. package/src/tests/seat-mismatch-report.test.ts +85 -0
  326. package/src/tests/seed-scripts.test.ts +49 -0
  327. package/src/tests/seed.test.ts +25 -0
  328. package/src/tests/session-costs-recompute-all-providers.test.ts +23 -0
  329. package/src/tests/slack-render-v2.test.ts +96 -0
  330. package/src/tests/slack-session-status.test.ts +626 -0
  331. package/src/tests/status.test.ts +29 -0
  332. package/src/tests/steering-promotion.test.ts +51 -0
  333. package/src/tests/store-progress-ownership.test.ts +136 -0
  334. package/src/tests/subscription-plans.test.ts +28 -0
  335. package/src/tests/task-citations.test.ts +3 -2
  336. package/src/tests/task-reactions.test.ts +29 -6
  337. package/src/tests/vcs-azure-devops.test.ts +73 -0
  338. package/src/tests/webhooks-task-blocked.test.ts +23 -0
  339. package/src/tests/workflow-agent-task.test.ts +30 -0
  340. package/src/tests/workflow-llm.test.ts +14 -0
  341. package/src/tests/workflow-triggers-v2.test.ts +320 -0
  342. package/src/tools/context-diff.ts +20 -0
  343. package/src/tools/extension-install.ts +27 -16
  344. package/src/tools/memory-edit.ts +47 -3
  345. package/src/tools/memory-search.ts +13 -1
  346. package/src/tools/memory-store.ts +55 -3
  347. package/src/tools/schedules/create-schedule.ts +1 -1
  348. package/src/tools/schedules/patch-schedule.ts +16 -3
  349. package/src/tools/schedules/update-schedule.ts +16 -3
  350. package/src/tools/send-task.ts +10 -7
  351. package/src/tools/store-progress.ts +49 -4
  352. package/src/tools/task-action.ts +2 -1
  353. package/src/tools/workflows/create-workflow.ts +8 -2
  354. package/src/tools/workflows/update-workflow.ts +8 -2
  355. package/src/types.ts +54 -7
  356. package/src/utils/codex-auth-failure.ts +34 -0
  357. package/src/utils/credentials.test.ts +31 -1
  358. package/src/utils/credentials.ts +92 -39
  359. package/src/utils/dsh-usage.ts +53 -0
  360. package/src/utils/error-tracker.ts +80 -0
  361. package/src/utils/harness-cli-version.ts +6 -4
  362. package/src/utils/internal-ai/complete-structured.ts +26 -3
  363. package/src/utils/internal-ai/credentials.ts +23 -9
  364. package/src/utils/internal-ai/index.ts +8 -1
  365. package/src/utils/internal-ai/models.ts +34 -8
  366. package/src/utils/internal-ai/summarize-session.ts +22 -8
  367. package/src/utils/model-rate-limit-windows.ts +12 -0
  368. package/src/utils/runtime-model-catalog.ts +15 -0
  369. package/src/utils/secret-scrubber.ts +8 -0
  370. package/src/utils/spawn-model-guard.ts +43 -0
  371. package/src/utils/subscription-plans.ts +22 -0
  372. package/src/vcs/azure-devops.ts +80 -0
  373. package/src/vcs/index.ts +5 -1
  374. package/src/vcs/types.ts +2 -2
  375. package/src/workflows/executors/agent-task.ts +7 -6
  376. package/src/workflows/model-validation.ts +45 -0
  377. package/src/workflows/patch-definition.ts +5 -0
  378. package/src/workflows/triggers.ts +85 -0
  379. package/templates/community/open-prompt-showreel/PLAYBOOK.md +336 -0
  380. package/templates/community/open-prompt-showreel/README.md +33 -0
  381. package/templates/community/open-prompt-showreel/example/tla-races/facts.ts +145 -0
  382. package/templates/community/open-prompt-showreel/example/tla-races/facts.v3.json +140 -0
  383. package/templates/community/open-prompt-showreel/example/tla-races/music.mist-city.json +8 -0
  384. package/templates/community/open-prompt-showreel/example/tla-races/reel.html +805 -0
  385. package/templates/community/open-prompt-showreel/example/tla-races/smoke.sh +48 -0
  386. package/templates/community/open-prompt-showreel/lead-prompt.md +89 -0
  387. package/templates/community/open-prompt-showreel/setup.sh +167 -0
  388. package/templates/community/open-prompt-showreel/skills/motion-design-replication/SKILL.md +85 -0
  389. package/templates/community/open-prompt-showreel/skills/motion-design-video-analysis/SKILL.md +115 -0
  390. package/templates/community/open-prompt-showreel/skills/open-prompt-showreel/SKILL.md +147 -0
  391. package/templates/community/open-prompt-showreel/skills/video-generation/SKILL.md +195 -0
  392. package/templates/community/open-prompt-showreel/tools/fetch-music.sh +65 -0
  393. package/templates/community/open-prompt-showreel/tools/music-analyze.py +69 -0
  394. package/templates/community/open-prompt-showreel/tools/music-current.sh +34 -0
  395. package/templates/community/open-prompt-showreel/tools/music-cut.sh +36 -0
  396. package/templates/community/open-prompt-showreel/tools/music-refine.py +39 -0
  397. package/templates/community/open-prompt-showreel/tools/render.mjs +157 -0
  398. package/templates/community/open-prompt-showreel/tools/sheet.sh +44 -0
  399. package/templates/community/open-prompt-showreel/tools/web-compress.sh +17 -0
  400. package/templates/community/open-prompt-showreel/tools/x-reencode.sh +21 -0
  401. package/templates/extensions/README.md +1 -0
  402. package/templates/extensions/deploy-awareness/README.md +28 -0
  403. package/templates/extensions/deploy-awareness/hooks.ts +152 -0
  404. package/templates/extensions/deploy-awareness/manifest.json +8 -0
  405. package/templates/skills/memory/config.json +2 -2
  406. package/templates/skills/memory/content.md +59 -5
  407. package/templates/skills/swarm-extensions/config.json +1 -1
  408. package/templates/skills/swarm-extensions/content.md +28 -5
  409. package/templates/skills/workflow-iterate/content.md +1 -0
  410. package/dist/boot-seeds-h7c32hvm.js +0 -27
  411. package/dist/cli-2djtr51b.js +0 -48
  412. package/dist/cli-7zttzetj.js +0 -324
  413. package/dist/cli-j490egb5.js +0 -5632
  414. package/dist/cli-m4excttz.js +0 -333481
  415. package/dist/index-bjmjh9h4.js +0 -29
@@ -1,4 +1,5 @@
1
1
  import { existsSync } from "node:fs";
2
+ import { deriveDefaultRoute, routeCredentialStatus } from "@desplega/model-routing";
2
3
  import { type ModelFamily, modelFamilyOf, windowForModelFamily } from "./model-rate-limit-windows";
3
4
 
4
5
  /** Env vars that may contain comma-separated credential pools */
@@ -113,8 +114,12 @@ export interface CredentialSelection {
113
114
  modelBlockedIndices?: number[];
114
115
  /** ISO of the earliest reset among modelBlockedIndices, or null/undefined when none. */
115
116
  earliestModelResetAt?: string | null;
117
+ /** Indices excluded because the key's subscription plan cannot run the requested model. */
118
+ seatBlockedIndices?: number[];
116
119
  /** Subscription plan id detected on the credential (`SUBSCRIPTION_PLANS`), reported with its usage. */
117
120
  plan?: string | null;
121
+ /** `authFailureFence` from `GET /api/keys/available`, read before the task (Codex pool only). */
122
+ authFailureFence?: number;
118
123
  }
119
124
 
120
125
  const MODEL_LABELS: Record<ModelFamily, string> = {
@@ -126,33 +131,49 @@ const MODEL_LABELS: Record<ModelFamily, string> = {
126
131
 
127
132
  /**
128
133
  * Thrown by `resolveCredentialPools` at task admission (`enforceModelCapacity`)
129
- * when every key for a pool is either key-wide rate-limited or blocked by the
130
- * requested model's weekly window, and `MODEL_WINDOW_EXHAUSTED_POLICY` is
131
- * `fail` (the default). The caller must not spawn the CLI on this error — see
132
- * `spawnProviderProcess` in `src/commands/runner.ts`. Taskless configuration
133
- * loads never throw it.
134
+ * when no key for a pool can run the requested model: every key is key-wide
135
+ * rate-limited, blocked by the model's weekly window, or on a subscription
136
+ * seat that cannot run the model (`seatBlockedCount`). A window block throws
137
+ * only when `MODEL_WINDOW_EXHAUSTED_POLICY` is `fail` (the default); a seat
138
+ * block never resets, so it throws under every policy. The caller must not
139
+ * spawn the CLI on this error — see `spawnProviderProcess` in
140
+ * `src/commands/runner.ts`. Taskless configuration loads never throw it.
134
141
  */
135
142
  export class ModelWindowExhaustedError extends Error {
136
143
  readonly model: ModelFamily;
137
144
  readonly window: string;
138
145
  readonly earliestResetAt: string | null;
139
146
  readonly keyType: string;
147
+ readonly seatBlockedCount: number;
148
+ readonly modelBlockedCount: number;
140
149
 
141
150
  constructor(opts: {
142
151
  model: ModelFamily;
143
152
  window: string;
144
153
  earliestResetAt: string | null;
145
154
  keyType: string;
155
+ seatBlockedCount?: number;
156
+ modelBlockedCount?: number;
146
157
  }) {
147
158
  const modelLabel = MODEL_LABELS[opts.model];
159
+ const seatBlockedCount = opts.seatBlockedCount ?? 0;
160
+ const modelBlockedCount = opts.modelBlockedCount ?? 0;
161
+ const windowClause =
162
+ modelBlockedCount > 0 && opts.earliestResetAt
163
+ ? `${modelBlockedCount} keys have the ${modelLabel} window exhausted until ${opts.earliestResetAt}`
164
+ : `${modelBlockedCount} keys have the ${modelLabel} window exhausted`;
148
165
  super(
149
- `No ${opts.keyType} key has ${modelLabel} capacity until ${opts.earliestResetAt ?? "unknown"}. Re-dispatch with another model or modelTier.`,
166
+ seatBlockedCount > 0
167
+ ? `No ${opts.keyType} key can run ${modelLabel}: ${seatBlockedCount} keys are on a seat without ${modelLabel}, ${windowClause}. Re-dispatch with another model or modelTier.`
168
+ : `No ${opts.keyType} key has ${modelLabel} capacity until ${opts.earliestResetAt ?? "unknown"}. Re-dispatch with another model or modelTier.`,
150
169
  );
151
170
  this.name = "ModelWindowExhaustedError";
152
171
  this.model = opts.model;
153
172
  this.window = opts.window;
154
173
  this.earliestResetAt = opts.earliestResetAt;
155
174
  this.keyType = opts.keyType;
175
+ this.seatBlockedCount = seatBlockedCount;
176
+ this.modelBlockedCount = modelBlockedCount;
156
177
  }
157
178
  }
158
179
 
@@ -173,6 +194,15 @@ function isJsonObject(value: string): boolean {
173
194
  * Falls back to random selection from all credentials if no available indices match.
174
195
  * A JSON object value (the CODEX_OAUTH blob) is always one credential.
175
196
  */
197
+ function splitCredentialPool(value: string): string[] {
198
+ return isJsonObject(value)
199
+ ? [value.trim()]
200
+ : value
201
+ .split(",")
202
+ .map((s) => s.trim())
203
+ .filter(Boolean);
204
+ }
205
+
176
206
  export function selectCredential(
177
207
  value: string,
178
208
  availableIndices?: number[],
@@ -183,12 +213,7 @@ export function selectCredential(
183
213
  // handed out fragments such as `"accountId":"..."}` as the selected credential
184
214
  // and stamped `965"}` as the key suffix on task records. Codex pools live in
185
215
  // config-store slots (codex_oauth_<n>), resolved by the runner instead.
186
- const credentials = isJsonObject(value)
187
- ? [value.trim()]
188
- : value
189
- .split(",")
190
- .map((s) => s.trim())
191
- .filter(Boolean);
216
+ const credentials = splitCredentialPool(value);
192
217
  if (credentials.length <= 1) {
193
218
  const selected = value.trim();
194
219
  const isRateLimitFallback = availableIndices !== undefined && availableIndices.length === 0;
@@ -247,17 +272,27 @@ export function selectRandomCredential(value: string): {
247
272
  return { selected: result.selected, index: result.index, total: result.total };
248
273
  }
249
274
 
275
+ export const CLAUDE_CREDENTIALS_HINT =
276
+ "Set CLAUDE_CODE_OAUTH_TOKEN or ANTHROPIC_API_KEY, or route Claude Code through a gateway (ANTHROPIC_BASE_URL + ANTHROPIC_AUTH_TOKEN or ANTHROPIC_API_KEY), Microsoft Foundry (CLAUDE_CODE_USE_FOUNDRY), Amazon Bedrock (CLAUDE_CODE_USE_BEDROCK), or Google Vertex AI (CLAUDE_CODE_USE_VERTEX).";
277
+
278
+ const CLAUDE_CREDENTIAL_TYPE_BY_PROVIDER: Record<string, string> = {
279
+ "claude-subscription": "oauth",
280
+ anthropic: "api_key",
281
+ "anthropic-gateway": "gateway",
282
+ };
283
+
250
284
  /**
251
- * Validate that at least one Claude credential is available.
252
- * Priority: CLAUDE_CODE_OAUTH_TOKEN > ANTHROPIC_API_KEY.
253
- * Returns the credential type found, or throws if neither is set.
285
+ * Validate that the claude harness has a usable default route (see
286
+ * `deriveDefaultRoute` in @desplega/model-routing for the precedence).
287
+ * Returns the credential type found ("oauth", "api_key", "gateway", "foundry",
288
+ * "bedrock", "vertex"), or throws with what is missing.
254
289
  */
255
- export function validateClaudeCredentials(
256
- env: Record<string, string | undefined>,
257
- ): "oauth" | "api_key" {
258
- if (env.CLAUDE_CODE_OAUTH_TOKEN) return "oauth";
259
- if (env.ANTHROPIC_API_KEY) return "api_key";
260
- throw new Error("No Claude credentials found. Set CLAUDE_CODE_OAUTH_TOKEN or ANTHROPIC_API_KEY.");
290
+ export function validateClaudeCredentials(env: Record<string, string | undefined>): string {
291
+ const route = deriveDefaultRoute("claude", env);
292
+ if (!route) throw new Error(`No Claude credentials found. ${CLAUDE_CREDENTIALS_HINT}`);
293
+ const status = routeCredentialStatus(route, env);
294
+ if (!status.ready) throw new Error(`No Claude credentials found. ${status.hint}`);
295
+ return CLAUDE_CREDENTIAL_TYPE_BY_PROVIDER[route.provider] ?? route.provider;
261
296
  }
262
297
 
263
298
  /**
@@ -283,6 +318,8 @@ export interface AvailabilityInfo {
283
318
  availableIndices: number[];
284
319
  modelBlockedIndices?: number[];
285
320
  earliestModelResetAt?: string | null;
321
+ /** Indices excluded because the key's subscription plan cannot run the model. */
322
+ seatBlockedIndices?: number[];
286
323
  }
287
324
 
288
325
  /**
@@ -315,11 +352,13 @@ async function fetchAvailableIndices(
315
352
  availableIndices: number[];
316
353
  modelBlockedIndices?: number[];
317
354
  earliestModelResetAt?: string | null;
355
+ seatBlockedIndices?: number[];
318
356
  };
319
357
  availableIndicesMap[envVar] = {
320
358
  availableIndices: data.availableIndices,
321
359
  modelBlockedIndices: data.modelBlockedIndices,
322
360
  earliestModelResetAt: data.earliestModelResetAt,
361
+ seatBlockedIndices: data.seatBlockedIndices,
323
362
  };
324
363
  if (data.availableIndices.length < totalKeys) {
325
364
  console.log(
@@ -374,7 +413,9 @@ export async function resolveCredentialPools(
374
413
  localBlocks?: Map<string, number>;
375
414
  /**
376
415
  * Set only at task admission (`spawnProviderProcess`). When true, an
377
- * exhausted model window fails fast with `ModelWindowExhaustedError`.
416
+ * exhausted model window, or a pool where every remaining key is on a
417
+ * seat that cannot run the model, fails fast with
418
+ * `ModelWindowExhaustedError`.
378
419
  * Taskless configuration loads (worker boot, credential recovery,
379
420
  * periodic reconciliation) leave it unset: they still pick a key, so an
380
421
  * exhausted default model never blocks boot or config refresh, and the
@@ -413,32 +454,43 @@ export async function resolveCredentialPools(
413
454
  modelBlockedCount += beforeCount - available.length;
414
455
  }
415
456
 
416
- // Every key is either key-wide rate-limited or blocked by this
417
- // model's weekly window, and at least one is blocked specifically by
418
- // the model (not just legacy key-wide rate limiting) — the picker
419
- // can't make progress for this model on this pool. Default policy
420
- // fails fast instead of looping the worker through the same
421
- // exhausted key every few minutes.
422
- if (
423
- opts?.enforceModelCapacity &&
424
- window &&
425
- modelFamily &&
426
- available &&
427
- available.length === 0 &&
428
- modelBlockedCount > 0
429
- ) {
457
+ const seatBlockedIndices = info?.seatBlockedIndices ?? [];
458
+ const seatBlockedCount = seatBlockedIndices.length;
459
+ let seatFallbackIndices: number[] | undefined;
460
+
461
+ // Every key is key-wide rate-limited, blocked by this model's weekly
462
+ // window, or on a seat that cannot run the model, and at least one is
463
+ // blocked specifically for the model (not just legacy key-wide rate
464
+ // limiting) — the picker can't make progress for this model on this
465
+ // pool. Default policy fails fast instead of looping the worker
466
+ // through the same exhausted key every few minutes. A seat block is a
467
+ // plan fact with no window and no reset, so the fallback pick excludes
468
+ // seat-blocked keys: a pick from them is a guaranteed failure. It fails
469
+ // only when every key is seat-blocked.
470
+ if (opts?.enforceModelCapacity && modelFamily && available && available.length === 0) {
430
471
  const policy = (env.MODEL_WINDOW_EXHAUSTED_POLICY ?? "fail").trim().toLowerCase();
431
- if (policy !== "fallback") {
472
+ const windowExhausted =
473
+ window !== undefined && modelBlockedCount > 0 && policy !== "fallback";
474
+ if (seatBlockedCount > 0) {
475
+ seatFallbackIndices = splitCredentialPool(val)
476
+ .map((_, i) => i)
477
+ .filter((i) => !seatBlockedIndices.includes(i));
478
+ }
479
+ if (windowExhausted || seatFallbackIndices?.length === 0) {
432
480
  throw new ModelWindowExhaustedError({
433
481
  model: modelFamily,
434
- window,
482
+ window: window ?? "",
435
483
  earliestResetAt: info?.earliestModelResetAt ?? null,
436
484
  keyType: envVar,
485
+ seatBlockedCount,
486
+ modelBlockedCount,
437
487
  });
438
488
  }
439
489
  }
440
490
 
441
- const result = selectCredential(val, available, envVar);
491
+ const result = seatFallbackIndices
492
+ ? { ...selectCredential(val, seatFallbackIndices, envVar), isRateLimitFallback: true }
493
+ : selectCredential(val, available, envVar);
442
494
  env[envVar] = result.selected;
443
495
  const availInfo = available ? ` (${available.length} available of ${result.total})` : "";
444
496
  console.log(
@@ -448,6 +500,7 @@ export async function resolveCredentialPools(
448
500
  ...result,
449
501
  modelBlockedIndices: info?.modelBlockedIndices,
450
502
  earliestModelResetAt: info?.earliestModelResetAt,
503
+ seatBlockedIndices: info?.seatBlockedIndices,
451
504
  });
452
505
  }
453
506
  }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * dsh `status.step_end.usage` normalization, shared by the worker adapter
3
+ * (session cost row) and the dashboard log parser (turn summary row) so both
4
+ * surfaces count the same tokens. Pure, no imports: `apps/ui` bundles it.
5
+ *
6
+ * dsh counts are disjoint and `totalTokens` is always their sum:
7
+ * `inputTokens + outputTokens + cacheReadTokens + cacheWriteTokens` on both
8
+ * routes (pi-ai `parseChunkUsage` for OpenRouter, `dsh-llm-deepseek` for the
9
+ * direct API). Reasoning is folded into `outputTokens`, not added on top.
10
+ *
11
+ * The cache buckets can go missing while `totalTokens` keeps them: dsh omits a
12
+ * zero cache field, and when a step is retried `dsh-headless` sums an optional
13
+ * bucket only if every attempt reported it. A failed attempt reports zeroed
14
+ * usage, so the step's `cacheReadTokens` disappears and only `totalTokens`
15
+ * still carries it. The missing remainder is derived from `totalTokens`.
16
+ */
17
+ export interface DshStepUsage {
18
+ /** Prompt tokens not served from cache. */
19
+ input: number;
20
+ output: number;
21
+ cacheRead: number;
22
+ cacheWrite: number;
23
+ /** Subset of `output`; only some routes report it. */
24
+ reasoning: number;
25
+ }
26
+
27
+ function count(value: unknown): number {
28
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
29
+ }
30
+
31
+ /** Normalizes one `step_end.usage` object; `null` when the step reported none. */
32
+ export function normalizeDshStepUsage(usage: unknown): DshStepUsage | null {
33
+ if (!usage || typeof usage !== "object") return null;
34
+ const u = usage as Record<string, unknown>;
35
+ const input = count(u.inputTokens);
36
+ const output = count(u.outputTokens);
37
+ const cacheWrite = count(u.cacheWriteTokens);
38
+ const cacheRead =
39
+ typeof u.cacheReadTokens === "number"
40
+ ? count(u.cacheReadTokens)
41
+ : Math.max(0, count(u.totalTokens) - input - output - cacheWrite);
42
+ return { input, output, cacheRead, cacheWrite, reasoning: count(u.reasoningTokens) };
43
+ }
44
+
45
+ export function addDshStepUsage(acc: DshStepUsage | undefined, step: DshStepUsage): DshStepUsage {
46
+ return {
47
+ input: (acc?.input ?? 0) + step.input,
48
+ output: (acc?.output ?? 0) + step.output,
49
+ cacheRead: (acc?.cacheRead ?? 0) + step.cacheRead,
50
+ cacheWrite: (acc?.cacheWrite ?? 0) + step.cacheWrite,
51
+ reasoning: (acc?.reasoning ?? 0) + step.reasoning,
52
+ };
53
+ }
@@ -95,6 +95,51 @@ export function parseRateLimitWindowTelemetry(
95
95
  }
96
96
  }
97
97
 
98
+ /**
99
+ * `rate_limit_info` fields that describe the seat's billing state (the
100
+ * `credits_required` payload). `scrubSecrets` does not match them, so they
101
+ * are dropped structurally before a log egress.
102
+ */
103
+ const SEAT_PAYLOAD_FIELDS = [
104
+ "overageDisabledReason",
105
+ "canUserPurchaseCredits",
106
+ "hasChargeableSavedPaymentMethod",
107
+ ] as const;
108
+
109
+ /**
110
+ * Returns a `rate_limit_event` message without the seat billing fields of
111
+ * its `rate_limit_info`. Any other message is returned unchanged. Error
112
+ * tracking reads the original message; only log egress uses the result.
113
+ */
114
+ export function redactRateLimitEvent<T>(message: T): T {
115
+ if (!message || typeof message !== "object") return message;
116
+ const json = message as Record<string, unknown>;
117
+ if (json.type !== "rate_limit_event") return message;
118
+ const info = json.rate_limit_info;
119
+ if (!info || typeof info !== "object") return message;
120
+ if (!SEAT_PAYLOAD_FIELDS.some((field) => field in info)) return message;
121
+ const redactedInfo = { ...(info as Record<string, unknown>) };
122
+ for (const field of SEAT_PAYLOAD_FIELDS) delete redactedInfo[field];
123
+ return { ...json, rate_limit_info: redactedInfo } as T;
124
+ }
125
+
126
+ /**
127
+ * Line form of `redactRateLimitEvent` for the CLI's stream-json output. A
128
+ * line that is not a `rate_limit_event` with seat billing fields is returned
129
+ * byte for byte.
130
+ */
131
+ export function redactRateLimitEventLine(line: string): string {
132
+ if (!line.includes('"rate_limit_event"')) return line;
133
+ let parsed: unknown;
134
+ try {
135
+ parsed = JSON.parse(line);
136
+ } catch {
137
+ return line;
138
+ }
139
+ const redacted = redactRateLimitEvent(parsed);
140
+ return redacted === parsed ? line : JSON.stringify(redacted);
141
+ }
142
+
98
143
  /**
99
144
  * Maximum cooldown horizon for a rate-limit reset. A weekly OAuth limit resets
100
145
  * up to ~7 days out, so the cap must be at least that or a weekly-limited key
@@ -186,6 +231,14 @@ export class SessionErrorTracker {
186
231
  private modelRateLimit:
187
232
  | { window: string; model: ModelFamily; resetAtMs: number; observedAt: string }
188
233
  | undefined;
234
+ /**
235
+ * Stashed `credits_required` rejection: the key's seat cannot run the
236
+ * session's model (e.g. "Fable 5.1 requires usage credits"). A seat fact,
237
+ * not a rate limit, so it never lands in `keyWideRejections`.
238
+ */
239
+ private creditsRequired:
240
+ | { observedAt: string; resetsAtMs?: number; overageDisabledReason?: string }
241
+ | undefined;
189
242
  private rateLimitWindows: RateLimitWindowTelemetry = {};
190
243
 
191
244
  /** Record an error from an assistant message with message.error field */
@@ -265,6 +318,21 @@ export class SessionErrorTracker {
265
318
  return;
266
319
  }
267
320
 
321
+ // Checked before the resetsAt validation: the observed event carries a
322
+ // resetsAt, but this branch must not depend on it.
323
+ if (info.errorCode === "credits_required") {
324
+ this.creditsRequired = {
325
+ observedAt,
326
+ resetsAtMs:
327
+ typeof info.resetsAt === "number" && Number.isFinite(info.resetsAt)
328
+ ? info.resetsAt * 1000
329
+ : undefined,
330
+ overageDisabledReason:
331
+ typeof info.overageDisabledReason === "string" ? info.overageDisabledReason : undefined,
332
+ };
333
+ return;
334
+ }
335
+
268
336
  const resetsAtSec = info.resetsAt;
269
337
  if (typeof resetsAtSec !== "number" || !Number.isFinite(resetsAtSec) || resetsAtSec <= 0) {
270
338
  console.warn(
@@ -344,6 +412,18 @@ export class SessionErrorTracker {
344
412
  };
345
413
  }
346
414
 
415
+ /**
416
+ * Returns the stashed `credits_required` rejection, or undefined if none was
417
+ * seen in this session. It never sets the key-wide reset time.
418
+ */
419
+ getCreditsRequired(): { observedAt: string; overageDisabledReason?: string } | undefined {
420
+ if (!this.creditsRequired) return undefined;
421
+ return {
422
+ observedAt: this.creditsRequired.observedAt,
423
+ overageDisabledReason: this.creditsRequired.overageDisabledReason,
424
+ };
425
+ }
426
+
347
427
  hasErrors(): boolean {
348
428
  return this.errors.length > 0;
349
429
  }
@@ -1,12 +1,13 @@
1
1
  /**
2
- * The worker's harness CLI version (`claude --version` / `codex --version`),
2
+ * The worker's harness CLI version (`claude --version`, `codex --version`, or `pi --version`),
3
3
  * reported on register and used to key harness_model_support rows. Worker-safe
4
4
  * (no DB). Probed once per process; null when the CLI is missing or silent.
5
+ * An unprobed harness returns a blank string to clear stale registration data.
5
6
  */
6
7
  import { isUnknownModelError } from "./harness-model-error";
7
8
  import { scrubSecrets } from "./secret-scrubber";
8
9
 
9
- const CLI_BINARY: Record<string, string> = { claude: "claude", codex: "codex" };
10
+ const CLI_BINARY: Record<string, string> = { claude: "claude", codex: "codex", pi: "pi" };
10
11
  const PROBE_TIMEOUT_MS = 10_000;
11
12
  const VERSION_RE = /\d+\.\d+\.\d+(?:[-+][\w.]+)?/;
12
13
 
@@ -18,7 +19,7 @@ export function parseCliVersion(output: string): string | null {
18
19
 
19
20
  export function probeHarnessCliVersion(harness: string): Promise<string | null> {
20
21
  const binary = CLI_BINARY[harness];
21
- if (!binary) return Promise.resolve(null);
22
+ if (!binary) return Promise.resolve("");
22
23
  let pending = probed.get(harness);
23
24
  if (!pending) {
24
25
  pending = (async () => {
@@ -66,7 +67,8 @@ export async function reportHarnessModelOutcome(opts: {
66
67
  failureReason?: string;
67
68
  fetchImpl?: typeof fetch;
68
69
  }): Promise<void> {
69
- if (!opts.model || !CLI_BINARY[opts.harness]) return;
70
+ // Only harnesses with verified unknown-model error classification report outcomes.
71
+ if (!opts.model || (opts.harness !== "claude" && opts.harness !== "codex")) return;
70
72
  const cliVersion = await cachedHarnessCliVersion(opts.harness);
71
73
  if (!cliVersion) return;
72
74
  const status =
@@ -192,6 +192,17 @@ export async function defaultSpawnClaudeCli(
192
192
  }
193
193
  }
194
194
 
195
+ /** Parsed output plus the model string that produced it. */
196
+ export interface CompleteStructuredResult<TData> {
197
+ data: TData;
198
+ /**
199
+ * Model that served the call, as resolved from the credential
200
+ * (`provider/model-id`, or the `claude -p` alias such as `haiku`). Lets
201
+ * callers record which judge produced the output.
202
+ */
203
+ model: string;
204
+ }
205
+
195
206
  /**
196
207
  * Run a structured-output completion. Returns the parsed object on success,
197
208
  * `null` on auth-missing or exhausted retries (errors logged, never thrown).
@@ -199,6 +210,18 @@ export async function defaultSpawnClaudeCli(
199
210
  export async function completeStructured<TZod extends z.ZodTypeAny>(
200
211
  opts: CompleteStructuredOptions<TZod>,
201
212
  ): Promise<z.infer<TZod> | null> {
213
+ const result = await completeStructuredWithModel(opts);
214
+ return result ? result.data : null;
215
+ }
216
+
217
+ /**
218
+ * Same as {@link completeStructured}, but also reports the model that served
219
+ * the call. Use it when the output must be attributable (e.g. `llm` memory
220
+ * ratings record their judge).
221
+ */
222
+ export async function completeStructuredWithModel<TZod extends z.ZodTypeAny>(
223
+ opts: CompleteStructuredOptions<TZod>,
224
+ ): Promise<CompleteStructuredResult<z.infer<TZod>> | null> {
202
225
  const retries = opts.retries ?? 3;
203
226
  const callerTag = opts.callerTag ?? "<unset>";
204
227
 
@@ -254,7 +277,7 @@ export async function completeStructured<TZod extends z.ZodTypeAny>(
254
277
  }
255
278
  const validated = opts.zodSchema.safeParse(parsedJson);
256
279
  if (validated.success) {
257
- return validated.data;
280
+ return { data: validated.data, model: cred.modelDefault };
258
281
  }
259
282
  lastErr = validated.error;
260
283
  } catch (err) {
@@ -343,7 +366,7 @@ export async function completeStructured<TZod extends z.ZodTypeAny>(
343
366
  if (parsedText !== undefined) {
344
367
  const validatedText = opts.zodSchema.safeParse(parsedText);
345
368
  if (validatedText.success) {
346
- return validatedText.data;
369
+ return { data: validatedText.data, model: cred.modelDefault };
347
370
  }
348
371
  }
349
372
  }
@@ -354,7 +377,7 @@ export async function completeStructured<TZod extends z.ZodTypeAny>(
354
377
 
355
378
  const validated = opts.zodSchema.safeParse(toolCall.arguments);
356
379
  if (validated.success) {
357
- return validated.data;
380
+ return { data: validated.data, model: cred.modelDefault };
358
381
  }
359
382
  userPrompt = `${userPrompt}\n\nThe ${opts.toolName} arguments did not validate: ${validated.error.message}. Please retry with correct arguments.`;
360
383
  lastErr = validated.error;
@@ -15,6 +15,7 @@
15
15
  * Worker-safe: uses fetch() only, no bun:sqlite import.
16
16
  */
17
17
 
18
+ import { isFirstPartyAnthropicUrl } from "@desplega/model-routing";
18
19
  import {
19
20
  InMemoryCredentialStore,
20
21
  type OAuthCredential,
@@ -24,7 +25,7 @@ import { registerBunOAuthFlows } from "@earendil-works/pi-ai/bun-oauth";
24
25
  import { getEnvApiKey } from "@earendil-works/pi-ai/compat";
25
26
  import { builtinModels } from "@earendil-works/pi-ai/providers/all";
26
27
  import { getValidCodexOAuth, persistCodexOAuth } from "../../providers/codex-oauth/storage.js";
27
- import { type CredentialKind, DEFAULT_MODEL, resolveModelString } from "./models.js";
28
+ import { type CredentialKind, DEFAULT_MODEL, resolveRaterModelString } from "./models.js";
28
29
 
29
30
  registerBunOAuthFlows();
30
31
 
@@ -69,6 +70,12 @@ const resolveOAuthApiKey: OAuthApiKeyResolver = async (providerId, credentials)
69
70
  };
70
71
  };
71
72
 
73
+ /**
74
+ * `modelDefault` is the session-summary / `llm` memory-rater model for the
75
+ * credential kind (`resolveRaterModelString`: `MEMORY_RATER_MODEL` env, else
76
+ * the pinned `MEMORY_RATER_DEFAULT_MODEL`). It is not the general
77
+ * `DEFAULT_MODEL`; callers that want that (workflow LLM nodes) read it directly.
78
+ */
72
79
  export type ResolvedCredential =
73
80
  | {
74
81
  kind: "openrouter" | "anthropic" | "openai" | "openai-codex";
@@ -107,7 +114,8 @@ export interface ResolveCredentialOptions {
107
114
  *
108
115
  * Precedence (top wins):
109
116
  * 1. `env.OPENROUTER_API_KEY` (via pi-ai `getEnvApiKey("openrouter")`)
110
- * 2. `env.ANTHROPIC_API_KEY` (via pi-ai `getEnvApiKey("anthropic")`)
117
+ * 2. `env.ANTHROPIC_API_KEY` (via pi-ai `getEnvApiKey("anthropic")`), unless
118
+ * `ANTHROPIC_BASE_URL` points at a gateway (the key is the gateway's)
111
119
  * 3. `env.OPENAI_API_KEY` (via pi-ai `getEnvApiKey("openai")`)
112
120
  * 4. codex OAuth (only when `apiUrl && apiKey` are provided)
113
121
  * 5. `env.CLAUDE_CODE_OAUTH_TOKEN` → claude-cli fallback
@@ -128,17 +136,23 @@ export async function resolveCredential(
128
136
  return {
129
137
  kind: "openrouter",
130
138
  apiKey: openrouterKey,
131
- modelDefault: resolveModelString("openrouter"),
139
+ modelDefault: resolveRaterModelString("openrouter"),
132
140
  };
133
141
  }
134
142
 
135
- // 2. Anthropic.
136
- const anthropicKey = env.ANTHROPIC_API_KEY ?? getEnvKey("anthropic");
143
+ // 2. Anthropic. pi-ai sends this key to api.anthropic.com, so skip it when
144
+ // ANTHROPIC_BASE_URL names a gateway: the key belongs to the gateway (#1800).
145
+ const anthropicBaseUrl = env.ANTHROPIC_BASE_URL?.trim();
146
+ const anthropicKeyIsForGateway =
147
+ Boolean(anthropicBaseUrl) && !isFirstPartyAnthropicUrl(anthropicBaseUrl ?? "");
148
+ const anthropicKey = anthropicKeyIsForGateway
149
+ ? undefined
150
+ : (env.ANTHROPIC_API_KEY ?? getEnvKey("anthropic"));
137
151
  if (anthropicKey) {
138
152
  return {
139
153
  kind: "anthropic",
140
154
  apiKey: anthropicKey,
141
- modelDefault: resolveModelString("anthropic"),
155
+ modelDefault: resolveRaterModelString("anthropic"),
142
156
  };
143
157
  }
144
158
 
@@ -148,7 +162,7 @@ export async function resolveCredential(
148
162
  return {
149
163
  kind: "openai",
150
164
  apiKey: openaiKey,
151
- modelDefault: resolveModelString("openai"),
165
+ modelDefault: resolveRaterModelString("openai"),
152
166
  };
153
167
  }
154
168
 
@@ -190,7 +204,7 @@ export async function resolveCredential(
190
204
  return {
191
205
  kind: "openai-codex",
192
206
  apiKey: oauthResult.apiKey,
193
- modelDefault: resolveModelString("openai-codex"),
207
+ modelDefault: resolveRaterModelString("openai-codex"),
194
208
  };
195
209
  }
196
210
  }
@@ -210,7 +224,7 @@ export async function resolveCredential(
210
224
  if (env.AGENT_SWARM_CLAUDE_OAUTH_TOKEN || env.CLAUDE_CODE_OAUTH_TOKEN) {
211
225
  return {
212
226
  kind: "claude-cli",
213
- modelDefault: resolveModelString("claude-cli"),
227
+ modelDefault: resolveRaterModelString("claude-cli"),
214
228
  };
215
229
  }
216
230
 
@@ -14,7 +14,9 @@
14
14
 
15
15
  export {
16
16
  type CompleteStructuredOptions,
17
+ type CompleteStructuredResult,
17
18
  completeStructured,
19
+ completeStructuredWithModel,
18
20
  } from "./complete-structured.js";
19
21
  export {
20
22
  type CredentialKind,
@@ -23,9 +25,14 @@ export {
23
25
  type ResolvedCredential,
24
26
  resolveCredential,
25
27
  } from "./credentials.js";
26
- export { parseModelStr, resolveModelString } from "./models.js";
28
+ export {
29
+ MEMORY_RATER_DEFAULT_MODEL,
30
+ parseModelStr,
31
+ resolveRaterModelString,
32
+ } from "./models.js";
27
33
  export {
28
34
  type SummarizeSessionOptions,
35
+ type SummarizeSessionResult,
29
36
  summarizeSession,
30
37
  summaryToolSchema,
31
38
  } from "./summarize-session.js";