@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,20 +1,43 @@
1
- import { describe, expect, mock, test } from "bun:test";
2
- import { readPkgVersion } from "../providers/harness-version";
1
+ import { afterEach, describe, expect, mock, test } from "bun:test";
2
+ import { mkdirSync, mkdtempSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { readInstalledCliPackageVersion, readPkgVersion } from "../providers/harness-version";
3
6
 
4
7
  describe("readPkgVersion", () => {
5
- test("reads pi version from the CLI before package.json probes", () => {
8
+ test("reads the pi version from the installed package without spawning", () => {
9
+ const spawn = mock(() => ({ stdout: "9.9.9\n", stderr: "", status: 0 }));
10
+ const requirePackageJson = mock(() => ({ version: "0.0.0" }));
11
+ const readInstalledVersion = mock(() => "0.79.1");
12
+
13
+ const version = readPkgVersion("@earendil-works/pi-coding-agent", {
14
+ requirePackageJson,
15
+ spawn,
16
+ readInstalledVersion,
17
+ });
18
+
19
+ expect(version).toBe("0.79.1");
20
+ expect(readInstalledVersion).toHaveBeenCalledWith("pi", "@earendil-works/pi-coding-agent");
21
+ expect(spawn).not.toHaveBeenCalled();
22
+ expect(requirePackageJson).not.toHaveBeenCalled();
23
+ });
24
+
25
+ test("reads pi version from the CLI when the installed package cannot be found", () => {
6
26
  const spawn = mock(() => ({ stdout: "0.79.1\n", stderr: "", status: 0 }));
7
27
  const requirePackageJson = mock(() => ({ version: "0.0.0" }));
8
28
 
9
29
  const version = readPkgVersion("@earendil-works/pi-coding-agent", {
10
30
  requirePackageJson,
11
31
  spawn,
32
+ readInstalledVersion: () => undefined,
12
33
  });
13
34
 
14
35
  expect(version).toBe("0.79.1");
15
36
  expect(spawn).toHaveBeenCalledWith("pi", ["--version"], {
16
37
  encoding: "utf8",
17
38
  stdio: ["ignore", "pipe", "pipe"],
39
+ timeout: 5_000,
40
+ killSignal: "SIGKILL",
18
41
  });
19
42
  expect(requirePackageJson).not.toHaveBeenCalled();
20
43
  });
@@ -26,12 +49,15 @@ describe("readPkgVersion", () => {
26
49
  const version = readPkgVersion("@opencode-ai/sdk", {
27
50
  requirePackageJson,
28
51
  spawn,
52
+ readInstalledVersion: () => undefined,
29
53
  });
30
54
 
31
55
  expect(version).toBe("1.16.2");
32
56
  expect(spawn).toHaveBeenCalledWith("opencode", ["--version"], {
33
57
  encoding: "utf8",
34
58
  stdio: ["ignore", "pipe", "pipe"],
59
+ timeout: 5_000,
60
+ killSignal: "SIGKILL",
35
61
  });
36
62
  expect(requirePackageJson).not.toHaveBeenCalled();
37
63
  });
@@ -40,8 +66,49 @@ describe("readPkgVersion", () => {
40
66
  const version = readPkgVersion("@earendil-works/pi-coding-agent", {
41
67
  requirePackageJson: () => ({ version: "0.79.1" }),
42
68
  spawn: mock(() => ({ stdout: "", stderr: "", status: 0 })),
69
+ readInstalledVersion: () => undefined,
43
70
  });
44
71
 
45
72
  expect(version).toBe("0.79.1");
46
73
  });
47
74
  });
75
+
76
+ describe("readInstalledCliPackageVersion", () => {
77
+ const originalPath = process.env.PATH;
78
+ const tmpRoots: string[] = [];
79
+
80
+ afterEach(() => {
81
+ process.env.PATH = originalPath;
82
+ for (const dir of tmpRoots.splice(0)) rmSync(dir, { recursive: true, force: true });
83
+ });
84
+
85
+ /** A global-install layout: `<root>/bin/fake-cli` -> `<root>/lib/node_modules/@acme/fake/dist/bundle/cli.js`. */
86
+ function installFakeCli(packageName: string, version: string): string {
87
+ const root = mkdtempSync(join(tmpdir(), "harness-version-"));
88
+ tmpRoots.push(root);
89
+ const pkgDir = join(root, "lib", "node_modules", "@acme", "fake");
90
+ mkdirSync(join(pkgDir, "dist", "bundle"), { recursive: true });
91
+ mkdirSync(join(root, "bin"), { recursive: true });
92
+ writeFileSync(join(pkgDir, "package.json"), JSON.stringify({ name: packageName, version }));
93
+ // A nested package.json without a matching name must be walked past.
94
+ writeFileSync(join(pkgDir, "dist", "package.json"), JSON.stringify({ type: "module" }));
95
+ writeFileSync(join(pkgDir, "dist", "bundle", "cli.js"), "#!/usr/bin/env node\n");
96
+ symlinkSync(join(pkgDir, "dist", "bundle", "cli.js"), join(root, "bin", "fake-cli"));
97
+ return join(root, "bin");
98
+ }
99
+
100
+ test("resolves the owning package version through the PATH symlink", () => {
101
+ process.env.PATH = installFakeCli("@acme/fake", "1.2.3");
102
+ expect(readInstalledCliPackageVersion("fake-cli", "@acme/fake")).toBe("1.2.3");
103
+ });
104
+
105
+ test("returns undefined when the owning package is a different one", () => {
106
+ process.env.PATH = installFakeCli("@acme/other", "1.2.3");
107
+ expect(readInstalledCliPackageVersion("fake-cli", "@acme/fake")).toBeUndefined();
108
+ });
109
+
110
+ test("returns undefined when the command is not on PATH", () => {
111
+ process.env.PATH = installFakeCli("@acme/fake", "1.2.3");
112
+ expect(readInstalledCliPackageVersion("not-installed-cli", "@acme/fake")).toBeUndefined();
113
+ });
114
+ });
@@ -711,6 +711,27 @@ describe("Tasks", () => {
711
711
  expect(body.alreadyFinished).toBe(true);
712
712
  });
713
713
 
714
+ test("POST /api/tasks/:id/finish — records the provider of a task whose harness never started", async () => {
715
+ const agentId = randomUUID();
716
+ expect((await post("/api/agents", { agentId, body: { name: "SpawnFailWorker" } })).status).toBe(
717
+ 201,
718
+ );
719
+ const created = await post("/api/tasks", {
720
+ agentId: ids.leadAgent,
721
+ body: { task: "Spawn failure provider test", agentId, routingReason: "human_pinned" },
722
+ });
723
+ const taskId = created.body.id as string;
724
+ expect((await get("/api/poll", { agentId })).body.trigger?.taskId).toBe(taskId);
725
+
726
+ const finished = await post(`/api/tasks/${taskId}/finish`, {
727
+ agentId,
728
+ body: { status: "failed", failureReason: "Spawn failed: no binary", provider: "dsh" },
729
+ });
730
+ expect(finished.status).toBe(200);
731
+ expect(finished.body.task.status).toBe("failed");
732
+ expect(finished.body.task.provider).toBe("dsh");
733
+ });
734
+
714
735
  test("POST /api/tasks/:id/finish — terminal retries reject, no-op, or force text only", async () => {
715
736
  const agentId = randomUUID();
716
737
  const registered = await post("/api/agents", {
@@ -0,0 +1,195 @@
1
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
2
+ import {
3
+ createServer as createHttpServer,
4
+ type IncomingMessage,
5
+ type Server,
6
+ type ServerResponse,
7
+ } from "node:http";
8
+ import {
9
+ closeDb,
10
+ completeTask,
11
+ createAgent,
12
+ createTaskExtended,
13
+ getTaskById,
14
+ initDb,
15
+ startTask,
16
+ } from "../be/db";
17
+ import { handleTasks } from "../http/tasks";
18
+ import { getPathSegments, parseQueryParams } from "../http/utils";
19
+ import { type HttpRequestAuth, setRequestAuth } from "../utils/request-auth-context";
20
+ import { listenOnFreePort } from "./test-net";
21
+
22
+ // Regression: an agent got 200 from HTTP progress and HTTP cancel on another
23
+ // agent's in-progress task. Progress follows task.progress.write (assignee,
24
+ // lead, human); cancel follows the MCP cancel-task policy for agents (lead or
25
+ // creator). Humans and the keyed runner wrapper keep access.
26
+ const ownerId = "aaaa0000-0000-4000-8000-000000000b01";
27
+ const otherId = "bbbb0000-0000-4000-8000-000000000b02";
28
+ const creatorId = "cccc0000-0000-4000-8000-000000000b03";
29
+ const leadId = "dddd0000-0000-4000-8000-000000000b04";
30
+ const userId = "eeee0000-0000-4000-8000-000000000b05";
31
+
32
+ type Caller =
33
+ | { as: "operator"; agentId?: string }
34
+ | { as: "session"; agentId: string }
35
+ | { as: "user" };
36
+
37
+ let server: Server;
38
+ let baseUrl: string;
39
+
40
+ function authFor(caller: Caller): HttpRequestAuth {
41
+ if (caller.as === "session") {
42
+ return { kind: "agent", agentId: caller.agentId, taskId: "session-task" };
43
+ }
44
+ if (caller.as === "user") {
45
+ return { kind: "user", userId, user: { id: userId } as never };
46
+ }
47
+ return { kind: "operator", fingerprint: "http-task-write-ownership" };
48
+ }
49
+
50
+ async function post(path: string, caller: Caller, body: unknown) {
51
+ const headers: Record<string, string> = {
52
+ "Content-Type": "application/json",
53
+ "x-test-caller": JSON.stringify(caller),
54
+ };
55
+ if (caller.as === "operator" && caller.agentId) headers["X-Agent-ID"] = caller.agentId;
56
+ const response = await fetch(`${baseUrl}${path}`, {
57
+ method: "POST",
58
+ headers,
59
+ body: JSON.stringify(body),
60
+ });
61
+ return response.status;
62
+ }
63
+
64
+ async function inProgressTask() {
65
+ const row = await createTaskExtended("http write ownership", {
66
+ agentId: ownerId,
67
+ creatorAgentId: creatorId,
68
+ source: "system",
69
+ followUpConfig: { disabled: true },
70
+ });
71
+ await startTask(row.id);
72
+ return row;
73
+ }
74
+
75
+ beforeAll(async () => {
76
+ initDb(":memory:");
77
+ await createAgent({ id: ownerId, name: "Owner", isLead: false, status: "idle" });
78
+ await createAgent({ id: otherId, name: "Other", isLead: false, status: "idle" });
79
+ await createAgent({ id: creatorId, name: "Creator", isLead: false, status: "idle" });
80
+ await createAgent({ id: leadId, name: "Lead", isLead: true, status: "idle" });
81
+
82
+ server = createHttpServer(async (req: IncomingMessage, res: ServerResponse) => {
83
+ const caller = JSON.parse(String(req.headers["x-test-caller"])) as Caller;
84
+ setRequestAuth(req, authFor(caller));
85
+ res.setHeader("Content-Type", "application/json");
86
+ const pathSegments = getPathSegments(req.url ?? "");
87
+ const query = parseQueryParams(req.url ?? "");
88
+ const myAgentId = req.headers["x-agent-id"] as string | undefined;
89
+ if (await handleTasks(req, res, pathSegments, query, myAgentId)) return;
90
+ res.writeHead(404);
91
+ res.end(JSON.stringify({ error: "Not found" }));
92
+ });
93
+ const port = await listenOnFreePort(server);
94
+ baseUrl = `http://127.0.0.1:${port}`;
95
+ });
96
+
97
+ afterAll(async () => {
98
+ await new Promise<void>((resolve) => server.close(() => resolve()));
99
+ closeDb();
100
+ });
101
+
102
+ describe("POST /api/tasks/{id}/progress ownership", () => {
103
+ test("another agent on the shared key gets 403 and writes nothing", async () => {
104
+ const task = await inProgressTask();
105
+ const status = await post(
106
+ `/api/tasks/${task.id}/progress`,
107
+ { as: "operator", agentId: otherId },
108
+ { progress: "not mine" },
109
+ );
110
+ expect(status).toBe(403);
111
+ expect((await getTaskById(task.id))?.progress).toBeUndefined();
112
+ });
113
+
114
+ test("another agent on a session token gets 403", async () => {
115
+ const task = await inProgressTask();
116
+ const status = await post(
117
+ `/api/tasks/${task.id}/progress`,
118
+ { as: "session", agentId: otherId },
119
+ { progress: "not mine" },
120
+ );
121
+ expect(status).toBe(403);
122
+ expect((await getTaskById(task.id))?.progress).toBeUndefined();
123
+ });
124
+
125
+ test("the assignee, a lead, the keyed runner wrapper, and a user may write", async () => {
126
+ const task = await inProgressTask();
127
+ const callers: Caller[] = [
128
+ { as: "operator", agentId: ownerId },
129
+ { as: "operator", agentId: leadId },
130
+ { as: "operator" },
131
+ { as: "user" },
132
+ ];
133
+ for (const [i, caller] of callers.entries()) {
134
+ const status = await post(`/api/tasks/${task.id}/progress`, caller, {
135
+ progress: `step ${i}`,
136
+ });
137
+ expect(status, JSON.stringify(caller)).toBe(200);
138
+ expect((await getTaskById(task.id))?.progress).toBe(`step ${i}`);
139
+ }
140
+ });
141
+ });
142
+
143
+ describe("POST /api/tasks/{id}/progress on a terminal task", () => {
144
+ test("late harness progress after completion is a no-op", async () => {
145
+ const task = await inProgressTask();
146
+ const caller: Caller = { as: "operator", agentId: ownerId };
147
+ expect(await post(`/api/tasks/${task.id}/progress`, caller, { progress: "Reading" })).toBe(200);
148
+ await completeTask(task.id, "done");
149
+ expect(await post(`/api/tasks/${task.id}/progress`, caller, { progress: "step_end" })).toBe(
150
+ 200,
151
+ );
152
+ const after = await getTaskById(task.id);
153
+ expect(after?.status).toBe("completed");
154
+ expect(after?.progress).toBe("Reading");
155
+ });
156
+ });
157
+
158
+ describe("POST /api/tasks/{id}/cancel ownership", () => {
159
+ test("an agent that is neither lead nor creator gets 403 and the task keeps running", async () => {
160
+ const task = await inProgressTask();
161
+ const status = await post(
162
+ `/api/tasks/${task.id}/cancel`,
163
+ { as: "operator", agentId: otherId },
164
+ { reason: "not mine" },
165
+ );
166
+ expect(status).toBe(403);
167
+ expect((await getTaskById(task.id))?.status).toBe("in_progress");
168
+ });
169
+
170
+ test("the assignee alone cannot cancel, matching MCP cancel-task", async () => {
171
+ const task = await inProgressTask();
172
+ const status = await post(
173
+ `/api/tasks/${task.id}/cancel`,
174
+ { as: "operator", agentId: ownerId },
175
+ {},
176
+ );
177
+ expect(status).toBe(403);
178
+ expect((await getTaskById(task.id))?.status).toBe("in_progress");
179
+ });
180
+
181
+ test("the creator, a lead, the operator, and a user may cancel", async () => {
182
+ const callers: Caller[] = [
183
+ { as: "operator", agentId: creatorId },
184
+ { as: "session", agentId: leadId },
185
+ { as: "operator" },
186
+ { as: "user" },
187
+ ];
188
+ for (const caller of callers) {
189
+ const task = await inProgressTask();
190
+ const status = await post(`/api/tasks/${task.id}/cancel`, caller, { reason: "stop" });
191
+ expect(status, JSON.stringify(caller)).toBe(200);
192
+ expect((await getTaskById(task.id))?.status).toBe("cancelled");
193
+ }
194
+ });
195
+ });
@@ -1,11 +1,14 @@
1
1
  import { describe, expect, test } from "bun:test";
2
2
  import { Type } from "typebox";
3
3
  import { z } from "zod";
4
+ import { SummaryWithRatingsSchema } from "../../be/memory/raters/llm.js";
4
5
  import {
5
6
  completeStructured,
7
+ completeStructuredWithModel,
6
8
  defaultSpawnClaudeCli,
7
9
  } from "../../utils/internal-ai/complete-structured.js";
8
10
  import type { ResolvedCredential } from "../../utils/internal-ai/credentials.js";
11
+ import { summaryToolSchema } from "../../utils/internal-ai/summarize-session.js";
9
12
 
10
13
  const ResultZodSchema = z.object({
11
14
  summary: z.string(),
@@ -69,6 +72,39 @@ describe("completeStructured", () => {
69
72
  expect(result).toEqual({ summary: "ok", count: 7 });
70
73
  });
71
74
 
75
+ test("session summary with a blank referencesSource is accepted on the first call", async () => {
76
+ let invocations = 0;
77
+ const args = {
78
+ summary: "learned things",
79
+ ratings: [
80
+ { id: "m1", score: 0.9, reasoning: "useful", referencesSource: "linear:DES-1" },
81
+ { id: "m2", score: 0.1, reasoning: "not used", referencesSource: "" },
82
+ { id: "m3", score: 0.5, reasoning: "neutral", referencesSource: " " },
83
+ ],
84
+ };
85
+ const result = await completeStructured({
86
+ zodSchema: SummaryWithRatingsSchema,
87
+ toolSchema: summaryToolSchema,
88
+ toolName: "record_session_summary",
89
+ toolDescription: "Record the session summary.",
90
+ systemPrompt: "sys",
91
+ userPrompt: "user",
92
+ _credentialOverride: {
93
+ kind: "openrouter",
94
+ apiKey: "test",
95
+ modelDefault: "openrouter/deepseek/deepseek-v4.1-flash",
96
+ },
97
+ _complete: async () => {
98
+ invocations++;
99
+ return makeMsg([
100
+ { type: "toolCall", id: "call_1", name: "record_session_summary", arguments: args },
101
+ ]);
102
+ },
103
+ });
104
+ expect(invocations).toBe(1);
105
+ expect(result?.ratings).toHaveLength(3);
106
+ });
107
+
72
108
  for (const { label, text, expected } of [
73
109
  {
74
110
  label: "bare JSON",
@@ -493,3 +529,61 @@ describe("defaultSpawnClaudeCli", () => {
493
529
  }
494
530
  });
495
531
  });
532
+
533
+ describe("completeStructuredWithModel", () => {
534
+ const baseOpts = {
535
+ zodSchema: ResultZodSchema,
536
+ toolSchema: ResultToolSchema,
537
+ toolName: "record_result",
538
+ toolDescription: "Record the result.",
539
+ systemPrompt: "sys",
540
+ userPrompt: "user",
541
+ };
542
+
543
+ test("pi-ai path: reports the credential's resolved model next to the data", async () => {
544
+ const result = await completeStructuredWithModel({
545
+ ...baseOpts,
546
+ _credentialOverride: {
547
+ kind: "openrouter",
548
+ apiKey: "test",
549
+ modelDefault: "openrouter/deepseek/deepseek-v4.1-flash",
550
+ },
551
+ _complete: async () =>
552
+ makeMsg([
553
+ {
554
+ type: "toolCall",
555
+ id: "call_1",
556
+ name: "record_result",
557
+ arguments: { summary: "ok", count: 7 },
558
+ },
559
+ ]),
560
+ });
561
+ expect(result).toEqual({
562
+ data: { summary: "ok", count: 7 },
563
+ model: "openrouter/deepseek/deepseek-v4.1-flash",
564
+ });
565
+ });
566
+
567
+ test("assistant-text fallback also reports the model", async () => {
568
+ const result = await completeStructuredWithModel({
569
+ ...baseOpts,
570
+ _credentialOverride: {
571
+ kind: "openrouter",
572
+ apiKey: "test",
573
+ modelDefault: "openrouter/deepseek/deepseek-v4.1-flash",
574
+ },
575
+ _complete: async () => makeMsg([{ type: "text", text: '{"summary":"t","count":2}' }]),
576
+ });
577
+ expect(result?.model).toBe("openrouter/deepseek/deepseek-v4.1-flash");
578
+ expect(result?.data).toEqual({ summary: "t", count: 2 });
579
+ });
580
+
581
+ test("claude-cli path: reports the cli model alias", async () => {
582
+ const result = await completeStructuredWithModel({
583
+ ...baseOpts,
584
+ _credentialOverride: { kind: "claude-cli", modelDefault: "haiku" } as ResolvedCredential,
585
+ _spawnClaudeCli: async () => JSON.stringify({ summary: "cli", count: 1 }),
586
+ });
587
+ expect(result).toEqual({ data: { summary: "cli", count: 1 }, model: "haiku" });
588
+ });
589
+ });
@@ -1,5 +1,6 @@
1
1
  import { describe, expect, test } from "bun:test";
2
2
  import {
3
+ DEFAULT_MODEL,
3
4
  type ResolveCredentialOptions,
4
5
  resolveCredential,
5
6
  } from "../../utils/internal-ai/credentials.js";
@@ -34,6 +35,27 @@ describe("resolveCredential", () => {
34
35
  }
35
36
  });
36
37
 
38
+ test("modelDefault follows the pinned rater model, not DEFAULT_MODEL", async () => {
39
+ const saved = process.env.MEMORY_RATER_MODEL;
40
+ delete process.env.MEMORY_RATER_MODEL;
41
+ const before = { ...DEFAULT_MODEL };
42
+ try {
43
+ DEFAULT_MODEL.openrouter = "openrouter/some/bumped-model";
44
+ DEFAULT_MODEL.anthropic = "anthropic/bumped";
45
+ const viaOpenRouter = await resolveCredential(
46
+ makeOpts({ env: { OPENROUTER_API_KEY: "or-1" } }),
47
+ );
48
+ expect(viaOpenRouter?.modelDefault).toBe("openrouter/deepseek/deepseek-v4.1-flash");
49
+ const viaAnthropic = await resolveCredential(
50
+ makeOpts({ env: { ANTHROPIC_API_KEY: "example-sk-ant-1" } }),
51
+ );
52
+ expect(viaAnthropic?.modelDefault).toBe("anthropic/claude-haiku-4-5");
53
+ } finally {
54
+ Object.assign(DEFAULT_MODEL, before);
55
+ if (saved !== undefined) process.env.MEMORY_RATER_MODEL = saved;
56
+ }
57
+ });
58
+
37
59
  test("ANTHROPIC_API_KEY when no openrouter", async () => {
38
60
  const cred = await resolveCredential(
39
61
  makeOpts({ env: { ANTHROPIC_API_KEY: "example-sk-ant-1" } }),
@@ -1,6 +1,11 @@
1
- import { describe, expect, test } from "bun:test";
1
+ import { afterEach, describe, expect, test } from "bun:test";
2
2
  import { getBuiltinModel } from "@earendil-works/pi-ai/providers/all";
3
- import { DEFAULT_MODEL, parseModelStr } from "../../utils/internal-ai/models.js";
3
+ import {
4
+ DEFAULT_MODEL,
5
+ MEMORY_RATER_DEFAULT_MODEL,
6
+ parseModelStr,
7
+ resolveRaterModelString,
8
+ } from "../../utils/internal-ai/models.js";
4
9
 
5
10
  describe("internal-ai default models", () => {
6
11
  for (const [kind, modelString] of Object.entries(DEFAULT_MODEL)) {
@@ -18,3 +23,32 @@ describe("internal-ai default models", () => {
18
23
  });
19
24
  }
20
25
  });
26
+
27
+ describe("memory rater default models", () => {
28
+ const savedEnv = process.env.MEMORY_RATER_MODEL;
29
+ afterEach(() => {
30
+ if (savedEnv === undefined) delete process.env.MEMORY_RATER_MODEL;
31
+ else process.env.MEMORY_RATER_MODEL = savedEnv;
32
+ });
33
+
34
+ for (const [kind, modelString] of Object.entries(MEMORY_RATER_DEFAULT_MODEL)) {
35
+ if (kind === "claude-cli") continue;
36
+
37
+ test(`${kind} rater default resolves in the pinned pi-ai catalog`, () => {
38
+ const [provider, modelId] = parseModelStr(modelString);
39
+ const model = getBuiltinModel(
40
+ provider as Parameters<typeof getBuiltinModel>[0],
41
+ modelId as never,
42
+ );
43
+ expect(model).toBeDefined();
44
+ expect(model.id).toBe(modelId);
45
+ expect(model.provider).toBe(provider);
46
+ });
47
+ }
48
+
49
+ test("MEMORY_RATER_MODEL still overrides every kind", () => {
50
+ process.env.MEMORY_RATER_MODEL = "openrouter/anthropic/claude-sonnet-4-5";
51
+ expect(resolveRaterModelString("openrouter")).toBe("openrouter/anthropic/claude-sonnet-4-5");
52
+ expect(resolveRaterModelString("claude-cli")).toBe("openrouter/anthropic/claude-sonnet-4-5");
53
+ });
54
+ });
@@ -4,7 +4,7 @@ import { SummaryWithRatingsSchema } from "../../be/memory/raters/llm.js";
4
4
  import { summaryToolSchema } from "../../utils/internal-ai/summarize-session.js";
5
5
 
6
6
  /**
7
- * 10 valid + 10 invalid fixtures. Both validators (zod via `safeParse`,
7
+ * Valid + invalid fixtures. Both validators (zod via `safeParse`,
8
8
  * typebox via `Value.Check`) must agree on every fixture.
9
9
  *
10
10
  * Note: zod's `SummaryWithRatingsSchema` defaults `ratings` to `[]` when
@@ -54,6 +54,24 @@ const VALID_CASES: unknown[] = [
54
54
  summary: "max-reason",
55
55
  ratings: [{ id: "m1", score: 0.5, reasoning: "x".repeat(500) }],
56
56
  },
57
+ // blank referencesSource: models emit "" for "no source". One blank value
58
+ // must not reject the whole summary; buildRatingsFromLlm drops the edge.
59
+ {
60
+ summary: "empty-ref",
61
+ ratings: [{ id: "m1", score: 0.5, reasoning: "ok", referencesSource: "" }],
62
+ },
63
+ {
64
+ summary: "whitespace-ref",
65
+ ratings: [{ id: "m1", score: 0.5, reasoning: "ok", referencesSource: " " }],
66
+ },
67
+ // one blank among valid ratings.
68
+ {
69
+ summary: "mixed-refs",
70
+ ratings: [
71
+ { id: "m1", score: 0.9, reasoning: "ok", referencesSource: "linear:DES-1" },
72
+ { id: "m2", score: 0.1, reasoning: "ok", referencesSource: "" },
73
+ ],
74
+ },
57
75
  ];
58
76
 
59
77
  const INVALID_CASES: unknown[] = [
@@ -74,6 +92,12 @@ const INVALID_CASES: unknown[] = [
74
92
  { summary: "ok", ratings: [{ id: "m1", score: -0.1, reasoning: "x" }] },
75
93
  // rating with non-string id.
76
94
  { summary: "ok", ratings: [{ id: 7, score: 0.5, reasoning: "x" }] },
95
+ // referencesSource still bounded and typed: only blank became valid.
96
+ { summary: "ok", ratings: [{ id: "m1", score: 0.5, reasoning: "x", referencesSource: 7 }] },
97
+ {
98
+ summary: "ok",
99
+ ratings: [{ id: "m1", score: 0.5, reasoning: "x", referencesSource: "x".repeat(513) }],
100
+ },
77
101
  ];
78
102
 
79
103
  describe("schema-parity: SummaryWithRatingsSchema (zod) vs summaryToolSchema (typebox)", () => {
@@ -11,7 +11,7 @@ Tool[Edit]: input={"file":"/tmp/x","old":"a","new":"b"} output="ok"
11
11
  Assistant: Done.`.padEnd(200, "x");
12
12
 
13
13
  describe("summarizeSession", () => {
14
- test("pass-through: injected _completeStructured result is returned verbatim", async () => {
14
+ test("pass-through: injected _completeStructured data is returned with the resolved model", async () => {
15
15
  const fake: z.infer<typeof SummaryWithRatingsSchema> = {
16
16
  summary: "Learned: X uses Y",
17
17
  ratings: [{ id: "mem-1", score: 0.9, reasoning: "very useful" }],
@@ -23,9 +23,25 @@ describe("summarizeSession", () => {
23
23
  taskContext: { sourceTaskId: "task-1", agentId: "agent-1" },
24
24
  apiUrl: "http://localhost:3013",
25
25
  apiKey: "k",
26
- _completeStructured: (async () => fake) as any,
26
+ _completeStructured: (async () => ({
27
+ data: fake,
28
+ model: "openrouter/deepseek/deepseek-v4.1-flash",
29
+ })) as any,
27
30
  });
28
- expect(result).toEqual(fake);
31
+ expect(result).toEqual({ ...fake, model: "openrouter/deepseek/deepseek-v4.1-flash" });
32
+ });
33
+
34
+ test("returns null when _completeStructured resolves no result", async () => {
35
+ const result = await summarizeSession({
36
+ harness: "pi",
37
+ transcript: LONG_TRANSCRIPT,
38
+ retrievals: [],
39
+ taskContext: { sourceTaskId: "task-1", agentId: "agent-1" },
40
+ apiUrl: "http://localhost:3013",
41
+ apiKey: "k",
42
+ _completeStructured: (async () => null) as any,
43
+ });
44
+ expect(result).toBeNull();
29
45
  });
30
46
 
31
47
  test("retrievals are injected into the userPrompt via buildSummaryWithRatingsPrompt", async () => {
@@ -39,7 +55,7 @@ describe("summarizeSession", () => {
39
55
  apiKey: "k",
40
56
  _completeStructured: (async (opts: { userPrompt: string }) => {
41
57
  capturedUserPrompt = opts.userPrompt;
42
- return { summary: "x", ratings: [] };
58
+ return { data: { summary: "x", ratings: [] }, model: "m" };
43
59
  }) as any,
44
60
  });
45
61
  expect(capturedUserPrompt).toContain("mem-abc");
@@ -62,7 +78,7 @@ describe("summarizeSession", () => {
62
78
  apiKey: "k",
63
79
  _completeStructured: (async (opts: { userPrompt: string }) => {
64
80
  capturedUserPrompt = opts.userPrompt;
65
- return { summary: "x", ratings: [] };
81
+ return { data: { summary: "x", ratings: [] }, model: "m" };
66
82
  }) as any,
67
83
  });
68
84
  expect(capturedUserPrompt).toContain("Task: do the thing");
@@ -79,7 +95,7 @@ describe("summarizeSession", () => {
79
95
  apiKey: "k",
80
96
  _completeStructured: (async () => {
81
97
  invocations++;
82
- return { summary: "x", ratings: [] };
98
+ return { data: { summary: "x", ratings: [] }, model: "m" };
83
99
  }) as any,
84
100
  });
85
101
  expect(result).toBeNull();
@@ -97,7 +113,7 @@ describe("summarizeSession", () => {
97
113
  apiKey: "k",
98
114
  _completeStructured: (async (opts: { callerTag?: string }) => {
99
115
  capturedTag = opts.callerTag ?? "";
100
- return { summary: "x", ratings: [] };
116
+ return { data: { summary: "x", ratings: [] }, model: "m" };
101
117
  }) as any,
102
118
  });
103
119
  expect(capturedTag).toBe("session-summary:pi");