@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
@@ -0,0 +1,374 @@
1
+ import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test";
2
+ import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import type { ExtensionFactory, ToolDefinition } from "@earendil-works/pi-coding-agent";
6
+ import * as piCodingAgent from "@earendil-works/pi-coding-agent";
7
+ import type { McpHttpClient, McpTool, McpToolCallResult } from "../mcp-client/http-client";
8
+ import {
9
+ createPiResourceLoader,
10
+ isCoreSwarmTool,
11
+ isPiToolDeferralEnabled,
12
+ mcpToolsToDefinitions,
13
+ PiMonoAdapter,
14
+ piDefaultToolAdditions,
15
+ piExtensionFactories,
16
+ SWARM_TOOL_NAMESPACE,
17
+ } from "../providers/pi-mono-adapter";
18
+ import type { ProviderSessionConfig } from "../providers/types";
19
+
20
+ const inputSchema = { type: "object", properties: {} };
21
+
22
+ function tool(name: string, extra: Partial<McpTool> = {}): McpTool {
23
+ return { name, description: `${name} tool`, inputSchema, ...extra };
24
+ }
25
+
26
+ function fakeClient(result: McpToolCallResult): McpHttpClient {
27
+ return { callTool: async () => result } as unknown as McpHttpClient;
28
+ }
29
+
30
+ function execute(def: ToolDefinition) {
31
+ return def.execute("call-1", {}, undefined, undefined, undefined as never);
32
+ }
33
+
34
+ function withEnv(key: string, value: string | undefined, fn: () => Promise<void> | void) {
35
+ return async () => {
36
+ const prev = process.env[key];
37
+ if (value === undefined) delete process.env[key];
38
+ else process.env[key] = value;
39
+ try {
40
+ await fn();
41
+ } finally {
42
+ if (prev === undefined) delete process.env[key];
43
+ else process.env[key] = prev;
44
+ }
45
+ };
46
+ }
47
+
48
+ describe("pi tool exposure mapping", () => {
49
+ const tools = [
50
+ tool("store-progress"),
51
+ tool("create-page"),
52
+ tool("slack-post", { _meta: { "anthropic/alwaysLoad": true } }),
53
+ ];
54
+ const client = fakeClient({ content: [] });
55
+
56
+ test("deferral on: core and manifest-preloaded tools stay direct, the rest defer", () => {
57
+ const defs = mcpToolsToDefinitions(client, tools, { deferNonCore: true });
58
+ const exposure = Object.fromEntries(defs.map((d) => [d.name, d.exposure]));
59
+ expect(exposure).toEqual({
60
+ "store-progress": "direct",
61
+ "create-page": "deferred",
62
+ "slack-post": "direct",
63
+ });
64
+ });
65
+
66
+ test("deferral off: no exposure is set, so pi declares every tool", () => {
67
+ const defs = mcpToolsToDefinitions(client, tools);
68
+ expect(defs.map((d) => d.exposure)).toEqual([undefined, undefined, undefined]);
69
+ });
70
+
71
+ test("core set follows CORE_TOOLS and alwaysLoad metadata only", () => {
72
+ expect(isCoreSwarmTool(tool("store-progress"))).toBe(true);
73
+ expect(isCoreSwarmTool(tool("create-page"))).toBe(false);
74
+ expect(isCoreSwarmTool(tool("create-page", { _meta: { "anthropic/alwaysLoad": true } }))).toBe(
75
+ true,
76
+ );
77
+ expect(isCoreSwarmTool(tool("create-page", { _meta: { "anthropic/alwaysLoad": false } }))).toBe(
78
+ false,
79
+ );
80
+ });
81
+
82
+ test("namespace is applied only when passed", () => {
83
+ const [withNs] = mcpToolsToDefinitions(client, [tool("create-page")], {
84
+ namespace: SWARM_TOOL_NAMESPACE,
85
+ });
86
+ const [withoutNs] = mcpToolsToDefinitions(client, [tool("create-page")]);
87
+ expect(withNs?.namespace).toEqual({ name: "agent-swarm" });
88
+ expect(withoutNs?.namespace).toBeUndefined();
89
+ });
90
+ });
91
+
92
+ describe("pi structuredContent pass-through", () => {
93
+ const outputSchema = {
94
+ type: "object",
95
+ properties: { success: { type: "boolean" }, details: {} },
96
+ additionalProperties: true,
97
+ };
98
+
99
+ test("returns structuredContent next to the text and keeps the server outputSchema", async () => {
100
+ const structuredContent = { success: true, message: "ok", details: { id: "t1" } };
101
+ const [def] = mcpToolsToDefinitions(
102
+ fakeClient({ content: [{ type: "text", text: "ok" }], structuredContent }),
103
+ [tool("get-task-details", { outputSchema })],
104
+ );
105
+ expect(def?.outputSchema).toEqual(outputSchema);
106
+ const result = await execute(def!);
107
+ expect(result.content).toEqual([{ type: "text", text: "ok" }]);
108
+ expect(result.structuredContent).toEqual(structuredContent);
109
+ });
110
+
111
+ test("tools without structuredContent or outputSchema are unchanged", async () => {
112
+ const [def] = mcpToolsToDefinitions(fakeClient({ content: [{ type: "text", text: "hi" }] }), [
113
+ tool("legacy"),
114
+ ]);
115
+ expect(def?.outputSchema).toBeUndefined();
116
+ const result = await execute(def!);
117
+ expect(result.structuredContent).toBeUndefined();
118
+ expect(result.content).toEqual([{ type: "text", text: "hi" }]);
119
+ });
120
+
121
+ test("isError still throws so pi reports the failure", async () => {
122
+ const [def] = mcpToolsToDefinitions(
123
+ fakeClient({
124
+ content: [{ type: "text", text: "boom" }],
125
+ structuredContent: { success: false },
126
+ isError: true,
127
+ }),
128
+ [tool("store-progress", { outputSchema })],
129
+ );
130
+ await expect(execute(def!)).rejects.toThrow("boom");
131
+ });
132
+ });
133
+
134
+ describe("pi extension factories and default tools per flag", () => {
135
+ const swarm: ExtensionFactory = () => {};
136
+
137
+ test("deferral off: only the swarm extension, no default-tool additions", () => {
138
+ expect(piExtensionFactories(swarm, { toolDeferral: false })).toEqual([swarm]);
139
+ expect(piDefaultToolAdditions({ toolDeferral: false })).toEqual([]);
140
+ });
141
+
142
+ test("deferral on: swarm extension plus tool_search", () => {
143
+ const factories = piExtensionFactories(swarm, { toolDeferral: true });
144
+ expect(factories).toHaveLength(2);
145
+ expect(factories[0]).toBe(swarm);
146
+ expect(piDefaultToolAdditions({ toolDeferral: true })).toEqual(["+tool_search"]);
147
+ });
148
+ });
149
+
150
+ describe("pi resource loader trust", () => {
151
+ test("task repo context files never reach the session; the server prompt does", async () => {
152
+ const root = mkdtempSync(join(tmpdir(), "pi-loader-"));
153
+ try {
154
+ const cwd = join(root, "repo");
155
+ const agentDir = join(root, "agent");
156
+ mkdirSync(cwd, { recursive: true });
157
+ mkdirSync(agentDir, { recursive: true });
158
+ writeFileSync(join(root, "AGENTS.md"), "ancestor instructions");
159
+ writeFileSync(join(cwd, "AGENTS.md"), "repo instructions");
160
+ writeFileSync(join(cwd, "CLAUDE.md"), "repo claude instructions");
161
+
162
+ const { resourceLoader } = await createPiResourceLoader({
163
+ cwd,
164
+ agentDir,
165
+ systemPrompt: "server prompt",
166
+ extensionFactories: [() => {}],
167
+ });
168
+
169
+ expect(resourceLoader.getAgentsFiles().agentsFiles).toEqual([]);
170
+ expect(resourceLoader.getAppendSystemPrompt()).toEqual(["server prompt"]);
171
+ expect(resourceLoader.getExtensions().extensions).toHaveLength(1);
172
+ } finally {
173
+ rmSync(root, { recursive: true, force: true });
174
+ }
175
+ });
176
+ });
177
+
178
+ describe("PI_TOOL_DEFERRAL flag", () => {
179
+ test("defaults off and parses boolean literals", () => {
180
+ expect(isPiToolDeferralEnabled({})).toBe(false);
181
+ expect(isPiToolDeferralEnabled({ PI_TOOL_DEFERRAL: "true" })).toBe(true);
182
+ expect(isPiToolDeferralEnabled({ PI_TOOL_DEFERRAL: "1" })).toBe(true);
183
+ expect(isPiToolDeferralEnabled({ PI_TOOL_DEFERRAL: "false" })).toBe(false);
184
+ });
185
+
186
+ test(
187
+ "traits.hasToolSearch follows the flag live",
188
+ withEnv("PI_TOOL_DEFERRAL", undefined, () => {
189
+ const adapter = new PiMonoAdapter();
190
+ expect(adapter.traits.hasToolSearch).toBe(false);
191
+ process.env.PI_TOOL_DEFERRAL = "true";
192
+ expect(adapter.traits.hasToolSearch).toBe(true);
193
+ }),
194
+ );
195
+ });
196
+
197
+ describe("PiMonoAdapter.createSession — tool deferral wiring", () => {
198
+ let spy: ReturnType<typeof spyOn> | undefined;
199
+ afterEach(() => spy?.mockRestore());
200
+
201
+ function serveTools() {
202
+ return Bun.serve({
203
+ port: 0,
204
+ async fetch(req) {
205
+ const url = new URL(req.url);
206
+ if (url.pathname === "/mcp") {
207
+ const body = (await req.json()) as { method?: string };
208
+ const tools =
209
+ body.method === "tools/list"
210
+ ? [tool("store-progress"), tool("create-page"), tool("memory-search")]
211
+ : undefined;
212
+ return Response.json({
213
+ jsonrpc: "2.0",
214
+ id: 1,
215
+ result: { protocolVersion: "2025-03-26", capabilities: {}, tools },
216
+ });
217
+ }
218
+ return Response.json({ servers: [] });
219
+ },
220
+ });
221
+ }
222
+
223
+ async function captureSessionOptions(): Promise<Record<string, unknown>> {
224
+ let captured: Record<string, unknown> = {};
225
+ spy = spyOn(piCodingAgent, "createAgentSession").mockImplementation((async (
226
+ opts: Record<string, unknown>,
227
+ ) => {
228
+ captured = opts;
229
+ return {
230
+ session: {
231
+ sessionId: "fake-session",
232
+ isStreaming: false,
233
+ model: undefined,
234
+ subscribe: () => () => {},
235
+ dispose: () => {},
236
+ bindExtensions: async () => {},
237
+ },
238
+ };
239
+ }) as typeof piCodingAgent.createAgentSession);
240
+ const server = serveTools();
241
+ try {
242
+ const config: ProviderSessionConfig = {
243
+ prompt: "hello",
244
+ systemPrompt: "",
245
+ model: "openrouter/google/gemini-3-flash-preview",
246
+ role: "worker",
247
+ agentId: "test-agent",
248
+ taskId: "test-task",
249
+ apiUrl: `http://localhost:${server.port}`,
250
+ apiKey: "example-test-key",
251
+ cwd: "/tmp",
252
+ logFile: `/tmp/pi-deferral-test-${Date.now()}-${Math.random().toString(36).slice(2)}.log`,
253
+ };
254
+ await new PiMonoAdapter().createSession(config);
255
+ } finally {
256
+ server.stop(true);
257
+ }
258
+ return captured;
259
+ }
260
+
261
+ test(
262
+ "flag on: non-core tools deferred and tool_search enabled by default",
263
+ withEnv("PI_TOOL_DEFERRAL", "true", async () => {
264
+ const opts = await captureSessionOptions();
265
+ const tools = opts.customTools as ToolDefinition[];
266
+ expect(Object.fromEntries(tools.map((t) => [t.name, t.exposure]))).toEqual({
267
+ "store-progress": "direct",
268
+ "create-page": "deferred",
269
+ "memory-search": "direct",
270
+ });
271
+ const settings = opts.settingsManager as piCodingAgent.SettingsManager;
272
+ expect(settings.getDefaultTools()).toContain("tool_search");
273
+ }),
274
+ );
275
+
276
+ test(
277
+ "flag off: every tool declared and no tool_search",
278
+ withEnv("PI_TOOL_DEFERRAL", undefined, async () => {
279
+ const opts = await captureSessionOptions();
280
+ const tools = opts.customTools as ToolDefinition[];
281
+ expect(tools.map((t) => t.exposure)).toEqual([undefined, undefined, undefined]);
282
+ const settings = opts.settingsManager as piCodingAgent.SettingsManager;
283
+ expect(settings.getDefaultTools() ?? []).not.toContain("tool_search");
284
+ }),
285
+ );
286
+ });
287
+
288
+ describe("PiMonoAdapter.createSession — real pi session", () => {
289
+ const envKeys = [
290
+ "PI_TOOL_DEFERRAL",
291
+ "PI_CODING_AGENT_DIR",
292
+ "OPENROUTER_API_KEY",
293
+ "OPENROUTER_BASE_URL",
294
+ ] as const;
295
+ const saved: Record<string, string | undefined> = {};
296
+ let agentDir = "";
297
+
298
+ beforeEach(() => {
299
+ for (const key of envKeys) saved[key] = process.env[key];
300
+ agentDir = mkdtempSync(join(tmpdir(), "pi-agent-dir-"));
301
+ process.env.PI_CODING_AGENT_DIR = agentDir;
302
+ process.env.OPENROUTER_API_KEY = "example-test-key";
303
+ // The session prompts on creation; keep that request off the network.
304
+ process.env.OPENROUTER_BASE_URL = "http://127.0.0.1:9/api/v1";
305
+ });
306
+ afterEach(() => {
307
+ for (const key of envKeys) {
308
+ if (saved[key] === undefined) delete process.env[key];
309
+ else process.env[key] = saved[key];
310
+ }
311
+ rmSync(agentDir, { recursive: true, force: true });
312
+ });
313
+
314
+ async function realSession() {
315
+ const server = Bun.serve({
316
+ port: 0,
317
+ async fetch(req) {
318
+ const url = new URL(req.url);
319
+ if (url.pathname !== "/mcp") return Response.json({ servers: [] });
320
+ const body = (await req.json()) as { id?: number; method?: string };
321
+ const result =
322
+ body.method === "tools/list"
323
+ ? { tools: [tool("store-progress"), tool("create-page")] }
324
+ : { protocolVersion: "2025-03-26", capabilities: {} };
325
+ return Response.json({ jsonrpc: "2.0", id: body.id ?? 1, result });
326
+ },
327
+ });
328
+ try {
329
+ const session = await new PiMonoAdapter().createSession({
330
+ prompt: "hello",
331
+ systemPrompt: "SWARM-PROMPT-MARKER",
332
+ model: "openrouter/google/gemini-3-flash-preview",
333
+ role: "worker",
334
+ agentId: "test-agent",
335
+ taskId: "test-task",
336
+ apiUrl: `http://localhost:${server.port}`,
337
+ apiKey: "example-test-key",
338
+ cwd: agentDir,
339
+ logFile: join(agentDir, "session.log"),
340
+ });
341
+ return (session as unknown as { agentSession: piCodingAgent.AgentSession }).agentSession;
342
+ } finally {
343
+ server.stop(true);
344
+ }
345
+ }
346
+
347
+ test("the swarm system prompt reaches the session", async () => {
348
+ const session = await realSession();
349
+ expect(session.systemPrompt).toContain("SWARM-PROMPT-MARKER");
350
+ session.dispose();
351
+ });
352
+
353
+ test("extensions in the task repo's .pi/ never load into the worker", async () => {
354
+ const marker = join(agentDir, "repo-extension-ran");
355
+ mkdirSync(join(agentDir, ".pi", "extensions"), { recursive: true });
356
+ writeFileSync(
357
+ join(agentDir, ".pi", "extensions", "repo.ts"),
358
+ `import { writeFileSync } from "node:fs";\nexport default () => writeFileSync(${JSON.stringify(marker)}, "1");\n`,
359
+ );
360
+ const session = await realSession();
361
+ expect(session.systemPrompt).toContain("SWARM-PROMPT-MARKER");
362
+ expect(existsSync(marker)).toBe(false);
363
+ session.dispose();
364
+ });
365
+
366
+ test("deferral on: deferred tools stay undeclared until tool_search loads them", async () => {
367
+ process.env.PI_TOOL_DEFERRAL = "true";
368
+ const session = await realSession();
369
+ expect(session.getActiveToolNames()).toContain("tool_search");
370
+ expect(session.getActiveToolNames()).toContain("store-progress");
371
+ expect(session.getActiveToolNames()).not.toContain("create-page");
372
+ session.dispose();
373
+ });
374
+ });
@@ -18,6 +18,9 @@ import { closeDb, getDb, initDb } from "../be/db";
18
18
  import "../be/seed-prompt-templates";
19
19
  import { getAllTemplateDefinitions } from "../prompts/registry";
20
20
  import { clearVolatileSecretsForTesting } from "../utils/secret-scrubber";
21
+ import { startHangWatchdog } from "./hang-watchdog";
22
+
23
+ startHangWatchdog();
21
24
 
22
25
  // @hono/node-server (pulled in transitively by @modelcontextprotocol/sdk's
23
26
  // streamableHttp transport) replaces globalThis.Response/Request with its own
@@ -73,6 +76,12 @@ const testTemplateGlobals = globalThis as typeof globalThis & {
73
76
  // so removing the key just forces the fast failure path (~0ms vs ~2s of API calls).
74
77
  delete process.env.OPENROUTER_API_KEY;
75
78
 
79
+ // Same reason for GitHub: the webhook handlers react 👀 on the triggering item with
80
+ // the App token or, failing that, `GITHUB_TOKEN`. A developer shell that exports
81
+ // GITHUB_TOKEN would otherwise send real reactions to api.github.com from every
82
+ // handler test. Suites that exercise the PAT path set it themselves.
83
+ delete process.env.GITHUB_TOKEN;
84
+
76
85
  // Deployment flags must not change the test suite's default registration and
77
86
  // task-admission contracts. Multi-runtime suites opt in by setting this to "true"
78
87
  // and restore it themselves. Tests of the code default explicitly delete it.
@@ -35,6 +35,8 @@ const SYSTEM_TEMPLATES = [
35
35
  "system.agent.steering",
36
36
  "system.agent.steering.delivery",
37
37
  "system.agent.tool_preload",
38
+ "system.agent.tool_discovery.search",
39
+ "system.agent.tool_discovery.direct",
38
40
  "system.agent.tools_skills",
39
41
  "system.agent.worker",
40
42
  "system.agent.worker.remote",
@@ -112,11 +114,11 @@ describe("Session templates: registration", () => {
112
114
  expect(sessionSystemEventTypes("session")).toEqual([...SESSION_TEMPLATES].sort());
113
115
  });
114
116
 
115
- test("registers 26 system and session templates in total", () => {
117
+ test("registers 28 system and session templates in total", () => {
116
118
  const all = getAllTemplateDefinitions();
117
119
  const sessionSystem = all.filter((d) => d.category === "system" || d.category === "session");
118
- // 21 system blocks + 5 session composites.
119
- expect(sessionSystem.length).toBe(26);
120
+ // 23 system blocks + 5 session composites.
121
+ expect(sessionSystem.length).toBe(28);
120
122
  });
121
123
 
122
124
  test("drops the v1 blocks that prompt v2 deleted", () => {
@@ -132,7 +132,6 @@ const TOUCH_POINTS: TouchPoint[] = [
132
132
  ],
133
133
  exempt: {
134
134
  acp: "A generic ACP target owns its own billing and the adapter reports totalCostUsd: 0.",
135
- dsh: "The dsh adapter reports no cost data today, so there is nothing to price.",
136
135
  },
137
136
  },
138
137
  {
@@ -168,7 +167,6 @@ const TOUCH_POINTS: TouchPoint[] = [
168
167
  "claude-managed":
169
168
  "Cloud harness: runs in Anthropic's sandbox, no local runtime to configure.",
170
169
  devin: "Cloud harness: runs on Devin's API, no local runtime to configure.",
171
- dsh: "KNOWN GAP (#1559): the server's LocalHarnessProviderSchema accepts dsh but the picker does not offer it yet.",
172
170
  },
173
171
  },
174
172
  ];
@@ -6,12 +6,13 @@
6
6
  //
7
7
  // Mirrors the narrow, single-purpose shape of src/tests/providers/codex-cost.test.ts.
8
8
 
9
- import { describe, expect, test } from "bun:test";
10
- import { mkdirSync, rmSync } from "node:fs";
9
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
10
+ import { mkdirSync, mkdtempSync, rmSync } from "node:fs";
11
11
  import { tmpdir } from "node:os";
12
12
  import { join } from "node:path";
13
- import { PiMonoSession } from "../../providers/pi-mono-adapter";
14
- import type { ProviderEvent, ProviderSessionConfig } from "../../providers/types";
13
+ import type { AgentSession, ExtensionToolContext } from "@earendil-works/pi-coding-agent";
14
+ import { PiMonoAdapter, PiMonoSession } from "../../providers/pi-mono-adapter";
15
+ import type { CostData, ProviderEvent, ProviderSessionConfig } from "../../providers/types";
15
16
 
16
17
  /**
17
18
  * Build a hand-rolled fake `AgentSession` that exercises the pi-mono-adapter
@@ -136,3 +137,188 @@ describe("PiMonoSession — provider tag on CostData", () => {
136
137
  }
137
138
  });
138
139
  });
140
+
141
+ // PI_CODEMODE_MODELS lets a codemode script call `models.classify()` and
142
+ // `models.generateImages()`. Those calls spend money that no assistant turn
143
+ // carries, so the question is whether the session cost the adapter reports
144
+ // includes them. On pi 1.0 it does, through four links, each exercised here
145
+ // against pi's real code:
146
+ // 1. the codemode tool puts the script's `models.*` usage on its result;
147
+ // 2. pi's agent loop copies `result.usage` onto the toolResult message;
148
+ // 3. `getSessionStats()` adds `usage` of every toolResult message;
149
+ // 4. `buildCostData` reports `stats.cost` as `totalCostUsd`.
150
+ describe("PiMonoSession — codemode models usage counts toward cost", () => {
151
+ const envKeys = [
152
+ "PI_CODEMODE",
153
+ "PI_CODEMODE_MODELS",
154
+ "PI_TOOL_DEFERRAL",
155
+ "PI_CODING_AGENT_DIR",
156
+ "OPENROUTER_API_KEY",
157
+ "OPENROUTER_BASE_URL",
158
+ ];
159
+ const saved: Record<string, string | undefined> = {};
160
+ let dir = "";
161
+
162
+ beforeEach(() => {
163
+ for (const key of envKeys) saved[key] = process.env[key];
164
+ dir = mkdtempSync(join(tmpdir(), "pi-cost-codemode-"));
165
+ process.env.PI_CODING_AGENT_DIR = join(dir, "agent");
166
+ process.env.OPENROUTER_API_KEY = "example-test-key";
167
+ // The session prompts on creation; keep that request off the network.
168
+ process.env.OPENROUTER_BASE_URL = "http://127.0.0.1:9/api/v1";
169
+ process.env.PI_CODEMODE = "true";
170
+ process.env.PI_CODEMODE_MODELS = "true";
171
+ delete process.env.PI_TOOL_DEFERRAL;
172
+ });
173
+ afterEach(() => {
174
+ for (const key of envKeys) {
175
+ if (saved[key] === undefined) delete process.env[key];
176
+ else process.env[key] = saved[key];
177
+ }
178
+ rmSync(dir, { recursive: true, force: true });
179
+ });
180
+
181
+ const CLASSIFIER_COST_USD = 0.0042;
182
+ const classifierUsage = {
183
+ input: 1200,
184
+ output: 40,
185
+ cacheRead: 0,
186
+ cacheWrite: 0,
187
+ totalTokens: 1240,
188
+ cost: { input: 0.004, output: 0.0002, cacheRead: 0, cacheWrite: 0, total: CLASSIFIER_COST_USD },
189
+ };
190
+
191
+ /** A model registry that answers `models.classify()` with a priced usage. */
192
+ function ctxWithClassifier(): ExtensionToolContext {
193
+ const classifier = { provider: "test", id: "clf-1", name: "Test classifier" };
194
+ return {
195
+ tools: [],
196
+ sessionManager: { getBranch: () => [] },
197
+ modelRegistry: {
198
+ getModelsOfType: () => [classifier],
199
+ getAvailableOfType: async () => [classifier],
200
+ getModelOfType: (type: string, provider: string, id: string) =>
201
+ type === "classifier" && provider === classifier.provider && id === classifier.id
202
+ ? classifier
203
+ : undefined,
204
+ classify: async () => ({ stopReason: "stop", output: {}, usage: classifierUsage }),
205
+ generateImages: async () => {
206
+ throw new Error("not used");
207
+ },
208
+ },
209
+ } as unknown as ExtensionToolContext;
210
+ }
211
+
212
+ const CLASSIFY_SCRIPT = `
213
+ const model = await models.getModelOfType("classifier", "test", "clf-1");
214
+ const result = await models.classify(model, {
215
+ state: {},
216
+ questions: { ok: { type: "bool", instructions: "Is it fine?", criteria: { true: "yes", false: "no" } } },
217
+ });
218
+ return result.stopReason;
219
+ `;
220
+
221
+ async function realSession(): Promise<{ provider: PiMonoSession; agentSession: AgentSession }> {
222
+ const server = Bun.serve({
223
+ port: 0,
224
+ async fetch(req) {
225
+ if (new URL(req.url).pathname !== "/mcp") return Response.json({ servers: [] });
226
+ const body = (await req.json()) as { id?: number; method?: string };
227
+ const result =
228
+ body.method === "tools/list"
229
+ ? { tools: [{ name: "store-progress", inputSchema: { type: "object" } }] }
230
+ : { protocolVersion: "2025-03-26", capabilities: {} };
231
+ return Response.json({ jsonrpc: "2.0", id: body.id ?? 1, result });
232
+ },
233
+ });
234
+ try {
235
+ const provider = (await new PiMonoAdapter().createSession({
236
+ prompt: "hello",
237
+ systemPrompt: "",
238
+ model: "openrouter/google/gemini-3-flash-preview",
239
+ role: "worker",
240
+ agentId: "test-agent",
241
+ taskId: "test-task",
242
+ apiUrl: `http://localhost:${server.port}`,
243
+ apiKey: "example-test-key",
244
+ cwd: dir,
245
+ logFile: join(dir, "session.log"),
246
+ })) as PiMonoSession;
247
+ return {
248
+ provider,
249
+ agentSession: (provider as unknown as { agentSession: AgentSession }).agentSession,
250
+ };
251
+ } finally {
252
+ server.stop(true);
253
+ }
254
+ }
255
+
256
+ test("a models.classify() script's usage reaches the cost the adapter reports", async () => {
257
+ const { provider, agentSession } = await realSession();
258
+ try {
259
+ const codemode = agentSession.getToolDefinition("codemode");
260
+ if (!codemode) throw new Error("codemode tool was not registered");
261
+
262
+ // 1. The real codemode tool, built by the adapter with models on.
263
+ const result = (await codemode.execute(
264
+ "call-1",
265
+ { code: CLASSIFY_SCRIPT },
266
+ undefined,
267
+ undefined,
268
+ ctxWithClassifier(),
269
+ )) as { content: Array<{ text?: string }>; usage?: typeof classifierUsage };
270
+ expect(result.content.map((item) => item.text ?? "").join("\n")).toStartWith(
271
+ "Script completed",
272
+ );
273
+ expect(result.usage?.cost.total).toBe(CLASSIFIER_COST_USD);
274
+
275
+ // 2. The toolResult message pi's agent loop builds (createToolResultMessage).
276
+ const before = agentSession.getSessionStats();
277
+ agentSession.sessionManager.appendMessage({
278
+ role: "toolResult",
279
+ toolCallId: "call-1",
280
+ toolName: "codemode",
281
+ content: [{ type: "text", text: "Script completed" }],
282
+ usage: result.usage,
283
+ isError: false,
284
+ timestamp: Date.now(),
285
+ } as unknown as Parameters<typeof agentSession.sessionManager.appendMessage>[0]);
286
+
287
+ // 3. pi's real session stats sum the toolResult usage.
288
+ const after = agentSession.getSessionStats();
289
+ expect(after.cost - before.cost).toBeCloseTo(CLASSIFIER_COST_USD, 10);
290
+ expect(after.tokens.input - before.tokens.input).toBe(classifierUsage.input);
291
+ expect(after.tokens.output - before.tokens.output).toBe(classifierUsage.output);
292
+
293
+ // 4. The adapter's CostData is built from those stats.
294
+ const costOf = (stats: typeof after): CostData =>
295
+ (provider as unknown as { buildCostData(s: typeof after): CostData }).buildCostData(stats);
296
+ expect(costOf(after).totalCostUsd - costOf(before).totalCostUsd).toBeCloseTo(
297
+ CLASSIFIER_COST_USD,
298
+ 10,
299
+ );
300
+ } finally {
301
+ agentSession.dispose();
302
+ }
303
+ }, 30_000);
304
+
305
+ test("models flag off: a script cannot reach models, so there is no usage to miss", async () => {
306
+ process.env.PI_CODEMODE_MODELS = "false";
307
+ const { agentSession } = await realSession();
308
+ try {
309
+ const codemode = agentSession.getToolDefinition("codemode");
310
+ if (!codemode) throw new Error("codemode tool was not registered");
311
+ const result = (await codemode.execute(
312
+ "call-2",
313
+ { code: CLASSIFY_SCRIPT },
314
+ undefined,
315
+ undefined,
316
+ ctxWithClassifier(),
317
+ )) as { content: Array<{ text?: string }>; usage?: unknown };
318
+ expect(result.content.map((item) => item.text ?? "").join("\n")).toStartWith("Script failed");
319
+ expect(result.usage).toBeUndefined();
320
+ } finally {
321
+ agentSession.dispose();
322
+ }
323
+ }, 30_000);
324
+ });