@wardby/cli 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (407) hide show
  1. package/.env.example +34 -4
  2. package/README.md +41 -4
  3. package/dist/claude-coding-worker/driver.d.ts +6 -1
  4. package/dist/claude-coding-worker/driver.js +27 -1
  5. package/dist/claude-coding-worker/main.js +2 -0
  6. package/dist/claude-coding-worker/tool-socket.d.ts +9 -0
  7. package/dist/claude-coding-worker/tool-socket.js +26 -0
  8. package/dist/cli-help.d.ts +1 -1
  9. package/dist/cli-help.js +13 -4
  10. package/dist/cli.d.ts +1 -1
  11. package/dist/cli.js +219 -29
  12. package/dist/coding/collect-exclude.d.ts +25 -0
  13. package/dist/coding/collect-exclude.js +76 -0
  14. package/dist/coding/profile.d.ts +67 -22
  15. package/dist/coding/profile.js +58 -25
  16. package/dist/coding/protected-path-wording.d.ts +24 -0
  17. package/dist/coding/protected-path-wording.js +55 -0
  18. package/dist/coding/protected-paths.d.ts +31 -0
  19. package/dist/coding/protected-paths.js +48 -0
  20. package/dist/coding/protocol.d.ts +75 -4
  21. package/dist/coding/protocol.js +107 -7
  22. package/dist/coding/registry/adapters.d.ts +2 -0
  23. package/dist/coding/registry/adapters.js +9 -0
  24. package/dist/coding/registry/allowlist.d.ts +9 -0
  25. package/dist/coding/registry/allowlist.js +28 -0
  26. package/dist/coding/registry/json-scan.d.ts +55 -0
  27. package/dist/coding/registry/json-scan.js +191 -0
  28. package/dist/coding/registry/lockfiles.d.ts +6 -0
  29. package/dist/coding/registry/lockfiles.js +89 -0
  30. package/dist/coding/registry/npm-lockfile.d.ts +9 -0
  31. package/dist/coding/registry/npm-lockfile.js +73 -0
  32. package/dist/coding/registry/npm-plan.d.ts +29 -0
  33. package/dist/coding/registry/npm-plan.js +289 -0
  34. package/dist/coding/registry/npm.d.ts +7 -0
  35. package/dist/coding/registry/npm.js +252 -0
  36. package/dist/coding/registry/pypi.d.ts +4 -0
  37. package/dist/coding/registry/pypi.js +210 -0
  38. package/dist/coding/registry/report.d.ts +37 -0
  39. package/dist/coding/registry/report.js +31 -0
  40. package/dist/coding/registry/token.d.ts +4 -0
  41. package/dist/coding/registry/token.js +7 -0
  42. package/dist/coding/registry/types.d.ts +286 -0
  43. package/dist/coding/registry/types.js +19 -0
  44. package/dist/coding/registry/worker-config.d.ts +14 -0
  45. package/dist/coding/registry/worker-config.js +31 -0
  46. package/dist/coding/services/builtins.d.ts +22 -0
  47. package/dist/coding/services/builtins.js +85 -0
  48. package/dist/coding/services/catalog.d.ts +443 -0
  49. package/dist/coding/services/catalog.js +159 -0
  50. package/dist/coding/services/declaration.d.ts +11 -0
  51. package/dist/coding/services/declaration.js +114 -0
  52. package/dist/coding/services/note.d.ts +8 -0
  53. package/dist/coding/services/note.js +15 -0
  54. package/dist/coding/services/resolve.d.ts +25 -0
  55. package/dist/coding/services/resolve.js +50 -0
  56. package/dist/coding/services/wording.d.ts +25 -0
  57. package/dist/coding/services/wording.js +70 -0
  58. package/dist/coding-proxy/main.js +24 -4
  59. package/dist/coding-worker/artifact.d.ts +6 -0
  60. package/dist/coding-worker/debug-trace.d.ts +37 -0
  61. package/dist/coding-worker/debug-trace.js +117 -0
  62. package/dist/coding-worker/driver.d.ts +13 -1
  63. package/dist/coding-worker/driver.js +102 -13
  64. package/dist/coding-worker/errors.js +8 -0
  65. package/dist/coding-worker/main.js +10 -2
  66. package/dist/coding-worker/sdk.d.ts +2 -2
  67. package/dist/coding-worker/sdk.js +7 -2
  68. package/dist/coding-worker/types.d.ts +3 -0
  69. package/dist/config/providers.d.ts +41 -0
  70. package/dist/config/providers.js +66 -0
  71. package/dist/core/budget-groups.d.ts +108 -5
  72. package/dist/core/budget-groups.js +125 -19
  73. package/dist/core/budget-wording.d.ts +22 -0
  74. package/dist/core/budget-wording.js +55 -0
  75. package/dist/core/datastores.js +18 -2
  76. package/dist/core/db.d.ts +5 -1
  77. package/dist/core/db.js +8 -2
  78. package/dist/core/dispatch.d.ts +33 -6
  79. package/dist/core/dispatch.js +260 -43
  80. package/dist/core/engine-native.js +27 -10
  81. package/dist/core/grants.d.ts +86 -0
  82. package/dist/core/grants.js +126 -0
  83. package/dist/core/host-events.d.ts +49 -0
  84. package/dist/core/host-events.js +257 -0
  85. package/dist/core/host-identity-links.d.ts +54 -0
  86. package/dist/core/host-identity-links.js +189 -0
  87. package/dist/core/host-status.d.ts +61 -0
  88. package/dist/core/host-status.js +211 -0
  89. package/dist/core/in-flight-runs.d.ts +8 -0
  90. package/dist/core/in-flight-runs.js +56 -0
  91. package/dist/core/provider-wording.d.ts +12 -0
  92. package/dist/core/provider-wording.js +44 -0
  93. package/dist/core/reconciler.d.ts +38 -2
  94. package/dist/core/reconciler.js +86 -2
  95. package/dist/core/repo-access.d.ts +99 -0
  96. package/dist/core/repo-access.js +136 -0
  97. package/dist/core/review-host-checks.d.ts +11 -0
  98. package/dist/core/review-host-checks.js +41 -0
  99. package/dist/core/review-host-tools.d.ts +47 -0
  100. package/dist/core/review-host-tools.js +347 -0
  101. package/dist/core/run-heartbeat.d.ts +27 -0
  102. package/dist/core/run-heartbeat.js +54 -0
  103. package/dist/core/runner.d.ts +30 -8
  104. package/dist/core/runner.js +237 -40
  105. package/dist/core/secrets.d.ts +11 -2
  106. package/dist/core/secrets.js +25 -6
  107. package/dist/core/subagent-memory-tools.d.ts +1 -1
  108. package/dist/core/subagent-memory-tools.js +16 -3
  109. package/dist/core/tool-admin.d.ts +81 -0
  110. package/dist/core/tool-admin.js +129 -0
  111. package/dist/core/tool-names.d.ts +42 -0
  112. package/dist/core/tool-names.js +64 -0
  113. package/dist/core/untrusted-content.d.ts +32 -0
  114. package/dist/core/untrusted-content.js +72 -0
  115. package/dist/core/webhooks.d.ts +8 -1
  116. package/dist/core/webhooks.js +23 -2
  117. package/dist/generated/prisma/browser.d.ts +100 -0
  118. package/dist/generated/prisma/client.d.ts +100 -0
  119. package/dist/generated/prisma/commonInputTypes.d.ts +30 -0
  120. package/dist/generated/prisma/enums.d.ts +6 -0
  121. package/dist/generated/prisma/enums.js +6 -1
  122. package/dist/generated/prisma/internal/class.d.ts +143 -0
  123. package/dist/generated/prisma/internal/class.js +4 -4
  124. package/dist/generated/prisma/internal/prismaNamespace.d.ts +1746 -577
  125. package/dist/generated/prisma/internal/prismaNamespace.js +179 -6
  126. package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +186 -0
  127. package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +179 -6
  128. package/dist/generated/prisma/models/Agent.d.ts +294 -1
  129. package/dist/generated/prisma/models/AgentRepository.d.ts +1425 -0
  130. package/dist/generated/prisma/models/AgentRepository.js +1 -0
  131. package/dist/generated/prisma/models/AgentTool.d.ts +95 -1
  132. package/dist/generated/prisma/models/AuthUser.d.ts +56 -1
  133. package/dist/generated/prisma/models/CodingAgentProfile.d.ts +258 -7
  134. package/dist/generated/prisma/models/CodingProxySession.d.ts +123 -2
  135. package/dist/generated/prisma/models/CodingRun.d.ts +1276 -95
  136. package/dist/generated/prisma/models/CodingService.d.ts +1348 -0
  137. package/dist/generated/prisma/models/CodingService.js +1 -0
  138. package/dist/generated/prisma/models/HostEventDelivery.d.ts +946 -0
  139. package/dist/generated/prisma/models/HostEventDelivery.js +1 -0
  140. package/dist/generated/prisma/models/HostIdentity.d.ts +1232 -0
  141. package/dist/generated/prisma/models/HostIdentity.js +1 -0
  142. package/dist/generated/prisma/models/HostIdentityLinkRequest.d.ts +1473 -0
  143. package/dist/generated/prisma/models/HostIdentityLinkRequest.js +1 -0
  144. package/dist/generated/prisma/models/Principal.d.ts +455 -0
  145. package/dist/generated/prisma/models/RegistryAllowance.d.ts +1148 -0
  146. package/dist/generated/prisma/models/RegistryAllowance.js +1 -0
  147. package/dist/generated/prisma/models/RegistryApprovedVersion.d.ts +1219 -0
  148. package/dist/generated/prisma/models/RegistryApprovedVersion.js +1 -0
  149. package/dist/generated/prisma/models/RegistryFetch.d.ts +1428 -0
  150. package/dist/generated/prisma/models/RegistryFetch.js +1 -0
  151. package/dist/generated/prisma/models/RegistryPlanRefusal.d.ts +1294 -0
  152. package/dist/generated/prisma/models/RegistryPlanRefusal.js +1 -0
  153. package/dist/generated/prisma/models/RegistryVersionFact.d.ts +1085 -0
  154. package/dist/generated/prisma/models/RegistryVersionFact.js +1 -0
  155. package/dist/generated/prisma/models/ResourceGrant.d.ts +1437 -0
  156. package/dist/generated/prisma/models/ResourceGrant.js +1 -0
  157. package/dist/generated/prisma/models/Run.d.ts +389 -1
  158. package/dist/generated/prisma/models/RunHostCheck.d.ts +1239 -0
  159. package/dist/generated/prisma/models/RunHostCheck.js +1 -0
  160. package/dist/generated/prisma/models/RunHostStatus.d.ts +1315 -0
  161. package/dist/generated/prisma/models/RunHostStatus.js +1 -0
  162. package/dist/generated/prisma/models/Tool.d.ts +15 -3
  163. package/dist/generated/prisma/models.d.ts +13 -0
  164. package/dist/help/build.d.ts +1 -0
  165. package/dist/help/build.js +9 -0
  166. package/dist/help/catalog.d.ts +24 -0
  167. package/dist/help/catalog.js +160 -0
  168. package/dist/help/cli.d.ts +2 -0
  169. package/dist/help/cli.js +65 -0
  170. package/dist/help/runtime.d.ts +3 -0
  171. package/dist/help/runtime.js +44 -0
  172. package/dist/help/search.d.ts +9 -0
  173. package/dist/help/search.js +104 -0
  174. package/dist/help-index.json +660 -0
  175. package/dist/import/cli-args.js +3 -2
  176. package/dist/import/create.d.ts +5 -0
  177. package/dist/import/create.js +67 -13
  178. package/dist/import/index.js +19 -9
  179. package/dist/import/neutral-schema.d.ts +11 -11
  180. package/dist/mcp/auth/access.d.ts +70 -0
  181. package/dist/mcp/auth/access.js +58 -0
  182. package/dist/mcp/auth/grants-cli.d.ts +149 -0
  183. package/dist/mcp/auth/grants-cli.js +518 -0
  184. package/dist/mcp/auth/host-account-cli.d.ts +2 -0
  185. package/dist/mcp/auth/host-account-cli.js +47 -0
  186. package/dist/mcp/auth/ownership.d.ts +26 -52
  187. package/dist/mcp/auth/ownership.js +19 -14
  188. package/dist/mcp/auth/repo-authorization.d.ts +22 -0
  189. package/dist/mcp/auth/repo-authorization.js +51 -0
  190. package/dist/mcp/auth/resource-server.d.ts +31 -2
  191. package/dist/mcp/auth/resource-server.js +84 -3
  192. package/dist/mcp/auth/self-hosted/browser.js +2 -2
  193. package/dist/mcp/auth/self-hosted/cli.js +47 -13
  194. package/dist/mcp/auth/self-hosted/credentials.d.ts +20 -2
  195. package/dist/mcp/auth/self-hosted/credentials.js +62 -4
  196. package/dist/mcp/auth/self-hosted/session.d.ts +2 -1
  197. package/dist/mcp/context.d.ts +27 -1
  198. package/dist/mcp/errors.d.ts +25 -6
  199. package/dist/mcp/errors.js +98 -0
  200. package/dist/mcp/host-events/github-ingress.d.ts +36 -0
  201. package/dist/mcp/host-events/github-ingress.js +92 -0
  202. package/dist/mcp/host-events/github-user-callback.d.ts +18 -0
  203. package/dist/mcp/host-events/github-user-callback.js +50 -0
  204. package/dist/mcp/index.d.ts +8 -1
  205. package/dist/mcp/index.js +106 -16
  206. package/dist/mcp/server.js +27 -9
  207. package/dist/mcp/tools/agents.js +446 -48
  208. package/dist/mcp/tools/budget-groups.js +3 -3
  209. package/dist/mcp/tools/datastore.js +22 -15
  210. package/dist/mcp/tools/grants.d.ts +2 -0
  211. package/dist/mcp/tools/grants.js +239 -0
  212. package/dist/mcp/tools/help.d.ts +5 -0
  213. package/dist/mcp/tools/help.js +67 -0
  214. package/dist/mcp/tools/host-accounts.d.ts +2 -0
  215. package/dist/mcp/tools/host-accounts.js +114 -0
  216. package/dist/mcp/tools/memory.d.ts +7 -1
  217. package/dist/mcp/tools/memory.js +5 -5
  218. package/dist/mcp/tools/repositories.d.ts +2 -0
  219. package/dist/mcp/tools/repositories.js +181 -0
  220. package/dist/mcp/tools/runs.d.ts +6 -0
  221. package/dist/mcp/tools/runs.js +60 -6
  222. package/dist/mcp/tools/scheduling.js +3 -6
  223. package/dist/mcp/tools/secrets.js +18 -6
  224. package/dist/mcp/tools/services.d.ts +2 -0
  225. package/dist/mcp/tools/services.js +222 -0
  226. package/dist/mcp/tools/subagents.js +52 -16
  227. package/dist/mcp/tools/tools.d.ts +41 -0
  228. package/dist/mcp/tools/tools.js +201 -40
  229. package/dist/mcp/tools/trigger.js +36 -6
  230. package/dist/mcp/tools/webhooks.js +15 -4
  231. package/dist/mcp/transport/streamable-http.d.ts +10 -0
  232. package/dist/mcp/transport/streamable-http.js +32 -2
  233. package/dist/providers/auth/delegating.d.ts +19 -0
  234. package/dist/providers/auth/delegating.js +71 -0
  235. package/dist/providers/auth/index.d.ts +2 -0
  236. package/dist/providers/auth/index.js +11 -0
  237. package/dist/providers/auth/self-hosted.d.ts +2 -1
  238. package/dist/providers/auth/self-hosted.js +16 -4
  239. package/dist/providers/auth/types.d.ts +6 -0
  240. package/dist/providers/coding-proxy/memory-ledger.d.ts +3 -0
  241. package/dist/providers/coding-proxy/memory-ledger.js +21 -2
  242. package/dist/providers/coding-proxy/metering.d.ts +4 -0
  243. package/dist/providers/coding-proxy/metering.js +28 -2
  244. package/dist/providers/coding-proxy/prisma-ledger.d.ts +17 -0
  245. package/dist/providers/coding-proxy/prisma-ledger.js +50 -5
  246. package/dist/providers/coding-proxy/proxy.d.ts +13 -0
  247. package/dist/providers/coding-proxy/proxy.js +479 -18
  248. package/dist/providers/coding-proxy/registry/audit.d.ts +131 -0
  249. package/dist/providers/coding-proxy/registry/audit.js +380 -0
  250. package/dist/providers/coding-proxy/registry/bounded-fetch.d.ts +16 -0
  251. package/dist/providers/coding-proxy/registry/bounded-fetch.js +77 -0
  252. package/dist/providers/coding-proxy/registry/plan.d.ts +58 -0
  253. package/dist/providers/coding-proxy/registry/plan.js +304 -0
  254. package/dist/providers/coding-proxy/registry/prisma-store.d.ts +34 -0
  255. package/dist/providers/coding-proxy/registry/prisma-store.js +151 -0
  256. package/dist/providers/coding-proxy/registry/service.d.ts +213 -0
  257. package/dist/providers/coding-proxy/registry/service.js +1137 -0
  258. package/dist/providers/coding-proxy/registry/store.d.ts +127 -0
  259. package/dist/providers/coding-proxy/registry/store.js +70 -0
  260. package/dist/providers/coding-proxy/runtime.d.ts +17 -0
  261. package/dist/providers/coding-proxy/runtime.js +63 -0
  262. package/dist/providers/coding-proxy/secure-fetch.js +0 -1
  263. package/dist/providers/coding-proxy/server.d.ts +11 -0
  264. package/dist/providers/coding-proxy/server.js +163 -0
  265. package/dist/providers/coding-proxy/types.d.ts +15 -0
  266. package/dist/providers/engine/types.d.ts +10 -0
  267. package/dist/providers/executor/build.d.ts +2 -2
  268. package/dist/providers/executor/composition.d.ts +3 -0
  269. package/dist/providers/executor/composition.js +19 -0
  270. package/dist/providers/executor/container.d.ts +105 -3
  271. package/dist/providers/executor/container.js +296 -20
  272. package/dist/providers/executor/dbos.d.ts +2 -3
  273. package/dist/providers/executor/in-process.d.ts +2 -3
  274. package/dist/providers/executor/routing.d.ts +7 -0
  275. package/dist/providers/executor/routing.js +13 -0
  276. package/dist/providers/executor/types.d.ts +22 -0
  277. package/dist/providers/jobs/claude-tool-setup.d.ts +23 -0
  278. package/dist/providers/jobs/claude-tool-setup.js +50 -0
  279. package/dist/providers/jobs/collect-prune.d.ts +7 -0
  280. package/dist/providers/jobs/collect-prune.js +27 -0
  281. package/dist/providers/jobs/docker-isolation.d.ts +48 -3
  282. package/dist/providers/jobs/docker-isolation.js +213 -22
  283. package/dist/providers/jobs/docker-services.d.ts +35 -0
  284. package/dist/providers/jobs/docker-services.js +191 -0
  285. package/dist/providers/jobs/docker.d.ts +49 -2
  286. package/dist/providers/jobs/docker.js +279 -32
  287. package/dist/providers/jobs/fake-kubernetes-api.d.ts +1 -0
  288. package/dist/providers/jobs/fake-kubernetes-api.js +12 -3
  289. package/dist/providers/jobs/kubernetes-isolation.d.ts +17 -2
  290. package/dist/providers/jobs/kubernetes-isolation.js +238 -57
  291. package/dist/providers/jobs/kubernetes-platform.d.ts +10 -4
  292. package/dist/providers/jobs/kubernetes-platform.js +11 -5
  293. package/dist/providers/jobs/kubernetes-preflight.js +3 -0
  294. package/dist/providers/jobs/kubernetes.d.ts +21 -2
  295. package/dist/providers/jobs/kubernetes.js +112 -21
  296. package/dist/providers/jobs/types.d.ts +18 -0
  297. package/dist/providers/llm/anthropic.js +6 -2
  298. package/dist/providers/llm/bedrock.js +2 -1
  299. package/dist/providers/llm/claude-messages.d.ts +5 -1
  300. package/dist/providers/llm/claude-messages.js +1 -0
  301. package/dist/providers/llm/claude-provider.d.ts +3 -1
  302. package/dist/providers/llm/claude-provider.js +5 -1
  303. package/dist/providers/llm/index.d.ts +1 -1
  304. package/dist/providers/llm/index.js +1 -1
  305. package/dist/providers/llm/pricing-anthropic.d.ts +10 -0
  306. package/dist/providers/llm/pricing-anthropic.js +32 -14
  307. package/dist/providers/llm/pricing-bedrock-claude.d.ts +8 -0
  308. package/dist/providers/llm/pricing-bedrock-claude.js +9 -0
  309. package/dist/providers/llm/routing.d.ts +10 -1
  310. package/dist/providers/llm/routing.js +18 -0
  311. package/dist/providers/llm/types.d.ts +12 -0
  312. package/dist/providers/llm/types.js +8 -1
  313. package/dist/providers/review-host/diff-lines.d.ts +16 -0
  314. package/dist/providers/review-host/diff-lines.js +59 -0
  315. package/dist/providers/review-host/github-events.d.ts +6 -0
  316. package/dist/providers/review-host/github-events.js +180 -0
  317. package/dist/providers/review-host/github-user-auth.d.ts +38 -0
  318. package/dist/providers/review-host/github-user-auth.js +128 -0
  319. package/dist/providers/review-host/github.d.ts +57 -0
  320. package/dist/providers/review-host/github.js +567 -0
  321. package/dist/providers/review-host/index.d.ts +12 -0
  322. package/dist/providers/review-host/index.js +30 -0
  323. package/dist/providers/review-host/review-format.d.ts +19 -0
  324. package/dist/providers/review-host/review-format.js +59 -0
  325. package/dist/providers/review-host/types.d.ts +284 -0
  326. package/dist/providers/review-host/types.js +24 -0
  327. package/dist/providers/vcs/git.d.ts +16 -6
  328. package/dist/providers/vcs/git.js +77 -13
  329. package/dist/providers/vcs/github.d.ts +74 -3
  330. package/dist/providers/vcs/github.js +195 -15
  331. package/dist/providers/vcs/types.d.ts +42 -4
  332. package/dist/quickstart/index.js +13 -2
  333. package/dist/sandbox/fetch-policy.d.ts +20 -2
  334. package/dist/sandbox/fetch-policy.js +64 -3
  335. package/dist/sandbox/host-functions.d.ts +9 -1
  336. package/dist/sandbox/host-functions.js +12 -5
  337. package/dist/serve.js +1 -1
  338. package/dist/wardby-bin.js +6 -0
  339. package/docs/README.md +30 -0
  340. package/docs/architecture-runtime.md +90 -0
  341. package/docs/assets/brand/wardby-icon-512.png +0 -0
  342. package/docs/assets/brand/wardby-icon.svg +16 -0
  343. package/docs/assets/brand/wardby-mascot-profile-512.png +0 -0
  344. package/docs/assets/brand/wardby-mascot.png +0 -0
  345. package/docs/assets/brand/wardby-mascot.svg +5 -0
  346. package/docs/assets/wardby-workflow.svg +106 -0
  347. package/docs/code-review-agents.md +481 -0
  348. package/docs/coding-agent-setup.md +172 -0
  349. package/docs/coding-packages.md +455 -0
  350. package/docs/coding-services.md +300 -0
  351. package/docs/coding-worker-byo-images.md +98 -0
  352. package/docs/coding-worker-isolation.md +1061 -0
  353. package/docs/getting-started-gke.md +615 -0
  354. package/docs/getting-started-identity-provider.md +308 -0
  355. package/docs/getting-started.md +133 -0
  356. package/docs/observability.md +53 -0
  357. package/docs/release-verification.md +66 -0
  358. package/docs/security-deployment.md +657 -0
  359. package/help/code-review-agents.md +31 -0
  360. package/help/coding-packages.md +31 -0
  361. package/help/coding-services.md +71 -0
  362. package/help/creating-agents.md +68 -0
  363. package/help/deploy-gke.md +39 -0
  364. package/help/deployment-targets.md +39 -0
  365. package/help/errors/budget-group-exhausted.md +25 -0
  366. package/help/errors/docker-isolation-unsupported.md +26 -0
  367. package/help/errors/protected-path.md +45 -0
  368. package/help/errors/repo-access.md +27 -0
  369. package/help/errors/service-declaration-invalid.md +27 -0
  370. package/help/errors/service-declaration-unavailable.md +25 -0
  371. package/help/errors/service-launcher-unsupported.md +30 -0
  372. package/help/errors/service-not-allowed.md +27 -0
  373. package/help/errors/service-unknown.md +23 -0
  374. package/help/errors/service-unready.md +49 -0
  375. package/help/getting-started.md +31 -0
  376. package/help/github.md +30 -0
  377. package/help/identity-and-access.md +39 -0
  378. package/help/mcp.md +30 -0
  379. package/help/native-capabilities.md +30 -0
  380. package/help/observability.md +41 -0
  381. package/help/operating-agents.md +30 -0
  382. package/help/security.md +27 -0
  383. package/help/troubleshooting/budgets.md +26 -0
  384. package/help/troubleshooting/coding-workers.md +47 -0
  385. package/help/troubleshooting/repository-access.md +27 -0
  386. package/package.json +12 -3
  387. package/prisma/migrations/20260925010000_coding_collect_exclude/migration.sql +8 -0
  388. package/prisma/migrations/20260925015000_allowed_egress_default/migration.sql +8 -0
  389. package/prisma/migrations/20260925020000_coding_package_registry/migration.sql +55 -0
  390. package/prisma/migrations/20260925030000_tool_name_per_owner/migration.sql +11 -0
  391. package/prisma/migrations/20260926010000_run_trigger_host_event/migration.sql +7 -0
  392. package/prisma/migrations/20260926020000_code_review_hosts/migration.sql +53 -0
  393. package/prisma/migrations/20260926030000_auth_user_roles/migration.sql +9 -0
  394. package/prisma/migrations/20260926040000_agent_effort/migration.sql +6 -0
  395. package/prisma/migrations/20260926050000_registry_lockfile_plan/migration.sql +32 -0
  396. package/prisma/migrations/20260926050000_repo_access_authorization/migration.sql +82 -0
  397. package/prisma/migrations/20260926060000_registry_plan_refusal/migration.sql +19 -0
  398. package/prisma/migrations/20260926100000_registry_plan_refusal_published_at/migration.sql +5 -0
  399. package/prisma/migrations/20260926190000_run_host_status/migration.sql +18 -0
  400. package/prisma/migrations/20260926210000_run_host_status_at_dispatch/migration.sql +5 -0
  401. package/prisma/migrations/20260927010000_resource_grants/migration.sql +72 -0
  402. package/prisma/migrations/20260927020000_proxy_session_budget_exhausted/migration.sql +3 -0
  403. package/prisma/migrations/20260927030000_coding_debug_trace/migration.sql +4 -0
  404. package/prisma/migrations/20260927040000_proxy_session_upstream_failure/migration.sql +3 -0
  405. package/prisma/migrations/20260927050000_coding_run_services/migration.sql +74 -0
  406. package/prisma/migrations/20260928000000_coding_run_tool_image/migration.sql +5 -0
  407. package/prisma/schema.prisma +407 -21
@@ -0,0 +1,181 @@
1
+ /**
2
+ * AgentRepository CRUD — which repositories on which code-review host a
3
+ * native agent may act on, and which host events trigger it. Linking needs
4
+ * more than owning the agent: the owner's own linked GitHub account must have
5
+ * the link's access on the repository (write for a write link, read for a
6
+ * read link), or a wardby admin must approve it explicitly (adminOverride).
7
+ * The authorization is stamped on the link and re-checked whenever the link
8
+ * is used (core/repo-access.ts). A repository is an owner binding (resource-
9
+ * sharing grants spec §3.4.3): link/unlink are the agent owner's alone (or
10
+ * an admin's explicit approval), whatever grants others hold on the agent,
11
+ * and owner-less agents can't be linked.
12
+ * See docs/private/2026-09-26-repo-access-authorization-spec-and-plan.md.
13
+ */
14
+ import { Prisma } from "#prisma";
15
+ import { normalizeGitHubRepository } from "../../coding/protocol.js";
16
+ import { requireAgentAccess, requireBindingOwner } from "../auth/access.js";
17
+ import { authorizeRepositoryForSet } from "../auth/repo-authorization.js";
18
+ import { requireScope } from "../auth/resource-server.js";
19
+ import { McpError } from "../errors.js";
20
+ import { textResult } from "./text-result.js";
21
+ const PROVIDERS = ["github"];
22
+ const TRIGGERS = ["pull_request", "mention"];
23
+ const CHECK_NAME = /^[A-Za-z0-9][A-Za-z0-9 ._/()-]{0,99}$/;
24
+ function normalizeRepository(provider, repository) {
25
+ try {
26
+ if (provider === "github")
27
+ return normalizeGitHubRepository(repository);
28
+ }
29
+ catch {
30
+ // fall through
31
+ }
32
+ throw new McpError(400, `invalid repository "${repository}" for provider ${provider}`);
33
+ }
34
+ async function assertNoConflicts(tx, args) {
35
+ if (args.triggers.length === 0 && args.checkName === null)
36
+ return;
37
+ const others = await tx.agentRepository.findMany({
38
+ where: { provider: args.provider, repository: args.repository, NOT: { agentId: args.agentId } },
39
+ });
40
+ if (args.triggers.includes("mention") && others.some((o) => o.triggers.includes("mention"))) {
41
+ throw new McpError(409, `Another agent already handles @-mentions on ${args.repository}.`);
42
+ }
43
+ // Against every other link, whatever its triggers: a check name identifies
44
+ // one agent's verdict on the repository (also a unique index).
45
+ if (args.checkName !== null && others.some((o) => o.checkName === args.checkName)) {
46
+ throw checkNameTaken(args.checkName, args.repository);
47
+ }
48
+ }
49
+ function checkNameTaken(checkName, repository) {
50
+ return new McpError(409, `Another agent already uses check name "${checkName}" on ${repository}.`);
51
+ }
52
+ export function registerRepositoryTools(mcp) {
53
+ mcp.registerTool({
54
+ name: "link_repository",
55
+ scope: "agents:write",
56
+ description: "Links a native agent you own to a repository on a code-review host, with its access, event triggers, and checkName. " +
57
+ "Your linked GitHub account (link_host_account) must have write access to the repository for a write link, or read for a " +
58
+ "read link; a wardby admin may instead approve it with adminOverride. checkName is only allowed (and required) with the " +
59
+ "pull_request trigger, and is unique per repository. Re-linking an already-linked repository replaces its access, " +
60
+ "triggers, and checkName (an omitted field is cleared, not kept) and re-checks access — always send the full desired state.",
61
+ inputSchema: {
62
+ type: "object",
63
+ additionalProperties: false,
64
+ properties: {
65
+ agentId: { type: "string" },
66
+ provider: { type: "string", enum: [...PROVIDERS] },
67
+ repository: { type: "string", minLength: 1, maxLength: 300 },
68
+ access: { type: "string", enum: ["read", "write"] },
69
+ triggers: { type: "array", items: { type: "string", enum: [...TRIGGERS] }, uniqueItems: true },
70
+ checkName: { type: "string", minLength: 1, maxLength: 100 },
71
+ adminOverride: {
72
+ type: "boolean",
73
+ description: "Admins only (agents:admin with the admin role): approve this link without checking GitHub access, recorded as an admin approval. Allowed on any agent that has an owner, not only the admin's own.",
74
+ },
75
+ },
76
+ required: ["agentId", "repository", "access"],
77
+ },
78
+ handler: async (args, ctx) => {
79
+ // An admin approving a repository explicitly may do it on any agent
80
+ // (the role already allows reassigning any agent with make_owner);
81
+ // everyone else, admins included, links only agents they own.
82
+ let agent;
83
+ if (args.adminOverride === true) {
84
+ requireScope(ctx, ctx.canonicalUri, "agents:admin");
85
+ agent = await ctx.db.agent.findUnique({ where: { id: args.agentId } });
86
+ if (!agent)
87
+ throw new McpError(404, `Agent "${args.agentId}" not found.`);
88
+ }
89
+ else {
90
+ agent = (await requireAgentAccess(ctx, args.agentId, "read")).agent;
91
+ requireBindingOwner(ctx, agent);
92
+ }
93
+ if (agent.kind !== "native") {
94
+ throw new McpError(400, "Repository links are for native agents only; coding agents use codingProfile.repository.");
95
+ }
96
+ const provider = args.provider ?? "github";
97
+ const repository = normalizeRepository(provider, args.repository);
98
+ const triggers = [...new Set(args.triggers ?? [])];
99
+ if (triggers.length > 0 && args.access !== "write")
100
+ throw new McpError(400, "Event triggers need write access.");
101
+ const checkName = args.checkName ?? null;
102
+ if (triggers.includes("pull_request") && !checkName) {
103
+ throw new McpError(400, "checkName is required with the pull_request trigger.");
104
+ }
105
+ if (checkName !== null && !triggers.includes("pull_request")) {
106
+ throw new McpError(400, "checkName needs the pull_request trigger (a check is only published for a dispatched PR).");
107
+ }
108
+ if (checkName !== null && !CHECK_NAME.test(checkName))
109
+ throw new McpError(400, "invalid checkName");
110
+ // The host call happens before, and outside, the serializable transaction.
111
+ const authorization = await authorizeRepositoryForSet(ctx, {
112
+ ownerId: agent.ownerId,
113
+ provider,
114
+ repository,
115
+ kind: args.access,
116
+ adminOverride: args.adminOverride === true,
117
+ });
118
+ let link;
119
+ try {
120
+ link = await ctx.db.$transaction(async (tx) => {
121
+ await assertNoConflicts(tx, { agentId: args.agentId, provider, repository, triggers, checkName });
122
+ const fields = { access: args.access, triggers, checkName, ...authorization };
123
+ return tx.agentRepository.upsert({
124
+ where: { agentId_provider_repository: { agentId: args.agentId, provider, repository } },
125
+ create: { agentId: args.agentId, provider, repository, ...fields },
126
+ update: fields,
127
+ });
128
+ }, { isolationLevel: "Serializable" });
129
+ }
130
+ catch (err) {
131
+ if (checkName !== null && err instanceof Prisma.PrismaClientKnownRequestError && err.code === "P2002") {
132
+ throw checkNameTaken(checkName, repository);
133
+ }
134
+ throw err;
135
+ }
136
+ return textResult({ linked: true, link });
137
+ },
138
+ });
139
+ mcp.registerTool({
140
+ name: "unlink_repository",
141
+ scope: "agents:write",
142
+ inputSchema: {
143
+ type: "object",
144
+ additionalProperties: false,
145
+ properties: {
146
+ agentId: { type: "string" },
147
+ provider: { type: "string", enum: [...PROVIDERS] },
148
+ repository: { type: "string", minLength: 1, maxLength: 300 },
149
+ },
150
+ required: ["agentId", "repository"],
151
+ },
152
+ handler: async (args, ctx) => {
153
+ const { agent } = await requireAgentAccess(ctx, args.agentId, "read");
154
+ requireBindingOwner(ctx, agent);
155
+ const provider = args.provider ?? "github";
156
+ const repository = normalizeRepository(provider, args.repository);
157
+ const { count } = await ctx.db.agentRepository.deleteMany({
158
+ where: { agentId: args.agentId, provider, repository },
159
+ });
160
+ return textResult({ unlinked: count > 0 });
161
+ },
162
+ });
163
+ mcp.registerTool({
164
+ name: "list_repositories",
165
+ scope: "agents:read",
166
+ inputSchema: {
167
+ type: "object",
168
+ additionalProperties: false,
169
+ properties: { agentId: { type: "string" } },
170
+ required: ["agentId"],
171
+ },
172
+ handler: async (args, ctx) => {
173
+ await requireAgentAccess(ctx, args.agentId, "read");
174
+ const links = await ctx.db.agentRepository.findMany({
175
+ where: { agentId: args.agentId },
176
+ orderBy: { createdAt: "asc" },
177
+ });
178
+ return textResult({ repositories: links });
179
+ },
180
+ });
181
+ }
@@ -1,2 +1,8 @@
1
1
  import type { WardbyMcpServer } from "../server.js";
2
+ /**
3
+ * Run visibility (resource-sharing grants spec §3.5, A6): a run's output is
4
+ * the triggerer's and the agent owner's, never every reader's. The owner
5
+ * (and the stdio operator) sees every run; anyone else only runs with
6
+ * Run.triggeredById equal to themselves.
7
+ */
2
8
  export declare function registerRunTools(mcp: WardbyMcpServer): void;
@@ -1,7 +1,15 @@
1
1
  import { McpError } from "../errors.js";
2
- import { requireReadableAgent } from "../auth/ownership.js";
2
+ import { agentAccess, requireAgentAccess } from "../auth/access.js";
3
3
  import { publicCodingRunResult } from "../../coding/protocol.js";
4
+ import { countPlanRefusals, summarizeRegistryFetches } from "../../coding/registry/report.js";
5
+ import { storedServiceLabels } from "../../coding/services/catalog.js";
4
6
  import { textResult } from "./text-result.js";
7
+ /**
8
+ * Run visibility (resource-sharing grants spec §3.5, A6): a run's output is
9
+ * the triggerer's and the agent owner's, never every reader's. The owner
10
+ * (and the stdio operator) sees every run; anyone else only runs with
11
+ * Run.triggeredById equal to themselves.
12
+ */
5
13
  export function registerRunTools(mcp) {
6
14
  mcp.registerTool({
7
15
  name: "list_runs",
@@ -12,9 +20,13 @@ export function registerRunTools(mcp) {
12
20
  required: ["agentId"],
13
21
  },
14
22
  handler: async (args, ctx) => {
15
- await requireReadableAgent(ctx.db, args.agentId, ctx.principal.id);
23
+ const { access } = await requireAgentAccess(ctx, args.agentId, "read");
16
24
  const runs = await ctx.db.run.findMany({
17
- where: { agentId: args.agentId, ...(args.status ? { status: args.status } : {}) },
25
+ where: {
26
+ agentId: args.agentId,
27
+ ...(args.status ? { status: args.status } : {}),
28
+ ...(access === "owner" ? {} : { triggeredById: ctx.principal.id }),
29
+ },
18
30
  take: args.limit,
19
31
  orderBy: { startedAt: "desc" },
20
32
  });
@@ -40,21 +52,63 @@ export function registerRunTools(mcp) {
40
52
  inputSchema: { type: "object", properties: { runId: { type: "string" } }, required: ["runId"] },
41
53
  handler: async (args, ctx) => {
42
54
  const run = await ctx.db.run.findUnique({ where: { id: args.runId } });
55
+ const notFound = new McpError(404, `Run "${args.runId}" not found.`);
43
56
  if (!run)
44
- throw new McpError(404, `Run "${args.runId}" not found.`);
45
- await requireReadableAgent(ctx.db, run.agentId, ctx.principal.id);
57
+ throw notFound;
58
+ // Its triggerer keeps seeing it even after losing access to the agent;
59
+ // otherwise only the agent's owner. read on the agent is not enough.
60
+ if (run.triggeredById !== ctx.principal.id) {
61
+ const agent = await ctx.db.agent.findUnique({ where: { id: run.agentId } });
62
+ if (!agent || (await agentAccess(ctx, agent)) !== "owner")
63
+ throw notFound;
64
+ }
46
65
  const codingRun = await ctx.db.codingRun.findUnique({
47
66
  where: { runId: run.id },
48
- select: { result: true, queuedAt: true },
67
+ select: {
68
+ result: true,
69
+ queuedAt: true,
70
+ failureCategory: true,
71
+ diagnosticId: true,
72
+ debugTrace: true,
73
+ services: true,
74
+ },
49
75
  });
50
76
  const codingResult = publicCodingRunResult(codingRun?.result);
51
77
  // A pending coding run with queuedAt is waiting for a concurrency slot
52
78
  // (CODING_MAX_CONCURRENT), not stuck.
53
79
  const codingQueuedAt = run.status === "pending" && codingRun?.queuedAt ? codingRun.queuedAt.toISOString() : undefined;
80
+ // Packages served/refused only exist for coding runs; a non-coding run
81
+ // has no RegistryFetch rows at all, so skip the query entirely.
82
+ const registryFetches = codingRun
83
+ ? await ctx.db.registryFetch.findMany({ where: { runId: run.id }, orderBy: { createdAt: "asc" } })
84
+ : [];
85
+ const { packages, packageRefusals } = summarizeRegistryFetches(registryFetches);
86
+ // Lockfile verification (POST /registry/<ecosystem>/-/plan): exact
87
+ // versions approved for the run, and distinct entries it refused.
88
+ const packagePlan = codingRun
89
+ ? {
90
+ approved: await ctx.db.registryApprovedVersion.count({ where: { runId: run.id } }),
91
+ refused: countPlanRefusals(registryFetches),
92
+ }
93
+ : undefined;
94
+ // A failed coding run's error is only an opaque id; these two say which
95
+ // stage failed and are the key an operator searches the control-plane
96
+ // log for (the real reason is logged there, never persisted). Both are
97
+ // designed to be safe to show the agent's owner (schema.prisma).
98
+ const failureCategory = codingRun?.failureCategory ?? undefined;
99
+ const diagnosticId = codingRun?.diagnosticId ?? undefined;
100
+ // Which services the run started with ("postgres 16"); never their images or environments.
101
+ const services = codingRun ? storedServiceLabels(codingRun.services) : [];
54
102
  return textResult({
55
103
  ...run,
56
104
  ...(codingResult ? { codingResult } : {}),
105
+ ...(failureCategory ? { failureCategory } : {}),
106
+ ...(diagnosticId ? { diagnosticId } : {}),
57
107
  ...(codingQueuedAt ? { codingQueuedAt } : {}),
108
+ // An admin turned on the debug trace: the worker's full trace is in its pod log.
109
+ ...(codingRun?.debugTrace ? { debugTrace: true } : {}),
110
+ ...(services.length > 0 ? { services } : {}),
111
+ ...(codingRun ? { packages, packageRefusals, packagePlan } : {}),
58
112
  });
59
113
  },
60
114
  });
@@ -1,7 +1,7 @@
1
1
  import { Prisma } from "#prisma";
2
2
  import { validateCronExpression } from "../../core/cron.js";
3
3
  import { McpError } from "../errors.js";
4
- import { assertCanMutate, requireOwnedAgent } from "../auth/ownership.js";
4
+ import { assertAgentAccess, requireAgentAccess } from "../auth/access.js";
5
5
  import { textResult } from "./text-result.js";
6
6
  export function registerSchedulingTools(mcp) {
7
7
  mcp.registerTool({
@@ -20,10 +20,7 @@ export function registerSchedulingTools(mcp) {
20
20
  throw new McpError(400, `Invalid schedule/timezone: ${err instanceof Error ? err.message : String(err)}`);
21
21
  }
22
22
  const agent = await ctx.db.$transaction(async (tx) => {
23
- const existing = await tx.agent.findUnique({ where: { id: args.agentId }, include: { codingProfile: true } });
24
- if (!existing)
25
- throw new McpError(404, `Agent "${args.agentId}" not found.`);
26
- assertCanMutate(existing.ownerId, ctx.principal.id, `Agent "${args.agentId}" is not owned by the caller.`);
23
+ const { agent: existing } = await assertAgentAccess(ctx, await tx.agent.findUnique({ where: { id: args.agentId }, include: { codingProfile: true } }), args.agentId, "write", tx);
27
24
  if (existing.kind === "coding" && !existing.codingProfile?.defaultTask) {
28
25
  throw new McpError(400, "A default task is required before enabling a coding-agent schedule.");
29
26
  }
@@ -40,7 +37,7 @@ export function registerSchedulingTools(mcp) {
40
37
  scope: "agents:write",
41
38
  inputSchema: { type: "object", properties: { agentId: { type: "string" } }, required: ["agentId"] },
42
39
  handler: async (args, ctx) => {
43
- await requireOwnedAgent(ctx.db, args.agentId, ctx.principal.id);
40
+ await requireAgentAccess(ctx, args.agentId, "write");
44
41
  const agent = await ctx.db.agent.update({ where: { id: args.agentId }, data: { scheduleEnabled: false } });
45
42
  return textResult(agent);
46
43
  },
@@ -6,7 +6,8 @@
6
6
  import { inputRequired, inputResponse } from "@modelcontextprotocol/server";
7
7
  import { createSecret, listSecrets, attachSecret, detachSecret, deleteSecret } from "../../core/secrets.js";
8
8
  import { McpError } from "../errors.js";
9
- import { requireOwnedAgent, requireOwnedSecret } from "../auth/ownership.js";
9
+ import { requireOwnedSecret } from "../auth/ownership.js";
10
+ import { requireAgentAccess, requireBindingOwner } from "../auth/access.js";
10
11
  import { getSecretElicitationOutcome } from "./secret-elicitation.js";
11
12
  import { textResult } from "./text-result.js";
12
13
  export function registerSecretsTools(mcp, opts) {
@@ -87,9 +88,15 @@ export function registerSecretsTools(mcp, opts) {
87
88
  required: ["agentId", "name"],
88
89
  },
89
90
  handler: async (args, ctx) => {
90
- await requireOwnedAgent(ctx.db, args.agentId, ctx.principal.id);
91
+ // Strictly the agent owner's own secret on the owner's own agent
92
+ // (A2/S2-2): a binding hands the value to every tool the owner lets
93
+ // read it. Not even the stdio operator crosses owners.
94
+ const { agent } = await requireAgentAccess(ctx, args.agentId, "read");
95
+ requireBindingOwner(ctx, agent);
96
+ const ownerId = agent.ownerId;
91
97
  try {
92
- await attachSecret(args.agentId, args.name, ctx.principal.id, ctx.db, args.alias ?? args.name);
98
+ // Resolved among the agent owner's secrets (== the caller here).
99
+ await attachSecret(args.agentId, args.name, ownerId, ctx.db, args.alias ?? args.name);
93
100
  }
94
101
  catch (err) {
95
102
  throw new McpError(404, err instanceof Error ? err.message : String(err));
@@ -100,15 +107,20 @@ export function registerSecretsTools(mcp, opts) {
100
107
  mcp.registerTool({
101
108
  name: "detach_secret",
102
109
  scope: "secrets:write",
110
+ description: "Detaches a secret from an agent. `name` is the alias it was attached under; the secret's own name also works when no attachment has that alias.",
103
111
  inputSchema: {
104
112
  type: "object",
105
113
  properties: { agentId: { type: "string" }, name: { type: "string" } },
106
114
  required: ["agentId", "name"],
107
115
  },
108
116
  handler: async (args, ctx) => {
109
- await requireOwnedAgent(ctx.db, args.agentId, ctx.principal.id);
110
- await detachSecret(args.agentId, args.name, ctx.db);
111
- return textResult({ detached: true });
117
+ const { agent } = await requireAgentAccess(ctx, args.agentId, "read");
118
+ requireBindingOwner(ctx, agent);
119
+ const count = await detachSecret(args.agentId, args.name, ctx.db);
120
+ if (count === 0) {
121
+ throw new McpError(404, `No secret is attached to agent "${args.agentId}" as "${args.name}".`);
122
+ }
123
+ return textResult({ detached: true, count });
112
124
  },
113
125
  });
114
126
  mcp.registerTool({
@@ -0,0 +1,2 @@
1
+ import type { WardbyMcpServer } from "../server.js";
2
+ export declare function registerServiceTools(mcp: WardbyMcpServer): void;
@@ -0,0 +1,222 @@
1
+ /**
2
+ * The coding-run service catalog over MCP (docs/coding-services.md). Reading
3
+ * it needs only agents:read: agent owners need to see which services exist and
4
+ * the variables (testEnv) their tests will get before allowing one, and entries
5
+ * hold no secrets (serviceEnv values are throwaway per-run test credentials).
6
+ * Changing it needs services:manage, a privileged scope honoured only with a
7
+ * role granting it (service-manager or admin; resource-server.ts): an entry
8
+ * decides which image runs next to every coding run that names it, and with
9
+ * what. Each change is audit-logged (event coding.service_catalog.<action>).
10
+ * Built-in entries ship with wardby and change only with a release.
11
+ */
12
+ import { z } from "zod";
13
+ import { CodingServiceDefinitionSchema, catalogEntryFromRow, codingServiceNameSchema, codingServiceVersionSchema, } from "../../coding/services/catalog.js";
14
+ import { logger } from "../../core/logger.js";
15
+ import { McpError } from "../errors.js";
16
+ import { textResult } from "./text-result.js";
17
+ const servicesLog = logger.child({ module: "mcp-services" });
18
+ const KeySchema = z.object({ name: codingServiceNameSchema, version: codingServiceVersionSchema }).strict();
19
+ /** Everything but the key is optional here; the merged entry is validated in full. */
20
+ const UpdateSchema = z
21
+ .object({
22
+ name: codingServiceNameSchema,
23
+ version: codingServiceVersionSchema,
24
+ image: z.unknown().optional(),
25
+ port: z.unknown().optional(),
26
+ serviceEnv: z.unknown().optional(),
27
+ testEnv: z.unknown().optional(),
28
+ readiness: z.unknown().optional(),
29
+ resources: z.unknown().optional(),
30
+ dataPath: z.unknown().optional(),
31
+ writablePaths: z.unknown().optional(),
32
+ })
33
+ .strict();
34
+ function parse(schema, label, value) {
35
+ const result = schema.safeParse(value);
36
+ if (!result.success) {
37
+ const details = result.error.issues
38
+ .map((issue) => `${issue.path.join(".") || "input"}: ${issue.message}`)
39
+ .join("; ");
40
+ throw new McpError(400, `Invalid ${label}: ${details}`);
41
+ }
42
+ return result.data;
43
+ }
44
+ function audit(ctx, action, entry) {
45
+ servicesLog.info({
46
+ event: `coding.service_catalog.${action}`,
47
+ name: entry.name,
48
+ version: entry.version,
49
+ ...(entry.image ? { image: entry.image } : {}),
50
+ by: ctx.principal.id,
51
+ }, "coding service catalog changed");
52
+ }
53
+ async function findEntry(ctx, key) {
54
+ const row = await ctx.db.codingService.findUnique({ where: { name_version: key } });
55
+ if (!row)
56
+ throw new McpError(404, `No coding service "${key.name} ${key.version}" in the catalog.`);
57
+ return row;
58
+ }
59
+ function refuseBuiltin(key) {
60
+ return new McpError(403, `"${key.name} ${key.version}" is built in: built-in services change only with a wardby release. Create an entry under another name or version instead.`);
61
+ }
62
+ function view(row) {
63
+ return { ...catalogEntryFromRow(row), builtin: row.builtin, createdAt: row.createdAt, updatedAt: row.updatedAt };
64
+ }
65
+ const keyJsonSchema = {
66
+ name: { type: "string", description: 'Catalog name a repository declares, e.g. "postgres".' },
67
+ version: { type: "string", description: 'Version a repository declares, e.g. "16".' },
68
+ };
69
+ const definitionProperties = {
70
+ ...keyJsonSchema,
71
+ image: { type: "string", description: "Registry digest to run: repo@sha256:<64 hex>. Tags are refused." },
72
+ port: {
73
+ type: "integer",
74
+ minimum: 1,
75
+ maximum: 65535,
76
+ description: "The port the service listens on (informational).",
77
+ },
78
+ serviceEnv: {
79
+ type: "object",
80
+ additionalProperties: { type: "string" },
81
+ description: "Environment for the service container. Throwaway per-run values only, never secrets.",
82
+ },
83
+ testEnv: {
84
+ type: "object",
85
+ additionalProperties: { type: "string" },
86
+ description: "Variables the agent's shells receive (e.g. DATABASE_URL). Upper-case names; wardby's own names (PATH, HOME, PIP_*, WARDBY_*, ...) are refused.",
87
+ },
88
+ readiness: {
89
+ type: "object",
90
+ additionalProperties: false,
91
+ required: ["command", "periodSeconds", "timeoutSeconds", "failureThreshold"],
92
+ properties: {
93
+ command: { type: "array", minItems: 1, maxItems: 16, items: { type: "string" } },
94
+ periodSeconds: { type: "integer", minimum: 1, maximum: 60 },
95
+ timeoutSeconds: { type: "integer", minimum: 1, maximum: 60 },
96
+ failureThreshold: { type: "integer", minimum: 1, maximum: 120 },
97
+ },
98
+ description: "Startup probe: a command in the service's image that succeeds once it accepts connections on 127.0.0.1.",
99
+ },
100
+ resources: {
101
+ type: "object",
102
+ additionalProperties: false,
103
+ required: ["cpuMillicores", "memoryMib", "diskMib"],
104
+ properties: {
105
+ cpuMillicores: { type: "integer", minimum: 100, maximum: 4000 },
106
+ memoryMib: { type: "integer", minimum: 64, maximum: 16384 },
107
+ diskMib: { type: "integer", minimum: 64, maximum: 8192 },
108
+ },
109
+ description: "Requests equal limits. diskMib sizes the data volume.",
110
+ },
111
+ dataPath: {
112
+ type: "string",
113
+ description: "Absolute directory the service writes its data to: an empty volume in each run.",
114
+ },
115
+ writablePaths: {
116
+ type: "array",
117
+ maxItems: 4,
118
+ items: { type: "string" },
119
+ description: "Further directories the image writes to (a socket directory, /tmp); its root filesystem is read-only.",
120
+ },
121
+ };
122
+ export function registerServiceTools(mcp) {
123
+ mcp.registerTool({
124
+ name: "list_services",
125
+ scope: "agents:read",
126
+ description: "The coding-run service catalog (name, version, image, built in or not): the names a coding agent's codingProfile.services can allow.",
127
+ inputSchema: { type: "object", additionalProperties: false, properties: {} },
128
+ handler: async (_args, ctx) => {
129
+ const rows = await ctx.db.codingService.findMany({ orderBy: [{ name: "asc" }, { version: "asc" }] });
130
+ return textResult(rows.map((row) => ({
131
+ name: row.name,
132
+ version: row.version,
133
+ kind: row.kind,
134
+ image: row.image,
135
+ port: row.port,
136
+ builtin: row.builtin,
137
+ })));
138
+ },
139
+ });
140
+ mcp.registerTool({
141
+ name: "get_service",
142
+ scope: "agents:read",
143
+ description: "One catalog entry in full, including the variables (testEnv) a run's shells receive — the contract a repository's tests rely on.",
144
+ inputSchema: {
145
+ type: "object",
146
+ additionalProperties: false,
147
+ required: ["name", "version"],
148
+ properties: keyJsonSchema,
149
+ },
150
+ handler: async (rawArgs, ctx) => {
151
+ const key = parse(KeySchema, "get_service arguments", rawArgs);
152
+ return textResult(view(await findEntry(ctx, key)));
153
+ },
154
+ });
155
+ mcp.registerTool({
156
+ name: "create_service",
157
+ scope: "services:manage",
158
+ description: "Service managers (services:manage): add a catalog entry repositories can then declare in .wardby/services.yaml. The image must be pinned by digest; kind is sidecar (a fresh instance in each run's pod).",
159
+ inputSchema: {
160
+ type: "object",
161
+ additionalProperties: false,
162
+ required: ["name", "version", "image", "port", "readiness", "resources", "dataPath"],
163
+ // kind is checked by the zod schema (only "sidecar" today), so a refusal names the field.
164
+ properties: { ...definitionProperties, kind: { type: "string", description: 'Only "sidecar" today.' } },
165
+ },
166
+ handler: async (rawArgs, ctx) => {
167
+ const definition = parse(CodingServiceDefinitionSchema, "create_service arguments", rawArgs);
168
+ const key = { name: definition.name, version: definition.version };
169
+ if (await ctx.db.codingService.findUnique({ where: { name_version: key } })) {
170
+ throw new McpError(409, `The catalog already has "${key.name} ${key.version}"; use update_service to change it.`);
171
+ }
172
+ const row = await ctx.db.codingService.create({
173
+ data: { ...definition, builtin: false, createdById: ctx.principal.id },
174
+ });
175
+ audit(ctx, "create", definition);
176
+ return textResult(view(row));
177
+ },
178
+ });
179
+ mcp.registerTool({
180
+ name: "update_service",
181
+ scope: "services:manage",
182
+ description: "Service managers (services:manage): change a non-built-in catalog entry (name and version identify it and never change). Runs already dispatched keep the entry they started with.",
183
+ inputSchema: {
184
+ type: "object",
185
+ additionalProperties: false,
186
+ required: ["name", "version"],
187
+ properties: definitionProperties,
188
+ },
189
+ handler: async (rawArgs, ctx) => {
190
+ const { name, version, ...patch } = parse(UpdateSchema, "update_service arguments", rawArgs);
191
+ const row = await findEntry(ctx, { name, version });
192
+ if (row.builtin)
193
+ throw refuseBuiltin({ name, version });
194
+ const changes = Object.fromEntries(Object.entries(patch).filter(([, value]) => value !== undefined));
195
+ const next = parse(CodingServiceDefinitionSchema, "coding service", { ...catalogEntryFromRow(row), ...changes });
196
+ const { name: _name, version: _version, kind: _kind, ...fields } = next;
197
+ const updated = await ctx.db.codingService.update({ where: { name_version: { name, version } }, data: fields });
198
+ audit(ctx, "update", next);
199
+ return textResult(view(updated));
200
+ },
201
+ });
202
+ mcp.registerTool({
203
+ name: "delete_service",
204
+ scope: "services:manage",
205
+ description: "Service managers (services:manage): remove a non-built-in catalog entry. Repositories still declaring it are refused at their next run (service_unknown).",
206
+ inputSchema: {
207
+ type: "object",
208
+ additionalProperties: false,
209
+ required: ["name", "version"],
210
+ properties: keyJsonSchema,
211
+ },
212
+ handler: async (rawArgs, ctx) => {
213
+ const key = parse(KeySchema, "delete_service arguments", rawArgs);
214
+ const row = await findEntry(ctx, key);
215
+ if (row.builtin)
216
+ throw refuseBuiltin(key);
217
+ await ctx.db.codingService.delete({ where: { name_version: key } });
218
+ audit(ctx, "delete", key);
219
+ return textResult({ deleted: key });
220
+ },
221
+ });
222
+ }