@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
@@ -3,6 +3,10 @@ import { isPackageAuditMode, mergePackageAuditReports, } from "../../coding/regi
3
3
  import { mkdir, realpath, rename, rm, writeFile } from "node:fs/promises";
4
4
  import { join, resolve, sep } from "node:path";
5
5
  import { withBaseCommit } from "../../coding/base-commit.js";
6
+ import { mcpTokenHash, mintMcpToken } from "../../coding/mcp-token.js";
7
+ import { isCodexCoding } from "../../core/mcp-tools.js";
8
+ import { CODING_MCP_GATEWAY_UNAVAILABLE } from "../../core/power.js";
9
+ import { codeProviderOf } from "../../coding/code-host.js";
6
10
  import { withPullRequestDescription } from "../../coding/pr-description-note.js";
7
11
  import { withLinkedIssue } from "../../coding/linked-issue-note.js";
8
12
  import { buildClaudeContext, MAX_RECORDED_SKIPS } from "../../coding/claude-context.js";
@@ -169,8 +173,20 @@ export class PrismaContainerExecutionStore {
169
173
  });
170
174
  if (!row?.codingRun)
171
175
  return null;
176
+ const link = row.codingRun.viaRepositoryLink
177
+ ? await this.db.agentRepository.findFirst({
178
+ where: {
179
+ agentId: row.agentId,
180
+ repository: row.codingRun.repository,
181
+ access: "write",
182
+ triggers: { isEmpty: true },
183
+ },
184
+ select: { authorizedVia: true },
185
+ })
186
+ : null;
172
187
  return {
173
188
  runId: row.id,
189
+ runStepsLevel: row.runStepsLevel,
174
190
  agentId: row.agentId,
175
191
  status: row.status,
176
192
  agentKind: row.agent.kind,
@@ -219,6 +235,9 @@ export class PrismaContainerExecutionStore {
219
235
  failureCategory: row.codingRun.failureCategory,
220
236
  profileRepository: row.agent.codingProfile?.repository ?? null,
221
237
  repositoryAuthorizedVia: row.agent.codingProfile?.repositoryAuthorizedVia ?? null,
238
+ viaRepositoryLink: row.codingRun.viaRepositoryLink,
239
+ repositoryLinkExists: link !== null,
240
+ repositoryLinkAuthorizedVia: link?.authorizedVia ?? null,
222
241
  pricingVersion: row.pricingVersion,
223
242
  pricingSnapshot: row.pricingSnapshot,
224
243
  };
@@ -596,9 +615,14 @@ export class PrismaContainerExecutionStore {
596
615
  return changed;
597
616
  }
598
617
  }
599
- /** Plain capabilities live only for the short provisioning window. */
618
+ /**
619
+ * Plain capabilities live only for the short provisioning window. A run with MCP tools keeps its
620
+ * MCP token here too, next to the capability, so the launcher delivers it the same way; both are
621
+ * deleted together.
622
+ */
600
623
  export class RunCapabilityVault {
601
624
  values = new Map();
625
+ mcpTokens = new Map();
602
626
  set(runId, capability) {
603
627
  if (this.values.has(runId))
604
628
  throw new Error("coding_capability_exists");
@@ -610,8 +634,18 @@ export class RunCapabilityVault {
610
634
  throw new Error("coding_capability_unavailable");
611
635
  return capability;
612
636
  }
637
+ setMcpToken(runId, token) {
638
+ if (this.mcpTokens.has(runId))
639
+ throw new Error("coding_mcp_token_exists");
640
+ this.mcpTokens.set(runId, token);
641
+ }
642
+ /** The run's MCP token; undefined for a run without MCP tools (and once the launch is over). */
643
+ async mcpToken(runId) {
644
+ return this.mcpTokens.get(runId);
645
+ }
613
646
  delete(runId) {
614
647
  this.values.delete(runId);
648
+ this.mcpTokens.delete(runId);
615
649
  }
616
650
  }
617
651
  /** A run's deduplicated served packages and refusals (see summarizeRegistryFetches), and its package audit. */
@@ -827,6 +861,20 @@ export class ContainerExecutor {
827
861
  if (TERMINAL_STATUSES.has(beforeSession.status))
828
862
  throw new Error("coding_run_no_longer_active");
829
863
  const deadlineAt = new Date(this.now().getTime() + run.timeoutSec * 1_000);
864
+ // The agent's MCP tools, pinned for the run; only a run with some gets a token. The token is
865
+ // never logged: only its hash goes to the session, the value only into the vault.
866
+ // A Codex run's shell commands can read the token, so it gets read-only tools only; decided by
867
+ // the provider the run was dispatched with, which is what jobSpec launches. A failed read fails
868
+ // the run with a fixed code (the cause goes to the operator log only, redacted).
869
+ let mcpTools;
870
+ try {
871
+ mcpTools =
872
+ (await this.options.mcpToolPins?.(run.agentId, { readOnlyOnly: isCodexCoding(run.provider) })) ?? [];
873
+ }
874
+ catch (error) {
875
+ throw new Error(MCP_PINS_UNAVAILABLE_ERROR, { cause: error });
876
+ }
877
+ const mcpToken = mcpTools.length > 0 ? mintMcpToken() : undefined;
830
878
  const session = await this.options.sessions.createSession({
831
879
  runId,
832
880
  credentialRef: this.credentialRef(run.provider),
@@ -835,15 +883,19 @@ export class ContainerExecutor {
835
883
  deadlineAt,
836
884
  budgetUsd: run.budgetUsd,
837
885
  terms: this.modelTerms(run),
886
+ ...(mcpToken ? { mcpTools, mcpTokenHash: mcpTokenHash(mcpToken) } : {}),
887
+ ...(run.runStepsLevel ? { runStepsLevel: run.runStepsLevel } : {}),
838
888
  });
839
889
  spendEnabled = true;
840
890
  sessionId = session.id;
841
891
  this.options.capabilities.set(runId, session.capability);
892
+ if (mcpToken)
893
+ this.options.capabilities.setMcpToken(runId, mcpToken);
842
894
  const inputArtifact = await this.writeInput(run, deadlineAt, workspace);
843
895
  const beforeLaunch = await this.requireCurrent(runId);
844
896
  if (TERMINAL_STATUSES.has(beforeLaunch.status))
845
897
  throw new Error("coding_run_no_longer_active");
846
- const spec = this.jobSpec(run, inputArtifact);
898
+ const spec = this.jobSpec(run, inputArtifact, { mcpTools: Boolean(mcpToken) });
847
899
  // Backends whose handle is derivable (Kubernetes) persist it first, so a crash between the
848
900
  // launch and the write still leaves a handle `abandon()` can stop and remove the run with.
849
901
  planned = this.options.jobs.plannedHandle?.(spec);
@@ -1005,13 +1057,21 @@ export class ContainerExecutor {
1005
1057
  * access. Throws RepoAccessError (refused before anything is cloned).
1006
1058
  */
1007
1059
  async authorizeRepository(run, opts) {
1060
+ // Checked first: a local repository is otherwise authorized by the trusted roots alone.
1061
+ if (run.viaRepositoryLink && !run.repositoryLinkExists)
1062
+ throw new RepoAccessError("repo_access_not_authorized");
1008
1063
  const sameRepository = run.profileRepository !== null && run.profileRepository === run.repository;
1064
+ const authorizedVia = run.viaRepositoryLink
1065
+ ? run.repositoryLinkAuthorizedVia
1066
+ : sameRepository
1067
+ ? run.repositoryAuthorizedVia
1068
+ : "host_permission";
1009
1069
  const decision = await this.options.repoAccess.authorizeUse({
1010
1070
  ownerId: run.ownerId,
1011
- provider: "github",
1071
+ provider: codeProviderOf(run.repository),
1012
1072
  repository: run.repository,
1013
1073
  required: requiredLevel("coding"),
1014
- authorizedVia: sameRepository ? run.repositoryAuthorizedVia : "host_permission",
1074
+ authorizedVia,
1015
1075
  retryTransient: opts.inFlight,
1016
1076
  });
1017
1077
  if (!decision.ok)
@@ -1510,7 +1570,8 @@ export class ContainerExecutor {
1510
1570
  status.number > 0) {
1511
1571
  candidates.push({ provider: "github", repository: run.repository, number: status.number });
1512
1572
  }
1513
- const closed = closingIssueNumber(description, run.repository);
1573
+ // The closing keyword names a GitHub issue: no other host's repository has one to read.
1574
+ const closed = codeProviderOf(run.repository) === "github" ? closingIssueNumber(description, run.repository) : undefined;
1514
1575
  if (closed !== undefined && !candidates.some((ref) => ref.provider === "github" && ref.number === closed)) {
1515
1576
  candidates.push({ provider: "github", repository: run.repository, number: closed });
1516
1577
  }
@@ -1724,7 +1785,8 @@ export class ContainerExecutor {
1724
1785
  if (!workspace)
1725
1786
  throw new Error("coding_workspace_lost");
1726
1787
  // The same in-flight access check a push gets: a revoked owner's run edits nothing.
1727
- await this.authorizeRepository(run, { inFlight: true });
1788
+ // Reloaded: the snapshot passed in predates the description reads above, and a link removed since then counts.
1789
+ await this.authorizeRepository(await this.requireCurrent(run.runId), { inFlight: true });
1728
1790
  const details = {
1729
1791
  summary: output.summary,
1730
1792
  tests: output.tests,
@@ -1990,7 +2052,7 @@ export class ContainerExecutor {
1990
2052
  this.emit({ stage: "queued", runId: run.runId });
1991
2053
  return false;
1992
2054
  }
1993
- jobSpec(run, inputArtifact) {
2055
+ jobSpec(run, inputArtifact, options = {}) {
1994
2056
  const provider = run.provider === "claude-code" ? "claude-code" : "codex";
1995
2057
  if (provider === "claude-code" && (!run.workerImage || !this.options.claudeToolRunnerImage)) {
1996
2058
  throw new Error("coding_provider_not_configured:claude-code");
@@ -2019,6 +2081,8 @@ export class ContainerExecutor {
2019
2081
  ...(run.dependencyCache ? { dependencyCache: true } : {}),
2020
2082
  // Claude Code only: the launcher gives the SDK's config dir, where the session grows, its own scratch.
2021
2083
  ...(run.sessionResume && provider === "claude-code" ? { sessionResume: true } : {}),
2084
+ // Only for a run with MCP tools: the launcher then delivers WARDBY_MCP_TOKEN from the vault.
2085
+ ...(options.mcpTools ? { mcpTools: true } : {}),
2022
2086
  timeoutSec: run.timeoutSec,
2023
2087
  limits: { ...this.options.limits, ...(run.workspaceDiskMb ? { diskMb: run.workspaceDiskMb } : {}) },
2024
2088
  labels: {},
@@ -2420,13 +2484,19 @@ function safeError(error) {
2420
2484
  const message = error instanceof Error ? error.message : "unknown";
2421
2485
  return `coding_executor:${redactAndTruncate(message, 200).replace(/[^A-Za-z0-9_.:-]/g, "_")}`;
2422
2486
  }
2487
+ /** The agent's MCP tool pins could not be read at launch (a database error): the run fails, nothing launched. */
2488
+ export const MCP_PINS_UNAVAILABLE_ERROR = "coding_mcp_pins_unavailable";
2489
+ /** Its category: the run's error is coding_failure_mcp_pins_unavailable:<diagnostic id>. */
2490
+ export const MCP_PINS_UNAVAILABLE_CATEGORY = "mcp_pins_unavailable";
2491
+ /** Low-power mode: the native gateway serving a run's MCP tools did not wake; nothing launched. */
2492
+ export const MCP_GATEWAY_UNAVAILABLE_CATEGORY = "mcp_gateway_unavailable";
2423
2493
  /**
2424
2494
  * Error-code prefixes that name their category outright, checked before the
2425
2495
  * substring heuristics below. Those heuristics filed every `github_*` error
2426
2496
  * (a failed PR lookup or create after the push, a token mint) under
2427
2497
  * "workspace" because "github" contains "git".
2428
2498
  */
2429
- const CATEGORY_BY_PREFIX = [
2499
+ export const CATEGORY_BY_PREFIX = [
2430
2500
  // Claude Code reached the agent's turn limit (codingProfile.maxTurns) before finishing.
2431
2501
  ["job_coding_turn_limit", "turn_limit"],
2432
2502
  // A service sidecar never became ready (src/providers/jobs/kubernetes.ts).
@@ -2453,7 +2523,13 @@ const CATEGORY_BY_PREFIX = [
2453
2523
  ["coding_provider_not_configured", "preflight"],
2454
2524
  // A GitHub repository on a server with no GitHub App: configuration, like an unsupported service.
2455
2525
  ["vcs_github_not_configured", "preflight"],
2526
+ // The same for any other host: a repository whose provider this server has not registered.
2527
+ ["vcs_host_provider_not_configured", "preflight"],
2456
2528
  ["vcs_", "workspace"],
2529
+ // The agent's MCP tool pins could not be read at launch: nothing was launched.
2530
+ [MCP_PINS_UNAVAILABLE_ERROR, MCP_PINS_UNAVAILABLE_CATEGORY],
2531
+ // Low-power mode: the native gateway serving the run's MCP tools did not wake (jobs/kubernetes.ts).
2532
+ [CODING_MCP_GATEWAY_UNAVAILABLE, MCP_GATEWAY_UNAVAILABLE_CATEGORY],
2457
2533
  // Local repositories (LocalRepoError codes): the same family as the vcs_ errors.
2458
2534
  ["local_", "workspace"],
2459
2535
  ["git_", "workspace"],
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Runs one approved snapshot of a bound tool with a restricted privileged host: fetch
3
+ * reaches the provider's API hosts inside this call's repository, using the operation's
4
+ * methods, with no redirects followed and only plain content headers, and the only secret alias is `host`. Datastores and readable secrets are
5
+ * absent, and console output is dropped. Never throws: every failure is a fixed code,
6
+ * so no tool text reaches a model, a run record or a published comment.
7
+ */
8
+ import type { SandboxLimits } from "../../sandbox/eval-core.js";
9
+ import { safeFetch } from "../../sandbox/safe-fetch.js";
10
+ import type { SecretEntry } from "../../core/secrets.js";
11
+ import { type HostOperation, type HostToolDeclineCode } from "./operations.js";
12
+ export declare const HOST_SECRET_ALIAS = "host";
13
+ export type HostToolErrorCode = "host_tool_failed" | "host_tool_timeout" | "host_tool_invalid_result" | "host_tool_invalid_params" | "host_tool_request_denied" | "host_tool_no_credential" | `host_${HostToolDeclineCode}`;
14
+ export type HostToolResult = {
15
+ ok: true;
16
+ value: unknown;
17
+ } | {
18
+ ok: false;
19
+ code: HostToolErrorCode;
20
+ };
21
+ export interface HostToolInvocation {
22
+ provider: string;
23
+ operation: HostOperation;
24
+ snapshot: {
25
+ code: string;
26
+ paramsZod: string;
27
+ };
28
+ params: Record<string, unknown>;
29
+ apiHosts: readonly string[];
30
+ pathPrefix: string;
31
+ credential: () => Promise<SecretEntry | undefined>;
32
+ fetchImpl?: typeof safeFetch;
33
+ limits?: Partial<SandboxLimits>;
34
+ /** The approved snapshot's time limit (effectiveToolTimeoutMs); absent = the default. */
35
+ timeoutMs?: number;
36
+ }
37
+ export declare function invokeHostTool(input: HostToolInvocation): Promise<HostToolResult>;
@@ -0,0 +1,117 @@
1
+ import { createPrivilegedHost } from "../../sandbox/host-functions.js";
2
+ import { runToolCode, toolLimits } from "../../sandbox/run-in-sandbox.js";
3
+ import { fetchTimeoutFor } from "../../sandbox/tool-timeout.js";
4
+ import { WALL_TIME_LIMIT_MS } from "../../sandbox/limits.js";
5
+ import { safeFetch } from "../../sandbox/safe-fetch.js";
6
+ import { validateParams } from "../../sandbox/zod-params.js";
7
+ import { HOST_OPERATIONS, HOST_TOOL_DECLINE_CODES, } from "./operations.js";
8
+ import { assertHostToolHeaders, assertHostToolRequest, HostToolRequestDenied, MAX_HOST_TOOL_FETCHES, } from "./request-guard.js";
9
+ export const HOST_SECRET_ALIAS = "host";
10
+ const absent = async () => {
11
+ throw new Error("host_tool_capability_absent");
12
+ };
13
+ export async function invokeHostTool(input) {
14
+ const definition = HOST_OPERATIONS[input.operation];
15
+ const failures = { denied: false, noCredential: false };
16
+ const validated = await validateParams(input.snapshot.paramsZod, input.params);
17
+ if (!validated.ok)
18
+ return { ok: false, code: "host_tool_invalid_params" };
19
+ const privileged = (signal) => {
20
+ const inner = createPrivilegedHost({
21
+ agentId: `host-provider:${input.provider}`,
22
+ // Never reached: every bridge that would use them is `absent` below.
23
+ datastore: {},
24
+ sharedDatastore: {},
25
+ logTag: `host-tool:${input.operation}`,
26
+ allowedFetchHosts: [...input.apiHosts],
27
+ // Redirects are never followed here: a later hop would skip the guard, so the tool re-fetches through it.
28
+ fetchImpl: (u, i, o) => (input.fetchImpl ?? safeFetch)(u, i, { ...o, followRedirects: false }),
29
+ signal,
30
+ fetchTimeoutMs: fetchTimeoutFor(input.timeoutMs ?? input.limits?.wallTimeLimitMs ?? WALL_TIME_LIMIT_MS),
31
+ secrets: {
32
+ get: async () => undefined,
33
+ resolve: async (name) => {
34
+ if (name !== HOST_SECRET_ALIAS)
35
+ return undefined;
36
+ let entry;
37
+ try {
38
+ entry = await input.credential();
39
+ }
40
+ catch {
41
+ entry = undefined;
42
+ }
43
+ // A thrown or partial credential must not carry its text into tool code. Registering
44
+ // checks the hosts, but a broker config can be narrowed afterwards.
45
+ if (!entry || !entry.broker || input.apiHosts.some((h) => !entry.broker.hosts.includes(h))) {
46
+ failures.noCredential = true;
47
+ throw new Error("host_tool_no_credential");
48
+ }
49
+ return entry;
50
+ },
51
+ },
52
+ });
53
+ let fetches = 0;
54
+ return {
55
+ __bridge_console: async () => null,
56
+ __bridge_fetch: async (argsJson) => {
57
+ try {
58
+ const [url, init, secretNames] = JSON.parse(argsJson);
59
+ if (++fetches > MAX_HOST_TOOL_FETCHES)
60
+ throw new HostToolRequestDenied();
61
+ if (secretNames !== undefined &&
62
+ !(Array.isArray(secretNames) && secretNames.every((n) => n === HOST_SECRET_ALIAS))) {
63
+ throw new HostToolRequestDenied();
64
+ }
65
+ assertHostToolHeaders(init?.headers);
66
+ assertHostToolRequest(url, init?.method ?? "GET", {
67
+ apiHosts: input.apiHosts,
68
+ pathPrefix: input.pathPrefix,
69
+ methods: definition.methods,
70
+ });
71
+ }
72
+ catch (err) {
73
+ failures.denied = true;
74
+ throw err;
75
+ }
76
+ return inner.__bridge_fetch(argsJson);
77
+ },
78
+ __bridge_datastoreGet: absent,
79
+ __bridge_datastoreSet: absent,
80
+ __bridge_datastoreDelete: absent,
81
+ __bridge_datastoreList: absent,
82
+ __bridge_sharedDatastoreGet: absent,
83
+ __bridge_sharedDatastoreSet: absent,
84
+ __bridge_sharedDatastoreDelete: absent,
85
+ __bridge_sharedDatastoreList: absent,
86
+ __bridge_secretsGet: absent,
87
+ };
88
+ };
89
+ const result = await runToolCode({
90
+ code: input.snapshot.code,
91
+ params: validated.value,
92
+ limits: toolLimits(input.timeoutMs, input.limits),
93
+ privileged,
94
+ });
95
+ // Checked first: a tool that catches a refusal must not turn it into a success.
96
+ if (failures.denied)
97
+ return { ok: false, code: "host_tool_request_denied" };
98
+ if (failures.noCredential)
99
+ return { ok: false, code: "host_tool_no_credential" };
100
+ if (!result.ok) {
101
+ return {
102
+ ok: false,
103
+ code: result.errorKind === "timeout" || result.errorKind === "cpu" ? "host_tool_timeout" : "host_tool_failed",
104
+ };
105
+ }
106
+ const decline = declineOf(result.value);
107
+ if (decline)
108
+ return { ok: false, code: `host_${decline}` };
109
+ const parsed = definition.result.safeParse(result.value);
110
+ return parsed.success ? { ok: true, value: parsed.data } : { ok: false, code: "host_tool_invalid_result" };
111
+ }
112
+ function declineOf(value) {
113
+ if (typeof value !== "object" || value === null || !("error" in value))
114
+ return null;
115
+ const { error } = value;
116
+ return HOST_TOOL_DECLINE_CODES.includes(error) ? error : null;
117
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * The contract between Wardby and a host provider's bound tools. A bound tool makes
3
+ * one host operation; paging, markers, safeguards and wording stay in Wardby. Wardby
4
+ * calls each tool with `{ workspace, repo, repository, ...operation fields }`.
5
+ *
6
+ * A tool answers with its operation's result, or `{ error: <decline code> }` when the
7
+ * host refused. Results are untrusted and validated here, and a tool's own text never
8
+ * reaches a model, a run record or a comment.
9
+ */
10
+ import { z } from "zod";
11
+ export declare const HOST_TOOL_CONTRACT_VERSION = 1;
12
+ /** What a tool may say about a host refusal: fixed words, so no tool text passes through. */
13
+ export declare const HOST_TOOL_DECLINE_CODES: readonly ["unauthorized", "forbidden", "not_found", "conflict", "rate_limited", "unavailable"];
14
+ export type HostToolDeclineCode = (typeof HOST_TOOL_DECLINE_CODES)[number];
15
+ type OperationDef = {
16
+ /** The HTTP methods this operation's requests may use. */
17
+ methods: readonly string[];
18
+ /** Changes nothing on the host: test_host_provider may call it. */
19
+ readOnly: boolean;
20
+ /** Which credential its `host` alias resolves to. */
21
+ credential: "repository" | "permission" | "none";
22
+ result: z.ZodType;
23
+ };
24
+ export declare const HOST_OPERATIONS: {
25
+ readonly getPullRequest: OperationDef;
26
+ readonly listPullRequestFiles: OperationDef;
27
+ readonly getDiff: OperationDef;
28
+ readonly findPullRequest: OperationDef;
29
+ readonly readFile: OperationDef;
30
+ readonly listFiles: OperationDef;
31
+ readonly listComments: OperationDef;
32
+ readonly listCommitStatuses: OperationDef;
33
+ readonly getRepositoryPermission: {
34
+ readonly methods: readonly ["GET"];
35
+ readonly readOnly: true;
36
+ readonly credential: "permission";
37
+ readonly result: z.ZodObject<{
38
+ permission: z.ZodEnum<{
39
+ read: "read";
40
+ write: "write";
41
+ none: "none";
42
+ triage: "triage";
43
+ maintain: "maintain";
44
+ admin: "admin";
45
+ }>;
46
+ }, z.core.$strict>;
47
+ };
48
+ readonly openPullRequest: OperationDef;
49
+ readonly updatePullRequest: OperationDef;
50
+ readonly createComment: OperationDef;
51
+ readonly editComment: OperationDef;
52
+ readonly resolveThread: OperationDef;
53
+ readonly submitVerdict: OperationDef;
54
+ readonly setCommitStatus: OperationDef;
55
+ readonly normalizeEvent: {
56
+ readonly methods: readonly [];
57
+ readonly readOnly: false;
58
+ readonly credential: "none";
59
+ readonly result: z.ZodUnknown;
60
+ };
61
+ };
62
+ export type HostOperation = keyof typeof HOST_OPERATIONS;
63
+ export declare const REQUIRED_OPERATIONS: readonly HostOperation[];
64
+ export declare function isHostOperation(name: string): name is HostOperation;
65
+ export {};
@@ -0,0 +1,149 @@
1
+ /**
2
+ * The contract between Wardby and a host provider's bound tools. A bound tool makes
3
+ * one host operation; paging, markers, safeguards and wording stay in Wardby. Wardby
4
+ * calls each tool with `{ workspace, repo, repository, ...operation fields }`.
5
+ *
6
+ * A tool answers with its operation's result, or `{ error: <decline code> }` when the
7
+ * host refused. Results are untrusted and validated here, and a tool's own text never
8
+ * reaches a model, a run record or a comment.
9
+ */
10
+ import { z } from "zod";
11
+ export const HOST_TOOL_CONTRACT_VERSION = 1;
12
+ /** What a tool may say about a host refusal: fixed words, so no tool text passes through. */
13
+ export const HOST_TOOL_DECLINE_CODES = [
14
+ "unauthorized",
15
+ "forbidden",
16
+ "not_found",
17
+ "conflict",
18
+ "rate_limited",
19
+ "unavailable",
20
+ ];
21
+ // What a paged operation returns; its tool takes the last one back as the optional `page` input.
22
+ const page = z.string().nullable();
23
+ const pullRequest = z
24
+ .object({
25
+ number: z.number().int().positive(),
26
+ title: z.string(),
27
+ body: z.string(),
28
+ author: z.string().nullable(),
29
+ // A merged pull request is "closed" with `merged` set: consumers compare state to "open".
30
+ state: z.enum(["open", "closed"]),
31
+ merged: z.boolean(),
32
+ draft: z.boolean(),
33
+ baseRef: z.string(),
34
+ headRef: z.string(),
35
+ headSha: z.string().min(1),
36
+ isFork: z.boolean(),
37
+ htmlUrl: z.string().url(),
38
+ })
39
+ .strict();
40
+ const written = z.object({ id: z.string().min(1) }).strict();
41
+ const read = (result) => ({
42
+ methods: ["GET"],
43
+ readOnly: true,
44
+ credential: "repository",
45
+ result,
46
+ });
47
+ const write = (methods, result) => ({
48
+ methods,
49
+ readOnly: false,
50
+ credential: "repository",
51
+ result,
52
+ });
53
+ export const HOST_OPERATIONS = {
54
+ getPullRequest: read(pullRequest),
55
+ listPullRequestFiles: read(z
56
+ .object({
57
+ files: z.array(z
58
+ .object({
59
+ filename: z.string(),
60
+ status: z.string(),
61
+ additions: z.number().int().nonnegative(),
62
+ deletions: z.number().int().nonnegative(),
63
+ previousFilename: z.string().optional(),
64
+ })
65
+ .strict()),
66
+ nextPage: page,
67
+ })
68
+ .strict()),
69
+ getDiff: read(z.object({ diff: z.string() }).strict()),
70
+ findPullRequest: read(z.object({ pullRequest: pullRequest.nullable() }).strict()),
71
+ readFile: read(z.discriminatedUnion("kind", [
72
+ z.object({ kind: z.literal("file"), content: z.string() }).strict(),
73
+ z.object({ kind: z.literal("directory") }).strict(),
74
+ z.object({ kind: z.literal("not_found") }).strict(),
75
+ ])),
76
+ listFiles: read(z
77
+ .object({
78
+ entries: z.array(z
79
+ .object({
80
+ path: z.string(),
81
+ type: z.enum(["file", "directory"]),
82
+ size: z.number().int().nonnegative().optional(),
83
+ })
84
+ .strict()),
85
+ nextPage: page,
86
+ })
87
+ .strict()),
88
+ listComments: read(z
89
+ .object({
90
+ comments: z.array(z
91
+ .object({
92
+ id: z.string().min(1),
93
+ author: z.string().nullable(),
94
+ authorId: z.string().nullable(),
95
+ body: z.string(),
96
+ createdAt: z.string(),
97
+ path: z.string().nullable(),
98
+ line: z.number().int().positive().nullable(),
99
+ parentId: z.string().nullable(),
100
+ resolved: z.boolean(),
101
+ })
102
+ .strict()),
103
+ nextPage: page,
104
+ })
105
+ .strict()),
106
+ listCommitStatuses: read(z
107
+ .object({
108
+ statuses: z.array(z
109
+ .object({
110
+ key: z.string(),
111
+ state: z.enum(["pending", "success", "failure", "stopped"]),
112
+ url: z.string().nullable(),
113
+ })
114
+ .strict()),
115
+ nextPage: page,
116
+ })
117
+ .strict()),
118
+ getRepositoryPermission: {
119
+ methods: ["GET"],
120
+ readOnly: true,
121
+ credential: "permission",
122
+ result: z.object({ permission: z.enum(["none", "read", "triage", "write", "maintain", "admin"]) }).strict(),
123
+ },
124
+ openPullRequest: write(["POST"], pullRequest),
125
+ updatePullRequest: write(["PUT"], pullRequest),
126
+ createComment: write(["POST"], written),
127
+ editComment: write(["PUT"], written),
128
+ resolveThread: write(["POST"], z.object({}).strict()),
129
+ submitVerdict: write(["POST"], z.object({}).strict()),
130
+ setCommitStatus: write(["POST"], z.object({}).strict()),
131
+ // PR 5 gives this its HostEvent schema. Nothing calls it before then.
132
+ normalizeEvent: { methods: [], readOnly: false, credential: "none", result: z.unknown() },
133
+ };
134
+ export const REQUIRED_OPERATIONS = [
135
+ "getPullRequest",
136
+ "listPullRequestFiles",
137
+ "getDiff",
138
+ "readFile",
139
+ "listFiles",
140
+ "listComments",
141
+ "createComment",
142
+ "submitVerdict",
143
+ "findPullRequest",
144
+ "openPullRequest",
145
+ "updatePullRequest",
146
+ ];
147
+ export function isHostOperation(name) {
148
+ return Object.hasOwn(HOST_OPERATIONS, name);
149
+ }
@@ -0,0 +1,79 @@
1
+ import { z } from "zod";
2
+ import type { PrismaClient } from "#prisma";
3
+ import type { SecretCipher } from "../secrets/types.js";
4
+ export type HostProviderErrorCode = "host_provider_not_found" | "host_provider_missing_operation" | "host_provider_tool_not_found" | "host_provider_secret_not_found" | "host_provider_secret_not_brokered" | "host_provider_secret_hosts" | "host_provider_secret_placement" | "host_provider_webhook_secret_shared" | "host_provider_webhook_secret_short" | "host_provider_permission_secret_shared" | "host_provider_username_is_secret" | "host_provider_in_use" | "host_provider_operation_not_bound" | "host_provider_tool_deleted";
5
+ export declare class HostProviderError extends Error {
6
+ readonly code: HostProviderErrorCode;
7
+ readonly detail: string;
8
+ constructor(code: HostProviderErrorCode, detail: string);
9
+ }
10
+ export declare const RegisterHostProviderSchema: z.ZodObject<{
11
+ provider: z.ZodString;
12
+ gitHost: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
13
+ apiHosts: z.ZodArray<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
14
+ gitUsername: z.ZodString;
15
+ pullRequestUrlTemplate: z.ZodString;
16
+ repositoryApiPath: z.ZodString;
17
+ permissionApiPath: z.ZodOptional<z.ZodString>;
18
+ webhook: z.ZodObject<{
19
+ header: z.ZodString;
20
+ algorithm: z.ZodLiteral<"sha256">;
21
+ deliveryHeader: z.ZodString;
22
+ eventHeader: z.ZodString;
23
+ secret: z.ZodString;
24
+ }, z.core.$strict>;
25
+ credentials: z.ZodArray<z.ZodObject<{
26
+ repositories: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
27
+ secret: z.ZodString;
28
+ }, z.core.$strict>>;
29
+ permissionCredential: z.ZodOptional<z.ZodString>;
30
+ trustedAccounts: z.ZodOptional<z.ZodArray<z.ZodString>>;
31
+ tools: z.ZodRecord<z.ZodString, z.ZodString>;
32
+ }, z.core.$strict>;
33
+ export type RegisterHostProviderInput = z.infer<typeof RegisterHostProviderSchema>;
34
+ export declare function findCredential<T extends {
35
+ repositoryPattern: string;
36
+ }>(credentials: readonly T[], repositoryPath: string): T | null;
37
+ export interface OperationView {
38
+ operation: string;
39
+ toolName: string | null;
40
+ status: "approved" | "changed" | "tool_deleted";
41
+ approvedAt: Date;
42
+ approvedById: string;
43
+ approvedSha256: string;
44
+ currentSha256: string | null;
45
+ /** The approved snapshot's time limit (null: the default), and the tool's now; a change waits for approval. */
46
+ approvedTimeoutMs: number | null;
47
+ currentTimeoutMs: number | null;
48
+ approvedCode?: string;
49
+ approvedParamsZod?: string;
50
+ currentCode?: string;
51
+ currentParamsZod?: string;
52
+ }
53
+ export interface HostProviderView {
54
+ provider: string;
55
+ contractVersion: number;
56
+ gitHost: string;
57
+ apiHosts: string[];
58
+ gitUsername: string;
59
+ pullRequestUrlTemplate: string;
60
+ repositoryApiPath: string;
61
+ permissionApiPath: string | null;
62
+ mode: "permission" | "allowlist";
63
+ webhook: unknown;
64
+ webhookSecret: string;
65
+ permissionSecret: string | null;
66
+ credentials: {
67
+ repositories: string;
68
+ secret: string;
69
+ kind: string;
70
+ }[];
71
+ trustedAccounts: string[];
72
+ operations: OperationView[];
73
+ }
74
+ export declare function registerHostProvider(db: PrismaClient, cipher: SecretCipher, principalId: string, input: RegisterHostProviderInput): Promise<HostProviderView>;
75
+ export declare function approveHostProviderTools(db: PrismaClient, principalId: string, provider: string, operations: readonly string[]): Promise<HostProviderView>;
76
+ export declare function getHostProviderView(db: PrismaClient, provider: string, options?: {
77
+ includeCode?: boolean;
78
+ }): Promise<HostProviderView | null>;
79
+ export declare function deleteHostProvider(db: PrismaClient, provider: string): Promise<void>;