@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
@@ -0,0 +1,206 @@
1
+ /**
2
+ * Hands coding runs' steps from the proxy to the control plane's run-steps listener
3
+ * (`POST <WARDBY_RUN_STEPS_URL>/internal/run-steps`, `Authorization: Bearer <the run capability>`).
4
+ * The proxy forwards raw step text over the authenticated in-cluster listener; the control plane
5
+ * redacts by exact value before anything is stored. Because the body is raw, this class never logs,
6
+ * echoes, or puts into an error any part of the body, the step text, or the capability: log lines
7
+ * carry a fixed reason and a count only.
8
+ *
9
+ * A bounded in-memory queue, drained one POST at a time off the relay path: a full queue or a slow
10
+ * or failing control plane drops steps and never delays a model request.
11
+ */
12
+ import { logger } from "../../core/logger.js";
13
+ import { MAX_RESULTS_PER_POST, MAX_RUN_STEPS_POST_BYTES, MAX_STEPS_PER_POST, RUN_STEPS_INGRESS_PATH, } from "../../core/run-steps/types.js";
14
+ const forwarderLog = logger.child({ module: "coding-proxy" });
15
+ export const MAX_FORWARD_QUEUE = 256;
16
+ /** A queue holds at most this many bytes of (estimated) serialized submissions. */
17
+ export const MAX_FORWARD_QUEUE_BYTES = 8 * 1024 * 1024;
18
+ /** Margin over the listener's 15 s request limit (its write can take lock wait 5 s + transaction 10 s). */
19
+ const DEFAULT_TIMEOUT_MS = 20_000;
20
+ const DEFAULT_FLUSH_TIMEOUT_MS = 5_000;
21
+ /** The envelope around the arrays, with slack for the runId. */
22
+ const ENVELOPE_SLACK_BYTES = 1024;
23
+ /** A cheap upper-ish bound on a batch's serialized size, with no stringify on the caller's stack. */
24
+ function estimate(batch) {
25
+ let bytes = ENVELOPE_SLACK_BYTES;
26
+ for (const step of batch.steps) {
27
+ bytes += step.text.length * 3 + step.callId.length * 3 + 256;
28
+ if (step.kind === "tool")
29
+ bytes += (step.input?.length ?? 0) * 3;
30
+ }
31
+ for (const result of batch.results)
32
+ bytes += 128 + (result.output?.length ?? 0) * 3;
33
+ return bytes;
34
+ }
35
+ /** Splits by count and by serialized UTF-8 size, so no body passes MAX_RUN_STEPS_POST_BYTES. */
36
+ function split(batch, runId) {
37
+ const parts = [];
38
+ const base = ENVELOPE_SLACK_BYTES + Buffer.byteLength(runId);
39
+ let steps = [];
40
+ let size = base;
41
+ for (const step of batch.steps) {
42
+ const bytes = Buffer.byteLength(JSON.stringify(step)) + 1;
43
+ if (steps.length > 0 && (steps.length >= MAX_STEPS_PER_POST || size + bytes > MAX_RUN_STEPS_POST_BYTES)) {
44
+ parts.push({ steps, results: [] });
45
+ steps = [];
46
+ size = base;
47
+ }
48
+ steps.push(step);
49
+ size += bytes;
50
+ }
51
+ if (steps.length > 0)
52
+ parts.push({ steps, results: [] });
53
+ let results = [];
54
+ let resultBytes = base;
55
+ for (const result of batch.results) {
56
+ const bytes = Buffer.byteLength(JSON.stringify(result)) + 1;
57
+ if (results.length > 0 &&
58
+ (results.length >= MAX_RESULTS_PER_POST || resultBytes + bytes > MAX_RUN_STEPS_POST_BYTES)) {
59
+ parts.push({ steps: [], results });
60
+ results = [];
61
+ resultBytes = base;
62
+ }
63
+ results.push(result);
64
+ resultBytes += bytes;
65
+ }
66
+ if (results.length > 0)
67
+ parts.push({ steps: [], results });
68
+ return parts;
69
+ }
70
+ export class RunStepForwarder {
71
+ options;
72
+ queue = [];
73
+ queueBytes = 0;
74
+ draining;
75
+ scheduled = false;
76
+ closed = false;
77
+ inflight;
78
+ dropped = 0;
79
+ loggedReasons = new Set();
80
+ constructor(options) {
81
+ this.options = options;
82
+ }
83
+ submit(capability, runId, batch) {
84
+ try {
85
+ if (this.closed)
86
+ return this.drop("closed");
87
+ const size = estimate(batch);
88
+ if (this.queue.length >= (this.options.maxQueue ?? MAX_FORWARD_QUEUE) ||
89
+ this.queueBytes + size > (this.options.maxQueueBytes ?? MAX_FORWARD_QUEUE_BYTES)) {
90
+ return this.drop("queue_full");
91
+ }
92
+ this.queue.push({ capability, runId, batch, estimate: size });
93
+ this.queueBytes += size;
94
+ this.kick();
95
+ }
96
+ catch {
97
+ this.drop("submit_failed");
98
+ }
99
+ }
100
+ /** Resolves once everything submitted so far was posted or dropped, or after a bound. Never rejects. */
101
+ async flush() {
102
+ let timer;
103
+ const bound = new Promise((resolve) => {
104
+ timer = setTimeout(resolve, this.options.flushTimeoutMs ?? DEFAULT_FLUSH_TIMEOUT_MS);
105
+ timer.unref();
106
+ });
107
+ const settled = (async () => {
108
+ while (this.draining || this.scheduled)
109
+ await (this.draining ?? new Promise((r) => setImmediate(r)));
110
+ })().catch(() => undefined);
111
+ await Promise.race([settled, bound]);
112
+ clearTimeout(timer);
113
+ }
114
+ /** Stops accepting, waits at most the flush bound, then drops what is left and aborts the in-flight POST. */
115
+ async close() {
116
+ this.closed = true;
117
+ await this.flush();
118
+ const left = this.queue.length;
119
+ this.queue = [];
120
+ this.queueBytes = 0;
121
+ for (let n = 0; n < left; n += 1)
122
+ this.drop("closed");
123
+ this.inflight?.abort();
124
+ }
125
+ kick() {
126
+ if (this.draining || this.scheduled || this.queue.length === 0)
127
+ return;
128
+ this.scheduled = true;
129
+ // Off the caller's stack: the first serialization must not run on the relay path.
130
+ setImmediate(() => {
131
+ this.scheduled = false;
132
+ if (this.draining || this.queue.length === 0)
133
+ return;
134
+ this.draining = this.drain().finally(() => {
135
+ this.draining = undefined;
136
+ this.kick();
137
+ });
138
+ });
139
+ }
140
+ async drain() {
141
+ for (let next = this.queue.shift(); next; next = this.queue.shift()) {
142
+ this.queueBytes -= next.estimate;
143
+ if (this.closed) {
144
+ this.drop("closed");
145
+ continue;
146
+ }
147
+ let bodies;
148
+ try {
149
+ bodies = split(next.batch, next.runId).map((part) => JSON.stringify({ runId: next.runId, ...part }));
150
+ }
151
+ catch {
152
+ this.drop("serialize_failed");
153
+ continue;
154
+ }
155
+ for (const body of bodies) {
156
+ if (this.closed)
157
+ this.drop("closed");
158
+ else
159
+ await this.post(next.capability, body);
160
+ }
161
+ }
162
+ }
163
+ /**
164
+ * The capability goes in the Authorization header only. The response body is never read, and a
165
+ * failure is logged by a fixed reason: an error's or a response's text never reaches a log line.
166
+ */
167
+ async post(capability, body) {
168
+ const controller = new AbortController();
169
+ this.inflight = controller;
170
+ const timer = setTimeout(() => controller.abort(), this.options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
171
+ timer.unref();
172
+ try {
173
+ const response = await (this.options.fetchImpl ?? fetch)(`${this.options.url}${RUN_STEPS_INGRESS_PATH}`, {
174
+ method: "POST",
175
+ headers: { authorization: `Bearer ${capability}`, "content-type": "application/json" },
176
+ body,
177
+ redirect: "error",
178
+ signal: controller.signal,
179
+ });
180
+ await response.body?.cancel().catch(() => undefined);
181
+ if (!response.ok)
182
+ this.drop(`http_${response.status}`);
183
+ }
184
+ catch {
185
+ this.drop(this.closed ? "closed" : controller.signal.aborted ? "timeout" : "unreachable");
186
+ }
187
+ finally {
188
+ clearTimeout(timer);
189
+ if (this.inflight === controller)
190
+ this.inflight = undefined;
191
+ }
192
+ }
193
+ drop(reason) {
194
+ this.dropped += 1;
195
+ const first = !this.loggedReasons.has(reason);
196
+ this.loggedReasons.add(reason);
197
+ if (first || this.dropped % 100 === 0) {
198
+ try {
199
+ (this.options.log ?? forwarderLog).info({ event: "run_steps.forward_dropped", reason, droppedParts: this.dropped }, "coding proxy: run steps not delivered to the control plane");
200
+ }
201
+ catch {
202
+ // logging must never break the relay
203
+ }
204
+ }
205
+ }
206
+ }
@@ -14,6 +14,19 @@ import type { ProxyAuditSink } from "./types.js";
14
14
  * JSON). Exported so its header-mapping can be unit-tested directly,
15
15
  * without needing a live upstream. */
16
16
  export declare function createRegistryUpstream(pinned: typeof globalThis.fetch): UpstreamFetch;
17
+ /**
18
+ * WARDBY_MCP_GATEWAY_URL: the native gateway's base URL, which the /mcp route forwards to (its
19
+ * /coding-mcp route). Unset or empty: /mcp answers 503 mcp_unavailable. Anything but an http(s) URL
20
+ * without credentials, query or fragment stops start-up (the value is not echoed).
21
+ */
22
+ export declare function loadMcpGatewayUrl(env: NodeJS.ProcessEnv): string | undefined;
23
+ /**
24
+ * WARDBY_RUN_STEPS_URL: the control plane's run-steps listener (WARDBY_RUN_STEPS_LISTEN on the
25
+ * server), which the proxy posts coding runs' steps to. Unset or empty: coding runs record no steps.
26
+ * Anything but an http(s) origin without credentials, path, query or fragment stops start-up (the
27
+ * value is not echoed, so a credential in it never reaches a log).
28
+ */
29
+ export declare function loadRunStepsUrl(env: NodeJS.ProcessEnv): string | undefined;
17
30
  export interface CodingProxyRuntimeOptions {
18
31
  db: PrismaClient;
19
32
  /** The package registry's own client, so its traffic (many concurrent
@@ -1,6 +1,7 @@
1
1
  import { loadDependencyCacheConfig } from "../../config/dependency-cache.js";
2
2
  import { createSimulatorModelFetch, createSimulatorRegistryFetch, startupTestSimulators, } from "../../config/test-simulators.js";
3
3
  import { logger } from "../../core/logger.js";
4
+ import { loadRunStepsLevel } from "../../core/run-steps/config.js";
4
5
  import { REGISTRY_ADAPTERS } from "../../coding/registry/adapters.js";
5
6
  import { CODING_PROXY_ALIAS, CODING_PROXY_DENY_PORT, CODING_PROXY_PORT } from "../jobs/docker-isolation.js";
6
7
  import { createBlobStore } from "../storage/index.js";
@@ -10,9 +11,11 @@ import { ModelRequestSeal } from "./dependency-cache/model-request-seal.js";
10
11
  import { DependencyCacheService } from "./dependency-cache/service.js";
11
12
  import { startDenyPortListener } from "./deny-port.js";
12
13
  import { EnvironmentCredentialResolver } from "./environment-credentials.js";
14
+ import { createMcpRoute } from "./mcp-route.js";
13
15
  import { createMockUpstream, mockUpstreamFromEnv } from "./mock-upstream.js";
14
16
  import { CodingProxy } from "./proxy.js";
15
17
  import { PrismaProxyLedger } from "./prisma-ledger.js";
18
+ import { RunStepForwarder } from "./run-step-forwarder.js";
16
19
  import { OsvAudit } from "./registry/audit.js";
17
20
  import { PrismaRegistryStore } from "./registry/prisma-store.js";
18
21
  import { DEFAULT_PLAN_MAX_ENTRIES, DEFAULT_PLAN_TIMEOUT_MS } from "./registry/plan.js";
@@ -44,6 +47,60 @@ export function createRegistryUpstream(pinned) {
44
47
  },
45
48
  });
46
49
  }
50
+ /**
51
+ * WARDBY_MCP_GATEWAY_URL: the native gateway's base URL, which the /mcp route forwards to (its
52
+ * /coding-mcp route). Unset or empty: /mcp answers 503 mcp_unavailable. Anything but an http(s) URL
53
+ * without credentials, query or fragment stops start-up (the value is not echoed).
54
+ */
55
+ export function loadMcpGatewayUrl(env) {
56
+ const value = env.WARDBY_MCP_GATEWAY_URL?.trim();
57
+ if (!value)
58
+ return undefined;
59
+ let url;
60
+ try {
61
+ url = new URL(value);
62
+ }
63
+ catch {
64
+ url = undefined;
65
+ }
66
+ if (!url ||
67
+ (url.protocol !== "http:" && url.protocol !== "https:") ||
68
+ url.username ||
69
+ url.password ||
70
+ url.search ||
71
+ url.hash) {
72
+ throw new Error("WARDBY_MCP_GATEWAY_URL must be an http(s) URL without credentials, query or fragment");
73
+ }
74
+ return url.href.replace(/\/+$/, "");
75
+ }
76
+ /**
77
+ * WARDBY_RUN_STEPS_URL: the control plane's run-steps listener (WARDBY_RUN_STEPS_LISTEN on the
78
+ * server), which the proxy posts coding runs' steps to. Unset or empty: coding runs record no steps.
79
+ * Anything but an http(s) origin without credentials, path, query or fragment stops start-up (the
80
+ * value is not echoed, so a credential in it never reaches a log).
81
+ */
82
+ export function loadRunStepsUrl(env) {
83
+ const value = env.WARDBY_RUN_STEPS_URL?.trim();
84
+ if (!value)
85
+ return undefined;
86
+ let url;
87
+ try {
88
+ url = new URL(value);
89
+ }
90
+ catch {
91
+ url = undefined;
92
+ }
93
+ if (!url ||
94
+ (url.protocol !== "http:" && url.protocol !== "https:") ||
95
+ url.username ||
96
+ url.password ||
97
+ url.pathname !== "/" ||
98
+ url.search ||
99
+ url.hash) {
100
+ throw new Error("WARDBY_RUN_STEPS_URL must be an http(s) URL without credentials, path, query or fragment");
101
+ }
102
+ return url.origin;
103
+ }
47
104
  /** Starts the trusted proxy with the fixed worker-only Docker endpoint, plus the deny port the enforcement witness probes. */
48
105
  export async function startConfiguredCodingProxy(options) {
49
106
  const env = options.env ?? process.env;
@@ -57,6 +114,12 @@ export async function startConfiguredCodingProxy(options) {
57
114
  const audit = mock
58
115
  ? (event) => options.audit?.({ ...event, mockUpstream: true })
59
116
  : options.audit;
117
+ // Throws on an invalid setting, before anything listens.
118
+ const mcpGatewayUrl = loadMcpGatewayUrl(env);
119
+ // Throws on an invalid setting, before anything listens.
120
+ const runStepsUrl = loadRunStepsUrl(env);
121
+ // Throws on an invalid WARDBY_RUN_STEPS. At off no step text leaves the proxy.
122
+ const runStepsCeiling = loadRunStepsLevel(env);
60
123
  // Throws (test_simulators_refused) on any refusal, before anything listens.
61
124
  const simulators = startupTestSimulators("coding-proxy", env);
62
125
  const buildPinnedFetch = options.createPinnedFetch ?? createPinnedProxyFetch;
@@ -123,8 +186,13 @@ export async function startConfiguredCodingProxy(options) {
123
186
  logger.warn({ module: "coding-proxy-runtime", event: "proxy.dependency_cache_no_blob_store" }, "coding proxy: WARDBY_DEPENDENCY_CACHE=true but no BLOB_STORE is configured; the dependency cache is off");
124
187
  }
125
188
  const seal = modelRequestSeal;
189
+ // Coding runs' steps go to the control plane only when WARDBY_RUN_STEPS_URL is set and
190
+ // WARDBY_RUN_STEPS is not off; otherwise no
191
+ // submitter is passed and the proxy does no step extraction.
192
+ const steps = runStepsUrl && runStepsCeiling !== "off" ? new RunStepForwarder({ url: runStepsUrl }) : undefined;
193
+ const ledger = new PrismaProxyLedger(options.db);
126
194
  const proxy = new CodingProxy({
127
- ledger: new PrismaProxyLedger(options.db),
195
+ ledger,
128
196
  credentials: new EnvironmentCredentialResolver(env),
129
197
  audit,
130
198
  // WARDBY_TEST_SIMULATORS (test only; refused alongside the mock upstream): the scripted model,
@@ -136,6 +204,8 @@ export async function startConfiguredCodingProxy(options) {
136
204
  : {}),
137
205
  // The run's first model request closes its dependency cache (no-op for a run with it off).
138
206
  ...(seal ? { onModelRequest: (runId) => seal.onModelRequest(runId) } : {}),
207
+ ...(steps ? { steps } : {}),
208
+ runStepsCeiling,
139
209
  });
140
210
  const startServer = options.startServer ?? startCodingProxyServer;
141
211
  let server;
@@ -146,11 +216,13 @@ export async function startConfiguredCodingProxy(options) {
146
216
  expectedHost: `${CODING_PROXY_ALIAS}:${CODING_PROXY_PORT}`,
147
217
  registry,
148
218
  ...(dependencyCache ? { dependencyCache } : {}),
219
+ mcp: createMcpRoute({ ledger, gatewayUrl: mcpGatewayUrl }),
149
220
  onRequest: options.onRequest,
150
221
  });
151
222
  }
152
223
  catch (error) {
153
224
  eviction?.stop();
225
+ await steps?.close();
154
226
  throw error;
155
227
  }
156
228
  let deny;
@@ -161,15 +233,26 @@ export async function startConfiguredCodingProxy(options) {
161
233
  // A proxy without its witness must not stay up: the launcher's gate would never be able to
162
234
  // prove a policy is enforced, and every launch would fail at the gate instead of at startup.
163
235
  eviction?.stop();
164
- await server.close();
236
+ try {
237
+ await server.close();
238
+ }
239
+ finally {
240
+ await steps?.close();
241
+ }
165
242
  throw error;
166
243
  }
167
244
  return {
168
245
  port: server.port,
169
246
  close: async () => {
170
247
  eviction?.stop();
171
- await deny.close();
172
- await server.close();
248
+ try {
249
+ await deny.close();
250
+ await server.close();
251
+ }
252
+ finally {
253
+ // Last: requests are done, so this is the final chance to hand over queued steps (bounded).
254
+ await steps?.close();
255
+ }
173
256
  },
174
257
  };
175
258
  }
@@ -1,3 +1,4 @@
1
+ import { type IncomingMessage, type ServerResponse } from "node:http";
1
2
  import type { ProxyAuditEvent } from "./types.js";
2
3
  import { type CodingProxy } from "./proxy.js";
3
4
  import { type RegistryService } from "./registry/service.js";
@@ -19,6 +20,8 @@ export interface CodingProxyServerConfig {
19
20
  dependencyCache?: Pick<DependencyCacheService, "handle" | "uploadMs">;
20
21
  /** Time to receive a whole request (default HTTP_LIMITS.requestMs); a cache upload gets its upload window instead. */
21
22
  requestMs?: number;
23
+ /** The worker's MCP route (`/mcp`, createMcpRoute); absent, /mcp is not found. */
24
+ mcp?: (request: IncomingMessage, response: ServerResponse) => Promise<void>;
22
25
  onRequest?: (event: {
23
26
  protocol: ProxyProtocol | "other";
24
27
  status: number;
@@ -6,6 +6,7 @@ import { logger } from "../../core/logger.js";
6
6
  import { CodingProxyError, PROXY_MAX_BODY_BYTES } from "./proxy.js";
7
7
  import { RegistryError } from "../../coding/registry/types.js";
8
8
  import { MAX_PLAN_BODY_BYTES } from "./registry/service.js";
9
+ import { MCP_ROUTE_PATH } from "./mcp-route.js";
9
10
  const proxyLog = logger.child({ module: "coding-proxy" });
10
11
  /**
11
12
  * Writes a proxy audit event to the operator log. The events carry ids,
@@ -266,6 +267,15 @@ export async function startCodingProxyServer(proxy, config) {
266
267
  });
267
268
  return;
268
269
  }
270
+ if (request.url === MCP_ROUTE_PATH || request.url?.startsWith(`${MCP_ROUTE_PATH}?`)) {
271
+ if (!config.mcp) {
272
+ closeIfBodyUnread(request, response);
273
+ sendError(response, 404, "not_found");
274
+ return;
275
+ }
276
+ await config.mcp(request, response);
277
+ return;
278
+ }
269
279
  if (request.method === "HEAD" && request.url === "/api/hello") {
270
280
  response.writeHead(200, { "cache-control": "no-store" }).end();
271
281
  return;
@@ -1,6 +1,8 @@
1
1
  import { type CatalogEntry } from "../llm/catalog-types.js";
2
+ import type { RunStepsLevel } from "../../core/run-steps/config.js";
2
3
  import type { ModelPricing } from "../llm/pricing-core.js";
3
4
  import type { ProxyRefusalDetails } from "../../core/budget-events.js";
5
+ import type { McpToolPin } from "../../core/mcp-tools.js";
4
6
  export type ProxySessionStatus = "active" | "cancelled";
5
7
  export type ProxyRequestStatus = "reserved" | "completed" | "released" | "uncertain";
6
8
  export type ProxyProtocol = "openai-responses" | "anthropic-messages";
@@ -46,6 +48,14 @@ export interface ProxySession {
46
48
  upstreamFailure?: string | null;
47
49
  /** The run's catalog entry, copied at session creation; null on sessions from before the catalog. */
48
50
  terms?: ProxyModelTerms | null;
51
+ /** The run's pinned MCP tools (no URLs or secrets); null when the run has none, and on older sessions. */
52
+ mcpTools: McpToolPin[] | null;
53
+ /** SHA-256 (base64url) of the run's MCP token; null when the run has no MCP tools. */
54
+ mcpTokenHash: string | null;
55
+ /** The run's MCP tools/call count so far. */
56
+ mcpCallCount: number;
57
+ /** The run's Run.runStepsLevel, copied at session creation; null on sessions from before run steps detail. */
58
+ runStepsLevel: RunStepsLevel | null;
49
59
  }
50
60
  export interface ProxyRequest {
51
61
  id: string;
@@ -89,6 +99,12 @@ export interface CreateProxySessionInput {
89
99
  budgetUsd: number;
90
100
  registryTokenHash: string;
91
101
  terms?: ProxyModelTerms;
102
+ /** The run's pinned MCP tools; set together with mcpTokenHash, and only when there is at least one. */
103
+ mcpTools?: McpToolPin[];
104
+ /** SHA-256 (base64url) of the run's MCP token (mcpTokenHash in src/coding/mcp-token.ts). */
105
+ mcpTokenHash?: string;
106
+ /** The run's level, fixed at dispatch; absent counts as summary. */
107
+ runStepsLevel?: RunStepsLevel;
92
108
  }
93
109
  export interface ReserveProxyRequestInput {
94
110
  id: string;
@@ -122,6 +138,8 @@ export type ReserveProxyRequestResult = {
122
138
  export interface ProxyLedger {
123
139
  createSession(input: CreateProxySessionInput): Promise<void>;
124
140
  findSessionByCapabilityHash(capabilityHash: string): Promise<ProxySession | null>;
141
+ /** The session whose MCP token hashes to this; any status (the caller checks it is active and in time). */
142
+ findSessionByMcpTokenHash(mcpTokenHash: string): Promise<ProxySession | undefined>;
125
143
  reserve(input: ReserveProxyRequestInput): Promise<ReserveProxyRequestResult>;
126
144
  complete(requestId: string, usage: ProxyUsage, actualCostUsd: number, upstreamStatus: number): Promise<ProxyRequest>;
127
145
  release(requestId: string, upstreamStatus: number): Promise<void>;
@@ -117,9 +117,35 @@ export interface EngineRunContext {
117
117
  onText?: (delta: string) => void;
118
118
  /** Called after each model turn that completed without error; the engine awaits it. */
119
119
  onProgress?: (progress: EngineProgress) => Promise<void>;
120
+ /**
121
+ * Receives each model turn's text and tool calls, then the turn's tool results, as the engine saw
122
+ * them. The runner turns them into the run's steps (src/core/run-steps); the engine never imports
123
+ * that code, so the sandbox worker image does not ship it. Synchronous and not awaited. A replay
124
+ * delivers the same call ids again; the writer drops the repeats.
125
+ */
126
+ onTurn?: (event: EngineTurnEvent) => void;
120
127
  /** See StepRunner. Optional; the in-process executor leaves it unset. */
121
128
  step?: StepRunner;
122
129
  }
130
+ /** What EngineRunContext.onTurn receives: a turn's output, then (if it called tools) their results in call order. */
131
+ export type EngineTurnEvent = {
132
+ kind: "turn";
133
+ /** 1-based turn number. */
134
+ turn: number;
135
+ text: string;
136
+ toolCalls: readonly {
137
+ id: string;
138
+ name: string;
139
+ argsJson: string;
140
+ }[];
141
+ } | {
142
+ kind: "results";
143
+ turn: number;
144
+ toolCalls: readonly {
145
+ id: string;
146
+ }[];
147
+ results: readonly string[];
148
+ };
123
149
  export type EngineStatus = "succeeded" | "budget_exhausted" | "refused" | "failed";
124
150
  export interface EngineResult {
125
151
  status: EngineStatus;
@@ -26,6 +26,7 @@ import { RunTranscriptRecorder, loadTranscriptMaxBytes, loadTranscriptRetentionD
26
26
  import { loadAgentSecretValues } from "../../core/secret-guard.js";
27
27
  import { createBlobStore } from "../storage/index.js";
28
28
  import { ContainerExecutor, PrismaContainerExecutionStore, RunCapabilityVault } from "./container.js";
29
+ import { loadMcpToolPins } from "../../core/mcp-tools.js";
29
30
  import { PrismaExecutionKindResolver, RoutingExecutor } from "./routing.js";
30
31
  import { createRepoAccessGate } from "../../core/repo-access.js";
31
32
  import { buildIssueTrackers } from "../issue-tracker/index.js";
@@ -116,6 +117,7 @@ export function buildConfiguredExecutor(options) {
116
117
  config: kubernetes,
117
118
  workspaceRoot,
118
119
  resolveCapability: (runId) => capabilities.get(runId),
120
+ resolveMcpToken: (runId) => capabilities.mcpToken(runId),
119
121
  readyTimeoutMs: kubernetes.readyTimeoutMs,
120
122
  enforcementExecTimeoutMs: kubernetes.enforcementExecTimeoutMs,
121
123
  power: options.power ?? DISABLED_POWER,
@@ -140,6 +142,7 @@ export function buildConfiguredExecutor(options) {
140
142
  workspaceRoot,
141
143
  proxyContainer: proxyContainer,
142
144
  resolveCapability: (runId) => capabilities.get(runId),
145
+ resolveMcpToken: (runId) => capabilities.mcpToken(runId),
143
146
  isRunActive: async (runId, handle) => {
144
147
  const coding = await options.db.codingRun.findUnique({
145
148
  where: { runId },
@@ -177,6 +180,8 @@ export function buildConfiguredExecutor(options) {
177
180
  vcs,
178
181
  sessions,
179
182
  capabilities,
183
+ // Remote MCP servers: a coding run gets its agent's pinned tools and an MCP token ([] when off).
184
+ mcpToolPins: (agentId, pinOptions) => loadMcpToolPins(options.db, agentId, pinOptions),
180
185
  artifactRoot,
181
186
  workerImage: config.workerImage,
182
187
  claudeWorkerImage: config.claudeWorkerImage,
@@ -1,6 +1,8 @@
1
+ import type { RunStepsLevel } from "../../core/run-steps/config.js";
1
2
  import { type PackageAuditEntry, type PackageAuditMode } from "../../coding/registry/audit-mode.js";
2
3
  import type { PrismaClient } from "#prisma";
3
4
  import type { RunTranscriptRecorder } from "../../coding/transcript/recorder.js";
5
+ import { type McpToolPin } from "../../core/mcp-tools.js";
4
6
  import { type CodingProvider } from "../../coding/provider.js";
5
7
  import { type TopUpOutcome } from "../../core/budget-topup.js";
6
8
  import type { IssueTrackerRegistry } from "../issue-tracker/types.js";
@@ -100,6 +102,8 @@ export type StoredDescriptionBaseline = DescriptionBaseline & {
100
102
  legacySource?: LegacyDescriptionSource;
101
103
  };
102
104
  export interface ContainerRunSnapshot {
105
+ /** Run.runStepsLevel, fixed at dispatch; absent on older snapshots = summary. */
106
+ runStepsLevel?: RunStepsLevel | null;
103
107
  runId: string;
104
108
  agentId: string;
105
109
  status: string;
@@ -172,6 +176,12 @@ export interface ContainerRunSnapshot {
172
176
  profileRepository: string | null;
173
177
  /** How the profile's repository was authorized (CodingAgentProfile.repositoryAuthorizedVia). */
174
178
  repositoryAuthorizedVia: string | null;
179
+ /** The run's repository was chosen from the agent's list (CodingRun.viaRepositoryLink). */
180
+ viaRepositoryLink: boolean;
181
+ /** How the agent's current write link for the run's repository was authorized; null when there is none. */
182
+ repositoryLinkAuthorizedVia: string | null;
183
+ /** The agent's write link for the run's repository still exists (always false for a run not made via a link). */
184
+ repositoryLinkExists: boolean;
175
185
  /** The run's catalog entry recorded at dispatch (Run.pricingSnapshot); null on runs from before the catalog. */
176
186
  pricingVersion: string | null;
177
187
  pricingSnapshot: unknown;
@@ -327,6 +337,12 @@ export interface CodingSessionController {
327
337
  budgetUsd: number;
328
338
  /** The run's catalog entry; the proxy prices and shapes the run's requests from it. */
329
339
  terms?: ProxyModelTerms;
340
+ /** The run's pinned MCP tools; set with mcpTokenHash, only when the agent has at least one. */
341
+ mcpTools?: McpToolPin[];
342
+ /** The hash of the run's MCP token (src/coding/mcp-token.ts); the token itself stays in the vault. */
343
+ mcpTokenHash?: string;
344
+ /** The run's run-steps level (Run.runStepsLevel); the proxy reads tool output only at detail. */
345
+ runStepsLevel?: RunStepsLevel;
330
346
  }): Promise<{
331
347
  id: string;
332
348
  capability: string;
@@ -337,11 +353,19 @@ export interface CodingSessionController {
337
353
  /** The code of the first model-provider failure the proxy relayed for this session, if any. */
338
354
  upstreamFailure?(sessionId: string): Promise<string | null>;
339
355
  }
340
- /** Plain capabilities live only for the short provisioning window. */
356
+ /**
357
+ * Plain capabilities live only for the short provisioning window. A run with MCP tools keeps its
358
+ * MCP token here too, next to the capability, so the launcher delivers it the same way; both are
359
+ * deleted together.
360
+ */
341
361
  export declare class RunCapabilityVault {
342
362
  private readonly values;
363
+ private readonly mcpTokens;
343
364
  set(runId: string, capability: string): void;
344
365
  get(runId: string): Promise<string>;
366
+ setMcpToken(runId: string, token: string): void;
367
+ /** The run's MCP token; undefined for a run without MCP tools (and once the launch is over). */
368
+ mcpToken(runId: string): Promise<string | undefined>;
345
369
  delete(runId: string): void;
346
370
  }
347
371
  export interface ContainerExecutorOptions {
@@ -352,6 +376,15 @@ export interface ContainerExecutorOptions {
352
376
  vcs: VcsProvider;
353
377
  sessions: CodingSessionController;
354
378
  capabilities: RunCapabilityVault;
379
+ /**
380
+ * The agent's MCP tool pins (composition wires loadMcpToolPins; [] when WARDBY_MCP_SERVERS=off).
381
+ * Absent = no coding run gets MCP tools. A run with pins gets an MCP token, delivered as
382
+ * WARDBY_MCP_TOKEN; one without gets neither. `readOnlyOnly` comes from the run's dispatched
383
+ * provider (isCodexCoding(run.provider)), never the agent's current profile.
384
+ */
385
+ mcpToolPins?: (agentId: string, options: {
386
+ readOnlyOnly: boolean;
387
+ }) => Promise<McpToolPin[]>;
355
388
  artifactRoot: string;
356
389
  /**
357
390
  * The Codex worker for the "node" toolchain (CODING_WORKER_IMAGE). Optional on a Claude-only
@@ -725,3 +758,16 @@ export declare class ContainerExecutor implements Executor {
725
758
  }
726
759
  /** Operator-facing failure text: the message plus its cause chain, redacted. */
727
760
  export declare function describeFailure(error: unknown, depth?: number): string;
761
+ /** The agent's MCP tool pins could not be read at launch (a database error): the run fails, nothing launched. */
762
+ export declare const MCP_PINS_UNAVAILABLE_ERROR = "coding_mcp_pins_unavailable";
763
+ /** Its category: the run's error is coding_failure_mcp_pins_unavailable:<diagnostic id>. */
764
+ export declare const MCP_PINS_UNAVAILABLE_CATEGORY = "mcp_pins_unavailable";
765
+ /** Low-power mode: the native gateway serving a run's MCP tools did not wake; nothing launched. */
766
+ export declare const MCP_GATEWAY_UNAVAILABLE_CATEGORY = "mcp_gateway_unavailable";
767
+ /**
768
+ * Error-code prefixes that name their category outright, checked before the
769
+ * substring heuristics below. Those heuristics filed every `github_*` error
770
+ * (a failed PR lookup or create after the push, a token mint) under
771
+ * "workspace" because "github" contains "git".
772
+ */
773
+ export declare const CATEGORY_BY_PREFIX: ReadonlyArray<readonly [prefix: string, category: string]>;