@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,4 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
+ import { normalizeCollectExclusions } from "../../coding/collect-exclude.js";
3
+ import { pruneCollectExcluded } from "./collect-prune.js";
2
4
  import { spawn } from "node:child_process";
3
5
  import { lstat, mkdir, mkdtemp, open, opendir, readdir, readFile, readlink, realpath, rename, rm, unlink, } from "node:fs/promises";
4
6
  import { dirname, isAbsolute, join, resolve, sep } from "node:path";
@@ -6,13 +8,24 @@ import { logger } from "../../core/logger.js";
6
8
  import { ensurePrivateDirectory } from "../../core/private-directory.js";
7
9
  import { readBoundedRegularFile } from "../../coding-worker/artifact.js";
8
10
  import { parseCodingAgentOutputJson, redactAndTruncate, MAX_CODING_ARTIFACT_BYTES, MAX_CODING_OUTPUT_ISSUES, SAFE_CODING_OUTPUT_ISSUE, } from "../../coding/protocol.js";
9
- import { assertDockerHostSupportsIsolation, assertClaudeAgentContainerInspection, assertClaudeToolRunnerContainerInspection, assertIsolationNetworkInspection, assertKeeperContainerInspection, assertProxyContainerInspection, assertStorageVolumeInspection, assertWorkerContainerInspection, buildDockerIsolationPlan, } from "./docker-isolation.js";
11
+ import { assertDockerHostSupportsIsolation, assertClaudeAgentContainerInspection, assertClaudeToolRunnerContainerInspection, assertIsolationNetworkInspection, assertKeeperContainerInspection, assertNetworkKeeperContainerInspection, assertProxyContainerInspection, assertStorageVolumeInspection, assertWorkerContainerInspection, buildDockerIsolationPlan, CLAUDE_TOOL_SETUP_ENV, dockerSupportsServicesFor, } from "./docker-isolation.js";
12
+ import { serviceUnreadyError } from "../../coding/services/wording.js";
13
+ import { assertServiceContainerInspection, buildDockerServicePlan, dockerServiceContainerNames, } from "./docker-services.js";
14
+ import { claudeToolSetup } from "./claude-tool-setup.js";
10
15
  import { replaceDirectoryFromStaging } from "./workspace-swap.js";
11
16
  const BACKEND = "docker";
12
17
  const STATE_SCHEMA_VERSION = 1;
13
18
  const MAX_DOCKER_OUTPUT_BYTES = 1024 * 1024;
14
19
  const MAX_DOCKER_SEED_DIAGNOSTIC_BYTES = 4 * 1024;
15
20
  const MAX_WORKSPACE_ENTRIES = 100_000;
21
+ /** How long one service image pull may take before the run fails as service_unready. */
22
+ export const SERVICE_IMAGE_PULL_TIMEOUT_MS = 300_000;
23
+ /**
24
+ * The default bound on a run's whole service start-up (every service's create, start, inspection and
25
+ * readiness probing; image pulls excluded), matching the Kubernetes launcher's pod-start bound.
26
+ */
27
+ export const DEFAULT_SERVICE_READY_TIMEOUT_MS = 120_000;
28
+ const MAX_SERVICE_PROBE_OUTPUT_BYTES = 64 * 1024;
16
29
  const TERMINAL_PHASES = new Set(["succeeded", "failed", "stopped", "lost", "removed"]);
17
30
  export const SAFE_WORKER_DIAGNOSTIC = /^(?:worker_[a-z_]+|coding_[a-z_]+|wardby_[a-z_]+)$/;
18
31
  /**
@@ -81,7 +94,7 @@ export class NodeDockerCommandRunner {
81
94
  if (args.some((argument) => argument.includes("\0")))
82
95
  throw new Error("docker_argument_invalid");
83
96
  const extraEnv = options.env ?? {};
84
- if (Object.keys(extraEnv).some((key) => key !== "WARDBY_RUN_CAPABILITY"))
97
+ if (Object.keys(extraEnv).some((key) => key !== "WARDBY_RUN_CAPABILITY" && key !== CLAUDE_TOOL_SETUP_ENV))
85
98
  throw new Error("docker_environment_invalid");
86
99
  const env = {
87
100
  PATH: this.path,
@@ -100,6 +113,13 @@ export class NodeDockerCommandRunner {
100
113
  rejectPromise(new DockerCommandError(null));
101
114
  return;
102
115
  }
116
+ let timedOut = false;
117
+ const timer = options.timeoutMs === undefined
118
+ ? undefined
119
+ : setTimeout(() => {
120
+ timedOut = true;
121
+ child.kill("SIGKILL");
122
+ }, options.timeoutMs);
103
123
  const stdout = [];
104
124
  const stderr = [];
105
125
  let bytes = 0;
@@ -120,6 +140,11 @@ export class NodeDockerCommandRunner {
120
140
  spawnFailed = true;
121
141
  });
122
142
  child.once("close", (code) => {
143
+ if (timer)
144
+ clearTimeout(timer);
145
+ if (timedOut) {
146
+ return rejectPromise(new DockerCommandError(code, false, false, { subcommand: args[0], stderr: "timed out" }));
147
+ }
123
148
  if (exceeded)
124
149
  return rejectPromise(new DockerCommandError(code, false, true));
125
150
  if (spawnFailed)
@@ -256,7 +281,7 @@ export class NodeDockerArtifactTransfer {
256
281
  });
257
282
  await docker.run(["container", "cp", sourceFile, `${container}:/run/wardby/storage/input/input.json`]);
258
283
  }
259
- async materializeDirectory(container, source, destination, maxBytes) {
284
+ async materializeDirectory(container, source, destination, maxBytes, exclusions) {
260
285
  await replaceDirectoryFromStaging(destination, maxBytes, "docker_workspace_destination_invalid", async (staging) => {
261
286
  await new NodeDockerCommandRunner({ dockerBinary: this.dockerBinary, homeDir: "/tmp", path: this.path }).run([
262
287
  "container",
@@ -264,6 +289,8 @@ export class NodeDockerArtifactTransfer {
264
289
  `${container}:${source}/.`,
265
290
  staging,
266
291
  ]);
292
+ // Before replaceDirectoryFromStaging validates: excluded folders never count.
293
+ await pruneCollectExcluded(staging, exclusions);
267
294
  });
268
295
  }
269
296
  }
@@ -380,6 +407,42 @@ function resourceLabels(record) {
380
407
  function labelsMatch(actual, expected) {
381
408
  return Object.entries(expected).every(([key, value]) => actual?.[key] === value);
382
409
  }
410
+ /**
411
+ * A run's service start-up budget: `budgetMs` from the start, extended by the time image pulls took,
412
+ * and never past the run's deadline.
413
+ */
414
+ class ServiceStartBudget {
415
+ now;
416
+ budgetMs;
417
+ deadlineAt;
418
+ startedAt;
419
+ excludedMs = 0;
420
+ constructor(now, budgetMs, deadlineAt) {
421
+ this.now = now;
422
+ this.budgetMs = budgetMs;
423
+ this.deadlineAt = deadlineAt;
424
+ this.startedAt = now();
425
+ }
426
+ /** What is left of the budget, capped by the run's deadline; zero once either runs out. */
427
+ remainingMs() {
428
+ const now = this.now();
429
+ return Math.max(0, Math.min(this.startedAt + this.excludedMs + this.budgetMs - now, this.deadlineAt - now));
430
+ }
431
+ /** What is left before the run's deadline: the only bound (besides its own) on an image pull. */
432
+ untilDeadlineMs() {
433
+ return Math.max(0, this.deadlineAt - this.now());
434
+ }
435
+ exclude(milliseconds) {
436
+ this.excludedMs += Math.max(0, milliseconds);
437
+ }
438
+ }
439
+ /** `coding_service_unready:<name>` (the executor's service_unready), keeping the Docker failure as the operator's cause. */
440
+ function serviceUnready(name, cause) {
441
+ const error = serviceUnreadyError(name);
442
+ if (cause !== undefined)
443
+ error.cause = cause;
444
+ return error;
445
+ }
383
446
  function capabilityFromInspection(container) {
384
447
  return container.Config?.Env?.find((value) => value.startsWith("WARDBY_RUN_CAPABILITY="))?.slice("WARDBY_RUN_CAPABILITY=".length);
385
448
  }
@@ -417,6 +480,8 @@ export class DockerJobLauncher {
417
480
  docker;
418
481
  transfer;
419
482
  now;
483
+ sleep;
484
+ serviceReadyTimeoutMs;
420
485
  locks = new Map();
421
486
  timers = new Map();
422
487
  ready;
@@ -429,8 +494,17 @@ export class DockerJobLauncher {
429
494
  this.docker = options.docker ?? new NodeDockerCommandRunner({ homeDir: join(this.stateRoot, ".home") });
430
495
  this.transfer = options.transfer ?? new NodeDockerArtifactTransfer();
431
496
  this.now = options.now ?? Date.now;
497
+ this.sleep =
498
+ options.sleep ?? ((milliseconds) => new Promise((resolvePromise) => setTimeout(resolvePromise, milliseconds)));
499
+ this.serviceReadyTimeoutMs = options.serviceReadyTimeoutMs ?? DEFAULT_SERVICE_READY_TIMEOUT_MS;
500
+ if (!Number.isFinite(this.serviceReadyTimeoutMs) || this.serviceReadyTimeoutMs <= 0)
501
+ throw new Error("docker_launcher_service_timeout_invalid");
432
502
  this.ready = this.initialize();
433
503
  }
504
+ /** Starts JobSpec.services (a network keeper plus one hardened container per service) for Codex and Claude Code runs. */
505
+ supportsServicesFor(provider) {
506
+ return dockerSupportsServicesFor(provider);
507
+ }
434
508
  async launch(spec) {
435
509
  await this.ready;
436
510
  return this.withRun(spec.runId, async () => this.launchLocked(spec));
@@ -493,7 +567,9 @@ export class DockerJobLauncher {
493
567
  if (resolve(destination) !== expected || !expected.startsWith(`${this.workspaceRoot}${sep}`)) {
494
568
  throw new Error("docker_workspace_destination_invalid");
495
569
  }
496
- await this.transfer.materializeDirectory(this.keeperName(current), "/run/wardby/storage/workspace", expected, current.spec.limits.diskMb * 1024 * 1024);
570
+ await this.transfer.materializeDirectory(this.keeperName(current), "/run/wardby/storage/workspace", expected, current.spec.limits.diskMb * 1024 * 1024,
571
+ // Names are always the current built-in list; only the paths come from the spec.
572
+ normalizeCollectExclusions(current.spec.collectExclude?.paths));
497
573
  });
498
574
  }
499
575
  async stop(handle, _reason) {
@@ -557,6 +633,7 @@ export class DockerJobLauncher {
557
633
  }
558
634
  async launchLocked(spec) {
559
635
  const plan = buildDockerIsolationPlan(spec, this.options.proxyContainer);
636
+ const services = buildDockerServicePlan(spec);
560
637
  const specHash = stableSpecHash(spec);
561
638
  const existing = await this.readRecord(spec.runId);
562
639
  if (existing) {
@@ -581,7 +658,7 @@ export class DockerJobLauncher {
581
658
  await this.refresh(existing);
582
659
  return clone(existing.handle);
583
660
  }
584
- return this.provision(existing, plan, capability);
661
+ return this.provision(existing, plan, services, capability);
585
662
  }
586
663
  await this.validateInputArtifact(spec.inputArtifact);
587
664
  const capability = ensureCapability(await this.options.resolveCapability(spec.runId));
@@ -600,7 +677,7 @@ export class DockerJobLauncher {
600
677
  // This durable record is the idempotency fence before any Docker side effect.
601
678
  await this.writeRecord(record);
602
679
  try {
603
- return await this.provision(record, plan, capability);
680
+ return await this.provision(record, plan, services, capability);
604
681
  }
605
682
  catch (error) {
606
683
  await this.options
@@ -613,7 +690,7 @@ export class DockerJobLauncher {
613
690
  throw error;
614
691
  }
615
692
  }
616
- async provision(record, plan, capability) {
693
+ async provision(record, plan, services, capability) {
617
694
  await this.assertHost();
618
695
  const labels = resourceLabels(record);
619
696
  await this.createAndAssert(appendLabels(plan.networkCreateArgs, labels), ["network", "inspect", plan.names.network], (value) => {
@@ -640,13 +717,21 @@ export class DockerJobLauncher {
640
717
  await this.run(plan.proxyNetworkConnectArgs);
641
718
  const proxy = await this.inspect(["container", "inspect", this.options.proxyContainer]);
642
719
  assertProxyContainerInspection(proxy, record.runId);
720
+ // Built before anything starts, so a setup past the tool runner's bounds fails the launch early.
721
+ const toolSetup = record.spec.provider === "claude-code" ? claudeToolSetup(record.spec, capability) : undefined;
722
+ // Services first (a run with services only): the Codex worker and Claude Code's tool runner join
723
+ // the network keeper's namespace, so it must exist, and every service must pass readiness before
724
+ // anything that runs the run's commands starts.
725
+ const networkKeeperId = plan.networkKeeperCreateArgs
726
+ ? await this.startServices(record, plan.networkKeeperCreateArgs, plan.names.networkKeeperContainer, services, labels)
727
+ : undefined;
643
728
  if (plan.toolCreateArgs) {
644
729
  await this.createAndAssert(appendLabels(plan.toolCreateArgs, labels), ["container", "inspect", plan.names.toolContainer], (value) => {
645
- assertClaudeToolRunnerContainerInspection(value, record.spec);
730
+ assertClaudeToolRunnerContainerInspection(value, record.spec, toolSetup, networkKeeperId);
646
731
  if (!labelsMatch(value.Config?.Labels, labels)) {
647
732
  throw new Error("docker_resource_attestation_failed");
648
733
  }
649
- });
734
+ }, { env: { WARDBY_TOOL_SETUP: toolSetup } });
650
735
  await this.startContainer(plan.names.toolContainer);
651
736
  await this.waitForToolRunner(plan.names.toolContainer);
652
737
  }
@@ -657,7 +742,7 @@ export class DockerJobLauncher {
657
742
  assertClaudeAgentContainerInspection(inspected, record.spec, capability);
658
743
  }
659
744
  else {
660
- assertWorkerContainerInspection(inspected, record.spec, capability);
745
+ assertWorkerContainerInspection(inspected, record.spec, capability, networkKeeperId);
661
746
  }
662
747
  if (!labelsMatch(inspected.Config?.Labels, labels))
663
748
  throw new Error("docker_resource_attestation_failed");
@@ -668,6 +753,138 @@ export class DockerJobLauncher {
668
753
  this.scheduleDeadline(record);
669
754
  return clone(record.handle);
670
755
  }
756
+ /**
757
+ * A run with services (docs/coding-services.md). The network keeper joins the run's internal
758
+ * network and owns the namespace; each service joins it and must pass its catalog readiness
759
+ * command before the next one starts, as Kubernetes starts native sidecars. The whole start-up is
760
+ * bounded (ServiceStartBudget): running out fails as coding_service_unready for the service being
761
+ * waited on. Returns the keeper's container ID, which Docker may report as the services' and
762
+ * worker's network mode.
763
+ */
764
+ async startServices(record, keeperArgs, keeperName, services, labels) {
765
+ const budget = new ServiceStartBudget(this.now, this.serviceReadyTimeoutMs, record.deadlineAt);
766
+ const keeper = (await this.createAndAssert(appendLabels(keeperArgs, labels), ["container", "inspect", keeperName], (value) => {
767
+ const inspected = value;
768
+ assertNetworkKeeperContainerInspection(inspected, record.spec);
769
+ if (!labelsMatch(inspected.Config?.Labels, labels))
770
+ throw new Error("docker_resource_attestation_failed");
771
+ }));
772
+ const keeperId = keeper.Id;
773
+ if (!keeperId)
774
+ throw new Error("docker_resource_attestation_failed");
775
+ await this.startContainer(keeperName);
776
+ for (const planned of services) {
777
+ const name = planned.service.name;
778
+ if (budget.remainingMs() <= 0)
779
+ throw serviceUnready(name);
780
+ await this.ensureServiceImage(planned, budget);
781
+ if (budget.remainingMs() <= 0)
782
+ throw serviceUnready(name);
783
+ let createFailure;
784
+ try {
785
+ await this.createAndAssert(appendLabels(planned.createArgs, labels), ["container", "inspect", planned.container], (value) => {
786
+ const inspected = value;
787
+ assertServiceContainerInspection(inspected, record.spec, planned.service, keeperId);
788
+ if (!labelsMatch(inspected.Config?.Labels, labels))
789
+ throw new Error("docker_resource_attestation_failed");
790
+ }, undefined, (error) => {
791
+ createFailure = error;
792
+ });
793
+ }
794
+ catch (error) {
795
+ // createAndAssert tolerates a failed create (a crash retry may find it made), so a service
796
+ // Docker refused to create shows up as a failed inspection; the create's own failure is the
797
+ // useful cause, not that generic "not found". Attestation failures rethrow as is.
798
+ if (error instanceof DockerCommandError)
799
+ throw serviceUnready(name, createFailure ?? error);
800
+ throw error;
801
+ }
802
+ if (budget.remainingMs() <= 0)
803
+ throw serviceUnready(name);
804
+ try {
805
+ await this.startContainer(planned.container);
806
+ }
807
+ catch (error) {
808
+ throw serviceUnready(name, error);
809
+ }
810
+ await this.waitForServiceReady(planned, budget);
811
+ }
812
+ return keeperId;
813
+ }
814
+ /**
815
+ * A local copy of the digest-pinned image is used as is; otherwise it is pulled, bounded by its
816
+ * own limit and the run's deadline. Pull time does not count toward the start-up budget.
817
+ */
818
+ async ensureServiceImage(planned, budget) {
819
+ try {
820
+ await this.run(["image", "inspect", "--format", "{{.Id}}", planned.service.image]);
821
+ return;
822
+ }
823
+ catch {
824
+ // Missing (or unreadable): pull it by its digest below.
825
+ }
826
+ const untilDeadline = budget.untilDeadlineMs();
827
+ if (untilDeadline <= 0)
828
+ throw serviceUnready(planned.service.name);
829
+ const startedAt = this.now();
830
+ try {
831
+ await this.run(["image", "pull", "--quiet", planned.service.image], {
832
+ timeoutMs: Math.min(SERVICE_IMAGE_PULL_TIMEOUT_MS, untilDeadline),
833
+ });
834
+ }
835
+ catch (error) {
836
+ throw serviceUnready(planned.service.name, error);
837
+ }
838
+ finally {
839
+ budget.exclude(this.now() - startedAt);
840
+ }
841
+ }
842
+ /**
843
+ * The catalog readiness command, like a Kubernetes startup probe: every periodSeconds, each
844
+ * attempt bounded by timeoutSeconds, failing after failureThreshold consecutive failures. A
845
+ * service that exited or vanished fails at once, and so does one still unready when the start-up
846
+ * budget runs out (each probe and wait is clipped to what is left of it). Probe output is never
847
+ * logged or kept, so these errors carry no cause.
848
+ */
849
+ async waitForServiceReady(planned, budget) {
850
+ const { command, periodSeconds, timeoutSeconds, failureThreshold } = planned.service.readiness;
851
+ const name = planned.service.name;
852
+ let failures = 0;
853
+ for (;;) {
854
+ if (budget.remainingMs() <= 0)
855
+ throw serviceUnready(name);
856
+ let state;
857
+ try {
858
+ state = await this.inspect(["container", "inspect", planned.container]);
859
+ }
860
+ catch (error) {
861
+ if (error instanceof DockerCommandError)
862
+ throw serviceUnready(name);
863
+ throw error;
864
+ }
865
+ if (state.State?.Running !== true)
866
+ throw serviceUnready(name);
867
+ const remaining = budget.remainingMs();
868
+ if (remaining <= 0)
869
+ throw serviceUnready(name);
870
+ try {
871
+ await this.run(["container", "exec", planned.container, ...command], {
872
+ timeoutMs: Math.min(timeoutSeconds * 1_000, remaining),
873
+ maxOutputBytes: MAX_SERVICE_PROBE_OUTPUT_BYTES,
874
+ });
875
+ return;
876
+ }
877
+ catch {
878
+ failures += 1;
879
+ if (failures >= failureThreshold)
880
+ throw serviceUnready(name);
881
+ }
882
+ const wait = Math.min(periodSeconds * 1_000, budget.remainingMs());
883
+ if (wait <= 0)
884
+ throw serviceUnready(name);
885
+ await this.sleep(wait);
886
+ }
887
+ }
671
888
  async refresh(record) {
672
889
  if (isTerminal(record))
673
890
  return;
@@ -701,7 +918,7 @@ export class DockerJobLauncher {
701
918
  "inspect",
702
919
  buildDockerIsolationPlan(record.spec, this.options.proxyContainer).names.toolContainer,
703
920
  ]);
704
- assertClaudeToolRunnerContainerInspection(tool, record.spec);
921
+ assertClaudeToolRunnerContainerInspection(tool, record.spec, claudeToolSetup(record.spec, capability), await this.attestedNetworkKeeperId(record));
705
922
  if (tool.State?.Running !== true || tool.State?.Status !== "running") {
706
923
  record.phase = "failed";
707
924
  record.result = { exitCode: 1, reason: "failed", diagnostic: "worker_tool_runner_failed" };
@@ -711,7 +928,7 @@ export class DockerJobLauncher {
711
928
  }
712
929
  }
713
930
  else {
714
- assertWorkerContainerInspection(inspection, record.spec, capability);
931
+ assertWorkerContainerInspection(inspection, record.spec, capability, await this.attestedNetworkKeeperId(record));
715
932
  }
716
933
  const phase = dockerState(inspection);
717
934
  if (!phase || phase === "provisioning" || phase === "active")
@@ -724,28 +941,27 @@ export class DockerJobLauncher {
724
941
  async stopRecord(record, timedOut) {
725
942
  if (isTerminal(record))
726
943
  return;
727
- try {
728
- await this.run(["container", "stop", "--time", "10", record.handle.id]);
729
- }
730
- catch (error) {
731
- if (!(error instanceof DockerCommandError && error.notFound))
732
- throw new Error("docker_stop_failed", { cause: error });
733
- }
944
+ await this.stopContainer(record.handle.id);
734
945
  if (record.spec.provider === "claude-code") {
735
- const tool = buildDockerIsolationPlan(record.spec, this.options.proxyContainer).names.toolContainer;
736
- try {
737
- await this.run(["container", "stop", "--time", "10", tool]);
738
- }
739
- catch (error) {
740
- if (!(error instanceof DockerCommandError && error.notFound))
741
- throw new Error("docker_stop_failed", { cause: error });
742
- }
946
+ await this.stopContainer(buildDockerIsolationPlan(record.spec, this.options.proxyContainer).names.toolContainer);
743
947
  }
948
+ // Services stop with the run; they (and the network keeper) are removed with its other resources.
949
+ for (const service of dockerServiceContainerNames(record.spec))
950
+ await this.stopContainer(service);
744
951
  record.phase = timedOut ? "failed" : "stopped";
745
952
  record.result = timedOut ? { exitCode: 124, reason: "timed_out" } : resultFor("stopped");
746
953
  await this.writeRecord(record);
747
954
  this.clearDeadline(record);
748
955
  }
956
+ async stopContainer(name) {
957
+ try {
958
+ await this.run(["container", "stop", "--time", "10", name]);
959
+ }
960
+ catch (error) {
961
+ if (!(error instanceof DockerCommandError && error.notFound))
962
+ throw new Error("docker_stop_failed", { cause: error });
963
+ }
964
+ }
749
965
  async sweepExpired(record) {
750
966
  // The DB verifier ran immediately before this call; never sweep by label alone.
751
967
  try {
@@ -765,17 +981,26 @@ export class DockerJobLauncher {
765
981
  await this.removeContainerIfAttested(plan.names.workerContainer, labels);
766
982
  if (plan.toolCreateArgs)
767
983
  await this.removeContainerIfAttested(plan.names.toolContainer, labels);
984
+ // Services, then the network keeper: nothing (the Codex worker, Claude's tool runner, a service)
985
+ // may still share its namespace when it goes.
986
+ // --volumes: an image VOLUME the attestation rejected must not outlive its container.
987
+ for (const service of dockerServiceContainerNames(record.spec)) {
988
+ await this.removeContainerIfAttested(service, labels, true);
989
+ }
990
+ if (plan.networkKeeperCreateArgs) {
991
+ await this.removeContainerIfAttested(plan.names.networkKeeperContainer, labels);
992
+ }
768
993
  await this.runIgnoreMissing(["network", "disconnect", plan.names.network, this.options.proxyContainer]);
769
994
  await this.removeContainerIfAttested(plan.names.keeperContainer, labels);
770
995
  await this.removeNetworkIfAttested(plan.names.network, labels);
771
996
  await this.removeVolumeIfAttested(plan.names.storageVolume, labels);
772
997
  }
773
- async removeContainerIfAttested(name, labels) {
998
+ async removeContainerIfAttested(name, labels, anonymousVolumes = false) {
774
999
  try {
775
1000
  const inspected = await this.inspect(["container", "inspect", name]);
776
1001
  if (!labelsMatch(inspected.Config?.Labels, labels))
777
1002
  throw new Error("docker_resource_attestation_failed");
778
- await this.runIgnoreMissing(["container", "rm", "--force", name]);
1003
+ await this.runIgnoreMissing(["container", "rm", "--force", ...(anonymousVolumes ? ["--volumes"] : []), name]);
779
1004
  }
780
1005
  catch (error) {
781
1006
  if (!(error instanceof DockerCommandError && error.notFound))
@@ -852,18 +1077,40 @@ export class DockerJobLauncher {
852
1077
  keeperName(record) {
853
1078
  return buildDockerIsolationPlan(record.spec, this.options.proxyContainer).names.keeperContainer;
854
1079
  }
1080
+ /** For a run with services: the attested network keeper's ID, which the worker's network mode may name. */
1081
+ async attestedNetworkKeeperId(record) {
1082
+ if (record.spec.services === undefined)
1083
+ return undefined;
1084
+ const name = buildDockerIsolationPlan(record.spec, this.options.proxyContainer).names.networkKeeperContainer;
1085
+ let keeper;
1086
+ try {
1087
+ keeper = await this.inspect(["container", "inspect", name]);
1088
+ }
1089
+ catch (error) {
1090
+ throw new Error("docker_status_unavailable", { cause: error });
1091
+ }
1092
+ assertNetworkKeeperContainerInspection(keeper, record.spec);
1093
+ if (!labelsMatch(keeper.Config?.Labels, resourceLabels(record)) || !keeper.Id) {
1094
+ throw new Error("docker_resource_attestation_failed");
1095
+ }
1096
+ return keeper.Id;
1097
+ }
855
1098
  async assertHost() {
856
1099
  const info = await this.inspect(["info", "--format", "{{json .}}"]);
857
1100
  assertDockerHostSupportsIsolation(info);
858
1101
  }
859
- async createAndAssert(createArgs, inspectArgs, assert, options) {
1102
+ async createAndAssert(createArgs, inspectArgs, assert, options, onCreateFailure) {
860
1103
  try {
861
1104
  await this.run(createArgs, options);
862
1105
  }
863
- catch {
1106
+ catch (error) {
864
1107
  // A retry may find a resource created before the previous process crashed.
1108
+ if (error instanceof DockerCommandError)
1109
+ onCreateFailure?.(error);
865
1110
  }
866
- assert(await this.inspect(inspectArgs));
1111
+ const inspected = await this.inspect(inspectArgs);
1112
+ assert(inspected);
1113
+ return inspected;
867
1114
  }
868
1115
  async waitForStorage(keeper) {
869
1116
  // The keeper emits this fixed marker only after creating every storage
@@ -8,6 +8,7 @@ export interface FakeExecCall {
8
8
  command: string[];
9
9
  stdin?: Readable;
10
10
  stdout?: Writable;
11
+ timeoutMs: number;
11
12
  }
12
13
  type Kind = "configmap" | "secret" | "pod" | "networkpolicy" | "service" | "endpoints";
13
14
  type Obj = {
@@ -122,8 +122,9 @@ export class FakeKubernetesApi {
122
122
  }
123
123
  async exec(namespace, pod, container, command, options) {
124
124
  // Like the real API: exec into a missing pod or an unknown container fails instead of succeeding
125
- // silently, so no test can pass on behaviour a cluster would reject. (The adapter's timeoutMs is
126
- // not modelled: tests drive slow execs through onExec directly.)
125
+ // silently, so no test can pass on behaviour a cluster would reject. (The adapter does not
126
+ // actually enforce timeoutMs itself: tests drive slow execs through onExec directly. The value
127
+ // is still recorded on the call, so a test can assert which timeout the caller configured.)
127
128
  const target = this.read("pod", namespace, pod);
128
129
  if (!target)
129
130
  throw new KubernetesNotFoundError(this.key("pod", namespace, pod));
@@ -131,7 +132,15 @@ export class FakeKubernetesApi {
131
132
  if (!containers.some((entry) => entry.name === container)) {
132
133
  throw new KubernetesNotFoundError(`${this.key("pod", namespace, pod)}/${container}`);
133
134
  }
134
- const call = { namespace, pod, container, command, stdin: options.stdin, stdout: options.stdout };
135
+ const call = {
136
+ namespace,
137
+ pod,
138
+ container,
139
+ command,
140
+ stdin: options.stdin,
141
+ stdout: options.stdout,
142
+ timeoutMs: options.timeoutMs,
143
+ };
135
144
  this.execCalls.push(call);
136
145
  return this.onExec(call);
137
146
  }
@@ -1,13 +1,26 @@
1
1
  import type { V1NetworkPolicy, V1Pod, V1Secret } from "@kubernetes/client-node";
2
+ import { type ResolvedCodingService } from "../../coding/services/catalog.js";
2
3
  import type { JobSpec } from "./types.js";
3
4
  import { type KubernetesPlatform } from "./kubernetes-platform.js";
4
5
  export declare const KUBERNETES_ISOLATION_ERROR = "kubernetes_isolation_unsupported";
5
- export declare const KUBERNETES_PROVIDER_UNSUPPORTED = "kubernetes_provider_unsupported";
6
6
  /** Extra seconds past timeoutSec before Kubernetes kills the pod (keeper included). */
7
7
  export declare const POD_DEADLINE_GRACE_SECONDS = 300;
8
8
  export declare const KEEPER_CONTAINER = "keeper";
9
9
  export declare const STORAGE_INIT_CONTAINER = "storage-init";
10
10
  export declare const WORKER_CONTAINER = "worker";
11
+ /** Claude's credential-free command runner: a native sidecar in the run pod. */
12
+ export declare const TOOL_RUNNER_CONTAINER = "tool-runner";
13
+ /** The run Secret's key holding the tool runner's setup (claude-tool-setup.ts). */
14
+ export declare const TOOL_SETUP_SECRET_KEY = "tool-setup";
15
+ /** Where the tool runner listens and the agent's relay connects (claude-tool-runner/main.mjs). */
16
+ export declare const TOOL_SOCKET_PATH = "/run/wardby/tool/runner.sock";
17
+ /** Every service sidecar's container name starts with this (service-postgres, service-redis, ...). */
18
+ export declare const SERVICE_CONTAINER_PREFIX = "service-";
19
+ /** Disk-backed scratch for each of a service's writablePaths (a socket directory, /tmp). */
20
+ export declare const SERVICE_SCRATCH_MIB = 64;
21
+ export declare function serviceContainerName(name: string): string;
22
+ /** What a service sidecar reserves of the pod's ephemeral storage: its data volume plus its scratch volumes. */
23
+ export declare function serviceEphemeralStorageMib(service: ResolvedCodingService): number;
11
24
  export declare const STORAGE_ROOT = "/run/wardby/storage";
12
25
  export declare const KEEPER_SEEDED_MARKER = "/run/wardby/storage/input/.seeded";
13
26
  export declare const PROXY_POD_LABEL: {
@@ -53,12 +66,14 @@ export interface RunPodOptions {
53
66
  namespace: string;
54
67
  proxyIp: string;
55
68
  runtimeClassName?: string;
69
+ /** Emitted only when set, so an unconfigured launcher submits exactly the pod it always has. */
70
+ priorityClassName?: string;
56
71
  /** Which platform's admission rules the emitted resources must already satisfy. Default: "generic". */
57
72
  platform?: KubernetesPlatform;
58
73
  }
59
74
  export declare function buildRunPod(spec: JobSpec, options: RunPodOptions): V1Pod;
60
75
  export declare function buildRunNetworkPolicy(spec: JobSpec, namespace: string): V1NetworkPolicy;
61
- export declare function buildCapabilitySecret(spec: JobSpec, namespace: string, capability: string): V1Secret;
76
+ export declare function buildCapabilitySecret(spec: JobSpec, namespace: string, capability: string, toolSetup?: string): V1Secret;
62
77
  /**
63
78
  * A copy of `pod` with ONLY the API server's own defaulting and aliasing undone
64
79
  * — the same `normalizeSpec` pass `assertRunPodMatches` runs, and nothing else.