@wardby/cli 0.2.1 → 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 (407) hide show
  1. package/.env.example +34 -4
  2. package/README.md +41 -4
  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 +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 +75 -4
  21. package/dist/coding/protocol.js +107 -7
  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 +6 -0
  60. package/dist/coding-worker/debug-trace.d.ts +37 -0
  61. package/dist/coding-worker/debug-trace.js +117 -0
  62. package/dist/coding-worker/driver.d.ts +13 -1
  63. package/dist/coding-worker/driver.js +102 -13
  64. package/dist/coding-worker/errors.js +8 -0
  65. package/dist/coding-worker/main.js +10 -2
  66. package/dist/coding-worker/sdk.d.ts +2 -2
  67. package/dist/coding-worker/sdk.js +7 -2
  68. package/dist/coding-worker/types.d.ts +3 -0
  69. package/dist/config/providers.d.ts +41 -0
  70. package/dist/config/providers.js +66 -0
  71. package/dist/core/budget-groups.d.ts +108 -5
  72. package/dist/core/budget-groups.js +125 -19
  73. package/dist/core/budget-wording.d.ts +22 -0
  74. package/dist/core/budget-wording.js +55 -0
  75. package/dist/core/datastores.js +18 -2
  76. package/dist/core/db.d.ts +5 -1
  77. package/dist/core/db.js +8 -2
  78. package/dist/core/dispatch.d.ts +33 -6
  79. package/dist/core/dispatch.js +260 -43
  80. package/dist/core/engine-native.js +27 -10
  81. package/dist/core/grants.d.ts +86 -0
  82. package/dist/core/grants.js +126 -0
  83. package/dist/core/host-events.d.ts +49 -0
  84. package/dist/core/host-events.js +257 -0
  85. package/dist/core/host-identity-links.d.ts +54 -0
  86. package/dist/core/host-identity-links.js +189 -0
  87. package/dist/core/host-status.d.ts +61 -0
  88. package/dist/core/host-status.js +211 -0
  89. package/dist/core/in-flight-runs.d.ts +8 -0
  90. package/dist/core/in-flight-runs.js +56 -0
  91. package/dist/core/provider-wording.d.ts +12 -0
  92. package/dist/core/provider-wording.js +44 -0
  93. package/dist/core/reconciler.d.ts +38 -2
  94. package/dist/core/reconciler.js +86 -2
  95. package/dist/core/repo-access.d.ts +99 -0
  96. package/dist/core/repo-access.js +136 -0
  97. package/dist/core/review-host-checks.d.ts +11 -0
  98. package/dist/core/review-host-checks.js +41 -0
  99. package/dist/core/review-host-tools.d.ts +47 -0
  100. package/dist/core/review-host-tools.js +347 -0
  101. package/dist/core/run-heartbeat.d.ts +27 -0
  102. package/dist/core/run-heartbeat.js +54 -0
  103. package/dist/core/runner.d.ts +30 -8
  104. package/dist/core/runner.js +237 -40
  105. package/dist/core/secrets.d.ts +11 -2
  106. package/dist/core/secrets.js +25 -6
  107. package/dist/core/subagent-memory-tools.d.ts +1 -1
  108. package/dist/core/subagent-memory-tools.js +16 -3
  109. package/dist/core/tool-admin.d.ts +81 -0
  110. package/dist/core/tool-admin.js +129 -0
  111. package/dist/core/tool-names.d.ts +42 -0
  112. package/dist/core/tool-names.js +64 -0
  113. package/dist/core/untrusted-content.d.ts +32 -0
  114. package/dist/core/untrusted-content.js +72 -0
  115. package/dist/core/webhooks.d.ts +8 -1
  116. package/dist/core/webhooks.js +23 -2
  117. package/dist/generated/prisma/browser.d.ts +100 -0
  118. package/dist/generated/prisma/client.d.ts +100 -0
  119. package/dist/generated/prisma/commonInputTypes.d.ts +30 -0
  120. package/dist/generated/prisma/enums.d.ts +6 -0
  121. package/dist/generated/prisma/enums.js +6 -1
  122. package/dist/generated/prisma/internal/class.d.ts +143 -0
  123. package/dist/generated/prisma/internal/class.js +4 -4
  124. package/dist/generated/prisma/internal/prismaNamespace.d.ts +1746 -577
  125. package/dist/generated/prisma/internal/prismaNamespace.js +179 -6
  126. package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +186 -0
  127. package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +179 -6
  128. package/dist/generated/prisma/models/Agent.d.ts +294 -1
  129. package/dist/generated/prisma/models/AgentRepository.d.ts +1425 -0
  130. package/dist/generated/prisma/models/AgentRepository.js +1 -0
  131. package/dist/generated/prisma/models/AgentTool.d.ts +95 -1
  132. package/dist/generated/prisma/models/AuthUser.d.ts +56 -1
  133. package/dist/generated/prisma/models/CodingAgentProfile.d.ts +258 -7
  134. package/dist/generated/prisma/models/CodingProxySession.d.ts +123 -2
  135. package/dist/generated/prisma/models/CodingRun.d.ts +1276 -95
  136. package/dist/generated/prisma/models/CodingService.d.ts +1348 -0
  137. package/dist/generated/prisma/models/CodingService.js +1 -0
  138. package/dist/generated/prisma/models/HostEventDelivery.d.ts +946 -0
  139. package/dist/generated/prisma/models/HostEventDelivery.js +1 -0
  140. package/dist/generated/prisma/models/HostIdentity.d.ts +1232 -0
  141. package/dist/generated/prisma/models/HostIdentity.js +1 -0
  142. package/dist/generated/prisma/models/HostIdentityLinkRequest.d.ts +1473 -0
  143. package/dist/generated/prisma/models/HostIdentityLinkRequest.js +1 -0
  144. package/dist/generated/prisma/models/Principal.d.ts +455 -0
  145. package/dist/generated/prisma/models/RegistryAllowance.d.ts +1148 -0
  146. package/dist/generated/prisma/models/RegistryAllowance.js +1 -0
  147. package/dist/generated/prisma/models/RegistryApprovedVersion.d.ts +1219 -0
  148. package/dist/generated/prisma/models/RegistryApprovedVersion.js +1 -0
  149. package/dist/generated/prisma/models/RegistryFetch.d.ts +1428 -0
  150. package/dist/generated/prisma/models/RegistryFetch.js +1 -0
  151. package/dist/generated/prisma/models/RegistryPlanRefusal.d.ts +1294 -0
  152. package/dist/generated/prisma/models/RegistryPlanRefusal.js +1 -0
  153. package/dist/generated/prisma/models/RegistryVersionFact.d.ts +1085 -0
  154. package/dist/generated/prisma/models/RegistryVersionFact.js +1 -0
  155. package/dist/generated/prisma/models/ResourceGrant.d.ts +1437 -0
  156. package/dist/generated/prisma/models/ResourceGrant.js +1 -0
  157. package/dist/generated/prisma/models/Run.d.ts +389 -1
  158. package/dist/generated/prisma/models/RunHostCheck.d.ts +1239 -0
  159. package/dist/generated/prisma/models/RunHostCheck.js +1 -0
  160. package/dist/generated/prisma/models/RunHostStatus.d.ts +1315 -0
  161. package/dist/generated/prisma/models/RunHostStatus.js +1 -0
  162. package/dist/generated/prisma/models/Tool.d.ts +15 -3
  163. package/dist/generated/prisma/models.d.ts +13 -0
  164. package/dist/help/build.d.ts +1 -0
  165. package/dist/help/build.js +9 -0
  166. package/dist/help/catalog.d.ts +24 -0
  167. package/dist/help/catalog.js +160 -0
  168. package/dist/help/cli.d.ts +2 -0
  169. package/dist/help/cli.js +65 -0
  170. package/dist/help/runtime.d.ts +3 -0
  171. package/dist/help/runtime.js +44 -0
  172. package/dist/help/search.d.ts +9 -0
  173. package/dist/help/search.js +104 -0
  174. package/dist/help-index.json +660 -0
  175. package/dist/import/cli-args.js +3 -2
  176. package/dist/import/create.d.ts +5 -0
  177. package/dist/import/create.js +67 -13
  178. package/dist/import/index.js +19 -9
  179. package/dist/import/neutral-schema.d.ts +11 -11
  180. package/dist/mcp/auth/access.d.ts +70 -0
  181. package/dist/mcp/auth/access.js +58 -0
  182. package/dist/mcp/auth/grants-cli.d.ts +149 -0
  183. package/dist/mcp/auth/grants-cli.js +518 -0
  184. package/dist/mcp/auth/host-account-cli.d.ts +2 -0
  185. package/dist/mcp/auth/host-account-cli.js +47 -0
  186. package/dist/mcp/auth/ownership.d.ts +26 -52
  187. package/dist/mcp/auth/ownership.js +19 -14
  188. package/dist/mcp/auth/repo-authorization.d.ts +22 -0
  189. package/dist/mcp/auth/repo-authorization.js +51 -0
  190. package/dist/mcp/auth/resource-server.d.ts +31 -2
  191. package/dist/mcp/auth/resource-server.js +84 -3
  192. package/dist/mcp/auth/self-hosted/browser.js +2 -2
  193. package/dist/mcp/auth/self-hosted/cli.js +47 -13
  194. package/dist/mcp/auth/self-hosted/credentials.d.ts +20 -2
  195. package/dist/mcp/auth/self-hosted/credentials.js +62 -4
  196. package/dist/mcp/auth/self-hosted/session.d.ts +2 -1
  197. package/dist/mcp/context.d.ts +27 -1
  198. package/dist/mcp/errors.d.ts +25 -6
  199. package/dist/mcp/errors.js +98 -0
  200. package/dist/mcp/host-events/github-ingress.d.ts +36 -0
  201. package/dist/mcp/host-events/github-ingress.js +92 -0
  202. package/dist/mcp/host-events/github-user-callback.d.ts +18 -0
  203. package/dist/mcp/host-events/github-user-callback.js +50 -0
  204. package/dist/mcp/index.d.ts +8 -1
  205. package/dist/mcp/index.js +106 -16
  206. package/dist/mcp/server.js +27 -9
  207. package/dist/mcp/tools/agents.js +446 -48
  208. package/dist/mcp/tools/budget-groups.js +3 -3
  209. package/dist/mcp/tools/datastore.js +22 -15
  210. package/dist/mcp/tools/grants.d.ts +2 -0
  211. package/dist/mcp/tools/grants.js +239 -0
  212. package/dist/mcp/tools/help.d.ts +5 -0
  213. package/dist/mcp/tools/help.js +67 -0
  214. package/dist/mcp/tools/host-accounts.d.ts +2 -0
  215. package/dist/mcp/tools/host-accounts.js +114 -0
  216. package/dist/mcp/tools/memory.d.ts +7 -1
  217. package/dist/mcp/tools/memory.js +5 -5
  218. package/dist/mcp/tools/repositories.d.ts +2 -0
  219. package/dist/mcp/tools/repositories.js +181 -0
  220. package/dist/mcp/tools/runs.d.ts +6 -0
  221. package/dist/mcp/tools/runs.js +60 -6
  222. package/dist/mcp/tools/scheduling.js +3 -6
  223. package/dist/mcp/tools/secrets.js +18 -6
  224. package/dist/mcp/tools/services.d.ts +2 -0
  225. package/dist/mcp/tools/services.js +222 -0
  226. package/dist/mcp/tools/subagents.js +52 -16
  227. package/dist/mcp/tools/tools.d.ts +41 -0
  228. package/dist/mcp/tools/tools.js +201 -40
  229. package/dist/mcp/tools/trigger.js +36 -6
  230. package/dist/mcp/tools/webhooks.js +15 -4
  231. package/dist/mcp/transport/streamable-http.d.ts +10 -0
  232. package/dist/mcp/transport/streamable-http.js +32 -2
  233. package/dist/providers/auth/delegating.d.ts +19 -0
  234. package/dist/providers/auth/delegating.js +71 -0
  235. package/dist/providers/auth/index.d.ts +2 -0
  236. package/dist/providers/auth/index.js +11 -0
  237. package/dist/providers/auth/self-hosted.d.ts +2 -1
  238. package/dist/providers/auth/self-hosted.js +16 -4
  239. package/dist/providers/auth/types.d.ts +6 -0
  240. package/dist/providers/coding-proxy/memory-ledger.d.ts +3 -0
  241. package/dist/providers/coding-proxy/memory-ledger.js +21 -2
  242. package/dist/providers/coding-proxy/metering.d.ts +4 -0
  243. package/dist/providers/coding-proxy/metering.js +28 -2
  244. package/dist/providers/coding-proxy/prisma-ledger.d.ts +17 -0
  245. package/dist/providers/coding-proxy/prisma-ledger.js +50 -5
  246. package/dist/providers/coding-proxy/proxy.d.ts +13 -0
  247. package/dist/providers/coding-proxy/proxy.js +479 -18
  248. package/dist/providers/coding-proxy/registry/audit.d.ts +131 -0
  249. package/dist/providers/coding-proxy/registry/audit.js +380 -0
  250. package/dist/providers/coding-proxy/registry/bounded-fetch.d.ts +16 -0
  251. package/dist/providers/coding-proxy/registry/bounded-fetch.js +77 -0
  252. package/dist/providers/coding-proxy/registry/plan.d.ts +58 -0
  253. package/dist/providers/coding-proxy/registry/plan.js +304 -0
  254. package/dist/providers/coding-proxy/registry/prisma-store.d.ts +34 -0
  255. package/dist/providers/coding-proxy/registry/prisma-store.js +151 -0
  256. package/dist/providers/coding-proxy/registry/service.d.ts +213 -0
  257. package/dist/providers/coding-proxy/registry/service.js +1137 -0
  258. package/dist/providers/coding-proxy/registry/store.d.ts +127 -0
  259. package/dist/providers/coding-proxy/registry/store.js +70 -0
  260. package/dist/providers/coding-proxy/runtime.d.ts +17 -0
  261. package/dist/providers/coding-proxy/runtime.js +63 -0
  262. package/dist/providers/coding-proxy/secure-fetch.js +0 -1
  263. package/dist/providers/coding-proxy/server.d.ts +11 -0
  264. package/dist/providers/coding-proxy/server.js +163 -0
  265. package/dist/providers/coding-proxy/types.d.ts +15 -0
  266. package/dist/providers/engine/types.d.ts +10 -0
  267. package/dist/providers/executor/build.d.ts +2 -2
  268. package/dist/providers/executor/composition.d.ts +3 -0
  269. package/dist/providers/executor/composition.js +19 -0
  270. package/dist/providers/executor/container.d.ts +105 -3
  271. package/dist/providers/executor/container.js +296 -20
  272. package/dist/providers/executor/dbos.d.ts +2 -3
  273. package/dist/providers/executor/in-process.d.ts +2 -3
  274. package/dist/providers/executor/routing.d.ts +7 -0
  275. package/dist/providers/executor/routing.js +13 -0
  276. package/dist/providers/executor/types.d.ts +22 -0
  277. package/dist/providers/jobs/claude-tool-setup.d.ts +23 -0
  278. package/dist/providers/jobs/claude-tool-setup.js +50 -0
  279. package/dist/providers/jobs/collect-prune.d.ts +7 -0
  280. package/dist/providers/jobs/collect-prune.js +27 -0
  281. package/dist/providers/jobs/docker-isolation.d.ts +48 -3
  282. package/dist/providers/jobs/docker-isolation.js +213 -22
  283. package/dist/providers/jobs/docker-services.d.ts +35 -0
  284. package/dist/providers/jobs/docker-services.js +191 -0
  285. package/dist/providers/jobs/docker.d.ts +49 -2
  286. package/dist/providers/jobs/docker.js +279 -32
  287. package/dist/providers/jobs/fake-kubernetes-api.d.ts +1 -0
  288. package/dist/providers/jobs/fake-kubernetes-api.js +12 -3
  289. package/dist/providers/jobs/kubernetes-isolation.d.ts +17 -2
  290. package/dist/providers/jobs/kubernetes-isolation.js +238 -57
  291. package/dist/providers/jobs/kubernetes-platform.d.ts +10 -4
  292. package/dist/providers/jobs/kubernetes-platform.js +11 -5
  293. package/dist/providers/jobs/kubernetes-preflight.js +3 -0
  294. package/dist/providers/jobs/kubernetes.d.ts +21 -2
  295. package/dist/providers/jobs/kubernetes.js +112 -21
  296. package/dist/providers/jobs/types.d.ts +18 -0
  297. package/dist/providers/llm/anthropic.js +6 -2
  298. package/dist/providers/llm/bedrock.js +2 -1
  299. package/dist/providers/llm/claude-messages.d.ts +5 -1
  300. package/dist/providers/llm/claude-messages.js +1 -0
  301. package/dist/providers/llm/claude-provider.d.ts +3 -1
  302. package/dist/providers/llm/claude-provider.js +5 -1
  303. package/dist/providers/llm/index.d.ts +1 -1
  304. package/dist/providers/llm/index.js +1 -1
  305. package/dist/providers/llm/pricing-anthropic.d.ts +10 -0
  306. package/dist/providers/llm/pricing-anthropic.js +32 -14
  307. package/dist/providers/llm/pricing-bedrock-claude.d.ts +8 -0
  308. package/dist/providers/llm/pricing-bedrock-claude.js +9 -0
  309. package/dist/providers/llm/routing.d.ts +10 -1
  310. package/dist/providers/llm/routing.js +18 -0
  311. package/dist/providers/llm/types.d.ts +12 -0
  312. package/dist/providers/llm/types.js +8 -1
  313. package/dist/providers/review-host/diff-lines.d.ts +16 -0
  314. package/dist/providers/review-host/diff-lines.js +59 -0
  315. package/dist/providers/review-host/github-events.d.ts +6 -0
  316. package/dist/providers/review-host/github-events.js +180 -0
  317. package/dist/providers/review-host/github-user-auth.d.ts +38 -0
  318. package/dist/providers/review-host/github-user-auth.js +128 -0
  319. package/dist/providers/review-host/github.d.ts +57 -0
  320. package/dist/providers/review-host/github.js +567 -0
  321. package/dist/providers/review-host/index.d.ts +12 -0
  322. package/dist/providers/review-host/index.js +30 -0
  323. package/dist/providers/review-host/review-format.d.ts +19 -0
  324. package/dist/providers/review-host/review-format.js +59 -0
  325. package/dist/providers/review-host/types.d.ts +284 -0
  326. package/dist/providers/review-host/types.js +24 -0
  327. package/dist/providers/vcs/git.d.ts +16 -6
  328. package/dist/providers/vcs/git.js +77 -13
  329. package/dist/providers/vcs/github.d.ts +74 -3
  330. package/dist/providers/vcs/github.js +195 -15
  331. package/dist/providers/vcs/types.d.ts +42 -4
  332. package/dist/quickstart/index.js +13 -2
  333. package/dist/sandbox/fetch-policy.d.ts +20 -2
  334. package/dist/sandbox/fetch-policy.js +64 -3
  335. package/dist/sandbox/host-functions.d.ts +9 -1
  336. package/dist/sandbox/host-functions.js +12 -5
  337. package/dist/serve.js +1 -1
  338. package/dist/wardby-bin.js +6 -0
  339. package/docs/README.md +30 -0
  340. package/docs/architecture-runtime.md +90 -0
  341. package/docs/assets/brand/wardby-icon-512.png +0 -0
  342. package/docs/assets/brand/wardby-icon.svg +16 -0
  343. package/docs/assets/brand/wardby-mascot-profile-512.png +0 -0
  344. package/docs/assets/brand/wardby-mascot.png +0 -0
  345. package/docs/assets/brand/wardby-mascot.svg +5 -0
  346. package/docs/assets/wardby-workflow.svg +106 -0
  347. package/docs/code-review-agents.md +481 -0
  348. package/docs/coding-agent-setup.md +172 -0
  349. package/docs/coding-packages.md +455 -0
  350. package/docs/coding-services.md +300 -0
  351. package/docs/coding-worker-byo-images.md +98 -0
  352. package/docs/coding-worker-isolation.md +1061 -0
  353. package/docs/getting-started-gke.md +615 -0
  354. package/docs/getting-started-identity-provider.md +308 -0
  355. package/docs/getting-started.md +133 -0
  356. package/docs/observability.md +53 -0
  357. package/docs/release-verification.md +66 -0
  358. package/docs/security-deployment.md +657 -0
  359. package/help/code-review-agents.md +31 -0
  360. package/help/coding-packages.md +31 -0
  361. package/help/coding-services.md +71 -0
  362. package/help/creating-agents.md +68 -0
  363. package/help/deploy-gke.md +39 -0
  364. package/help/deployment-targets.md +39 -0
  365. package/help/errors/budget-group-exhausted.md +25 -0
  366. package/help/errors/docker-isolation-unsupported.md +26 -0
  367. package/help/errors/protected-path.md +45 -0
  368. package/help/errors/repo-access.md +27 -0
  369. package/help/errors/service-declaration-invalid.md +27 -0
  370. package/help/errors/service-declaration-unavailable.md +25 -0
  371. package/help/errors/service-launcher-unsupported.md +30 -0
  372. package/help/errors/service-not-allowed.md +27 -0
  373. package/help/errors/service-unknown.md +23 -0
  374. package/help/errors/service-unready.md +49 -0
  375. package/help/getting-started.md +31 -0
  376. package/help/github.md +30 -0
  377. package/help/identity-and-access.md +39 -0
  378. package/help/mcp.md +30 -0
  379. package/help/native-capabilities.md +30 -0
  380. package/help/observability.md +41 -0
  381. package/help/operating-agents.md +30 -0
  382. package/help/security.md +27 -0
  383. package/help/troubleshooting/budgets.md +26 -0
  384. package/help/troubleshooting/coding-workers.md +47 -0
  385. package/help/troubleshooting/repository-access.md +27 -0
  386. package/package.json +12 -3
  387. package/prisma/migrations/20260925010000_coding_collect_exclude/migration.sql +8 -0
  388. package/prisma/migrations/20260925015000_allowed_egress_default/migration.sql +8 -0
  389. package/prisma/migrations/20260925020000_coding_package_registry/migration.sql +55 -0
  390. package/prisma/migrations/20260925030000_tool_name_per_owner/migration.sql +11 -0
  391. package/prisma/migrations/20260926010000_run_trigger_host_event/migration.sql +7 -0
  392. package/prisma/migrations/20260926020000_code_review_hosts/migration.sql +53 -0
  393. package/prisma/migrations/20260926030000_auth_user_roles/migration.sql +9 -0
  394. package/prisma/migrations/20260926040000_agent_effort/migration.sql +6 -0
  395. package/prisma/migrations/20260926050000_registry_lockfile_plan/migration.sql +32 -0
  396. package/prisma/migrations/20260926050000_repo_access_authorization/migration.sql +82 -0
  397. package/prisma/migrations/20260926060000_registry_plan_refusal/migration.sql +19 -0
  398. package/prisma/migrations/20260926100000_registry_plan_refusal_published_at/migration.sql +5 -0
  399. package/prisma/migrations/20260926190000_run_host_status/migration.sql +18 -0
  400. package/prisma/migrations/20260926210000_run_host_status_at_dispatch/migration.sql +5 -0
  401. package/prisma/migrations/20260927010000_resource_grants/migration.sql +72 -0
  402. package/prisma/migrations/20260927020000_proxy_session_budget_exhausted/migration.sql +3 -0
  403. package/prisma/migrations/20260927030000_coding_debug_trace/migration.sql +4 -0
  404. package/prisma/migrations/20260927040000_proxy_session_upstream_failure/migration.sql +3 -0
  405. package/prisma/migrations/20260927050000_coding_run_services/migration.sql +74 -0
  406. package/prisma/migrations/20260928000000_coding_run_tool_image/migration.sql +5 -0
  407. package/prisma/schema.prisma +407 -21
@@ -1,14 +1,23 @@
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 { normalizeCollectExclusions } from "../../coding/collect-exclude.js";
4
5
  import { CodingProfileSchema } from "../../coding/profile.js";
6
+ import { parseStoredServices, storedServiceNames, workerServices } from "../../coding/services/catalog.js";
7
+ import { MAX_SERVICE_DECLARATION_BYTES, SERVICE_DECLARATION_PATH } from "../../coding/services/declaration.js";
8
+ import { SERVICE_UNREADY_CATEGORY, SERVICE_UNREADY_ERROR, serviceUnreadyName, serviceUnreadySentence, } from "../../coding/services/wording.js";
9
+ import { PROTECTED_PATH_CATEGORY, protectedPathFromError, protectedPathSentence, } from "../../coding/protected-path-wording.js";
10
+ import { budgetSentence } from "../../core/budget-wording.js";
11
+ import { classifyProviderFailure, providerClassOfCategory, providerSentence, } from "../../core/provider-wording.js";
5
12
  import { logger } from "../../core/logger.js";
13
+ import { requiredLevel } from "../../core/repo-access.js";
6
14
  import { ensurePrivateDirectory } from "../../core/private-directory.js";
7
15
  import { codingRunObserver } from "../../coding/observability.js";
8
16
  import { CODING_PROTOCOL_VERSION, CodingRunResultSchema, CodingTaskInputSchema, parseCodingAgentOutputJson, redactAndTruncate, } from "../../coding/protocol.js";
9
17
  import { getModelPricing } from "../llm/pricing.js";
10
18
  import { getAnthropicPricing } from "../llm/pricing-anthropic.js";
11
- import { isImmutableDockerImage } from "../jobs/docker-isolation.js";
19
+ import { CODING_PROXY_ALIAS, CODING_PROXY_PORT, isImmutableDockerImage } from "../jobs/docker-isolation.js";
20
+ import { normalizeRegistryLockfiles } from "../../coding/registry/lockfiles.js";
12
21
  import { HEARTBEAT_TIMEOUT_MS } from "../../core/timing.js";
13
22
  const containerLog = logger.child({ module: "container-executor" });
14
23
  const TERMINAL_STATUSES = new Set(["succeeded", "failed", "refused", "lost", "budget_exhausted", "cancelled"]);
@@ -33,6 +42,19 @@ function proxyProtocol(provider) {
33
42
  return "anthropic-messages";
34
43
  throw new Error("coding_provider_unsupported");
35
44
  }
45
+ /**
46
+ * Claude Code's commands run in its tool runner, which never reports back through the agent, so the
47
+ * host rewrites registry-proxy download URLs in collected lockfiles itself (the Codex driver does it
48
+ * in the worker). Best effort and symlink-safe (lockfiles.ts); a Codex workspace is left alone.
49
+ */
50
+ export async function normalizeCollectedLockfiles(provider, workspacePath) {
51
+ if (provider !== "claude-code")
52
+ return [];
53
+ return normalizeRegistryLockfiles({
54
+ workspace: workspacePath,
55
+ proxyBaseUrl: `http://${CODING_PROXY_ALIAS}:${CODING_PROXY_PORT}`,
56
+ });
57
+ }
36
58
  export class PrismaContainerExecutionStore {
37
59
  db;
38
60
  options;
@@ -43,7 +65,10 @@ export class PrismaContainerExecutionStore {
43
65
  async load(runId) {
44
66
  const row = await this.db.run.findUnique({
45
67
  where: { id: runId },
46
- include: { agent: true, codingRun: { include: { proxySession: true } } },
68
+ include: {
69
+ agent: { include: { codingProfile: true, budgetGroup: { select: { name: true } } } },
70
+ codingRun: { include: { proxySession: true } },
71
+ },
47
72
  });
48
73
  if (!row?.codingRun)
49
74
  return null;
@@ -60,10 +85,12 @@ export class PrismaContainerExecutionStore {
60
85
  provider: row.codingRun.provider,
61
86
  model: row.codingRun.model,
62
87
  timeoutSec: row.codingRun.timeoutSec,
63
- allowedEgress: row.codingRun.allowedEgress,
64
88
  protectedPaths: row.codingRun.protectedPaths,
89
+ collectExclude: row.codingRun.collectExclude,
65
90
  rootCodingRunId: row.codingRun.rootCodingRunId,
66
91
  budgetUsd: Number(row.codingRun.budgetReservedUsd),
92
+ agentBudgetUsd: Number(row.agent.budgetUsd),
93
+ budgetGroupName: row.agent.budgetGroup?.name ?? null,
67
94
  tokensIn: row.tokensIn,
68
95
  tokensOut: row.tokensOut,
69
96
  costUsd: Number(row.costUsd),
@@ -74,7 +101,13 @@ export class PrismaContainerExecutionStore {
74
101
  proxySessionId: row.codingRun.proxySession?.id ?? null,
75
102
  result: row.codingRun.result,
76
103
  workerImage: row.codingRun.workerImage,
104
+ toolImage: row.codingRun.toolImage,
77
105
  workspaceDiskMb: row.codingRun.workspaceDiskMb,
106
+ debugTrace: row.codingRun.debugTrace,
107
+ services: row.codingRun.services,
108
+ failureCategory: row.codingRun.failureCategory,
109
+ profileRepository: row.agent.codingProfile?.repository ?? null,
110
+ repositoryAuthorizedVia: row.agent.codingProfile?.repositoryAuthorizedVia ?? null,
78
111
  };
79
112
  }
80
113
  async claimProvisioning(runId, claimId) {
@@ -247,6 +280,9 @@ export class RunCapabilityVault {
247
280
  }
248
281
  class PreflightError extends Error {
249
282
  }
283
+ /** A refusal by repository authorization; its message is the failure category's prefix (repo_access_*). */
284
+ class RepoAccessError extends PreflightError {
285
+ }
250
286
  export class ContainerExecutor {
251
287
  options;
252
288
  artifactRoot;
@@ -273,6 +309,12 @@ export class ContainerExecutor {
273
309
  if (image !== undefined && !isImmutableDockerImage(image))
274
310
  throw new Error("coding_worker_image_invalid");
275
311
  }
312
+ for (const versions of Object.values(options.claudeToolRunnerImages ?? {})) {
313
+ for (const image of Object.values(versions)) {
314
+ if (!isImmutableDockerImage(image))
315
+ throw new Error("coding_worker_image_invalid");
316
+ }
317
+ }
276
318
  if (!/^[A-Za-z][A-Za-z0-9_.:-]{0,199}$/.test(options.credentialRef)) {
277
319
  throw new Error("coding_credential_ref_invalid");
278
320
  }
@@ -397,6 +439,9 @@ export class ContainerExecutor {
397
439
  if ((await this.options.store.claimProvisioning(runId, claimId)) !== "claimed")
398
440
  return;
399
441
  }
442
+ // A launched run (recovered after a restart) is in flight: a transient
443
+ // host error is retried once before it is stopped.
444
+ await this.authorizeRepository(run, { inFlight: Boolean(handle) });
400
445
  try {
401
446
  workspace = await this.options.vcs.recoverWorkspace(prepared);
402
447
  if (!workspace && handle)
@@ -474,8 +519,13 @@ export class ContainerExecutor {
474
519
  await this.options.sessions.cancelSession(sessionId).catch(() => undefined);
475
520
  if (handle)
476
521
  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);
522
+ const outOfBudget = spendEnabled && (await this.outOfBudget(sessionId));
523
+ const status = outOfBudget
524
+ ? "budget_exhausted"
525
+ : !spendEnabled && error instanceof PreflightError
526
+ ? "refused"
527
+ : "failed";
528
+ const failure = outOfBudget ? this.budgetFailure(error) : this.failure(error);
479
529
  this.emit({
480
530
  stage: "stopping",
481
531
  runId,
@@ -487,9 +537,17 @@ export class ContainerExecutor {
487
537
  this.terminal(run, status, failure.audit);
488
538
  if (handle)
489
539
  await this.options.jobs.remove(handle).catch(() => undefined);
540
+ // A service sidecar that never became ready (kubernetes.ts waitForKeeper) is named on the host.
541
+ const unreadyService = outOfBudget ? null : serviceUnreadyName(error);
542
+ const protectedPath = outOfBudget ? null : protectedPathFromError(error);
490
543
  if (workspace) {
491
544
  await this.options.vcs.cleanup(workspace).catch(() => undefined);
492
- await this.options.vcs.notifyContinuationFinished?.(workspace, "failed", { agentName: run.agentName });
545
+ await this.options.vcs.notifyContinuationFinished?.(workspace, outOfBudget ? "budget_exhausted" : "failed", {
546
+ agentName: run.agentName,
547
+ ...(outOfBudget ? { budgetSentence: this.budgetSentence(run) } : {}),
548
+ ...(unreadyService ? { serviceSentence: serviceUnreadySentence([unreadyService]) } : {}),
549
+ ...(protectedPath ? { protectedPathSentence: protectedPathSentence(protectedPath) } : {}),
550
+ });
493
551
  }
494
552
  this.emit({ stage: "cleanup", runId, jobId: handle?.id, cleanupSucceeded: true });
495
553
  }
@@ -531,6 +589,26 @@ export class ContainerExecutor {
531
589
  clearInterval(timer);
532
590
  }
533
591
  }
592
+ /**
593
+ * The run may use its repository only if the agent's current owner still
594
+ * has write on it, or the profile's approval (admin or grandfathered)
595
+ * covers exactly this repository. A profile changed since dispatch no
596
+ * longer covers the run's repository, so that falls back to the owner's
597
+ * access. Throws RepoAccessError (refused before anything is cloned).
598
+ */
599
+ async authorizeRepository(run, opts) {
600
+ const sameRepository = run.profileRepository !== null && run.profileRepository === run.repository;
601
+ const decision = await this.options.repoAccess.authorizeUse({
602
+ ownerId: run.ownerId,
603
+ provider: "github",
604
+ repository: run.repository,
605
+ required: requiredLevel("coding"),
606
+ authorizedVia: sameRepository ? run.repositoryAuthorizedVia : "host_permission",
607
+ retryTransient: opts.inFlight,
608
+ });
609
+ if (!decision.ok)
610
+ throw new RepoAccessError(`repo_access_${decision.reason}`);
611
+ }
534
612
  preflight(run) {
535
613
  try {
536
614
  if (run.status !== "pending" && run.status !== "running")
@@ -552,7 +630,6 @@ export class ContainerExecutor {
552
630
  baseRef: run.baseRef,
553
631
  defaultTask: null,
554
632
  timeoutSec: run.timeoutSec,
555
- allowedEgress: run.allowedEgress,
556
633
  protectedPaths: run.protectedPaths,
557
634
  });
558
635
  const expectedHeadRunId = run.rootCodingRunId ?? run.runId;
@@ -577,6 +654,7 @@ export class ContainerExecutor {
577
654
  baseRef: profile.baseRef,
578
655
  headRef: run.headRef,
579
656
  protectedPaths: profile.protectedPaths,
657
+ collectExclude: [...normalizeCollectExclusions(run.collectExclude).paths],
580
658
  continuation: run.rootCodingRunId ? { rootRunId: run.rootCodingRunId } : undefined,
581
659
  };
582
660
  }
@@ -589,16 +667,34 @@ export class ContainerExecutor {
589
667
  if (sessionId)
590
668
  await this.options.sessions.cancelSession(sessionId).catch(() => undefined);
591
669
  // For notifyContinuationFinished in the `finally` below -- defaults to
592
- // "failed" and is only flipped right before an actual success return.
670
+ // "failed" and is only flipped right before a success or out-of-budget return.
593
671
  let outcome = "failed";
594
672
  let finishedSummary;
673
+ // Set when a failed job's session relayed a model-provider failure; names it on the host.
674
+ // Only a failed job: a transient provider error the worker retried past did not end the run.
675
+ let providerClass;
676
+ // Set when finalizeChanges refused the collected diff for touching a protected path; names it on the host.
677
+ let protectedPath;
595
678
  try {
596
679
  if (jobState !== "succeeded") {
597
680
  const collected = await this.options.jobs.collect(handle).catch(() => null);
598
681
  const reason = collected?.diagnostic ?? collected?.reason ?? jobState;
599
682
  this.emit({ stage: "collected", runId: run.runId, jobId: handle.id });
683
+ // The worker usually fails outright when the proxy refuses a model
684
+ // request for budget; the session records that refusal.
685
+ if (await this.outOfBudget(sessionId)) {
686
+ const failure = this.budgetFailure(`job_${reason}`, { issues: collected?.diagnosticIssues });
687
+ await this.options.store.terminate(run.runId, "budget_exhausted", failure.error, failure.audit);
688
+ this.terminal(run, "budget_exhausted", failure.audit);
689
+ outcome = "budget_exhausted";
690
+ return;
691
+ }
600
692
  const status = jobState === "lost" ? "lost" : "failed";
601
- const failure = this.failure(`job_${reason}`, { issues: collected?.diagnosticIssues });
693
+ const provider = status === "failed" ? await this.providerFailure(sessionId) : null;
694
+ const failure = provider
695
+ ? this.providerFailureOf(run, provider, `job_${reason}`, { issues: collected?.diagnosticIssues })
696
+ : this.failure(`job_${reason}`, { issues: collected?.diagnosticIssues });
697
+ providerClass = provider?.class;
602
698
  await this.options.store.terminate(run.runId, status, failure.error, failure.audit);
603
699
  this.terminal(run, status, failure.audit);
604
700
  return;
@@ -615,6 +711,7 @@ export class ContainerExecutor {
615
711
  this.emit({ stage: "budget_cutoff", runId: run.runId, jobId: handle.id });
616
712
  await this.options.store.complete(run.runId, "budget_exhausted", this.resultFor(output, current, "budget_exhausted"));
617
713
  this.terminal(current, "budget_exhausted");
714
+ outcome = "budget_exhausted";
618
715
  return;
619
716
  }
620
717
  if (output.outcome === "no_changes") {
@@ -629,17 +726,24 @@ export class ContainerExecutor {
629
726
  if (!workspace)
630
727
  throw new Error("coding_workspace_lost");
631
728
  await this.options.jobs.materializeWorkspace(handle, workspace.workspacePath);
729
+ await normalizeCollectedLockfiles(current.provider, workspace.workspacePath);
632
730
  current = await this.requireCurrent(run.runId);
633
731
  if (current.costUsd > current.budgetUsd) {
634
732
  this.emit({ stage: "budget_cutoff", runId: run.runId, jobId: handle.id });
635
733
  await this.options.store.complete(run.runId, "budget_exhausted", this.resultFor(output, current, "budget_exhausted"));
636
734
  this.terminal(current, "budget_exhausted");
735
+ outcome = "budget_exhausted";
637
736
  return;
638
737
  }
738
+ // One more (usually cached) check right before anything is pushed: access
739
+ // revoked while the run was working stops the push.
740
+ await this.authorizeRepository(current, { inFlight: true });
741
+ const report = await this.loadRegistryReport(run.runId);
639
742
  const finalized = await this.options.vcs.finalizeChanges(workspace, {
640
743
  summary: output.summary,
641
744
  tests: output.tests,
642
745
  tag: output.tag,
746
+ ...report,
643
747
  });
644
748
  const result = this.resultFor(output, current, finalized.outcome, finalized);
645
749
  await this.options.store.complete(run.runId, "succeeded", result);
@@ -651,9 +755,18 @@ export class ContainerExecutor {
651
755
  finishedSummary = output.summary;
652
756
  }
653
757
  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);
758
+ if (await this.outOfBudget(sessionId)) {
759
+ const failure = this.budgetFailure(error);
760
+ await this.options.store.terminate(run.runId, "budget_exhausted", failure.error, failure.audit);
761
+ this.terminal(run, "budget_exhausted", failure.audit);
762
+ outcome = "budget_exhausted";
763
+ }
764
+ else {
765
+ const failure = this.failure(error);
766
+ await this.options.store.terminate(run.runId, "failed", failure.error, failure.audit);
767
+ this.terminal(run, "failed", failure.audit);
768
+ protectedPath = protectedPathFromError(error) ?? undefined;
769
+ }
657
770
  }
658
771
  finally {
659
772
  await this.options.jobs.remove(handle).catch(() => undefined);
@@ -664,12 +777,34 @@ export class ContainerExecutor {
664
777
  await this.options.vcs.notifyContinuationFinished?.(workspace, outcome, {
665
778
  summary: finishedSummary,
666
779
  agentName: run.agentName,
780
+ ...(outcome === "budget_exhausted" ? { budgetSentence: this.budgetSentence(run) } : {}),
781
+ ...(outcome === "failed" && providerClass ? { providerSentence: providerSentence(providerClass) } : {}),
782
+ ...(outcome === "failed" && protectedPath
783
+ ? { protectedPathSentence: protectedPathSentence(protectedPath) }
784
+ : {}),
667
785
  });
668
786
  }
669
787
  await rm(this.artifactPath(run.runId), { recursive: true, force: true }).catch(() => undefined);
670
788
  this.emit({ stage: "cleanup", runId: run.runId, jobId: handle.id, cleanupSucceeded: true });
671
789
  }
672
790
  }
791
+ /** The package report is informational: any failure loading it (a
792
+ * rejection, a synchronous throw, or a malformed result) yields no
793
+ * package section rather than failing finalization. */
794
+ async loadRegistryReport(runId) {
795
+ try {
796
+ const report = await this.options.registryReport?.(runId);
797
+ const packages = Array.isArray(report?.packages) ? report.packages : [];
798
+ const packageRefusals = Array.isArray(report?.packageRefusals) ? report.packageRefusals : [];
799
+ return {
800
+ ...(packages.length > 0 ? { packages } : {}),
801
+ ...(packageRefusals.length > 0 ? { packageRefusals } : {}),
802
+ };
803
+ }
804
+ catch {
805
+ return {};
806
+ }
807
+ }
673
808
  resultFor(output, run, forcedOutcome, finalized) {
674
809
  const outcome = forcedOutcome ?? (output.outcome === "budget_exhausted" ? "budget_exhausted" : "no_changes");
675
810
  return CodingRunResultSchema.parse({
@@ -694,15 +829,12 @@ export class ContainerExecutor {
694
829
  }
695
830
  resolveCodingWorkerImage(selector) {
696
831
  if (selector.provider === "claude-code") {
697
- if (selector.toolchain !== "node" || selector.toolchainVersion !== null) {
698
- throw new Error("coding_toolchain_unsupported:claude-code");
699
- }
832
+ this.claudeToolImage(selector);
700
833
  const image = selector.workerImageRef ?? this.options.claudeWorkerImage;
701
- if (!image || !this.options.claudeToolRunnerImage)
834
+ if (!image)
702
835
  throw new Error("coding_provider_not_configured:claude-code");
703
- if (!isImmutableDockerImage(image) || !isImmutableDockerImage(this.options.claudeToolRunnerImage)) {
836
+ if (!isImmutableDockerImage(image))
704
837
  throw new Error("coding_worker_image_invalid");
705
- }
706
838
  return image;
707
839
  }
708
840
  if (selector.provider !== "codex") {
@@ -722,6 +854,40 @@ export class ContainerExecutor {
722
854
  }
723
855
  return image;
724
856
  }
857
+ /** Claude's tool-runner image for the agent's toolchain, at dispatch (Executor.resolveCodingToolImage); null for Codex. */
858
+ resolveCodingToolImage(selector) {
859
+ return selector.provider === "claude-code" ? this.claudeToolImage(selector) : null;
860
+ }
861
+ /** The tool runner is what runs a Claude agent's commands, so it is what the toolchain selects. */
862
+ claudeToolImage(selector) {
863
+ if (!this.options.claudeToolRunnerImage)
864
+ throw new Error("coding_provider_not_configured:claude-code");
865
+ const image = selector.toolchain === "node" && selector.toolchainVersion === null
866
+ ? this.options.claudeToolRunnerImage
867
+ : selector.toolchainVersion !== null
868
+ ? this.options.claudeToolRunnerImages?.[selector.toolchain]?.[selector.toolchainVersion]
869
+ : undefined;
870
+ if (!image)
871
+ throw new Error("coding_toolchain_unsupported:claude-code");
872
+ if (!isImmutableDockerImage(image))
873
+ throw new Error("coding_worker_image_invalid");
874
+ return image;
875
+ }
876
+ /** Coding-run services: the repository's declaration at its base ref (Executor.readCodingServiceDeclaration). */
877
+ async readCodingServiceDeclaration(input) {
878
+ if (!this.options.vcs.readRepositoryFile)
879
+ return null;
880
+ return this.options.vcs.readRepositoryFile({
881
+ repository: input.repository,
882
+ ref: input.baseRef,
883
+ path: SERVICE_DECLARATION_PATH,
884
+ maxBytes: MAX_SERVICE_DECLARATION_BYTES,
885
+ });
886
+ }
887
+ /** The job launcher decides (Kubernetes and Docker start services for both providers). */
888
+ supportsCodingServices(provider) {
889
+ return this.options.jobs.supportsServicesFor?.(provider) === true;
890
+ }
725
891
  async requireCurrent(runId) {
726
892
  const current = await this.options.store.load(runId);
727
893
  if (!current)
@@ -736,16 +902,24 @@ export class ContainerExecutor {
736
902
  if (run.workspaceDiskMb && run.workspaceDiskMb > this.options.maxDiskMb) {
737
903
  throw new Error("coding_workspace_disk_exceeds_limit");
738
904
  }
905
+ const services = parseStoredServices(run.services);
906
+ // Dispatch refuses services this deployment can't start; this is the backstop.
907
+ if (services.length > 0 && !this.supportsCodingServices(provider)) {
908
+ throw new Error("coding_services_unsupported_launcher");
909
+ }
739
910
  return {
740
911
  kind: "coding-agent",
741
912
  runId: run.runId,
742
913
  provider,
743
914
  image: run.workerImage ?? this.options.workerImage,
744
- ...(provider === "claude-code" ? { toolImage: this.options.claudeToolRunnerImage } : {}),
915
+ // A run keeps the tool image it was dispatched with; rows from before CodingRun.toolImage use the default.
916
+ ...(provider === "claude-code" ? { toolImage: run.toolImage ?? this.options.claudeToolRunnerImage } : {}),
745
917
  inputArtifact,
746
918
  timeoutSec: run.timeoutSec,
747
919
  limits: { ...this.options.limits, ...(run.workspaceDiskMb ? { diskMb: run.workspaceDiskMb } : {}) },
748
920
  labels: {},
921
+ collectExclude: normalizeCollectExclusions(run.collectExclude),
922
+ ...(services.length > 0 ? { services } : {}),
749
923
  };
750
924
  }
751
925
  credentialRef(provider) {
@@ -766,6 +940,7 @@ export class ContainerExecutor {
766
940
  throw new Error("coding_artifact_path_invalid");
767
941
  const destination = join(directory, "input.json");
768
942
  const temporary = join(directory, `.input-${randomUUID()}.tmp`);
943
+ const services = parseStoredServices(run.services);
769
944
  const input = CodingTaskInputSchema.parse({
770
945
  schemaVersion: CODING_PROTOCOL_VERSION,
771
946
  runId: run.runId,
@@ -777,6 +952,10 @@ export class ContainerExecutor {
777
952
  budgetUsd: run.budgetUsd,
778
953
  deadlineAt: deadlineAt.toISOString(),
779
954
  continuationOf: run.rootCodingRunId ? { runId: run.rootCodingRunId } : undefined,
955
+ // Only when true: an untraced run's input stays exactly what older workers expect.
956
+ ...(run.debugTrace ? { debugTrace: true } : {}),
957
+ // Only when there are some: every other run's input stays exactly what older workers expect.
958
+ ...(services.length > 0 ? { services: workerServices(services) } : {}),
780
959
  });
781
960
  // The run directory is 0700; world-readable mode only crosses Docker's UID boundary.
782
961
  await writeFile(temporary, JSON.stringify(input), { flag: "wx", mode: 0o444 });
@@ -789,12 +968,20 @@ export class ContainerExecutor {
789
968
  preflightForCleanup(run) {
790
969
  if (!Array.isArray(run.protectedPaths) || !run.protectedPaths.every((path) => typeof path === "string"))
791
970
  return null;
971
+ let collectExclude;
972
+ try {
973
+ collectExclude = [...normalizeCollectExclusions(run.collectExclude).paths];
974
+ }
975
+ catch {
976
+ return null;
977
+ }
792
978
  return {
793
979
  runId: run.runId,
794
980
  repository: run.repository,
795
981
  baseRef: run.baseRef,
796
982
  headRef: run.headRef,
797
983
  protectedPaths: run.protectedPaths,
984
+ collectExclude,
798
985
  continuation: run.rootCodingRunId ? { rootRunId: run.rootCodingRunId } : undefined,
799
986
  };
800
987
  }
@@ -838,11 +1025,71 @@ export class ContainerExecutor {
838
1025
  // notification actually firing) -- safe to call again because
839
1026
  // notifyContinuationFinished is find-and-update-or-no-op, never
840
1027
  // find-or-create.
841
- await this.options.vcs.notifyContinuationFinished?.(workspace, run.status === "succeeded" ? "succeeded" : "failed", { agentName: run.agentName });
1028
+ const outcome = run.status === "succeeded" ? "succeeded" : run.status === "budget_exhausted" ? "budget_exhausted" : "failed";
1029
+ const providerClass = run.status === "failed" ? providerClassOfCategory(run.failureCategory) : null;
1030
+ const unreadyServices = run.status === "failed" && run.failureCategory === SERVICE_UNREADY_CATEGORY
1031
+ ? storedServiceNames(run.services)
1032
+ : null;
1033
+ // No path survives to the stored run (only the category does -- see
1034
+ // protected-path-wording.ts), so this can only ever use the pathless sentence.
1035
+ const protectedPathFailed = run.status === "failed" && run.failureCategory === PROTECTED_PATH_CATEGORY;
1036
+ await this.options.vcs.notifyContinuationFinished?.(workspace, outcome, {
1037
+ agentName: run.agentName,
1038
+ ...(outcome === "budget_exhausted" ? { budgetSentence: this.budgetSentence(run) } : {}),
1039
+ ...(providerClass ? { providerSentence: providerSentence(providerClass) } : {}),
1040
+ ...(unreadyServices ? { serviceSentence: serviceUnreadySentence(unreadyServices) } : {}),
1041
+ ...(protectedPathFailed ? { protectedPathSentence: protectedPathSentence(null) } : {}),
1042
+ });
842
1043
  }
843
1044
  await rm(this.artifactPath(run.runId), { recursive: true, force: true }).catch(() => undefined);
844
1045
  this.emit({ stage: "cleanup", runId: run.runId, jobId: run.jobHandle?.id, cleanupSucceeded: true });
845
1046
  }
1047
+ /** Whether the proxy refused this run's session for budget. Never throws. */
1048
+ async outOfBudget(sessionId) {
1049
+ if (!sessionId || !this.options.sessions.budgetExhausted)
1050
+ return false;
1051
+ return this.options.sessions.budgetExhausted(sessionId).catch(() => false);
1052
+ }
1053
+ /** The model-provider failure the proxy relayed for this run's session, classified. Never throws. */
1054
+ async providerFailure(sessionId) {
1055
+ if (!sessionId || !this.options.sessions.upstreamFailure)
1056
+ return null;
1057
+ const code = await this.options.sessions.upstreamFailure(sessionId).catch(() => null);
1058
+ return code ? { class: classifyProviderFailure(code), code } : null;
1059
+ }
1060
+ /**
1061
+ * A failure the model provider's refusal caused. The persisted error names
1062
+ * the class only; the raw upstream code (a fixed identifier, never a message)
1063
+ * goes to one operator line an alert can key on, and the failure itself is
1064
+ * still logged under its diagnostic id.
1065
+ */
1066
+ providerFailureOf(run, provider, error, options = {}) {
1067
+ const logged = this.failure(error, options);
1068
+ containerLog.warn({ event: "coding.provider_failure", runId: run.runId, class: provider.class, upstreamCode: provider.code }, "the model provider refused this coding run's requests");
1069
+ return {
1070
+ error: `coding_provider_${provider.class}`,
1071
+ audit: { failureCategory: `provider_${provider.class}`, diagnosticId: logged.audit.diagnosticId },
1072
+ };
1073
+ }
1074
+ /** The host-safe sentence for a run that ran out of budget. */
1075
+ budgetSentence(run) {
1076
+ return budgetSentence({
1077
+ runBudgetUsd: run.budgetUsd,
1078
+ agentBudgetUsd: run.agentBudgetUsd ?? run.budgetUsd,
1079
+ budgetGroupName: run.budgetGroupName,
1080
+ });
1081
+ }
1082
+ /**
1083
+ * A failure the proxy's budget refusal caused. The failure itself is still
1084
+ * logged under its diagnostic id, so the operator keeps the real reason.
1085
+ */
1086
+ budgetFailure(error, options = {}) {
1087
+ const logged = this.failure(error, options);
1088
+ return {
1089
+ error: "coding_budget_exhausted",
1090
+ audit: { failureCategory: "budget", diagnosticId: logged.audit.diagnosticId },
1091
+ };
1092
+ }
846
1093
  emit(event) {
847
1094
  try {
848
1095
  this.observer.emit(event);
@@ -913,7 +1160,36 @@ function safeError(error) {
913
1160
  const message = error instanceof Error ? error.message : "unknown";
914
1161
  return `coding_executor:${redactAndTruncate(message, 200).replace(/[^A-Za-z0-9_.:-]/g, "_")}`;
915
1162
  }
1163
+ /**
1164
+ * Error-code prefixes that name their category outright, checked before the
1165
+ * substring heuristics below. Those heuristics filed every `github_*` error
1166
+ * (a failed PR lookup or create after the push, a token mint) under
1167
+ * "workspace" because "github" contains "git".
1168
+ */
1169
+ const CATEGORY_BY_PREFIX = [
1170
+ // A service sidecar never became ready (src/providers/jobs/kubernetes.ts).
1171
+ [SERVICE_UNREADY_ERROR, SERVICE_UNREADY_CATEGORY],
1172
+ // Services on a launcher that can't start them: refused at dispatch, failed here as a backstop.
1173
+ ["coding_services_unsupported", "preflight"],
1174
+ // Claude Code's tool runner failed to start or never became ready: the same category on both launchers.
1175
+ ["docker_tool_runner_", "job"],
1176
+ ["kubernetes_tool_runner_", "job"],
1177
+ // Checked first: the host could not be asked (after one retry), not a lost permission.
1178
+ ["repo_access_check_unavailable", "repo_access_unavailable"],
1179
+ ["repo_access_", "repo_access"],
1180
+ ["github_", "github"],
1181
+ // Checked before the generic "vcs_" fallback: the collected diff touched a
1182
+ // path this agent may not edit, not a generic workspace/git failure.
1183
+ ["vcs_protected_path:", PROTECTED_PATH_CATEGORY],
1184
+ ["vcs_", "workspace"],
1185
+ ["git_", "workspace"],
1186
+ ];
916
1187
  function failureCategory(error) {
1188
+ const raw = error instanceof Error ? error.message : typeof error === "string" ? error : "";
1189
+ for (const [prefix, category] of CATEGORY_BY_PREFIX) {
1190
+ if (raw.startsWith(prefix))
1191
+ return category;
1192
+ }
917
1193
  const message = safeError(error);
918
1194
  if (message.includes("preflight") || message.includes("ownership") || message.includes("image"))
919
1195
  return "preflight";
@@ -13,12 +13,11 @@
13
13
  * `recover()` (Task 6) before declaring a DBOS run lost.
14
14
  */
15
15
  import type { PrismaClient } from "#prisma";
16
- import type { ProviderRegistry } from "../index.js";
17
16
  import type { DbosConfig } from "../../config/providers.js";
18
- import { type RunnerDb } from "../../core/runner.js";
17
+ import { type NativeRunProviders, type RunnerDb } from "../../core/runner.js";
19
18
  import type { Executor, ExecutionRecoveryResult, PersistedExecutionHandle } from "./types.js";
20
19
  export declare const DBOS_BACKEND = "dbos";
21
- type Providers = Pick<ProviderRegistry, "llm" | "engine" | "datastore" | "secrets" | "memory">;
20
+ type Providers = NativeRunProviders;
22
21
  type Db = RunnerDb & Pick<PrismaClient, "run">;
23
22
  export declare class DbosExecutor implements Executor {
24
23
  private readonly providers;
@@ -5,14 +5,13 @@
5
5
  * heartbeat and recovers the run to `lost` rather than leaving it stuck
6
6
  * `running` forever.
7
7
  */
8
- import type { ProviderRegistry } from "../index.js";
9
- import { type RunnerDb } from "../../core/runner.js";
8
+ import { type NativeRunProviders, type RunnerDb } from "../../core/runner.js";
10
9
  import type { Executor } from "./types.js";
11
10
  export declare class InProcessExecutor implements Executor {
12
11
  private readonly providers;
13
12
  private readonly db;
14
13
  private readonly heartbeatIntervalMs;
15
- constructor(providers: Pick<ProviderRegistry, "llm" | "engine" | "datastore" | "secrets" | "memory">, db?: RunnerDb, heartbeatIntervalMs?: number);
14
+ constructor(providers: NativeRunProviders, db?: RunnerDb, heartbeatIntervalMs?: number);
16
15
  start(runId: string): Promise<void>;
17
16
  stop(_runId: string, _reason?: string): Promise<void>;
18
17
  }
@@ -1,4 +1,5 @@
1
1
  import type { CodingImageSelector, Executor, ExecutionRecoveryResult, PersistedExecutionHandle } from "./types.js";
2
+ import type { CodingProvider } from "../../coding/provider.js";
2
3
  export interface ExecutionKindResolver {
3
4
  kindForRun(runId: string): Promise<"native" | "coding" | null>;
4
5
  }
@@ -21,6 +22,12 @@ export declare class RoutingExecutor implements Executor {
21
22
  launch(): Promise<void>;
22
23
  close(): Promise<void>;
23
24
  resolveCodingWorkerImage(selector: CodingImageSelector): string;
25
+ resolveCodingToolImage(selector: CodingImageSelector): string | null;
26
+ readCodingServiceDeclaration(input: {
27
+ repository: string;
28
+ baseRef: string;
29
+ }): Promise<string | null>;
30
+ supportsCodingServices(provider: CodingProvider): boolean;
24
31
  }
25
32
  export declare class PrismaExecutionKindResolver implements ExecutionKindResolver {
26
33
  private readonly db;
@@ -54,6 +54,19 @@ export class RoutingExecutor {
54
54
  throw new Error("coding_execution_not_configured");
55
55
  return this.coding.resolveCodingWorkerImage(selector);
56
56
  }
57
+ resolveCodingToolImage(selector) {
58
+ if (!this.coding.resolveCodingToolImage)
59
+ throw new Error("coding_execution_not_configured");
60
+ return this.coding.resolveCodingToolImage(selector);
61
+ }
62
+ async readCodingServiceDeclaration(input) {
63
+ if (!this.coding.readCodingServiceDeclaration)
64
+ return null;
65
+ return this.coding.readCodingServiceDeclaration(input);
66
+ }
67
+ supportsCodingServices(provider) {
68
+ return this.coding.supportsCodingServices?.(provider) === true;
69
+ }
57
70
  }
58
71
  export class PrismaExecutionKindResolver {
59
72
  db;
@@ -51,5 +51,27 @@ export interface Executor {
51
51
  * the LLM pricing tables' unknown-model throw.
52
52
  */
53
53
  resolveCodingWorkerImage?(selector: CodingImageSelector): string;
54
+ /**
55
+ * Claude Code's tool-runner image (the container that runs its commands) for the agent's
56
+ * toolchain, resolved and persisted at dispatch alongside the worker image; null for Codex, whose
57
+ * one worker image carries the toolchain. Throws, like resolveCodingWorkerImage, on a toolchain
58
+ * with no image.
59
+ */
60
+ resolveCodingToolImage?(selector: CodingImageSelector): string | null;
61
+ /**
62
+ * Coding-run services (docs/coding-services.md): the raw text of the
63
+ * repository's .wardby/services.yaml at `baseRef`, or null when it has none.
64
+ * Called by dispatch before its transaction (it is a network call). An
65
+ * executor without it gives coding runs no services.
66
+ */
67
+ readCodingServiceDeclaration?(input: {
68
+ repository: string;
69
+ baseRef: string;
70
+ }): Promise<string | null>;
71
+ /**
72
+ * Whether this executor can start coding-run services for a run of `provider`: its job launcher
73
+ * says so (Kubernetes and Docker, for Codex and Claude Code).
74
+ */
75
+ supportsCodingServices?(provider: CodingProvider): boolean;
54
76
  }
55
77
  import type { CodingProvider } from "../../coding/provider.js";