@wardby/cli 0.7.0 → 0.8.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 (304) hide show
  1. package/.env.example +5 -5
  2. package/README.md +6 -1
  3. package/deploy/local/docker-compose.quickstart-coding.yml +6 -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 +61 -0
  9. package/dist/claude-plugin/command.js +376 -0
  10. package/dist/cli-help.d.ts +1 -1
  11. package/dist/cli-help.js +2 -1
  12. package/dist/cli.js +47 -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 +1 -1
  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 +36 -11
  46. package/dist/core/error-code.js +1 -0
  47. package/dist/core/grants.d.ts +28 -3
  48. package/dist/core/grants.js +46 -4
  49. package/dist/core/host-events.js +7 -3
  50. package/dist/core/host-status.js +2 -2
  51. package/dist/core/issue-status.js +4 -3
  52. package/dist/core/mark-ready.d.ts +1 -1
  53. package/dist/core/mark-ready.js +1 -1
  54. package/dist/core/mcp-client/client.d.ts +47 -0
  55. package/dist/core/mcp-client/client.js +141 -0
  56. package/dist/core/mcp-client/jsonrpc.d.ts +15 -0
  57. package/dist/core/mcp-client/jsonrpc.js +54 -0
  58. package/dist/core/mcp-client/results.d.ts +13 -0
  59. package/dist/core/mcp-client/results.js +42 -0
  60. package/dist/core/mcp-servers.d.ts +90 -0
  61. package/dist/core/mcp-servers.js +230 -0
  62. package/dist/core/mcp-tools.d.ts +108 -0
  63. package/dist/core/mcp-tools.js +287 -0
  64. package/dist/core/merge-order-check.js +38 -19
  65. package/dist/core/notification-dispatcher.js +2 -1
  66. package/dist/core/related-pull-requests.js +26 -10
  67. package/dist/core/review-fix.js +2 -2
  68. package/dist/core/review-host-tools.js +3 -1
  69. package/dist/core/runner.d.ts +66 -16
  70. package/dist/core/runner.js +281 -73
  71. package/dist/core/secret-broker-config.d.ts +2 -0
  72. package/dist/core/secret-broker-config.js +1 -1
  73. package/dist/core/secret-guard.d.ts +5 -2
  74. package/dist/core/secret-guard.js +22 -7
  75. package/dist/core/secrets.d.ts +11 -1
  76. package/dist/core/secrets.js +27 -1
  77. package/dist/core/serializable-retry.d.ts +18 -0
  78. package/dist/core/serializable-retry.js +43 -0
  79. package/dist/core/tool-admin.d.ts +5 -1
  80. package/dist/core/tool-admin.js +7 -2
  81. package/dist/core/tool-names.d.ts +13 -0
  82. package/dist/core/tool-names.js +20 -0
  83. package/dist/core/webhooks.d.ts +4 -0
  84. package/dist/core/webhooks.js +68 -49
  85. package/dist/core/workflow-run-events.js +2 -1
  86. package/dist/generated/prisma/browser.d.ts +31 -0
  87. package/dist/generated/prisma/client.d.ts +31 -0
  88. package/dist/generated/prisma/commonInputTypes.d.ts +50 -50
  89. package/dist/generated/prisma/internal/class.d.ts +66 -0
  90. package/dist/generated/prisma/internal/class.js +4 -4
  91. package/dist/generated/prisma/internal/prismaNamespace.d.ts +530 -1
  92. package/dist/generated/prisma/internal/prismaNamespace.js +75 -2
  93. package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +79 -0
  94. package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +75 -2
  95. package/dist/generated/prisma/models/Agent.d.ts +271 -0
  96. package/dist/generated/prisma/models/AgentMcpServer.d.ts +1412 -0
  97. package/dist/generated/prisma/models/AgentMcpServer.js +1 -0
  98. package/dist/generated/prisma/models/AgentRepository.d.ts +40 -1
  99. package/dist/generated/prisma/models/AuthUser.d.ts +1 -1
  100. package/dist/generated/prisma/models/CodingAgentProfile.d.ts +0 -7
  101. package/dist/generated/prisma/models/CodingProxySession.d.ts +119 -2
  102. package/dist/generated/prisma/models/CodingRun.d.ts +76 -1
  103. package/dist/generated/prisma/models/HostProvider.d.ts +2176 -0
  104. package/dist/generated/prisma/models/HostProvider.js +1 -0
  105. package/dist/generated/prisma/models/HostProviderCredential.d.ts +1285 -0
  106. package/dist/generated/prisma/models/HostProviderCredential.js +1 -0
  107. package/dist/generated/prisma/models/HostProviderOperation.d.ts +1470 -0
  108. package/dist/generated/prisma/models/HostProviderOperation.js +1 -0
  109. package/dist/generated/prisma/models/HostProviderTrustedAccount.d.ts +1145 -0
  110. package/dist/generated/prisma/models/HostProviderTrustedAccount.js +1 -0
  111. package/dist/generated/prisma/models/McpServer.d.ts +1500 -0
  112. package/dist/generated/prisma/models/McpServer.js +1 -0
  113. package/dist/generated/prisma/models/NativeGatewayCall.d.ts +39 -1
  114. package/dist/generated/prisma/models/Principal.d.ts +167 -0
  115. package/dist/generated/prisma/models/ResourceGrant.d.ts +1 -1
  116. package/dist/generated/prisma/models/Secret.d.ts +387 -0
  117. package/dist/generated/prisma/models/Tool.d.ts +223 -1
  118. package/dist/generated/prisma/models.d.ts +6 -0
  119. package/dist/help/search.js +12 -3
  120. package/dist/help-index.json +719 -47
  121. package/dist/import/create.js +16 -1
  122. package/dist/import/neutral-schema.d.ts +1 -0
  123. package/dist/import/neutral-schema.js +2 -0
  124. package/dist/mcp/auth/grants-cli.d.ts +6 -4
  125. package/dist/mcp/auth/grants-cli.js +42 -3
  126. package/dist/mcp/auth/resource-server.d.ts +3 -1
  127. package/dist/mcp/auth/resource-server.js +17 -2
  128. package/dist/mcp/context.d.ts +3 -0
  129. package/dist/mcp/index.js +11 -0
  130. package/dist/mcp/tools/agents.js +100 -15
  131. package/dist/mcp/tools/grants.js +66 -28
  132. package/dist/mcp/tools/host-providers.d.ts +2 -0
  133. package/dist/mcp/tools/host-providers.js +215 -0
  134. package/dist/mcp/tools/mcp-servers.d.ts +26 -0
  135. package/dist/mcp/tools/mcp-servers.js +481 -0
  136. package/dist/mcp/tools/repositories.js +50 -10
  137. package/dist/mcp/tools/runs.js +178 -18
  138. package/dist/mcp/tools/secret-elicitation.d.ts +2 -0
  139. package/dist/mcp/tools/secret-elicitation.js +25 -2
  140. package/dist/mcp/tools/secrets.js +38 -4
  141. package/dist/mcp/tools/tools.d.ts +6 -4
  142. package/dist/mcp/tools/tools.js +40 -8
  143. package/dist/mcp/tools/trigger.js +28 -6
  144. package/dist/mcp/transport/http-limits.d.ts +2 -0
  145. package/dist/mcp/transport/http-limits.js +6 -2
  146. package/dist/mcp/webhooks/ingress.js +10 -0
  147. package/dist/native-worker/gateway-server.d.ts +14 -0
  148. package/dist/native-worker/gateway-server.js +28 -7
  149. package/dist/native-worker/gateway.d.ts +2 -0
  150. package/dist/native-worker/gateway.js +5 -1
  151. package/dist/native-worker/http-server.d.ts +4 -0
  152. package/dist/native-worker/http-server.js +51 -0
  153. package/dist/native-worker/ledger.d.ts +6 -2
  154. package/dist/native-worker/ledger.js +5 -4
  155. package/dist/native-worker/protocol.d.ts +1 -0
  156. package/dist/native-worker/protocol.js +4 -1
  157. package/dist/providers/coding-proxy/capability-hash.d.ts +2 -0
  158. package/dist/providers/coding-proxy/capability-hash.js +5 -0
  159. package/dist/providers/coding-proxy/mcp-route.d.ts +34 -0
  160. package/dist/providers/coding-proxy/mcp-route.js +166 -0
  161. package/dist/providers/coding-proxy/memory-ledger.d.ts +2 -0
  162. package/dist/providers/coding-proxy/memory-ledger.js +15 -0
  163. package/dist/providers/coding-proxy/prisma-ledger.d.ts +1 -0
  164. package/dist/providers/coding-proxy/prisma-ledger.js +30 -2
  165. package/dist/providers/coding-proxy/proxy.d.ts +7 -1
  166. package/dist/providers/coding-proxy/proxy.js +134 -34
  167. package/dist/providers/coding-proxy/runtime.d.ts +6 -0
  168. package/dist/providers/coding-proxy/runtime.js +32 -1
  169. package/dist/providers/coding-proxy/server.d.ts +3 -0
  170. package/dist/providers/coding-proxy/server.js +10 -0
  171. package/dist/providers/coding-proxy/types.d.ts +13 -0
  172. package/dist/providers/executor/composition.js +5 -0
  173. package/dist/providers/executor/container.d.ts +33 -1
  174. package/dist/providers/executor/container.js +76 -7
  175. package/dist/providers/host-tools/invoke.d.ts +37 -0
  176. package/dist/providers/host-tools/invoke.js +117 -0
  177. package/dist/providers/host-tools/operations.d.ts +65 -0
  178. package/dist/providers/host-tools/operations.js +149 -0
  179. package/dist/providers/host-tools/registry.d.ts +79 -0
  180. package/dist/providers/host-tools/registry.js +362 -0
  181. package/dist/providers/host-tools/request-guard.d.ts +17 -0
  182. package/dist/providers/host-tools/request-guard.js +99 -0
  183. package/dist/providers/host-tools/self-test.d.ts +64 -0
  184. package/dist/providers/host-tools/self-test.js +142 -0
  185. package/dist/providers/jobs/docker-isolation.d.ts +2 -2
  186. package/dist/providers/jobs/docker-isolation.js +30 -8
  187. package/dist/providers/jobs/docker.d.ts +8 -0
  188. package/dist/providers/jobs/docker.js +33 -9
  189. package/dist/providers/jobs/kubernetes-isolation.d.ts +3 -1
  190. package/dist/providers/jobs/kubernetes-isolation.js +15 -2
  191. package/dist/providers/jobs/kubernetes.d.ts +5 -0
  192. package/dist/providers/jobs/kubernetes.js +6 -1
  193. package/dist/providers/jobs/types.d.ts +6 -0
  194. package/dist/providers/review-host/types.d.ts +6 -4
  195. package/dist/providers/review-host/types.js +1 -1
  196. package/dist/providers/vcs/git.d.ts +6 -2
  197. package/dist/providers/vcs/git.js +15 -4
  198. package/dist/providers/vcs/github.d.ts +2 -71
  199. package/dist/providers/vcs/github.js +8 -11
  200. package/dist/providers/vcs/index.d.ts +1 -1
  201. package/dist/providers/vcs/index.js +1 -1
  202. package/dist/providers/vcs/remote.d.ts +4 -0
  203. package/dist/providers/vcs/remote.js +7 -1
  204. package/dist/providers/vcs/routing.d.ts +12 -6
  205. package/dist/providers/vcs/routing.js +21 -6
  206. package/dist/providers/vcs/types.d.ts +71 -3
  207. package/dist/quickstart/index.d.ts +5 -0
  208. package/dist/quickstart/index.js +21 -0
  209. package/dist/quickstart-images.json +1 -1
  210. package/dist/sandbox/eval-core.d.ts +12 -2
  211. package/dist/sandbox/eval-core.js +20 -4
  212. package/dist/sandbox/host-functions.d.ts +4 -0
  213. package/dist/sandbox/host-functions.js +7 -2
  214. package/dist/sandbox/limits.d.ts +4 -1
  215. package/dist/sandbox/limits.js +4 -1
  216. package/dist/sandbox/run-in-sandbox.d.ts +6 -0
  217. package/dist/sandbox/run-in-sandbox.js +14 -2
  218. package/dist/sandbox/safe-fetch.d.ts +2 -0
  219. package/dist/sandbox/safe-fetch.js +1 -1
  220. package/dist/sandbox/secret-broker.d.ts +1 -0
  221. package/dist/sandbox/secret-broker.js +1 -1
  222. package/dist/sandbox/tool-timeout.d.ts +32 -0
  223. package/dist/sandbox/tool-timeout.js +52 -0
  224. package/dist/sandbox/user-tool.d.ts +8 -1
  225. package/dist/sandbox/user-tool.js +9 -3
  226. package/dist/viewer/graph.js +2 -1
  227. package/dist/wardby-bin.js +5 -0
  228. package/docs/README.md +11 -1
  229. package/docs/agent-recipes.md +37 -4
  230. package/docs/architecture-runtime.md +1 -1
  231. package/docs/claude-code-plugin.md +164 -0
  232. package/docs/code-review-agents.md +6 -0
  233. package/docs/coding-agent-setup.md +77 -1
  234. package/docs/coding-worker-byo-images.md +4 -2
  235. package/docs/coding-worker-isolation.md +78 -7
  236. package/docs/debugging-agent-runs.md +242 -0
  237. package/docs/getting-started-gke.md +5 -4
  238. package/docs/getting-started-identity-provider.md +43 -37
  239. package/docs/getting-started.md +8 -0
  240. package/docs/host-providers.md +294 -0
  241. package/docs/mcp-servers.md +254 -0
  242. package/docs/native-sandbox.md +25 -9
  243. package/docs/security-deployment.md +110 -31
  244. package/docs/tool-secrets.md +3 -0
  245. package/docs/viewer-api.md +6 -0
  246. package/docs/viewer-api.openapi.yml +1 -1
  247. package/help/admin-viewer.md +4 -0
  248. package/help/agent-recipes.md +33 -2
  249. package/help/brokered-secrets.md +3 -0
  250. package/help/build-claude-code-image.md +1 -1
  251. package/help/claude-code-run-pane.md +40 -0
  252. package/help/errors/coding-mcp-pins-unavailable.md +35 -0
  253. package/help/errors/database-auth-invalid.md +1 -1
  254. package/help/errors/host-provider-error.md +40 -0
  255. package/help/errors/host-tool-failed.md +36 -0
  256. package/help/errors/mcp-call-limit.md +22 -0
  257. package/help/errors/mcp-call-timeout.md +22 -0
  258. package/help/errors/mcp-server-auth-failed.md +22 -0
  259. package/help/errors/mcp-server-credentials-unavailable.md +45 -0
  260. package/help/errors/mcp-server-error.md +28 -0
  261. package/help/errors/mcp-server-unreachable.md +22 -0
  262. package/help/errors/mcp-servers-off.md +21 -0
  263. package/help/errors/mcp-tool-not-allowed.md +26 -0
  264. package/help/errors/mcp-tool-removed.md +22 -0
  265. package/help/errors/mcp-unavailable.md +25 -0
  266. package/help/errors/native-sandbox-capacity.md +1 -1
  267. package/help/errors/native-sandbox-deadline-exceeded.md +1 -1
  268. package/help/errors/native-sandbox-docker-failed.md +1 -1
  269. package/help/errors/native-sandbox-gateway-error.md +1 -1
  270. package/help/errors/native-sandbox-gateway-unavailable.md +1 -1
  271. package/help/errors/native-sandbox-image-not-pinned.md +1 -1
  272. package/help/errors/native-sandbox-isolation-mismatch.md +1 -1
  273. package/help/errors/native-sandbox-network-unenforced.md +1 -1
  274. package/help/errors/native-sandbox-requires-catalog.md +1 -1
  275. package/help/errors/native-sandbox-unavailable.md +1 -1
  276. package/help/errors/native-sandbox-warm-delivery-failed.md +1 -1
  277. package/help/errors/native-sandbox-worker-exited.md +1 -1
  278. package/help/errors/native-sandbox-worker-lost.md +1 -1
  279. package/help/errors/native-sandbox-worker-unready.md +1 -1
  280. package/help/errors/repository-not-listed.md +36 -0
  281. package/help/errors/repository-required.md +36 -0
  282. package/help/errors/tool-timeout.md +38 -0
  283. package/help/host-providers.md +40 -0
  284. package/help/identity-and-access.md +4 -1
  285. package/help/mcp-servers.md +73 -0
  286. package/help/mcp.md +7 -2
  287. package/help/native-capabilities.md +27 -1
  288. package/help/native-sandbox.md +1 -1
  289. package/help/operating-agents.md +16 -0
  290. package/help/related-pull-requests.md +7 -3
  291. package/help/repo-instructions-and-skills.md +3 -1
  292. package/help/resume-runs-locally.md +3 -0
  293. package/help/stop-run.md +1 -1
  294. package/help/troubleshooting/coding-workers.md +9 -1
  295. package/help/troubleshooting/debugging-agent-runs.md +29 -0
  296. package/package.json +2 -1
  297. package/prisma/migrations/20261102000000_host_providers/migration.sql +56 -0
  298. package/prisma/migrations/20261104000000_coding_agent_repository_list/migration.sql +5 -0
  299. package/prisma/migrations/20261106000000_tool_timeout/migration.sql +4 -0
  300. package/prisma/migrations/20261107000000_mcp_servers/migration.sql +33 -0
  301. package/prisma/migrations/20261108000000_coding_mcp/migration.sql +5 -0
  302. package/prisma/migrations/20261109000000_task_run_index/migration.sql +3 -0
  303. package/prisma/schema.prisma +159 -14
  304. package/scripts/git-askpass.sh +1 -1
@@ -1,6 +1,7 @@
1
1
  import { type CatalogEntry } from "../llm/catalog-types.js";
2
2
  import type { ModelPricing } from "../llm/pricing-core.js";
3
3
  import type { ProxyRefusalDetails } from "../../core/budget-events.js";
4
+ import type { McpToolPin } from "../../core/mcp-tools.js";
4
5
  export type ProxySessionStatus = "active" | "cancelled";
5
6
  export type ProxyRequestStatus = "reserved" | "completed" | "released" | "uncertain";
6
7
  export type ProxyProtocol = "openai-responses" | "anthropic-messages";
@@ -46,6 +47,12 @@ export interface ProxySession {
46
47
  upstreamFailure?: string | null;
47
48
  /** The run's catalog entry, copied at session creation; null on sessions from before the catalog. */
48
49
  terms?: ProxyModelTerms | null;
50
+ /** The run's pinned MCP tools (no URLs or secrets); null when the run has none, and on older sessions. */
51
+ mcpTools: McpToolPin[] | null;
52
+ /** SHA-256 (base64url) of the run's MCP token; null when the run has no MCP tools. */
53
+ mcpTokenHash: string | null;
54
+ /** The run's MCP tools/call count so far. */
55
+ mcpCallCount: number;
49
56
  }
50
57
  export interface ProxyRequest {
51
58
  id: string;
@@ -89,6 +96,10 @@ export interface CreateProxySessionInput {
89
96
  budgetUsd: number;
90
97
  registryTokenHash: string;
91
98
  terms?: ProxyModelTerms;
99
+ /** The run's pinned MCP tools; set together with mcpTokenHash, and only when there is at least one. */
100
+ mcpTools?: McpToolPin[];
101
+ /** SHA-256 (base64url) of the run's MCP token (mcpTokenHash in src/coding/mcp-token.ts). */
102
+ mcpTokenHash?: string;
92
103
  }
93
104
  export interface ReserveProxyRequestInput {
94
105
  id: string;
@@ -122,6 +133,8 @@ export type ReserveProxyRequestResult = {
122
133
  export interface ProxyLedger {
123
134
  createSession(input: CreateProxySessionInput): Promise<void>;
124
135
  findSessionByCapabilityHash(capabilityHash: string): Promise<ProxySession | null>;
136
+ /** The session whose MCP token hashes to this; any status (the caller checks it is active and in time). */
137
+ findSessionByMcpTokenHash(mcpTokenHash: string): Promise<ProxySession | undefined>;
125
138
  reserve(input: ReserveProxyRequestInput): Promise<ReserveProxyRequestResult>;
126
139
  complete(requestId: string, usage: ProxyUsage, actualCostUsd: number, upstreamStatus: number): Promise<ProxyRequest>;
127
140
  release(requestId: string, upstreamStatus: number): Promise<void>;
@@ -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,7 @@
1
1
  import { type PackageAuditEntry, type PackageAuditMode } from "../../coding/registry/audit-mode.js";
2
2
  import type { PrismaClient } from "#prisma";
3
3
  import type { RunTranscriptRecorder } from "../../coding/transcript/recorder.js";
4
+ import { type McpToolPin } from "../../core/mcp-tools.js";
4
5
  import { type CodingProvider } from "../../coding/provider.js";
5
6
  import { type TopUpOutcome } from "../../core/budget-topup.js";
6
7
  import type { IssueTrackerRegistry } from "../issue-tracker/types.js";
@@ -172,6 +173,12 @@ export interface ContainerRunSnapshot {
172
173
  profileRepository: string | null;
173
174
  /** How the profile's repository was authorized (CodingAgentProfile.repositoryAuthorizedVia). */
174
175
  repositoryAuthorizedVia: string | null;
176
+ /** The run's repository was chosen from the agent's list (CodingRun.viaRepositoryLink). */
177
+ viaRepositoryLink: boolean;
178
+ /** How the agent's current write link for the run's repository was authorized; null when there is none. */
179
+ repositoryLinkAuthorizedVia: string | null;
180
+ /** The agent's write link for the run's repository still exists (always false for a run not made via a link). */
181
+ repositoryLinkExists: boolean;
175
182
  /** The run's catalog entry recorded at dispatch (Run.pricingSnapshot); null on runs from before the catalog. */
176
183
  pricingVersion: string | null;
177
184
  pricingSnapshot: unknown;
@@ -327,6 +334,10 @@ export interface CodingSessionController {
327
334
  budgetUsd: number;
328
335
  /** The run's catalog entry; the proxy prices and shapes the run's requests from it. */
329
336
  terms?: ProxyModelTerms;
337
+ /** The run's pinned MCP tools; set with mcpTokenHash, only when the agent has at least one. */
338
+ mcpTools?: McpToolPin[];
339
+ /** The hash of the run's MCP token (src/coding/mcp-token.ts); the token itself stays in the vault. */
340
+ mcpTokenHash?: string;
330
341
  }): Promise<{
331
342
  id: string;
332
343
  capability: string;
@@ -337,11 +348,19 @@ export interface CodingSessionController {
337
348
  /** The code of the first model-provider failure the proxy relayed for this session, if any. */
338
349
  upstreamFailure?(sessionId: string): Promise<string | null>;
339
350
  }
340
- /** Plain capabilities live only for the short provisioning window. */
351
+ /**
352
+ * Plain capabilities live only for the short provisioning window. A run with MCP tools keeps its
353
+ * MCP token here too, next to the capability, so the launcher delivers it the same way; both are
354
+ * deleted together.
355
+ */
341
356
  export declare class RunCapabilityVault {
342
357
  private readonly values;
358
+ private readonly mcpTokens;
343
359
  set(runId: string, capability: string): void;
344
360
  get(runId: string): Promise<string>;
361
+ setMcpToken(runId: string, token: string): void;
362
+ /** The run's MCP token; undefined for a run without MCP tools (and once the launch is over). */
363
+ mcpToken(runId: string): Promise<string | undefined>;
345
364
  delete(runId: string): void;
346
365
  }
347
366
  export interface ContainerExecutorOptions {
@@ -352,6 +371,15 @@ export interface ContainerExecutorOptions {
352
371
  vcs: VcsProvider;
353
372
  sessions: CodingSessionController;
354
373
  capabilities: RunCapabilityVault;
374
+ /**
375
+ * The agent's MCP tool pins (composition wires loadMcpToolPins; [] when WARDBY_MCP_SERVERS=off).
376
+ * Absent = no coding run gets MCP tools. A run with pins gets an MCP token, delivered as
377
+ * WARDBY_MCP_TOKEN; one without gets neither. `readOnlyOnly` comes from the run's dispatched
378
+ * provider (isCodexCoding(run.provider)), never the agent's current profile.
379
+ */
380
+ mcpToolPins?: (agentId: string, options: {
381
+ readOnlyOnly: boolean;
382
+ }) => Promise<McpToolPin[]>;
355
383
  artifactRoot: string;
356
384
  /**
357
385
  * The Codex worker for the "node" toolchain (CODING_WORKER_IMAGE). Optional on a Claude-only
@@ -725,3 +753,7 @@ export declare class ContainerExecutor implements Executor {
725
753
  }
726
754
  /** Operator-facing failure text: the message plus its cause chain, redacted. */
727
755
  export declare function describeFailure(error: unknown, depth?: number): string;
756
+ /** The agent's MCP tool pins could not be read at launch (a database error): the run fails, nothing launched. */
757
+ export declare const MCP_PINS_UNAVAILABLE_ERROR = "coding_mcp_pins_unavailable";
758
+ /** Its category: the run's error is coding_failure_mcp_pins_unavailable:<diagnostic id>. */
759
+ export declare const MCP_PINS_UNAVAILABLE_CATEGORY = "mcp_pins_unavailable";
@@ -3,6 +3,9 @@ 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 { codeProviderOf } from "../../coding/code-host.js";
6
9
  import { withPullRequestDescription } from "../../coding/pr-description-note.js";
7
10
  import { withLinkedIssue } from "../../coding/linked-issue-note.js";
8
11
  import { buildClaudeContext, MAX_RECORDED_SKIPS } from "../../coding/claude-context.js";
@@ -169,6 +172,17 @@ export class PrismaContainerExecutionStore {
169
172
  });
170
173
  if (!row?.codingRun)
171
174
  return null;
175
+ const link = row.codingRun.viaRepositoryLink
176
+ ? await this.db.agentRepository.findFirst({
177
+ where: {
178
+ agentId: row.agentId,
179
+ repository: row.codingRun.repository,
180
+ access: "write",
181
+ triggers: { isEmpty: true },
182
+ },
183
+ select: { authorizedVia: true },
184
+ })
185
+ : null;
172
186
  return {
173
187
  runId: row.id,
174
188
  agentId: row.agentId,
@@ -219,6 +233,9 @@ export class PrismaContainerExecutionStore {
219
233
  failureCategory: row.codingRun.failureCategory,
220
234
  profileRepository: row.agent.codingProfile?.repository ?? null,
221
235
  repositoryAuthorizedVia: row.agent.codingProfile?.repositoryAuthorizedVia ?? null,
236
+ viaRepositoryLink: row.codingRun.viaRepositoryLink,
237
+ repositoryLinkExists: link !== null,
238
+ repositoryLinkAuthorizedVia: link?.authorizedVia ?? null,
222
239
  pricingVersion: row.pricingVersion,
223
240
  pricingSnapshot: row.pricingSnapshot,
224
241
  };
@@ -596,9 +613,14 @@ export class PrismaContainerExecutionStore {
596
613
  return changed;
597
614
  }
598
615
  }
599
- /** Plain capabilities live only for the short provisioning window. */
616
+ /**
617
+ * Plain capabilities live only for the short provisioning window. A run with MCP tools keeps its
618
+ * MCP token here too, next to the capability, so the launcher delivers it the same way; both are
619
+ * deleted together.
620
+ */
600
621
  export class RunCapabilityVault {
601
622
  values = new Map();
623
+ mcpTokens = new Map();
602
624
  set(runId, capability) {
603
625
  if (this.values.has(runId))
604
626
  throw new Error("coding_capability_exists");
@@ -610,8 +632,18 @@ export class RunCapabilityVault {
610
632
  throw new Error("coding_capability_unavailable");
611
633
  return capability;
612
634
  }
635
+ setMcpToken(runId, token) {
636
+ if (this.mcpTokens.has(runId))
637
+ throw new Error("coding_mcp_token_exists");
638
+ this.mcpTokens.set(runId, token);
639
+ }
640
+ /** The run's MCP token; undefined for a run without MCP tools (and once the launch is over). */
641
+ async mcpToken(runId) {
642
+ return this.mcpTokens.get(runId);
643
+ }
613
644
  delete(runId) {
614
645
  this.values.delete(runId);
646
+ this.mcpTokens.delete(runId);
615
647
  }
616
648
  }
617
649
  /** A run's deduplicated served packages and refusals (see summarizeRegistryFetches), and its package audit. */
@@ -827,6 +859,20 @@ export class ContainerExecutor {
827
859
  if (TERMINAL_STATUSES.has(beforeSession.status))
828
860
  throw new Error("coding_run_no_longer_active");
829
861
  const deadlineAt = new Date(this.now().getTime() + run.timeoutSec * 1_000);
862
+ // The agent's MCP tools, pinned for the run; only a run with some gets a token. The token is
863
+ // never logged: only its hash goes to the session, the value only into the vault.
864
+ // A Codex run's shell commands can read the token, so it gets read-only tools only; decided by
865
+ // the provider the run was dispatched with, which is what jobSpec launches. A failed read fails
866
+ // the run with a fixed code (the cause goes to the operator log only, redacted).
867
+ let mcpTools;
868
+ try {
869
+ mcpTools =
870
+ (await this.options.mcpToolPins?.(run.agentId, { readOnlyOnly: isCodexCoding(run.provider) })) ?? [];
871
+ }
872
+ catch (error) {
873
+ throw new Error(MCP_PINS_UNAVAILABLE_ERROR, { cause: error });
874
+ }
875
+ const mcpToken = mcpTools.length > 0 ? mintMcpToken() : undefined;
830
876
  const session = await this.options.sessions.createSession({
831
877
  runId,
832
878
  credentialRef: this.credentialRef(run.provider),
@@ -835,15 +881,18 @@ export class ContainerExecutor {
835
881
  deadlineAt,
836
882
  budgetUsd: run.budgetUsd,
837
883
  terms: this.modelTerms(run),
884
+ ...(mcpToken ? { mcpTools, mcpTokenHash: mcpTokenHash(mcpToken) } : {}),
838
885
  });
839
886
  spendEnabled = true;
840
887
  sessionId = session.id;
841
888
  this.options.capabilities.set(runId, session.capability);
889
+ if (mcpToken)
890
+ this.options.capabilities.setMcpToken(runId, mcpToken);
842
891
  const inputArtifact = await this.writeInput(run, deadlineAt, workspace);
843
892
  const beforeLaunch = await this.requireCurrent(runId);
844
893
  if (TERMINAL_STATUSES.has(beforeLaunch.status))
845
894
  throw new Error("coding_run_no_longer_active");
846
- const spec = this.jobSpec(run, inputArtifact);
895
+ const spec = this.jobSpec(run, inputArtifact, { mcpTools: Boolean(mcpToken) });
847
896
  // Backends whose handle is derivable (Kubernetes) persist it first, so a crash between the
848
897
  // launch and the write still leaves a handle `abandon()` can stop and remove the run with.
849
898
  planned = this.options.jobs.plannedHandle?.(spec);
@@ -1005,13 +1054,21 @@ export class ContainerExecutor {
1005
1054
  * access. Throws RepoAccessError (refused before anything is cloned).
1006
1055
  */
1007
1056
  async authorizeRepository(run, opts) {
1057
+ // Checked first: a local repository is otherwise authorized by the trusted roots alone.
1058
+ if (run.viaRepositoryLink && !run.repositoryLinkExists)
1059
+ throw new RepoAccessError("repo_access_not_authorized");
1008
1060
  const sameRepository = run.profileRepository !== null && run.profileRepository === run.repository;
1061
+ const authorizedVia = run.viaRepositoryLink
1062
+ ? run.repositoryLinkAuthorizedVia
1063
+ : sameRepository
1064
+ ? run.repositoryAuthorizedVia
1065
+ : "host_permission";
1009
1066
  const decision = await this.options.repoAccess.authorizeUse({
1010
1067
  ownerId: run.ownerId,
1011
- provider: "github",
1068
+ provider: codeProviderOf(run.repository),
1012
1069
  repository: run.repository,
1013
1070
  required: requiredLevel("coding"),
1014
- authorizedVia: sameRepository ? run.repositoryAuthorizedVia : "host_permission",
1071
+ authorizedVia,
1015
1072
  retryTransient: opts.inFlight,
1016
1073
  });
1017
1074
  if (!decision.ok)
@@ -1510,7 +1567,8 @@ export class ContainerExecutor {
1510
1567
  status.number > 0) {
1511
1568
  candidates.push({ provider: "github", repository: run.repository, number: status.number });
1512
1569
  }
1513
- const closed = closingIssueNumber(description, run.repository);
1570
+ // The closing keyword names a GitHub issue: no other host's repository has one to read.
1571
+ const closed = codeProviderOf(run.repository) === "github" ? closingIssueNumber(description, run.repository) : undefined;
1514
1572
  if (closed !== undefined && !candidates.some((ref) => ref.provider === "github" && ref.number === closed)) {
1515
1573
  candidates.push({ provider: "github", repository: run.repository, number: closed });
1516
1574
  }
@@ -1724,7 +1782,8 @@ export class ContainerExecutor {
1724
1782
  if (!workspace)
1725
1783
  throw new Error("coding_workspace_lost");
1726
1784
  // The same in-flight access check a push gets: a revoked owner's run edits nothing.
1727
- await this.authorizeRepository(run, { inFlight: true });
1785
+ // Reloaded: the snapshot passed in predates the description reads above, and a link removed since then counts.
1786
+ await this.authorizeRepository(await this.requireCurrent(run.runId), { inFlight: true });
1728
1787
  const details = {
1729
1788
  summary: output.summary,
1730
1789
  tests: output.tests,
@@ -1990,7 +2049,7 @@ export class ContainerExecutor {
1990
2049
  this.emit({ stage: "queued", runId: run.runId });
1991
2050
  return false;
1992
2051
  }
1993
- jobSpec(run, inputArtifact) {
2052
+ jobSpec(run, inputArtifact, options = {}) {
1994
2053
  const provider = run.provider === "claude-code" ? "claude-code" : "codex";
1995
2054
  if (provider === "claude-code" && (!run.workerImage || !this.options.claudeToolRunnerImage)) {
1996
2055
  throw new Error("coding_provider_not_configured:claude-code");
@@ -2019,6 +2078,8 @@ export class ContainerExecutor {
2019
2078
  ...(run.dependencyCache ? { dependencyCache: true } : {}),
2020
2079
  // Claude Code only: the launcher gives the SDK's config dir, where the session grows, its own scratch.
2021
2080
  ...(run.sessionResume && provider === "claude-code" ? { sessionResume: true } : {}),
2081
+ // Only for a run with MCP tools: the launcher then delivers WARDBY_MCP_TOKEN from the vault.
2082
+ ...(options.mcpTools ? { mcpTools: true } : {}),
2022
2083
  timeoutSec: run.timeoutSec,
2023
2084
  limits: { ...this.options.limits, ...(run.workspaceDiskMb ? { diskMb: run.workspaceDiskMb } : {}) },
2024
2085
  labels: {},
@@ -2420,6 +2481,10 @@ function safeError(error) {
2420
2481
  const message = error instanceof Error ? error.message : "unknown";
2421
2482
  return `coding_executor:${redactAndTruncate(message, 200).replace(/[^A-Za-z0-9_.:-]/g, "_")}`;
2422
2483
  }
2484
+ /** The agent's MCP tool pins could not be read at launch (a database error): the run fails, nothing launched. */
2485
+ export const MCP_PINS_UNAVAILABLE_ERROR = "coding_mcp_pins_unavailable";
2486
+ /** Its category: the run's error is coding_failure_mcp_pins_unavailable:<diagnostic id>. */
2487
+ export const MCP_PINS_UNAVAILABLE_CATEGORY = "mcp_pins_unavailable";
2423
2488
  /**
2424
2489
  * Error-code prefixes that name their category outright, checked before the
2425
2490
  * substring heuristics below. Those heuristics filed every `github_*` error
@@ -2453,7 +2518,11 @@ const CATEGORY_BY_PREFIX = [
2453
2518
  ["coding_provider_not_configured", "preflight"],
2454
2519
  // A GitHub repository on a server with no GitHub App: configuration, like an unsupported service.
2455
2520
  ["vcs_github_not_configured", "preflight"],
2521
+ // The same for any other host: a repository whose provider this server has not registered.
2522
+ ["vcs_host_provider_not_configured", "preflight"],
2456
2523
  ["vcs_", "workspace"],
2524
+ // The agent's MCP tool pins could not be read at launch: nothing was launched.
2525
+ [MCP_PINS_UNAVAILABLE_ERROR, MCP_PINS_UNAVAILABLE_CATEGORY],
2457
2526
  // Local repositories (LocalRepoError codes): the same family as the vcs_ errors.
2458
2527
  ["local_", "workspace"],
2459
2528
  ["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 {};