@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,27 @@
1
+ // Azure DevOps Integration
2
+ export {
3
+ likeAzureDevOpsComment,
4
+ postAzureDevOpsPullRequestComment,
5
+ resetAzureDevOpsBotIdCache,
6
+ resolveAzureDevOpsBotId,
7
+ } from "./api";
8
+ export {
9
+ AZURE_DEVOPS_BOT_NAME,
10
+ getAzureDevOpsOrgUrl,
11
+ getAzureDevOpsToken,
12
+ initAzureDevOps,
13
+ isAzureDevOpsEnabled,
14
+ resetAzureDevOps,
15
+ verifyAzureDevOpsWebhook,
16
+ } from "./auth";
17
+ export {
18
+ commentedPayloadOf,
19
+ createdPullRequestOf,
20
+ handlePullRequestCommented,
21
+ handlePullRequestCreated,
22
+ } from "./handlers";
23
+ export type {
24
+ AzureDevOpsWebhookEvent,
25
+ PullRequestCommentedEvent,
26
+ PullRequestCreatedEvent,
27
+ } from "./types";
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Azure DevOps event prompt template definitions.
3
+ *
4
+ * Each template is registered at module load time via registerTemplate().
5
+ * Handlers import this module for the side-effect of registration.
6
+ *
7
+ * Template text uses {{variable}} syntax. The resolver interpolates variables
8
+ * and expands {{@template[id]}} references before returning the final text.
9
+ */
10
+
11
+ import { registerTemplate } from "../prompts/registry";
12
+
13
+ // ============================================================================
14
+ // Common building blocks (Azure DevOps-specific)
15
+ // ============================================================================
16
+
17
+ registerTemplate({
18
+ eventType: "common.delegation_instruction.azure_devops",
19
+ header: "",
20
+ defaultBody:
21
+ "\n\n**Delegation instruction:** As the lead agent, analyze this and decide whether to handle it yourself or delegate to a worker agent. Use `send-task` to delegate with clear instructions.",
22
+ variables: [],
23
+ category: "common",
24
+ });
25
+
26
+ // `az repos` has no comment command, so replies go through `az devops invoke`.
27
+ registerTemplate({
28
+ eventType: "common.command_suggestions.azure_devops_pr",
29
+ header: "",
30
+ defaultBody:
31
+ 'Workers have the `az` CLI with the azure-devops extension, authenticated with the swarm PAT. Inspect the PR with `az repos pr show --id {{pr_id}}`. To reply on the PR, write `{"comments":[{"content":"<reply>"}],"status":1}` to a JSON file and run `az devops invoke --area git --resource pullRequestThreads --route-parameters project={{project}} repositoryId={{repository}} pullRequestId={{pr_id}} --http-method POST --in-file <file> --api-version 7.1`.',
32
+ variables: [
33
+ { name: "pr_id", description: "Pull request ID" },
34
+ { name: "project", description: "Azure DevOps project name" },
35
+ { name: "repository", description: "Azure Repos repository name" },
36
+ ],
37
+ category: "common",
38
+ });
39
+
40
+ const SHARED_VARIABLES = [
41
+ { name: "pr_id", description: "Pull request ID" },
42
+ { name: "pr_title", description: "Pull request title" },
43
+ {
44
+ name: "repo",
45
+ description: "Repository clone URL (https://dev.azure.com/<org>/<project>/_git/<repo>)",
46
+ },
47
+ { name: "project", description: "Azure DevOps project name" },
48
+ { name: "repository", description: "Azure Repos repository name" },
49
+ { name: "pr_url", description: "Pull request web URL" },
50
+ ];
51
+
52
+ // ============================================================================
53
+ // Pull request events
54
+ // ============================================================================
55
+
56
+ registerTemplate({
57
+ eventType: "azure-devops.pull_request.opened",
58
+ header: "[Azure DevOps PR #{{pr_id}}] {{pr_title}}",
59
+ defaultBody: `Repo: {{repo}}
60
+ Author: {{username}}
61
+ Branch: {{source_branch}} → {{target_branch}}
62
+ URL: {{pr_url}}
63
+
64
+ {{context_section}}{{@template[common.command_suggestions.azure_devops_pr]}}{{@template[common.delegation_instruction.azure_devops]}}`,
65
+ variables: [
66
+ ...SHARED_VARIABLES,
67
+ {
68
+ name: "username",
69
+ description:
70
+ "Author identity — resolved canonical name (e.g. 'Luis (azure-devops:luis@example.com)') or the UNKNOWN sentinel",
71
+ },
72
+ { name: "source_branch", description: "Source branch name" },
73
+ { name: "target_branch", description: "Target branch name" },
74
+ { name: "context_section", description: "Context section with description or empty string" },
75
+ ],
76
+ category: "event",
77
+ });
78
+
79
+ // ============================================================================
80
+ // Pull request comment events
81
+ // ============================================================================
82
+
83
+ registerTemplate({
84
+ eventType: "azure-devops.comment.mentioned",
85
+ header: "[Azure DevOps Comment on PR #{{pr_id}}] {{username}} mentioned bot",
86
+ defaultBody: `Repo: {{repo}}
87
+ PR: {{pr_title}}
88
+ URL: {{pr_url}}
89
+
90
+ Comment:
91
+ {{context}}{{existing_task_note}}
92
+
93
+ {{@template[common.command_suggestions.azure_devops_pr]}}{{@template[common.delegation_instruction.azure_devops]}}`,
94
+ variables: [
95
+ ...SHARED_VARIABLES,
96
+ {
97
+ name: "username",
98
+ description:
99
+ "Comment author identity — resolved canonical name (e.g. 'Luis (azure-devops:luis@example.com)') or the UNKNOWN sentinel",
100
+ },
101
+ { name: "context", description: "Extracted mention context from comment" },
102
+ {
103
+ name: "existing_task_note",
104
+ description: "Note about existing active task, or empty string",
105
+ },
106
+ ],
107
+ category: "event",
108
+ });
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Azure DevOps service-hook payload types.
3
+ *
4
+ * Based on https://learn.microsoft.com/azure/devops/service-hooks/events
5
+ * (resource version 1.0 for pull requests, 2.0 for pull request comments).
6
+ * Only the fields we actually use are typed.
7
+ */
8
+
9
+ export interface AzureDevOpsIdentityRef {
10
+ id: string; // identity GUID; a picker @mention stores it as `@<GUID>`
11
+ displayName: string;
12
+ uniqueName?: string; // usually the sign-in email
13
+ url?: string;
14
+ imageUrl?: string;
15
+ }
16
+
17
+ export interface AzureDevOpsProjectRef {
18
+ id: string;
19
+ name: string;
20
+ url?: string;
21
+ }
22
+
23
+ export interface AzureDevOpsRepository {
24
+ id: string;
25
+ name: string;
26
+ url: string; // REST URL of the repository
27
+ project: AzureDevOpsProjectRef;
28
+ defaultBranch?: string;
29
+ remoteUrl: string; // clone URL, may carry a `user@` prefix
30
+ }
31
+
32
+ export interface AzureDevOpsPullRequest {
33
+ repository: AzureDevOpsRepository;
34
+ pullRequestId: number;
35
+ status: string; // "active" | "abandoned" | "completed"
36
+ createdBy: AzureDevOpsIdentityRef;
37
+ title: string;
38
+ description?: string | null;
39
+ sourceRefName: string; // e.g. "refs/heads/feature"
40
+ targetRefName: string;
41
+ url: string; // REST URL of the pull request
42
+ _links?: { web?: { href: string } };
43
+ }
44
+
45
+ export interface AzureDevOpsComment {
46
+ id: number;
47
+ parentCommentId: number;
48
+ author: AzureDevOpsIdentityRef;
49
+ content: string;
50
+ publishedDate: string;
51
+ lastContentUpdatedDate?: string;
52
+ commentType: string; // "text" | "system" | "codeChange"
53
+ _links?: {
54
+ self?: { href: string };
55
+ threads?: { href: string };
56
+ };
57
+ }
58
+
59
+ interface ResourceContainer {
60
+ id: string;
61
+ baseUrl?: string; // e.g. "https://dev.azure.com/fabrikam/"
62
+ }
63
+
64
+ interface ServiceHookEvent<TType extends string, TResource> {
65
+ id: string;
66
+ eventType: TType;
67
+ publisherId: string;
68
+ resource: TResource;
69
+ resourceVersion?: string;
70
+ resourceContainers?: {
71
+ collection?: ResourceContainer;
72
+ account?: ResourceContainer;
73
+ project?: ResourceContainer;
74
+ };
75
+ createdDate?: string;
76
+ }
77
+
78
+ /** `git.pullrequest.created` */
79
+ export type PullRequestCreatedEvent = ServiceHookEvent<
80
+ "git.pullrequest.created",
81
+ AzureDevOpsPullRequest
82
+ >;
83
+
84
+ /** `ms.vss-code.git-pullrequest-comment-event` */
85
+ export type PullRequestCommentedEvent = ServiceHookEvent<
86
+ "ms.vss-code.git-pullrequest-comment-event",
87
+ { comment: AzureDevOpsComment; pullRequest: AzureDevOpsPullRequest }
88
+ >;
89
+
90
+ export type AzureDevOpsWebhookEvent = PullRequestCreatedEvent | PullRequestCommentedEvent;
@@ -0,0 +1,70 @@
1
+ import { deriveProviderFromKeyType } from "../../utils/credentials";
2
+ import type { ModelFamily } from "../../utils/model-rate-limit-windows";
3
+ import { planAllowsModelFamily } from "../../utils/subscription-plans";
4
+ import { getDbClient } from "./runtime";
5
+
6
+ /**
7
+ * Records a `credits_required` rejection: the key's seat cannot run `model`.
8
+ * Sets `lastSeatMismatchAt` / `lastSeatMismatchModel` on every scope row of
9
+ * the key. A Claude key whose plan the operator did not pick gets
10
+ * `claude_team_standard` / `detected`, the only plan that excludes Fable,
11
+ * but only when that plan excludes `model`: an Opus or Sonnet rejection is
12
+ * no evidence of a standard seat. A `manual` plan is never changed; a
13
+ * contradiction is logged instead. The key `status` is not touched.
14
+ */
15
+ export async function recordKeySeatMismatch(
16
+ keyType: string,
17
+ keySuffix: string,
18
+ keyIndex: number,
19
+ model: ModelFamily,
20
+ scope = "global",
21
+ scopeId: string | null = null,
22
+ ): Promise<{ planChanged: boolean }> {
23
+ const now = new Date().toISOString();
24
+ const effectiveScopeId = scopeId ?? "";
25
+ const provider = deriveProviderFromKeyType(keyType);
26
+ return getDbClient().transaction(async (tx) => {
27
+ await tx.run(
28
+ `INSERT INTO api_key_status (keyType, keySuffix, keyIndex, scope, scopeId, provider, updatedAt)
29
+ VALUES (?, ?, ?, ?, ?, ?, ?)
30
+ ON CONFLICT(keyType, keySuffix, scope, scopeId)
31
+ DO UPDATE SET
32
+ keyIndex = excluded.keyIndex,
33
+ provider = excluded.provider,
34
+ updatedAt = excluded.updatedAt`,
35
+ [keyType, keySuffix, keyIndex, scope, effectiveScopeId, provider, now],
36
+ );
37
+ await tx.run(
38
+ `UPDATE api_key_status SET lastSeatMismatchAt = ?, lastSeatMismatchModel = ?
39
+ WHERE keyType = ? AND keySuffix = ?`,
40
+ [now, model, keyType, keySuffix],
41
+ );
42
+
43
+ let planChanged = false;
44
+ if (
45
+ keyType === "CLAUDE_CODE_OAUTH_TOKEN" &&
46
+ !planAllowsModelFamily("claude_team_standard", model)
47
+ ) {
48
+ const result = await tx.run(
49
+ `UPDATE api_key_status SET plan = 'claude_team_standard', planSource = 'detected'
50
+ WHERE keyType = ? AND keySuffix = ? AND COALESCE(planSource, '') != 'manual'`,
51
+ [keyType, keySuffix],
52
+ );
53
+ planChanged = result.changes > 0;
54
+ }
55
+
56
+ if (!planChanged) {
57
+ const stored = await tx.get<{ plan: string | null }>(
58
+ `SELECT plan FROM api_key_status
59
+ WHERE keyType = ? AND keySuffix = ? AND scope = ? AND scopeId = ?`,
60
+ [keyType, keySuffix, scope, effectiveScopeId],
61
+ );
62
+ if (planAllowsModelFamily(stored?.plan, model)) {
63
+ console.warn(
64
+ `[api-keys] seat mismatch: key ...${keySuffix} (${keyType}) has plan ${stored?.plan ?? "none"} but cannot run ${model}`,
65
+ );
66
+ }
67
+ }
68
+ return { planChanged };
69
+ });
70
+ }
@@ -288,7 +288,7 @@ export function rowToAgentTask(row: AgentTaskRow): AgentTask {
288
288
  slackReplySent: !!row.slackReplySent,
289
289
  slackProgressMessageTs: row.slackProgressMessageTs ?? undefined,
290
290
  slackTreeRootMessageTs: row.slackTreeRootMessageTs ?? undefined,
291
- vcsProvider: (row.vcsProvider as "github" | "gitlab" | null) ?? undefined,
291
+ vcsProvider: (row.vcsProvider as "github" | "gitlab" | "azure-devops" | null) ?? undefined,
292
292
  vcsRepo: row.vcsRepo ?? undefined,
293
293
  vcsEventType: row.vcsEventType ?? undefined,
294
294
  vcsNumber: row.vcsNumber ?? undefined,
@@ -317,6 +317,22 @@ export async function updateTaskClaudeSessionId(
317
317
  return row ? rowToAgentTask(row) : null;
318
318
  }
319
319
 
320
+ /**
321
+ * Records the harness that ran (or tried to run) a task when no session ever
322
+ * reported it. `provider` is otherwise written only by the session-init path,
323
+ * so a task whose harness failed to spawn kept `provider` NULL and looked like
324
+ * a task that never started. Never overwrites a provider already recorded.
325
+ */
326
+ export async function recordTaskProviderIfUnset(
327
+ taskId: string,
328
+ provider: ProviderName,
329
+ ): Promise<void> {
330
+ await getDbClient().run("UPDATE agent_tasks SET provider = ? WHERE id = ? AND provider IS NULL", [
331
+ provider,
332
+ taskId,
333
+ ]);
334
+ }
335
+
320
336
  /**
321
337
  * Sets or clears a task's display title (session rename). Trims the input and
322
338
  * normalizes an empty string to NULL (clear). Deliberately does NOT touch
@@ -68,6 +68,9 @@ export interface DbClient extends DbExecutor {
68
68
  * really did occupy the driver and block the event loop, up to
69
69
  * `attemptSpinMs` per BUSY attempt. It excludes all lock waiting and all
70
70
  * backoff sleeping.
71
+ *
72
+ * `changes` counts only the rows the statement itself changed. Unlike
73
+ * `run`, it excludes rows touched by foreign-key actions.
71
74
  */
72
75
  runTimed(sql: string, params?: DbParam[]): Promise<{ changes: number; executionMs: number }>;
73
76
  /**
@@ -278,8 +281,14 @@ class BunSqliteClient implements DbClient {
278
281
  const result = await this.execute(
279
282
  sql,
280
283
  (db) => {
281
- const run = db.query(sql).run(...params);
282
- return { changes: run.changes };
284
+ db.query(sql).run(...params);
285
+ // bun:sqlite's `run().changes` also counts rows touched by foreign-key
286
+ // actions (an ON DELETE SET NULL child update counts as a change), so
287
+ // a DELETE on a self-referencing table reports up to twice the rows it
288
+ // removed. SQLite's changes() counts only the statement's own rows.
289
+ // Same connection, still inside the lock: nothing can run in between.
290
+ const own = db.query("SELECT changes() AS n").get() as { n: number } | null;
291
+ return { changes: own?.n ?? 0 };
283
292
  },
284
293
  (ms) => {
285
294
  executionMs += ms;
@@ -20,33 +20,63 @@ const YIELD_MS = 25;
20
20
  * This is deliberately a closed list. Neither table nor column names may come
21
21
  * from operator configuration: a retention policy must never target a table
22
22
  * that code review has not explicitly approved as safe to delete from.
23
+ *
24
+ * Two policies exist:
25
+ * - `age`: the env value is a number of days; rows whose `timeColumn` is older
26
+ * than that horizon are deleted.
27
+ * - `keepLatest`: the env value is a count; within each `partitionBy` group,
28
+ * only the newest `rankBy` rows are kept. The newest row of a group is
29
+ * never deleted, because the smallest accepted count is 1.
23
30
  */
24
31
  export const DB_RETENTION_TABLES = [
25
32
  {
26
33
  table: "session_logs",
34
+ policy: "age",
27
35
  timeColumn: "createdAt",
28
36
  envKey: "SESSION_LOG_RETENTION_DAYS",
29
37
  metricsKey: "sessionLogs",
30
38
  },
31
39
  {
32
40
  table: "agent_log",
41
+ policy: "age",
33
42
  timeColumn: "createdAt",
34
43
  envKey: "AGENT_LOG_RETENTION_DAYS",
35
44
  metricsKey: "agentLog",
36
45
  },
37
46
  {
38
47
  table: "events",
48
+ policy: "age",
39
49
  timeColumn: "createdAt",
40
50
  envKey: "EVENTS_RETENTION_DAYS",
41
51
  metricsKey: "events",
42
52
  },
43
- ] as const satisfies ReadonlyArray<{
44
- table: string;
45
- timeColumn: string;
46
- envKey: string;
47
- metricsKey: string;
48
- initialBatchSize?: number;
49
- }>;
53
+ {
54
+ // Every profile self-edit stores a full copy of the field, so a large,
55
+ // frequently edited field grows this table without bound. Ranking runs on
56
+ // the covering idx_cv_agent_field index, so neither the COUNT nor the
57
+ // candidate scan reads `content`. Rows are large (a version can be
58
+ // hundreds of KB), so batches start and stay small.
59
+ table: "context_versions",
60
+ policy: "keepLatest",
61
+ partitionBy: "agentId, field",
62
+ rankBy: "version",
63
+ envKey: "CONTEXT_VERSIONS_KEEP_LATEST",
64
+ metricsKey: "contextVersions",
65
+ initialBatchSize: 50,
66
+ maxBatchSize: 100,
67
+ },
68
+ ] as const satisfies ReadonlyArray<
69
+ {
70
+ table: string;
71
+ envKey: string;
72
+ metricsKey: string;
73
+ initialBatchSize?: number;
74
+ maxBatchSize?: number;
75
+ } & (
76
+ | { policy: "age"; timeColumn: string }
77
+ | { policy: "keepLatest"; partitionBy: string; rankBy: string }
78
+ )
79
+ >;
50
80
 
51
81
  type RetentionTable = (typeof DB_RETENTION_TABLES)[number];
52
82
  type RetentionMetricsKey = RetentionTable["metricsKey"];
@@ -261,27 +291,65 @@ function yieldTick(): Promise<void> {
261
291
  return new Promise((resolve) => setTimeout(resolve, YIELD_MS));
262
292
  }
263
293
 
264
- function retentionDays(table: RetentionTable): number | null {
294
+ /** Days for an `age` table, kept-version count for a `keepLatest` table; null when disabled. */
295
+ function retentionValue(table: RetentionTable): number | null {
265
296
  return readPositiveIntEnv(table.envKey);
266
297
  }
267
298
 
299
+ function initialBatchSize(table: RetentionTable): number {
300
+ return "initialBatchSize" in table ? table.initialBatchSize : DEFAULT_BATCH_SIZE;
301
+ }
302
+
303
+ function maxBatchSize(table: RetentionTable): number {
304
+ return "maxBatchSize" in table ? table.maxBatchSize : MAX_BATCH_SIZE;
305
+ }
306
+
307
+ /** The backlog COUNT and the batched DELETE for one table under its policy. */
308
+ type SweepStatements = {
309
+ backlogSql: string;
310
+ /** Takes `params` followed by the batch LIMIT. */
311
+ deleteSql: string;
312
+ params: Array<string | number>;
313
+ };
314
+
315
+ export function sweepStatements(table: RetentionTable, value: number, now: Date): SweepStatements {
316
+ if (table.policy === "keepLatest") {
317
+ // ROW_NUMBER over the (partition, rank DESC) index: rank 1 is the newest
318
+ // row of its group. The DELETE removes the deepest history first, so an
319
+ // interrupted sweep leaves each group's surviving versions contiguous.
320
+ const ranked = `SELECT rowid AS rid, ROW_NUMBER() OVER (PARTITION BY ${table.partitionBy} ORDER BY ${table.rankBy} DESC) AS rn FROM ${table.table}`;
321
+ return {
322
+ backlogSql: `SELECT COUNT(*) AS n FROM (${ranked}) WHERE rn > ?`,
323
+ deleteSql: `DELETE FROM ${table.table} WHERE rowid IN (
324
+ SELECT rid FROM (${ranked}) WHERE rn > ? ORDER BY rn DESC LIMIT ?
325
+ )`,
326
+ params: [value],
327
+ };
328
+ }
329
+ const cutoff = new Date(now.getTime() - value * DAY_MS).toISOString();
330
+ return {
331
+ backlogSql: `SELECT COUNT(*) AS n FROM ${table.table} WHERE ${table.timeColumn} < ?`,
332
+ deleteSql: `DELETE FROM ${table.table} WHERE rowid IN (
333
+ SELECT rowid FROM ${table.table} WHERE ${table.timeColumn} < ? ORDER BY ${table.timeColumn} LIMIT ?
334
+ )`,
335
+ params: [cutoff],
336
+ };
337
+ }
338
+
268
339
  function dryRunEnabled(env: NodeJS.ProcessEnv = process.env): boolean {
269
340
  const raw = env.DB_RETENTION_DRY_RUN;
270
341
  if (raw === undefined || raw.trim() === "") return false;
271
342
  return isEnvFlagEnabled("DB_RETENTION_DRY_RUN", true, env);
272
343
  }
273
344
 
274
- async function indexedBacklogCount(table: RetentionTable, cutoff: string): Promise<number> {
275
- const row = await getDbClient().get<{ n: number }>(
276
- `SELECT COUNT(*) AS n FROM ${table.table} WHERE ${table.timeColumn} < ?`,
277
- [cutoff],
278
- );
345
+ async function indexedBacklogCount(statements: SweepStatements): Promise<number> {
346
+ const row = await getDbClient().get<{ n: number }>(statements.backlogSql, statements.params);
279
347
  return row?.n ?? 0;
280
348
  }
281
349
 
282
350
  async function sweepTable(
283
351
  table: RetentionTable,
284
- cutoff: string,
352
+ statements: SweepStatements,
285
353
  dryRun: boolean,
286
354
  deadline: number,
287
355
  signal: AbortSignal,
@@ -290,33 +358,29 @@ async function sweepTable(
290
358
  const client = getDbClient();
291
359
 
292
360
  if (dryRun) {
293
- const backlogRemaining = await indexedBacklogCount(table, cutoff);
361
+ const backlogRemaining = await indexedBacklogCount(statements);
294
362
  return {
295
363
  rowsDeleted: 0,
296
364
  batches: 0,
297
365
  backlogRemaining,
298
366
  drained: backlogRemaining === 0,
299
367
  slowestStatementMs: 0,
300
- batchSize: batchSizeByTable[table.metricsKey] ?? DEFAULT_BATCH_SIZE,
368
+ batchSize: batchSizeByTable[table.metricsKey] ?? initialBatchSize(table),
301
369
  };
302
370
  }
303
371
 
304
372
  let rowsDeleted = 0;
305
373
  let batches = 0;
306
374
  let slowestStatementMs = 0;
307
- let size = batchSizeByTable[table.metricsKey] ?? DEFAULT_BATCH_SIZE;
375
+ let size = batchSizeByTable[table.metricsKey] ?? initialBatchSize(table);
376
+ const ceiling = maxBatchSize(table);
308
377
 
309
378
  try {
310
379
  while (!signal.aborted && Date.now() < deadline) {
311
380
  const limitUsed = size;
312
381
  // Never wrap multiple batches in a transaction: one autocommit DELETE per
313
382
  // batch, so the write lock and any event-loop stall end with each statement.
314
- const result = await client.runTimed(
315
- `DELETE FROM ${table.table} WHERE rowid IN (
316
- SELECT rowid FROM ${table.table} WHERE ${table.timeColumn} < ? ORDER BY ${table.timeColumn} LIMIT ?
317
- )`,
318
- [cutoff, limitUsed],
319
- );
383
+ const result = await client.runTimed(statements.deleteSql, [...statements.params, limitUsed]);
320
384
  // Execution time only. Wall time around the call also covers waiting for
321
385
  // the client's FIFO lock and its BUSY backoff sleeps, neither of which
322
386
  // this statement spent in the driver: charging those to the stall metric
@@ -331,7 +395,7 @@ async function sweepTable(
331
395
  if (elapsed > maxStatementMs) {
332
396
  size = Math.max(MIN_BATCH_SIZE, Math.floor(size / 2));
333
397
  } else if (elapsed < maxStatementMs / 5) {
334
- size = Math.min(MAX_BATCH_SIZE, size * 2);
398
+ size = Math.min(ceiling, size * 2);
335
399
  }
336
400
 
337
401
  // A DELETE that changed fewer rows than the LIMIT it used means the
@@ -351,7 +415,7 @@ async function sweepTable(
351
415
  // backlogRemaining > 0 drops the table from `undrained`, leaves catch-up
352
416
  // unarmed, and publishes that contradiction on /api/metrics until the next
353
417
  // hourly tick.
354
- const backlogRemaining = await indexedBacklogCount(table, cutoff);
418
+ const backlogRemaining = await indexedBacklogCount(statements);
355
419
  return {
356
420
  rowsDeleted,
357
421
  batches,
@@ -409,7 +473,7 @@ export function runDbRetentionTick(options: DbRetentionTickOptions = {}): Promis
409
473
  const sweepAllTables = async (rawTickSpan: SwarmSpan): Promise<void> => {
410
474
  const tickSpan = bestEffortSpan(rawTickSpan);
411
475
  const tickDeadline = tickStartedAt + budget;
412
- const enabled = DB_RETENTION_TABLES.filter((table) => retentionDays(table) !== null);
476
+ const enabled = DB_RETENTION_TABLES.filter((table) => retentionValue(table) !== null);
413
477
  if (enabled.length === 0) {
414
478
  lastSweepOrder = [];
415
479
  tickSpan.setAttributes({
@@ -431,18 +495,17 @@ export function runDbRetentionTick(options: DbRetentionTickOptions = {}): Promis
431
495
  const undrained = new Set<RetentionMetricsKey>();
432
496
 
433
497
  const sweepOne = async (table: RetentionTable, tableDeadline: number): Promise<void> => {
434
- const days = retentionDays(table);
435
- if (days === null) return;
498
+ const value = retentionValue(table);
499
+ if (value === null) return;
436
500
  const startedAt = Date.now();
437
501
  const previous = retentionStats[table.metricsKey];
438
502
  const tableSpan = bestEffortStartSpan("db.retention.table", {
439
503
  "agentswarm.retention.table": table.table,
440
504
  });
441
505
  try {
442
- const cutoff = new Date(cutoffBase.getTime() - days * DAY_MS).toISOString();
443
506
  const result = await sweepTable(
444
507
  table,
445
- cutoff,
508
+ sweepStatements(table, value, cutoffBase),
446
509
  dryRun,
447
510
  tableDeadline,
448
511
  abortController.signal,
@@ -529,7 +592,7 @@ export function runDbRetentionTick(options: DbRetentionTickOptions = {}): Promis
529
592
  partial?.progress.batchSize ??
530
593
  previous?.batchSize ??
531
594
  batchSizeByTable[table.metricsKey] ??
532
- DEFAULT_BATCH_SIZE;
595
+ initialBatchSize(table);
533
596
  // Committed rows advance the running total and justify the vacuum,
534
597
  // exactly as they would have on the success path.
535
598
  const cumulative = (cumulativeRowsDeleted[table.metricsKey] ?? 0) + rowsDeleted;
@@ -670,7 +733,7 @@ export function _getLastSweepOrderForTests(): RetentionMetricsKey[] {
670
733
  export async function startDbRetention(intervalMs = RETENTION_INTERVAL_MS): Promise<void> {
671
734
  if (retentionTimer) return;
672
735
  const configured = DB_RETENTION_TABLES.map(
673
- (table) => `${table.table}=${retentionDays(table) ?? "disabled"}`,
736
+ (table) => `${table.table}=${retentionValue(table) ?? "disabled"}`,
674
737
  ).join(", ");
675
738
  console.log(`[db-retention] starting (${configured}, dryRun=${dryRunEnabled()})`);
676
739
  retentionTimer = scheduleContextFree(() =>