@wardby/cli 0.2.0 → 0.3.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 (491) hide show
  1. package/.env.example +34 -4
  2. package/README.md +183 -71
  3. package/dist/claude-coding-worker/driver.d.ts +7 -1
  4. package/dist/claude-coding-worker/driver.js +31 -2
  5. package/dist/claude-coding-worker/main.js +5 -2
  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 +13 -4
  10. package/dist/cli.d.ts +1 -1
  11. package/dist/cli.js +219 -29
  12. package/dist/coding/collect-exclude.d.ts +25 -0
  13. package/dist/coding/collect-exclude.js +76 -0
  14. package/dist/coding/profile.d.ts +67 -22
  15. package/dist/coding/profile.js +58 -25
  16. package/dist/coding/protected-path-wording.d.ts +24 -0
  17. package/dist/coding/protected-path-wording.js +55 -0
  18. package/dist/coding/protected-paths.d.ts +31 -0
  19. package/dist/coding/protected-paths.js +48 -0
  20. package/dist/coding/protocol.d.ts +99 -3
  21. package/dist/coding/protocol.js +155 -4
  22. package/dist/coding/registry/adapters.d.ts +2 -0
  23. package/dist/coding/registry/adapters.js +9 -0
  24. package/dist/coding/registry/allowlist.d.ts +9 -0
  25. package/dist/coding/registry/allowlist.js +28 -0
  26. package/dist/coding/registry/json-scan.d.ts +55 -0
  27. package/dist/coding/registry/json-scan.js +191 -0
  28. package/dist/coding/registry/lockfiles.d.ts +6 -0
  29. package/dist/coding/registry/lockfiles.js +89 -0
  30. package/dist/coding/registry/npm-lockfile.d.ts +9 -0
  31. package/dist/coding/registry/npm-lockfile.js +73 -0
  32. package/dist/coding/registry/npm-plan.d.ts +29 -0
  33. package/dist/coding/registry/npm-plan.js +289 -0
  34. package/dist/coding/registry/npm.d.ts +7 -0
  35. package/dist/coding/registry/npm.js +252 -0
  36. package/dist/coding/registry/pypi.d.ts +4 -0
  37. package/dist/coding/registry/pypi.js +210 -0
  38. package/dist/coding/registry/report.d.ts +37 -0
  39. package/dist/coding/registry/report.js +31 -0
  40. package/dist/coding/registry/token.d.ts +4 -0
  41. package/dist/coding/registry/token.js +7 -0
  42. package/dist/coding/registry/types.d.ts +286 -0
  43. package/dist/coding/registry/types.js +19 -0
  44. package/dist/coding/registry/worker-config.d.ts +14 -0
  45. package/dist/coding/registry/worker-config.js +31 -0
  46. package/dist/coding/services/builtins.d.ts +22 -0
  47. package/dist/coding/services/builtins.js +85 -0
  48. package/dist/coding/services/catalog.d.ts +443 -0
  49. package/dist/coding/services/catalog.js +159 -0
  50. package/dist/coding/services/declaration.d.ts +11 -0
  51. package/dist/coding/services/declaration.js +114 -0
  52. package/dist/coding/services/note.d.ts +8 -0
  53. package/dist/coding/services/note.js +15 -0
  54. package/dist/coding/services/resolve.d.ts +25 -0
  55. package/dist/coding/services/resolve.js +50 -0
  56. package/dist/coding/services/wording.d.ts +25 -0
  57. package/dist/coding/services/wording.js +70 -0
  58. package/dist/coding-proxy/main.js +24 -4
  59. package/dist/coding-worker/artifact.d.ts +18 -0
  60. package/dist/coding-worker/artifact.js +48 -6
  61. package/dist/coding-worker/debug-trace.d.ts +37 -0
  62. package/dist/coding-worker/debug-trace.js +117 -0
  63. package/dist/coding-worker/driver.d.ts +14 -1
  64. package/dist/coding-worker/driver.js +106 -14
  65. package/dist/coding-worker/errors.d.ts +7 -0
  66. package/dist/coding-worker/errors.js +26 -0
  67. package/dist/coding-worker/main.js +13 -4
  68. package/dist/coding-worker/sdk.d.ts +2 -2
  69. package/dist/coding-worker/sdk.js +7 -2
  70. package/dist/coding-worker/types.d.ts +3 -0
  71. package/dist/config/providers.d.ts +41 -0
  72. package/dist/config/providers.js +66 -0
  73. package/dist/core/budget-groups.d.ts +109 -6
  74. package/dist/core/budget-groups.js +125 -19
  75. package/dist/core/budget-wording.d.ts +22 -0
  76. package/dist/core/budget-wording.js +55 -0
  77. package/dist/core/coding-queue.d.ts +1 -1
  78. package/dist/core/datastores.d.ts +1 -1
  79. package/dist/core/datastores.js +18 -2
  80. package/dist/core/db.d.ts +40 -2
  81. package/dist/core/db.js +75 -2
  82. package/dist/core/dispatch.d.ts +49 -7
  83. package/dist/core/dispatch.js +290 -45
  84. package/dist/core/engine-native.js +27 -10
  85. package/dist/core/grants.d.ts +86 -0
  86. package/dist/core/grants.js +126 -0
  87. package/dist/core/host-events.d.ts +49 -0
  88. package/dist/core/host-events.js +257 -0
  89. package/dist/core/host-identity-links.d.ts +54 -0
  90. package/dist/core/host-identity-links.js +189 -0
  91. package/dist/core/host-status.d.ts +61 -0
  92. package/dist/core/host-status.js +211 -0
  93. package/dist/core/http-runtime.js +3 -4
  94. package/dist/core/in-flight-runs.d.ts +8 -0
  95. package/dist/core/in-flight-runs.js +56 -0
  96. package/dist/core/lease.d.ts +1 -1
  97. package/dist/core/private-directory.d.ts +20 -0
  98. package/dist/core/private-directory.js +36 -0
  99. package/dist/core/provider-wording.d.ts +12 -0
  100. package/dist/core/provider-wording.js +44 -0
  101. package/dist/core/reconciler.d.ts +39 -3
  102. package/dist/core/reconciler.js +86 -2
  103. package/dist/core/repo-access.d.ts +99 -0
  104. package/dist/core/repo-access.js +136 -0
  105. package/dist/core/review-host-checks.d.ts +11 -0
  106. package/dist/core/review-host-checks.js +41 -0
  107. package/dist/core/review-host-tools.d.ts +47 -0
  108. package/dist/core/review-host-tools.js +347 -0
  109. package/dist/core/run-heartbeat.d.ts +27 -0
  110. package/dist/core/run-heartbeat.js +54 -0
  111. package/dist/core/runner.d.ts +31 -9
  112. package/dist/core/runner.js +237 -40
  113. package/dist/core/scheduler.d.ts +1 -1
  114. package/dist/core/secrets.d.ts +12 -3
  115. package/dist/core/secrets.js +25 -6
  116. package/dist/core/subagent-memory-tools.d.ts +2 -2
  117. package/dist/core/subagent-memory-tools.js +16 -3
  118. package/dist/core/tool-admin.d.ts +81 -0
  119. package/dist/core/tool-admin.js +129 -0
  120. package/dist/core/tool-names.d.ts +42 -0
  121. package/dist/core/tool-names.js +64 -0
  122. package/dist/core/untrusted-content.d.ts +32 -0
  123. package/dist/core/untrusted-content.js +72 -0
  124. package/dist/core/webhooks.d.ts +9 -2
  125. package/dist/core/webhooks.js +23 -2
  126. package/dist/generated/prisma/browser.d.ts +328 -0
  127. package/dist/generated/prisma/browser.js +17 -0
  128. package/dist/generated/prisma/client.d.ts +347 -0
  129. package/dist/generated/prisma/client.js +34 -0
  130. package/dist/generated/prisma/commonInputTypes.d.ts +839 -0
  131. package/dist/generated/prisma/commonInputTypes.js +10 -0
  132. package/dist/generated/prisma/enums.d.ts +59 -0
  133. package/dist/generated/prisma/enums.js +59 -0
  134. package/dist/generated/prisma/internal/class.d.ts +605 -0
  135. package/dist/generated/prisma/internal/class.js +49 -0
  136. package/dist/generated/prisma/internal/prismaNamespace.d.ts +4494 -0
  137. package/dist/generated/prisma/internal/prismaNamespace.js +628 -0
  138. package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +635 -0
  139. package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +599 -0
  140. package/dist/generated/prisma/models/Agent.d.ts +3560 -0
  141. package/dist/generated/prisma/models/Agent.js +1 -0
  142. package/dist/generated/prisma/models/AgentDatastore.d.ts +1210 -0
  143. package/dist/generated/prisma/models/AgentDatastore.js +1 -0
  144. package/dist/generated/prisma/models/AgentMemory.d.ts +986 -0
  145. package/dist/generated/prisma/models/AgentMemory.js +1 -0
  146. package/dist/generated/prisma/models/AgentRepository.d.ts +1425 -0
  147. package/dist/generated/prisma/models/AgentRepository.js +1 -0
  148. package/dist/generated/prisma/models/AgentSecret.d.ts +1212 -0
  149. package/dist/generated/prisma/models/AgentSecret.js +1 -0
  150. package/dist/generated/prisma/models/AgentSubAgent.d.ts +1217 -0
  151. package/dist/generated/prisma/models/AgentSubAgent.js +1 -0
  152. package/dist/generated/prisma/models/AgentTool.d.ts +1403 -0
  153. package/dist/generated/prisma/models/AgentTool.js +1 -0
  154. package/dist/generated/prisma/models/AuthFormChallenge.d.ts +1298 -0
  155. package/dist/generated/prisma/models/AuthFormChallenge.js +1 -0
  156. package/dist/generated/prisma/models/AuthLoginKey.d.ts +1245 -0
  157. package/dist/generated/prisma/models/AuthLoginKey.js +1 -0
  158. package/dist/generated/prisma/models/AuthRateLimit.d.ts +979 -0
  159. package/dist/generated/prisma/models/AuthRateLimit.js +1 -0
  160. package/dist/generated/prisma/models/AuthSession.d.ts +1376 -0
  161. package/dist/generated/prisma/models/AuthSession.js +1 -0
  162. package/dist/generated/prisma/models/AuthUser.d.ts +1700 -0
  163. package/dist/generated/prisma/models/AuthUser.js +1 -0
  164. package/dist/generated/prisma/models/BudgetGroup.d.ts +1548 -0
  165. package/dist/generated/prisma/models/BudgetGroup.js +1 -0
  166. package/dist/generated/prisma/models/CodingAgentProfile.d.ts +1681 -0
  167. package/dist/generated/prisma/models/CodingAgentProfile.js +1 -0
  168. package/dist/generated/prisma/models/CodingProxyRequest.d.ts +1749 -0
  169. package/dist/generated/prisma/models/CodingProxyRequest.js +1 -0
  170. package/dist/generated/prisma/models/CodingProxySession.d.ts +1626 -0
  171. package/dist/generated/prisma/models/CodingProxySession.js +1 -0
  172. package/dist/generated/prisma/models/CodingRun.d.ts +3583 -0
  173. package/dist/generated/prisma/models/CodingRun.js +1 -0
  174. package/dist/generated/prisma/models/CodingService.d.ts +1348 -0
  175. package/dist/generated/prisma/models/CodingService.js +1 -0
  176. package/dist/generated/prisma/models/Datastore.d.ts +1434 -0
  177. package/dist/generated/prisma/models/Datastore.js +1 -0
  178. package/dist/generated/prisma/models/DatastoreEntry.d.ts +1314 -0
  179. package/dist/generated/prisma/models/DatastoreEntry.js +1 -0
  180. package/dist/generated/prisma/models/HostEventDelivery.d.ts +946 -0
  181. package/dist/generated/prisma/models/HostEventDelivery.js +1 -0
  182. package/dist/generated/prisma/models/HostIdentity.d.ts +1232 -0
  183. package/dist/generated/prisma/models/HostIdentity.js +1 -0
  184. package/dist/generated/prisma/models/HostIdentityLinkRequest.d.ts +1473 -0
  185. package/dist/generated/prisma/models/HostIdentityLinkRequest.js +1 -0
  186. package/dist/generated/prisma/models/OAuthAuthorizationCode.d.ts +1526 -0
  187. package/dist/generated/prisma/models/OAuthAuthorizationCode.js +1 -0
  188. package/dist/generated/prisma/models/OAuthAuthorizationRequest.d.ts +1350 -0
  189. package/dist/generated/prisma/models/OAuthAuthorizationRequest.js +1 -0
  190. package/dist/generated/prisma/models/OAuthClient.d.ts +1289 -0
  191. package/dist/generated/prisma/models/OAuthClient.js +1 -0
  192. package/dist/generated/prisma/models/OAuthFamily.d.ts +1540 -0
  193. package/dist/generated/prisma/models/OAuthFamily.js +1 -0
  194. package/dist/generated/prisma/models/OAuthGrant.d.ts +1280 -0
  195. package/dist/generated/prisma/models/OAuthGrant.js +1 -0
  196. package/dist/generated/prisma/models/Principal.d.ts +2245 -0
  197. package/dist/generated/prisma/models/Principal.js +1 -0
  198. package/dist/generated/prisma/models/RegistryAllowance.d.ts +1148 -0
  199. package/dist/generated/prisma/models/RegistryAllowance.js +1 -0
  200. package/dist/generated/prisma/models/RegistryApprovedVersion.d.ts +1219 -0
  201. package/dist/generated/prisma/models/RegistryApprovedVersion.js +1 -0
  202. package/dist/generated/prisma/models/RegistryFetch.d.ts +1428 -0
  203. package/dist/generated/prisma/models/RegistryFetch.js +1 -0
  204. package/dist/generated/prisma/models/RegistryPlanRefusal.d.ts +1294 -0
  205. package/dist/generated/prisma/models/RegistryPlanRefusal.js +1 -0
  206. package/dist/generated/prisma/models/RegistryVersionFact.d.ts +1085 -0
  207. package/dist/generated/prisma/models/RegistryVersionFact.js +1 -0
  208. package/dist/generated/prisma/models/ResourceGrant.d.ts +1437 -0
  209. package/dist/generated/prisma/models/ResourceGrant.js +1 -0
  210. package/dist/generated/prisma/models/Run.d.ts +2669 -0
  211. package/dist/generated/prisma/models/Run.js +1 -0
  212. package/dist/generated/prisma/models/RunHostCheck.d.ts +1239 -0
  213. package/dist/generated/prisma/models/RunHostCheck.js +1 -0
  214. package/dist/generated/prisma/models/RunHostStatus.d.ts +1315 -0
  215. package/dist/generated/prisma/models/RunHostStatus.js +1 -0
  216. package/dist/generated/prisma/models/SchedulerLease.d.ts +970 -0
  217. package/dist/generated/prisma/models/SchedulerLease.js +1 -0
  218. package/dist/generated/prisma/models/Secret.d.ts +1399 -0
  219. package/dist/generated/prisma/models/Secret.js +1 -0
  220. package/dist/generated/prisma/models/SecretElicitationOutcome.d.ts +973 -0
  221. package/dist/generated/prisma/models/SecretElicitationOutcome.js +1 -0
  222. package/dist/generated/prisma/models/Task.d.ts +1176 -0
  223. package/dist/generated/prisma/models/Task.js +1 -0
  224. package/dist/generated/prisma/models/Tool.d.ts +1490 -0
  225. package/dist/generated/prisma/models/Tool.js +1 -0
  226. package/dist/generated/prisma/models/Webhook.d.ts +1386 -0
  227. package/dist/generated/prisma/models/Webhook.js +1 -0
  228. package/dist/generated/prisma/models.d.ts +45 -0
  229. package/dist/generated/prisma/models.js +1 -0
  230. package/dist/help/build.d.ts +1 -0
  231. package/dist/help/build.js +9 -0
  232. package/dist/help/catalog.d.ts +24 -0
  233. package/dist/help/catalog.js +160 -0
  234. package/dist/help/cli.d.ts +2 -0
  235. package/dist/help/cli.js +65 -0
  236. package/dist/help/runtime.d.ts +3 -0
  237. package/dist/help/runtime.js +44 -0
  238. package/dist/help/search.d.ts +9 -0
  239. package/dist/help/search.js +104 -0
  240. package/dist/help-index.json +660 -0
  241. package/dist/import/cli-args.js +3 -2
  242. package/dist/import/create.d.ts +6 -1
  243. package/dist/import/create.js +68 -14
  244. package/dist/import/index.d.ts +1 -1
  245. package/dist/import/index.js +19 -9
  246. package/dist/import/neutral-schema.d.ts +11 -11
  247. package/dist/mcp/auth/access.d.ts +70 -0
  248. package/dist/mcp/auth/access.js +58 -0
  249. package/dist/mcp/auth/grants-cli.d.ts +149 -0
  250. package/dist/mcp/auth/grants-cli.js +518 -0
  251. package/dist/mcp/auth/host-account-cli.d.ts +2 -0
  252. package/dist/mcp/auth/host-account-cli.js +47 -0
  253. package/dist/mcp/auth/ownership.d.ts +37 -63
  254. package/dist/mcp/auth/ownership.js +19 -14
  255. package/dist/mcp/auth/principal.d.ts +1 -1
  256. package/dist/mcp/auth/repo-authorization.d.ts +22 -0
  257. package/dist/mcp/auth/repo-authorization.js +51 -0
  258. package/dist/mcp/auth/resource-server.d.ts +32 -3
  259. package/dist/mcp/auth/resource-server.js +84 -3
  260. package/dist/mcp/auth/self-hosted/browser.js +22 -8
  261. package/dist/mcp/auth/self-hosted/cli.d.ts +1 -1
  262. package/dist/mcp/auth/self-hosted/cli.js +47 -13
  263. package/dist/mcp/auth/self-hosted/credentials.d.ts +21 -3
  264. package/dist/mcp/auth/self-hosted/credentials.js +62 -4
  265. package/dist/mcp/auth/self-hosted/rate-limit.d.ts +1 -1
  266. package/dist/mcp/auth/self-hosted/session.d.ts +3 -2
  267. package/dist/mcp/context.d.ts +28 -2
  268. package/dist/mcp/errors.d.ts +25 -6
  269. package/dist/mcp/errors.js +98 -0
  270. package/dist/mcp/host-events/github-ingress.d.ts +36 -0
  271. package/dist/mcp/host-events/github-ingress.js +92 -0
  272. package/dist/mcp/host-events/github-user-callback.d.ts +18 -0
  273. package/dist/mcp/host-events/github-user-callback.js +50 -0
  274. package/dist/mcp/index.d.ts +8 -1
  275. package/dist/mcp/index.js +106 -16
  276. package/dist/mcp/server.d.ts +1 -1
  277. package/dist/mcp/server.js +27 -9
  278. package/dist/mcp/shared/page-style.d.ts +16 -0
  279. package/dist/mcp/shared/page-style.js +129 -0
  280. package/dist/mcp/tasks/manager.d.ts +1 -1
  281. package/dist/mcp/tools/agents.js +447 -49
  282. package/dist/mcp/tools/budget-groups.js +3 -3
  283. package/dist/mcp/tools/datastore.js +23 -16
  284. package/dist/mcp/tools/grants.d.ts +2 -0
  285. package/dist/mcp/tools/grants.js +239 -0
  286. package/dist/mcp/tools/help.d.ts +5 -0
  287. package/dist/mcp/tools/help.js +67 -0
  288. package/dist/mcp/tools/host-accounts.d.ts +2 -0
  289. package/dist/mcp/tools/host-accounts.js +114 -0
  290. package/dist/mcp/tools/memory.d.ts +7 -1
  291. package/dist/mcp/tools/memory.js +5 -5
  292. package/dist/mcp/tools/repositories.d.ts +2 -0
  293. package/dist/mcp/tools/repositories.js +181 -0
  294. package/dist/mcp/tools/runs.d.ts +6 -0
  295. package/dist/mcp/tools/runs.js +60 -6
  296. package/dist/mcp/tools/scheduling.js +4 -7
  297. package/dist/mcp/tools/secret-elicitation-form.d.ts +1 -1
  298. package/dist/mcp/tools/secret-elicitation-form.js +10 -28
  299. package/dist/mcp/tools/secret-elicitation.d.ts +1 -1
  300. package/dist/mcp/tools/secrets.js +18 -6
  301. package/dist/mcp/tools/services.d.ts +2 -0
  302. package/dist/mcp/tools/services.js +222 -0
  303. package/dist/mcp/tools/subagents.js +53 -17
  304. package/dist/mcp/tools/tools.d.ts +41 -0
  305. package/dist/mcp/tools/tools.js +202 -41
  306. package/dist/mcp/tools/trigger.js +36 -6
  307. package/dist/mcp/tools/webhooks.js +15 -4
  308. package/dist/mcp/transport/streamable-http.d.ts +11 -1
  309. package/dist/mcp/transport/streamable-http.js +32 -2
  310. package/dist/mcp/unattended-schedules.d.ts +1 -1
  311. package/dist/mcp/webhooks/ingress.d.ts +1 -1
  312. package/dist/providers/auth/delegating.d.ts +19 -0
  313. package/dist/providers/auth/delegating.js +71 -0
  314. package/dist/providers/auth/index.d.ts +3 -1
  315. package/dist/providers/auth/index.js +11 -0
  316. package/dist/providers/auth/self-hosted.d.ts +4 -3
  317. package/dist/providers/auth/self-hosted.js +16 -4
  318. package/dist/providers/auth/types.d.ts +6 -0
  319. package/dist/providers/coding-proxy/memory-ledger.d.ts +3 -0
  320. package/dist/providers/coding-proxy/memory-ledger.js +21 -2
  321. package/dist/providers/coding-proxy/metering.d.ts +4 -0
  322. package/dist/providers/coding-proxy/metering.js +28 -2
  323. package/dist/providers/coding-proxy/prisma-ledger.d.ts +18 -1
  324. package/dist/providers/coding-proxy/prisma-ledger.js +50 -5
  325. package/dist/providers/coding-proxy/proxy.d.ts +13 -0
  326. package/dist/providers/coding-proxy/proxy.js +479 -18
  327. package/dist/providers/coding-proxy/registry/audit.d.ts +131 -0
  328. package/dist/providers/coding-proxy/registry/audit.js +380 -0
  329. package/dist/providers/coding-proxy/registry/bounded-fetch.d.ts +16 -0
  330. package/dist/providers/coding-proxy/registry/bounded-fetch.js +77 -0
  331. package/dist/providers/coding-proxy/registry/plan.d.ts +58 -0
  332. package/dist/providers/coding-proxy/registry/plan.js +304 -0
  333. package/dist/providers/coding-proxy/registry/prisma-store.d.ts +34 -0
  334. package/dist/providers/coding-proxy/registry/prisma-store.js +151 -0
  335. package/dist/providers/coding-proxy/registry/service.d.ts +213 -0
  336. package/dist/providers/coding-proxy/registry/service.js +1137 -0
  337. package/dist/providers/coding-proxy/registry/store.d.ts +127 -0
  338. package/dist/providers/coding-proxy/registry/store.js +70 -0
  339. package/dist/providers/coding-proxy/runtime.d.ts +18 -1
  340. package/dist/providers/coding-proxy/runtime.js +63 -0
  341. package/dist/providers/coding-proxy/secure-fetch.js +0 -1
  342. package/dist/providers/coding-proxy/server.d.ts +11 -0
  343. package/dist/providers/coding-proxy/server.js +163 -0
  344. package/dist/providers/coding-proxy/types.d.ts +15 -0
  345. package/dist/providers/datastore/postgres.d.ts +1 -1
  346. package/dist/providers/engine/types.d.ts +10 -0
  347. package/dist/providers/executor/build.d.ts +3 -3
  348. package/dist/providers/executor/composition.d.ts +4 -1
  349. package/dist/providers/executor/composition.js +19 -0
  350. package/dist/providers/executor/container.d.ts +106 -4
  351. package/dist/providers/executor/container.js +305 -22
  352. package/dist/providers/executor/dbos.d.ts +3 -4
  353. package/dist/providers/executor/in-process.d.ts +2 -3
  354. package/dist/providers/executor/routing.d.ts +7 -0
  355. package/dist/providers/executor/routing.js +13 -0
  356. package/dist/providers/executor/types.d.ts +22 -0
  357. package/dist/providers/jobs/claude-tool-setup.d.ts +23 -0
  358. package/dist/providers/jobs/claude-tool-setup.js +50 -0
  359. package/dist/providers/jobs/collect-prune.d.ts +7 -0
  360. package/dist/providers/jobs/collect-prune.js +27 -0
  361. package/dist/providers/jobs/docker-isolation.d.ts +48 -3
  362. package/dist/providers/jobs/docker-isolation.js +213 -22
  363. package/dist/providers/jobs/docker-services.d.ts +35 -0
  364. package/dist/providers/jobs/docker-services.js +191 -0
  365. package/dist/providers/jobs/docker.d.ts +60 -2
  366. package/dist/providers/jobs/docker.js +313 -50
  367. package/dist/providers/jobs/fake-kubernetes-api.d.ts +1 -0
  368. package/dist/providers/jobs/fake-kubernetes-api.js +12 -3
  369. package/dist/providers/jobs/kubernetes-isolation.d.ts +17 -2
  370. package/dist/providers/jobs/kubernetes-isolation.js +238 -57
  371. package/dist/providers/jobs/kubernetes-platform.d.ts +10 -4
  372. package/dist/providers/jobs/kubernetes-platform.js +11 -5
  373. package/dist/providers/jobs/kubernetes-preflight.js +3 -0
  374. package/dist/providers/jobs/kubernetes.d.ts +21 -2
  375. package/dist/providers/jobs/kubernetes.js +117 -31
  376. package/dist/providers/jobs/types.d.ts +20 -0
  377. package/dist/providers/llm/anthropic.js +6 -2
  378. package/dist/providers/llm/bedrock.js +2 -1
  379. package/dist/providers/llm/claude-messages.d.ts +5 -1
  380. package/dist/providers/llm/claude-messages.js +1 -0
  381. package/dist/providers/llm/claude-provider.d.ts +3 -1
  382. package/dist/providers/llm/claude-provider.js +5 -1
  383. package/dist/providers/llm/index.d.ts +1 -1
  384. package/dist/providers/llm/index.js +1 -1
  385. package/dist/providers/llm/pricing-anthropic.d.ts +10 -0
  386. package/dist/providers/llm/pricing-anthropic.js +32 -14
  387. package/dist/providers/llm/pricing-bedrock-claude.d.ts +8 -0
  388. package/dist/providers/llm/pricing-bedrock-claude.js +9 -0
  389. package/dist/providers/llm/routing.d.ts +10 -1
  390. package/dist/providers/llm/routing.js +18 -0
  391. package/dist/providers/llm/types.d.ts +12 -0
  392. package/dist/providers/llm/types.js +8 -1
  393. package/dist/providers/memory/postgres.d.ts +1 -1
  394. package/dist/providers/review-host/diff-lines.d.ts +16 -0
  395. package/dist/providers/review-host/diff-lines.js +59 -0
  396. package/dist/providers/review-host/github-events.d.ts +6 -0
  397. package/dist/providers/review-host/github-events.js +180 -0
  398. package/dist/providers/review-host/github-user-auth.d.ts +38 -0
  399. package/dist/providers/review-host/github-user-auth.js +128 -0
  400. package/dist/providers/review-host/github.d.ts +57 -0
  401. package/dist/providers/review-host/github.js +567 -0
  402. package/dist/providers/review-host/index.d.ts +12 -0
  403. package/dist/providers/review-host/index.js +30 -0
  404. package/dist/providers/review-host/review-format.d.ts +19 -0
  405. package/dist/providers/review-host/review-format.js +59 -0
  406. package/dist/providers/review-host/types.d.ts +284 -0
  407. package/dist/providers/review-host/types.js +24 -0
  408. package/dist/providers/vcs/git.d.ts +16 -6
  409. package/dist/providers/vcs/git.js +79 -14
  410. package/dist/providers/vcs/github.d.ts +74 -3
  411. package/dist/providers/vcs/github.js +195 -15
  412. package/dist/providers/vcs/types.d.ts +42 -4
  413. package/dist/quickstart/index.js +34 -32
  414. package/dist/quickstart/migrate.d.ts +23 -0
  415. package/dist/quickstart/migrate.js +125 -0
  416. package/dist/sandbox/fetch-policy.d.ts +20 -2
  417. package/dist/sandbox/fetch-policy.js +64 -3
  418. package/dist/sandbox/host-functions.d.ts +9 -1
  419. package/dist/sandbox/host-functions.js +12 -5
  420. package/dist/serve.js +1 -1
  421. package/dist/wardby-bin.js +6 -0
  422. package/docs/README.md +30 -0
  423. package/docs/architecture-runtime.md +90 -0
  424. package/docs/assets/brand/wardby-icon-512.png +0 -0
  425. package/docs/assets/brand/wardby-icon.svg +16 -0
  426. package/docs/assets/brand/wardby-mascot-profile-512.png +0 -0
  427. package/docs/assets/brand/wardby-mascot.png +0 -0
  428. package/docs/assets/brand/wardby-mascot.svg +5 -0
  429. package/docs/assets/wardby-workflow.svg +106 -0
  430. package/docs/code-review-agents.md +481 -0
  431. package/docs/coding-agent-setup.md +172 -0
  432. package/docs/coding-packages.md +455 -0
  433. package/docs/coding-services.md +300 -0
  434. package/docs/coding-worker-byo-images.md +98 -0
  435. package/docs/coding-worker-isolation.md +1061 -0
  436. package/docs/getting-started-gke.md +615 -0
  437. package/docs/getting-started-identity-provider.md +308 -0
  438. package/docs/getting-started.md +133 -0
  439. package/docs/observability.md +53 -0
  440. package/docs/release-verification.md +66 -0
  441. package/docs/security-deployment.md +657 -0
  442. package/help/code-review-agents.md +31 -0
  443. package/help/coding-packages.md +31 -0
  444. package/help/coding-services.md +71 -0
  445. package/help/creating-agents.md +68 -0
  446. package/help/deploy-gke.md +39 -0
  447. package/help/deployment-targets.md +39 -0
  448. package/help/errors/budget-group-exhausted.md +25 -0
  449. package/help/errors/docker-isolation-unsupported.md +26 -0
  450. package/help/errors/protected-path.md +45 -0
  451. package/help/errors/repo-access.md +27 -0
  452. package/help/errors/service-declaration-invalid.md +27 -0
  453. package/help/errors/service-declaration-unavailable.md +25 -0
  454. package/help/errors/service-launcher-unsupported.md +30 -0
  455. package/help/errors/service-not-allowed.md +27 -0
  456. package/help/errors/service-unknown.md +23 -0
  457. package/help/errors/service-unready.md +49 -0
  458. package/help/getting-started.md +31 -0
  459. package/help/github.md +30 -0
  460. package/help/identity-and-access.md +39 -0
  461. package/help/mcp.md +30 -0
  462. package/help/native-capabilities.md +30 -0
  463. package/help/observability.md +41 -0
  464. package/help/operating-agents.md +30 -0
  465. package/help/security.md +27 -0
  466. package/help/troubleshooting/budgets.md +26 -0
  467. package/help/troubleshooting/coding-workers.md +47 -0
  468. package/help/troubleshooting/repository-access.md +27 -0
  469. package/package.json +30 -11
  470. package/prisma/migrate.config.mjs +20 -0
  471. package/prisma/migrations/20260925010000_coding_collect_exclude/migration.sql +8 -0
  472. package/prisma/migrations/20260925015000_allowed_egress_default/migration.sql +8 -0
  473. package/prisma/migrations/20260925020000_coding_package_registry/migration.sql +55 -0
  474. package/prisma/migrations/20260925030000_tool_name_per_owner/migration.sql +11 -0
  475. package/prisma/migrations/20260926010000_run_trigger_host_event/migration.sql +7 -0
  476. package/prisma/migrations/20260926020000_code_review_hosts/migration.sql +53 -0
  477. package/prisma/migrations/20260926030000_auth_user_roles/migration.sql +9 -0
  478. package/prisma/migrations/20260926040000_agent_effort/migration.sql +6 -0
  479. package/prisma/migrations/20260926050000_registry_lockfile_plan/migration.sql +32 -0
  480. package/prisma/migrations/20260926050000_repo_access_authorization/migration.sql +82 -0
  481. package/prisma/migrations/20260926060000_registry_plan_refusal/migration.sql +19 -0
  482. package/prisma/migrations/20260926100000_registry_plan_refusal_published_at/migration.sql +5 -0
  483. package/prisma/migrations/20260926190000_run_host_status/migration.sql +18 -0
  484. package/prisma/migrations/20260926210000_run_host_status_at_dispatch/migration.sql +5 -0
  485. package/prisma/migrations/20260927010000_resource_grants/migration.sql +72 -0
  486. package/prisma/migrations/20260927020000_proxy_session_budget_exhausted/migration.sql +3 -0
  487. package/prisma/migrations/20260927030000_coding_debug_trace/migration.sql +4 -0
  488. package/prisma/migrations/20260927040000_proxy_session_upstream_failure/migration.sql +3 -0
  489. package/prisma/migrations/20260927050000_coding_run_services/migration.sql +74 -0
  490. package/prisma/migrations/20260928000000_coding_run_tool_image/migration.sql +5 -0
  491. package/prisma/schema.prisma +417 -23
@@ -10,9 +10,30 @@
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
- import type { Agent, BudgetGroup, PrismaClient } from "@prisma/client";
36
+ import type { Agent, BudgetGroup, PrismaClient } from "#prisma";
16
37
  export type Period = "day" | "week" | "month";
17
38
  export type BudgetGroupsDb = Pick<PrismaClient, "budgetGroup" | "run" | "agent">;
18
39
  /**
@@ -24,9 +45,69 @@ 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
+ * First come, first served: count only the holds of runs ordered strictly
74
+ * before this one. Unset (a coding dispatch, whose row does not exist yet)
75
+ * counts every live hold.
76
+ */
77
+ holdsBefore?: RunOrder;
78
+ /** CODING_QUEUE_TIMEOUT_SEC; read from the environment when omitted. */
79
+ codingQueueTimeoutSec?: number;
80
+ }
81
+ /**
82
+ * How long a run's last heartbeat keeps its hold alive: the reconciler's
83
+ * heartbeat timeout plus one reconcile interval, so a managed run is reaped
84
+ * (or re-claimed, which refreshes heartbeatAt) before its hold lapses.
85
+ * Managed native runs beat every HEARTBEAT_INTERVAL_MS (InProcessExecutor,
86
+ * DbosExecutor), as do `wardby run` and native sub-agent children
87
+ * (withRunHeartbeat); a coding run beats while its job is polled.
88
+ */
89
+ export declare const HOLD_HEARTBEAT_TTL_MS: number;
90
+ /** Slack past a coding run's queue + run timeout before its hold lapses (matches the sub-agent wait grace). */
91
+ export declare const CODING_HOLD_GRACE_SEC = 60;
92
+ /** Smallest amount worth holding or reserving: CodingRun.budgetReservedUsd has 6 decimal places. */
93
+ export declare const MIN_RESERVATION_USD = 0.000001;
94
+ interface HoldRow {
95
+ id: string;
96
+ status: string;
97
+ startedAt: Date;
98
+ heartbeatAt: Date | null;
99
+ codingRun: {
100
+ timeoutSec: number;
101
+ } | null;
102
+ }
103
+ /**
104
+ * Whether an in-flight row still holds budget. A run holds while its last
105
+ * sign of life (heartbeatAt, or startedAt before its first beat) is within
106
+ * HOLD_HEARTBEAT_TTL_MS. A coding run also holds, beat or not, until its
107
+ * queue timeout + run timeout + grace have passed since dispatch: a queued
108
+ * coding run does not beat, and nothing can legitimately spend past that.
109
+ */
110
+ export declare function isHoldLive(run: HoldRow, now: Date, codingQueueTimeoutSec: number): boolean;
30
111
  /**
31
112
  * Live per-period spend for whichever of a group's daily/weekly/monthly
32
113
  * caps are actually set -- unset periods are omitted from the result, not
@@ -34,7 +115,7 @@ export interface PeriodSpend {
34
115
  * (month contains week contains day), bucketed in JS, rather than a
35
116
  * separate DB round trip per period.
36
117
  */
37
- export declare function computeGroupSpend(db: BudgetGroupsDb, group: Pick<BudgetGroup, "dailyBudgetUsd" | "weeklyBudgetUsd" | "monthlyBudgetUsd">, memberAgentIds: string[], now?: Date): Promise<PeriodSpend[]>;
118
+ export declare function computeGroupSpend(db: BudgetGroupsDb, group: Pick<BudgetGroup, "dailyBudgetUsd" | "weeklyBudgetUsd" | "monthlyBudgetUsd">, members: readonly GroupMember[], now?: Date, options?: SpendOptions): Promise<PeriodSpend[]>;
38
119
  export interface RunTreeSpend {
39
120
  rootRunId: string;
40
121
  capUsd: number;
@@ -49,13 +130,28 @@ export interface RunTreeSpend {
49
130
  * spent so far. The root's ceiling is recomputed fresh here, not pinned
50
131
  * from whatever it was when the root run started — consistent with
51
132
  * BudgetGroup periods already always being live-recomputed rather than
52
- * snapshotted.
133
+ * snapshotted. That group check sees the group as the root did when it
134
+ * loaded: the tree's own holds are left out (the tree spends inside the
135
+ * root's hold, so counting it again would leave the tree nothing), and so
136
+ * are holds of runs that started after the root (they already counted the
137
+ * root's hold).
53
138
  */
54
139
  export declare function computeRunTreeSpend(db: BudgetGroupsDb, parentRunId: string, now?: Date): Promise<RunTreeSpend>;
140
+ export type BudgetConstraint = Period | "run-tree";
55
141
  export interface EffectiveBudgetResult {
56
142
  effectiveBudgetUsd: number;
57
143
  /** Which constraint(s), if any, are tighter than the agent's own budgetUsd right now. */
58
- constrainedBy: (Period | "run-tree")[];
144
+ constrainedBy: BudgetConstraint[];
145
+ /** The first constraint with nothing left (< MIN_RESERVATION_USD), set only when there is one. */
146
+ exhaustedBy?: BudgetConstraint;
147
+ }
148
+ export interface EffectiveBudgetOptions {
149
+ /**
150
+ * The run this budget is for, when its row already exists (a native run's
151
+ * load step): its own hold is not counted against itself, and neither are
152
+ * the holds of runs that started after it (first come, first served).
153
+ */
154
+ self?: RunOrder;
59
155
  }
60
156
  /**
61
157
  * The per-run budget ceiling to actually use for `agent`: its own
@@ -68,5 +164,12 @@ export interface EffectiveBudgetResult {
68
164
  * with no caps configured is returned unchanged. Also logs a warning
69
165
  * (never blocks, never throws) once a period's spend crosses the group's
70
166
  * own warnThresholdRatio.
167
+ *
168
+ * A sub-agent in the root's own budget group spends inside the root's hold,
169
+ * so its group check is the root's: the tree's holds and the holds of runs
170
+ * after the root are left out. A sub-agent in any other group is an
171
+ * ordinary member of that group and counts every earlier live hold there,
172
+ * the tree's included.
71
173
  */
72
- export declare function effectiveBudgetForRun(db: BudgetGroupsDb, agent: Pick<Agent, "id" | "budgetGroupId" | "budgetUsd">, now?: Date, parentRunId?: string): Promise<EffectiveBudgetResult>;
174
+ export declare function effectiveBudgetForRun(db: BudgetGroupsDb, agent: Pick<Agent, "id" | "budgetGroupId" | "budgetUsd">, now?: Date, parentRunId?: string, options?: EffectiveBudgetOptions): Promise<EffectiveBudgetResult>;
175
+ 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,56 @@ 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 queueTimeoutSec = options.codingQueueTimeoutSec ?? loadCodingConcurrencyConfig().queueTimeoutSec;
88
+ const runs = members.length === 0
46
89
  ? []
47
90
  : await db.run.findMany({
48
- where: { agentId: { in: memberAgentIds }, startedAt: { gte: widestStart } },
49
- select: { costUsd: true, startedAt: true },
91
+ where: { agentId: { in: [...memberBudgets.keys()] }, startedAt: { gte: widestStart } },
92
+ select: {
93
+ id: true,
94
+ agentId: true,
95
+ status: true,
96
+ costUsd: true,
97
+ startedAt: true,
98
+ heartbeatAt: true,
99
+ codingRun: { select: { budgetReservedUsd: true, timeoutSec: true } },
100
+ },
50
101
  });
102
+ const accounted = runs.map((r) => {
103
+ const costUsd = Number(r.costUsd);
104
+ let reservedUsd = 0;
105
+ const holds = !excluded.has(r.id) &&
106
+ (options.holdsBefore === undefined || isOrderedBefore(r, options.holdsBefore)) &&
107
+ isHoldLive(r, now, queueTimeoutSec);
108
+ if (holds) {
109
+ // A coding run holds exactly what dispatch reserved. A native run (or
110
+ // a coding run whose CodingRun row is missing) holds its agent's
111
+ // per-run budget: never less than what it can actually spend.
112
+ const reservation = r.codingRun != null ? Number(r.codingRun.budgetReservedUsd) : (memberBudgets.get(r.agentId) ?? 0);
113
+ reservedUsd = Math.max(0, reservation - costUsd);
114
+ }
115
+ return { startedAt: r.startedAt, costUsd, reservedUsd };
116
+ });
51
117
  return configured.map(({ period, capUsd }) => {
52
118
  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) };
119
+ const inPeriod = accounted.filter((r) => r.startedAt >= start);
120
+ const spentUsd = inPeriod.reduce((sum, r) => sum + r.costUsd, 0);
121
+ const reservedUsd = inPeriod.reduce((sum, r) => sum + r.reservedUsd, 0);
122
+ return { period, capUsd, spentUsd, reservedUsd, remainingUsd: Math.max(0, capUsd - spentUsd - reservedUsd) };
55
123
  });
56
124
  }
57
125
  /** Walks parentRunId upward from `runId` to the tree's root (parentRunId null). */
58
126
  async function findRootRun(db, runId) {
59
127
  let current = await db.run.findUniqueOrThrow({
60
128
  where: { id: runId },
61
- select: { id: true, agentId: true, parentRunId: true },
129
+ select: { id: true, agentId: true, parentRunId: true, startedAt: true },
62
130
  });
63
131
  while (current.parentRunId) {
64
132
  current = await db.run.findUniqueOrThrow({
65
133
  where: { id: current.parentRunId },
66
- select: { id: true, agentId: true, parentRunId: true },
134
+ select: { id: true, agentId: true, parentRunId: true, startedAt: true },
67
135
  });
68
136
  }
69
137
  return current;
@@ -88,9 +156,17 @@ async function collectTreeRunIds(db, rootRunId) {
88
156
  * spent so far. The root's ceiling is recomputed fresh here, not pinned
89
157
  * from whatever it was when the root run started — consistent with
90
158
  * BudgetGroup periods already always being live-recomputed rather than
91
- * snapshotted.
159
+ * snapshotted. That group check sees the group as the root did when it
160
+ * loaded: the tree's own holds are left out (the tree spends inside the
161
+ * root's hold, so counting it again would leave the tree nothing), and so
162
+ * are holds of runs that started after the root (they already counted the
163
+ * root's hold).
92
164
  */
93
165
  export async function computeRunTreeSpend(db, parentRunId, now = new Date()) {
166
+ const { rootRunId, capUsd, spentUsd, remainingUsd } = await runTreeSpend(db, parentRunId, now);
167
+ return { rootRunId, capUsd, spentUsd, remainingUsd };
168
+ }
169
+ async function runTreeSpend(db, parentRunId, now) {
94
170
  const root = await findRootRun(db, parentRunId);
95
171
  const treeRunIds = await collectTreeRunIds(db, root.id);
96
172
  const rows = await db.run.findMany({ where: { id: { in: treeRunIds } }, select: { costUsd: true } });
@@ -99,11 +175,23 @@ export async function computeRunTreeSpend(db, parentRunId, now = new Date()) {
99
175
  where: { id: root.agentId },
100
176
  select: { id: true, budgetGroupId: true, budgetUsd: true },
101
177
  });
178
+ const rootOrder = { id: root.id, startedAt: root.startedAt };
102
179
  // Root has no parentRunId of its own, so this terminates in one level —
103
180
  // no unbounded recursion regardless of how deep `parentRunId` itself was.
104
- const rootCeiling = await effectiveBudgetForRun(db, rootAgent, now);
181
+ const rootCeiling = await groupCappedBudget(db, rootAgent, now, {
182
+ excludeReservationRunIds: treeRunIds,
183
+ holdsBefore: rootOrder,
184
+ });
105
185
  const capUsd = rootCeiling.effectiveBudgetUsd;
106
- return { rootRunId: root.id, capUsd, spentUsd, remainingUsd: Math.max(0, capUsd - spentUsd) };
186
+ return {
187
+ rootRunId: root.id,
188
+ capUsd,
189
+ spentUsd,
190
+ remainingUsd: Math.max(0, capUsd - spentUsd),
191
+ treeRunIds,
192
+ root: rootOrder,
193
+ rootBudgetGroupId: rootAgent.budgetGroupId,
194
+ };
107
195
  }
108
196
  /**
109
197
  * The per-run budget ceiling to actually use for `agent`: its own
@@ -116,17 +204,35 @@ export async function computeRunTreeSpend(db, parentRunId, now = new Date()) {
116
204
  * with no caps configured is returned unchanged. Also logs a warning
117
205
  * (never blocks, never throws) once a period's spend crosses the group's
118
206
  * own warnThresholdRatio.
207
+ *
208
+ * A sub-agent in the root's own budget group spends inside the root's hold,
209
+ * so its group check is the root's: the tree's holds and the holds of runs
210
+ * after the root are left out. A sub-agent in any other group is an
211
+ * ordinary member of that group and counts every earlier live hold there,
212
+ * the tree's included.
119
213
  */
120
- export async function effectiveBudgetForRun(db, agent, now = new Date(), parentRunId) {
214
+ export async function effectiveBudgetForRun(db, agent, now = new Date(), parentRunId, options = {}) {
215
+ const self = options.self;
216
+ const ownSpend = { excludeReservationRunIds: self ? [self.id] : [], holdsBefore: self };
217
+ if (!parentRunId)
218
+ return groupCappedBudget(db, agent, now, ownSpend);
219
+ const tree = await runTreeSpend(db, parentRunId, now);
220
+ const sharesRootGroup = agent.budgetGroupId !== null && agent.budgetGroupId === tree.rootBudgetGroupId;
221
+ const spend = sharesRootGroup
222
+ ? { excludeReservationRunIds: [...tree.treeRunIds, ...(self ? [self.id] : [])], holdsBefore: tree.root }
223
+ : ownSpend;
224
+ return groupCappedBudget(db, agent, now, spend, { value: tree.remainingUsd, reason: "run-tree" });
225
+ }
226
+ async function groupCappedBudget(db, agent, now, spendOptions, extra) {
121
227
  const ownBudgetUsd = Number(agent.budgetUsd);
122
228
  const candidates = [];
123
229
  if (agent.budgetGroupId) {
124
230
  const group = await db.budgetGroup.findUnique({
125
231
  where: { id: agent.budgetGroupId },
126
- include: { agents: { select: { id: true } } },
232
+ include: { agents: { select: { id: true, budgetUsd: true } } },
127
233
  });
128
234
  if (group) {
129
- const spend = await computeGroupSpend(db, group, group.agents.map((a) => a.id), now);
235
+ const spend = await computeGroupSpend(db, group, group.agents, now, spendOptions);
130
236
  const warnThresholdRatio = Number(group.warnThresholdRatio);
131
237
  for (const s of spend) {
132
238
  candidates.push({ value: s.remainingUsd, reason: s.period });
@@ -137,13 +243,13 @@ export async function effectiveBudgetForRun(db, agent, now = new Date(), parentR
137
243
  }
138
244
  }
139
245
  }
140
- if (parentRunId) {
141
- const tree = await computeRunTreeSpend(db, parentRunId, now);
142
- candidates.push({ value: tree.remainingUsd, reason: "run-tree" });
143
- }
246
+ if (extra)
247
+ candidates.push(extra);
144
248
  if (candidates.length === 0)
145
249
  return { effectiveBudgetUsd: ownBudgetUsd, constrainedBy: [] };
146
250
  const constrainedBy = candidates.filter((c) => c.value < ownBudgetUsd).map((c) => c.reason);
147
251
  const effectiveBudgetUsd = Math.min(ownBudgetUsd, ...candidates.map((c) => c.value));
148
- return { effectiveBudgetUsd, constrainedBy };
252
+ // Less than the smallest reservable amount is nothing left.
253
+ const exhausted = candidates.find((c) => c.value < MIN_RESERVATION_USD);
254
+ return { effectiveBudgetUsd, constrainedBy, ...(exhausted ? { exhaustedBy: exhausted.reason } : {}) };
149
255
  }
@@ -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>;
@@ -0,0 +1,55 @@
1
+ const MAX_GROUP_NAME_CHARS = 64;
2
+ const usd = (amount) => `$${amount.toFixed(2)}`;
3
+ /** A group name is operator-chosen text; keep it to one short, quote-free line. */
4
+ function groupName(name) {
5
+ const cleaned = (name ?? "").replace(/["`]/g, "").replace(/\s+/g, " ").trim().slice(0, MAX_GROUP_NAME_CHARS);
6
+ return cleaned || null;
7
+ }
8
+ export function budgetSentence(facts) {
9
+ const group = groupName(facts.budgetGroupName);
10
+ if (facts.refused) {
11
+ const within = `this run could not start within its ${usd(facts.runBudgetUsd ?? facts.agentBudgetUsd)} budget`;
12
+ return group
13
+ ? `Out of budget: ${within} or what the "${group}" budget group had left of its limit.`
14
+ : `Out of budget: ${within}.`;
15
+ }
16
+ const runBudget = facts.runBudgetUsd ?? facts.agentBudgetUsd;
17
+ const used = `Out of budget: this run's ${usd(runBudget)} budget was used up`;
18
+ // A run reserved less than the agent's own budget: its group had only that much left.
19
+ if (group && runBudget < facts.agentBudgetUsd) {
20
+ return `${used} (the "${group}" budget group had only that much left of its limit).`;
21
+ }
22
+ return `${used}.`;
23
+ }
24
+ /**
25
+ * The sentence for a finished run, read from the database: a coding run's
26
+ * reserved budget, otherwise the agent's. Falls back to a sentence without
27
+ * amounts when they cannot be read. Never throws.
28
+ */
29
+ export async function loadBudgetSentence(db, runId, status) {
30
+ const refused = status === "refused";
31
+ try {
32
+ const row = await db.run.findUnique({
33
+ where: { id: runId },
34
+ select: {
35
+ agent: { select: { budgetUsd: true, budgetGroup: { select: { name: true } } } },
36
+ codingRun: { select: { budgetReservedUsd: true } },
37
+ },
38
+ });
39
+ const agentBudgetUsd = Number(row?.agent?.budgetUsd);
40
+ if (!row?.agent || !Number.isFinite(agentBudgetUsd))
41
+ throw new Error("budget_facts_unavailable");
42
+ const reserved = row.codingRun ? Number(row.codingRun.budgetReservedUsd) : NaN;
43
+ return budgetSentence({
44
+ runBudgetUsd: Number.isFinite(reserved) ? reserved : null,
45
+ agentBudgetUsd,
46
+ budgetGroupName: row.agent.budgetGroup?.name ?? null,
47
+ refused,
48
+ });
49
+ }
50
+ catch {
51
+ return refused
52
+ ? "Out of budget: this run could not start within its budget."
53
+ : "Out of budget: this run's budget was used up.";
54
+ }
55
+ }
@@ -14,7 +14,7 @@
14
14
  * Called on the scheduler leader's tick and immediately in any process
15
15
  * whose coding run just finished (ContainerExecutor onSlotReleased).
16
16
  */
17
- import type { PrismaClient } from "@prisma/client";
17
+ import type { PrismaClient } from "#prisma";
18
18
  import type { Executor } from "../providers/executor/types.js";
19
19
  export declare const CODING_QUEUE_TIMEOUT_ERROR = "coding_queue_timeout";
20
20
  export type CodingQueueDb = Pick<PrismaClient, "run" | "codingRun" | "$transaction">;
@@ -4,7 +4,7 @@
4
4
  * by a per-attachment `boundName` (sharedDatastore.get(boundName, key)) —
5
5
  * same boundName convention as AgentSecret.
6
6
  */
7
- import type { PrismaClient, Datastore as DatastoreRow } from "@prisma/client";
7
+ import type { PrismaClient, Datastore as DatastoreRow } from "#prisma";
8
8
  import type { Datastore, DatastoreSetOptions, DatastoreValue } from "../providers/datastore/types.js";
9
9
  export type DatastoreMetadata = Pick<DatastoreRow, "id" | "name" | "ownerId" | "createdAt" | "updatedAt">;
10
10
  export interface SharedDatastoreAccessor {
@@ -25,9 +25,25 @@ export async function attachDatastore(agentId, datastoreId, db, boundName) {
25
25
  export async function detachDatastore(agentId, boundName, db) {
26
26
  await db.agentDatastore.deleteMany({ where: { agentId, boundName } });
27
27
  }
28
+ /**
29
+ * Owner rule (resource-sharing grants spec §3.4.1): a binding resolves only
30
+ * while the datastore's owner is the agent's CURRENT owner, both non-null.
31
+ * A cross-owner binding (written before that rule, or left behind by
32
+ * make_owner) and every binding on an owner-less agent resolve like an
33
+ * unattached boundName, so one owner's store never reaches another owner's
34
+ * agent (A2/S2-2, R2-1).
35
+ */
28
36
  async function resolveDatastoreId(agentId, boundName, db) {
29
- const attachment = await db.agentDatastore.findFirst({ where: { agentId, boundName } });
30
- return attachment?.datastoreId;
37
+ const attachment = await db.agentDatastore.findFirst({
38
+ where: { agentId, boundName },
39
+ include: { datastore: { select: { ownerId: true } }, agent: { select: { ownerId: true } } },
40
+ });
41
+ if (!attachment)
42
+ return undefined;
43
+ const owner = attachment.agent.ownerId;
44
+ if (owner === null || attachment.datastore.ownerId !== owner)
45
+ return undefined;
46
+ return attachment.datastoreId;
31
47
  }
32
48
  /**
33
49
  * Resolves `boundName` to the Datastore attached to this agent under that
package/dist/core/db.d.ts CHANGED
@@ -1,2 +1,40 @@
1
- import { PrismaClient } from "@prisma/client";
2
- export declare const prisma: PrismaClient<import("@prisma/client").Prisma.PrismaClientOptions, never, import("@prisma/client/runtime/library").DefaultArgs>;
1
+ import { PrismaClient } from "#prisma";
2
+ /**
3
+ * Connection-pool sizing for the node-postgres pool behind @prisma/adapter-pg.
4
+ *
5
+ * Prisma 6's query engine owned the pool: `connection_limit` defaulted to
6
+ * cpus * 2 + 1 and a caller waited at most `pool_timeout` = 10 s for a free
7
+ * connection (then P2024). Prisma 7 hands the pool to `pg`, whose defaults are
8
+ * max = 10 and connectionTimeoutMillis = 0 — wait for a connection *forever*,
9
+ * so a saturated pool would hang requests instead of failing them. Both values
10
+ * are therefore set explicitly, to Prisma 6's defaults:
11
+ *
12
+ * - max: availableParallelism() * 2 + 1 (Prisma 6 counted physical cores; the
13
+ * logical count is the closest portable Node equivalent).
14
+ * - connectionTimeoutMillis: 10 000 (Prisma 6's pool_timeout). A timeout now
15
+ * surfaces as a pg "timeout exceeded when trying to connect" error, not P2024.
16
+ *
17
+ * `pg` ignores Prisma's own URL parameters, so the two that tuned these same
18
+ * knobs (`connection_limit`, `pool_timeout` in seconds) are still honoured here
19
+ * for URLs that set them. idleTimeoutMillis stays at pg's 10 s default.
20
+ *
21
+ * TLS is whatever the URL asks for (`sslmode`, `sslrootcert`, ...); certificate
22
+ * validation is never disabled here.
23
+ */
24
+ export declare const DEFAULT_POOL_TIMEOUT_MS = 10000;
25
+ export declare function defaultPoolMax(): number;
26
+ export interface PoolSettings {
27
+ max: number;
28
+ connectionTimeoutMillis: number;
29
+ }
30
+ export declare function poolSettings(connectionString: string): PoolSettings;
31
+ /**
32
+ * The one way wardby builds a Prisma client. `url` defaults to DATABASE_URL
33
+ * as read at call time.
34
+ */
35
+ /** `poolMax` overrides the pool size for a caller that sizes its own pools
36
+ * (the coding proxy keeps its ledger and its package registry apart). */
37
+ export declare function createPrismaClient(url?: string | undefined, options?: {
38
+ poolMax?: number;
39
+ }): PrismaClient;
40
+ export declare const prisma: PrismaClient;
package/dist/core/db.js CHANGED
@@ -1,2 +1,75 @@
1
- import { PrismaClient } from "@prisma/client";
2
- export const prisma = new PrismaClient();
1
+ import { availableParallelism } from "node:os";
2
+ import { PrismaPg } from "@prisma/adapter-pg";
3
+ import { PrismaClient } from "#prisma";
4
+ /**
5
+ * Connection-pool sizing for the node-postgres pool behind @prisma/adapter-pg.
6
+ *
7
+ * Prisma 6's query engine owned the pool: `connection_limit` defaulted to
8
+ * cpus * 2 + 1 and a caller waited at most `pool_timeout` = 10 s for a free
9
+ * connection (then P2024). Prisma 7 hands the pool to `pg`, whose defaults are
10
+ * max = 10 and connectionTimeoutMillis = 0 — wait for a connection *forever*,
11
+ * so a saturated pool would hang requests instead of failing them. Both values
12
+ * are therefore set explicitly, to Prisma 6's defaults:
13
+ *
14
+ * - max: availableParallelism() * 2 + 1 (Prisma 6 counted physical cores; the
15
+ * logical count is the closest portable Node equivalent).
16
+ * - connectionTimeoutMillis: 10 000 (Prisma 6's pool_timeout). A timeout now
17
+ * surfaces as a pg "timeout exceeded when trying to connect" error, not P2024.
18
+ *
19
+ * `pg` ignores Prisma's own URL parameters, so the two that tuned these same
20
+ * knobs (`connection_limit`, `pool_timeout` in seconds) are still honoured here
21
+ * for URLs that set them. idleTimeoutMillis stays at pg's 10 s default.
22
+ *
23
+ * TLS is whatever the URL asks for (`sslmode`, `sslrootcert`, ...); certificate
24
+ * validation is never disabled here.
25
+ */
26
+ export const DEFAULT_POOL_TIMEOUT_MS = 10_000;
27
+ export function defaultPoolMax() {
28
+ return availableParallelism() * 2 + 1;
29
+ }
30
+ export function poolSettings(connectionString) {
31
+ const settings = { max: defaultPoolMax(), connectionTimeoutMillis: DEFAULT_POOL_TIMEOUT_MS };
32
+ let params;
33
+ try {
34
+ params = new URL(connectionString).searchParams;
35
+ }
36
+ catch {
37
+ return settings;
38
+ }
39
+ const limit = Number(params.get("connection_limit"));
40
+ if (Number.isInteger(limit) && limit > 0)
41
+ settings.max = limit;
42
+ const timeout = params.get("pool_timeout");
43
+ if (timeout !== null && timeout !== "" && Number.isFinite(Number(timeout)) && Number(timeout) >= 0) {
44
+ // Prisma's pool_timeout=0 meant "no timeout"; pg's 0 means the same.
45
+ settings.connectionTimeoutMillis = Number(timeout) * 1000;
46
+ }
47
+ return settings;
48
+ }
49
+ /**
50
+ * Without a URL, pg would silently fall back to PGHOST/localhost:5432 as the
51
+ * OS user. Prisma 6 failed the first query instead ("Environment variable not
52
+ * found: DATABASE_URL"); keep that: construction succeeds, connecting fails.
53
+ */
54
+ class MissingUrlAdapter extends PrismaPg {
55
+ async connect() {
56
+ throw new Error("DATABASE_URL is not set: cannot connect to the database.");
57
+ }
58
+ }
59
+ /**
60
+ * The one way wardby builds a Prisma client. `url` defaults to DATABASE_URL
61
+ * as read at call time.
62
+ */
63
+ /** `poolMax` overrides the pool size for a caller that sizes its own pools
64
+ * (the coding proxy keeps its ledger and its package registry apart). */
65
+ export function createPrismaClient(url = process.env.DATABASE_URL, options = {}) {
66
+ const adapter = url
67
+ ? new PrismaPg({
68
+ connectionString: url,
69
+ ...poolSettings(url),
70
+ ...(options.poolMax !== undefined ? { max: options.poolMax } : {}),
71
+ })
72
+ : new MissingUrlAdapter({ connectionString: "" });
73
+ return new PrismaClient({ adapter });
74
+ }
75
+ export const prisma = createPrismaClient();