@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
@@ -51,5 +51,27 @@ export interface Executor {
51
51
  * the LLM pricing tables' unknown-model throw.
52
52
  */
53
53
  resolveCodingWorkerImage?(selector: CodingImageSelector): string;
54
+ /**
55
+ * Claude Code's tool-runner image (the container that runs its commands) for the agent's
56
+ * toolchain, resolved and persisted at dispatch alongside the worker image; null for Codex, whose
57
+ * one worker image carries the toolchain. Throws, like resolveCodingWorkerImage, on a toolchain
58
+ * with no image.
59
+ */
60
+ resolveCodingToolImage?(selector: CodingImageSelector): string | null;
61
+ /**
62
+ * Coding-run services (docs/coding-services.md): the raw text of the
63
+ * repository's .wardby/services.yaml at `baseRef`, or null when it has none.
64
+ * Called by dispatch before its transaction (it is a network call). An
65
+ * executor without it gives coding runs no services.
66
+ */
67
+ readCodingServiceDeclaration?(input: {
68
+ repository: string;
69
+ baseRef: string;
70
+ }): Promise<string | null>;
71
+ /**
72
+ * Whether this executor can start coding-run services for a run of `provider`: its job launcher
73
+ * says so (Kubernetes and Docker, for Codex and Claude Code).
74
+ */
75
+ supportsCodingServices?(provider: CodingProvider): boolean;
54
76
  }
55
77
  import type { CodingProvider } from "../../coding/provider.js";
@@ -0,0 +1,23 @@
1
+ import type { JobSpec } from "./types.js";
2
+ /**
3
+ * The tool runner's parseToolSetup bounds (src/claude-tool-runner/command.mjs; a test keeps them
4
+ * equal). A setup past any of them would stop the tool runner before it reports ready, so the
5
+ * launcher refuses to build one instead.
6
+ */
7
+ export declare const CLAUDE_TOOL_SETUP_LIMITS: {
8
+ readonly maxEnvEntries: 1024;
9
+ readonly maxEnvValueBytes: 4096;
10
+ readonly maxFiles: 8;
11
+ readonly maxFileBytes: number;
12
+ readonly maxSetupBytes: number;
13
+ };
14
+ export interface ClaudeToolSetup {
15
+ schemaVersion: 1;
16
+ env: Record<string, string>;
17
+ files: {
18
+ path: string;
19
+ content: string;
20
+ mode: number;
21
+ }[];
22
+ }
23
+ export declare function claudeToolSetup(spec: JobSpec, capability: string): string;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * What Claude Code's tool runner (the credential-free container that runs commands in the
3
+ * repository) needs from the trusted launcher: the package-registry settings every Codex shell
4
+ * gets, built from the run's registry-only token (deriveRegistryToken, never the capability), and
5
+ * the run's service test variables. Delivered as CLAUDE_TOOL_SETUP_ENV: a Kubernetes Secret key, or
6
+ * `docker create --env NAME` with the value in the CLI's own environment.
7
+ */
8
+ import { serviceEnvironment } from "../../coding-worker/driver.js";
9
+ import { registryWorkerSetup } from "../../coding/registry/worker-config.js";
10
+ import { CODING_PROXY_ALIAS, CODING_PROXY_PORT } from "./docker-isolation.js";
11
+ /** Where the tool runner mounts the repository; the registry's config files live under its .cache. */
12
+ const TOOL_WORKSPACE = "/workspace";
13
+ /**
14
+ * The tool runner's parseToolSetup bounds (src/claude-tool-runner/command.mjs; a test keeps them
15
+ * equal). A setup past any of them would stop the tool runner before it reports ready, so the
16
+ * launcher refuses to build one instead.
17
+ */
18
+ export const CLAUDE_TOOL_SETUP_LIMITS = {
19
+ maxEnvEntries: 1024,
20
+ maxEnvValueBytes: 4096,
21
+ maxFiles: 8,
22
+ maxFileBytes: 16 * 1024,
23
+ maxSetupBytes: 96 * 1024,
24
+ };
25
+ export function claudeToolSetup(spec, capability) {
26
+ if (spec.provider !== "claude-code")
27
+ throw new Error("claude_tool_setup_provider");
28
+ const registry = registryWorkerSetup({
29
+ proxyBaseUrl: `http://${CODING_PROXY_ALIAS}:${CODING_PROXY_PORT}`,
30
+ capability,
31
+ cacheRoot: `${TOOL_WORKSPACE}/.cache`,
32
+ });
33
+ const setup = {
34
+ schemaVersion: 1,
35
+ // Same precedence as the Codex driver: the registry's own settings win over a service's.
36
+ env: { ...serviceEnvironment(spec.services), ...registry.env },
37
+ files: registry.files,
38
+ };
39
+ const raw = JSON.stringify(setup);
40
+ const limits = CLAUDE_TOOL_SETUP_LIMITS;
41
+ const entries = Object.values(setup.env);
42
+ if (entries.length > limits.maxEnvEntries ||
43
+ entries.some((value) => Buffer.byteLength(value) > limits.maxEnvValueBytes) ||
44
+ setup.files.length > limits.maxFiles ||
45
+ setup.files.some((file) => Buffer.byteLength(file.content) > limits.maxFileBytes) ||
46
+ Buffer.byteLength(raw) > limits.maxSetupBytes) {
47
+ throw new Error("claude_tool_setup_too_large");
48
+ }
49
+ return raw;
50
+ }
@@ -0,0 +1,7 @@
1
+ import { type CollectExclusions } from "../../coding/collect-exclude.js";
2
+ /**
3
+ * Deletes every excluded path from a staging copy of a workspace before it is
4
+ * validated. Walks with lstat and never descends through a symlink; `rm` removes
5
+ * a symlink itself, not its target.
6
+ */
7
+ export declare function pruneCollectExcluded(root: string, exclusions: CollectExclusions): Promise<void>;
@@ -0,0 +1,27 @@
1
+ import { lstat, opendir, rm } from "node:fs/promises";
2
+ import { join, relative, sep } from "node:path";
3
+ import { isCollectExcluded } from "../../coding/collect-exclude.js";
4
+ /**
5
+ * Deletes every excluded path from a staging copy of a workspace before it is
6
+ * validated. Walks with lstat and never descends through a symlink; `rm` removes
7
+ * a symlink itself, not its target.
8
+ */
9
+ export async function pruneCollectExcluded(root, exclusions) {
10
+ const visit = async (directory) => {
11
+ const handle = await opendir(directory);
12
+ const children = [];
13
+ for await (const entry of handle)
14
+ children.push(join(directory, entry.name));
15
+ for (const child of children) {
16
+ const relativePath = relative(root, child).split(sep).join("/");
17
+ if (isCollectExcluded(relativePath, exclusions)) {
18
+ await rm(child, { recursive: true, force: true });
19
+ continue;
20
+ }
21
+ const metadata = await lstat(child);
22
+ if (metadata.isDirectory() && !metadata.isSymbolicLink())
23
+ await visit(child);
24
+ }
25
+ };
26
+ await visit(root);
27
+ }
@@ -1,8 +1,22 @@
1
+ import type { CodingProvider } from "../../coding/provider.js";
1
2
  import type { JobSpec } from "./types.js";
2
3
  export declare const CODING_WORKER_UID = 10001;
3
4
  export declare const CODING_WORKER_GID = 10001;
5
+ /**
6
+ * The network keeper (a run with services only): owns the run's network namespace so each
7
+ * service and the worker can share it with `--network container:`, as a pod's containers do.
8
+ */
9
+ export declare const NETWORK_KEEPER_PIDS_LIMIT = 32;
10
+ export declare const NETWORK_KEEPER_MEMORY_MB = 64;
11
+ /** Idles until stopped, on the worker image's own `node` (every worker image ships it for the storage keeper). */
12
+ export declare const NETWORK_KEEPER_SCRIPT = "process.once('SIGTERM', () => process.exit(0)); setInterval(() => {}, 2147483647);";
4
13
  export declare const CODING_PROXY_ALIAS = "wardby-proxy";
5
14
  export declare const CODING_PROXY_PORT = 8787;
15
+ /**
16
+ * The one variable Claude's tool runner receives: its package-registry and service settings as JSON
17
+ * (claude-tool-setup.ts). Never the run capability.
18
+ */
19
+ export declare const CLAUDE_TOOL_SETUP_ENV = "WARDBY_TOOL_SETUP";
6
20
  /**
7
21
  * The proxy's deny port. Nothing is served here (see coding-proxy/deny-port.ts);
8
22
  * it is the enforcement witness — the one destination a run's NetworkPolicy must
@@ -25,6 +39,7 @@ export interface DockerIsolationNames {
25
39
  keeperContainer: string;
26
40
  workerContainer: string;
27
41
  toolContainer: string;
42
+ networkKeeperContainer: string;
28
43
  }
29
44
  export interface DockerIsolationPlan {
30
45
  names: DockerIsolationNames;
@@ -35,6 +50,8 @@ export interface DockerIsolationPlan {
35
50
  keeperCreateArgs: string[];
36
51
  workerCreateArgs: string[];
37
52
  toolCreateArgs?: string[];
53
+ /** Present only for a run with services (docs/coding-services.md). */
54
+ networkKeeperCreateArgs?: string[];
38
55
  proxyNetworkConnectArgs: string[];
39
56
  }
40
57
  export interface DockerHostInfo {
@@ -70,11 +87,14 @@ export interface DockerVolumeInspection {
70
87
  Options?: Record<string, string>;
71
88
  }
72
89
  export interface DockerContainerInspection {
90
+ Id?: string;
73
91
  Config?: {
74
92
  Env?: string[];
75
93
  User?: string;
76
94
  Image?: string;
77
95
  Labels?: Record<string, string>;
96
+ Entrypoint?: string[] | null;
97
+ Cmd?: string[] | null;
78
98
  };
79
99
  HostConfig?: {
80
100
  AutoRemove?: boolean;
@@ -140,18 +160,42 @@ export interface DockerContainerInspection {
140
160
  Ports?: Record<string, unknown>;
141
161
  };
142
162
  }
163
+ /**
164
+ * Which runs the Docker launcher starts services for: Codex and Claude Code. The container that runs
165
+ * the run's commands (Codex's worker, Claude Code's tool runner) joins the network keeper's
166
+ * namespace and reaches the services on 127.0.0.1; Claude Code's agent stays on the run network.
167
+ */
168
+ export declare function dockerSupportsServicesFor(provider: CodingProvider): boolean;
143
169
  /** A registry digest reference (`[host[:port]/]path@sha256:<64 hex>`); no local image IDs, no tags. */
144
170
  export declare function isRepositoryDigest(image: string): boolean;
145
171
  /** Accepts a registry digest or Docker's content-addressed local image ID. */
146
172
  export declare function isImmutableDockerImage(image: string): boolean;
173
+ /** The opaque per-run token in every Docker object name; never the run ID itself. */
174
+ export declare function isolationToken(runId: string): string;
147
175
  export declare function isolationNames(runId: string): DockerIsolationNames;
176
+ /** The fixed isolation labels every run object carries (docker-services.ts builds with them). */
177
+ export declare function isolationLabelArgs(runId: string): string[];
178
+ export declare function hasIsolationLabels(labels: Record<string, string> | undefined, runId: string): boolean;
148
179
  export declare function buildIsolationNetworkCreateArgs(runId: string): string[];
149
180
  export declare function buildStorageVolumeCreateArgs(spec: JobSpec): string[];
150
181
  export declare function buildKeeperCreateArgs(spec: JobSpec): string[];
182
+ export declare function buildNetworkKeeperCreateArgs(spec: JobSpec): string[];
151
183
  export declare function buildWorkerCreateArgs(spec: JobSpec, proxyPort?: number): string[];
184
+ export declare function claudeToolLimits(spec: JobSpec): {
185
+ cpus: number;
186
+ memoryMb: number;
187
+ pids: number;
188
+ };
152
189
  /** The Claude agent sees the task/result and socket, never the repository workspace. */
153
190
  export declare function buildClaudeAgentCreateArgs(spec: JobSpec, proxyPort?: number): string[];
154
- /** The tool runner receives only the checkout and socket; it never joins the proxy network. */
191
+ /**
192
+ * The tool runner receives only the checkout and its own storage subpath; it shares the run's
193
+ * proxy network with the agent (one room) but holds no capability, so the proxy grants it only
194
+ * registry access via its own WARDBY_TOOL_SETUP value. For a run with services it joins the network
195
+ * keeper's namespace instead, which is on that same run network: it reaches the proxy by its alias
196
+ * and the services on 127.0.0.1, and nothing else. It talks to the agent over the Unix socket in
197
+ * their shared `tool` storage subpath, which does not depend on either container's network.
198
+ */
155
199
  export declare function buildClaudeToolRunnerCreateArgs(spec: JobSpec): string[];
156
200
  export declare function buildProxyNetworkConnectArgs(runId: string, proxyContainer: string): string[];
157
201
  export declare function buildDockerIsolationPlan(spec: JobSpec, proxyContainer: string): DockerIsolationPlan;
@@ -160,7 +204,8 @@ export declare function assertIsolationNetworkInspection(network: DockerNetworkI
160
204
  export declare function assertIsolationNetworkParticipants(network: DockerNetworkInspection, expectedContainerNames: string[]): void;
161
205
  export declare function assertStorageVolumeInspection(volume: DockerVolumeInspection, spec: JobSpec): void;
162
206
  export declare function assertKeeperContainerInspection(container: DockerContainerInspection, spec: JobSpec): void;
207
+ export declare function assertNetworkKeeperContainerInspection(container: DockerContainerInspection, spec: JobSpec): void;
163
208
  export declare function assertProxyContainerInspection(container: DockerContainerInspection, runId: string): void;
164
- export declare function assertWorkerContainerInspection(container: DockerContainerInspection, spec: JobSpec, expectedCapability: string): void;
209
+ export declare function assertWorkerContainerInspection(container: DockerContainerInspection, spec: JobSpec, expectedCapability: string, networkKeeperId?: string): void;
165
210
  export declare function assertClaudeAgentContainerInspection(container: DockerContainerInspection, spec: JobSpec, expectedCapability: string): void;
166
- export declare function assertClaudeToolRunnerContainerInspection(container: DockerContainerInspection, spec: JobSpec): void;
211
+ export declare function assertClaudeToolRunnerContainerInspection(container: DockerContainerInspection, spec: JobSpec, expectedSetup: string, networkKeeperId?: string): void;
@@ -3,8 +3,23 @@ export const CODING_WORKER_UID = 10001;
3
3
  export const CODING_WORKER_GID = 10001;
4
4
  // The keeper needs headroom for runc's short-lived exec process plus Node's threads.
5
5
  const KEEPER_PIDS_LIMIT = 32;
6
+ /**
7
+ * The network keeper (a run with services only): owns the run's network namespace so each
8
+ * service and the worker can share it with `--network container:`, as a pod's containers do.
9
+ */
10
+ export const NETWORK_KEEPER_PIDS_LIMIT = 32;
11
+ export const NETWORK_KEEPER_MEMORY_MB = 64;
12
+ /** `--cpus 0.1`, as Docker reports it. */
13
+ const NETWORK_KEEPER_NANO_CPUS = 100_000_000;
14
+ /** Idles until stopped, on the worker image's own `node` (every worker image ships it for the storage keeper). */
15
+ export const NETWORK_KEEPER_SCRIPT = "process.once('SIGTERM', () => process.exit(0)); setInterval(() => {}, 2147483647);";
6
16
  export const CODING_PROXY_ALIAS = "wardby-proxy";
7
17
  export const CODING_PROXY_PORT = 8787;
18
+ /**
19
+ * The one variable Claude's tool runner receives: its package-registry and service settings as JSON
20
+ * (claude-tool-setup.ts). Never the run capability.
21
+ */
22
+ export const CLAUDE_TOOL_SETUP_ENV = "WARDBY_TOOL_SETUP";
8
23
  /**
9
24
  * The proxy's deny port. Nothing is served here (see coding-proxy/deny-port.ts);
10
25
  * it is the enforcement witness — the one destination a run's NetworkPolicy must
@@ -50,6 +65,14 @@ function assertIntegerRange(value, min, max) {
50
65
  if (!Number.isSafeInteger(value) || value < min || value > max)
51
66
  throw isolationError();
52
67
  }
68
+ /**
69
+ * Which runs the Docker launcher starts services for: Codex and Claude Code. The container that runs
70
+ * the run's commands (Codex's worker, Claude Code's tool runner) joins the network keeper's
71
+ * namespace and reaches the services on 127.0.0.1; Claude Code's agent stays on the run network.
72
+ */
73
+ export function dockerSupportsServicesFor(provider) {
74
+ return provider === "codex" || provider === "claude-code";
75
+ }
53
76
  function validateSpec(spec) {
54
77
  if (spec.kind !== "coding-agent" || !/^[a-zA-Z0-9][a-zA-Z0-9_.:-]{0,199}$/.test(spec.runId)) {
55
78
  throw isolationError();
@@ -61,12 +84,18 @@ function validateSpec(spec) {
61
84
  if (spec.provider === "claude-code") {
62
85
  if (!spec.toolImage || !isImmutableDockerImage(spec.toolImage))
63
86
  throw isolationError();
64
- if (spec.limits.cpus < 0.35 || spec.limits.memoryMb < 256 || spec.limits.pids < 32)
87
+ if (spec.limits.cpus < 0.35 || spec.limits.memoryMb < 256 || spec.limits.pids < CLAUDE_MIN_PIDS)
65
88
  throw isolationError();
66
89
  }
67
90
  else if (spec.toolImage !== undefined) {
68
91
  throw isolationError();
69
92
  }
93
+ if (spec.services !== undefined) {
94
+ // docker-services.ts re-validates every entry against the catalog schema. Both providers' shells
95
+ // share the network keeper's namespace: Codex's worker, and Claude Code's tool runner.
96
+ if (spec.services.length === 0)
97
+ throw isolationError();
98
+ }
70
99
  assertFiniteRange(spec.limits.cpus, 0.1, 32);
71
100
  assertIntegerRange(spec.limits.memoryMb, 128, 65_536);
72
101
  assertIntegerRange(spec.limits.pids, 16, 4_096);
@@ -85,21 +114,33 @@ function validateDockerObject(value) {
85
114
  if (!DOCKER_OBJECT.test(value))
86
115
  throw isolationError();
87
116
  }
88
- export function isolationNames(runId) {
117
+ /** The opaque per-run token in every Docker object name; never the run ID itself. */
118
+ export function isolationToken(runId) {
89
119
  if (!/^[a-zA-Z0-9][a-zA-Z0-9_.:-]{0,199}$/.test(runId))
90
120
  throw isolationError();
91
- const token = runHash(runId).slice(0, 20);
121
+ return runHash(runId).slice(0, 20);
122
+ }
123
+ export function isolationNames(runId) {
124
+ const token = isolationToken(runId);
92
125
  return {
93
126
  network: `wardby-net-${token}`,
94
127
  storageVolume: `wardby-storage-${token}`,
95
128
  keeperContainer: `wardby-keeper-${token}`,
96
129
  workerContainer: `wardby-worker-${token}`,
97
130
  toolContainer: `wardby-tools-${token}`,
131
+ networkKeeperContainer: `wardby-netns-${token}`,
98
132
  };
99
133
  }
100
134
  function labels(runId) {
101
135
  return ["--label", LABEL_MANAGED, "--label", LABEL_COMPONENT, "--label", `io.wardby.run-sha256=${runHash(runId)}`];
102
136
  }
137
+ /** The fixed isolation labels every run object carries (docker-services.ts builds with them). */
138
+ export function isolationLabelArgs(runId) {
139
+ return labels(runId);
140
+ }
141
+ export function hasIsolationLabels(labels, runId) {
142
+ return hasResourceLabels(labels, runId);
143
+ }
103
144
  function storageMountOptions(spec) {
104
145
  const inodes = Math.max(4_096, Math.min(262_144, spec.limits.diskMb * 256));
105
146
  return `size=${spec.limits.diskMb}m,nr_inodes=${inodes},uid=${CODING_WORKER_UID},gid=${CODING_WORKER_GID},mode=0700,nosuid,nodev`;
@@ -192,6 +233,68 @@ export function buildKeeperCreateArgs(spec) {
192
233
  "/opt/wardby/coding-worker/keeper.js",
193
234
  ];
194
235
  }
236
+ export function buildNetworkKeeperCreateArgs(spec) {
237
+ validateSpec(spec);
238
+ if (spec.services === undefined)
239
+ throw isolationError();
240
+ const names = isolationNames(spec.runId);
241
+ return [
242
+ "container",
243
+ "create",
244
+ "--name",
245
+ names.networkKeeperContainer,
246
+ "--pull",
247
+ "never",
248
+ "--user",
249
+ `${CODING_WORKER_UID}:${CODING_WORKER_GID}`,
250
+ "--network",
251
+ names.network,
252
+ "--read-only",
253
+ "--cap-drop",
254
+ "ALL",
255
+ "--security-opt",
256
+ "no-new-privileges=true",
257
+ "--security-opt",
258
+ "seccomp=builtin",
259
+ "--init",
260
+ "--cgroupns",
261
+ "private",
262
+ "--ipc",
263
+ "none",
264
+ "--cpus",
265
+ "0.1",
266
+ "--memory",
267
+ `${NETWORK_KEEPER_MEMORY_MB}m`,
268
+ "--memory-swap",
269
+ `${NETWORK_KEEPER_MEMORY_MB}m`,
270
+ "--memory-swappiness",
271
+ "0",
272
+ "--pids-limit",
273
+ String(NETWORK_KEEPER_PIDS_LIMIT),
274
+ "--restart",
275
+ "no",
276
+ "--log-driver",
277
+ "local",
278
+ "--log-opt",
279
+ "max-size=1m",
280
+ "--log-opt",
281
+ "max-file=2",
282
+ ...labels(spec.runId),
283
+ "--entrypoint",
284
+ "node",
285
+ spec.image,
286
+ "-e",
287
+ NETWORK_KEEPER_SCRIPT,
288
+ ];
289
+ }
290
+ /**
291
+ * The network of the container that runs the run's commands (Codex's worker, Claude Code's tool
292
+ * runner): the run network, or the network keeper's namespace for a run with services.
293
+ */
294
+ function workerNetworkMode(spec) {
295
+ const names = isolationNames(spec.runId);
296
+ return spec.services === undefined ? names.network : `container:${names.networkKeeperContainer}`;
297
+ }
195
298
  export function buildWorkerCreateArgs(spec, proxyPort = CODING_PROXY_PORT) {
196
299
  validateSpec(spec);
197
300
  assertIntegerRange(proxyPort, 1, 65_535);
@@ -207,7 +310,7 @@ export function buildWorkerCreateArgs(spec, proxyPort = CODING_PROXY_PORT) {
207
310
  "--user",
208
311
  `${CODING_WORKER_UID}:${CODING_WORKER_GID}`,
209
312
  "--network",
210
- names.network,
313
+ workerNetworkMode(spec),
211
314
  "--read-only",
212
315
  "--cap-drop",
213
316
  "ALL",
@@ -262,11 +365,15 @@ export function buildWorkerCreateArgs(spec, proxyPort = CODING_PROXY_PORT) {
262
365
  spec.image,
263
366
  ];
264
367
  }
265
- function claudeToolLimits(spec) {
368
+ /** The tool runner's shell + npm shim (two Node processes) + npm peak near 30 PIDs; 64 leaves headroom. */
369
+ const CLAUDE_TOOL_PIDS = 64;
370
+ /** A Claude run must leave its agent at least 32 PIDs after the tool runner's share. */
371
+ const CLAUDE_MIN_PIDS = CLAUDE_TOOL_PIDS + 32;
372
+ export function claudeToolLimits(spec) {
266
373
  return {
267
374
  cpus: 0.25,
268
375
  memoryMb: Math.min(512, Math.max(128, Math.floor(spec.limits.memoryMb / 3))),
269
- pids: 16,
376
+ pids: CLAUDE_TOOL_PIDS,
270
377
  };
271
378
  }
272
379
  function claudeAgentLimits(spec) {
@@ -351,7 +458,14 @@ export function buildClaudeAgentCreateArgs(spec, proxyPort = CODING_PROXY_PORT)
351
458
  spec.image,
352
459
  ];
353
460
  }
354
- /** The tool runner receives only the checkout and socket; it never joins the proxy network. */
461
+ /**
462
+ * The tool runner receives only the checkout and its own storage subpath; it shares the run's
463
+ * proxy network with the agent (one room) but holds no capability, so the proxy grants it only
464
+ * registry access via its own WARDBY_TOOL_SETUP value. For a run with services it joins the network
465
+ * keeper's namespace instead, which is on that same run network: it reaches the proxy by its alias
466
+ * and the services on 127.0.0.1, and nothing else. It talks to the agent over the Unix socket in
467
+ * their shared `tool` storage subpath, which does not depend on either container's network.
468
+ */
355
469
  export function buildClaudeToolRunnerCreateArgs(spec) {
356
470
  validateSpec(spec);
357
471
  if (spec.provider !== "claude-code" || !spec.toolImage)
@@ -368,8 +482,11 @@ export function buildClaudeToolRunnerCreateArgs(spec) {
368
482
  "never",
369
483
  "--user",
370
484
  `${CODING_WORKER_UID}:${CODING_WORKER_GID}`,
485
+ // One room (docs/coding-worker-isolation.md): the run's proxy network, like the agent, so npm and
486
+ // pip reach the registry. It holds no capability, so the proxy refuses it anything else. With
487
+ // services: the network keeper's namespace on that network, so the services answer on 127.0.0.1.
371
488
  "--network",
372
- "none",
489
+ workerNetworkMode(spec),
373
490
  "--read-only",
374
491
  "--cap-drop",
375
492
  "ALL",
@@ -402,6 +519,9 @@ export function buildClaudeToolRunnerCreateArgs(spec) {
402
519
  `type=volume,src=${names.storageVolume},dst=${WORKER_PATHS.workspace},volume-subpath=workspace,volume-nocopy`,
403
520
  "--mount",
404
521
  `type=volume,src=${names.storageVolume},dst=${WORKER_PATHS.tool},volume-subpath=tool,volume-nocopy`,
522
+ // By name only: the value (claude-tool-setup.ts) is in the docker CLI's environment, never argv.
523
+ "--env",
524
+ CLAUDE_TOOL_SETUP_ENV,
405
525
  "--restart",
406
526
  "no",
407
527
  "--stop-signal",
@@ -434,6 +554,7 @@ export function buildDockerIsolationPlan(spec, proxyContainer) {
434
554
  keeperCreateArgs: buildKeeperCreateArgs(spec),
435
555
  workerCreateArgs: spec.provider === "claude-code" ? buildClaudeAgentCreateArgs(spec) : buildWorkerCreateArgs(spec),
436
556
  ...(spec.provider === "claude-code" ? { toolCreateArgs: buildClaudeToolRunnerCreateArgs(spec) } : {}),
557
+ ...(spec.services !== undefined ? { networkKeeperCreateArgs: buildNetworkKeeperCreateArgs(spec) } : {}),
437
558
  proxyNetworkConnectArgs: buildProxyNetworkConnectArgs(spec.runId, proxyContainer),
438
559
  };
439
560
  }
@@ -520,6 +641,77 @@ export function assertKeeperContainerInspection(container, spec) {
520
641
  throw isolationError();
521
642
  }
522
643
  }
644
+ export function assertNetworkKeeperContainerInspection(container, spec) {
645
+ validateSpec(spec);
646
+ if (spec.services === undefined)
647
+ throw isolationError();
648
+ const names = isolationNames(spec.runId);
649
+ const host = container.HostConfig;
650
+ const security = host?.SecurityOpt ?? [];
651
+ if (container.Config?.User !== `${CODING_WORKER_UID}:${CODING_WORKER_GID}` ||
652
+ container.Config?.Image !== spec.image ||
653
+ !hasResourceLabels(container.Config.Labels, spec.runId) ||
654
+ host?.NetworkMode !== names.network ||
655
+ host.ReadonlyRootfs !== true ||
656
+ host.Privileged !== false ||
657
+ (host.Binds?.length ?? 0) !== 0 ||
658
+ (host.CapAdd?.length ?? 0) !== 0 ||
659
+ !host.CapDrop?.includes("ALL") ||
660
+ !security.includes("no-new-privileges=true") ||
661
+ !security.includes("seccomp=builtin") ||
662
+ host.PidsLimit !== NETWORK_KEEPER_PIDS_LIMIT ||
663
+ host.Memory !== NETWORK_KEEPER_MEMORY_MB * 1024 * 1024 ||
664
+ host.MemorySwap !== host.Memory ||
665
+ // cgroup v2 hosts may report this as null after accepting the explicit
666
+ // no-swappiness request; MemorySwap still attests that swap is disabled.
667
+ (host.MemorySwappiness !== 0 && host.MemorySwappiness !== null) ||
668
+ host.NanoCpus !== NETWORK_KEEPER_NANO_CPUS ||
669
+ host.CgroupnsMode !== "private" ||
670
+ host.IpcMode !== "none" ||
671
+ host.PidMode !== "" ||
672
+ host.Init !== true ||
673
+ host.RestartPolicy?.Name !== "no" ||
674
+ host.LogConfig?.Type !== "local" ||
675
+ host.LogConfig.Config?.["max-size"] !== "1m" ||
676
+ host.LogConfig.Config?.["max-file"] !== "2" ||
677
+ (host.Devices?.length ?? 0) !== 0 ||
678
+ (host.DeviceRequests?.length ?? 0) !== 0 ||
679
+ (host.GroupAdd?.length ?? 0) !== 0 ||
680
+ // The worker and services take their DNS and port view from this container.
681
+ (host.Dns?.length ?? 0) !== 0 ||
682
+ (host.DnsOptions?.length ?? 0) !== 0 ||
683
+ (host.DnsSearch?.length ?? 0) !== 0 ||
684
+ (host.ExtraHosts?.length ?? 0) !== 0 ||
685
+ Object.keys(host.PortBindings ?? {}).length !== 0 ||
686
+ host.PublishAllPorts !== false ||
687
+ Object.keys(container.NetworkSettings?.Ports ?? {}).length !== 0 ||
688
+ (host.Mounts?.length ?? 0) !== 0 ||
689
+ (container.Mounts?.length ?? 0) !== 0 ||
690
+ Object.keys(container.NetworkSettings?.Networks ?? {}).join(",") !== names.network ||
691
+ // Exactly the idle script buildNetworkKeeperCreateArgs runs, never the image's own entrypoint.
692
+ JSON.stringify(container.Config.Entrypoint ?? null) !== JSON.stringify(["node"]) ||
693
+ JSON.stringify(container.Config.Cmd ?? null) !== JSON.stringify(["-e", NETWORK_KEEPER_SCRIPT])) {
694
+ throw isolationError();
695
+ }
696
+ }
697
+ /**
698
+ * The worker (Codex) or tool runner (Claude Code) of a run without services is on the run network
699
+ * only. With services it is in the network keeper's namespace: Docker reports `container:<name>`
700
+ * before start and `container:<id>` after, and lists no networks of its own.
701
+ */
702
+ function workerNetworkAttested(container, spec, networkKeeperId) {
703
+ const names = isolationNames(spec.runId);
704
+ const mode = container.HostConfig?.NetworkMode;
705
+ const networks = Object.keys(container.NetworkSettings?.Networks ?? {});
706
+ if (spec.services === undefined) {
707
+ return mode === names.network && networks.length === 1 && networks[0] === names.network;
708
+ }
709
+ const shared = [
710
+ `container:${names.networkKeeperContainer}`,
711
+ ...(networkKeeperId ? [`container:${networkKeeperId}`] : []),
712
+ ];
713
+ return mode !== undefined && shared.includes(mode) && networks.length === 0;
714
+ }
523
715
  export function assertProxyContainerInspection(container, runId) {
524
716
  const internalNetwork = isolationNames(runId).network;
525
717
  const networks = container.NetworkSettings?.Networks ?? {};
@@ -571,11 +763,10 @@ function assertExactWorkerMounts(container, names) {
571
763
  }
572
764
  }
573
765
  }
574
- export function assertWorkerContainerInspection(container, spec, expectedCapability) {
766
+ export function assertWorkerContainerInspection(container, spec, expectedCapability, networkKeeperId) {
575
767
  validateSpec(spec);
576
768
  const names = isolationNames(spec.runId);
577
769
  const host = container.HostConfig;
578
- const networks = Object.keys(container.NetworkSettings?.Networks ?? {});
579
770
  const security = host?.SecurityOpt ?? [];
580
771
  const wardbyEnvironment = (container.Config?.Env ?? []).filter((value) => value.startsWith("WARDBY_"));
581
772
  if (container.Config?.User !== `${CODING_WORKER_UID}:${CODING_WORKER_GID}` ||
@@ -594,7 +785,7 @@ export function assertWorkerContainerInspection(container, spec, expectedCapabil
594
785
  host.PidsLimit !== spec.limits.pids ||
595
786
  host.NanoCpus !== Math.round(spec.limits.cpus * 1_000_000_000) ||
596
787
  host.ShmSize !== 16 * 1024 * 1024 ||
597
- host.NetworkMode !== names.network ||
788
+ !workerNetworkAttested(container, spec, networkKeeperId) ||
598
789
  host.PidMode !== "" ||
599
790
  host.RestartPolicy?.Name !== "no" ||
600
791
  host.LogConfig?.Type !== "local" ||
@@ -611,9 +802,7 @@ export function assertWorkerContainerInspection(container, spec, expectedCapabil
611
802
  (host.GroupAdd?.length ?? 0) !== 0 ||
612
803
  Object.keys(host.PortBindings ?? {}).length !== 0 ||
613
804
  host.PublishAllPorts !== false ||
614
- Object.keys(container.NetworkSettings?.Ports ?? {}).length !== 0 ||
615
- networks.length !== 1 ||
616
- networks[0] !== names.network) {
805
+ Object.keys(container.NetworkSettings?.Ports ?? {}).length !== 0) {
617
806
  throw isolationError();
618
807
  }
619
808
  if (wardbyEnvironment.length !== 2 ||
@@ -645,7 +834,7 @@ function assertExactMountSet(container, names, expected) {
645
834
  throw isolationError();
646
835
  }
647
836
  }
648
- function assertClaudeContainerBaseline(container, image, network, spec, limits) {
837
+ function assertClaudeContainerBaseline(container, image, spec, limits) {
649
838
  const host = container.HostConfig;
650
839
  const security = host?.SecurityOpt ?? [];
651
840
  if (container.Config?.User !== `${CODING_WORKER_UID}:${CODING_WORKER_GID}` ||
@@ -667,7 +856,6 @@ function assertClaudeContainerBaseline(container, image, network, spec, limits)
667
856
  host.PidsLimit !== limits.pids ||
668
857
  host.NanoCpus !== Math.round(limits.cpus * 1_000_000_000) ||
669
858
  host.ShmSize !== 16 * 1024 * 1024 ||
670
- host.NetworkMode !== network ||
671
859
  host.PidMode !== "" ||
672
860
  host.RestartPolicy?.Name !== "no" ||
673
861
  host.LogConfig?.Type !== "local" ||
@@ -693,7 +881,7 @@ export function assertClaudeAgentContainerInspection(container, spec, expectedCa
693
881
  if (spec.provider !== "claude-code")
694
882
  throw isolationError();
695
883
  const names = isolationNames(spec.runId);
696
- assertClaudeContainerBaseline(container, spec.image, names.network, spec, claudeAgentLimits(spec));
884
+ assertClaudeContainerBaseline(container, spec.image, spec, claudeAgentLimits(spec));
697
885
  const environment = (container.Config?.Env ?? []).filter((value) => value.startsWith("WARDBY_"));
698
886
  if (environment.length !== 2 ||
699
887
  !environment.includes(`WARDBY_PROXY_URL=http://${CODING_PROXY_ALIAS}:${CODING_PROXY_PORT}`) ||
@@ -704,18 +892,21 @@ export function assertClaudeAgentContainerInspection(container, spec, expectedCa
704
892
  { path: WORKER_PATHS.output, writable: true, subpath: "output" },
705
893
  { path: WORKER_PATHS.tool, writable: true, subpath: "tool" },
706
894
  ]);
707
- if (Object.keys(container.NetworkSettings?.Networks ?? {}).join(",") !== names.network)
895
+ // The agent holds the capability: always on the run network only, never in the keeper's namespace.
896
+ if (container.HostConfig?.NetworkMode !== names.network ||
897
+ Object.keys(container.NetworkSettings?.Networks ?? {}).join(",") !== names.network)
708
898
  throw isolationError();
709
899
  }
710
- export function assertClaudeToolRunnerContainerInspection(container, spec) {
900
+ export function assertClaudeToolRunnerContainerInspection(container, spec, expectedSetup, networkKeeperId) {
711
901
  validateSpec(spec);
712
902
  if (spec.provider !== "claude-code" || !spec.toolImage)
713
903
  throw isolationError();
714
904
  const names = isolationNames(spec.runId);
715
- assertClaudeContainerBaseline(container, spec.toolImage, "none", spec, claudeToolLimits(spec));
716
- if ((container.Config?.Env ?? []).some((value) => value.startsWith("WARDBY_")))
905
+ assertClaudeContainerBaseline(container, spec.toolImage, spec, claudeToolLimits(spec));
906
+ const environment = (container.Config?.Env ?? []).filter((value) => value.startsWith("WARDBY_"));
907
+ if (environment.length !== 1 || environment[0] !== `${CLAUDE_TOOL_SETUP_ENV}=${expectedSetup}`)
717
908
  throw isolationError();
718
- if (Object.keys(container.NetworkSettings?.Networks ?? {}).join(",") !== "none")
909
+ if (!workerNetworkAttested(container, spec, networkKeeperId))
719
910
  throw isolationError();
720
911
  assertExactMountSet(container, names, [
721
912
  { path: WORKER_PATHS.workspace, writable: true, subpath: "workspace" },