@wardby/cli 0.7.0 → 0.9.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 (367) hide show
  1. package/.env.example +5 -5
  2. package/README.md +6 -1
  3. package/deploy/local/docker-compose.quickstart-coding.yml +11 -0
  4. package/dist/claude-coding-worker/driver.d.ts +12 -0
  5. package/dist/claude-coding-worker/driver.js +29 -7
  6. package/dist/claude-coding-worker/main.js +5 -1
  7. package/dist/claude-coding-worker/sdk.js +17 -1
  8. package/dist/claude-plugin/command.d.ts +64 -0
  9. package/dist/claude-plugin/command.js +379 -0
  10. package/dist/cli-help.d.ts +1 -1
  11. package/dist/cli-help.js +2 -1
  12. package/dist/cli.js +71 -3
  13. package/dist/coding/code-host.d.ts +16 -0
  14. package/dist/coding/code-host.js +60 -0
  15. package/dist/coding/mcp-token.d.ts +12 -0
  16. package/dist/coding/mcp-token.js +26 -0
  17. package/dist/coding/protocol.js +2 -0
  18. package/dist/coding/redact.js +6 -1
  19. package/dist/coding/transcript/recorder.d.ts +5 -4
  20. package/dist/coding/transcript/recorder.js +3 -9
  21. package/dist/coding-worker/debug-trace.d.ts +3 -1
  22. package/dist/coding-worker/debug-trace.js +4 -2
  23. package/dist/coding-worker/driver.js +8 -1
  24. package/dist/coding-worker/main.js +5 -1
  25. package/dist/coding-worker/mcp-config.d.ts +27 -0
  26. package/dist/coding-worker/mcp-config.js +32 -0
  27. package/dist/coding-worker/sdk.d.ts +36 -0
  28. package/dist/coding-worker/sdk.js +28 -1
  29. package/dist/coding-worker/types.d.ts +4 -0
  30. package/dist/config/mcp-call-limit.d.ts +8 -0
  31. package/dist/config/mcp-call-limit.js +18 -0
  32. package/dist/config/mcp-servers.d.ts +8 -0
  33. package/dist/config/mcp-servers.js +12 -0
  34. package/dist/config/test-simulators.d.ts +10 -0
  35. package/dist/config/test-simulators.js +28 -0
  36. package/dist/config/tool-timeout.d.ts +1 -0
  37. package/dist/config/tool-timeout.js +18 -0
  38. package/dist/core/coding-mcp.d.ts +36 -0
  39. package/dist/core/coding-mcp.js +253 -0
  40. package/dist/core/coding-repository.d.ts +46 -0
  41. package/dist/core/coding-repository.js +92 -0
  42. package/dist/core/delegation-slots.d.ts +27 -0
  43. package/dist/core/delegation-slots.js +44 -0
  44. package/dist/core/dispatch.d.ts +6 -2
  45. package/dist/core/dispatch.js +39 -11
  46. package/dist/core/engine-native.d.ts +2 -0
  47. package/dist/core/engine-native.js +13 -0
  48. package/dist/core/error-code.js +2 -0
  49. package/dist/core/grants.d.ts +28 -3
  50. package/dist/core/grants.js +46 -4
  51. package/dist/core/host-events.js +7 -3
  52. package/dist/core/host-status.js +2 -2
  53. package/dist/core/issue-status.js +4 -3
  54. package/dist/core/listen-address.d.ts +10 -0
  55. package/dist/core/listen-address.js +14 -0
  56. package/dist/core/mark-ready.d.ts +1 -1
  57. package/dist/core/mark-ready.js +1 -1
  58. package/dist/core/mcp-client/client.d.ts +47 -0
  59. package/dist/core/mcp-client/client.js +141 -0
  60. package/dist/core/mcp-client/jsonrpc.d.ts +15 -0
  61. package/dist/core/mcp-client/jsonrpc.js +54 -0
  62. package/dist/core/mcp-client/results.d.ts +13 -0
  63. package/dist/core/mcp-client/results.js +42 -0
  64. package/dist/core/mcp-servers.d.ts +90 -0
  65. package/dist/core/mcp-servers.js +230 -0
  66. package/dist/core/mcp-tools.d.ts +108 -0
  67. package/dist/core/mcp-tools.js +287 -0
  68. package/dist/core/merge-order-check.js +38 -19
  69. package/dist/core/notification-dispatcher.js +2 -1
  70. package/dist/core/power-idle.d.ts +8 -2
  71. package/dist/core/power-idle.js +17 -3
  72. package/dist/core/power.d.ts +5 -0
  73. package/dist/core/power.js +5 -0
  74. package/dist/core/related-pull-requests.js +26 -10
  75. package/dist/core/retention-days.d.ts +5 -0
  76. package/dist/core/retention-days.js +14 -0
  77. package/dist/core/review-fix.js +2 -2
  78. package/dist/core/review-host-tools.js +3 -1
  79. package/dist/core/run-steps/config.d.ts +25 -0
  80. package/dist/core/run-steps/config.js +40 -0
  81. package/dist/core/run-steps/control-chars.d.ts +16 -0
  82. package/dist/core/run-steps/control-chars.js +26 -0
  83. package/dist/core/run-steps/detail.d.ts +32 -0
  84. package/dist/core/run-steps/detail.js +240 -0
  85. package/dist/core/run-steps/extract.d.ts +52 -0
  86. package/dist/core/run-steps/extract.js +467 -0
  87. package/dist/core/run-steps/ingress.d.ts +51 -0
  88. package/dist/core/run-steps/ingress.js +160 -0
  89. package/dist/core/run-steps/last-step.d.ts +15 -0
  90. package/dist/core/run-steps/last-step.js +35 -0
  91. package/dist/core/run-steps/native.d.ts +48 -0
  92. package/dist/core/run-steps/native.js +164 -0
  93. package/dist/core/run-steps/seen.d.ts +16 -0
  94. package/dist/core/run-steps/seen.js +35 -0
  95. package/dist/core/run-steps/summarize.d.ts +57 -0
  96. package/dist/core/run-steps/summarize.js +304 -0
  97. package/dist/core/run-steps/sweep.d.ts +21 -0
  98. package/dist/core/run-steps/sweep.js +41 -0
  99. package/dist/core/run-steps/types.d.ts +89 -0
  100. package/dist/core/run-steps/types.js +39 -0
  101. package/dist/core/run-steps/wire.d.ts +35 -0
  102. package/dist/core/run-steps/wire.js +47 -0
  103. package/dist/core/run-steps/writer.d.ts +49 -0
  104. package/dist/core/run-steps/writer.js +279 -0
  105. package/dist/core/runner.d.ts +70 -16
  106. package/dist/core/runner.js +296 -74
  107. package/dist/core/secret-broker-config.d.ts +2 -0
  108. package/dist/core/secret-broker-config.js +1 -1
  109. package/dist/core/secret-guard.d.ts +5 -2
  110. package/dist/core/secret-guard.js +22 -7
  111. package/dist/core/secrets.d.ts +11 -1
  112. package/dist/core/secrets.js +27 -1
  113. package/dist/core/serializable-retry.d.ts +18 -0
  114. package/dist/core/serializable-retry.js +43 -0
  115. package/dist/core/tool-admin.d.ts +5 -1
  116. package/dist/core/tool-admin.js +7 -2
  117. package/dist/core/tool-names.d.ts +13 -0
  118. package/dist/core/tool-names.js +20 -0
  119. package/dist/core/webhooks.d.ts +4 -0
  120. package/dist/core/webhooks.js +68 -49
  121. package/dist/core/workflow-run-events.js +2 -1
  122. package/dist/generated/prisma/browser.d.ts +39 -0
  123. package/dist/generated/prisma/client.d.ts +39 -0
  124. package/dist/generated/prisma/commonInputTypes.d.ts +80 -50
  125. package/dist/generated/prisma/enums.d.ts +6 -0
  126. package/dist/generated/prisma/enums.js +5 -0
  127. package/dist/generated/prisma/internal/class.d.ts +77 -0
  128. package/dist/generated/prisma/internal/class.js +4 -4
  129. package/dist/generated/prisma/internal/prismaNamespace.d.ts +632 -1
  130. package/dist/generated/prisma/internal/prismaNamespace.js +94 -3
  131. package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +98 -0
  132. package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +94 -3
  133. package/dist/generated/prisma/models/Agent.d.ts +372 -1
  134. package/dist/generated/prisma/models/AgentMcpServer.d.ts +1412 -0
  135. package/dist/generated/prisma/models/AgentMcpServer.js +1 -0
  136. package/dist/generated/prisma/models/AgentRepository.d.ts +40 -1
  137. package/dist/generated/prisma/models/AuthUser.d.ts +1 -1
  138. package/dist/generated/prisma/models/CodingAgentProfile.d.ts +0 -7
  139. package/dist/generated/prisma/models/CodingProxySession.d.ts +159 -2
  140. package/dist/generated/prisma/models/CodingRun.d.ts +76 -1
  141. package/dist/generated/prisma/models/HostProvider.d.ts +2176 -0
  142. package/dist/generated/prisma/models/HostProvider.js +1 -0
  143. package/dist/generated/prisma/models/HostProviderCredential.d.ts +1285 -0
  144. package/dist/generated/prisma/models/HostProviderCredential.js +1 -0
  145. package/dist/generated/prisma/models/HostProviderOperation.d.ts +1470 -0
  146. package/dist/generated/prisma/models/HostProviderOperation.js +1 -0
  147. package/dist/generated/prisma/models/HostProviderTrustedAccount.d.ts +1145 -0
  148. package/dist/generated/prisma/models/HostProviderTrustedAccount.js +1 -0
  149. package/dist/generated/prisma/models/McpServer.d.ts +1500 -0
  150. package/dist/generated/prisma/models/McpServer.js +1 -0
  151. package/dist/generated/prisma/models/NativeGatewayCall.d.ts +39 -1
  152. package/dist/generated/prisma/models/Principal.d.ts +167 -0
  153. package/dist/generated/prisma/models/ResourceGrant.d.ts +1 -1
  154. package/dist/generated/prisma/models/Run.d.ts +503 -1
  155. package/dist/generated/prisma/models/RunStep.d.ts +1453 -0
  156. package/dist/generated/prisma/models/RunStep.js +1 -0
  157. package/dist/generated/prisma/models/Secret.d.ts +387 -0
  158. package/dist/generated/prisma/models/Tool.d.ts +223 -1
  159. package/dist/generated/prisma/models.d.ts +7 -0
  160. package/dist/help/search.js +12 -3
  161. package/dist/help-index.json +812 -52
  162. package/dist/import/create.js +16 -1
  163. package/dist/import/neutral-schema.d.ts +1 -0
  164. package/dist/import/neutral-schema.js +2 -0
  165. package/dist/mcp/auth/access.d.ts +1 -0
  166. package/dist/mcp/auth/grants-cli.d.ts +6 -4
  167. package/dist/mcp/auth/grants-cli.js +42 -3
  168. package/dist/mcp/auth/resource-server.d.ts +3 -1
  169. package/dist/mcp/auth/resource-server.js +17 -2
  170. package/dist/mcp/context.d.ts +6 -0
  171. package/dist/mcp/index.js +21 -0
  172. package/dist/mcp/tools/agents.js +135 -20
  173. package/dist/mcp/tools/grants.js +66 -28
  174. package/dist/mcp/tools/host-providers.d.ts +2 -0
  175. package/dist/mcp/tools/host-providers.js +215 -0
  176. package/dist/mcp/tools/mcp-servers.d.ts +26 -0
  177. package/dist/mcp/tools/mcp-servers.js +481 -0
  178. package/dist/mcp/tools/power.js +2 -2
  179. package/dist/mcp/tools/repositories.js +50 -10
  180. package/dist/mcp/tools/run-access.d.ts +7 -0
  181. package/dist/mcp/tools/run-access.js +14 -0
  182. package/dist/mcp/tools/run-steps.d.ts +16 -0
  183. package/dist/mcp/tools/run-steps.js +85 -0
  184. package/dist/mcp/tools/runs.js +186 -18
  185. package/dist/mcp/tools/secret-elicitation.d.ts +2 -0
  186. package/dist/mcp/tools/secret-elicitation.js +25 -2
  187. package/dist/mcp/tools/secrets.js +38 -4
  188. package/dist/mcp/tools/tools.d.ts +6 -4
  189. package/dist/mcp/tools/tools.js +40 -8
  190. package/dist/mcp/tools/trigger.js +28 -6
  191. package/dist/mcp/transport/http-limits.d.ts +2 -0
  192. package/dist/mcp/transport/http-limits.js +6 -2
  193. package/dist/mcp/webhooks/ingress.js +10 -0
  194. package/dist/native-worker/gateway-server.d.ts +18 -1
  195. package/dist/native-worker/gateway-server.js +38 -9
  196. package/dist/native-worker/gateway.d.ts +5 -0
  197. package/dist/native-worker/gateway.js +13 -1
  198. package/dist/native-worker/http-server.d.ts +6 -4
  199. package/dist/native-worker/http-server.js +53 -8
  200. package/dist/native-worker/ledger.d.ts +6 -2
  201. package/dist/native-worker/ledger.js +5 -4
  202. package/dist/native-worker/protocol.d.ts +1 -0
  203. package/dist/native-worker/protocol.js +4 -1
  204. package/dist/providers/coding-proxy/capability-hash.d.ts +2 -0
  205. package/dist/providers/coding-proxy/capability-hash.js +5 -0
  206. package/dist/providers/coding-proxy/mcp-route.d.ts +34 -0
  207. package/dist/providers/coding-proxy/mcp-route.js +166 -0
  208. package/dist/providers/coding-proxy/memory-ledger.d.ts +2 -0
  209. package/dist/providers/coding-proxy/memory-ledger.js +16 -0
  210. package/dist/providers/coding-proxy/prisma-ledger.d.ts +1 -0
  211. package/dist/providers/coding-proxy/prisma-ledger.js +36 -2
  212. package/dist/providers/coding-proxy/proxy.d.ts +33 -1
  213. package/dist/providers/coding-proxy/proxy.js +266 -75
  214. package/dist/providers/coding-proxy/run-step-forwarder.d.ts +42 -0
  215. package/dist/providers/coding-proxy/run-step-forwarder.js +206 -0
  216. package/dist/providers/coding-proxy/runtime.d.ts +13 -0
  217. package/dist/providers/coding-proxy/runtime.js +87 -4
  218. package/dist/providers/coding-proxy/server.d.ts +3 -0
  219. package/dist/providers/coding-proxy/server.js +10 -0
  220. package/dist/providers/coding-proxy/types.d.ts +18 -0
  221. package/dist/providers/engine/types.d.ts +26 -0
  222. package/dist/providers/executor/composition.js +5 -0
  223. package/dist/providers/executor/container.d.ts +47 -1
  224. package/dist/providers/executor/container.js +84 -8
  225. package/dist/providers/host-tools/invoke.d.ts +37 -0
  226. package/dist/providers/host-tools/invoke.js +117 -0
  227. package/dist/providers/host-tools/operations.d.ts +65 -0
  228. package/dist/providers/host-tools/operations.js +149 -0
  229. package/dist/providers/host-tools/registry.d.ts +79 -0
  230. package/dist/providers/host-tools/registry.js +362 -0
  231. package/dist/providers/host-tools/request-guard.d.ts +17 -0
  232. package/dist/providers/host-tools/request-guard.js +99 -0
  233. package/dist/providers/host-tools/self-test.d.ts +64 -0
  234. package/dist/providers/host-tools/self-test.js +142 -0
  235. package/dist/providers/jobs/docker-isolation.d.ts +2 -2
  236. package/dist/providers/jobs/docker-isolation.js +30 -8
  237. package/dist/providers/jobs/docker.d.ts +8 -0
  238. package/dist/providers/jobs/docker.js +33 -9
  239. package/dist/providers/jobs/kubernetes-isolation.d.ts +3 -1
  240. package/dist/providers/jobs/kubernetes-isolation.js +15 -2
  241. package/dist/providers/jobs/kubernetes.d.ts +13 -0
  242. package/dist/providers/jobs/kubernetes.js +28 -5
  243. package/dist/providers/jobs/types.d.ts +6 -0
  244. package/dist/providers/review-host/types.d.ts +6 -4
  245. package/dist/providers/review-host/types.js +1 -1
  246. package/dist/providers/vcs/git.d.ts +6 -2
  247. package/dist/providers/vcs/git.js +15 -4
  248. package/dist/providers/vcs/github.d.ts +2 -71
  249. package/dist/providers/vcs/github.js +8 -11
  250. package/dist/providers/vcs/index.d.ts +1 -1
  251. package/dist/providers/vcs/index.js +1 -1
  252. package/dist/providers/vcs/remote.d.ts +4 -0
  253. package/dist/providers/vcs/remote.js +7 -1
  254. package/dist/providers/vcs/routing.d.ts +12 -6
  255. package/dist/providers/vcs/routing.js +21 -6
  256. package/dist/providers/vcs/types.d.ts +71 -3
  257. package/dist/quickstart/index.d.ts +5 -0
  258. package/dist/quickstart/index.js +21 -0
  259. package/dist/quickstart-images.json +1 -1
  260. package/dist/sandbox/eval-core.d.ts +12 -2
  261. package/dist/sandbox/eval-core.js +20 -4
  262. package/dist/sandbox/host-functions.d.ts +4 -0
  263. package/dist/sandbox/host-functions.js +7 -2
  264. package/dist/sandbox/limits.d.ts +4 -1
  265. package/dist/sandbox/limits.js +4 -1
  266. package/dist/sandbox/run-in-sandbox.d.ts +6 -0
  267. package/dist/sandbox/run-in-sandbox.js +14 -2
  268. package/dist/sandbox/safe-fetch.d.ts +2 -0
  269. package/dist/sandbox/safe-fetch.js +1 -1
  270. package/dist/sandbox/secret-broker.d.ts +1 -0
  271. package/dist/sandbox/secret-broker.js +1 -1
  272. package/dist/sandbox/tool-timeout.d.ts +32 -0
  273. package/dist/sandbox/tool-timeout.js +52 -0
  274. package/dist/sandbox/user-tool.d.ts +8 -1
  275. package/dist/sandbox/user-tool.js +9 -3
  276. package/dist/serve.js +26 -18
  277. package/dist/viewer/api-schema.d.ts +66 -0
  278. package/dist/viewer/api-schema.js +31 -0
  279. package/dist/viewer/graph.js +2 -1
  280. package/dist/viewer/openapi.js +2 -1
  281. package/dist/viewer/run-detail.d.ts +2 -0
  282. package/dist/viewer/run-detail.js +26 -1
  283. package/dist/wardby-bin.js +5 -0
  284. package/docs/README.md +11 -1
  285. package/docs/agent-recipes.md +37 -4
  286. package/docs/architecture-runtime.md +1 -1
  287. package/docs/claude-code-plugin.md +208 -0
  288. package/docs/code-review-agents.md +6 -0
  289. package/docs/coding-agent-setup.md +94 -1
  290. package/docs/coding-worker-byo-images.md +4 -2
  291. package/docs/coding-worker-isolation.md +94 -7
  292. package/docs/debugging-agent-runs.md +354 -0
  293. package/docs/getting-started-gke.md +33 -11
  294. package/docs/getting-started-identity-provider.md +43 -37
  295. package/docs/getting-started.md +8 -0
  296. package/docs/host-providers.md +294 -0
  297. package/docs/mcp-servers.md +263 -0
  298. package/docs/native-sandbox.md +31 -9
  299. package/docs/security-deployment.md +161 -31
  300. package/docs/tool-secrets.md +3 -0
  301. package/docs/viewer-api.md +27 -12
  302. package/docs/viewer-api.openapi.yml +78 -2
  303. package/help/admin-viewer.md +5 -0
  304. package/help/agent-recipes.md +33 -2
  305. package/help/brokered-secrets.md +3 -0
  306. package/help/build-claude-code-image.md +1 -1
  307. package/help/claude-code-run-pane.md +63 -0
  308. package/help/deploy-gke.md +5 -0
  309. package/help/errors/coding-mcp-gateway-unavailable.md +46 -0
  310. package/help/errors/coding-mcp-pins-unavailable.md +35 -0
  311. package/help/errors/database-auth-invalid.md +1 -1
  312. package/help/errors/host-provider-error.md +40 -0
  313. package/help/errors/host-tool-failed.md +36 -0
  314. package/help/errors/mcp-call-limit.md +22 -0
  315. package/help/errors/mcp-call-timeout.md +22 -0
  316. package/help/errors/mcp-server-auth-failed.md +22 -0
  317. package/help/errors/mcp-server-credentials-unavailable.md +45 -0
  318. package/help/errors/mcp-server-error.md +28 -0
  319. package/help/errors/mcp-server-unreachable.md +22 -0
  320. package/help/errors/mcp-servers-off.md +21 -0
  321. package/help/errors/mcp-tool-not-allowed.md +26 -0
  322. package/help/errors/mcp-tool-removed.md +22 -0
  323. package/help/errors/mcp-unavailable.md +25 -0
  324. package/help/errors/native-sandbox-capacity.md +1 -1
  325. package/help/errors/native-sandbox-deadline-exceeded.md +1 -1
  326. package/help/errors/native-sandbox-docker-failed.md +1 -1
  327. package/help/errors/native-sandbox-gateway-error.md +1 -1
  328. package/help/errors/native-sandbox-gateway-unavailable.md +1 -1
  329. package/help/errors/native-sandbox-image-not-pinned.md +1 -1
  330. package/help/errors/native-sandbox-isolation-mismatch.md +1 -1
  331. package/help/errors/native-sandbox-network-unenforced.md +1 -1
  332. package/help/errors/native-sandbox-requires-catalog.md +1 -1
  333. package/help/errors/native-sandbox-unavailable.md +1 -1
  334. package/help/errors/native-sandbox-warm-delivery-failed.md +1 -1
  335. package/help/errors/native-sandbox-worker-exited.md +1 -1
  336. package/help/errors/native-sandbox-worker-lost.md +1 -1
  337. package/help/errors/native-sandbox-worker-unready.md +1 -1
  338. package/help/errors/repository-not-listed.md +36 -0
  339. package/help/errors/repository-required.md +36 -0
  340. package/help/errors/tool-timeout.md +38 -0
  341. package/help/host-providers.md +40 -0
  342. package/help/identity-and-access.md +4 -1
  343. package/help/low-power-mode.md +19 -8
  344. package/help/mcp-servers.md +73 -0
  345. package/help/mcp.md +9 -2
  346. package/help/native-capabilities.md +27 -1
  347. package/help/native-sandbox.md +1 -1
  348. package/help/operating-agents.md +16 -0
  349. package/help/related-pull-requests.md +7 -3
  350. package/help/repo-instructions-and-skills.md +3 -1
  351. package/help/resume-runs-locally.md +7 -0
  352. package/help/run-steps.md +140 -0
  353. package/help/stop-run.md +1 -1
  354. package/help/troubleshooting/coding-workers.md +9 -1
  355. package/help/troubleshooting/debugging-agent-runs.md +29 -0
  356. package/package.json +2 -1
  357. package/prisma/migrations/20261102000000_host_providers/migration.sql +56 -0
  358. package/prisma/migrations/20261104000000_coding_agent_repository_list/migration.sql +5 -0
  359. package/prisma/migrations/20261106000000_tool_timeout/migration.sql +4 -0
  360. package/prisma/migrations/20261107000000_mcp_servers/migration.sql +33 -0
  361. package/prisma/migrations/20261108000000_coding_mcp/migration.sql +5 -0
  362. package/prisma/migrations/20261109000000_task_run_index/migration.sql +3 -0
  363. package/prisma/migrations/20261110000000_run_steps/migration.sql +26 -0
  364. package/prisma/migrations/20261111000000_run_steps_viewer_notify/migration.sql +59 -0
  365. package/prisma/migrations/20261112000000_run_steps_detail/migration.sql +9 -0
  366. package/prisma/schema.prisma +211 -14
  367. package/scripts/git-askpass.sh +1 -1
@@ -7,6 +7,8 @@
7
7
  */
8
8
  import { Prisma } from "#prisma";
9
9
  import { z } from "zod";
10
+ import { RUN_STEPS_LEVELS, loadRunStepsLevel, runStepsAboveCeilingWarning, } from "../../core/run-steps/config.js";
11
+ import { codeProviderOf } from "../../coding/code-host.js";
10
12
  import { CodingProfilePatchSchema, CodingProfileSchema } from "../../coding/profile.js";
11
13
  import { DEFAULT_CLAUDE_MAX_OUTPUT_TOKENS, DEFAULT_CLAUDE_MAX_TURNS, MAX_CLAUDE_MAX_OUTPUT_TOKENS, MIN_CLAUDE_MAX_OUTPUT_TOKENS, DEFAULT_DEBUG_TRACE_MAX_MB, MAX_CODING_TURNS, MAX_DEBUG_TRACE_MAX_MB, isLocalRepository, } from "../../coding/protocol.js";
12
14
  import { loadBlobStoreConfig, loadContainerExecutorConfig } from "../../config/providers.js";
@@ -18,12 +20,15 @@ import { ModelUnavailableError } from "../../providers/llm/catalog-types.js";
18
20
  import { currentModelCatalog } from "../../providers/llm/catalog-store.js";
19
21
  import { validateCronExpression } from "../../core/cron.js";
20
22
  import { scheduleStartFields } from "../../core/schedule-start.js";
23
+ import { preferIndexScans, withSerializableRetry } from "../../core/serializable-retry.js";
21
24
  import { modelSupportedEfforts } from "../../providers/llm/routing.js";
25
+ import { agentMcpServerViews, codexUnavailableMcpTools, codexWriteToolsWarning } from "./mcp-servers.js";
26
+ import { isCodexCoding } from "../../core/mcp-tools.js";
22
27
  import { PROJECT_KEY } from "../../providers/issue-tracker/types.js";
23
28
  import { LLM_EFFORT_LEVELS, isLlmEffort } from "../../providers/llm/types.js";
24
29
  import { requireReadableBudgetGroup } from "../auth/ownership.js";
25
30
  import { agentAccessResolver, agentNotFound, assertAgentAccess, readableAgentsWhere, requireAgentAccess, } from "../auth/access.js";
26
- import { EVERYONE_KEY, atLeast, canDelegate, deleteGrantsFor, effectiveAccess, isPublicWritable, } from "../../core/grants.js";
31
+ import { EVERYONE_KEY, atLeast, canDelegate, deleteGrantsFor, effectiveAccess, isPublicWritable, filterUnusableMcpServers, } from "../../core/grants.js";
27
32
  import { projectTool } from "./tools.js";
28
33
  import { withZod3ToolWarnings } from "../../core/zod3-window.js";
29
34
  import { requireAnyScope, requireScope } from "../auth/resource-server.js";
@@ -120,6 +125,27 @@ function withProfileWarnings(agent) {
120
125
  const warnings = profileWarnings(profile, now);
121
126
  return warnings.length ? { ...shown, warnings } : shown;
122
127
  }
128
+ // No JSON-schema enum on purpose: the zod check then refuses a bad value naming off, summary and detail.
129
+ const RUN_STEPS_DESCRIPTION = "Run steps level for this agent's runs (owner only): off records nothing, summary one line per tool call or message " +
130
+ "(the default), detail also the tool input, the end of each tool's output and the model's text, readable only by the " +
131
+ "agent's owner and the run's starter. A run records the lower of this and the instance's WARDBY_RUN_STEPS, fixed " +
132
+ "when it starts. detail stores tool output: secrets the run was given are removed, but a secret that exists only in " +
133
+ "a file the run read can remain. Null clears it (summary).";
134
+ /** For a warning only: an invalid WARDBY_RUN_STEPS stopped start-up already; never fails the tool call. */
135
+ function runStepsCeiling() {
136
+ try {
137
+ return loadRunStepsLevel(process.env);
138
+ }
139
+ catch {
140
+ return undefined;
141
+ }
142
+ }
143
+ /** An agent as returned to operators: a warning when its runSteps is above the instance's ceiling. */
144
+ function withRunStepsWarning(agent) {
145
+ const ceiling = runStepsCeiling();
146
+ const warning = ceiling ? runStepsAboveCeilingWarning(agent.runSteps, ceiling) : undefined;
147
+ return warning ? { ...agent, warnings: [...(agent.warnings ?? []), warning] } : agent;
148
+ }
123
149
  const agentFields = {
124
150
  name: z.string().trim().min(1).max(MAX_AGENT_NAME_CHARS),
125
151
  systemPrompt: z.string().min(1).max(MAX_SYSTEM_PROMPT_CHARS),
@@ -136,6 +162,7 @@ const agentFields = {
136
162
  budgetGroupId: z.string().min(1).max(128),
137
163
  memoryEnabled: z.boolean(),
138
164
  effort: z.enum(LLM_EFFORT_LEVELS),
165
+ runSteps: z.enum(RUN_STEPS_LEVELS),
139
166
  nativeExecutionMode: z.enum(NATIVE_EXECUTION_MODES),
140
167
  defectProjectKey: z.string().regex(PROJECT_KEY, "must be an upper-case Jira project key"),
141
168
  defectIssueType: z.string().trim().min(1).max(100),
@@ -171,6 +198,7 @@ const CreateAgentSchema = z
171
198
  budgetGroupId: agentFields.budgetGroupId.optional(),
172
199
  memoryEnabled: agentFields.memoryEnabled.default(false),
173
200
  effort: agentFields.effort.optional(),
201
+ runSteps: agentFields.runSteps.optional(),
174
202
  nativeExecutionMode: agentFields.nativeExecutionMode.optional(),
175
203
  defectProjectKey: agentFields.defectProjectKey.optional(),
176
204
  defectIssueType: agentFields.defectIssueType.optional(),
@@ -229,6 +257,7 @@ const UpdateAgentSchema = z
229
257
  budgetGroupId: agentFields.budgetGroupId.nullable().optional(),
230
258
  memoryEnabled: agentFields.memoryEnabled.optional(),
231
259
  effort: agentFields.effort.nullable().optional(),
260
+ runSteps: agentFields.runSteps.nullable().optional(),
232
261
  nativeExecutionMode: agentFields.nativeExecutionMode.optional(),
233
262
  defectProjectKey: agentFields.defectProjectKey.nullable().optional(),
234
263
  defectIssueType: agentFields.defectIssueType.nullable().optional(),
@@ -491,7 +520,7 @@ export function registerAgentTools(mcp) {
491
520
  type: "integer",
492
521
  minimum: 1,
493
522
  maximum: MAX_DELEGATIONS_PER_RUN,
494
- description: "Native agents with sub-agents: how many delegate_to_* calls one run may make, each to a different sub-agent " +
523
+ description: "Native agents with sub-agents: how many delegate_to_* calls one run may make, to each sub-agent and repository at most once " +
495
524
  "(one after another, or together with parallelDelegations). Default 1: route to exactly one.",
496
525
  },
497
526
  parallelDelegations: { type: "boolean", description: PARALLEL_DELEGATIONS_DESCRIPTION },
@@ -506,6 +535,7 @@ export function registerAgentTools(mcp) {
506
535
  enum: [...LLM_EFFORT_LEVELS],
507
536
  description: "Reasoning effort for native agents. Unset uses the provider default.",
508
537
  },
538
+ runSteps: { type: "string", description: RUN_STEPS_DESCRIPTION },
509
539
  nativeExecutionMode: {
510
540
  type: "string",
511
541
  enum: [...NATIVE_EXECUTION_MODES],
@@ -550,7 +580,7 @@ export function registerAgentTools(mcp) {
550
580
  const authorization = codingProfile
551
581
  ? await authorizeRepositoryForSet(ctx, {
552
582
  ownerId: ctx.principal.id,
553
- provider: "github",
583
+ provider: codeProviderOf(codingProfile.repository),
554
584
  repository: codingProfile.repository,
555
585
  kind: "coding",
556
586
  adminOverride: repositoryAdminOverride === true,
@@ -571,7 +601,7 @@ export function registerAgentTools(mcp) {
571
601
  },
572
602
  include: { codingProfile: true },
573
603
  });
574
- return textResult(withProfileWarnings(withOperatorMode(agent)));
604
+ return textResult(withRunStepsWarning(withProfileWarnings(withOperatorMode(agent))));
575
605
  },
576
606
  });
577
607
  mcp.registerTool({
@@ -597,7 +627,7 @@ export function registerAgentTools(mcp) {
597
627
  type: "integer",
598
628
  minimum: 1,
599
629
  maximum: MAX_DELEGATIONS_PER_RUN,
600
- description: "Native agents with sub-agents: how many delegate_to_* calls one run may make, each to a different sub-agent " +
630
+ description: "Native agents with sub-agents: how many delegate_to_* calls one run may make, to each sub-agent and repository at most once " +
601
631
  "(one after another, or together with parallelDelegations). Default 1: route to exactly one.",
602
632
  },
603
633
  parallelDelegations: { type: "boolean", description: PARALLEL_DELEGATIONS_DESCRIPTION },
@@ -612,6 +642,7 @@ export function registerAgentTools(mcp) {
612
642
  enum: [...LLM_EFFORT_LEVELS, null],
613
643
  description: "Reasoning effort for native agents. Null clears it back to the provider default.",
614
644
  },
645
+ runSteps: { type: ["string", "null"], description: RUN_STEPS_DESCRIPTION },
615
646
  nativeExecutionMode: {
616
647
  type: "string",
617
648
  enum: [...NATIVE_EXECUTION_MODES],
@@ -726,19 +757,21 @@ export function registerAgentTools(mcp) {
726
757
  // and budget-group membership direct what runs do with the owner's
727
758
  // repository access and money: owner-only, like the repository itself
728
759
  // (review I1/M1/M6). Write keeps name, prompt, model, budget amount,
729
- // turns, effort, memory and schedule.
760
+ // turns, effort, memory and schedule. The run steps level is the owner's too:
761
+ // detail records tool output that only the owner and the run's starter may read.
730
762
  const ownerOnlyChange = (existingKind) => args.codingProfile !== undefined ||
731
763
  (args.kind !== undefined && args.kind !== existingKind) ||
732
- args.budgetGroupId !== undefined;
764
+ args.budgetGroupId !== undefined ||
765
+ args.runSteps !== undefined;
733
766
  const refuseOwnerOnly = () => {
734
- throw new McpError(403, `Agent "${args.id}": only its owner can change its coding profile, kind or budget group.`);
767
+ throw new McpError(403, `Agent "${args.id}": only its owner can change its coding profile, kind, budget group or run steps level.`);
735
768
  };
736
769
  if (!adminEdit && !isOwner && ownerOnlyChange(before.kind))
737
770
  refuseOwnerOnly();
738
771
  const authorization = plannedRepository !== null && repositoryChanges(before)
739
772
  ? await authorizeRepositoryForSet(ctx, {
740
773
  ownerId: before.ownerId,
741
- provider: "github",
774
+ provider: codeProviderOf(plannedRepository),
742
775
  repository: plannedRepository,
743
776
  kind: "coding",
744
777
  adminOverride: args.repositoryAdminOverride === true,
@@ -774,6 +807,17 @@ export function registerAgentTools(mcp) {
774
807
  await requireReadableBudgetGroup(tx, args.budgetGroupId, ctx.principal.id);
775
808
  }
776
809
  const nextKind = args.kind ?? existing.kind;
810
+ if (nextKind !== existing.kind) {
811
+ // A write link without triggers is a coding agent's extra repository
812
+ // and a native agent's plain write access; changing kind would
813
+ // silently reinterpret it.
814
+ const reinterpreted = await tx.agentRepository.count({
815
+ where: { agentId: args.id, access: "write", triggers: { isEmpty: true } },
816
+ });
817
+ if (reinterpreted > 0) {
818
+ throw new McpError(400, `Agent "${args.id}" has write repository links without triggers, which mean different things on native and coding agents; unlink them before changing its kind.`);
819
+ }
820
+ }
777
821
  // A coding repository is an owner binding (agentOwnerBindings): an
778
822
  // agent everyone can write never gets one, as attach_* refuse theirs.
779
823
  if (((nextKind === "coding" && existing.kind !== "coding") || repositoryChanges(existing)) &&
@@ -892,16 +936,59 @@ export function registerAgentTools(mcp) {
892
936
  by: ctx.principal.id,
893
937
  }, "coding package audit mode set");
894
938
  }
895
- return textResult(withProfileWarnings(withOperatorMode(agent)));
939
+ const shown = withRunStepsWarning(withProfileWarnings(withOperatorMode(agent)));
940
+ // Becoming Codex (a provider switch, or a native agent becoming coding) leaves out every attached
941
+ // MCP tool the server doesn't mark read-only: say which, so the owner isn't surprised by a run.
942
+ const wasCodex = before.kind === "coding" && isCodexCoding(before.codingProfile?.provider);
943
+ if (agent.kind === "coding" && isCodexCoding(agent.codingProfile?.provider) && !wasCodex) {
944
+ const writers = await codexUnavailableMcpTools(ctx.db, agent.id);
945
+ if (writers.length > 0) {
946
+ return textResult({ ...shown, warnings: [...(shown.warnings ?? []), codexWriteToolsWarning(writers)] });
947
+ }
948
+ }
949
+ return textResult(shown);
896
950
  },
897
951
  });
898
952
  mcp.registerTool({
899
953
  name: "list_agents",
900
954
  scope: "agents:read",
901
- description: "Lists the agents you can see. Each row has an `access` field, your level on that agent: owner > write > execute > read. When showing the agents as a table, include `access` as a column.",
902
- inputSchema: { type: "object", properties: {}, additionalProperties: false },
903
- handler: async (_args, ctx) => {
904
- const [where, accessOf] = await Promise.all([readableAgentsWhere(ctx), agentAccessResolver(ctx)]);
955
+ description: "Lists the agents you can see. Each row has an `access` field, your level on that agent: owner > write > execute > read. When showing the agents as a table, include `access` as a column. " +
956
+ "summary: true returns only id, name, kind, model, codingProvider, budgetUsd, ownerId, runSteps and access per agent, for listings that don't need the full configuration. " +
957
+ "all: true lists every agent in the deployment (admins only, admin:view); an agent you have no grant on shows access `none`.",
958
+ inputSchema: {
959
+ type: "object",
960
+ properties: { summary: { type: "boolean" }, all: { type: "boolean" } },
961
+ additionalProperties: false,
962
+ },
963
+ handler: async (args, ctx) => {
964
+ if (args.all === true && !ctx.operator)
965
+ requireScope(ctx, ctx.canonicalUri, "admin:view");
966
+ const [where, accessOf] = await Promise.all([
967
+ args.all === true ? Promise.resolve({}) : readableAgentsWhere(ctx),
968
+ agentAccessResolver(ctx),
969
+ ]);
970
+ if (args.summary === true) {
971
+ const agents = await ctx.db.agent.findMany({
972
+ where,
973
+ select: {
974
+ id: true,
975
+ name: true,
976
+ kind: true,
977
+ model: true,
978
+ budgetUsd: true,
979
+ ownerId: true,
980
+ runSteps: true,
981
+ codingProfile: { select: { provider: true } },
982
+ },
983
+ orderBy: { name: "asc" },
984
+ });
985
+ return textResult(agents.map(({ codingProfile, budgetUsd, ...agent }) => ({
986
+ ...agent,
987
+ codingProvider: codingProfile?.provider ?? null,
988
+ budgetUsd: Number(budgetUsd),
989
+ access: accessOf(agent),
990
+ })));
991
+ }
905
992
  const agents = await ctx.db.agent.findMany({ where, include: { codingProfile: true } });
906
993
  return textResult(agents.map((agent) => ({ ...withOperatorMode(agent), access: accessOf(agent) })));
907
994
  },
@@ -923,8 +1010,9 @@ export function registerAgentTools(mcp) {
923
1010
  // A7: a tool's code and schema are its owner's, whoever can read the
924
1011
  // agent (the agent's owner included). Capabilities stay visible: config.
925
1012
  return textResult({
926
- ...withProfileWarnings(withOperatorMode(agent)),
1013
+ ...withRunStepsWarning(withProfileWarnings(withOperatorMode(agent))),
927
1014
  access,
1015
+ mcpServers: await agentMcpServerViews(ctx.db, agent.id),
928
1016
  // Zod 3 window: the caller's own tools carry their warnings (see core/zod3-window.ts).
929
1017
  tools: await Promise.all(agent.tools.map(async (attachment) => ({
930
1018
  ...attachment,
@@ -974,7 +1062,7 @@ export function registerAgentTools(mcp) {
974
1062
  mcp.registerTool({
975
1063
  name: "make_owner",
976
1064
  scope: "agents:admin",
977
- 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.",
1065
+ description: "Admins: gives an agent a new owner (an existing principal id). Secret, datastore and MCP server 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.",
978
1066
  inputSchema: {
979
1067
  type: "object",
980
1068
  additionalProperties: false,
@@ -996,7 +1084,9 @@ export function registerAgentTools(mcp) {
996
1084
  const principal = await ctx.db.principal.findUnique({ where: { id: newOwner } });
997
1085
  if (!principal)
998
1086
  throw new McpError(400, `Principal "${newOwner}" not found.`);
999
- const result = await ctx.db.$transaction(async (tx) => {
1087
+ // Bounded retry: a concurrent grant or attach on the same rows aborts one side (Serializable).
1088
+ const result = await withSerializableRetry(() => ctx.db.$transaction(async (tx) => {
1089
+ await preferIndexScans(tx);
1000
1090
  const agent = await tx.agent.findUnique({ where: { id: args.agentId }, include: { codingProfile: true } });
1001
1091
  if (!agent)
1002
1092
  throw agentNotFound(args.agentId);
@@ -1015,7 +1105,10 @@ export function registerAgentTools(mcp) {
1015
1105
  where: { agentId: agent.id, authorizedVia: approved },
1016
1106
  orderBy: { repository: "asc" },
1017
1107
  });
1018
- await tx.agentRepository.updateMany({ where: { agentId: agent.id, authorizedVia: approved }, data: reset });
1108
+ await tx.agentRepository.updateMany({
1109
+ where: { agentId: agent.id, authorizedVia: approved },
1110
+ data: reset,
1111
+ });
1019
1112
  revoked.push(...links.map((l) => l.repository));
1020
1113
  const profile = await tx.codingAgentProfile.updateMany({
1021
1114
  where: { agentId: agent.id, repositoryAuthorizedVia: approved },
@@ -1072,9 +1165,31 @@ export function registerAgentTools(mcp) {
1072
1165
  where: { agentId: agent.id, datastoreId: { in: foreignDatastores.map((b) => b.datastoreId) } },
1073
1166
  });
1074
1167
  }
1168
+ // MCP server attachments: the server's brokered secrets act for its
1169
+ // owner, so a server the new owner neither owns nor holds use on is detached.
1170
+ const mcpServers = await tx.agentMcpServer.findMany({
1171
+ where: { agentId: agent.id },
1172
+ include: { server: { select: { id: true, name: true, ownerId: true } } },
1173
+ orderBy: { serverId: "asc" },
1174
+ });
1175
+ const foreignMcpServers = await filterUnusableMcpServers(tx, () => newOwner, mcpServers);
1176
+ if (foreignMcpServers.length > 0) {
1177
+ await tx.agentMcpServer.deleteMany({
1178
+ where: { agentId: agent.id, serverId: { in: foreignMcpServers.map((b) => b.serverId) } },
1179
+ });
1180
+ }
1075
1181
  const bindingsRemoved = [
1076
1182
  ...foreignSecrets.map((b) => ({ kind: "secret", boundName: b.boundName, resourceId: b.secretId })),
1077
- ...foreignDatastores.map((b) => ({ kind: "datastore", boundName: b.boundName, resourceId: b.datastoreId })),
1183
+ ...foreignDatastores.map((b) => ({
1184
+ kind: "datastore",
1185
+ boundName: b.boundName,
1186
+ resourceId: b.datastoreId,
1187
+ })),
1188
+ ...foreignMcpServers.map((b) => ({
1189
+ kind: "mcp_server",
1190
+ boundName: b.server.name,
1191
+ resourceId: b.serverId,
1192
+ })),
1078
1193
  ];
1079
1194
  // Tool capabilities: rows aren't rewritten; the runner ignores any
1080
1195
  // the new owner never stamped (capabilitiesGrantedById).
@@ -1132,7 +1247,7 @@ export function registerAgentTools(mcp) {
1132
1247
  everyoneGrant,
1133
1248
  webhooksInactive,
1134
1249
  };
1135
- }, { isolationLevel: Prisma.TransactionIsolationLevel.Serializable });
1250
+ }, { isolationLevel: Prisma.TransactionIsolationLevel.Serializable }));
1136
1251
  return textResult(result);
1137
1252
  },
1138
1253
  });
@@ -1,18 +1,22 @@
1
1
  /**
2
2
  * grant_access / revoke_access / list_access: the owner's sharing controls
3
- * (resource-sharing grants spec §3.11). Phase 1 shares agents only; other
4
- * resource types are refused as "not shareable yet".
3
+ * (resource-sharing grants spec §3.11). Agents and MCP servers are shareable;
4
+ * other resource types are refused as "not shareable yet".
5
5
  *
6
6
  * - Only the resource's owner (or the stdio operator) grants and revokes.
7
7
  * Admins get no implicit access: they may only list_access, for incident
8
8
  * response ("who can reach this?"), and reassign with make_owner.
9
9
  * - One grant per resource and grantee, so granting again replaces the
10
10
  * level (also downgrades). Everyone grants are capped below write.
11
- * - Takes effect on the next check; runs already in flight keep going.
11
+ * - Takes effect on the next check; runs already in flight keep going. For
12
+ * MCP servers the check is every tool call: revoking or lowering a grant
13
+ * below use also removes the attachments of agents whose owner can no
14
+ * longer use the server, in the same transaction.
12
15
  */
13
16
  import { z } from "zod";
14
17
  import { Prisma } from "#prisma";
15
- import { EVERYONE_KEY, EVERYONE_MAX, LEVELS, agentOwnerBindings, SHAREABLE_TYPES, accessRank, atLeast, isLevel, isResourceType, principalGranteeKey, } from "../../core/grants.js";
18
+ import { EVERYONE_KEY, EVERYONE_MAX, LEVELS, agentOwnerBindings, SHAREABLE_TYPES, accessRank, atLeast, isLevel, isResourceType, principalGranteeKey, pruneUnusableMcpAttachments, } from "../../core/grants.js";
19
+ import { preferIndexScans, withSerializableRetry } from "../../core/serializable-retry.js";
16
20
  import { agentNotFound, assertAgentAccess, agentAccess } from "../auth/access.js";
17
21
  import { permissionsOf } from "../auth/resource-server.js";
18
22
  import { McpError } from "../errors.js";
@@ -54,10 +58,17 @@ async function resolveGrantee(db, grantee) {
54
58
  }
55
59
  return principal;
56
60
  }
57
- /** Grant management is the owner's (or the stdio operator's). Phase 1: agents. */
61
+ /** Grant management is the owner's (or the stdio operator's). Agents and MCP servers. */
58
62
  async function requireOwnedResource(ctx, db, type, id) {
59
- // Only "agent" reaches here in Phase 1 (shareableType).
60
- void type;
63
+ if (type === "mcp_server") {
64
+ const server = await db.mcpServer.findUnique({ where: { id } });
65
+ // Missing and someone else's look the same. The stdio operator owns everything.
66
+ if (!server || (!ctx.operator && server.ownerId !== ctx.principal.id)) {
67
+ throw new McpError(404, `MCP server "${id}" not found.`);
68
+ }
69
+ return server;
70
+ }
71
+ // Only "agent" and "mcp_server" reach here (shareableType).
61
72
  const row = await db.agent.findUnique({ where: { id } });
62
73
  return (await assertAgentAccess(ctx, row, id, "owner", db)).agent;
63
74
  }
@@ -65,17 +76,21 @@ async function requireOwnedResource(ctx, db, type, id) {
65
76
  * The ownership check and the write in one Serializable transaction (review
66
77
  * M3): a concurrent make_owner, which resets grants in its own transaction,
67
78
  * conflicts instead of letting the old owner's grant land on the new
68
- * owner's agent.
79
+ * owner's agent. A conflict is retried a bounded number of times
80
+ * (withSerializableRetry); the last one still reaches the caller as a 409.
69
81
  */
70
82
  function inSerializable(ctx, fn) {
71
- return ctx.db.$transaction((tx) => fn(tx), { isolationLevel: Prisma.TransactionIsolationLevel.Serializable });
83
+ return withSerializableRetry(() => ctx.db.$transaction(async (tx) => {
84
+ await preferIndexScans(tx);
85
+ return fn(tx);
86
+ }, { isolationLevel: Prisma.TransactionIsolationLevel.Serializable }));
72
87
  }
73
88
  function isAdmin(ctx) {
74
89
  return ctx.scopes.has("agents:admin") && permissionsOf(ctx.roles).has("agents:admin");
75
90
  }
76
91
  const TYPE_PROPERTIES = {
77
92
  // No enum: another type gets a clear "not shareable yet" from the handler.
78
- resourceType: { type: "string", description: 'Only "agent" is shareable today.' },
93
+ resourceType: { type: "string", description: 'Agents and MCP servers are shareable: "agent" or "mcp_server".' },
79
94
  resourceId: { type: "string" },
80
95
  };
81
96
  const GRANTEE_PROPERTY = {
@@ -100,21 +115,22 @@ export function registerGrantTools(mcp) {
100
115
  mcp.registerTool({
101
116
  name: "grant_access",
102
117
  scope: "agents:write",
103
- description: "Shares an agent you own with one principal or with everyone, at a level: " +
118
+ description: "Shares an agent or MCP server you own with one principal or with everyone, at a level: " +
104
119
  "read (see its config, never secret values or other owners' tool code); " +
105
120
  "execute (also trigger runs and see the runs they triggered); " +
106
121
  "write (also change its name, prompt, model, budget amount, max turns, effort, memory on/off and schedule, attach tools they can use and detach tools, and create webhooks). " +
107
122
  "Owner-only whatever the level: the coding profile (task, base ref, protected paths, task-override opt-in, image, packages, limits), kind, budget group, sub-agents, secret/datastore/repository bindings, tool capabilities, memory and datastore contents, grants, delete. " +
108
123
  "Consequences: execute lets them run your agent with your tools, secrets, datastores and repository, and every run shares the agent's memory; they supply no task text unless it's a coding agent with allowWebhookTaskOverride. " +
109
124
  "Write lets them rewrite what those runs do: the prompt can make a run use everything the agent can already reach (the capabilities you granted, its memory, its linked repositories), and on a coding agent the prompt is part of every coding task, so write directs work done with your GitHub access. " +
110
- "Granting again replaces the level. Everyone may get write only on a native agent with no secrets, datastores, repositories, sub-agents or tool capabilities. The grantee must have signed in once.",
125
+ "MCP servers (resourceType mcp_server): read < use < write; use on an MCP server lets them attach it to their own agents; its tools then run with your server's credentials. Write adds nothing beyond use: only the owner updates, refreshes, deletes or re-grants a server. Lowering a grant to read (including everyone from use to read) detaches the server from every agent whose owner can no longer use it, and their runs' next call to it is refused. " +
126
+ "Granting again replaces the level. Everyone may get write only on a native agent with no secrets, datastores, repositories, sub-agents, MCP servers or tool capabilities. The grantee must have signed in once.",
111
127
  inputSchema: {
112
128
  type: "object",
113
129
  additionalProperties: false,
114
130
  properties: {
115
131
  ...TYPE_PROPERTIES,
116
132
  grantee: GRANTEE_PROPERTY,
117
- level: { type: "string", description: "Agents: read < execute < write." },
133
+ level: { type: "string", description: "Agents: read < execute < write. MCP servers: read < use < write." },
118
134
  },
119
135
  required: ["resourceType", "resourceId", "grantee", "level"],
120
136
  },
@@ -124,7 +140,7 @@ export function registerGrantTools(mcp) {
124
140
  if (!isLevel(type, args.level)) {
125
141
  throw new McpError(400, `Level "${args.level}" is not valid for ${type} (${LEVELS[type].join(" < ")}).`);
126
142
  }
127
- const { resource, principal, grant } = await inSerializable(ctx, async (tx) => {
143
+ const { resource, principal, grant, attachmentsRemoved } = await inSerializable(ctx, async (tx) => {
128
144
  const resource = await requireOwnedResource(ctx, tx, type, args.resourceId);
129
145
  const principal = await resolveGrantee(tx, args.grantee);
130
146
  if (principal && (principal.id === resource.ownerId || principal.id === ctx.principal.id)) {
@@ -154,7 +170,11 @@ export function registerGrantTools(mcp) {
154
170
  },
155
171
  update: { level: args.level, source: "owner", grantedById: ctx.principal.id },
156
172
  });
157
- return { resource, principal, grant };
173
+ // Below use, agents whose owner can no longer use the server lose it now (C1).
174
+ const attachmentsRemoved = type === "mcp_server" && !atLeast(type, args.level, "use")
175
+ ? await pruneUnusableMcpAttachments(tx, resource.id)
176
+ : 0;
177
+ return { resource, principal, grant, attachmentsRemoved };
158
178
  });
159
179
  return textResult({
160
180
  granted: true,
@@ -167,13 +187,14 @@ export function registerGrantTools(mcp) {
167
187
  ? { principalId: principal.id, subject: principal.subject }
168
188
  : { principalId: principal.id },
169
189
  level: grant.level,
190
+ ...(attachmentsRemoved > 0 ? { attachmentsRemoved } : {}),
170
191
  });
171
192
  },
172
193
  });
173
194
  mcp.registerTool({
174
195
  name: "revoke_access",
175
196
  scope: "agents:write",
176
- description: "Removes a grant on an agent you own. Idempotent. Takes effect on the next check (trigger, delegation, webhook fire); runs already in flight keep going.",
197
+ description: "Removes a grant on an agent or MCP server you own. Idempotent. Takes effect on the next check (trigger, delegation, webhook fire); runs already in flight keep going. For an MCP server the check is every tool call: the server is detached from every agent whose owner can no longer use it (your own agents are untouched), and a run's next call to it is refused with mcp_tool_not_allowed.",
177
198
  inputSchema: {
178
199
  type: "object",
179
200
  additionalProperties: false,
@@ -183,20 +204,25 @@ export function registerGrantTools(mcp) {
183
204
  handler: async (rawArgs, ctx) => {
184
205
  const args = parse(RevokeSchema, "revoke_access", rawArgs);
185
206
  const type = shareableType(args.resourceType);
186
- const { count } = await inSerializable(ctx, async (tx) => {
207
+ const { count, attachmentsRemoved } = await inSerializable(ctx, async (tx) => {
187
208
  const resource = await requireOwnedResource(ctx, tx, type, args.resourceId);
188
209
  const granteeKey = args.grantee === "everyone"
189
210
  ? EVERYONE_KEY
190
211
  : principalGranteeKey((await resolveGrantee(tx, args.grantee)).id);
191
- return tx.resourceGrant.deleteMany({ where: { resourceType: type, resourceId: resource.id, granteeKey } });
212
+ const { count } = await tx.resourceGrant.deleteMany({
213
+ where: { resourceType: type, resourceId: resource.id, granteeKey },
214
+ });
215
+ // Agents whose owner can no longer use the server lose it now (C1).
216
+ const attachmentsRemoved = type === "mcp_server" ? await pruneUnusableMcpAttachments(tx, resource.id) : 0;
217
+ return { count, attachmentsRemoved };
192
218
  });
193
- return textResult({ revoked: count > 0 });
219
+ return textResult({ revoked: count > 0, ...(attachmentsRemoved > 0 ? { attachmentsRemoved } : {}) });
194
220
  },
195
221
  });
196
222
  mcp.registerTool({
197
223
  name: "list_access",
198
224
  scope: "agents:read",
199
- description: "Lists who an agent is shared with (grant metadata only). For its owner, the stdio operator, and admins (incident response).",
225
+ description: "Lists who an agent or MCP server is shared with (grant metadata only). For its owner, the stdio operator, and admins (incident response).",
200
226
  inputSchema: {
201
227
  type: "object",
202
228
  additionalProperties: false,
@@ -206,16 +232,28 @@ export function registerGrantTools(mcp) {
206
232
  handler: async (rawArgs, ctx) => {
207
233
  const args = parse(ListSchema, "list_access", rawArgs);
208
234
  const type = shareableType(args.resourceType);
209
- const resource = await ctx.db.agent.findUnique({ where: { id: args.resourceId } });
210
- if (!resource)
211
- throw agentNotFound(args.resourceId);
212
- if (!isAdmin(ctx)) {
213
- const access = await agentAccess(ctx, resource);
214
- if (!atLeast("agent", access, "read"))
235
+ let resource;
236
+ if (type === "mcp_server") {
237
+ const server = await ctx.db.mcpServer.findUnique({ where: { id: args.resourceId } });
238
+ // Owner, stdio operator or admin; anyone else sees a missing server.
239
+ if (!server || (!ctx.operator && !isAdmin(ctx) && server.ownerId !== ctx.principal.id)) {
240
+ throw new McpError(404, `MCP server "${args.resourceId}" not found.`);
241
+ }
242
+ resource = server;
243
+ }
244
+ else {
245
+ const agent = await ctx.db.agent.findUnique({ where: { id: args.resourceId } });
246
+ if (!agent)
215
247
  throw agentNotFound(args.resourceId);
216
- if (access !== "owner") {
217
- throw new McpError(403, `Agent "${resource.id}": only its owner (or an admin) can list who it is shared with.`);
248
+ if (!isAdmin(ctx)) {
249
+ const access = await agentAccess(ctx, agent);
250
+ if (!atLeast("agent", access, "read"))
251
+ throw agentNotFound(args.resourceId);
252
+ if (access !== "owner") {
253
+ throw new McpError(403, `Agent "${agent.id}": only its owner (or an admin) can list who it is shared with.`);
254
+ }
218
255
  }
256
+ resource = agent;
219
257
  }
220
258
  const grants = await ctx.db.resourceGrant.findMany({
221
259
  where: { resourceType: type, resourceId: resource.id },
@@ -0,0 +1,2 @@
1
+ import type { WardbyMcpServer } from "../server.js";
2
+ export declare function registerHostProviderTools(mcp: WardbyMcpServer): void;