@wardby/cli 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (407) hide show
  1. package/.env.example +34 -4
  2. package/README.md +41 -4
  3. package/dist/claude-coding-worker/driver.d.ts +6 -1
  4. package/dist/claude-coding-worker/driver.js +27 -1
  5. package/dist/claude-coding-worker/main.js +2 -0
  6. package/dist/claude-coding-worker/tool-socket.d.ts +9 -0
  7. package/dist/claude-coding-worker/tool-socket.js +26 -0
  8. package/dist/cli-help.d.ts +1 -1
  9. package/dist/cli-help.js +13 -4
  10. package/dist/cli.d.ts +1 -1
  11. package/dist/cli.js +219 -29
  12. package/dist/coding/collect-exclude.d.ts +25 -0
  13. package/dist/coding/collect-exclude.js +76 -0
  14. package/dist/coding/profile.d.ts +67 -22
  15. package/dist/coding/profile.js +58 -25
  16. package/dist/coding/protected-path-wording.d.ts +24 -0
  17. package/dist/coding/protected-path-wording.js +55 -0
  18. package/dist/coding/protected-paths.d.ts +31 -0
  19. package/dist/coding/protected-paths.js +48 -0
  20. package/dist/coding/protocol.d.ts +75 -4
  21. package/dist/coding/protocol.js +107 -7
  22. package/dist/coding/registry/adapters.d.ts +2 -0
  23. package/dist/coding/registry/adapters.js +9 -0
  24. package/dist/coding/registry/allowlist.d.ts +9 -0
  25. package/dist/coding/registry/allowlist.js +28 -0
  26. package/dist/coding/registry/json-scan.d.ts +55 -0
  27. package/dist/coding/registry/json-scan.js +191 -0
  28. package/dist/coding/registry/lockfiles.d.ts +6 -0
  29. package/dist/coding/registry/lockfiles.js +89 -0
  30. package/dist/coding/registry/npm-lockfile.d.ts +9 -0
  31. package/dist/coding/registry/npm-lockfile.js +73 -0
  32. package/dist/coding/registry/npm-plan.d.ts +29 -0
  33. package/dist/coding/registry/npm-plan.js +289 -0
  34. package/dist/coding/registry/npm.d.ts +7 -0
  35. package/dist/coding/registry/npm.js +252 -0
  36. package/dist/coding/registry/pypi.d.ts +4 -0
  37. package/dist/coding/registry/pypi.js +210 -0
  38. package/dist/coding/registry/report.d.ts +37 -0
  39. package/dist/coding/registry/report.js +31 -0
  40. package/dist/coding/registry/token.d.ts +4 -0
  41. package/dist/coding/registry/token.js +7 -0
  42. package/dist/coding/registry/types.d.ts +286 -0
  43. package/dist/coding/registry/types.js +19 -0
  44. package/dist/coding/registry/worker-config.d.ts +14 -0
  45. package/dist/coding/registry/worker-config.js +31 -0
  46. package/dist/coding/services/builtins.d.ts +22 -0
  47. package/dist/coding/services/builtins.js +85 -0
  48. package/dist/coding/services/catalog.d.ts +443 -0
  49. package/dist/coding/services/catalog.js +159 -0
  50. package/dist/coding/services/declaration.d.ts +11 -0
  51. package/dist/coding/services/declaration.js +114 -0
  52. package/dist/coding/services/note.d.ts +8 -0
  53. package/dist/coding/services/note.js +15 -0
  54. package/dist/coding/services/resolve.d.ts +25 -0
  55. package/dist/coding/services/resolve.js +50 -0
  56. package/dist/coding/services/wording.d.ts +25 -0
  57. package/dist/coding/services/wording.js +70 -0
  58. package/dist/coding-proxy/main.js +24 -4
  59. package/dist/coding-worker/artifact.d.ts +6 -0
  60. package/dist/coding-worker/debug-trace.d.ts +37 -0
  61. package/dist/coding-worker/debug-trace.js +117 -0
  62. package/dist/coding-worker/driver.d.ts +13 -1
  63. package/dist/coding-worker/driver.js +102 -13
  64. package/dist/coding-worker/errors.js +8 -0
  65. package/dist/coding-worker/main.js +10 -2
  66. package/dist/coding-worker/sdk.d.ts +2 -2
  67. package/dist/coding-worker/sdk.js +7 -2
  68. package/dist/coding-worker/types.d.ts +3 -0
  69. package/dist/config/providers.d.ts +41 -0
  70. package/dist/config/providers.js +66 -0
  71. package/dist/core/budget-groups.d.ts +108 -5
  72. package/dist/core/budget-groups.js +125 -19
  73. package/dist/core/budget-wording.d.ts +22 -0
  74. package/dist/core/budget-wording.js +55 -0
  75. package/dist/core/datastores.js +18 -2
  76. package/dist/core/db.d.ts +5 -1
  77. package/dist/core/db.js +8 -2
  78. package/dist/core/dispatch.d.ts +33 -6
  79. package/dist/core/dispatch.js +260 -43
  80. package/dist/core/engine-native.js +27 -10
  81. package/dist/core/grants.d.ts +86 -0
  82. package/dist/core/grants.js +126 -0
  83. package/dist/core/host-events.d.ts +49 -0
  84. package/dist/core/host-events.js +257 -0
  85. package/dist/core/host-identity-links.d.ts +54 -0
  86. package/dist/core/host-identity-links.js +189 -0
  87. package/dist/core/host-status.d.ts +61 -0
  88. package/dist/core/host-status.js +211 -0
  89. package/dist/core/in-flight-runs.d.ts +8 -0
  90. package/dist/core/in-flight-runs.js +56 -0
  91. package/dist/core/provider-wording.d.ts +12 -0
  92. package/dist/core/provider-wording.js +44 -0
  93. package/dist/core/reconciler.d.ts +38 -2
  94. package/dist/core/reconciler.js +86 -2
  95. package/dist/core/repo-access.d.ts +99 -0
  96. package/dist/core/repo-access.js +136 -0
  97. package/dist/core/review-host-checks.d.ts +11 -0
  98. package/dist/core/review-host-checks.js +41 -0
  99. package/dist/core/review-host-tools.d.ts +47 -0
  100. package/dist/core/review-host-tools.js +347 -0
  101. package/dist/core/run-heartbeat.d.ts +27 -0
  102. package/dist/core/run-heartbeat.js +54 -0
  103. package/dist/core/runner.d.ts +30 -8
  104. package/dist/core/runner.js +237 -40
  105. package/dist/core/secrets.d.ts +11 -2
  106. package/dist/core/secrets.js +25 -6
  107. package/dist/core/subagent-memory-tools.d.ts +1 -1
  108. package/dist/core/subagent-memory-tools.js +16 -3
  109. package/dist/core/tool-admin.d.ts +81 -0
  110. package/dist/core/tool-admin.js +129 -0
  111. package/dist/core/tool-names.d.ts +42 -0
  112. package/dist/core/tool-names.js +64 -0
  113. package/dist/core/untrusted-content.d.ts +32 -0
  114. package/dist/core/untrusted-content.js +72 -0
  115. package/dist/core/webhooks.d.ts +8 -1
  116. package/dist/core/webhooks.js +23 -2
  117. package/dist/generated/prisma/browser.d.ts +100 -0
  118. package/dist/generated/prisma/client.d.ts +100 -0
  119. package/dist/generated/prisma/commonInputTypes.d.ts +30 -0
  120. package/dist/generated/prisma/enums.d.ts +6 -0
  121. package/dist/generated/prisma/enums.js +6 -1
  122. package/dist/generated/prisma/internal/class.d.ts +143 -0
  123. package/dist/generated/prisma/internal/class.js +4 -4
  124. package/dist/generated/prisma/internal/prismaNamespace.d.ts +1746 -577
  125. package/dist/generated/prisma/internal/prismaNamespace.js +179 -6
  126. package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +186 -0
  127. package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +179 -6
  128. package/dist/generated/prisma/models/Agent.d.ts +294 -1
  129. package/dist/generated/prisma/models/AgentRepository.d.ts +1425 -0
  130. package/dist/generated/prisma/models/AgentRepository.js +1 -0
  131. package/dist/generated/prisma/models/AgentTool.d.ts +95 -1
  132. package/dist/generated/prisma/models/AuthUser.d.ts +56 -1
  133. package/dist/generated/prisma/models/CodingAgentProfile.d.ts +258 -7
  134. package/dist/generated/prisma/models/CodingProxySession.d.ts +123 -2
  135. package/dist/generated/prisma/models/CodingRun.d.ts +1276 -95
  136. package/dist/generated/prisma/models/CodingService.d.ts +1348 -0
  137. package/dist/generated/prisma/models/CodingService.js +1 -0
  138. package/dist/generated/prisma/models/HostEventDelivery.d.ts +946 -0
  139. package/dist/generated/prisma/models/HostEventDelivery.js +1 -0
  140. package/dist/generated/prisma/models/HostIdentity.d.ts +1232 -0
  141. package/dist/generated/prisma/models/HostIdentity.js +1 -0
  142. package/dist/generated/prisma/models/HostIdentityLinkRequest.d.ts +1473 -0
  143. package/dist/generated/prisma/models/HostIdentityLinkRequest.js +1 -0
  144. package/dist/generated/prisma/models/Principal.d.ts +455 -0
  145. package/dist/generated/prisma/models/RegistryAllowance.d.ts +1148 -0
  146. package/dist/generated/prisma/models/RegistryAllowance.js +1 -0
  147. package/dist/generated/prisma/models/RegistryApprovedVersion.d.ts +1219 -0
  148. package/dist/generated/prisma/models/RegistryApprovedVersion.js +1 -0
  149. package/dist/generated/prisma/models/RegistryFetch.d.ts +1428 -0
  150. package/dist/generated/prisma/models/RegistryFetch.js +1 -0
  151. package/dist/generated/prisma/models/RegistryPlanRefusal.d.ts +1294 -0
  152. package/dist/generated/prisma/models/RegistryPlanRefusal.js +1 -0
  153. package/dist/generated/prisma/models/RegistryVersionFact.d.ts +1085 -0
  154. package/dist/generated/prisma/models/RegistryVersionFact.js +1 -0
  155. package/dist/generated/prisma/models/ResourceGrant.d.ts +1437 -0
  156. package/dist/generated/prisma/models/ResourceGrant.js +1 -0
  157. package/dist/generated/prisma/models/Run.d.ts +389 -1
  158. package/dist/generated/prisma/models/RunHostCheck.d.ts +1239 -0
  159. package/dist/generated/prisma/models/RunHostCheck.js +1 -0
  160. package/dist/generated/prisma/models/RunHostStatus.d.ts +1315 -0
  161. package/dist/generated/prisma/models/RunHostStatus.js +1 -0
  162. package/dist/generated/prisma/models/Tool.d.ts +15 -3
  163. package/dist/generated/prisma/models.d.ts +13 -0
  164. package/dist/help/build.d.ts +1 -0
  165. package/dist/help/build.js +9 -0
  166. package/dist/help/catalog.d.ts +24 -0
  167. package/dist/help/catalog.js +160 -0
  168. package/dist/help/cli.d.ts +2 -0
  169. package/dist/help/cli.js +65 -0
  170. package/dist/help/runtime.d.ts +3 -0
  171. package/dist/help/runtime.js +44 -0
  172. package/dist/help/search.d.ts +9 -0
  173. package/dist/help/search.js +104 -0
  174. package/dist/help-index.json +660 -0
  175. package/dist/import/cli-args.js +3 -2
  176. package/dist/import/create.d.ts +5 -0
  177. package/dist/import/create.js +67 -13
  178. package/dist/import/index.js +19 -9
  179. package/dist/import/neutral-schema.d.ts +11 -11
  180. package/dist/mcp/auth/access.d.ts +70 -0
  181. package/dist/mcp/auth/access.js +58 -0
  182. package/dist/mcp/auth/grants-cli.d.ts +149 -0
  183. package/dist/mcp/auth/grants-cli.js +518 -0
  184. package/dist/mcp/auth/host-account-cli.d.ts +2 -0
  185. package/dist/mcp/auth/host-account-cli.js +47 -0
  186. package/dist/mcp/auth/ownership.d.ts +26 -52
  187. package/dist/mcp/auth/ownership.js +19 -14
  188. package/dist/mcp/auth/repo-authorization.d.ts +22 -0
  189. package/dist/mcp/auth/repo-authorization.js +51 -0
  190. package/dist/mcp/auth/resource-server.d.ts +31 -2
  191. package/dist/mcp/auth/resource-server.js +84 -3
  192. package/dist/mcp/auth/self-hosted/browser.js +2 -2
  193. package/dist/mcp/auth/self-hosted/cli.js +47 -13
  194. package/dist/mcp/auth/self-hosted/credentials.d.ts +20 -2
  195. package/dist/mcp/auth/self-hosted/credentials.js +62 -4
  196. package/dist/mcp/auth/self-hosted/session.d.ts +2 -1
  197. package/dist/mcp/context.d.ts +27 -1
  198. package/dist/mcp/errors.d.ts +25 -6
  199. package/dist/mcp/errors.js +98 -0
  200. package/dist/mcp/host-events/github-ingress.d.ts +36 -0
  201. package/dist/mcp/host-events/github-ingress.js +92 -0
  202. package/dist/mcp/host-events/github-user-callback.d.ts +18 -0
  203. package/dist/mcp/host-events/github-user-callback.js +50 -0
  204. package/dist/mcp/index.d.ts +8 -1
  205. package/dist/mcp/index.js +106 -16
  206. package/dist/mcp/server.js +27 -9
  207. package/dist/mcp/tools/agents.js +446 -48
  208. package/dist/mcp/tools/budget-groups.js +3 -3
  209. package/dist/mcp/tools/datastore.js +22 -15
  210. package/dist/mcp/tools/grants.d.ts +2 -0
  211. package/dist/mcp/tools/grants.js +239 -0
  212. package/dist/mcp/tools/help.d.ts +5 -0
  213. package/dist/mcp/tools/help.js +67 -0
  214. package/dist/mcp/tools/host-accounts.d.ts +2 -0
  215. package/dist/mcp/tools/host-accounts.js +114 -0
  216. package/dist/mcp/tools/memory.d.ts +7 -1
  217. package/dist/mcp/tools/memory.js +5 -5
  218. package/dist/mcp/tools/repositories.d.ts +2 -0
  219. package/dist/mcp/tools/repositories.js +181 -0
  220. package/dist/mcp/tools/runs.d.ts +6 -0
  221. package/dist/mcp/tools/runs.js +60 -6
  222. package/dist/mcp/tools/scheduling.js +3 -6
  223. package/dist/mcp/tools/secrets.js +18 -6
  224. package/dist/mcp/tools/services.d.ts +2 -0
  225. package/dist/mcp/tools/services.js +222 -0
  226. package/dist/mcp/tools/subagents.js +52 -16
  227. package/dist/mcp/tools/tools.d.ts +41 -0
  228. package/dist/mcp/tools/tools.js +201 -40
  229. package/dist/mcp/tools/trigger.js +36 -6
  230. package/dist/mcp/tools/webhooks.js +15 -4
  231. package/dist/mcp/transport/streamable-http.d.ts +10 -0
  232. package/dist/mcp/transport/streamable-http.js +32 -2
  233. package/dist/providers/auth/delegating.d.ts +19 -0
  234. package/dist/providers/auth/delegating.js +71 -0
  235. package/dist/providers/auth/index.d.ts +2 -0
  236. package/dist/providers/auth/index.js +11 -0
  237. package/dist/providers/auth/self-hosted.d.ts +2 -1
  238. package/dist/providers/auth/self-hosted.js +16 -4
  239. package/dist/providers/auth/types.d.ts +6 -0
  240. package/dist/providers/coding-proxy/memory-ledger.d.ts +3 -0
  241. package/dist/providers/coding-proxy/memory-ledger.js +21 -2
  242. package/dist/providers/coding-proxy/metering.d.ts +4 -0
  243. package/dist/providers/coding-proxy/metering.js +28 -2
  244. package/dist/providers/coding-proxy/prisma-ledger.d.ts +17 -0
  245. package/dist/providers/coding-proxy/prisma-ledger.js +50 -5
  246. package/dist/providers/coding-proxy/proxy.d.ts +13 -0
  247. package/dist/providers/coding-proxy/proxy.js +479 -18
  248. package/dist/providers/coding-proxy/registry/audit.d.ts +131 -0
  249. package/dist/providers/coding-proxy/registry/audit.js +380 -0
  250. package/dist/providers/coding-proxy/registry/bounded-fetch.d.ts +16 -0
  251. package/dist/providers/coding-proxy/registry/bounded-fetch.js +77 -0
  252. package/dist/providers/coding-proxy/registry/plan.d.ts +58 -0
  253. package/dist/providers/coding-proxy/registry/plan.js +304 -0
  254. package/dist/providers/coding-proxy/registry/prisma-store.d.ts +34 -0
  255. package/dist/providers/coding-proxy/registry/prisma-store.js +151 -0
  256. package/dist/providers/coding-proxy/registry/service.d.ts +213 -0
  257. package/dist/providers/coding-proxy/registry/service.js +1137 -0
  258. package/dist/providers/coding-proxy/registry/store.d.ts +127 -0
  259. package/dist/providers/coding-proxy/registry/store.js +70 -0
  260. package/dist/providers/coding-proxy/runtime.d.ts +17 -0
  261. package/dist/providers/coding-proxy/runtime.js +63 -0
  262. package/dist/providers/coding-proxy/secure-fetch.js +0 -1
  263. package/dist/providers/coding-proxy/server.d.ts +11 -0
  264. package/dist/providers/coding-proxy/server.js +163 -0
  265. package/dist/providers/coding-proxy/types.d.ts +15 -0
  266. package/dist/providers/engine/types.d.ts +10 -0
  267. package/dist/providers/executor/build.d.ts +2 -2
  268. package/dist/providers/executor/composition.d.ts +3 -0
  269. package/dist/providers/executor/composition.js +19 -0
  270. package/dist/providers/executor/container.d.ts +105 -3
  271. package/dist/providers/executor/container.js +296 -20
  272. package/dist/providers/executor/dbos.d.ts +2 -3
  273. package/dist/providers/executor/in-process.d.ts +2 -3
  274. package/dist/providers/executor/routing.d.ts +7 -0
  275. package/dist/providers/executor/routing.js +13 -0
  276. package/dist/providers/executor/types.d.ts +22 -0
  277. package/dist/providers/jobs/claude-tool-setup.d.ts +23 -0
  278. package/dist/providers/jobs/claude-tool-setup.js +50 -0
  279. package/dist/providers/jobs/collect-prune.d.ts +7 -0
  280. package/dist/providers/jobs/collect-prune.js +27 -0
  281. package/dist/providers/jobs/docker-isolation.d.ts +48 -3
  282. package/dist/providers/jobs/docker-isolation.js +213 -22
  283. package/dist/providers/jobs/docker-services.d.ts +35 -0
  284. package/dist/providers/jobs/docker-services.js +191 -0
  285. package/dist/providers/jobs/docker.d.ts +49 -2
  286. package/dist/providers/jobs/docker.js +279 -32
  287. package/dist/providers/jobs/fake-kubernetes-api.d.ts +1 -0
  288. package/dist/providers/jobs/fake-kubernetes-api.js +12 -3
  289. package/dist/providers/jobs/kubernetes-isolation.d.ts +17 -2
  290. package/dist/providers/jobs/kubernetes-isolation.js +238 -57
  291. package/dist/providers/jobs/kubernetes-platform.d.ts +10 -4
  292. package/dist/providers/jobs/kubernetes-platform.js +11 -5
  293. package/dist/providers/jobs/kubernetes-preflight.js +3 -0
  294. package/dist/providers/jobs/kubernetes.d.ts +21 -2
  295. package/dist/providers/jobs/kubernetes.js +112 -21
  296. package/dist/providers/jobs/types.d.ts +18 -0
  297. package/dist/providers/llm/anthropic.js +6 -2
  298. package/dist/providers/llm/bedrock.js +2 -1
  299. package/dist/providers/llm/claude-messages.d.ts +5 -1
  300. package/dist/providers/llm/claude-messages.js +1 -0
  301. package/dist/providers/llm/claude-provider.d.ts +3 -1
  302. package/dist/providers/llm/claude-provider.js +5 -1
  303. package/dist/providers/llm/index.d.ts +1 -1
  304. package/dist/providers/llm/index.js +1 -1
  305. package/dist/providers/llm/pricing-anthropic.d.ts +10 -0
  306. package/dist/providers/llm/pricing-anthropic.js +32 -14
  307. package/dist/providers/llm/pricing-bedrock-claude.d.ts +8 -0
  308. package/dist/providers/llm/pricing-bedrock-claude.js +9 -0
  309. package/dist/providers/llm/routing.d.ts +10 -1
  310. package/dist/providers/llm/routing.js +18 -0
  311. package/dist/providers/llm/types.d.ts +12 -0
  312. package/dist/providers/llm/types.js +8 -1
  313. package/dist/providers/review-host/diff-lines.d.ts +16 -0
  314. package/dist/providers/review-host/diff-lines.js +59 -0
  315. package/dist/providers/review-host/github-events.d.ts +6 -0
  316. package/dist/providers/review-host/github-events.js +180 -0
  317. package/dist/providers/review-host/github-user-auth.d.ts +38 -0
  318. package/dist/providers/review-host/github-user-auth.js +128 -0
  319. package/dist/providers/review-host/github.d.ts +57 -0
  320. package/dist/providers/review-host/github.js +567 -0
  321. package/dist/providers/review-host/index.d.ts +12 -0
  322. package/dist/providers/review-host/index.js +30 -0
  323. package/dist/providers/review-host/review-format.d.ts +19 -0
  324. package/dist/providers/review-host/review-format.js +59 -0
  325. package/dist/providers/review-host/types.d.ts +284 -0
  326. package/dist/providers/review-host/types.js +24 -0
  327. package/dist/providers/vcs/git.d.ts +16 -6
  328. package/dist/providers/vcs/git.js +77 -13
  329. package/dist/providers/vcs/github.d.ts +74 -3
  330. package/dist/providers/vcs/github.js +195 -15
  331. package/dist/providers/vcs/types.d.ts +42 -4
  332. package/dist/quickstart/index.js +13 -2
  333. package/dist/sandbox/fetch-policy.d.ts +20 -2
  334. package/dist/sandbox/fetch-policy.js +64 -3
  335. package/dist/sandbox/host-functions.d.ts +9 -1
  336. package/dist/sandbox/host-functions.js +12 -5
  337. package/dist/serve.js +1 -1
  338. package/dist/wardby-bin.js +6 -0
  339. package/docs/README.md +30 -0
  340. package/docs/architecture-runtime.md +90 -0
  341. package/docs/assets/brand/wardby-icon-512.png +0 -0
  342. package/docs/assets/brand/wardby-icon.svg +16 -0
  343. package/docs/assets/brand/wardby-mascot-profile-512.png +0 -0
  344. package/docs/assets/brand/wardby-mascot.png +0 -0
  345. package/docs/assets/brand/wardby-mascot.svg +5 -0
  346. package/docs/assets/wardby-workflow.svg +106 -0
  347. package/docs/code-review-agents.md +481 -0
  348. package/docs/coding-agent-setup.md +172 -0
  349. package/docs/coding-packages.md +455 -0
  350. package/docs/coding-services.md +300 -0
  351. package/docs/coding-worker-byo-images.md +98 -0
  352. package/docs/coding-worker-isolation.md +1061 -0
  353. package/docs/getting-started-gke.md +615 -0
  354. package/docs/getting-started-identity-provider.md +308 -0
  355. package/docs/getting-started.md +133 -0
  356. package/docs/observability.md +53 -0
  357. package/docs/release-verification.md +66 -0
  358. package/docs/security-deployment.md +657 -0
  359. package/help/code-review-agents.md +31 -0
  360. package/help/coding-packages.md +31 -0
  361. package/help/coding-services.md +71 -0
  362. package/help/creating-agents.md +68 -0
  363. package/help/deploy-gke.md +39 -0
  364. package/help/deployment-targets.md +39 -0
  365. package/help/errors/budget-group-exhausted.md +25 -0
  366. package/help/errors/docker-isolation-unsupported.md +26 -0
  367. package/help/errors/protected-path.md +45 -0
  368. package/help/errors/repo-access.md +27 -0
  369. package/help/errors/service-declaration-invalid.md +27 -0
  370. package/help/errors/service-declaration-unavailable.md +25 -0
  371. package/help/errors/service-launcher-unsupported.md +30 -0
  372. package/help/errors/service-not-allowed.md +27 -0
  373. package/help/errors/service-unknown.md +23 -0
  374. package/help/errors/service-unready.md +49 -0
  375. package/help/getting-started.md +31 -0
  376. package/help/github.md +30 -0
  377. package/help/identity-and-access.md +39 -0
  378. package/help/mcp.md +30 -0
  379. package/help/native-capabilities.md +30 -0
  380. package/help/observability.md +41 -0
  381. package/help/operating-agents.md +30 -0
  382. package/help/security.md +27 -0
  383. package/help/troubleshooting/budgets.md +26 -0
  384. package/help/troubleshooting/coding-workers.md +47 -0
  385. package/help/troubleshooting/repository-access.md +27 -0
  386. package/package.json +12 -3
  387. package/prisma/migrations/20260925010000_coding_collect_exclude/migration.sql +8 -0
  388. package/prisma/migrations/20260925015000_allowed_egress_default/migration.sql +8 -0
  389. package/prisma/migrations/20260925020000_coding_package_registry/migration.sql +55 -0
  390. package/prisma/migrations/20260925030000_tool_name_per_owner/migration.sql +11 -0
  391. package/prisma/migrations/20260926010000_run_trigger_host_event/migration.sql +7 -0
  392. package/prisma/migrations/20260926020000_code_review_hosts/migration.sql +53 -0
  393. package/prisma/migrations/20260926030000_auth_user_roles/migration.sql +9 -0
  394. package/prisma/migrations/20260926040000_agent_effort/migration.sql +6 -0
  395. package/prisma/migrations/20260926050000_registry_lockfile_plan/migration.sql +32 -0
  396. package/prisma/migrations/20260926050000_repo_access_authorization/migration.sql +82 -0
  397. package/prisma/migrations/20260926060000_registry_plan_refusal/migration.sql +19 -0
  398. package/prisma/migrations/20260926100000_registry_plan_refusal_published_at/migration.sql +5 -0
  399. package/prisma/migrations/20260926190000_run_host_status/migration.sql +18 -0
  400. package/prisma/migrations/20260926210000_run_host_status_at_dispatch/migration.sql +5 -0
  401. package/prisma/migrations/20260927010000_resource_grants/migration.sql +72 -0
  402. package/prisma/migrations/20260927020000_proxy_session_budget_exhausted/migration.sql +3 -0
  403. package/prisma/migrations/20260927030000_coding_debug_trace/migration.sql +4 -0
  404. package/prisma/migrations/20260927040000_proxy_session_upstream_failure/migration.sql +3 -0
  405. package/prisma/migrations/20260927050000_coding_run_services/migration.sql +74 -0
  406. package/prisma/migrations/20260928000000_coding_run_tool_image/migration.sql +5 -0
  407. package/prisma/schema.prisma +407 -21
@@ -1,11 +1,24 @@
1
- /** Agent CRUD with scope checks in server.ts and ownership checks here. */
1
+ /**
2
+ * Agent CRUD with scope checks in server.ts and access checks here
3
+ * (auth/access.ts: owner, or a grant of read < execute < write). Owner-only
4
+ * whatever the grants: delete_agent and repository changes; make_owner is
5
+ * the agents:admin escape hatch. See
6
+ * docs/private/2026-09-26-resource-sharing-grants-spec-and-plan.md.
7
+ */
2
8
  import { Prisma } from "#prisma";
3
9
  import { z } from "zod";
4
10
  import { CodingProfilePatchSchema, CodingProfileSchema } from "../../coding/profile.js";
5
11
  import { codingProviderSupportsModel } from "../../coding/provider.js";
6
12
  import { validateCronExpression } from "../../core/cron.js";
7
- import { assertCanMutate, canRead, requireOwnedAgent, requireReadableBudgetGroup, visibleToPrincipal, } from "../auth/ownership.js";
8
- import { requireScope } from "../auth/resource-server.js";
13
+ import { modelSupportedEfforts } from "../../providers/llm/routing.js";
14
+ import { LLM_EFFORT_LEVELS, isLlmEffort } from "../../providers/llm/types.js";
15
+ import { requireReadableBudgetGroup } from "../auth/ownership.js";
16
+ import { agentAccessResolver, agentNotFound, assertAgentAccess, readableAgentsWhere, requireAgentAccess, } from "../auth/access.js";
17
+ import { EVERYONE_KEY, atLeast, canDelegate, deleteGrantsFor, effectiveAccess } from "../../core/grants.js";
18
+ import { projectTool } from "./tools.js";
19
+ import { requireAnyScope, requireScope } from "../auth/resource-server.js";
20
+ import { logger } from "../../core/logger.js";
21
+ import { authorizeRepositoryForSet } from "../auth/repo-authorization.js";
9
22
  import { McpError } from "../errors.js";
10
23
  import { textResult } from "./text-result.js";
11
24
  // workerImageRef is the BYO-arbitrary-image escape hatch (see
@@ -15,6 +28,18 @@ import { textResult } from "./text-result.js";
15
28
  function requireWorkerImageRefScope(ctx) {
16
29
  requireScope(ctx, ctx.canonicalUri, "agents:admin");
17
30
  }
31
+ // A debug trace makes the worker log prompts and repository content to its
32
+ // pod log, so turning it on (or off) is an admin action, gated like
33
+ // workerImageRef and audited.
34
+ function requireDebugTraceScope(ctx) {
35
+ requireScope(ctx, ctx.canonicalUri, "agents:admin");
36
+ }
37
+ const agentsLog = logger.child({ module: "mcp-agents" });
38
+ const MAX_DEBUG_TRACE_MINUTES = 24 * 60;
39
+ /** Package allowlists widen what a run may download, so they need their own approval. */
40
+ function requirePackageApproval(ctx) {
41
+ requireAnyScope(ctx, ctx.canonicalUri, "packages:approve", "agents:admin");
42
+ }
18
43
  const MAX_AGENT_NAME_CHARS = 200;
19
44
  const MAX_SYSTEM_PROMPT_CHARS = 64 * 1024;
20
45
  const MAX_MODEL_CHARS = 128;
@@ -31,6 +56,7 @@ const agentFields = {
31
56
  kind: z.enum(["native", "coding"]),
32
57
  budgetGroupId: z.string().min(1).max(128),
33
58
  memoryEnabled: z.boolean(),
59
+ effort: z.enum(LLM_EFFORT_LEVELS),
34
60
  };
35
61
  const CreateAgentSchema = z
36
62
  .object({
@@ -45,7 +71,9 @@ const CreateAgentSchema = z
45
71
  kind: agentFields.kind.default("native"),
46
72
  budgetGroupId: agentFields.budgetGroupId.optional(),
47
73
  memoryEnabled: agentFields.memoryEnabled.default(false),
74
+ effort: agentFields.effort.optional(),
48
75
  codingProfile: CodingProfileSchema.optional(),
76
+ repositoryAdminOverride: z.boolean().optional(),
49
77
  })
50
78
  .strict()
51
79
  .superRefine((value, ctx) => {
@@ -91,21 +119,47 @@ const UpdateAgentSchema = z
91
119
  kind: agentFields.kind.optional(),
92
120
  budgetGroupId: agentFields.budgetGroupId.nullable().optional(),
93
121
  memoryEnabled: agentFields.memoryEnabled.optional(),
94
- codingProfile: CodingProfilePatchSchema.optional(),
122
+ effort: agentFields.effort.nullable().optional(),
123
+ codingProfile: CodingProfilePatchSchema.extend({
124
+ /** Not a stored profile field: sets CodingAgentProfile.debugTraceUntil = now + minutes (null clears). */
125
+ debugTraceMinutes: z.number().int().min(1).max(MAX_DEBUG_TRACE_MINUTES).nullable().optional(),
126
+ }).optional(),
127
+ repositoryAdminOverride: z.boolean().optional(),
95
128
  })
96
129
  .strict();
130
+ const REPOSITORY_ADMIN_OVERRIDE = {
131
+ type: "boolean",
132
+ description: "Admins only (agents:admin with the admin role): approve codingProfile.repository without checking GitHub access, recorded as an admin approval. On an agent the admin doesn't own, only codingProfile.repository may change.",
133
+ };
134
+ /** The profile columns a repository authorization is stamped into. */
135
+ function profileStamp(authorization) {
136
+ return {
137
+ repositoryAuthorizedVia: authorization.authorizedVia,
138
+ repositoryAuthorizedById: authorization.authorizedById,
139
+ repositoryAuthorizedAt: authorization.authorizedAt,
140
+ };
141
+ }
97
142
  const profileJsonSchema = {
98
143
  type: "object",
99
144
  additionalProperties: false,
100
145
  properties: {
101
146
  provider: { type: "string", enum: ["codex", "claude-code"] },
102
- repository: { type: "string" },
147
+ repository: {
148
+ type: "string",
149
+ description: "owner/name on GitHub. Your linked GitHub account (link_host_account) must have write access to it, unless a wardby admin approves it (repositoryAdminOverride). Re-checked on every run.",
150
+ },
103
151
  baseRef: { type: "string" },
104
152
  defaultTask: { type: ["string", "null"] },
105
153
  allowWebhookTaskOverride: { type: "boolean" },
106
154
  timeoutSec: { type: "integer", minimum: 60, maximum: 7200 },
107
- allowedEgress: { type: "array", maxItems: 64, items: { type: "string" } },
108
- protectedPaths: { type: "array", minItems: 1, maxItems: 128, items: { type: "string" } },
155
+ protectedPaths: {
156
+ type: "array",
157
+ minItems: 1,
158
+ maxItems: 128,
159
+ items: { type: "string" },
160
+ description: 'Glob patterns (*, **, ?) a run may not change. A leading ! makes an exception, a literal file path without wildcards: ["docs/**", "!docs/changelog.md"] protects docs/ except the changelog. Every run also protects .wardby/** except .wardby/services.yaml, whatever this list says; exceptions cannot unprotect .wardby/.',
161
+ },
162
+ collectExclude: { type: "array", maxItems: 64, items: { type: "string" } },
109
163
  toolchain: { type: "string", enum: ["node", "node-python"] },
110
164
  toolchainVersion: { type: ["string", "null"] },
111
165
  workerImageRef: { type: ["string", "null"] },
@@ -115,6 +169,22 @@ const profileJsonSchema = {
115
169
  maximum: 32768,
116
170
  description: "Workspace disk size in MiB. Null uses the deployment default (CODING_DISK_MB), capped by the operator's CODING_MAX_DISK_MB.",
117
171
  },
172
+ packageAllowlist: {
173
+ type: "object",
174
+ description: "Approved top-level packages per ecosystem (npm, pypi). Needs packages:approve or agents:admin.",
175
+ additionalProperties: { type: "array", maxItems: 256, items: { type: "string" } },
176
+ },
177
+ packagePolicy: {
178
+ type: "object",
179
+ properties: { minReleaseAgeDays: { type: "integer", minimum: 0, maximum: 30 } },
180
+ additionalProperties: false,
181
+ },
182
+ services: {
183
+ type: "array",
184
+ maxItems: 16,
185
+ items: { type: "string" },
186
+ description: 'Coding-run services this agent\'s runs may start: catalog names such as "postgres" (any version the catalog has). A repository asks for them in .wardby/services.yaml on its base branch; a run asking for one not listed here is refused. Empty = none.',
187
+ },
118
188
  },
119
189
  };
120
190
  function invalidArguments(label, error) {
@@ -143,6 +213,24 @@ function validateSchedule(schedule, timezone) {
143
213
  throw new McpError(400, `Invalid schedule/timezone: ${error instanceof Error ? error.message : String(error)}`);
144
214
  }
145
215
  }
216
+ /**
217
+ * Effort only drives the native engine, and only on a model that accepts the
218
+ * level; reject rather than store a setting that would silently do nothing.
219
+ */
220
+ function validateEffort(kind, model, effort) {
221
+ if (effort == null)
222
+ return;
223
+ if (kind !== "native") {
224
+ throw new McpError(400, "effort is only valid for native agents; coding agents do not use it.");
225
+ }
226
+ const accepted = modelSupportedEfforts(model);
227
+ if (!isLlmEffort(effort) || !accepted.includes(effort)) {
228
+ throw new McpError(400, `Model "${model}" does not accept effort "${effort}". ` +
229
+ (accepted.length > 0
230
+ ? `Accepted levels: ${accepted.join(", ")}.`
231
+ : "It accepts no effort setting; leave effort unset (or null)."));
232
+ }
233
+ }
146
234
  function storedProfile(profile) {
147
235
  return CodingProfileSchema.parse({
148
236
  provider: profile.provider,
@@ -151,14 +239,27 @@ function storedProfile(profile) {
151
239
  defaultTask: profile.defaultTask,
152
240
  allowWebhookTaskOverride: profile.allowWebhookTaskOverride,
153
241
  timeoutSec: profile.timeoutSec,
154
- allowedEgress: profile.allowedEgress,
155
242
  protectedPaths: profile.protectedPaths,
243
+ collectExclude: profile.collectExclude,
156
244
  toolchain: profile.toolchain,
157
245
  toolchainVersion: profile.toolchainVersion,
158
246
  workerImageRef: profile.workerImageRef,
159
247
  workspaceDiskMb: profile.workspaceDiskMb,
248
+ packageAllowlist: profile.packageAllowlist,
249
+ packagePolicy: profile.packagePolicy,
250
+ services: profile.services,
160
251
  });
161
252
  }
253
+ /** A service name an agent is allowed must exist in the catalog (any version); typos fail here, not at a run. */
254
+ async function requireCatalogServiceNames(db, names) {
255
+ if (names.length === 0)
256
+ return;
257
+ const known = new Set((await db.codingService.findMany({ where: { name: { in: [...names] } }, select: { name: true } })).map((row) => row.name));
258
+ const unknown = names.filter((name) => !known.has(name));
259
+ if (unknown.length > 0) {
260
+ throw new McpError(400, `wardby's service catalog has no service named ${unknown.map((name) => `"${name}"`).join(", ")}; list_services shows the catalog.`);
261
+ }
262
+ }
162
263
  export function registerAgentTools(mcp) {
163
264
  mcp.registerTool({
164
265
  name: "create_agent",
@@ -178,7 +279,13 @@ export function registerAgentTools(mcp) {
178
279
  kind: { type: "string", enum: ["native", "coding"] },
179
280
  budgetGroupId: { type: "string" },
180
281
  memoryEnabled: { type: "boolean" },
282
+ effort: {
283
+ type: "string",
284
+ enum: [...LLM_EFFORT_LEVELS],
285
+ description: "Reasoning effort for native agents. Unset uses the provider default.",
286
+ },
181
287
  codingProfile: { ...profileJsonSchema, required: ["repository"] },
288
+ repositoryAdminOverride: REPOSITORY_ADMIN_OVERRIDE,
182
289
  },
183
290
  required: ["name", "systemPrompt", "model", "budgetUsd"],
184
291
  },
@@ -186,16 +293,39 @@ export function registerAgentTools(mcp) {
186
293
  const args = parseCreateAgent(rawArgs);
187
294
  if (args.codingProfile?.workerImageRef != null)
188
295
  requireWorkerImageRefScope(ctx);
296
+ const packages = args.codingProfile;
297
+ if (packages &&
298
+ (Object.keys(packages.packageAllowlist ?? {}).length > 0 ||
299
+ Object.keys(packages.packagePolicy ?? {}).length > 0))
300
+ requirePackageApproval(ctx);
189
301
  validateSchedule(args.schedule, args.timezone ?? "UTC");
302
+ validateEffort(args.kind, args.model, args.effort);
303
+ if (args.codingProfile)
304
+ await requireCatalogServiceNames(ctx.db, args.codingProfile.services);
190
305
  if (args.budgetGroupId) {
191
306
  await requireReadableBudgetGroup(ctx.db, args.budgetGroupId, ctx.principal.id);
192
307
  }
193
- const { codingProfile, ...agentData } = args;
308
+ const { codingProfile, repositoryAdminOverride, ...agentData } = args;
309
+ if (repositoryAdminOverride === true && !codingProfile) {
310
+ throw new McpError(400, "repositoryAdminOverride only applies with a codingProfile repository.");
311
+ }
312
+ // The creator is the owner, so it's the creator's GitHub access that counts.
313
+ const authorization = codingProfile
314
+ ? await authorizeRepositoryForSet(ctx, {
315
+ ownerId: ctx.principal.id,
316
+ provider: "github",
317
+ repository: codingProfile.repository,
318
+ kind: "coding",
319
+ adminOverride: repositoryAdminOverride === true,
320
+ })
321
+ : null;
194
322
  const agent = await ctx.db.agent.create({
195
323
  data: {
196
324
  ...agentData,
197
325
  ownerId: ctx.principal.id,
198
- ...(codingProfile ? { codingProfile: { create: codingProfile } } : {}),
326
+ ...(codingProfile && authorization
327
+ ? { codingProfile: { create: { ...codingProfile, ...profileStamp(authorization) } } }
328
+ : {}),
199
329
  },
200
330
  include: { codingProfile: true },
201
331
  });
@@ -221,7 +351,24 @@ export function registerAgentTools(mcp) {
221
351
  kind: { type: "string", enum: ["native", "coding"] },
222
352
  budgetGroupId: { type: ["string", "null"] },
223
353
  memoryEnabled: { type: "boolean" },
224
- codingProfile: profileJsonSchema,
354
+ effort: {
355
+ type: ["string", "null"],
356
+ enum: [...LLM_EFFORT_LEVELS, null],
357
+ description: "Reasoning effort for native agents. Null clears it back to the provider default.",
358
+ },
359
+ codingProfile: {
360
+ ...profileJsonSchema,
361
+ properties: {
362
+ ...profileJsonSchema.properties,
363
+ debugTraceMinutes: {
364
+ type: ["integer", "null"],
365
+ minimum: 1,
366
+ maximum: MAX_DEBUG_TRACE_MINUTES,
367
+ description: "Admins only (agents:admin): trace every coding run of this agent for this many minutes from now; the worker writes its full stream trace, which can include prompts and repository content, to the run pod's log. Null turns it off.",
368
+ },
369
+ },
370
+ },
371
+ repositoryAdminOverride: REPOSITORY_ADMIN_OVERRIDE,
225
372
  },
226
373
  required: ["id"],
227
374
  },
@@ -229,18 +376,105 @@ export function registerAgentTools(mcp) {
229
376
  const args = parseUpdateAgent(rawArgs);
230
377
  if (args.codingProfile?.workerImageRef !== undefined)
231
378
  requireWorkerImageRefScope(ctx);
379
+ const debugTraceMinutes = args.codingProfile?.debugTraceMinutes;
380
+ if (debugTraceMinutes !== undefined)
381
+ requireDebugTraceScope(ctx);
382
+ if (args.codingProfile?.packageAllowlist !== undefined || args.codingProfile?.packagePolicy !== undefined)
383
+ requirePackageApproval(ctx);
384
+ // Repository authorization calls GitHub, so it runs before (outside) the
385
+ // serializable transaction, on a pre-read; the transaction then verifies
386
+ // the pre-read still holds. Only a changed repository (or becoming a
387
+ // coding agent) is re-authorized; other edits keep the existing stamp.
388
+ const before = await ctx.db.agent.findUnique({ where: { id: args.id }, include: { codingProfile: true } });
389
+ if (!before)
390
+ throw agentNotFound(args.id);
391
+ // Strict: a repository is a binding, so only the agent's owner sets
392
+ // one (or an admin's explicit approval, below). Not a write-grantee,
393
+ // not the stdio operator.
394
+ const isOwner = before.ownerId !== null && before.ownerId === ctx.principal.id;
395
+ // An admin may approve a repository (repositoryAdminOverride) on an agent
396
+ // they don't own, but change nothing else on it.
397
+ const adminEdit = args.repositoryAdminOverride === true && !isOwner;
398
+ if (adminEdit) {
399
+ requireScope(ctx, ctx.canonicalUri, "agents:admin");
400
+ const { id: _id, repositoryAdminOverride: _flag, codingProfile, ...others } = args;
401
+ const profileKeys = Object.keys(codingProfile ?? {});
402
+ if (Object.values(others).some((v) => v !== undefined) ||
403
+ profileKeys.length !== 1 ||
404
+ profileKeys[0] !== "repository") {
405
+ throw new McpError(403, "On an agent you don't own, an admin may change only codingProfile.repository (with repositoryAdminOverride).");
406
+ }
407
+ }
408
+ else {
409
+ await assertAgentAccess(ctx, before, args.id, "write");
410
+ }
411
+ const repositoryAfter = (kind, profile) => kind === "coding" ? (args.codingProfile?.repository ?? profile?.repository ?? null) : null;
412
+ const plannedRepository = repositoryAfter(args.kind ?? before.kind, before.codingProfile);
413
+ const repositoryChanges = (existing) => {
414
+ const next = repositoryAfter(args.kind ?? existing.kind, existing.codingProfile);
415
+ return next !== null && (existing.kind !== "coding" || existing.codingProfile?.repository !== next);
416
+ };
417
+ const refuseRepositoryBinding = () => {
418
+ throw new McpError(403, `Agent "${args.id}": only its owner can give it a repository (codingProfile.repository, or becoming a coding agent); a repository is a binding, not shared config.`);
419
+ };
420
+ if (!adminEdit && !isOwner && repositoryChanges(before))
421
+ refuseRepositoryBinding();
422
+ // The coding profile as a whole (task, base ref, protected paths,
423
+ // task-override opt-in, image, packages, limits...), leaving coding,
424
+ // and budget-group membership direct what runs do with the owner's
425
+ // repository access and money: owner-only, like the repository itself
426
+ // (review I1/M1/M6). Write keeps name, prompt, model, budget amount,
427
+ // turns, effort, memory and schedule.
428
+ const ownerOnlyChange = (existingKind) => args.codingProfile !== undefined ||
429
+ (args.kind !== undefined && args.kind !== existingKind) ||
430
+ args.budgetGroupId !== undefined;
431
+ const refuseOwnerOnly = () => {
432
+ throw new McpError(403, `Agent "${args.id}": only its owner can change its coding profile, kind or budget group.`);
433
+ };
434
+ if (!adminEdit && !isOwner && ownerOnlyChange(before.kind))
435
+ refuseOwnerOnly();
436
+ const authorization = plannedRepository !== null && repositoryChanges(before)
437
+ ? await authorizeRepositoryForSet(ctx, {
438
+ ownerId: before.ownerId,
439
+ provider: "github",
440
+ repository: plannedRepository,
441
+ kind: "coding",
442
+ adminOverride: args.repositoryAdminOverride === true,
443
+ })
444
+ : null;
445
+ if (args.repositoryAdminOverride === true && !authorization) {
446
+ throw new McpError(400, "repositoryAdminOverride only applies when the coding repository changes.");
447
+ }
448
+ const debugTraceUntil = debugTraceMinutes === undefined
449
+ ? undefined
450
+ : debugTraceMinutes === null
451
+ ? null
452
+ : new Date(Date.now() + debugTraceMinutes * 60_000);
232
453
  const agent = await ctx.db.$transaction(async (tx) => {
233
454
  const existing = await tx.agent.findUnique({ where: { id: args.id }, include: { codingProfile: true } });
234
455
  if (!existing)
235
- throw new McpError(404, `Agent "${args.id}" not found.`);
236
- assertCanMutate(existing.ownerId, ctx.principal.id, `Agent "${args.id}" is not owned by the caller.`);
456
+ throw agentNotFound(args.id);
457
+ if (!adminEdit) {
458
+ await assertAgentAccess(ctx, existing, args.id, "write", tx);
459
+ if (!isOwner && repositoryChanges(existing))
460
+ refuseRepositoryBinding();
461
+ if (!isOwner && ownerOnlyChange(existing.kind))
462
+ refuseOwnerOnly();
463
+ }
464
+ if (existing.ownerId !== before.ownerId ||
465
+ (repositoryChanges(existing) &&
466
+ (!authorization ||
467
+ repositoryAfter(args.kind ?? existing.kind, existing.codingProfile) !== plannedRepository))) {
468
+ throw new McpError(409, `Agent "${args.id}" changed while it was being updated; try again.`);
469
+ }
237
470
  if (args.budgetGroupId) {
238
- await requireReadableBudgetGroup(ctx.db, args.budgetGroupId, ctx.principal.id);
471
+ await requireReadableBudgetGroup(tx, args.budgetGroupId, ctx.principal.id);
239
472
  }
240
473
  const nextKind = args.kind ?? existing.kind;
241
474
  if (nextKind === "native" && args.codingProfile) {
242
475
  throw new McpError(400, "A coding profile is only valid for coding agents.");
243
476
  }
477
+ validateEffort(nextKind, args.model ?? existing.model, args.effort !== undefined ? args.effort : existing.effort);
244
478
  let nextProfile = null;
245
479
  if (nextKind === "coding") {
246
480
  const attachedTools = await tx.agentTool.count({ where: { agentId: args.id } });
@@ -248,10 +482,13 @@ export function registerAgentTools(mcp) {
248
482
  throw new McpError(400, "Detach all native sandbox tools before changing an agent to coding.");
249
483
  }
250
484
  const currentProfile = existing.codingProfile ? storedProfile(existing.codingProfile) : undefined;
251
- const profileResult = CodingProfileSchema.safeParse({ ...currentProfile, ...args.codingProfile });
485
+ const { debugTraceMinutes: _minutes, ...profilePatch } = args.codingProfile ?? {};
486
+ const profileResult = CodingProfileSchema.safeParse({ ...currentProfile, ...profilePatch });
252
487
  if (!profileResult.success)
253
488
  throw invalidArguments("coding profile", profileResult.error);
254
489
  nextProfile = profileResult.data;
490
+ if (args.codingProfile?.services !== undefined)
491
+ await requireCatalogServiceNames(tx, nextProfile.services);
255
492
  const nextModel = args.model ?? existing.model;
256
493
  if (!codingProviderSupportsModel(nextProfile.provider, nextModel)) {
257
494
  throw new McpError(400, `Model "${nextModel}" is not supported by coding provider "${nextProfile.provider}".`);
@@ -264,14 +501,16 @@ export function registerAgentTools(mcp) {
264
501
  if (nextKind === "coding" && nextSchedule && nextScheduleEnabled && !nextProfile?.defaultTask) {
265
502
  throw new McpError(400, "A default task is required for an enabled coding-agent schedule.");
266
503
  }
267
- const { id, codingProfile: _profilePatch, ...updates } = args;
504
+ const { id, codingProfile: _profilePatch, repositoryAdminOverride: _override, ...updates } = args;
505
+ const stamp = authorization ? profileStamp(authorization) : {};
506
+ const trace = debugTraceUntil === undefined ? {} : { debugTraceUntil };
268
507
  const profileMutation = nextKind === "native"
269
508
  ? existing.codingProfile
270
509
  ? { delete: true }
271
510
  : undefined
272
511
  : existing.codingProfile
273
- ? { update: nextProfile }
274
- : { create: nextProfile };
512
+ ? { update: { ...nextProfile, ...stamp, ...trace } }
513
+ : { create: { ...nextProfile, ...stamp, ...trace } };
275
514
  return tx.agent.update({
276
515
  where: { id },
277
516
  data: {
@@ -281,6 +520,14 @@ export function registerAgentTools(mcp) {
281
520
  include: { codingProfile: true },
282
521
  });
283
522
  }, { isolationLevel: Prisma.TransactionIsolationLevel.Serializable });
523
+ if (debugTraceUntil !== undefined) {
524
+ agentsLog.info({
525
+ event: "coding.debug_trace.set",
526
+ agentId: agent.id,
527
+ until: debugTraceUntil?.toISOString() ?? null,
528
+ by: ctx.principal.id,
529
+ }, "coding debug trace set");
530
+ }
284
531
  return textResult(agent);
285
532
  },
286
533
  });
@@ -289,11 +536,9 @@ export function registerAgentTools(mcp) {
289
536
  scope: "agents:read",
290
537
  inputSchema: { type: "object", properties: {}, additionalProperties: false },
291
538
  handler: async (_args, ctx) => {
292
- const agents = await ctx.db.agent.findMany({
293
- where: visibleToPrincipal(ctx.principal.id),
294
- include: { codingProfile: true },
295
- });
296
- return textResult(agents);
539
+ const [where, accessOf] = await Promise.all([readableAgentsWhere(ctx), agentAccessResolver(ctx)]);
540
+ const agents = await ctx.db.agent.findMany({ where, include: { codingProfile: true } });
541
+ return textResult(agents.map((agent) => ({ ...agent, access: accessOf(agent) })));
297
542
  },
298
543
  });
299
544
  mcp.registerTool({
@@ -306,14 +551,20 @@ export function registerAgentTools(mcp) {
306
551
  additionalProperties: false,
307
552
  },
308
553
  handler: async (args, ctx) => {
309
- const agent = await ctx.db.agent.findUnique({
554
+ const { agent, access } = await assertAgentAccess(ctx, await ctx.db.agent.findUnique({
310
555
  where: { id: args.id },
311
- include: { tools: { include: { tool: true } }, codingProfile: true },
556
+ include: { tools: { include: { tool: true } }, codingProfile: true, repositories: true },
557
+ }), args.id, "read");
558
+ // A7: a tool's code and schema are its owner's, whoever can read the
559
+ // agent (the agent's owner included). Capabilities stay visible: config.
560
+ return textResult({
561
+ ...agent,
562
+ access,
563
+ tools: agent.tools.map((attachment) => ({
564
+ ...attachment,
565
+ tool: projectTool(attachment.tool, ctx.principal.id),
566
+ })),
312
567
  });
313
- if (!agent || !canRead(agent.ownerId, ctx.principal.id)) {
314
- throw new McpError(404, `Agent "${args.id}" not found.`);
315
- }
316
- return textResult(agent);
317
568
  },
318
569
  });
319
570
  mcp.registerTool({
@@ -326,9 +577,13 @@ export function registerAgentTools(mcp) {
326
577
  additionalProperties: false,
327
578
  },
328
579
  handler: async (args, ctx) => {
329
- await requireOwnedAgent(ctx.db, args.id, ctx.principal.id);
580
+ await requireAgentAccess(ctx, args.id, "owner");
330
581
  try {
331
- await ctx.db.agent.delete({ where: { id: args.id } });
582
+ // Grants have no FK to the resource: removed in the same transaction.
583
+ await ctx.db.$transaction(async (tx) => {
584
+ await deleteGrantsFor(tx, "agent", args.id);
585
+ await tx.agent.delete({ where: { id: args.id } });
586
+ });
332
587
  }
333
588
  catch (err) {
334
589
  // Run.agentId has no onDelete rule, so Postgres refuses to delete an
@@ -343,33 +598,176 @@ export function registerAgentTools(mcp) {
343
598
  return textResult({ deleted: args.id });
344
599
  },
345
600
  });
346
- // Deliberately bypasses ownership entirely (no assertCanMutate/
347
- // requireOwnedAgent call) — that's the point of this tool vs. the
348
- // self-service null-owner-is-public-mutable rule everything else here
349
- // follows. Gated on agents:admin, a step up from agents:write, so an
350
- // ordinary caller can never reach it regardless of what they own.
351
- // ownerId: null releases the agent back to public, same as any agent
352
- // that was never assigned an owner.
601
+ // Deliberately bypasses access checks entirely: that's the point of this
602
+ // tool, the audited agents:admin escape hatch. Gated on agents:admin, a
603
+ // step up from agents:write, so an ordinary caller can never reach it
604
+ // regardless of what they own. The new owner must exist; releasing an
605
+ // agent to owner-less is gone (sharing is done with grants). Everything
606
+ // bound to the agent belongs to its owner, so a transfer drops what the
607
+ // new owner doesn't own (resource-sharing grants spec §3.6, R2-1).
353
608
  mcp.registerTool({
354
609
  name: "make_owner",
355
610
  scope: "agents:admin",
611
+ description: "Admins: gives an agent a new owner (an existing principal id). Secret and datastore bindings the new owner doesn't own are removed; tool capabilities the new owner never granted are suspended until they re-run attach_tool; sub-agent edges the new owner can't delegate are removed; between two owners every grant on the agent is reset (adopting an owner-less agent keeps them). Returns what changed.",
356
612
  inputSchema: {
357
613
  type: "object",
358
614
  additionalProperties: false,
359
- properties: { agentId: { type: "string" }, ownerId: { type: ["string", "null"] } },
615
+ properties: {
616
+ agentId: { type: "string" },
617
+ ownerId: { type: ["string", "null"] },
618
+ keepEveryoneExecute: {
619
+ type: "boolean",
620
+ description: "When adopting an owner-less agent: keep an everyone grant at execute instead of lowering it to read. Anyone could then run it with the new owner's secrets and capabilities.",
621
+ },
622
+ },
360
623
  required: ["agentId", "ownerId"],
361
624
  },
362
625
  handler: async (args, ctx) => {
363
- const agent = await ctx.db.agent.findUnique({ where: { id: args.agentId } });
364
- if (!agent)
365
- throw new McpError(404, `Agent "${args.agentId}" not found.`);
366
- if (args.ownerId !== null) {
367
- const principal = await ctx.db.principal.findUnique({ where: { id: args.ownerId } });
368
- if (!principal)
369
- throw new McpError(400, `Principal "${args.ownerId}" not found.`);
626
+ if (args.ownerId === null) {
627
+ throw new McpError(400, "make_owner no longer releases an agent to owner-less; share it with grant_access instead (everyone at execute, or named principals).");
370
628
  }
371
- const updated = await ctx.db.agent.update({ where: { id: args.agentId }, data: { ownerId: args.ownerId } });
372
- return textResult(updated);
629
+ const newOwner = args.ownerId;
630
+ const principal = await ctx.db.principal.findUnique({ where: { id: newOwner } });
631
+ if (!principal)
632
+ throw new McpError(400, `Principal "${newOwner}" not found.`);
633
+ const result = await ctx.db.$transaction(async (tx) => {
634
+ const agent = await tx.agent.findUnique({ where: { id: args.agentId }, include: { codingProfile: true } });
635
+ if (!agent)
636
+ throw agentNotFound(args.agentId);
637
+ // Admin and grandfathered repository approvals were granted to the
638
+ // agent under its current owner. Moving it away from that owner
639
+ // turns them into ordinary checks of the next owner's own GitHub
640
+ // access, so an approval never travels with the agent. An
641
+ // owner-less agent getting its first owner keeps them: that is the
642
+ // pre-upgrade step for owner-less agents with repositories.
643
+ const revoked = [];
644
+ const transfer = agent.ownerId !== null && agent.ownerId !== newOwner;
645
+ if (transfer) {
646
+ const approved = { in: ["admin", "grandfathered"] };
647
+ const reset = { authorizedVia: "host_permission", authorizedById: null, authorizedAt: new Date() };
648
+ const links = await tx.agentRepository.findMany({
649
+ where: { agentId: agent.id, authorizedVia: approved },
650
+ orderBy: { repository: "asc" },
651
+ });
652
+ await tx.agentRepository.updateMany({ where: { agentId: agent.id, authorizedVia: approved }, data: reset });
653
+ revoked.push(...links.map((l) => l.repository));
654
+ const profile = await tx.codingAgentProfile.updateMany({
655
+ where: { agentId: agent.id, repositoryAuthorizedVia: approved },
656
+ data: {
657
+ repositoryAuthorizedVia: reset.authorizedVia,
658
+ repositoryAuthorizedById: null,
659
+ repositoryAuthorizedAt: reset.authorizedAt,
660
+ },
661
+ });
662
+ if (profile.count > 0 && agent.codingProfile)
663
+ revoked.push(agent.codingProfile.repository);
664
+ }
665
+ // Grants: between two owners every grant is reset (the new owner
666
+ // decides who shares it); adopting an owner-less agent keeps them,
667
+ // but lowers an everyone grant to read unless keepEveryoneExecute:
668
+ // the agent may regain the new owner's secrets and capabilities
669
+ // (review I3).
670
+ const grantsReset = transfer ? await deleteGrantsFor(tx, "agent", agent.id) : 0;
671
+ let everyoneGrant = null;
672
+ if (agent.ownerId === null) {
673
+ const everyone = await tx.resourceGrant.findFirst({
674
+ where: { resourceType: "agent", resourceId: agent.id, granteeKey: EVERYONE_KEY },
675
+ });
676
+ if (everyone) {
677
+ everyoneGrant = { before: everyone.level, after: everyone.level };
678
+ if (everyone.level !== "read" && args.keepEveryoneExecute !== true) {
679
+ await tx.resourceGrant.update({
680
+ where: { id: everyone.id },
681
+ data: { level: "read", source: "operator", grantedById: ctx.principal.id },
682
+ });
683
+ everyoneGrant.after = "read";
684
+ }
685
+ }
686
+ }
687
+ const updated = await tx.agent.update({ where: { id: args.agentId }, data: { ownerId: newOwner } });
688
+ // Secret/datastore bindings the new owner doesn't own: removed.
689
+ const secrets = await tx.agentSecret.findMany({
690
+ where: { agentId: agent.id },
691
+ include: { secret: { select: { ownerId: true } } },
692
+ });
693
+ const foreignSecrets = secrets.filter((b) => b.secret.ownerId !== newOwner);
694
+ if (foreignSecrets.length > 0) {
695
+ await tx.agentSecret.deleteMany({
696
+ where: { agentId: agent.id, secretId: { in: foreignSecrets.map((b) => b.secretId) } },
697
+ });
698
+ }
699
+ const datastores = await tx.agentDatastore.findMany({
700
+ where: { agentId: agent.id },
701
+ include: { datastore: { select: { ownerId: true } } },
702
+ });
703
+ const foreignDatastores = datastores.filter((b) => b.datastore.ownerId !== newOwner);
704
+ if (foreignDatastores.length > 0) {
705
+ await tx.agentDatastore.deleteMany({
706
+ where: { agentId: agent.id, datastoreId: { in: foreignDatastores.map((b) => b.datastoreId) } },
707
+ });
708
+ }
709
+ const bindingsRemoved = [
710
+ ...foreignSecrets.map((b) => ({ kind: "secret", boundName: b.boundName, resourceId: b.secretId })),
711
+ ...foreignDatastores.map((b) => ({ kind: "datastore", boundName: b.boundName, resourceId: b.datastoreId })),
712
+ ];
713
+ // Tool capabilities: rows aren't rewritten; the runner ignores any
714
+ // the new owner never stamped (capabilitiesGrantedById).
715
+ const attachments = await tx.agentTool.findMany({
716
+ where: { agentId: agent.id },
717
+ include: { tool: { select: { id: true, name: true } } },
718
+ });
719
+ const toolCapabilitiesSuspended = attachments
720
+ .filter((at) => at.capabilitiesGrantedById !== newOwner)
721
+ .map((at) => ({ toolId: at.tool.id, toolName: at.tool.name }));
722
+ // Sub-agent edges (both directions) the new owner can't delegate.
723
+ const edges = await tx.agentSubAgent.findMany({
724
+ where: { OR: [{ parentAgentId: agent.id }, { childAgentId: agent.id }] },
725
+ include: {
726
+ parent: { select: { id: true, ownerId: true } },
727
+ child: { select: { id: true, ownerId: true } },
728
+ },
729
+ });
730
+ const subAgentEdgesRemoved = [];
731
+ for (const edge of edges) {
732
+ const parent = edge.parentAgentId === agent.id ? { ownerId: newOwner } : edge.parent;
733
+ const child = edge.childAgentId === agent.id ? { id: agent.id, ownerId: newOwner } : edge.child;
734
+ if (!(await canDelegate(tx, parent, child))) {
735
+ subAgentEdgesRemoved.push({
736
+ parentAgentId: edge.parentAgentId,
737
+ childAgentId: edge.childAgentId,
738
+ boundName: edge.boundName,
739
+ });
740
+ }
741
+ }
742
+ for (const edge of subAgentEdgesRemoved) {
743
+ await tx.agentSubAgent.deleteMany({
744
+ where: { parentAgentId: edge.parentAgentId, childAgentId: edge.childAgentId },
745
+ });
746
+ }
747
+ // Webhooks others created stay, but fire only while their creator
748
+ // may still trigger the agent (core/webhooks.ts).
749
+ const webhooks = await tx.webhook.findMany({ where: { agentId: agent.id } });
750
+ const webhooksInactive = [];
751
+ for (const webhook of webhooks) {
752
+ if (webhook.ownerId === newOwner)
753
+ continue;
754
+ const creatorAccess = await effectiveAccess(tx, "agent", updated, webhook.ownerId);
755
+ if (!atLeast("agent", creatorAccess, "execute")) {
756
+ webhooksInactive.push({ id: webhook.id, createdBy: webhook.ownerId });
757
+ }
758
+ }
759
+ return {
760
+ ...updated,
761
+ repositoryApprovalsRevoked: revoked,
762
+ bindingsRemoved,
763
+ toolCapabilitiesSuspended,
764
+ subAgentEdgesRemoved,
765
+ grantsReset,
766
+ everyoneGrant,
767
+ webhooksInactive,
768
+ };
769
+ }, { isolationLevel: Prisma.TransactionIsolationLevel.Serializable });
770
+ return textResult(result);
373
771
  },
374
772
  });
375
773
  }