@wardby/cli 0.2.1 → 0.4.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 (555) hide show
  1. package/.env.example +53 -4
  2. package/README.md +52 -13
  3. package/dist/claude-coding-worker/driver.d.ts +6 -1
  4. package/dist/claude-coding-worker/driver.js +27 -1
  5. package/dist/claude-coding-worker/main.js +2 -0
  6. package/dist/claude-coding-worker/tool-socket.d.ts +9 -0
  7. package/dist/claude-coding-worker/tool-socket.js +26 -0
  8. package/dist/cli-help.d.ts +1 -1
  9. package/dist/cli-help.js +14 -4
  10. package/dist/cli.d.ts +1 -1
  11. package/dist/cli.js +284 -36
  12. package/dist/coding/base-commit.d.ts +6 -0
  13. package/dist/coding/base-commit.js +12 -0
  14. package/dist/coding/collect-exclude.d.ts +25 -0
  15. package/dist/coding/collect-exclude.js +76 -0
  16. package/dist/coding/profile.d.ts +67 -22
  17. package/dist/coding/profile.js +58 -25
  18. package/dist/coding/protected-path-wording.d.ts +24 -0
  19. package/dist/coding/protected-path-wording.js +55 -0
  20. package/dist/coding/protected-paths.d.ts +31 -0
  21. package/dist/coding/protected-paths.js +48 -0
  22. package/dist/coding/protocol.d.ts +91 -4
  23. package/dist/coding/protocol.js +123 -12
  24. package/dist/coding/provider.d.ts +6 -1
  25. package/dist/coding/provider.js +16 -9
  26. package/dist/coding/registry/adapters.d.ts +2 -0
  27. package/dist/coding/registry/adapters.js +9 -0
  28. package/dist/coding/registry/allowlist.d.ts +9 -0
  29. package/dist/coding/registry/allowlist.js +28 -0
  30. package/dist/coding/registry/json-scan.d.ts +55 -0
  31. package/dist/coding/registry/json-scan.js +191 -0
  32. package/dist/coding/registry/lockfiles.d.ts +6 -0
  33. package/dist/coding/registry/lockfiles.js +89 -0
  34. package/dist/coding/registry/npm-lockfile.d.ts +9 -0
  35. package/dist/coding/registry/npm-lockfile.js +73 -0
  36. package/dist/coding/registry/npm-plan.d.ts +29 -0
  37. package/dist/coding/registry/npm-plan.js +289 -0
  38. package/dist/coding/registry/npm.d.ts +7 -0
  39. package/dist/coding/registry/npm.js +252 -0
  40. package/dist/coding/registry/pypi.d.ts +4 -0
  41. package/dist/coding/registry/pypi.js +210 -0
  42. package/dist/coding/registry/report.d.ts +37 -0
  43. package/dist/coding/registry/report.js +31 -0
  44. package/dist/coding/registry/token.d.ts +4 -0
  45. package/dist/coding/registry/token.js +7 -0
  46. package/dist/coding/registry/types.d.ts +286 -0
  47. package/dist/coding/registry/types.js +19 -0
  48. package/dist/coding/registry/worker-config.d.ts +14 -0
  49. package/dist/coding/registry/worker-config.js +31 -0
  50. package/dist/coding/services/builtins.d.ts +22 -0
  51. package/dist/coding/services/builtins.js +85 -0
  52. package/dist/coding/services/catalog.d.ts +443 -0
  53. package/dist/coding/services/catalog.js +159 -0
  54. package/dist/coding/services/declaration.d.ts +11 -0
  55. package/dist/coding/services/declaration.js +114 -0
  56. package/dist/coding/services/note.d.ts +8 -0
  57. package/dist/coding/services/note.js +15 -0
  58. package/dist/coding/services/resolve.d.ts +25 -0
  59. package/dist/coding/services/resolve.js +50 -0
  60. package/dist/coding/services/wording.d.ts +25 -0
  61. package/dist/coding/services/wording.js +70 -0
  62. package/dist/coding-proxy/main.js +24 -4
  63. package/dist/coding-worker/artifact.d.ts +6 -0
  64. package/dist/coding-worker/debug-trace.d.ts +37 -0
  65. package/dist/coding-worker/debug-trace.js +117 -0
  66. package/dist/coding-worker/driver.d.ts +13 -1
  67. package/dist/coding-worker/driver.js +102 -13
  68. package/dist/coding-worker/errors.js +8 -0
  69. package/dist/coding-worker/main.js +10 -2
  70. package/dist/coding-worker/sdk.d.ts +2 -2
  71. package/dist/coding-worker/sdk.js +7 -2
  72. package/dist/coding-worker/types.d.ts +3 -0
  73. package/dist/config/providers.d.ts +66 -0
  74. package/dist/config/providers.js +137 -0
  75. package/dist/core/attribution.d.ts +101 -0
  76. package/dist/core/attribution.js +208 -0
  77. package/dist/core/budget-groups.d.ts +120 -10
  78. package/dist/core/budget-groups.js +133 -24
  79. package/dist/core/budget-wording.d.ts +22 -0
  80. package/dist/core/budget-wording.js +55 -0
  81. package/dist/core/coding-queue.d.ts +3 -0
  82. package/dist/core/coding-queue.js +6 -2
  83. package/dist/core/coding-service-status.d.ts +11 -0
  84. package/dist/core/coding-service-status.js +17 -0
  85. package/dist/core/cost-report.d.ts +88 -0
  86. package/dist/core/cost-report.js +248 -0
  87. package/dist/core/datastores.js +18 -2
  88. package/dist/core/db.d.ts +5 -1
  89. package/dist/core/db.js +8 -2
  90. package/dist/core/dispatch.d.ts +74 -7
  91. package/dist/core/dispatch.js +382 -47
  92. package/dist/core/engine-native.js +44 -14
  93. package/dist/core/glob.d.ts +10 -0
  94. package/dist/core/glob.js +33 -0
  95. package/dist/core/grants.d.ts +86 -0
  96. package/dist/core/grants.js +126 -0
  97. package/dist/core/host-events.d.ts +72 -0
  98. package/dist/core/host-events.js +597 -0
  99. package/dist/core/host-identity-links.d.ts +54 -0
  100. package/dist/core/host-identity-links.js +189 -0
  101. package/dist/core/host-status.d.ts +78 -0
  102. package/dist/core/host-status.js +228 -0
  103. package/dist/core/in-flight-runs.d.ts +8 -0
  104. package/dist/core/in-flight-runs.js +56 -0
  105. package/dist/core/issue-bridge.d.ts +60 -0
  106. package/dist/core/issue-bridge.js +189 -0
  107. package/dist/core/issue-dedupe.d.ts +70 -0
  108. package/dist/core/issue-dedupe.js +255 -0
  109. package/dist/core/issue-events.d.ts +42 -0
  110. package/dist/core/issue-events.js +155 -0
  111. package/dist/core/issue-status.d.ts +29 -0
  112. package/dist/core/issue-status.js +241 -0
  113. package/dist/core/issue-tracker-tools.d.ts +64 -0
  114. package/dist/core/issue-tracker-tools.js +850 -0
  115. package/dist/core/model-usage.d.ts +10 -0
  116. package/dist/core/model-usage.js +24 -0
  117. package/dist/core/provider-wording.d.ts +12 -0
  118. package/dist/core/provider-wording.js +44 -0
  119. package/dist/core/reconciler.d.ts +42 -2
  120. package/dist/core/reconciler.js +97 -2
  121. package/dist/core/repo-access.d.ts +99 -0
  122. package/dist/core/repo-access.js +136 -0
  123. package/dist/core/review-host-checks.d.ts +11 -0
  124. package/dist/core/review-host-checks.js +41 -0
  125. package/dist/core/review-host-tools.d.ts +47 -0
  126. package/dist/core/review-host-tools.js +354 -0
  127. package/dist/core/run-heartbeat.d.ts +27 -0
  128. package/dist/core/run-heartbeat.js +54 -0
  129. package/dist/core/run-pricing.d.ts +61 -0
  130. package/dist/core/run-pricing.js +56 -0
  131. package/dist/core/runner.d.ts +33 -8
  132. package/dist/core/runner.js +410 -62
  133. package/dist/core/scheduler.d.ts +4 -1
  134. package/dist/core/scheduler.js +3 -2
  135. package/dist/core/secrets.d.ts +11 -2
  136. package/dist/core/secrets.js +25 -6
  137. package/dist/core/self-defects.d.ts +80 -0
  138. package/dist/core/self-defects.js +180 -0
  139. package/dist/core/subagent-memory-tools.d.ts +1 -1
  140. package/dist/core/subagent-memory-tools.js +16 -3
  141. package/dist/core/tool-admin.d.ts +81 -0
  142. package/dist/core/tool-admin.js +129 -0
  143. package/dist/core/tool-names.d.ts +42 -0
  144. package/dist/core/tool-names.js +67 -0
  145. package/dist/core/untrusted-content.d.ts +32 -0
  146. package/dist/core/untrusted-content.js +72 -0
  147. package/dist/core/webhooks.d.ts +17 -2
  148. package/dist/core/webhooks.js +42 -3
  149. package/dist/generated/prisma/browser.d.ts +166 -0
  150. package/dist/generated/prisma/client.d.ts +166 -0
  151. package/dist/generated/prisma/commonInputTypes.d.ts +152 -52
  152. package/dist/generated/prisma/enums.d.ts +13 -0
  153. package/dist/generated/prisma/enums.js +12 -1
  154. package/dist/generated/prisma/internal/class.d.ts +242 -0
  155. package/dist/generated/prisma/internal/class.js +4 -4
  156. package/dist/generated/prisma/internal/prismaNamespace.d.ts +2571 -577
  157. package/dist/generated/prisma/internal/prismaNamespace.js +312 -6
  158. package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +328 -0
  159. package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +312 -6
  160. package/dist/generated/prisma/models/Agent.d.ts +682 -1
  161. package/dist/generated/prisma/models/AgentIssueProject.d.ts +1838 -0
  162. package/dist/generated/prisma/models/AgentIssueProject.js +1 -0
  163. package/dist/generated/prisma/models/AgentRepository.d.ts +1425 -0
  164. package/dist/generated/prisma/models/AgentRepository.js +1 -0
  165. package/dist/generated/prisma/models/AgentTool.d.ts +95 -1
  166. package/dist/generated/prisma/models/AuthUser.d.ts +56 -1
  167. package/dist/generated/prisma/models/CodingAgentProfile.d.ts +258 -7
  168. package/dist/generated/prisma/models/CodingProxySession.d.ts +195 -2
  169. package/dist/generated/prisma/models/CodingRun.d.ts +1405 -95
  170. package/dist/generated/prisma/models/CodingRunServiceStatus.d.ts +1404 -0
  171. package/dist/generated/prisma/models/CodingRunServiceStatus.js +1 -0
  172. package/dist/generated/prisma/models/CodingService.d.ts +1348 -0
  173. package/dist/generated/prisma/models/CodingService.js +1 -0
  174. package/dist/generated/prisma/models/HostEventDelivery.d.ts +946 -0
  175. package/dist/generated/prisma/models/HostEventDelivery.js +1 -0
  176. package/dist/generated/prisma/models/HostIdentity.d.ts +1232 -0
  177. package/dist/generated/prisma/models/HostIdentity.js +1 -0
  178. package/dist/generated/prisma/models/HostIdentityLinkRequest.d.ts +1473 -0
  179. package/dist/generated/prisma/models/HostIdentityLinkRequest.js +1 -0
  180. package/dist/generated/prisma/models/IssueFingerprint.d.ts +1183 -0
  181. package/dist/generated/prisma/models/IssueFingerprint.js +1 -0
  182. package/dist/generated/prisma/models/IssuePullRequest.d.ts +1255 -0
  183. package/dist/generated/prisma/models/IssuePullRequest.js +1 -0
  184. package/dist/generated/prisma/models/ModelCatalogEntry.d.ts +1322 -0
  185. package/dist/generated/prisma/models/ModelCatalogEntry.js +1 -0
  186. package/dist/generated/prisma/models/Principal.d.ts +455 -0
  187. package/dist/generated/prisma/models/RegistryAllowance.d.ts +1148 -0
  188. package/dist/generated/prisma/models/RegistryAllowance.js +1 -0
  189. package/dist/generated/prisma/models/RegistryApprovedVersion.d.ts +1219 -0
  190. package/dist/generated/prisma/models/RegistryApprovedVersion.js +1 -0
  191. package/dist/generated/prisma/models/RegistryFetch.d.ts +1428 -0
  192. package/dist/generated/prisma/models/RegistryFetch.js +1 -0
  193. package/dist/generated/prisma/models/RegistryPlanRefusal.d.ts +1294 -0
  194. package/dist/generated/prisma/models/RegistryPlanRefusal.js +1 -0
  195. package/dist/generated/prisma/models/RegistryVersionFact.d.ts +1085 -0
  196. package/dist/generated/prisma/models/RegistryVersionFact.js +1 -0
  197. package/dist/generated/prisma/models/ResourceGrant.d.ts +1437 -0
  198. package/dist/generated/prisma/models/ResourceGrant.js +1 -0
  199. package/dist/generated/prisma/models/Run.d.ts +1402 -82
  200. package/dist/generated/prisma/models/RunAttribution.d.ts +1259 -0
  201. package/dist/generated/prisma/models/RunAttribution.js +1 -0
  202. package/dist/generated/prisma/models/RunHostCheck.d.ts +1239 -0
  203. package/dist/generated/prisma/models/RunHostCheck.js +1 -0
  204. package/dist/generated/prisma/models/RunHostStatus.d.ts +1315 -0
  205. package/dist/generated/prisma/models/RunHostStatus.js +1 -0
  206. package/dist/generated/prisma/models/RunIssueStatus.d.ts +1199 -0
  207. package/dist/generated/prisma/models/RunIssueStatus.js +1 -0
  208. package/dist/generated/prisma/models/RunModelUsage.d.ts +1316 -0
  209. package/dist/generated/prisma/models/RunModelUsage.js +1 -0
  210. package/dist/generated/prisma/models/Tool.d.ts +15 -3
  211. package/dist/generated/prisma/models/WorkItem.d.ts +1408 -0
  212. package/dist/generated/prisma/models/WorkItem.js +1 -0
  213. package/dist/generated/prisma/models.d.ts +22 -0
  214. package/dist/help/build.d.ts +1 -0
  215. package/dist/help/build.js +9 -0
  216. package/dist/help/catalog.d.ts +24 -0
  217. package/dist/help/catalog.js +160 -0
  218. package/dist/help/cli.d.ts +2 -0
  219. package/dist/help/cli.js +65 -0
  220. package/dist/help/runtime.d.ts +3 -0
  221. package/dist/help/runtime.js +44 -0
  222. package/dist/help/search.d.ts +9 -0
  223. package/dist/help/search.js +104 -0
  224. package/dist/help-index.json +999 -0
  225. package/dist/import/cli-args.js +3 -2
  226. package/dist/import/create.d.ts +5 -0
  227. package/dist/import/create.js +67 -13
  228. package/dist/import/index.js +19 -9
  229. package/dist/import/neutral-schema.d.ts +18 -18
  230. package/dist/knowledge/check.d.ts +13 -0
  231. package/dist/knowledge/check.js +69 -0
  232. package/dist/knowledge/cli.d.ts +14 -0
  233. package/dist/knowledge/cli.js +67 -0
  234. package/dist/knowledge/concept.d.ts +54 -0
  235. package/dist/knowledge/concept.js +78 -0
  236. package/dist/knowledge/note.d.ts +11 -0
  237. package/dist/knowledge/note.js +39 -0
  238. package/dist/knowledge/relevance.d.ts +11 -0
  239. package/dist/knowledge/relevance.js +14 -0
  240. package/dist/knowledge/span-hash.d.ts +3 -0
  241. package/dist/knowledge/span-hash.js +16 -0
  242. package/dist/mcp/auth/access.d.ts +72 -0
  243. package/dist/mcp/auth/access.js +58 -0
  244. package/dist/mcp/auth/grants-cli.d.ts +149 -0
  245. package/dist/mcp/auth/grants-cli.js +518 -0
  246. package/dist/mcp/auth/host-account-cli.d.ts +2 -0
  247. package/dist/mcp/auth/host-account-cli.js +47 -0
  248. package/dist/mcp/auth/ownership.d.ts +25 -51
  249. package/dist/mcp/auth/ownership.js +19 -14
  250. package/dist/mcp/auth/repo-authorization.d.ts +22 -0
  251. package/dist/mcp/auth/repo-authorization.js +51 -0
  252. package/dist/mcp/auth/resource-server.d.ts +33 -2
  253. package/dist/mcp/auth/resource-server.js +99 -3
  254. package/dist/mcp/auth/self-hosted/browser.js +2 -2
  255. package/dist/mcp/auth/self-hosted/cli.js +47 -13
  256. package/dist/mcp/auth/self-hosted/credentials.d.ts +23 -5
  257. package/dist/mcp/auth/self-hosted/credentials.js +62 -4
  258. package/dist/mcp/auth/self-hosted/session.d.ts +7 -6
  259. package/dist/mcp/context.d.ts +30 -1
  260. package/dist/mcp/errors.d.ts +25 -6
  261. package/dist/mcp/errors.js +98 -0
  262. package/dist/mcp/host-events/deliveries.d.ts +9 -0
  263. package/dist/mcp/host-events/deliveries.js +17 -0
  264. package/dist/mcp/host-events/github-ingress.d.ts +38 -0
  265. package/dist/mcp/host-events/github-ingress.js +83 -0
  266. package/dist/mcp/host-events/github-user-callback.d.ts +18 -0
  267. package/dist/mcp/host-events/github-user-callback.js +50 -0
  268. package/dist/mcp/host-events/jira-ingress.d.ts +29 -0
  269. package/dist/mcp/host-events/jira-ingress.js +92 -0
  270. package/dist/mcp/index.d.ts +10 -1
  271. package/dist/mcp/index.js +187 -19
  272. package/dist/mcp/server.js +32 -11
  273. package/dist/mcp/tools/agents.js +519 -50
  274. package/dist/mcp/tools/budget-groups.js +3 -3
  275. package/dist/mcp/tools/cost-report.d.ts +8 -0
  276. package/dist/mcp/tools/cost-report.js +60 -0
  277. package/dist/mcp/tools/datastore.js +22 -15
  278. package/dist/mcp/tools/grants.d.ts +2 -0
  279. package/dist/mcp/tools/grants.js +239 -0
  280. package/dist/mcp/tools/help.d.ts +5 -0
  281. package/dist/mcp/tools/help.js +67 -0
  282. package/dist/mcp/tools/host-accounts.d.ts +2 -0
  283. package/dist/mcp/tools/host-accounts.js +114 -0
  284. package/dist/mcp/tools/issue-projects.d.ts +2 -0
  285. package/dist/mcp/tools/issue-projects.js +238 -0
  286. package/dist/mcp/tools/memory.d.ts +7 -1
  287. package/dist/mcp/tools/memory.js +5 -5
  288. package/dist/mcp/tools/model-catalog.d.ts +22 -0
  289. package/dist/mcp/tools/model-catalog.js +423 -0
  290. package/dist/mcp/tools/repositories.d.ts +2 -0
  291. package/dist/mcp/tools/repositories.js +182 -0
  292. package/dist/mcp/tools/runs.d.ts +6 -0
  293. package/dist/mcp/tools/runs.js +60 -6
  294. package/dist/mcp/tools/scheduling.js +3 -6
  295. package/dist/mcp/tools/secrets.js +18 -6
  296. package/dist/mcp/tools/services.d.ts +2 -0
  297. package/dist/mcp/tools/services.js +222 -0
  298. package/dist/mcp/tools/subagents.js +52 -16
  299. package/dist/mcp/tools/tools.d.ts +41 -0
  300. package/dist/mcp/tools/tools.js +201 -40
  301. package/dist/mcp/tools/trigger.js +65 -7
  302. package/dist/mcp/tools/webhooks.js +15 -4
  303. package/dist/mcp/transport/streamable-http.d.ts +15 -0
  304. package/dist/mcp/transport/streamable-http.js +55 -3
  305. package/dist/mcp/webhooks/ingress.d.ts +2 -1
  306. package/dist/mcp/webhooks/ingress.js +9 -2
  307. package/dist/providers/auth/delegating.d.ts +19 -0
  308. package/dist/providers/auth/delegating.js +71 -0
  309. package/dist/providers/auth/index.d.ts +2 -0
  310. package/dist/providers/auth/index.js +11 -0
  311. package/dist/providers/auth/self-hosted.d.ts +10 -2
  312. package/dist/providers/auth/self-hosted.js +55 -6
  313. package/dist/providers/auth/types.d.ts +6 -0
  314. package/dist/providers/coding-proxy/memory-ledger.d.ts +4 -1
  315. package/dist/providers/coding-proxy/memory-ledger.js +31 -3
  316. package/dist/providers/coding-proxy/metering.d.ts +6 -1
  317. package/dist/providers/coding-proxy/metering.js +41 -4
  318. package/dist/providers/coding-proxy/prisma-ledger.d.ts +17 -0
  319. package/dist/providers/coding-proxy/prisma-ledger.js +106 -8
  320. package/dist/providers/coding-proxy/proxy.d.ts +25 -2
  321. package/dist/providers/coding-proxy/proxy.js +561 -38
  322. package/dist/providers/coding-proxy/registry/audit.d.ts +131 -0
  323. package/dist/providers/coding-proxy/registry/audit.js +380 -0
  324. package/dist/providers/coding-proxy/registry/bounded-fetch.d.ts +16 -0
  325. package/dist/providers/coding-proxy/registry/bounded-fetch.js +77 -0
  326. package/dist/providers/coding-proxy/registry/plan.d.ts +58 -0
  327. package/dist/providers/coding-proxy/registry/plan.js +304 -0
  328. package/dist/providers/coding-proxy/registry/prisma-store.d.ts +34 -0
  329. package/dist/providers/coding-proxy/registry/prisma-store.js +151 -0
  330. package/dist/providers/coding-proxy/registry/service.d.ts +213 -0
  331. package/dist/providers/coding-proxy/registry/service.js +1137 -0
  332. package/dist/providers/coding-proxy/registry/store.d.ts +127 -0
  333. package/dist/providers/coding-proxy/registry/store.js +70 -0
  334. package/dist/providers/coding-proxy/runtime.d.ts +17 -0
  335. package/dist/providers/coding-proxy/runtime.js +63 -0
  336. package/dist/providers/coding-proxy/secure-fetch.js +0 -1
  337. package/dist/providers/coding-proxy/server.d.ts +11 -0
  338. package/dist/providers/coding-proxy/server.js +163 -0
  339. package/dist/providers/coding-proxy/types.d.ts +33 -1
  340. package/dist/providers/coding-proxy/types.js +12 -1
  341. package/dist/providers/engine/types.d.ts +29 -0
  342. package/dist/providers/executor/build.d.ts +2 -2
  343. package/dist/providers/executor/composition.d.ts +3 -0
  344. package/dist/providers/executor/composition.js +28 -1
  345. package/dist/providers/executor/container.d.ts +134 -4
  346. package/dist/providers/executor/container.js +394 -37
  347. package/dist/providers/executor/dbos.d.ts +4 -3
  348. package/dist/providers/executor/dbos.js +7 -5
  349. package/dist/providers/executor/in-process.d.ts +2 -3
  350. package/dist/providers/executor/routing.d.ts +13 -0
  351. package/dist/providers/executor/routing.js +18 -0
  352. package/dist/providers/executor/types.d.ts +34 -0
  353. package/dist/providers/issue-tracker/adf.d.ts +31 -0
  354. package/dist/providers/issue-tracker/adf.js +181 -0
  355. package/dist/providers/issue-tracker/index.d.ts +5 -0
  356. package/dist/providers/issue-tracker/index.js +12 -0
  357. package/dist/providers/issue-tracker/jira-client.d.ts +41 -0
  358. package/dist/providers/issue-tracker/jira-client.js +151 -0
  359. package/dist/providers/issue-tracker/jira-events.d.ts +3 -0
  360. package/dist/providers/issue-tracker/jira-events.js +98 -0
  361. package/dist/providers/issue-tracker/jira.d.ts +116 -0
  362. package/dist/providers/issue-tracker/jira.js +502 -0
  363. package/dist/providers/issue-tracker/types.d.ts +269 -0
  364. package/dist/providers/issue-tracker/types.js +16 -0
  365. package/dist/providers/jobs/claude-tool-setup.d.ts +23 -0
  366. package/dist/providers/jobs/claude-tool-setup.js +50 -0
  367. package/dist/providers/jobs/collect-prune.d.ts +7 -0
  368. package/dist/providers/jobs/collect-prune.js +27 -0
  369. package/dist/providers/jobs/docker-isolation.d.ts +48 -3
  370. package/dist/providers/jobs/docker-isolation.js +213 -22
  371. package/dist/providers/jobs/docker-services.d.ts +35 -0
  372. package/dist/providers/jobs/docker-services.js +191 -0
  373. package/dist/providers/jobs/docker.d.ts +53 -2
  374. package/dist/providers/jobs/docker.js +307 -32
  375. package/dist/providers/jobs/fake-kubernetes-api.d.ts +1 -0
  376. package/dist/providers/jobs/fake-kubernetes-api.js +12 -3
  377. package/dist/providers/jobs/kubernetes-isolation.d.ts +17 -2
  378. package/dist/providers/jobs/kubernetes-isolation.js +238 -57
  379. package/dist/providers/jobs/kubernetes-platform.d.ts +10 -4
  380. package/dist/providers/jobs/kubernetes-platform.js +11 -5
  381. package/dist/providers/jobs/kubernetes-preflight.js +3 -0
  382. package/dist/providers/jobs/kubernetes.d.ts +24 -2
  383. package/dist/providers/jobs/kubernetes.js +153 -22
  384. package/dist/providers/jobs/service-state.d.ts +22 -0
  385. package/dist/providers/jobs/service-state.js +17 -0
  386. package/dist/providers/jobs/types.d.ts +18 -0
  387. package/dist/providers/llm/anthropic.d.ts +3 -3
  388. package/dist/providers/llm/anthropic.js +3 -5
  389. package/dist/providers/llm/bedrock.d.ts +3 -3
  390. package/dist/providers/llm/bedrock.js +3 -8
  391. package/dist/providers/llm/catalog-lookup.d.ts +10 -0
  392. package/dist/providers/llm/catalog-lookup.js +15 -0
  393. package/dist/providers/llm/catalog-shipped.d.ts +18 -0
  394. package/dist/providers/llm/catalog-shipped.js +197 -0
  395. package/dist/providers/llm/catalog-store.d.ts +58 -0
  396. package/dist/providers/llm/catalog-store.js +138 -0
  397. package/dist/providers/llm/catalog-types.d.ts +66 -0
  398. package/dist/providers/llm/catalog-types.js +64 -0
  399. package/dist/providers/llm/catalog.d.ts +61 -0
  400. package/dist/providers/llm/catalog.js +147 -0
  401. package/dist/providers/llm/claude-messages.d.ts +5 -1
  402. package/dist/providers/llm/claude-messages.js +1 -0
  403. package/dist/providers/llm/claude-provider.d.ts +10 -12
  404. package/dist/providers/llm/claude-provider.js +15 -6
  405. package/dist/providers/llm/index.d.ts +9 -6
  406. package/dist/providers/llm/index.js +8 -5
  407. package/dist/providers/llm/openai.d.ts +14 -5
  408. package/dist/providers/llm/openai.js +24 -14
  409. package/dist/providers/llm/pricing-core.d.ts +5 -3
  410. package/dist/providers/llm/registration.js +8 -12
  411. package/dist/providers/llm/routing.d.ts +21 -11
  412. package/dist/providers/llm/routing.js +47 -13
  413. package/dist/providers/llm/types.d.ts +12 -0
  414. package/dist/providers/llm/types.js +8 -1
  415. package/dist/providers/review-host/diff-lines.d.ts +16 -0
  416. package/dist/providers/review-host/diff-lines.js +59 -0
  417. package/dist/providers/review-host/github-events.d.ts +6 -0
  418. package/dist/providers/review-host/github-events.js +226 -0
  419. package/dist/providers/review-host/github-user-auth.d.ts +38 -0
  420. package/dist/providers/review-host/github-user-auth.js +128 -0
  421. package/dist/providers/review-host/github.d.ts +57 -0
  422. package/dist/providers/review-host/github.js +568 -0
  423. package/dist/providers/review-host/index.d.ts +12 -0
  424. package/dist/providers/review-host/index.js +30 -0
  425. package/dist/providers/review-host/review-format.d.ts +19 -0
  426. package/dist/providers/review-host/review-format.js +59 -0
  427. package/dist/providers/review-host/types.d.ts +309 -0
  428. package/dist/providers/review-host/types.js +24 -0
  429. package/dist/providers/vcs/git.d.ts +16 -6
  430. package/dist/providers/vcs/git.js +78 -34
  431. package/dist/providers/vcs/github.d.ts +94 -3
  432. package/dist/providers/vcs/github.js +223 -17
  433. package/dist/providers/vcs/types.d.ts +48 -4
  434. package/dist/quickstart/index.d.ts +2 -0
  435. package/dist/quickstart/index.js +36 -8
  436. package/dist/sandbox/fetch-policy.d.ts +20 -2
  437. package/dist/sandbox/fetch-policy.js +64 -3
  438. package/dist/sandbox/host-functions.d.ts +9 -1
  439. package/dist/sandbox/host-functions.js +12 -5
  440. package/dist/serve.js +8 -2
  441. package/dist/viewer/api-schema.d.ts +2757 -0
  442. package/dist/viewer/api-schema.js +165 -0
  443. package/dist/viewer/build-schemas.d.ts +2 -0
  444. package/dist/viewer/build-schemas.js +18 -0
  445. package/dist/viewer/event-bus.d.ts +38 -0
  446. package/dist/viewer/event-bus.js +232 -0
  447. package/dist/viewer/graph.d.ts +40 -0
  448. package/dist/viewer/graph.js +243 -0
  449. package/dist/viewer/http.d.ts +30 -0
  450. package/dist/viewer/http.js +133 -0
  451. package/dist/viewer/run-detail.d.ts +4 -0
  452. package/dist/viewer/run-detail.js +61 -0
  453. package/dist/wardby-bin.js +11 -0
  454. package/docs/README.md +40 -0
  455. package/docs/agent-recipes.md +383 -0
  456. package/docs/architecture-runtime.md +90 -0
  457. package/docs/assets/brand/wardby-icon-512.png +0 -0
  458. package/docs/assets/brand/wardby-icon.svg +16 -0
  459. package/docs/assets/brand/wardby-mascot-profile-512.png +0 -0
  460. package/docs/assets/brand/wardby-mascot.png +0 -0
  461. package/docs/assets/brand/wardby-mascot.svg +5 -0
  462. package/docs/assets/wardby-workflow.svg +106 -0
  463. package/docs/code-review-agents.md +508 -0
  464. package/docs/coding-agent-setup.md +175 -0
  465. package/docs/coding-packages.md +455 -0
  466. package/docs/coding-services.md +300 -0
  467. package/docs/coding-worker-byo-images.md +98 -0
  468. package/docs/coding-worker-isolation.md +1068 -0
  469. package/docs/getting-started-gke.md +632 -0
  470. package/docs/getting-started-identity-provider.md +319 -0
  471. package/docs/getting-started.md +147 -0
  472. package/docs/jira-agents.md +649 -0
  473. package/docs/knowledge.md +387 -0
  474. package/docs/models.md +221 -0
  475. package/docs/observability.md +53 -0
  476. package/docs/release-verification.md +66 -0
  477. package/docs/security-deployment.md +667 -0
  478. package/docs/viewer-api.md +142 -0
  479. package/help/admin-viewer.md +39 -0
  480. package/help/agent-recipes.md +173 -0
  481. package/help/architecture-agent.md +189 -0
  482. package/help/builder-agent.md +80 -0
  483. package/help/code-review-agents.md +37 -0
  484. package/help/coding-packages.md +31 -0
  485. package/help/coding-services.md +71 -0
  486. package/help/cost-attribution.md +67 -0
  487. package/help/creating-agents.md +90 -0
  488. package/help/deploy-gke.md +45 -0
  489. package/help/deployment-targets.md +39 -0
  490. package/help/errors/budget-group-exhausted.md +25 -0
  491. package/help/errors/docker-isolation-unsupported.md +26 -0
  492. package/help/errors/model-unavailable.md +63 -0
  493. package/help/errors/protected-path.md +45 -0
  494. package/help/errors/repo-access.md +27 -0
  495. package/help/errors/service-declaration-invalid.md +27 -0
  496. package/help/errors/service-declaration-unavailable.md +25 -0
  497. package/help/errors/service-launcher-unsupported.md +30 -0
  498. package/help/errors/service-not-allowed.md +27 -0
  499. package/help/errors/service-unknown.md +23 -0
  500. package/help/errors/service-unready.md +49 -0
  501. package/help/getting-started.md +32 -0
  502. package/help/github.md +48 -0
  503. package/help/identity-and-access.md +44 -0
  504. package/help/jira.md +135 -0
  505. package/help/knowledge.md +47 -0
  506. package/help/mcp.md +30 -0
  507. package/help/models.md +90 -0
  508. package/help/native-capabilities.md +30 -0
  509. package/help/observability.md +41 -0
  510. package/help/operating-agents.md +36 -0
  511. package/help/security.md +27 -0
  512. package/help/troubleshooting/budgets.md +32 -0
  513. package/help/troubleshooting/coding-workers.md +47 -0
  514. package/help/troubleshooting/repository-access.md +27 -0
  515. package/package.json +14 -3
  516. package/prisma/migrations/20260925010000_coding_collect_exclude/migration.sql +8 -0
  517. package/prisma/migrations/20260925015000_allowed_egress_default/migration.sql +8 -0
  518. package/prisma/migrations/20260925020000_coding_package_registry/migration.sql +55 -0
  519. package/prisma/migrations/20260925030000_tool_name_per_owner/migration.sql +11 -0
  520. package/prisma/migrations/20260926010000_run_trigger_host_event/migration.sql +7 -0
  521. package/prisma/migrations/20260926020000_code_review_hosts/migration.sql +53 -0
  522. package/prisma/migrations/20260926030000_auth_user_roles/migration.sql +9 -0
  523. package/prisma/migrations/20260926040000_agent_effort/migration.sql +6 -0
  524. package/prisma/migrations/20260926050000_registry_lockfile_plan/migration.sql +32 -0
  525. package/prisma/migrations/20260926050000_repo_access_authorization/migration.sql +82 -0
  526. package/prisma/migrations/20260926060000_registry_plan_refusal/migration.sql +19 -0
  527. package/prisma/migrations/20260926100000_registry_plan_refusal_published_at/migration.sql +5 -0
  528. package/prisma/migrations/20260926190000_run_host_status/migration.sql +18 -0
  529. package/prisma/migrations/20260926210000_run_host_status_at_dispatch/migration.sql +5 -0
  530. package/prisma/migrations/20260927010000_resource_grants/migration.sql +72 -0
  531. package/prisma/migrations/20260927020000_proxy_session_budget_exhausted/migration.sql +3 -0
  532. package/prisma/migrations/20260927030000_coding_debug_trace/migration.sql +4 -0
  533. package/prisma/migrations/20260927040000_proxy_session_upstream_failure/migration.sql +3 -0
  534. package/prisma/migrations/20260927050000_coding_run_services/migration.sql +74 -0
  535. package/prisma/migrations/20260928000000_coding_run_tool_image/migration.sql +5 -0
  536. package/prisma/migrations/20260930000000_jira_issue_projects/migration.sql +34 -0
  537. package/prisma/migrations/20261001000000_jira_phase2_allowlists/migration.sql +3 -0
  538. package/prisma/migrations/20261001010000_jira_link_types_allowlist/migration.sql +2 -0
  539. package/prisma/migrations/20261002000000_jira_coding_bridge/migration.sql +28 -0
  540. package/prisma/migrations/20261002010000_jira_issue_creation/migration.sql +25 -0
  541. package/prisma/migrations/20261003000000_issue_cost_attribution/migration.sql +56 -0
  542. package/prisma/migrations/20261003010000_coding_run_service_status/migration.sql +23 -0
  543. package/prisma/migrations/20261003020000_viewer_notify/migration.sql +54 -0
  544. package/prisma/migrations/20261003030000_viewer_notify_fixes/migration.sql +47 -0
  545. package/prisma/migrations/20261003040000_viewer_indexes/migration.sql +12 -0
  546. package/prisma/migrations/20261004000000_model_catalog/migration.sql +26 -0
  547. package/prisma/schema.prisma +663 -21
  548. package/dist/mcp/tools/models.d.ts +0 -8
  549. package/dist/mcp/tools/models.js +0 -15
  550. package/dist/providers/llm/pricing-anthropic.d.ts +0 -4
  551. package/dist/providers/llm/pricing-anthropic.js +0 -30
  552. package/dist/providers/llm/pricing-bedrock-claude.d.ts +0 -12
  553. package/dist/providers/llm/pricing-bedrock-claude.js +0 -37
  554. package/dist/providers/llm/pricing.d.ts +0 -30
  555. package/dist/providers/llm/pricing.js +0 -74
@@ -0,0 +1,208 @@
1
+ import { ISSUE_KEY, ISSUE_TRACKER_PROVIDERS, projectOf, } from "../providers/issue-tracker/types.js";
2
+ import { logger } from "./logger.js";
3
+ const log = logger.child({ module: "attribution" });
4
+ /** A WorkItem snapshotted this recently is reused as-is, without calling the tracker. */
5
+ export const SNAPSHOT_CACHE_MS = 10 * 60 * 1000;
6
+ export const SNAPSHOT_TIMEOUT_MS = 3000;
7
+ /**
8
+ * Snapshot budget for a caller that is answering a request (a webhook, an
9
+ * MCP tool call, a host event): a short timeout and no 429 retry, so a slow
10
+ * or rate-limited tracker degrades to key-only attribution instead of
11
+ * stalling the response.
12
+ */
13
+ export const RESPONSE_PATH_SNAPSHOT_BUDGET = { timeoutMs: 2000, retryOn429: false };
14
+ /**
15
+ * The work item a new attribution points at, snapshotted from the tracker
16
+ * unless a fresh snapshot is already stored. Never throws: a failed lookup or
17
+ * snapshot attributes by key only.
18
+ */
19
+ export async function resolveWorkItem(db, trackers, provider, key, opts = {}) {
20
+ const now = opts.now ?? new Date();
21
+ const keyOnly = (scopeKey = projectOf(key)) => ({ provider, key, scopeKey, snapshot: null });
22
+ let existing;
23
+ try {
24
+ existing = await db.workItem.findUnique({
25
+ where: { provider_key: { provider, key } },
26
+ select: { refreshedAt: true, scopeKey: true },
27
+ });
28
+ }
29
+ catch (err) {
30
+ log.warn({ err, provider, key }, "work item lookup failed; attributing by key only");
31
+ return keyOnly();
32
+ }
33
+ if (existing?.refreshedAt && now.getTime() - existing.refreshedAt.getTime() < SNAPSHOT_CACHE_MS) {
34
+ return keyOnly(existing.scopeKey);
35
+ }
36
+ const tracker = trackers?.[provider];
37
+ if (!tracker)
38
+ return keyOnly(existing?.scopeKey);
39
+ try {
40
+ const snapshot = await tracker.snapshotIssue(key, {
41
+ timeoutMs: opts.timeoutMs ?? SNAPSHOT_TIMEOUT_MS,
42
+ retryOn429: opts.retryOn429,
43
+ });
44
+ return { provider, key, scopeKey: snapshot.scopeKey, snapshot };
45
+ }
46
+ catch (err) {
47
+ log.warn({ err, provider, key }, "issue snapshot failed; attributing by key only");
48
+ return keyOnly(existing?.scopeKey);
49
+ }
50
+ }
51
+ /** Upserts the item (and its parent) and returns the item's id and the parent to freeze. A key-only result never clobbers stored fields. */
52
+ async function upsertWorkItem(tx, item, now) {
53
+ const s = item.snapshot;
54
+ if (s?.parent) {
55
+ // The parent's own type/scope/title are refreshed when it is snapshotted itself; here only make sure it exists
56
+ // and take a title we were handed for free.
57
+ await tx.workItem.upsert({
58
+ where: { provider_key: { provider: item.provider, key: s.parent.key } },
59
+ create: {
60
+ provider: item.provider,
61
+ key: s.parent.key,
62
+ scopeKey: projectOf(s.parent.key),
63
+ ...(s.parent.title ? { title: s.parent.title } : {}),
64
+ },
65
+ update: s.parent.title ? { title: s.parent.title } : {},
66
+ });
67
+ }
68
+ const snapshotFields = s
69
+ ? {
70
+ title: s.title ?? null,
71
+ type: s.type ?? null,
72
+ url: s.url,
73
+ scopeKey: s.scopeKey,
74
+ parentKey: s.parent?.key ?? null,
75
+ parentKind: s.parent?.kind ?? null,
76
+ refreshedAt: now,
77
+ }
78
+ : {};
79
+ const row = await tx.workItem.upsert({
80
+ where: { provider_key: { provider: item.provider, key: item.key } },
81
+ create: { provider: item.provider, key: item.key, scopeKey: item.scopeKey, ...snapshotFields },
82
+ update: snapshotFields,
83
+ select: { id: true, parentKey: true },
84
+ });
85
+ return row;
86
+ }
87
+ /**
88
+ * Covers runs created before attribution existed (no backfill): a continued
89
+ * coding run that already carries CodingRun.issueKey, else a direct parent
90
+ * with a RunIssueStatus. Grandchildren then inherit the child's new
91
+ * RunAttribution, so only the direct parent is checked.
92
+ */
93
+ async function legacyIssue(tx, from) {
94
+ if (from.continuesCodingRunId) {
95
+ const prior = await tx.codingRun.findUnique({
96
+ where: { runId: from.continuesCodingRunId },
97
+ select: { issueProvider: true, issueKey: true },
98
+ });
99
+ if (prior?.issueProvider && prior.issueKey)
100
+ return { provider: prior.issueProvider, key: prior.issueKey };
101
+ }
102
+ if (from.parentRunId) {
103
+ const status = await tx.runIssueStatus.findUnique({
104
+ where: { runId: from.parentRunId },
105
+ select: { provider: true, issueKey: true },
106
+ });
107
+ if (status)
108
+ return { provider: status.provider, key: status.issueKey };
109
+ }
110
+ return null;
111
+ }
112
+ /**
113
+ * Writes the run's attribution inside dispatch's persist transaction.
114
+ * Precedence: the parent run's attribution, then the continued coding run's,
115
+ * then a pre-attribution issue (legacyIssue), then the caller's intent. Returns the item's provider/key (for
116
+ * CodingRun.issueProvider/issueKey), or null when the run is unattributed.
117
+ */
118
+ export async function attributeRun(tx, runId, from, now = new Date()) {
119
+ for (const ancestorId of [from.parentRunId, from.continuesCodingRunId]) {
120
+ if (!ancestorId)
121
+ continue;
122
+ const inherited = await tx.runAttribution.findUnique({
123
+ where: { runId: ancestorId },
124
+ select: { workItemId: true, parentKeyAtRun: true, workItem: { select: { provider: true, key: true } } },
125
+ });
126
+ if (!inherited)
127
+ continue;
128
+ await tx.runAttribution.create({
129
+ data: { runId, workItemId: inherited.workItemId, parentKeyAtRun: inherited.parentKeyAtRun, source: "inherited" },
130
+ });
131
+ return { provider: inherited.workItem.provider, key: inherited.workItem.key };
132
+ }
133
+ const legacy = await legacyIssue(tx, from);
134
+ if (legacy) {
135
+ const item = { ...legacy, scopeKey: projectOf(legacy.key), snapshot: null };
136
+ const { id, parentKey } = await upsertWorkItem(tx, item, now);
137
+ await tx.runAttribution.create({ data: { runId, workItemId: id, parentKeyAtRun: parentKey, source: "inherited" } });
138
+ return legacy;
139
+ }
140
+ if (!from.intent)
141
+ return null;
142
+ const { id, parentKey } = await upsertWorkItem(tx, from.intent.item, now);
143
+ await tx.runAttribution.create({
144
+ data: { runId, workItemId: id, parentKeyAtRun: parentKey, source: from.intent.source },
145
+ });
146
+ return { provider: from.intent.item.provider, key: from.intent.item.key };
147
+ }
148
+ /** The issue a pull request was opened for (phase-3 IssuePullRequest); the earliest link when several exist. */
149
+ export async function linkedPullRequestIssue(db, pr) {
150
+ try {
151
+ const row = await db.issuePullRequest.findFirst({
152
+ where: { codeProvider: pr.codeProvider, repository: pr.repository, number: pr.number },
153
+ orderBy: { createdAt: "asc" },
154
+ select: { issueProvider: true, issueKey: true },
155
+ });
156
+ return row ? { provider: row.issueProvider, key: row.issueKey } : null;
157
+ }
158
+ catch (err) {
159
+ log.warn({ err, ...pr }, "linked issue lookup failed; the run is unattributed");
160
+ return null;
161
+ }
162
+ }
163
+ /** Attribution for a run on a pull request that was opened for an issue; undefined when it has none. */
164
+ export async function linkedPullRequestAttribution(db, trackers, pr, opts) {
165
+ const issue = await linkedPullRequestIssue(db, pr);
166
+ if (!issue)
167
+ return undefined;
168
+ return { source: "linked_pr", item: await resolveWorkItem(db, trackers, issue.provider, issue.key, opts) };
169
+ }
170
+ /** A refusal whose message is safe to return to the caller. */
171
+ export class AttributionError extends Error {
172
+ constructor(message) {
173
+ super(message);
174
+ this.name = "AttributionError";
175
+ }
176
+ }
177
+ /**
178
+ * An issue a caller names on trigger_agent or a webhook. It decides whose
179
+ * cost this run counts toward, so it must be well formed and in a project the
180
+ * agent is linked to (AgentIssueProject) — otherwise the dispatch is refused.
181
+ * Issue keys are uppercase, so a key that differs only by case or surrounding
182
+ * spaces ("pay-241") is corrected rather than refused. `field` is the caller's
183
+ * name for the value (trigger_agent: issue; webhooks: wardbyIssue), so
184
+ * refusals name the field the caller actually sent.
185
+ */
186
+ export async function validateExplicitIssue(db, agentId, issue, field = "issue") {
187
+ const candidate = issue && typeof issue === "object" ? issue : {};
188
+ const provider = candidate.provider;
189
+ const key = typeof candidate.key === "string" ? candidate.key.trim().toUpperCase() : candidate.key;
190
+ if (typeof provider !== "string" || !ISSUE_TRACKER_PROVIDERS.includes(provider)) {
191
+ throw new AttributionError(`${field}.provider must be one of: ${ISSUE_TRACKER_PROVIDERS.join(", ")}.`);
192
+ }
193
+ if (typeof key !== "string" || !ISSUE_KEY.test(key)) {
194
+ throw new AttributionError(`${field}.key must be an issue key such as PROJ-123.`);
195
+ }
196
+ const link = await db.agentIssueProject.findUnique({
197
+ where: { agentId_provider_projectKey: { agentId, provider, projectKey: projectOf(key) } },
198
+ select: { agentId: true },
199
+ });
200
+ if (!link)
201
+ throw new AttributionError(`This agent is not linked to ${provider} project ${projectOf(key)}.`);
202
+ return { provider: provider, key };
203
+ }
204
+ export async function explicitAttribution(db, trackers, agentId, issue, opts) {
205
+ const { field, ...budget } = opts ?? {};
206
+ const { provider, key } = await validateExplicitIssue(db, agentId, issue, field);
207
+ return { source: "explicit", item: await resolveWorkItem(db, trackers, provider, key, budget) };
208
+ }
@@ -10,7 +10,28 @@
10
10
  * enforces that tightened ceiling with no changes of its own: a group
11
11
  * whose period budget is already exhausted simply produces an effective
12
12
  * budget of 0, which the engine's turn-1 gate already refuses exactly
13
- * like any other zero/negative budget.
13
+ * like any other zero/negative budget. A coding run gets the same effective
14
+ * budget at dispatch (dispatch.ts), where it becomes the run's reservation.
15
+ *
16
+ * Spend is the real cost of the period's runs plus the unspent part of every
17
+ * live run still in flight (pending or running): its hold, minus what it has
18
+ * spent so far, never below zero. A coding run's hold is its
19
+ * CodingRun.budgetReservedUsd; a native run's is its agent's current
20
+ * per-run budgetUsd, an upper bound on the effective budget it pinned at load.
21
+ * Counting holds is what stops concurrent runs from each claiming the
22
+ * same remainder (security review E-04).
23
+ *
24
+ * Only live runs hold anything (see isHoldLive): a run that stopped
25
+ * heartbeating (a crashed process, a Ctrl-C'd `wardby run`) or a coding run
26
+ * past its queue + run timeout releases its hold even while its row still
27
+ * says pending/running, so a zombie row cannot pin a group for a whole period.
28
+ *
29
+ * A native run asks for its budget at load, after its row already exists, and
30
+ * counts only the holds of runs that started before it (startedAt, then id):
31
+ * first come, first served. Two members dispatched on the same tick never
32
+ * both see the other's hold and starve each other; the earlier one gets the
33
+ * remainder and the later one gets what the earlier one leaves. A coding
34
+ * dispatch has no row yet, so it counts every live hold.
14
35
  */
15
36
  import type { Agent, BudgetGroup, PrismaClient } from "#prisma";
16
37
  export type Period = "day" | "week" | "month";
@@ -24,9 +45,75 @@ export declare function periodStart(period: Period, now: Date): Date;
24
45
  export interface PeriodSpend {
25
46
  period: Period;
26
47
  capUsd: number;
48
+ /** Real cost recorded by the period's runs, finished or not. */
27
49
  spentUsd: number;
50
+ /** Unspent reservations of the period's in-flight (pending/running) runs. */
51
+ reservedUsd: number;
52
+ /** capUsd - spentUsd - reservedUsd, never below zero. */
28
53
  remainingUsd: number;
29
54
  }
55
+ /** A group member as computeGroupSpend needs it: a native run in flight holds its agent's budgetUsd. */
56
+ export interface GroupMember {
57
+ id: string;
58
+ budgetUsd: unknown;
59
+ }
60
+ /** A run's position in first-come-first-served order: startedAt, then id. */
61
+ export interface RunOrder {
62
+ id: string;
63
+ startedAt: Date;
64
+ }
65
+ export interface SpendOptions {
66
+ /**
67
+ * Runs whose unspent hold is left out (their real cost still counts): the
68
+ * run asking for its own budget, and the run tree a sub-agent spends from,
69
+ * whose hold already covers it.
70
+ */
71
+ excludeReservationRunIds?: readonly string[];
72
+ /**
73
+ * Runs left out of the group's spend entirely, real cost and hold alike:
74
+ * the run tree when computing the tree's own ceiling, whose spend is
75
+ * subtracted from that ceiling separately and must count only once.
76
+ */
77
+ excludeRunIds?: readonly string[];
78
+ /**
79
+ * First come, first served: count only the holds of runs ordered strictly
80
+ * before this one. Unset (a coding dispatch, whose row does not exist yet)
81
+ * counts every live hold.
82
+ */
83
+ holdsBefore?: RunOrder;
84
+ /** CODING_QUEUE_TIMEOUT_SEC; read from the environment when omitted. */
85
+ codingQueueTimeoutSec?: number;
86
+ }
87
+ /**
88
+ * How long a run's last heartbeat keeps its hold alive: the reconciler's
89
+ * heartbeat timeout plus one reconcile interval, so a managed run is reaped
90
+ * (or re-claimed, which refreshes heartbeatAt) before its hold lapses.
91
+ * Managed native runs beat every HEARTBEAT_INTERVAL_MS (InProcessExecutor,
92
+ * DbosExecutor), as do `wardby run` and native sub-agent children
93
+ * (withRunHeartbeat); a coding run beats while its job is polled.
94
+ */
95
+ export declare const HOLD_HEARTBEAT_TTL_MS: number;
96
+ /** Slack past a coding run's queue + run timeout before its hold lapses (matches the sub-agent wait grace). */
97
+ export declare const CODING_HOLD_GRACE_SEC = 60;
98
+ /** Smallest amount worth holding or reserving: CodingRun.budgetReservedUsd has 6 decimal places. */
99
+ export declare const MIN_RESERVATION_USD = 0.000001;
100
+ interface HoldRow {
101
+ id: string;
102
+ status: string;
103
+ startedAt: Date;
104
+ heartbeatAt: Date | null;
105
+ codingRun: {
106
+ timeoutSec: number;
107
+ } | null;
108
+ }
109
+ /**
110
+ * Whether an in-flight row still holds budget. A run holds while its last
111
+ * sign of life (heartbeatAt, or startedAt before its first beat) is within
112
+ * HOLD_HEARTBEAT_TTL_MS. A coding run also holds, beat or not, until its
113
+ * queue timeout + run timeout + grace have passed since dispatch: a queued
114
+ * coding run does not beat, and nothing can legitimately spend past that.
115
+ */
116
+ export declare function isHoldLive(run: HoldRow, now: Date, codingQueueTimeoutSec: number): boolean;
30
117
  /**
31
118
  * Live per-period spend for whichever of a group's daily/weekly/monthly
32
119
  * caps are actually set -- unset periods are omitted from the result, not
@@ -34,7 +121,7 @@ export interface PeriodSpend {
34
121
  * (month contains week contains day), bucketed in JS, rather than a
35
122
  * separate DB round trip per period.
36
123
  */
37
- export declare function computeGroupSpend(db: BudgetGroupsDb, group: Pick<BudgetGroup, "dailyBudgetUsd" | "weeklyBudgetUsd" | "monthlyBudgetUsd">, memberAgentIds: string[], now?: Date): Promise<PeriodSpend[]>;
124
+ export declare function computeGroupSpend(db: BudgetGroupsDb, group: Pick<BudgetGroup, "dailyBudgetUsd" | "weeklyBudgetUsd" | "monthlyBudgetUsd">, members: readonly GroupMember[], now?: Date, options?: SpendOptions): Promise<PeriodSpend[]>;
38
125
  export interface RunTreeSpend {
39
126
  rootRunId: string;
40
127
  capUsd: number;
@@ -44,18 +131,34 @@ export interface RunTreeSpend {
44
131
  /**
45
132
  * Real-time shared budget scope for a sub-agent dispatch: the whole run tree
46
133
  * rooted at `parentRunId`'s ultimate ancestor shares one ceiling —
47
- * the root's own effective budget (its own budgetUsd, itself tightened by
48
- * its own BudgetGroup if any) minus everything every run in the tree has
49
- * spent so far. The root's ceiling is recomputed fresh here, not pinned
50
- * from whatever it was when the root run started — consistent with
51
- * BudgetGroup periods already always being live-recomputed rather than
52
- * snapshotted.
134
+ * the root's own budgetUsd, itself tightened by the root agent's BudgetGroup
135
+ * if any — minus everything every run in the tree has spent so far. The
136
+ * ceiling is recomputed fresh here, not pinned from whatever it was when the
137
+ * root run started — consistent with BudgetGroup periods already always
138
+ * being live-recomputed rather than snapshotted. Its group check leaves the
139
+ * tree's runs out entirely, cost and hold alike: the tree's spend is
140
+ * subtracted once, below, so counting it in the group too would charge it
141
+ * twice, and the tree spends inside the root's hold, so counting that hold
142
+ * would leave the tree nothing. It also leaves out holds of runs that started
143
+ * after the root (they already counted the root's hold). capUsd is therefore
144
+ * the tree's whole ceiling, and remainingUsd = capUsd - spentUsd.
53
145
  */
54
146
  export declare function computeRunTreeSpend(db: BudgetGroupsDb, parentRunId: string, now?: Date): Promise<RunTreeSpend>;
147
+ export type BudgetConstraint = Period | "run-tree";
55
148
  export interface EffectiveBudgetResult {
56
149
  effectiveBudgetUsd: number;
57
150
  /** Which constraint(s), if any, are tighter than the agent's own budgetUsd right now. */
58
- constrainedBy: (Period | "run-tree")[];
151
+ constrainedBy: BudgetConstraint[];
152
+ /** The first constraint with nothing left (< MIN_RESERVATION_USD), set only when there is one. */
153
+ exhaustedBy?: BudgetConstraint;
154
+ }
155
+ export interface EffectiveBudgetOptions {
156
+ /**
157
+ * The run this budget is for, when its row already exists (a native run's
158
+ * load step): its own hold is not counted against itself, and neither are
159
+ * the holds of runs that started after it (first come, first served).
160
+ */
161
+ self?: RunOrder;
59
162
  }
60
163
  /**
61
164
  * The per-run budget ceiling to actually use for `agent`: its own
@@ -68,5 +171,12 @@ export interface EffectiveBudgetResult {
68
171
  * with no caps configured is returned unchanged. Also logs a warning
69
172
  * (never blocks, never throws) once a period's spend crosses the group's
70
173
  * own warnThresholdRatio.
174
+ *
175
+ * A sub-agent in the root's own budget group spends inside the root's hold,
176
+ * so its group check is the root's: the tree's holds and the holds of runs
177
+ * after the root are left out. A sub-agent in any other group is an
178
+ * ordinary member of that group and counts every earlier live hold there,
179
+ * the tree's included.
71
180
  */
72
- export declare function effectiveBudgetForRun(db: BudgetGroupsDb, agent: Pick<Agent, "id" | "budgetGroupId" | "budgetUsd">, now?: Date, parentRunId?: string): Promise<EffectiveBudgetResult>;
181
+ export declare function effectiveBudgetForRun(db: BudgetGroupsDb, agent: Pick<Agent, "id" | "budgetGroupId" | "budgetUsd">, now?: Date, parentRunId?: string, options?: EffectiveBudgetOptions): Promise<EffectiveBudgetResult>;
182
+ export {};
@@ -1,4 +1,6 @@
1
+ import { loadCodingConcurrencyConfig } from "../config/providers.js";
1
2
  import { logger } from "./logger.js";
3
+ import { HEARTBEAT_TIMEOUT_MS, RECONCILE_INTERVAL_MS } from "./timing.js";
2
4
  const budgetGroupLog = logger.child({ module: "budget-groups" });
3
5
  /**
4
6
  * Calendar-aligned UTC period start. "week" is the ISO week: Monday 00:00
@@ -20,6 +22,44 @@ export function periodStart(period, now) {
20
22
  function toNullableNumber(value) {
21
23
  return value === null || value === undefined ? null : Number(value);
22
24
  }
25
+ const IN_FLIGHT = new Set(["pending", "running"]);
26
+ /**
27
+ * How long a run's last heartbeat keeps its hold alive: the reconciler's
28
+ * heartbeat timeout plus one reconcile interval, so a managed run is reaped
29
+ * (or re-claimed, which refreshes heartbeatAt) before its hold lapses.
30
+ * Managed native runs beat every HEARTBEAT_INTERVAL_MS (InProcessExecutor,
31
+ * DbosExecutor), as do `wardby run` and native sub-agent children
32
+ * (withRunHeartbeat); a coding run beats while its job is polled.
33
+ */
34
+ export const HOLD_HEARTBEAT_TTL_MS = HEARTBEAT_TIMEOUT_MS + RECONCILE_INTERVAL_MS;
35
+ /** Slack past a coding run's queue + run timeout before its hold lapses (matches the sub-agent wait grace). */
36
+ export const CODING_HOLD_GRACE_SEC = 60;
37
+ /** Smallest amount worth holding or reserving: CodingRun.budgetReservedUsd has 6 decimal places. */
38
+ export const MIN_RESERVATION_USD = 0.000001;
39
+ /**
40
+ * Whether an in-flight row still holds budget. A run holds while its last
41
+ * sign of life (heartbeatAt, or startedAt before its first beat) is within
42
+ * HOLD_HEARTBEAT_TTL_MS. A coding run also holds, beat or not, until its
43
+ * queue timeout + run timeout + grace have passed since dispatch: a queued
44
+ * coding run does not beat, and nothing can legitimately spend past that.
45
+ */
46
+ export function isHoldLive(run, now, codingQueueTimeoutSec) {
47
+ if (!IN_FLIGHT.has(run.status))
48
+ return false;
49
+ const lastSign = (run.heartbeatAt ?? run.startedAt).getTime();
50
+ if (now.getTime() - lastSign <= HOLD_HEARTBEAT_TTL_MS)
51
+ return true;
52
+ if (run.codingRun) {
53
+ const deadlineMs = run.startedAt.getTime() + (codingQueueTimeoutSec + run.codingRun.timeoutSec + CODING_HOLD_GRACE_SEC) * 1000;
54
+ return now.getTime() <= deadlineMs;
55
+ }
56
+ return false;
57
+ }
58
+ function isOrderedBefore(run, other) {
59
+ const a = run.startedAt.getTime();
60
+ const b = other.startedAt.getTime();
61
+ return a < b || (a === b && run.id < other.id);
62
+ }
23
63
  /**
24
64
  * Live per-period spend for whichever of a group's daily/weekly/monthly
25
65
  * caps are actually set -- unset periods are omitted from the result, not
@@ -27,7 +67,7 @@ function toNullableNumber(value) {
27
67
  * (month contains week contains day), bucketed in JS, rather than a
28
68
  * separate DB round trip per period.
29
69
  */
30
- export async function computeGroupSpend(db, group, memberAgentIds, now = new Date()) {
70
+ export async function computeGroupSpend(db, group, members, now = new Date(), options = {}) {
31
71
  const caps = [
32
72
  { period: "day", capUsd: toNullableNumber(group.dailyBudgetUsd) },
33
73
  { period: "week", capUsd: toNullableNumber(group.weeklyBudgetUsd) },
@@ -42,28 +82,58 @@ export async function computeGroupSpend(db, group, memberAgentIds, now = new Dat
42
82
  // query itself as a cheap no-op; an empty `agentId: { in: [] }` would
43
83
  // match nothing anyway.
44
84
  const widestStart = periodStart("month", now);
45
- const runs = memberAgentIds.length === 0
85
+ const memberBudgets = new Map(members.map((m) => [m.id, Number(m.budgetUsd)]));
86
+ const excluded = new Set(options.excludeReservationRunIds ?? []);
87
+ const dropped = new Set(options.excludeRunIds ?? []);
88
+ const queueTimeoutSec = options.codingQueueTimeoutSec ?? loadCodingConcurrencyConfig().queueTimeoutSec;
89
+ const runs = members.length === 0
46
90
  ? []
47
91
  : await db.run.findMany({
48
- where: { agentId: { in: memberAgentIds }, startedAt: { gte: widestStart } },
49
- select: { costUsd: true, startedAt: true },
92
+ where: { agentId: { in: [...memberBudgets.keys()] }, startedAt: { gte: widestStart } },
93
+ select: {
94
+ id: true,
95
+ agentId: true,
96
+ status: true,
97
+ costUsd: true,
98
+ startedAt: true,
99
+ heartbeatAt: true,
100
+ codingRun: { select: { budgetReservedUsd: true, timeoutSec: true } },
101
+ },
50
102
  });
103
+ const counted = runs.filter((r) => !dropped.has(r.id));
104
+ const accounted = counted.map((r) => {
105
+ const costUsd = Number(r.costUsd);
106
+ let reservedUsd = 0;
107
+ const holds = !excluded.has(r.id) &&
108
+ (options.holdsBefore === undefined || isOrderedBefore(r, options.holdsBefore)) &&
109
+ isHoldLive(r, now, queueTimeoutSec);
110
+ if (holds) {
111
+ // A coding run holds exactly what dispatch reserved. A native run (or
112
+ // a coding run whose CodingRun row is missing) holds its agent's
113
+ // per-run budget: never less than what it can actually spend.
114
+ const reservation = r.codingRun != null ? Number(r.codingRun.budgetReservedUsd) : (memberBudgets.get(r.agentId) ?? 0);
115
+ reservedUsd = Math.max(0, reservation - costUsd);
116
+ }
117
+ return { startedAt: r.startedAt, costUsd, reservedUsd };
118
+ });
51
119
  return configured.map(({ period, capUsd }) => {
52
120
  const start = periodStart(period, now);
53
- const spentUsd = runs.filter((r) => r.startedAt >= start).reduce((sum, r) => sum + Number(r.costUsd), 0);
54
- return { period, capUsd, spentUsd, remainingUsd: Math.max(0, capUsd - spentUsd) };
121
+ const inPeriod = accounted.filter((r) => r.startedAt >= start);
122
+ const spentUsd = inPeriod.reduce((sum, r) => sum + r.costUsd, 0);
123
+ const reservedUsd = inPeriod.reduce((sum, r) => sum + r.reservedUsd, 0);
124
+ return { period, capUsd, spentUsd, reservedUsd, remainingUsd: Math.max(0, capUsd - spentUsd - reservedUsd) };
55
125
  });
56
126
  }
57
127
  /** Walks parentRunId upward from `runId` to the tree's root (parentRunId null). */
58
128
  async function findRootRun(db, runId) {
59
129
  let current = await db.run.findUniqueOrThrow({
60
130
  where: { id: runId },
61
- select: { id: true, agentId: true, parentRunId: true },
131
+ select: { id: true, agentId: true, parentRunId: true, startedAt: true },
62
132
  });
63
133
  while (current.parentRunId) {
64
134
  current = await db.run.findUniqueOrThrow({
65
135
  where: { id: current.parentRunId },
66
- select: { id: true, agentId: true, parentRunId: true },
136
+ select: { id: true, agentId: true, parentRunId: true, startedAt: true },
67
137
  });
68
138
  }
69
139
  return current;
@@ -83,14 +153,23 @@ async function collectTreeRunIds(db, rootRunId) {
83
153
  /**
84
154
  * Real-time shared budget scope for a sub-agent dispatch: the whole run tree
85
155
  * rooted at `parentRunId`'s ultimate ancestor shares one ceiling —
86
- * the root's own effective budget (its own budgetUsd, itself tightened by
87
- * its own BudgetGroup if any) minus everything every run in the tree has
88
- * spent so far. The root's ceiling is recomputed fresh here, not pinned
89
- * from whatever it was when the root run started — consistent with
90
- * BudgetGroup periods already always being live-recomputed rather than
91
- * snapshotted.
156
+ * the root's own budgetUsd, itself tightened by the root agent's BudgetGroup
157
+ * if any — minus everything every run in the tree has spent so far. The
158
+ * ceiling is recomputed fresh here, not pinned from whatever it was when the
159
+ * root run started — consistent with BudgetGroup periods already always
160
+ * being live-recomputed rather than snapshotted. Its group check leaves the
161
+ * tree's runs out entirely, cost and hold alike: the tree's spend is
162
+ * subtracted once, below, so counting it in the group too would charge it
163
+ * twice, and the tree spends inside the root's hold, so counting that hold
164
+ * would leave the tree nothing. It also leaves out holds of runs that started
165
+ * after the root (they already counted the root's hold). capUsd is therefore
166
+ * the tree's whole ceiling, and remainingUsd = capUsd - spentUsd.
92
167
  */
93
168
  export async function computeRunTreeSpend(db, parentRunId, now = new Date()) {
169
+ const { rootRunId, capUsd, spentUsd, remainingUsd } = await runTreeSpend(db, parentRunId, now);
170
+ return { rootRunId, capUsd, spentUsd, remainingUsd };
171
+ }
172
+ async function runTreeSpend(db, parentRunId, now) {
94
173
  const root = await findRootRun(db, parentRunId);
95
174
  const treeRunIds = await collectTreeRunIds(db, root.id);
96
175
  const rows = await db.run.findMany({ where: { id: { in: treeRunIds } }, select: { costUsd: true } });
@@ -99,11 +178,23 @@ export async function computeRunTreeSpend(db, parentRunId, now = new Date()) {
99
178
  where: { id: root.agentId },
100
179
  select: { id: true, budgetGroupId: true, budgetUsd: true },
101
180
  });
181
+ const rootOrder = { id: root.id, startedAt: root.startedAt };
102
182
  // Root has no parentRunId of its own, so this terminates in one level —
103
183
  // no unbounded recursion regardless of how deep `parentRunId` itself was.
104
- const rootCeiling = await effectiveBudgetForRun(db, rootAgent, now);
184
+ const rootCeiling = await groupCappedBudget(db, rootAgent, now, {
185
+ excludeRunIds: treeRunIds,
186
+ holdsBefore: rootOrder,
187
+ });
105
188
  const capUsd = rootCeiling.effectiveBudgetUsd;
106
- return { rootRunId: root.id, capUsd, spentUsd, remainingUsd: Math.max(0, capUsd - spentUsd) };
189
+ return {
190
+ rootRunId: root.id,
191
+ capUsd,
192
+ spentUsd,
193
+ remainingUsd: Math.max(0, capUsd - spentUsd),
194
+ treeRunIds,
195
+ root: rootOrder,
196
+ rootBudgetGroupId: rootAgent.budgetGroupId,
197
+ };
107
198
  }
108
199
  /**
109
200
  * The per-run budget ceiling to actually use for `agent`: its own
@@ -116,17 +207,35 @@ export async function computeRunTreeSpend(db, parentRunId, now = new Date()) {
116
207
  * with no caps configured is returned unchanged. Also logs a warning
117
208
  * (never blocks, never throws) once a period's spend crosses the group's
118
209
  * own warnThresholdRatio.
210
+ *
211
+ * A sub-agent in the root's own budget group spends inside the root's hold,
212
+ * so its group check is the root's: the tree's holds and the holds of runs
213
+ * after the root are left out. A sub-agent in any other group is an
214
+ * ordinary member of that group and counts every earlier live hold there,
215
+ * the tree's included.
119
216
  */
120
- export async function effectiveBudgetForRun(db, agent, now = new Date(), parentRunId) {
217
+ export async function effectiveBudgetForRun(db, agent, now = new Date(), parentRunId, options = {}) {
218
+ const self = options.self;
219
+ const ownSpend = { excludeReservationRunIds: self ? [self.id] : [], holdsBefore: self };
220
+ if (!parentRunId)
221
+ return groupCappedBudget(db, agent, now, ownSpend);
222
+ const tree = await runTreeSpend(db, parentRunId, now);
223
+ const sharesRootGroup = agent.budgetGroupId !== null && agent.budgetGroupId === tree.rootBudgetGroupId;
224
+ const spend = sharesRootGroup
225
+ ? { excludeReservationRunIds: [...tree.treeRunIds, ...(self ? [self.id] : [])], holdsBefore: tree.root }
226
+ : ownSpend;
227
+ return groupCappedBudget(db, agent, now, spend, { value: tree.remainingUsd, reason: "run-tree" });
228
+ }
229
+ async function groupCappedBudget(db, agent, now, spendOptions, extra) {
121
230
  const ownBudgetUsd = Number(agent.budgetUsd);
122
231
  const candidates = [];
123
232
  if (agent.budgetGroupId) {
124
233
  const group = await db.budgetGroup.findUnique({
125
234
  where: { id: agent.budgetGroupId },
126
- include: { agents: { select: { id: true } } },
235
+ include: { agents: { select: { id: true, budgetUsd: true } } },
127
236
  });
128
237
  if (group) {
129
- const spend = await computeGroupSpend(db, group, group.agents.map((a) => a.id), now);
238
+ const spend = await computeGroupSpend(db, group, group.agents, now, spendOptions);
130
239
  const warnThresholdRatio = Number(group.warnThresholdRatio);
131
240
  for (const s of spend) {
132
241
  candidates.push({ value: s.remainingUsd, reason: s.period });
@@ -137,13 +246,13 @@ export async function effectiveBudgetForRun(db, agent, now = new Date(), parentR
137
246
  }
138
247
  }
139
248
  }
140
- if (parentRunId) {
141
- const tree = await computeRunTreeSpend(db, parentRunId, now);
142
- candidates.push({ value: tree.remainingUsd, reason: "run-tree" });
143
- }
249
+ if (extra)
250
+ candidates.push(extra);
144
251
  if (candidates.length === 0)
145
252
  return { effectiveBudgetUsd: ownBudgetUsd, constrainedBy: [] };
146
253
  const constrainedBy = candidates.filter((c) => c.value < ownBudgetUsd).map((c) => c.reason);
147
254
  const effectiveBudgetUsd = Math.min(ownBudgetUsd, ...candidates.map((c) => c.value));
148
- return { effectiveBudgetUsd, constrainedBy };
255
+ // Less than the smallest reservable amount is nothing left.
256
+ const exhausted = candidates.find((c) => c.value < MIN_RESERVATION_USD);
257
+ return { effectiveBudgetUsd, constrainedBy, ...(exhausted ? { exhaustedBy: exhausted.reason } : {}) };
149
258
  }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The one sentence a host (a PR comment, a check summary) is told when a run
3
+ * ran out of budget, or could not start for lack of it. Public repositories
4
+ * read these, so it names amounts and the budget group only: never error
5
+ * text, token counts, or ids.
6
+ */
7
+ import type { PrismaClient } from "#prisma";
8
+ export interface BudgetFacts {
9
+ /** The run's own budget (a coding run's reservation); falls back to the agent's. */
10
+ runBudgetUsd?: number | null;
11
+ agentBudgetUsd: number;
12
+ budgetGroupName?: string | null;
13
+ /** The run was refused before it started. */
14
+ refused?: boolean;
15
+ }
16
+ export declare function budgetSentence(facts: BudgetFacts): string;
17
+ /**
18
+ * The sentence for a finished run, read from the database: a coding run's
19
+ * reserved budget, otherwise the agent's. Falls back to a sentence without
20
+ * amounts when they cannot be read. Never throws.
21
+ */
22
+ export declare function loadBudgetSentence(db: Pick<PrismaClient, "run">, runId: string, status: string): Promise<string>;