@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
@@ -1,14 +1,27 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { mkdir, realpath, rename, rm, writeFile } from "node:fs/promises";
3
3
  import { join, resolve, sep } from "node:path";
4
+ import { withBaseCommit } from "../../coding/base-commit.js";
5
+ import { normalizeCollectExclusions } from "../../coding/collect-exclude.js";
6
+ import { codingProviderForModelProvider } from "../../coding/provider.js";
4
7
  import { CodingProfileSchema } from "../../coding/profile.js";
8
+ import { parseStoredServices, storedServiceNames, workerServices } from "../../coding/services/catalog.js";
9
+ import { MAX_SERVICE_DECLARATION_BYTES, SERVICE_DECLARATION_PATH } from "../../coding/services/declaration.js";
10
+ import { SERVICE_UNREADY_CATEGORY, SERVICE_UNREADY_ERROR, serviceUnreadyName, serviceUnreadySentence, } from "../../coding/services/wording.js";
11
+ import { PROTECTED_PATH_CATEGORY, protectedPathFromError, protectedPathSentence, } from "../../coding/protected-path-wording.js";
12
+ import { budgetSentence } from "../../core/budget-wording.js";
13
+ import { fileSelfDefect } from "../../core/self-defects.js";
14
+ import { classifyProviderFailure, providerClassOfCategory, providerSentence, } from "../../core/provider-wording.js";
5
15
  import { logger } from "../../core/logger.js";
16
+ import { requiredLevel } from "../../core/repo-access.js";
6
17
  import { ensurePrivateDirectory } from "../../core/private-directory.js";
7
18
  import { codingRunObserver } from "../../coding/observability.js";
8
19
  import { CODING_PROTOCOL_VERSION, CodingRunResultSchema, CodingTaskInputSchema, parseCodingAgentOutputJson, redactAndTruncate, } from "../../coding/protocol.js";
9
- import { getModelPricing } from "../llm/pricing.js";
10
- import { getAnthropicPricing } from "../llm/pricing-anthropic.js";
11
- import { isImmutableDockerImage } from "../jobs/docker-isolation.js";
20
+ import { parseStoredEntry } from "../llm/catalog-types.js";
21
+ import { resolveCodingEntry } from "../../core/run-pricing.js";
22
+ import { CODING_PROXY_ALIAS, CODING_PROXY_PORT, isImmutableDockerImage } from "../jobs/docker-isolation.js";
23
+ import { normalizeRegistryLockfiles } from "../../coding/registry/lockfiles.js";
24
+ import { ISSUE_KEY, ISSUE_TRACKER_NAMES } from "../issue-tracker/types.js";
12
25
  import { HEARTBEAT_TIMEOUT_MS } from "../../core/timing.js";
13
26
  const containerLog = logger.child({ module: "container-executor" });
14
27
  const TERMINAL_STATUSES = new Set(["succeeded", "failed", "refused", "lost", "budget_exhausted", "cancelled"]);
@@ -33,6 +46,19 @@ function proxyProtocol(provider) {
33
46
  return "anthropic-messages";
34
47
  throw new Error("coding_provider_unsupported");
35
48
  }
49
+ /**
50
+ * Claude Code's commands run in its tool runner, which never reports back through the agent, so the
51
+ * host rewrites registry-proxy download URLs in collected lockfiles itself (the Codex driver does it
52
+ * in the worker). Best effort and symlink-safe (lockfiles.ts); a Codex workspace is left alone.
53
+ */
54
+ export async function normalizeCollectedLockfiles(provider, workspacePath) {
55
+ if (provider !== "claude-code")
56
+ return [];
57
+ return normalizeRegistryLockfiles({
58
+ workspace: workspacePath,
59
+ proxyBaseUrl: `http://${CODING_PROXY_ALIAS}:${CODING_PROXY_PORT}`,
60
+ });
61
+ }
36
62
  export class PrismaContainerExecutionStore {
37
63
  db;
38
64
  options;
@@ -40,10 +66,22 @@ export class PrismaContainerExecutionStore {
40
66
  this.db = db;
41
67
  this.options = options;
42
68
  }
69
+ /** Best effort (bounded, never throws): only called by the write that made the row terminal, so it files once. */
70
+ async selfDefect(run) {
71
+ if (this.hasTrackers)
72
+ await fileSelfDefect(this.db, this.options.issueTrackers, run);
73
+ }
74
+ /** An empty registry (no Jira site configured) means no self-defect queries at all. */
75
+ get hasTrackers() {
76
+ return Object.values(this.options.issueTrackers ?? {}).some(Boolean);
77
+ }
43
78
  async load(runId) {
44
79
  const row = await this.db.run.findUnique({
45
80
  where: { id: runId },
46
- include: { agent: true, codingRun: { include: { proxySession: true } } },
81
+ include: {
82
+ agent: { include: { codingProfile: true, budgetGroup: { select: { name: true } } } },
83
+ codingRun: { include: { proxySession: true } },
84
+ },
47
85
  });
48
86
  if (!row?.codingRun)
49
87
  return null;
@@ -60,10 +98,14 @@ export class PrismaContainerExecutionStore {
60
98
  provider: row.codingRun.provider,
61
99
  model: row.codingRun.model,
62
100
  timeoutSec: row.codingRun.timeoutSec,
63
- allowedEgress: row.codingRun.allowedEgress,
64
101
  protectedPaths: row.codingRun.protectedPaths,
102
+ collectExclude: row.codingRun.collectExclude,
65
103
  rootCodingRunId: row.codingRun.rootCodingRunId,
104
+ issueProvider: row.codingRun.issueProvider,
105
+ issueKey: row.codingRun.issueKey,
66
106
  budgetUsd: Number(row.codingRun.budgetReservedUsd),
107
+ agentBudgetUsd: Number(row.agent.budgetUsd),
108
+ budgetGroupName: row.agent.budgetGroup?.name ?? null,
67
109
  tokensIn: row.tokensIn,
68
110
  tokensOut: row.tokensOut,
69
111
  costUsd: Number(row.costUsd),
@@ -74,7 +116,15 @@ export class PrismaContainerExecutionStore {
74
116
  proxySessionId: row.codingRun.proxySession?.id ?? null,
75
117
  result: row.codingRun.result,
76
118
  workerImage: row.codingRun.workerImage,
119
+ toolImage: row.codingRun.toolImage,
77
120
  workspaceDiskMb: row.codingRun.workspaceDiskMb,
121
+ debugTrace: row.codingRun.debugTrace,
122
+ services: row.codingRun.services,
123
+ failureCategory: row.codingRun.failureCategory,
124
+ profileRepository: row.agent.codingProfile?.repository ?? null,
125
+ repositoryAuthorizedVia: row.agent.codingProfile?.repositoryAuthorizedVia ?? null,
126
+ pricingVersion: row.pricingVersion,
127
+ pricingSnapshot: row.pricingSnapshot,
78
128
  };
79
129
  }
80
130
  async claimProvisioning(runId, claimId) {
@@ -187,13 +237,14 @@ export class PrismaContainerExecutionStore {
187
237
  });
188
238
  }
189
239
  async complete(runId, status, result) {
190
- await this.db.$transaction(async (tx) => {
240
+ const finishedAt = new Date();
241
+ const finished = await this.db.$transaction(async (tx) => {
191
242
  const run = await tx.run.findUnique({ where: { id: runId }, include: { codingRun: true } });
192
243
  if (!run?.codingRun)
193
244
  throw new Error("coding_run_not_found");
194
245
  if (TERMINAL_STATUSES.has(run.status)) {
195
246
  if (run.codingRun.result && stableJson(run.codingRun.result) === stableJson(result))
196
- return;
247
+ return null;
197
248
  throw new Error("coding_run_terminal_conflict");
198
249
  }
199
250
  await tx.codingRun.update({
@@ -205,26 +256,40 @@ export class PrismaContainerExecutionStore {
205
256
  data: {
206
257
  status,
207
258
  finalText: result.summary,
208
- finishedAt: new Date(),
259
+ finishedAt,
209
260
  heartbeatAt: new Date(),
210
261
  error: null,
211
262
  },
212
263
  });
264
+ return { id: runId, agentId: run.agentId };
213
265
  });
266
+ if (finished && status === "budget_exhausted") {
267
+ await this.selfDefect({ ...finished, status, error: null, finishedAt });
268
+ }
214
269
  }
215
270
  async terminate(runId, status, error, audit) {
216
- await this.db.$transaction(async (tx) => {
271
+ const finishedAt = new Date();
272
+ const agentId = await this.db.$transaction(async (tx) => {
217
273
  const updated = await tx.run.updateMany({
218
274
  where: { id: runId, status: { in: ["pending", "running"] } },
219
- data: { status, error, finishedAt: new Date(), heartbeatAt: new Date() },
275
+ data: { status, error, finishedAt, heartbeatAt: new Date() },
220
276
  });
221
- if (updated.count && audit) {
277
+ if (!updated.count)
278
+ return null;
279
+ if (audit) {
222
280
  await tx.codingRun.update({
223
281
  where: { runId },
224
282
  data: { failureCategory: audit.failureCategory, diagnosticId: audit.diagnosticId },
225
283
  });
226
284
  }
285
+ if (!this.hasTrackers)
286
+ return null;
287
+ const row = await tx.run.findUnique({ where: { id: runId }, select: { agentId: true } });
288
+ return row?.agentId ?? null;
227
289
  });
290
+ // After commit, so the failure category is readable; cancelled/refused are skipped inside fileSelfDefect.
291
+ if (agentId)
292
+ await this.selfDefect({ id: runId, agentId, status, error, finishedAt });
228
293
  }
229
294
  }
230
295
  /** Plain capabilities live only for the short provisioning window. */
@@ -247,6 +312,9 @@ export class RunCapabilityVault {
247
312
  }
248
313
  class PreflightError extends Error {
249
314
  }
315
+ /** A refusal by repository authorization; its message is the failure category's prefix (repo_access_*). */
316
+ class RepoAccessError extends PreflightError {
317
+ }
250
318
  export class ContainerExecutor {
251
319
  options;
252
320
  artifactRoot;
@@ -273,6 +341,12 @@ export class ContainerExecutor {
273
341
  if (image !== undefined && !isImmutableDockerImage(image))
274
342
  throw new Error("coding_worker_image_invalid");
275
343
  }
344
+ for (const versions of Object.values(options.claudeToolRunnerImages ?? {})) {
345
+ for (const image of Object.values(versions)) {
346
+ if (!isImmutableDockerImage(image))
347
+ throw new Error("coding_worker_image_invalid");
348
+ }
349
+ }
276
350
  if (!/^[A-Za-z][A-Za-z0-9_.:-]{0,199}$/.test(options.credentialRef)) {
277
351
  throw new Error("coding_credential_ref_invalid");
278
352
  }
@@ -397,6 +471,9 @@ export class ContainerExecutor {
397
471
  if ((await this.options.store.claimProvisioning(runId, claimId)) !== "claimed")
398
472
  return;
399
473
  }
474
+ // A launched run (recovered after a restart) is in flight: a transient
475
+ // host error is retried once before it is stopped.
476
+ await this.authorizeRepository(run, { inFlight: Boolean(handle) });
400
477
  try {
401
478
  workspace = await this.options.vcs.recoverWorkspace(prepared);
402
479
  if (!workspace && handle)
@@ -425,11 +502,12 @@ export class ContainerExecutor {
425
502
  allowedModels: [run.model],
426
503
  deadlineAt,
427
504
  budgetUsd: run.budgetUsd,
505
+ terms: this.modelTerms(run),
428
506
  });
429
507
  spendEnabled = true;
430
508
  sessionId = session.id;
431
509
  this.options.capabilities.set(runId, session.capability);
432
- const inputArtifact = await this.writeInput(run, deadlineAt);
510
+ const inputArtifact = await this.writeInput(run, deadlineAt, workspace.baseCommit);
433
511
  const beforeLaunch = await this.requireCurrent(runId);
434
512
  if (TERMINAL_STATUSES.has(beforeLaunch.status))
435
513
  throw new Error("coding_run_no_longer_active");
@@ -474,8 +552,13 @@ export class ContainerExecutor {
474
552
  await this.options.sessions.cancelSession(sessionId).catch(() => undefined);
475
553
  if (handle)
476
554
  await this.options.jobs.stop(handle, "executor_failure").catch(() => undefined);
477
- const status = !spendEnabled && error instanceof PreflightError ? "refused" : "failed";
478
- const failure = this.failure(error);
555
+ const outOfBudget = spendEnabled && (await this.outOfBudget(sessionId));
556
+ const status = outOfBudget
557
+ ? "budget_exhausted"
558
+ : !spendEnabled && error instanceof PreflightError
559
+ ? "refused"
560
+ : "failed";
561
+ const failure = outOfBudget ? this.budgetFailure(error) : this.failure(error);
479
562
  this.emit({
480
563
  stage: "stopping",
481
564
  runId,
@@ -487,9 +570,17 @@ export class ContainerExecutor {
487
570
  this.terminal(run, status, failure.audit);
488
571
  if (handle)
489
572
  await this.options.jobs.remove(handle).catch(() => undefined);
573
+ // A service sidecar that never became ready (kubernetes.ts waitForKeeper) is named on the host.
574
+ const unreadyService = outOfBudget ? null : serviceUnreadyName(error);
575
+ const protectedPath = outOfBudget ? null : protectedPathFromError(error);
490
576
  if (workspace) {
491
577
  await this.options.vcs.cleanup(workspace).catch(() => undefined);
492
- await this.options.vcs.notifyContinuationFinished?.(workspace, "failed", { agentName: run.agentName });
578
+ await this.options.vcs.notifyContinuationFinished?.(workspace, outOfBudget ? "budget_exhausted" : "failed", {
579
+ agentName: run.agentName,
580
+ ...(outOfBudget ? { budgetSentence: this.budgetSentence(run) } : {}),
581
+ ...(unreadyService ? { serviceSentence: serviceUnreadySentence([unreadyService]) } : {}),
582
+ ...(protectedPath ? { protectedPathSentence: protectedPathSentence(protectedPath) } : {}),
583
+ });
493
584
  }
494
585
  this.emit({ stage: "cleanup", runId, jobId: handle?.id, cleanupSucceeded: true });
495
586
  }
@@ -531,18 +622,70 @@ export class ContainerExecutor {
531
622
  clearInterval(timer);
532
623
  }
533
624
  }
625
+ /**
626
+ * The run may use its repository only if the agent's current owner still
627
+ * has write on it, or the profile's approval (admin or grandfathered)
628
+ * covers exactly this repository. A profile changed since dispatch no
629
+ * longer covers the run's repository, so that falls back to the owner's
630
+ * access. Throws RepoAccessError (refused before anything is cloned).
631
+ */
632
+ async authorizeRepository(run, opts) {
633
+ const sameRepository = run.profileRepository !== null && run.profileRepository === run.repository;
634
+ const decision = await this.options.repoAccess.authorizeUse({
635
+ ownerId: run.ownerId,
636
+ provider: "github",
637
+ repository: run.repository,
638
+ required: requiredLevel("coding"),
639
+ authorizedVia: sameRepository ? run.repositoryAuthorizedVia : "host_permission",
640
+ retryTransient: opts.inFlight,
641
+ });
642
+ if (!decision.ok)
643
+ throw new RepoAccessError(`repo_access_${decision.reason}`);
644
+ }
645
+ /** The originating issue for the PR title/body: control-plane data from the CodingRun row, key re-validated. */
646
+ issueFor(run) {
647
+ const { issueProvider, issueKey } = run;
648
+ if (!issueProvider || !issueKey || !ISSUE_KEY.test(issueKey))
649
+ return {};
650
+ let url;
651
+ try {
652
+ url = this.options.issueUrl?.(issueProvider, issueKey);
653
+ }
654
+ catch {
655
+ url = undefined;
656
+ }
657
+ const trackerName = Object.hasOwn(ISSUE_TRACKER_NAMES, issueProvider)
658
+ ? ISSUE_TRACKER_NAMES[issueProvider]
659
+ : undefined;
660
+ return {
661
+ issue: {
662
+ key: issueKey,
663
+ ...(url?.startsWith("https://") ? { url } : {}),
664
+ ...(trackerName ? { trackerName } : {}),
665
+ },
666
+ };
667
+ }
668
+ /** The run's stored model terms; a run from before the catalog resolves them from the current catalog. */
669
+ modelTerms(run) {
670
+ if (run.provider !== "codex" && run.provider !== "claude-code")
671
+ throw new Error("coding_provider_unsupported");
672
+ const stored = parseStoredEntry(run.pricingSnapshot);
673
+ if (stored && run.pricingVersion) {
674
+ if (stored.modelId !== run.model || codingProviderForModelProvider(stored.provider) !== run.provider) {
675
+ throw new Error("coding_run_pricing_mismatch");
676
+ }
677
+ return { version: run.pricingVersion, entry: stored };
678
+ }
679
+ const entry = resolveCodingEntry(run.provider, run.model);
680
+ return { version: entry.priceVersion, entry: parseStoredEntry(entry) };
681
+ }
534
682
  preflight(run) {
535
683
  try {
536
684
  if (run.status !== "pending" && run.status !== "running")
537
685
  throw new Error("coding_run_status_invalid");
538
686
  if (run.agentKind !== "coding" || !run.ownerId)
539
687
  throw new Error("coding_run_ownership_invalid");
540
- if (run.provider === "codex")
541
- getModelPricing(run.model);
542
- else if (run.provider === "claude-code")
543
- getAnthropicPricing(run.model);
544
- else
545
- throw new Error("coding_provider_unsupported");
688
+ this.modelTerms(run);
546
689
  if (!Number.isFinite(run.budgetUsd) || run.budgetUsd <= 0 || run.costUsd > run.budgetUsd) {
547
690
  throw new Error("coding_run_budget_invalid");
548
691
  }
@@ -552,7 +695,6 @@ export class ContainerExecutor {
552
695
  baseRef: run.baseRef,
553
696
  defaultTask: null,
554
697
  timeoutSec: run.timeoutSec,
555
- allowedEgress: run.allowedEgress,
556
698
  protectedPaths: run.protectedPaths,
557
699
  });
558
700
  const expectedHeadRunId = run.rootCodingRunId ?? run.runId;
@@ -577,6 +719,7 @@ export class ContainerExecutor {
577
719
  baseRef: profile.baseRef,
578
720
  headRef: run.headRef,
579
721
  protectedPaths: profile.protectedPaths,
722
+ collectExclude: [...normalizeCollectExclusions(run.collectExclude).paths],
580
723
  continuation: run.rootCodingRunId ? { rootRunId: run.rootCodingRunId } : undefined,
581
724
  };
582
725
  }
@@ -589,16 +732,34 @@ export class ContainerExecutor {
589
732
  if (sessionId)
590
733
  await this.options.sessions.cancelSession(sessionId).catch(() => undefined);
591
734
  // For notifyContinuationFinished in the `finally` below -- defaults to
592
- // "failed" and is only flipped right before an actual success return.
735
+ // "failed" and is only flipped right before a success or out-of-budget return.
593
736
  let outcome = "failed";
594
737
  let finishedSummary;
738
+ // Set when a failed job's session relayed a model-provider failure; names it on the host.
739
+ // Only a failed job: a transient provider error the worker retried past did not end the run.
740
+ let providerClass;
741
+ // Set when finalizeChanges refused the collected diff for touching a protected path; names it on the host.
742
+ let protectedPath;
595
743
  try {
596
744
  if (jobState !== "succeeded") {
597
745
  const collected = await this.options.jobs.collect(handle).catch(() => null);
598
746
  const reason = collected?.diagnostic ?? collected?.reason ?? jobState;
599
747
  this.emit({ stage: "collected", runId: run.runId, jobId: handle.id });
748
+ // The worker usually fails outright when the proxy refuses a model
749
+ // request for budget; the session records that refusal.
750
+ if (await this.outOfBudget(sessionId)) {
751
+ const failure = this.budgetFailure(`job_${reason}`, { issues: collected?.diagnosticIssues });
752
+ await this.options.store.terminate(run.runId, "budget_exhausted", failure.error, failure.audit);
753
+ this.terminal(run, "budget_exhausted", failure.audit);
754
+ outcome = "budget_exhausted";
755
+ return;
756
+ }
600
757
  const status = jobState === "lost" ? "lost" : "failed";
601
- const failure = this.failure(`job_${reason}`, { issues: collected?.diagnosticIssues });
758
+ const provider = status === "failed" ? await this.providerFailure(sessionId) : null;
759
+ const failure = provider
760
+ ? this.providerFailureOf(run, provider, `job_${reason}`, { issues: collected?.diagnosticIssues })
761
+ : this.failure(`job_${reason}`, { issues: collected?.diagnosticIssues });
762
+ providerClass = provider?.class;
602
763
  await this.options.store.terminate(run.runId, status, failure.error, failure.audit);
603
764
  this.terminal(run, status, failure.audit);
604
765
  return;
@@ -615,6 +776,7 @@ export class ContainerExecutor {
615
776
  this.emit({ stage: "budget_cutoff", runId: run.runId, jobId: handle.id });
616
777
  await this.options.store.complete(run.runId, "budget_exhausted", this.resultFor(output, current, "budget_exhausted"));
617
778
  this.terminal(current, "budget_exhausted");
779
+ outcome = "budget_exhausted";
618
780
  return;
619
781
  }
620
782
  if (output.outcome === "no_changes") {
@@ -629,17 +791,25 @@ export class ContainerExecutor {
629
791
  if (!workspace)
630
792
  throw new Error("coding_workspace_lost");
631
793
  await this.options.jobs.materializeWorkspace(handle, workspace.workspacePath);
794
+ await normalizeCollectedLockfiles(current.provider, workspace.workspacePath);
632
795
  current = await this.requireCurrent(run.runId);
633
796
  if (current.costUsd > current.budgetUsd) {
634
797
  this.emit({ stage: "budget_cutoff", runId: run.runId, jobId: handle.id });
635
798
  await this.options.store.complete(run.runId, "budget_exhausted", this.resultFor(output, current, "budget_exhausted"));
636
799
  this.terminal(current, "budget_exhausted");
800
+ outcome = "budget_exhausted";
637
801
  return;
638
802
  }
803
+ // One more (usually cached) check right before anything is pushed: access
804
+ // revoked while the run was working stops the push.
805
+ await this.authorizeRepository(current, { inFlight: true });
806
+ const report = await this.loadRegistryReport(run.runId);
639
807
  const finalized = await this.options.vcs.finalizeChanges(workspace, {
640
808
  summary: output.summary,
641
809
  tests: output.tests,
642
810
  tag: output.tag,
811
+ ...report,
812
+ ...this.issueFor(current),
643
813
  });
644
814
  const result = this.resultFor(output, current, finalized.outcome, finalized);
645
815
  await this.options.store.complete(run.runId, "succeeded", result);
@@ -651,9 +821,18 @@ export class ContainerExecutor {
651
821
  finishedSummary = output.summary;
652
822
  }
653
823
  catch (error) {
654
- const failure = this.failure(error);
655
- await this.options.store.terminate(run.runId, "failed", failure.error, failure.audit);
656
- this.terminal(run, "failed", failure.audit);
824
+ if (await this.outOfBudget(sessionId)) {
825
+ const failure = this.budgetFailure(error);
826
+ await this.options.store.terminate(run.runId, "budget_exhausted", failure.error, failure.audit);
827
+ this.terminal(run, "budget_exhausted", failure.audit);
828
+ outcome = "budget_exhausted";
829
+ }
830
+ else {
831
+ const failure = this.failure(error);
832
+ await this.options.store.terminate(run.runId, "failed", failure.error, failure.audit);
833
+ this.terminal(run, "failed", failure.audit);
834
+ protectedPath = protectedPathFromError(error) ?? undefined;
835
+ }
657
836
  }
658
837
  finally {
659
838
  await this.options.jobs.remove(handle).catch(() => undefined);
@@ -664,12 +843,34 @@ export class ContainerExecutor {
664
843
  await this.options.vcs.notifyContinuationFinished?.(workspace, outcome, {
665
844
  summary: finishedSummary,
666
845
  agentName: run.agentName,
846
+ ...(outcome === "budget_exhausted" ? { budgetSentence: this.budgetSentence(run) } : {}),
847
+ ...(outcome === "failed" && providerClass ? { providerSentence: providerSentence(providerClass) } : {}),
848
+ ...(outcome === "failed" && protectedPath
849
+ ? { protectedPathSentence: protectedPathSentence(protectedPath) }
850
+ : {}),
667
851
  });
668
852
  }
669
853
  await rm(this.artifactPath(run.runId), { recursive: true, force: true }).catch(() => undefined);
670
854
  this.emit({ stage: "cleanup", runId: run.runId, jobId: handle.id, cleanupSucceeded: true });
671
855
  }
672
856
  }
857
+ /** The package report is informational: any failure loading it (a
858
+ * rejection, a synchronous throw, or a malformed result) yields no
859
+ * package section rather than failing finalization. */
860
+ async loadRegistryReport(runId) {
861
+ try {
862
+ const report = await this.options.registryReport?.(runId);
863
+ const packages = Array.isArray(report?.packages) ? report.packages : [];
864
+ const packageRefusals = Array.isArray(report?.packageRefusals) ? report.packageRefusals : [];
865
+ return {
866
+ ...(packages.length > 0 ? { packages } : {}),
867
+ ...(packageRefusals.length > 0 ? { packageRefusals } : {}),
868
+ };
869
+ }
870
+ catch {
871
+ return {};
872
+ }
873
+ }
673
874
  resultFor(output, run, forcedOutcome, finalized) {
674
875
  const outcome = forcedOutcome ?? (output.outcome === "budget_exhausted" ? "budget_exhausted" : "no_changes");
675
876
  return CodingRunResultSchema.parse({
@@ -694,15 +895,12 @@ export class ContainerExecutor {
694
895
  }
695
896
  resolveCodingWorkerImage(selector) {
696
897
  if (selector.provider === "claude-code") {
697
- if (selector.toolchain !== "node" || selector.toolchainVersion !== null) {
698
- throw new Error("coding_toolchain_unsupported:claude-code");
699
- }
898
+ this.claudeToolImage(selector);
700
899
  const image = selector.workerImageRef ?? this.options.claudeWorkerImage;
701
- if (!image || !this.options.claudeToolRunnerImage)
900
+ if (!image)
702
901
  throw new Error("coding_provider_not_configured:claude-code");
703
- if (!isImmutableDockerImage(image) || !isImmutableDockerImage(this.options.claudeToolRunnerImage)) {
902
+ if (!isImmutableDockerImage(image))
704
903
  throw new Error("coding_worker_image_invalid");
705
- }
706
904
  return image;
707
905
  }
708
906
  if (selector.provider !== "codex") {
@@ -722,6 +920,51 @@ export class ContainerExecutor {
722
920
  }
723
921
  return image;
724
922
  }
923
+ /** Claude's tool-runner image for the agent's toolchain, at dispatch (Executor.resolveCodingToolImage); null for Codex. */
924
+ resolveCodingToolImage(selector) {
925
+ return selector.provider === "claude-code" ? this.claudeToolImage(selector) : null;
926
+ }
927
+ /** The tool runner is what runs a Claude agent's commands, so it is what the toolchain selects. */
928
+ claudeToolImage(selector) {
929
+ if (!this.options.claudeToolRunnerImage)
930
+ throw new Error("coding_provider_not_configured:claude-code");
931
+ const image = selector.toolchain === "node" && selector.toolchainVersion === null
932
+ ? this.options.claudeToolRunnerImage
933
+ : selector.toolchainVersion !== null
934
+ ? this.options.claudeToolRunnerImages?.[selector.toolchain]?.[selector.toolchainVersion]
935
+ : undefined;
936
+ if (!image)
937
+ throw new Error("coding_toolchain_unsupported:claude-code");
938
+ if (!isImmutableDockerImage(image))
939
+ throw new Error("coding_worker_image_invalid");
940
+ return image;
941
+ }
942
+ /** Coding-run services: the repository's declaration at its base ref (Executor.readCodingServiceDeclaration). */
943
+ async readCodingServiceDeclaration(input) {
944
+ if (!this.options.vcs.readRepositoryFile)
945
+ return null;
946
+ return this.options.vcs.readRepositoryFile({
947
+ repository: input.repository,
948
+ ref: input.baseRef,
949
+ path: SERVICE_DECLARATION_PATH,
950
+ maxBytes: MAX_SERVICE_DECLARATION_BYTES,
951
+ });
952
+ }
953
+ /** One repository file at the run's base ref (Executor.readCodingRepositoryFile). */
954
+ async readCodingRepositoryFile(input) {
955
+ if (!this.options.vcs.readRepositoryFile)
956
+ return null;
957
+ return this.options.vcs.readRepositoryFile({
958
+ repository: input.repository,
959
+ ref: input.baseRef,
960
+ path: input.path,
961
+ maxBytes: input.maxBytes,
962
+ });
963
+ }
964
+ /** The job launcher decides (Kubernetes and Docker start services for both providers). */
965
+ supportsCodingServices(provider) {
966
+ return this.options.jobs.supportsServicesFor?.(provider) === true;
967
+ }
725
968
  async requireCurrent(runId) {
726
969
  const current = await this.options.store.load(runId);
727
970
  if (!current)
@@ -736,16 +979,24 @@ export class ContainerExecutor {
736
979
  if (run.workspaceDiskMb && run.workspaceDiskMb > this.options.maxDiskMb) {
737
980
  throw new Error("coding_workspace_disk_exceeds_limit");
738
981
  }
982
+ const services = parseStoredServices(run.services);
983
+ // Dispatch refuses services this deployment can't start; this is the backstop.
984
+ if (services.length > 0 && !this.supportsCodingServices(provider)) {
985
+ throw new Error("coding_services_unsupported_launcher");
986
+ }
739
987
  return {
740
988
  kind: "coding-agent",
741
989
  runId: run.runId,
742
990
  provider,
743
991
  image: run.workerImage ?? this.options.workerImage,
744
- ...(provider === "claude-code" ? { toolImage: this.options.claudeToolRunnerImage } : {}),
992
+ // A run keeps the tool image it was dispatched with; rows from before CodingRun.toolImage use the default.
993
+ ...(provider === "claude-code" ? { toolImage: run.toolImage ?? this.options.claudeToolRunnerImage } : {}),
745
994
  inputArtifact,
746
995
  timeoutSec: run.timeoutSec,
747
996
  limits: { ...this.options.limits, ...(run.workspaceDiskMb ? { diskMb: run.workspaceDiskMb } : {}) },
748
997
  labels: {},
998
+ collectExclude: normalizeCollectExclusions(run.collectExclude),
999
+ ...(services.length > 0 ? { services } : {}),
749
1000
  };
750
1001
  }
751
1002
  credentialRef(provider) {
@@ -755,7 +1006,7 @@ export class ContainerExecutor {
755
1006
  return this.options.anthropicCredentialRef;
756
1007
  throw new Error("coding_provider_not_configured:claude-code");
757
1008
  }
758
- async writeInput(run, deadlineAt) {
1009
+ async writeInput(run, deadlineAt, baseCommit) {
759
1010
  await ensurePrivateDirectory(this.artifactRoot);
760
1011
  const rootReal = await realpath(this.artifactRoot);
761
1012
  const directory = resolve(rootReal, run.runId);
@@ -766,17 +1017,26 @@ export class ContainerExecutor {
766
1017
  throw new Error("coding_artifact_path_invalid");
767
1018
  const destination = join(directory, "input.json");
768
1019
  const temporary = join(directory, `.input-${randomUUID()}.tmp`);
1020
+ const services = parseStoredServices(run.services);
1021
+ const task = withBaseCommit(run.task, baseCommit);
1022
+ if (task === run.task && /^[0-9a-f]{40}$/.test(baseCommit)) {
1023
+ containerLog.warn({ event: "coding.base_commit_omitted", runId: run.runId }, "the coding task left no room for the base commit line");
1024
+ }
769
1025
  const input = CodingTaskInputSchema.parse({
770
1026
  schemaVersion: CODING_PROTOCOL_VERSION,
771
1027
  runId: run.runId,
772
1028
  repository: run.repository,
773
1029
  baseRef: run.baseRef,
774
1030
  headRef: run.headRef,
775
- task: run.task,
1031
+ task,
776
1032
  model: run.model,
777
1033
  budgetUsd: run.budgetUsd,
778
1034
  deadlineAt: deadlineAt.toISOString(),
779
1035
  continuationOf: run.rootCodingRunId ? { runId: run.rootCodingRunId } : undefined,
1036
+ // Only when true: an untraced run's input stays exactly what older workers expect.
1037
+ ...(run.debugTrace ? { debugTrace: true } : {}),
1038
+ // Only when there are some: every other run's input stays exactly what older workers expect.
1039
+ ...(services.length > 0 ? { services: workerServices(services) } : {}),
780
1040
  });
781
1041
  // The run directory is 0700; world-readable mode only crosses Docker's UID boundary.
782
1042
  await writeFile(temporary, JSON.stringify(input), { flag: "wx", mode: 0o444 });
@@ -789,12 +1049,20 @@ export class ContainerExecutor {
789
1049
  preflightForCleanup(run) {
790
1050
  if (!Array.isArray(run.protectedPaths) || !run.protectedPaths.every((path) => typeof path === "string"))
791
1051
  return null;
1052
+ let collectExclude;
1053
+ try {
1054
+ collectExclude = [...normalizeCollectExclusions(run.collectExclude).paths];
1055
+ }
1056
+ catch {
1057
+ return null;
1058
+ }
792
1059
  return {
793
1060
  runId: run.runId,
794
1061
  repository: run.repository,
795
1062
  baseRef: run.baseRef,
796
1063
  headRef: run.headRef,
797
1064
  protectedPaths: run.protectedPaths,
1065
+ collectExclude,
798
1066
  continuation: run.rootCodingRunId ? { rootRunId: run.rootCodingRunId } : undefined,
799
1067
  };
800
1068
  }
@@ -838,11 +1106,71 @@ export class ContainerExecutor {
838
1106
  // notification actually firing) -- safe to call again because
839
1107
  // notifyContinuationFinished is find-and-update-or-no-op, never
840
1108
  // find-or-create.
841
- await this.options.vcs.notifyContinuationFinished?.(workspace, run.status === "succeeded" ? "succeeded" : "failed", { agentName: run.agentName });
1109
+ const outcome = run.status === "succeeded" ? "succeeded" : run.status === "budget_exhausted" ? "budget_exhausted" : "failed";
1110
+ const providerClass = run.status === "failed" ? providerClassOfCategory(run.failureCategory) : null;
1111
+ const unreadyServices = run.status === "failed" && run.failureCategory === SERVICE_UNREADY_CATEGORY
1112
+ ? storedServiceNames(run.services)
1113
+ : null;
1114
+ // No path survives to the stored run (only the category does -- see
1115
+ // protected-path-wording.ts), so this can only ever use the pathless sentence.
1116
+ const protectedPathFailed = run.status === "failed" && run.failureCategory === PROTECTED_PATH_CATEGORY;
1117
+ await this.options.vcs.notifyContinuationFinished?.(workspace, outcome, {
1118
+ agentName: run.agentName,
1119
+ ...(outcome === "budget_exhausted" ? { budgetSentence: this.budgetSentence(run) } : {}),
1120
+ ...(providerClass ? { providerSentence: providerSentence(providerClass) } : {}),
1121
+ ...(unreadyServices ? { serviceSentence: serviceUnreadySentence(unreadyServices) } : {}),
1122
+ ...(protectedPathFailed ? { protectedPathSentence: protectedPathSentence(null) } : {}),
1123
+ });
842
1124
  }
843
1125
  await rm(this.artifactPath(run.runId), { recursive: true, force: true }).catch(() => undefined);
844
1126
  this.emit({ stage: "cleanup", runId: run.runId, jobId: run.jobHandle?.id, cleanupSucceeded: true });
845
1127
  }
1128
+ /** Whether the proxy refused this run's session for budget. Never throws. */
1129
+ async outOfBudget(sessionId) {
1130
+ if (!sessionId || !this.options.sessions.budgetExhausted)
1131
+ return false;
1132
+ return this.options.sessions.budgetExhausted(sessionId).catch(() => false);
1133
+ }
1134
+ /** The model-provider failure the proxy relayed for this run's session, classified. Never throws. */
1135
+ async providerFailure(sessionId) {
1136
+ if (!sessionId || !this.options.sessions.upstreamFailure)
1137
+ return null;
1138
+ const code = await this.options.sessions.upstreamFailure(sessionId).catch(() => null);
1139
+ return code ? { class: classifyProviderFailure(code), code } : null;
1140
+ }
1141
+ /**
1142
+ * A failure the model provider's refusal caused. The persisted error names
1143
+ * the class only; the raw upstream code (a fixed identifier, never a message)
1144
+ * goes to one operator line an alert can key on, and the failure itself is
1145
+ * still logged under its diagnostic id.
1146
+ */
1147
+ providerFailureOf(run, provider, error, options = {}) {
1148
+ const logged = this.failure(error, options);
1149
+ containerLog.warn({ event: "coding.provider_failure", runId: run.runId, class: provider.class, upstreamCode: provider.code }, "the model provider refused this coding run's requests");
1150
+ return {
1151
+ error: `coding_provider_${provider.class}`,
1152
+ audit: { failureCategory: `provider_${provider.class}`, diagnosticId: logged.audit.diagnosticId },
1153
+ };
1154
+ }
1155
+ /** The host-safe sentence for a run that ran out of budget. */
1156
+ budgetSentence(run) {
1157
+ return budgetSentence({
1158
+ runBudgetUsd: run.budgetUsd,
1159
+ agentBudgetUsd: run.agentBudgetUsd ?? run.budgetUsd,
1160
+ budgetGroupName: run.budgetGroupName,
1161
+ });
1162
+ }
1163
+ /**
1164
+ * A failure the proxy's budget refusal caused. The failure itself is still
1165
+ * logged under its diagnostic id, so the operator keeps the real reason.
1166
+ */
1167
+ budgetFailure(error, options = {}) {
1168
+ const logged = this.failure(error, options);
1169
+ return {
1170
+ error: "coding_budget_exhausted",
1171
+ audit: { failureCategory: "budget", diagnosticId: logged.audit.diagnosticId },
1172
+ };
1173
+ }
846
1174
  emit(event) {
847
1175
  try {
848
1176
  this.observer.emit(event);
@@ -913,7 +1241,36 @@ function safeError(error) {
913
1241
  const message = error instanceof Error ? error.message : "unknown";
914
1242
  return `coding_executor:${redactAndTruncate(message, 200).replace(/[^A-Za-z0-9_.:-]/g, "_")}`;
915
1243
  }
1244
+ /**
1245
+ * Error-code prefixes that name their category outright, checked before the
1246
+ * substring heuristics below. Those heuristics filed every `github_*` error
1247
+ * (a failed PR lookup or create after the push, a token mint) under
1248
+ * "workspace" because "github" contains "git".
1249
+ */
1250
+ const CATEGORY_BY_PREFIX = [
1251
+ // A service sidecar never became ready (src/providers/jobs/kubernetes.ts).
1252
+ [SERVICE_UNREADY_ERROR, SERVICE_UNREADY_CATEGORY],
1253
+ // Services on a launcher that can't start them: refused at dispatch, failed here as a backstop.
1254
+ ["coding_services_unsupported", "preflight"],
1255
+ // Claude Code's tool runner failed to start or never became ready: the same category on both launchers.
1256
+ ["docker_tool_runner_", "job"],
1257
+ ["kubernetes_tool_runner_", "job"],
1258
+ // Checked first: the host could not be asked (after one retry), not a lost permission.
1259
+ ["repo_access_check_unavailable", "repo_access_unavailable"],
1260
+ ["repo_access_", "repo_access"],
1261
+ ["github_", "github"],
1262
+ // Checked before the generic "vcs_" fallback: the collected diff touched a
1263
+ // path this agent may not edit, not a generic workspace/git failure.
1264
+ ["vcs_protected_path:", PROTECTED_PATH_CATEGORY],
1265
+ ["vcs_", "workspace"],
1266
+ ["git_", "workspace"],
1267
+ ];
916
1268
  function failureCategory(error) {
1269
+ const raw = error instanceof Error ? error.message : typeof error === "string" ? error : "";
1270
+ for (const [prefix, category] of CATEGORY_BY_PREFIX) {
1271
+ if (raw.startsWith(prefix))
1272
+ return category;
1273
+ }
917
1274
  const message = safeError(error);
918
1275
  if (message.includes("preflight") || message.includes("ownership") || message.includes("image"))
919
1276
  return "preflight";