@wardby/cli 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (491) hide show
  1. package/.env.example +34 -4
  2. package/README.md +183 -71
  3. package/dist/claude-coding-worker/driver.d.ts +7 -1
  4. package/dist/claude-coding-worker/driver.js +31 -2
  5. package/dist/claude-coding-worker/main.js +5 -2
  6. package/dist/claude-coding-worker/tool-socket.d.ts +9 -0
  7. package/dist/claude-coding-worker/tool-socket.js +26 -0
  8. package/dist/cli-help.d.ts +1 -1
  9. package/dist/cli-help.js +13 -4
  10. package/dist/cli.d.ts +1 -1
  11. package/dist/cli.js +219 -29
  12. package/dist/coding/collect-exclude.d.ts +25 -0
  13. package/dist/coding/collect-exclude.js +76 -0
  14. package/dist/coding/profile.d.ts +67 -22
  15. package/dist/coding/profile.js +58 -25
  16. package/dist/coding/protected-path-wording.d.ts +24 -0
  17. package/dist/coding/protected-path-wording.js +55 -0
  18. package/dist/coding/protected-paths.d.ts +31 -0
  19. package/dist/coding/protected-paths.js +48 -0
  20. package/dist/coding/protocol.d.ts +99 -3
  21. package/dist/coding/protocol.js +155 -4
  22. package/dist/coding/registry/adapters.d.ts +2 -0
  23. package/dist/coding/registry/adapters.js +9 -0
  24. package/dist/coding/registry/allowlist.d.ts +9 -0
  25. package/dist/coding/registry/allowlist.js +28 -0
  26. package/dist/coding/registry/json-scan.d.ts +55 -0
  27. package/dist/coding/registry/json-scan.js +191 -0
  28. package/dist/coding/registry/lockfiles.d.ts +6 -0
  29. package/dist/coding/registry/lockfiles.js +89 -0
  30. package/dist/coding/registry/npm-lockfile.d.ts +9 -0
  31. package/dist/coding/registry/npm-lockfile.js +73 -0
  32. package/dist/coding/registry/npm-plan.d.ts +29 -0
  33. package/dist/coding/registry/npm-plan.js +289 -0
  34. package/dist/coding/registry/npm.d.ts +7 -0
  35. package/dist/coding/registry/npm.js +252 -0
  36. package/dist/coding/registry/pypi.d.ts +4 -0
  37. package/dist/coding/registry/pypi.js +210 -0
  38. package/dist/coding/registry/report.d.ts +37 -0
  39. package/dist/coding/registry/report.js +31 -0
  40. package/dist/coding/registry/token.d.ts +4 -0
  41. package/dist/coding/registry/token.js +7 -0
  42. package/dist/coding/registry/types.d.ts +286 -0
  43. package/dist/coding/registry/types.js +19 -0
  44. package/dist/coding/registry/worker-config.d.ts +14 -0
  45. package/dist/coding/registry/worker-config.js +31 -0
  46. package/dist/coding/services/builtins.d.ts +22 -0
  47. package/dist/coding/services/builtins.js +85 -0
  48. package/dist/coding/services/catalog.d.ts +443 -0
  49. package/dist/coding/services/catalog.js +159 -0
  50. package/dist/coding/services/declaration.d.ts +11 -0
  51. package/dist/coding/services/declaration.js +114 -0
  52. package/dist/coding/services/note.d.ts +8 -0
  53. package/dist/coding/services/note.js +15 -0
  54. package/dist/coding/services/resolve.d.ts +25 -0
  55. package/dist/coding/services/resolve.js +50 -0
  56. package/dist/coding/services/wording.d.ts +25 -0
  57. package/dist/coding/services/wording.js +70 -0
  58. package/dist/coding-proxy/main.js +24 -4
  59. package/dist/coding-worker/artifact.d.ts +18 -0
  60. package/dist/coding-worker/artifact.js +48 -6
  61. package/dist/coding-worker/debug-trace.d.ts +37 -0
  62. package/dist/coding-worker/debug-trace.js +117 -0
  63. package/dist/coding-worker/driver.d.ts +14 -1
  64. package/dist/coding-worker/driver.js +106 -14
  65. package/dist/coding-worker/errors.d.ts +7 -0
  66. package/dist/coding-worker/errors.js +26 -0
  67. package/dist/coding-worker/main.js +13 -4
  68. package/dist/coding-worker/sdk.d.ts +2 -2
  69. package/dist/coding-worker/sdk.js +7 -2
  70. package/dist/coding-worker/types.d.ts +3 -0
  71. package/dist/config/providers.d.ts +41 -0
  72. package/dist/config/providers.js +66 -0
  73. package/dist/core/budget-groups.d.ts +109 -6
  74. package/dist/core/budget-groups.js +125 -19
  75. package/dist/core/budget-wording.d.ts +22 -0
  76. package/dist/core/budget-wording.js +55 -0
  77. package/dist/core/coding-queue.d.ts +1 -1
  78. package/dist/core/datastores.d.ts +1 -1
  79. package/dist/core/datastores.js +18 -2
  80. package/dist/core/db.d.ts +40 -2
  81. package/dist/core/db.js +75 -2
  82. package/dist/core/dispatch.d.ts +49 -7
  83. package/dist/core/dispatch.js +290 -45
  84. package/dist/core/engine-native.js +27 -10
  85. package/dist/core/grants.d.ts +86 -0
  86. package/dist/core/grants.js +126 -0
  87. package/dist/core/host-events.d.ts +49 -0
  88. package/dist/core/host-events.js +257 -0
  89. package/dist/core/host-identity-links.d.ts +54 -0
  90. package/dist/core/host-identity-links.js +189 -0
  91. package/dist/core/host-status.d.ts +61 -0
  92. package/dist/core/host-status.js +211 -0
  93. package/dist/core/http-runtime.js +3 -4
  94. package/dist/core/in-flight-runs.d.ts +8 -0
  95. package/dist/core/in-flight-runs.js +56 -0
  96. package/dist/core/lease.d.ts +1 -1
  97. package/dist/core/private-directory.d.ts +20 -0
  98. package/dist/core/private-directory.js +36 -0
  99. package/dist/core/provider-wording.d.ts +12 -0
  100. package/dist/core/provider-wording.js +44 -0
  101. package/dist/core/reconciler.d.ts +39 -3
  102. package/dist/core/reconciler.js +86 -2
  103. package/dist/core/repo-access.d.ts +99 -0
  104. package/dist/core/repo-access.js +136 -0
  105. package/dist/core/review-host-checks.d.ts +11 -0
  106. package/dist/core/review-host-checks.js +41 -0
  107. package/dist/core/review-host-tools.d.ts +47 -0
  108. package/dist/core/review-host-tools.js +347 -0
  109. package/dist/core/run-heartbeat.d.ts +27 -0
  110. package/dist/core/run-heartbeat.js +54 -0
  111. package/dist/core/runner.d.ts +31 -9
  112. package/dist/core/runner.js +237 -40
  113. package/dist/core/scheduler.d.ts +1 -1
  114. package/dist/core/secrets.d.ts +12 -3
  115. package/dist/core/secrets.js +25 -6
  116. package/dist/core/subagent-memory-tools.d.ts +2 -2
  117. package/dist/core/subagent-memory-tools.js +16 -3
  118. package/dist/core/tool-admin.d.ts +81 -0
  119. package/dist/core/tool-admin.js +129 -0
  120. package/dist/core/tool-names.d.ts +42 -0
  121. package/dist/core/tool-names.js +64 -0
  122. package/dist/core/untrusted-content.d.ts +32 -0
  123. package/dist/core/untrusted-content.js +72 -0
  124. package/dist/core/webhooks.d.ts +9 -2
  125. package/dist/core/webhooks.js +23 -2
  126. package/dist/generated/prisma/browser.d.ts +328 -0
  127. package/dist/generated/prisma/browser.js +17 -0
  128. package/dist/generated/prisma/client.d.ts +347 -0
  129. package/dist/generated/prisma/client.js +34 -0
  130. package/dist/generated/prisma/commonInputTypes.d.ts +839 -0
  131. package/dist/generated/prisma/commonInputTypes.js +10 -0
  132. package/dist/generated/prisma/enums.d.ts +59 -0
  133. package/dist/generated/prisma/enums.js +59 -0
  134. package/dist/generated/prisma/internal/class.d.ts +605 -0
  135. package/dist/generated/prisma/internal/class.js +49 -0
  136. package/dist/generated/prisma/internal/prismaNamespace.d.ts +4494 -0
  137. package/dist/generated/prisma/internal/prismaNamespace.js +628 -0
  138. package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +635 -0
  139. package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +599 -0
  140. package/dist/generated/prisma/models/Agent.d.ts +3560 -0
  141. package/dist/generated/prisma/models/Agent.js +1 -0
  142. package/dist/generated/prisma/models/AgentDatastore.d.ts +1210 -0
  143. package/dist/generated/prisma/models/AgentDatastore.js +1 -0
  144. package/dist/generated/prisma/models/AgentMemory.d.ts +986 -0
  145. package/dist/generated/prisma/models/AgentMemory.js +1 -0
  146. package/dist/generated/prisma/models/AgentRepository.d.ts +1425 -0
  147. package/dist/generated/prisma/models/AgentRepository.js +1 -0
  148. package/dist/generated/prisma/models/AgentSecret.d.ts +1212 -0
  149. package/dist/generated/prisma/models/AgentSecret.js +1 -0
  150. package/dist/generated/prisma/models/AgentSubAgent.d.ts +1217 -0
  151. package/dist/generated/prisma/models/AgentSubAgent.js +1 -0
  152. package/dist/generated/prisma/models/AgentTool.d.ts +1403 -0
  153. package/dist/generated/prisma/models/AgentTool.js +1 -0
  154. package/dist/generated/prisma/models/AuthFormChallenge.d.ts +1298 -0
  155. package/dist/generated/prisma/models/AuthFormChallenge.js +1 -0
  156. package/dist/generated/prisma/models/AuthLoginKey.d.ts +1245 -0
  157. package/dist/generated/prisma/models/AuthLoginKey.js +1 -0
  158. package/dist/generated/prisma/models/AuthRateLimit.d.ts +979 -0
  159. package/dist/generated/prisma/models/AuthRateLimit.js +1 -0
  160. package/dist/generated/prisma/models/AuthSession.d.ts +1376 -0
  161. package/dist/generated/prisma/models/AuthSession.js +1 -0
  162. package/dist/generated/prisma/models/AuthUser.d.ts +1700 -0
  163. package/dist/generated/prisma/models/AuthUser.js +1 -0
  164. package/dist/generated/prisma/models/BudgetGroup.d.ts +1548 -0
  165. package/dist/generated/prisma/models/BudgetGroup.js +1 -0
  166. package/dist/generated/prisma/models/CodingAgentProfile.d.ts +1681 -0
  167. package/dist/generated/prisma/models/CodingAgentProfile.js +1 -0
  168. package/dist/generated/prisma/models/CodingProxyRequest.d.ts +1749 -0
  169. package/dist/generated/prisma/models/CodingProxyRequest.js +1 -0
  170. package/dist/generated/prisma/models/CodingProxySession.d.ts +1626 -0
  171. package/dist/generated/prisma/models/CodingProxySession.js +1 -0
  172. package/dist/generated/prisma/models/CodingRun.d.ts +3583 -0
  173. package/dist/generated/prisma/models/CodingRun.js +1 -0
  174. package/dist/generated/prisma/models/CodingService.d.ts +1348 -0
  175. package/dist/generated/prisma/models/CodingService.js +1 -0
  176. package/dist/generated/prisma/models/Datastore.d.ts +1434 -0
  177. package/dist/generated/prisma/models/Datastore.js +1 -0
  178. package/dist/generated/prisma/models/DatastoreEntry.d.ts +1314 -0
  179. package/dist/generated/prisma/models/DatastoreEntry.js +1 -0
  180. package/dist/generated/prisma/models/HostEventDelivery.d.ts +946 -0
  181. package/dist/generated/prisma/models/HostEventDelivery.js +1 -0
  182. package/dist/generated/prisma/models/HostIdentity.d.ts +1232 -0
  183. package/dist/generated/prisma/models/HostIdentity.js +1 -0
  184. package/dist/generated/prisma/models/HostIdentityLinkRequest.d.ts +1473 -0
  185. package/dist/generated/prisma/models/HostIdentityLinkRequest.js +1 -0
  186. package/dist/generated/prisma/models/OAuthAuthorizationCode.d.ts +1526 -0
  187. package/dist/generated/prisma/models/OAuthAuthorizationCode.js +1 -0
  188. package/dist/generated/prisma/models/OAuthAuthorizationRequest.d.ts +1350 -0
  189. package/dist/generated/prisma/models/OAuthAuthorizationRequest.js +1 -0
  190. package/dist/generated/prisma/models/OAuthClient.d.ts +1289 -0
  191. package/dist/generated/prisma/models/OAuthClient.js +1 -0
  192. package/dist/generated/prisma/models/OAuthFamily.d.ts +1540 -0
  193. package/dist/generated/prisma/models/OAuthFamily.js +1 -0
  194. package/dist/generated/prisma/models/OAuthGrant.d.ts +1280 -0
  195. package/dist/generated/prisma/models/OAuthGrant.js +1 -0
  196. package/dist/generated/prisma/models/Principal.d.ts +2245 -0
  197. package/dist/generated/prisma/models/Principal.js +1 -0
  198. package/dist/generated/prisma/models/RegistryAllowance.d.ts +1148 -0
  199. package/dist/generated/prisma/models/RegistryAllowance.js +1 -0
  200. package/dist/generated/prisma/models/RegistryApprovedVersion.d.ts +1219 -0
  201. package/dist/generated/prisma/models/RegistryApprovedVersion.js +1 -0
  202. package/dist/generated/prisma/models/RegistryFetch.d.ts +1428 -0
  203. package/dist/generated/prisma/models/RegistryFetch.js +1 -0
  204. package/dist/generated/prisma/models/RegistryPlanRefusal.d.ts +1294 -0
  205. package/dist/generated/prisma/models/RegistryPlanRefusal.js +1 -0
  206. package/dist/generated/prisma/models/RegistryVersionFact.d.ts +1085 -0
  207. package/dist/generated/prisma/models/RegistryVersionFact.js +1 -0
  208. package/dist/generated/prisma/models/ResourceGrant.d.ts +1437 -0
  209. package/dist/generated/prisma/models/ResourceGrant.js +1 -0
  210. package/dist/generated/prisma/models/Run.d.ts +2669 -0
  211. package/dist/generated/prisma/models/Run.js +1 -0
  212. package/dist/generated/prisma/models/RunHostCheck.d.ts +1239 -0
  213. package/dist/generated/prisma/models/RunHostCheck.js +1 -0
  214. package/dist/generated/prisma/models/RunHostStatus.d.ts +1315 -0
  215. package/dist/generated/prisma/models/RunHostStatus.js +1 -0
  216. package/dist/generated/prisma/models/SchedulerLease.d.ts +970 -0
  217. package/dist/generated/prisma/models/SchedulerLease.js +1 -0
  218. package/dist/generated/prisma/models/Secret.d.ts +1399 -0
  219. package/dist/generated/prisma/models/Secret.js +1 -0
  220. package/dist/generated/prisma/models/SecretElicitationOutcome.d.ts +973 -0
  221. package/dist/generated/prisma/models/SecretElicitationOutcome.js +1 -0
  222. package/dist/generated/prisma/models/Task.d.ts +1176 -0
  223. package/dist/generated/prisma/models/Task.js +1 -0
  224. package/dist/generated/prisma/models/Tool.d.ts +1490 -0
  225. package/dist/generated/prisma/models/Tool.js +1 -0
  226. package/dist/generated/prisma/models/Webhook.d.ts +1386 -0
  227. package/dist/generated/prisma/models/Webhook.js +1 -0
  228. package/dist/generated/prisma/models.d.ts +45 -0
  229. package/dist/generated/prisma/models.js +1 -0
  230. package/dist/help/build.d.ts +1 -0
  231. package/dist/help/build.js +9 -0
  232. package/dist/help/catalog.d.ts +24 -0
  233. package/dist/help/catalog.js +160 -0
  234. package/dist/help/cli.d.ts +2 -0
  235. package/dist/help/cli.js +65 -0
  236. package/dist/help/runtime.d.ts +3 -0
  237. package/dist/help/runtime.js +44 -0
  238. package/dist/help/search.d.ts +9 -0
  239. package/dist/help/search.js +104 -0
  240. package/dist/help-index.json +660 -0
  241. package/dist/import/cli-args.js +3 -2
  242. package/dist/import/create.d.ts +6 -1
  243. package/dist/import/create.js +68 -14
  244. package/dist/import/index.d.ts +1 -1
  245. package/dist/import/index.js +19 -9
  246. package/dist/import/neutral-schema.d.ts +11 -11
  247. package/dist/mcp/auth/access.d.ts +70 -0
  248. package/dist/mcp/auth/access.js +58 -0
  249. package/dist/mcp/auth/grants-cli.d.ts +149 -0
  250. package/dist/mcp/auth/grants-cli.js +518 -0
  251. package/dist/mcp/auth/host-account-cli.d.ts +2 -0
  252. package/dist/mcp/auth/host-account-cli.js +47 -0
  253. package/dist/mcp/auth/ownership.d.ts +37 -63
  254. package/dist/mcp/auth/ownership.js +19 -14
  255. package/dist/mcp/auth/principal.d.ts +1 -1
  256. package/dist/mcp/auth/repo-authorization.d.ts +22 -0
  257. package/dist/mcp/auth/repo-authorization.js +51 -0
  258. package/dist/mcp/auth/resource-server.d.ts +32 -3
  259. package/dist/mcp/auth/resource-server.js +84 -3
  260. package/dist/mcp/auth/self-hosted/browser.js +22 -8
  261. package/dist/mcp/auth/self-hosted/cli.d.ts +1 -1
  262. package/dist/mcp/auth/self-hosted/cli.js +47 -13
  263. package/dist/mcp/auth/self-hosted/credentials.d.ts +21 -3
  264. package/dist/mcp/auth/self-hosted/credentials.js +62 -4
  265. package/dist/mcp/auth/self-hosted/rate-limit.d.ts +1 -1
  266. package/dist/mcp/auth/self-hosted/session.d.ts +3 -2
  267. package/dist/mcp/context.d.ts +28 -2
  268. package/dist/mcp/errors.d.ts +25 -6
  269. package/dist/mcp/errors.js +98 -0
  270. package/dist/mcp/host-events/github-ingress.d.ts +36 -0
  271. package/dist/mcp/host-events/github-ingress.js +92 -0
  272. package/dist/mcp/host-events/github-user-callback.d.ts +18 -0
  273. package/dist/mcp/host-events/github-user-callback.js +50 -0
  274. package/dist/mcp/index.d.ts +8 -1
  275. package/dist/mcp/index.js +106 -16
  276. package/dist/mcp/server.d.ts +1 -1
  277. package/dist/mcp/server.js +27 -9
  278. package/dist/mcp/shared/page-style.d.ts +16 -0
  279. package/dist/mcp/shared/page-style.js +129 -0
  280. package/dist/mcp/tasks/manager.d.ts +1 -1
  281. package/dist/mcp/tools/agents.js +447 -49
  282. package/dist/mcp/tools/budget-groups.js +3 -3
  283. package/dist/mcp/tools/datastore.js +23 -16
  284. package/dist/mcp/tools/grants.d.ts +2 -0
  285. package/dist/mcp/tools/grants.js +239 -0
  286. package/dist/mcp/tools/help.d.ts +5 -0
  287. package/dist/mcp/tools/help.js +67 -0
  288. package/dist/mcp/tools/host-accounts.d.ts +2 -0
  289. package/dist/mcp/tools/host-accounts.js +114 -0
  290. package/dist/mcp/tools/memory.d.ts +7 -1
  291. package/dist/mcp/tools/memory.js +5 -5
  292. package/dist/mcp/tools/repositories.d.ts +2 -0
  293. package/dist/mcp/tools/repositories.js +181 -0
  294. package/dist/mcp/tools/runs.d.ts +6 -0
  295. package/dist/mcp/tools/runs.js +60 -6
  296. package/dist/mcp/tools/scheduling.js +4 -7
  297. package/dist/mcp/tools/secret-elicitation-form.d.ts +1 -1
  298. package/dist/mcp/tools/secret-elicitation-form.js +10 -28
  299. package/dist/mcp/tools/secret-elicitation.d.ts +1 -1
  300. package/dist/mcp/tools/secrets.js +18 -6
  301. package/dist/mcp/tools/services.d.ts +2 -0
  302. package/dist/mcp/tools/services.js +222 -0
  303. package/dist/mcp/tools/subagents.js +53 -17
  304. package/dist/mcp/tools/tools.d.ts +41 -0
  305. package/dist/mcp/tools/tools.js +202 -41
  306. package/dist/mcp/tools/trigger.js +36 -6
  307. package/dist/mcp/tools/webhooks.js +15 -4
  308. package/dist/mcp/transport/streamable-http.d.ts +11 -1
  309. package/dist/mcp/transport/streamable-http.js +32 -2
  310. package/dist/mcp/unattended-schedules.d.ts +1 -1
  311. package/dist/mcp/webhooks/ingress.d.ts +1 -1
  312. package/dist/providers/auth/delegating.d.ts +19 -0
  313. package/dist/providers/auth/delegating.js +71 -0
  314. package/dist/providers/auth/index.d.ts +3 -1
  315. package/dist/providers/auth/index.js +11 -0
  316. package/dist/providers/auth/self-hosted.d.ts +4 -3
  317. package/dist/providers/auth/self-hosted.js +16 -4
  318. package/dist/providers/auth/types.d.ts +6 -0
  319. package/dist/providers/coding-proxy/memory-ledger.d.ts +3 -0
  320. package/dist/providers/coding-proxy/memory-ledger.js +21 -2
  321. package/dist/providers/coding-proxy/metering.d.ts +4 -0
  322. package/dist/providers/coding-proxy/metering.js +28 -2
  323. package/dist/providers/coding-proxy/prisma-ledger.d.ts +18 -1
  324. package/dist/providers/coding-proxy/prisma-ledger.js +50 -5
  325. package/dist/providers/coding-proxy/proxy.d.ts +13 -0
  326. package/dist/providers/coding-proxy/proxy.js +479 -18
  327. package/dist/providers/coding-proxy/registry/audit.d.ts +131 -0
  328. package/dist/providers/coding-proxy/registry/audit.js +380 -0
  329. package/dist/providers/coding-proxy/registry/bounded-fetch.d.ts +16 -0
  330. package/dist/providers/coding-proxy/registry/bounded-fetch.js +77 -0
  331. package/dist/providers/coding-proxy/registry/plan.d.ts +58 -0
  332. package/dist/providers/coding-proxy/registry/plan.js +304 -0
  333. package/dist/providers/coding-proxy/registry/prisma-store.d.ts +34 -0
  334. package/dist/providers/coding-proxy/registry/prisma-store.js +151 -0
  335. package/dist/providers/coding-proxy/registry/service.d.ts +213 -0
  336. package/dist/providers/coding-proxy/registry/service.js +1137 -0
  337. package/dist/providers/coding-proxy/registry/store.d.ts +127 -0
  338. package/dist/providers/coding-proxy/registry/store.js +70 -0
  339. package/dist/providers/coding-proxy/runtime.d.ts +18 -1
  340. package/dist/providers/coding-proxy/runtime.js +63 -0
  341. package/dist/providers/coding-proxy/secure-fetch.js +0 -1
  342. package/dist/providers/coding-proxy/server.d.ts +11 -0
  343. package/dist/providers/coding-proxy/server.js +163 -0
  344. package/dist/providers/coding-proxy/types.d.ts +15 -0
  345. package/dist/providers/datastore/postgres.d.ts +1 -1
  346. package/dist/providers/engine/types.d.ts +10 -0
  347. package/dist/providers/executor/build.d.ts +3 -3
  348. package/dist/providers/executor/composition.d.ts +4 -1
  349. package/dist/providers/executor/composition.js +19 -0
  350. package/dist/providers/executor/container.d.ts +106 -4
  351. package/dist/providers/executor/container.js +305 -22
  352. package/dist/providers/executor/dbos.d.ts +3 -4
  353. package/dist/providers/executor/in-process.d.ts +2 -3
  354. package/dist/providers/executor/routing.d.ts +7 -0
  355. package/dist/providers/executor/routing.js +13 -0
  356. package/dist/providers/executor/types.d.ts +22 -0
  357. package/dist/providers/jobs/claude-tool-setup.d.ts +23 -0
  358. package/dist/providers/jobs/claude-tool-setup.js +50 -0
  359. package/dist/providers/jobs/collect-prune.d.ts +7 -0
  360. package/dist/providers/jobs/collect-prune.js +27 -0
  361. package/dist/providers/jobs/docker-isolation.d.ts +48 -3
  362. package/dist/providers/jobs/docker-isolation.js +213 -22
  363. package/dist/providers/jobs/docker-services.d.ts +35 -0
  364. package/dist/providers/jobs/docker-services.js +191 -0
  365. package/dist/providers/jobs/docker.d.ts +60 -2
  366. package/dist/providers/jobs/docker.js +313 -50
  367. package/dist/providers/jobs/fake-kubernetes-api.d.ts +1 -0
  368. package/dist/providers/jobs/fake-kubernetes-api.js +12 -3
  369. package/dist/providers/jobs/kubernetes-isolation.d.ts +17 -2
  370. package/dist/providers/jobs/kubernetes-isolation.js +238 -57
  371. package/dist/providers/jobs/kubernetes-platform.d.ts +10 -4
  372. package/dist/providers/jobs/kubernetes-platform.js +11 -5
  373. package/dist/providers/jobs/kubernetes-preflight.js +3 -0
  374. package/dist/providers/jobs/kubernetes.d.ts +21 -2
  375. package/dist/providers/jobs/kubernetes.js +117 -31
  376. package/dist/providers/jobs/types.d.ts +20 -0
  377. package/dist/providers/llm/anthropic.js +6 -2
  378. package/dist/providers/llm/bedrock.js +2 -1
  379. package/dist/providers/llm/claude-messages.d.ts +5 -1
  380. package/dist/providers/llm/claude-messages.js +1 -0
  381. package/dist/providers/llm/claude-provider.d.ts +3 -1
  382. package/dist/providers/llm/claude-provider.js +5 -1
  383. package/dist/providers/llm/index.d.ts +1 -1
  384. package/dist/providers/llm/index.js +1 -1
  385. package/dist/providers/llm/pricing-anthropic.d.ts +10 -0
  386. package/dist/providers/llm/pricing-anthropic.js +32 -14
  387. package/dist/providers/llm/pricing-bedrock-claude.d.ts +8 -0
  388. package/dist/providers/llm/pricing-bedrock-claude.js +9 -0
  389. package/dist/providers/llm/routing.d.ts +10 -1
  390. package/dist/providers/llm/routing.js +18 -0
  391. package/dist/providers/llm/types.d.ts +12 -0
  392. package/dist/providers/llm/types.js +8 -1
  393. package/dist/providers/memory/postgres.d.ts +1 -1
  394. package/dist/providers/review-host/diff-lines.d.ts +16 -0
  395. package/dist/providers/review-host/diff-lines.js +59 -0
  396. package/dist/providers/review-host/github-events.d.ts +6 -0
  397. package/dist/providers/review-host/github-events.js +180 -0
  398. package/dist/providers/review-host/github-user-auth.d.ts +38 -0
  399. package/dist/providers/review-host/github-user-auth.js +128 -0
  400. package/dist/providers/review-host/github.d.ts +57 -0
  401. package/dist/providers/review-host/github.js +567 -0
  402. package/dist/providers/review-host/index.d.ts +12 -0
  403. package/dist/providers/review-host/index.js +30 -0
  404. package/dist/providers/review-host/review-format.d.ts +19 -0
  405. package/dist/providers/review-host/review-format.js +59 -0
  406. package/dist/providers/review-host/types.d.ts +284 -0
  407. package/dist/providers/review-host/types.js +24 -0
  408. package/dist/providers/vcs/git.d.ts +16 -6
  409. package/dist/providers/vcs/git.js +79 -14
  410. package/dist/providers/vcs/github.d.ts +74 -3
  411. package/dist/providers/vcs/github.js +195 -15
  412. package/dist/providers/vcs/types.d.ts +42 -4
  413. package/dist/quickstart/index.js +34 -32
  414. package/dist/quickstart/migrate.d.ts +23 -0
  415. package/dist/quickstart/migrate.js +125 -0
  416. package/dist/sandbox/fetch-policy.d.ts +20 -2
  417. package/dist/sandbox/fetch-policy.js +64 -3
  418. package/dist/sandbox/host-functions.d.ts +9 -1
  419. package/dist/sandbox/host-functions.js +12 -5
  420. package/dist/serve.js +1 -1
  421. package/dist/wardby-bin.js +6 -0
  422. package/docs/README.md +30 -0
  423. package/docs/architecture-runtime.md +90 -0
  424. package/docs/assets/brand/wardby-icon-512.png +0 -0
  425. package/docs/assets/brand/wardby-icon.svg +16 -0
  426. package/docs/assets/brand/wardby-mascot-profile-512.png +0 -0
  427. package/docs/assets/brand/wardby-mascot.png +0 -0
  428. package/docs/assets/brand/wardby-mascot.svg +5 -0
  429. package/docs/assets/wardby-workflow.svg +106 -0
  430. package/docs/code-review-agents.md +481 -0
  431. package/docs/coding-agent-setup.md +172 -0
  432. package/docs/coding-packages.md +455 -0
  433. package/docs/coding-services.md +300 -0
  434. package/docs/coding-worker-byo-images.md +98 -0
  435. package/docs/coding-worker-isolation.md +1061 -0
  436. package/docs/getting-started-gke.md +615 -0
  437. package/docs/getting-started-identity-provider.md +308 -0
  438. package/docs/getting-started.md +133 -0
  439. package/docs/observability.md +53 -0
  440. package/docs/release-verification.md +66 -0
  441. package/docs/security-deployment.md +657 -0
  442. package/help/code-review-agents.md +31 -0
  443. package/help/coding-packages.md +31 -0
  444. package/help/coding-services.md +71 -0
  445. package/help/creating-agents.md +68 -0
  446. package/help/deploy-gke.md +39 -0
  447. package/help/deployment-targets.md +39 -0
  448. package/help/errors/budget-group-exhausted.md +25 -0
  449. package/help/errors/docker-isolation-unsupported.md +26 -0
  450. package/help/errors/protected-path.md +45 -0
  451. package/help/errors/repo-access.md +27 -0
  452. package/help/errors/service-declaration-invalid.md +27 -0
  453. package/help/errors/service-declaration-unavailable.md +25 -0
  454. package/help/errors/service-launcher-unsupported.md +30 -0
  455. package/help/errors/service-not-allowed.md +27 -0
  456. package/help/errors/service-unknown.md +23 -0
  457. package/help/errors/service-unready.md +49 -0
  458. package/help/getting-started.md +31 -0
  459. package/help/github.md +30 -0
  460. package/help/identity-and-access.md +39 -0
  461. package/help/mcp.md +30 -0
  462. package/help/native-capabilities.md +30 -0
  463. package/help/observability.md +41 -0
  464. package/help/operating-agents.md +30 -0
  465. package/help/security.md +27 -0
  466. package/help/troubleshooting/budgets.md +26 -0
  467. package/help/troubleshooting/coding-workers.md +47 -0
  468. package/help/troubleshooting/repository-access.md +27 -0
  469. package/package.json +30 -11
  470. package/prisma/migrate.config.mjs +20 -0
  471. package/prisma/migrations/20260925010000_coding_collect_exclude/migration.sql +8 -0
  472. package/prisma/migrations/20260925015000_allowed_egress_default/migration.sql +8 -0
  473. package/prisma/migrations/20260925020000_coding_package_registry/migration.sql +55 -0
  474. package/prisma/migrations/20260925030000_tool_name_per_owner/migration.sql +11 -0
  475. package/prisma/migrations/20260926010000_run_trigger_host_event/migration.sql +7 -0
  476. package/prisma/migrations/20260926020000_code_review_hosts/migration.sql +53 -0
  477. package/prisma/migrations/20260926030000_auth_user_roles/migration.sql +9 -0
  478. package/prisma/migrations/20260926040000_agent_effort/migration.sql +6 -0
  479. package/prisma/migrations/20260926050000_registry_lockfile_plan/migration.sql +32 -0
  480. package/prisma/migrations/20260926050000_repo_access_authorization/migration.sql +82 -0
  481. package/prisma/migrations/20260926060000_registry_plan_refusal/migration.sql +19 -0
  482. package/prisma/migrations/20260926100000_registry_plan_refusal_published_at/migration.sql +5 -0
  483. package/prisma/migrations/20260926190000_run_host_status/migration.sql +18 -0
  484. package/prisma/migrations/20260926210000_run_host_status_at_dispatch/migration.sql +5 -0
  485. package/prisma/migrations/20260927010000_resource_grants/migration.sql +72 -0
  486. package/prisma/migrations/20260927020000_proxy_session_budget_exhausted/migration.sql +3 -0
  487. package/prisma/migrations/20260927030000_coding_debug_trace/migration.sql +4 -0
  488. package/prisma/migrations/20260927040000_proxy_session_upstream_failure/migration.sql +3 -0
  489. package/prisma/migrations/20260927050000_coding_run_services/migration.sql +74 -0
  490. package/prisma/migrations/20260928000000_coding_run_tool_image/migration.sql +5 -0
  491. package/prisma/schema.prisma +417 -23
@@ -1,18 +1,58 @@
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";
5
7
  import { logger } from "../../core/logger.js";
6
- import { parseCodingAgentOutputJson, redactAndTruncate, MAX_CODING_ARTIFACT_BYTES } from "../../coding/protocol.js";
7
- import { assertDockerHostSupportsIsolation, assertClaudeAgentContainerInspection, assertClaudeToolRunnerContainerInspection, assertIsolationNetworkInspection, assertKeeperContainerInspection, assertProxyContainerInspection, assertStorageVolumeInspection, assertWorkerContainerInspection, buildDockerIsolationPlan, } from "./docker-isolation.js";
8
+ import { ensurePrivateDirectory } from "../../core/private-directory.js";
9
+ import { readBoundedRegularFile } from "../../coding-worker/artifact.js";
10
+ import { parseCodingAgentOutputJson, redactAndTruncate, MAX_CODING_ARTIFACT_BYTES, MAX_CODING_OUTPUT_ISSUES, SAFE_CODING_OUTPUT_ISSUE, } from "../../coding/protocol.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";
8
15
  import { replaceDirectoryFromStaging } from "./workspace-swap.js";
9
16
  const BACKEND = "docker";
10
17
  const STATE_SCHEMA_VERSION = 1;
11
18
  const MAX_DOCKER_OUTPUT_BYTES = 1024 * 1024;
12
19
  const MAX_DOCKER_SEED_DIAGNOSTIC_BYTES = 4 * 1024;
13
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;
14
29
  const TERMINAL_PHASES = new Set(["succeeded", "failed", "stopped", "lost", "removed"]);
15
30
  export const SAFE_WORKER_DIAGNOSTIC = /^(?:worker_[a-z_]+|coding_[a-z_]+|wardby_[a-z_]+)$/;
31
+ /**
32
+ * Worker output is untrusted: recognizes only the fixed `{"error": code}` line,
33
+ * plus an optional `issues` list whose every entry must pass the shared pattern.
34
+ * A list with any entry that does not is dropped whole rather than filtered, so
35
+ * a malformed worker cannot smuggle a partial value through.
36
+ */
37
+ export function parseWorkerDiagnosticLine(line) {
38
+ let value;
39
+ try {
40
+ value = JSON.parse(line);
41
+ }
42
+ catch {
43
+ return undefined;
44
+ }
45
+ if (typeof value?.error !== "string" || !SAFE_WORKER_DIAGNOSTIC.test(value.error))
46
+ return undefined;
47
+ const issues = value.issues;
48
+ const safeIssues = Array.isArray(issues) &&
49
+ issues.length > 0 &&
50
+ issues.length <= MAX_CODING_OUTPUT_ISSUES &&
51
+ issues.every((issue) => typeof issue === "string" && SAFE_CODING_OUTPUT_ISSUE.test(issue))
52
+ ? issues
53
+ : undefined;
54
+ return { diagnostic: value.error, ...(safeIssues ? { diagnosticIssues: safeIssues } : {}) };
55
+ }
16
56
  const dockerLog = logger.child({ module: "docker-jobs" });
17
57
  /**
18
58
  * Docker failures never include daemon output in their public message -- that
@@ -54,7 +94,7 @@ export class NodeDockerCommandRunner {
54
94
  if (args.some((argument) => argument.includes("\0")))
55
95
  throw new Error("docker_argument_invalid");
56
96
  const extraEnv = options.env ?? {};
57
- 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))
58
98
  throw new Error("docker_environment_invalid");
59
99
  const env = {
60
100
  PATH: this.path,
@@ -73,6 +113,13 @@ export class NodeDockerCommandRunner {
73
113
  rejectPromise(new DockerCommandError(null));
74
114
  return;
75
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);
76
123
  const stdout = [];
77
124
  const stderr = [];
78
125
  let bytes = 0;
@@ -93,6 +140,11 @@ export class NodeDockerCommandRunner {
93
140
  spawnFailed = true;
94
141
  });
95
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
+ }
96
148
  if (exceeded)
97
149
  return rejectPromise(new DockerCommandError(code, false, true));
98
150
  if (spawnFailed)
@@ -229,7 +281,7 @@ export class NodeDockerArtifactTransfer {
229
281
  });
230
282
  await docker.run(["container", "cp", sourceFile, `${container}:/run/wardby/storage/input/input.json`]);
231
283
  }
232
- async materializeDirectory(container, source, destination, maxBytes) {
284
+ async materializeDirectory(container, source, destination, maxBytes, exclusions) {
233
285
  await replaceDirectoryFromStaging(destination, maxBytes, "docker_workspace_destination_invalid", async (staging) => {
234
286
  await new NodeDockerCommandRunner({ dockerBinary: this.dockerBinary, homeDir: "/tmp", path: this.path }).run([
235
287
  "container",
@@ -237,6 +289,8 @@ export class NodeDockerArtifactTransfer {
237
289
  `${container}:${source}/.`,
238
290
  staging,
239
291
  ]);
292
+ // Before replaceDirectoryFromStaging validates: excluded folders never count.
293
+ await pruneCollectExcluded(staging, exclusions);
240
294
  });
241
295
  }
242
296
  }
@@ -353,6 +407,42 @@ function resourceLabels(record) {
353
407
  function labelsMatch(actual, expected) {
354
408
  return Object.entries(expected).every(([key, value]) => actual?.[key] === value);
355
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
+ }
356
446
  function capabilityFromInspection(container) {
357
447
  return container.Config?.Env?.find((value) => value.startsWith("WARDBY_RUN_CAPABILITY="))?.slice("WARDBY_RUN_CAPABILITY=".length);
358
448
  }
@@ -390,6 +480,8 @@ export class DockerJobLauncher {
390
480
  docker;
391
481
  transfer;
392
482
  now;
483
+ sleep;
484
+ serviceReadyTimeoutMs;
393
485
  locks = new Map();
394
486
  timers = new Map();
395
487
  ready;
@@ -402,8 +494,17 @@ export class DockerJobLauncher {
402
494
  this.docker = options.docker ?? new NodeDockerCommandRunner({ homeDir: join(this.stateRoot, ".home") });
403
495
  this.transfer = options.transfer ?? new NodeDockerArtifactTransfer();
404
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");
405
502
  this.ready = this.initialize();
406
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
+ }
407
508
  async launch(spec) {
408
509
  await this.ready;
409
510
  return this.withRun(spec.runId, async () => this.launchLocked(spec));
@@ -440,7 +541,7 @@ export class DockerJobLauncher {
440
541
  if (current.phase === "failed" && !current.result.diagnostic) {
441
542
  const diagnostic = await this.readWorkerFailureDiagnostic(current);
442
543
  if (diagnostic) {
443
- current.result = { ...current.result, diagnostic };
544
+ current.result = { ...current.result, ...diagnostic };
444
545
  await this.writeRecord(current);
445
546
  }
446
547
  }
@@ -466,7 +567,9 @@ export class DockerJobLauncher {
466
567
  if (resolve(destination) !== expected || !expected.startsWith(`${this.workspaceRoot}${sep}`)) {
467
568
  throw new Error("docker_workspace_destination_invalid");
468
569
  }
469
- 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));
470
573
  });
471
574
  }
472
575
  async stop(handle, _reason) {
@@ -500,7 +603,7 @@ export class DockerJobLauncher {
500
603
  });
501
604
  }
502
605
  async initialize() {
503
- await mkdir(this.stateRoot, { recursive: true, mode: 0o700 });
606
+ await ensurePrivateDirectory(this.stateRoot);
504
607
  await mkdir(join(this.stateRoot, ".home"), { recursive: true, mode: 0o700 });
505
608
  await realpath(this.stateRoot);
506
609
  const records = await this.allRecords();
@@ -530,6 +633,7 @@ export class DockerJobLauncher {
530
633
  }
531
634
  async launchLocked(spec) {
532
635
  const plan = buildDockerIsolationPlan(spec, this.options.proxyContainer);
636
+ const services = buildDockerServicePlan(spec);
533
637
  const specHash = stableSpecHash(spec);
534
638
  const existing = await this.readRecord(spec.runId);
535
639
  if (existing) {
@@ -554,7 +658,7 @@ export class DockerJobLauncher {
554
658
  await this.refresh(existing);
555
659
  return clone(existing.handle);
556
660
  }
557
- return this.provision(existing, plan, capability);
661
+ return this.provision(existing, plan, services, capability);
558
662
  }
559
663
  await this.validateInputArtifact(spec.inputArtifact);
560
664
  const capability = ensureCapability(await this.options.resolveCapability(spec.runId));
@@ -573,7 +677,7 @@ export class DockerJobLauncher {
573
677
  // This durable record is the idempotency fence before any Docker side effect.
574
678
  await this.writeRecord(record);
575
679
  try {
576
- return await this.provision(record, plan, capability);
680
+ return await this.provision(record, plan, services, capability);
577
681
  }
578
682
  catch (error) {
579
683
  await this.options
@@ -586,7 +690,7 @@ export class DockerJobLauncher {
586
690
  throw error;
587
691
  }
588
692
  }
589
- async provision(record, plan, capability) {
693
+ async provision(record, plan, services, capability) {
590
694
  await this.assertHost();
591
695
  const labels = resourceLabels(record);
592
696
  await this.createAndAssert(appendLabels(plan.networkCreateArgs, labels), ["network", "inspect", plan.names.network], (value) => {
@@ -613,13 +717,21 @@ export class DockerJobLauncher {
613
717
  await this.run(plan.proxyNetworkConnectArgs);
614
718
  const proxy = await this.inspect(["container", "inspect", this.options.proxyContainer]);
615
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;
616
728
  if (plan.toolCreateArgs) {
617
729
  await this.createAndAssert(appendLabels(plan.toolCreateArgs, labels), ["container", "inspect", plan.names.toolContainer], (value) => {
618
- assertClaudeToolRunnerContainerInspection(value, record.spec);
730
+ assertClaudeToolRunnerContainerInspection(value, record.spec, toolSetup, networkKeeperId);
619
731
  if (!labelsMatch(value.Config?.Labels, labels)) {
620
732
  throw new Error("docker_resource_attestation_failed");
621
733
  }
622
- });
734
+ }, { env: { WARDBY_TOOL_SETUP: toolSetup } });
623
735
  await this.startContainer(plan.names.toolContainer);
624
736
  await this.waitForToolRunner(plan.names.toolContainer);
625
737
  }
@@ -630,7 +742,7 @@ export class DockerJobLauncher {
630
742
  assertClaudeAgentContainerInspection(inspected, record.spec, capability);
631
743
  }
632
744
  else {
633
- assertWorkerContainerInspection(inspected, record.spec, capability);
745
+ assertWorkerContainerInspection(inspected, record.spec, capability, networkKeeperId);
634
746
  }
635
747
  if (!labelsMatch(inspected.Config?.Labels, labels))
636
748
  throw new Error("docker_resource_attestation_failed");
@@ -641,6 +753,138 @@ export class DockerJobLauncher {
641
753
  this.scheduleDeadline(record);
642
754
  return clone(record.handle);
643
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
+ }
644
888
  async refresh(record) {
645
889
  if (isTerminal(record))
646
890
  return;
@@ -674,7 +918,7 @@ export class DockerJobLauncher {
674
918
  "inspect",
675
919
  buildDockerIsolationPlan(record.spec, this.options.proxyContainer).names.toolContainer,
676
920
  ]);
677
- assertClaudeToolRunnerContainerInspection(tool, record.spec);
921
+ assertClaudeToolRunnerContainerInspection(tool, record.spec, claudeToolSetup(record.spec, capability), await this.attestedNetworkKeeperId(record));
678
922
  if (tool.State?.Running !== true || tool.State?.Status !== "running") {
679
923
  record.phase = "failed";
680
924
  record.result = { exitCode: 1, reason: "failed", diagnostic: "worker_tool_runner_failed" };
@@ -684,7 +928,7 @@ export class DockerJobLauncher {
684
928
  }
685
929
  }
686
930
  else {
687
- assertWorkerContainerInspection(inspection, record.spec, capability);
931
+ assertWorkerContainerInspection(inspection, record.spec, capability, await this.attestedNetworkKeeperId(record));
688
932
  }
689
933
  const phase = dockerState(inspection);
690
934
  if (!phase || phase === "provisioning" || phase === "active")
@@ -697,28 +941,27 @@ export class DockerJobLauncher {
697
941
  async stopRecord(record, timedOut) {
698
942
  if (isTerminal(record))
699
943
  return;
700
- try {
701
- await this.run(["container", "stop", "--time", "10", record.handle.id]);
702
- }
703
- catch (error) {
704
- if (!(error instanceof DockerCommandError && error.notFound))
705
- throw new Error("docker_stop_failed", { cause: error });
706
- }
944
+ await this.stopContainer(record.handle.id);
707
945
  if (record.spec.provider === "claude-code") {
708
- const tool = buildDockerIsolationPlan(record.spec, this.options.proxyContainer).names.toolContainer;
709
- try {
710
- await this.run(["container", "stop", "--time", "10", tool]);
711
- }
712
- catch (error) {
713
- if (!(error instanceof DockerCommandError && error.notFound))
714
- throw new Error("docker_stop_failed", { cause: error });
715
- }
946
+ await this.stopContainer(buildDockerIsolationPlan(record.spec, this.options.proxyContainer).names.toolContainer);
716
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);
717
951
  record.phase = timedOut ? "failed" : "stopped";
718
952
  record.result = timedOut ? { exitCode: 124, reason: "timed_out" } : resultFor("stopped");
719
953
  await this.writeRecord(record);
720
954
  this.clearDeadline(record);
721
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
+ }
722
965
  async sweepExpired(record) {
723
966
  // The DB verifier ran immediately before this call; never sweep by label alone.
724
967
  try {
@@ -738,17 +981,26 @@ export class DockerJobLauncher {
738
981
  await this.removeContainerIfAttested(plan.names.workerContainer, labels);
739
982
  if (plan.toolCreateArgs)
740
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
+ }
741
993
  await this.runIgnoreMissing(["network", "disconnect", plan.names.network, this.options.proxyContainer]);
742
994
  await this.removeContainerIfAttested(plan.names.keeperContainer, labels);
743
995
  await this.removeNetworkIfAttested(plan.names.network, labels);
744
996
  await this.removeVolumeIfAttested(plan.names.storageVolume, labels);
745
997
  }
746
- async removeContainerIfAttested(name, labels) {
998
+ async removeContainerIfAttested(name, labels, anonymousVolumes = false) {
747
999
  try {
748
1000
  const inspected = await this.inspect(["container", "inspect", name]);
749
1001
  if (!labelsMatch(inspected.Config?.Labels, labels))
750
1002
  throw new Error("docker_resource_attestation_failed");
751
- await this.runIgnoreMissing(["container", "rm", "--force", name]);
1003
+ await this.runIgnoreMissing(["container", "rm", "--force", ...(anonymousVolumes ? ["--volumes"] : []), name]);
752
1004
  }
753
1005
  catch (error) {
754
1006
  if (!(error instanceof DockerCommandError && error.notFound))
@@ -792,13 +1044,7 @@ export class DockerJobLauncher {
792
1044
  const target = join(staging, "result.json");
793
1045
  try {
794
1046
  await this.run(["container", "cp", `${this.keeperName(record)}:/run/wardby/storage/output/result.json`, target]);
795
- const metadata = await lstat(target);
796
- if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size > MAX_CODING_ARTIFACT_BYTES) {
797
- throw new Error("docker_result_artifact_invalid");
798
- }
799
- const raw = await readFile(target, "utf8");
800
- if (Buffer.byteLength(raw, "utf8") > MAX_CODING_ARTIFACT_BYTES)
801
- throw new Error("docker_result_artifact_invalid");
1047
+ const raw = await readBoundedRegularFile(target, MAX_CODING_ARTIFACT_BYTES, "docker_result_artifact_invalid");
802
1048
  const output = parseCodingAgentOutputJson(raw);
803
1049
  if (output.runId !== record.runId)
804
1050
  throw new Error("docker_result_run_mismatch");
@@ -818,14 +1064,9 @@ export class DockerJobLauncher {
818
1064
  const logs = await this.run(["container", "logs", "--tail", "8", record.handle.id]);
819
1065
  const raw = `${logs.stdout}\n${logs.stderr}`;
820
1066
  for (const line of raw.split("\n").reverse()) {
821
- try {
822
- const value = JSON.parse(line);
823
- if (typeof value.error === "string" && SAFE_WORKER_DIAGNOSTIC.test(value.error))
824
- return value.error;
825
- }
826
- catch {
827
- // Worker output is untrusted; only parse one fixed JSON shape.
828
- }
1067
+ const diagnostic = parseWorkerDiagnosticLine(line);
1068
+ if (diagnostic)
1069
+ return diagnostic;
829
1070
  }
830
1071
  }
831
1072
  catch {
@@ -836,18 +1077,40 @@ export class DockerJobLauncher {
836
1077
  keeperName(record) {
837
1078
  return buildDockerIsolationPlan(record.spec, this.options.proxyContainer).names.keeperContainer;
838
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
+ }
839
1098
  async assertHost() {
840
1099
  const info = await this.inspect(["info", "--format", "{{json .}}"]);
841
1100
  assertDockerHostSupportsIsolation(info);
842
1101
  }
843
- async createAndAssert(createArgs, inspectArgs, assert, options) {
1102
+ async createAndAssert(createArgs, inspectArgs, assert, options, onCreateFailure) {
844
1103
  try {
845
1104
  await this.run(createArgs, options);
846
1105
  }
847
- catch {
1106
+ catch (error) {
848
1107
  // A retry may find a resource created before the previous process crashed.
1108
+ if (error instanceof DockerCommandError)
1109
+ onCreateFailure?.(error);
849
1110
  }
850
- assert(await this.inspect(inspectArgs));
1111
+ const inspected = await this.inspect(inspectArgs);
1112
+ assert(inspected);
1113
+ return inspected;
851
1114
  }
852
1115
  async waitForStorage(keeper) {
853
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.