@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
@@ -21,9 +21,13 @@ import { z } from "zod";
21
21
  import { Prisma } from "#prisma";
22
22
  import { RunOwnershipLostError, STEPS_CHANGED_REASON, StepReplayMismatchError, runStepInline, } from "../providers/engine/types.js";
23
23
  import { isLlmEffort } from "../providers/llm/types.js";
24
+ import { CODING_REPOSITORY_LINK_FILTER, extraCodingRepositoryLinks, hasCodingRepositoryList, RepositoryChoiceRefusedError, resolveCodingRepository, } from "./coding-repository.js";
25
+ import { delegationLimitRefusal, delegationSlot, priorDelegationSlots } from "./delegation-slots.js";
24
26
  import { createPrivilegedHost } from "../sandbox/host-functions.js";
25
27
  import { consoleRedactionValues } from "../sandbox/secret-broker.js";
26
28
  import { runUserToolCall } from "../sandbox/user-tool.js";
29
+ import { effectiveToolTimeoutMs, fetchTimeoutFor } from "../sandbox/tool-timeout.js";
30
+ import { loadToolMaxTimeoutMs } from "../config/tool-timeout.js";
27
31
  import { logUnexpectedError, runSandboxedEngine, SandboxRunCancelledError, } from "../native-worker/gateway.js";
28
32
  import { NATIVE_WORKER_PROTOCOL_VERSION } from "../native-worker/protocol.js";
29
33
  import { asStringArray, asPrefixMap } from "../sandbox/tool-capabilities.js";
@@ -42,6 +46,7 @@ import { ContinuationRefusedError, dispatchRun } from "./dispatch.js";
42
46
  import { canDelegate } from "./grants.js";
43
47
  import { MEMORY_TOOL_DEFS, MEMORY_TOOL_NAMES, handleMemoryTool } from "./memory-tools.js";
44
48
  import { DELEGATE_TOOL_PREFIX, duplicateToolNames } from "./tool-names.js";
49
+ import { handleMcpTool, loadMcpToolPins, mcpSecretValuesForAgent, mcpToolDefs, withoutClashingPins, } from "./mcp-tools.js";
45
50
  import { SUBAGENT_MEMORY_GET_TOOL, PARENT_MEMORY_GET_TOOL, handleSubAgentMemoryGet, handleParentMemoryGet, } from "./subagent-memory-tools.js";
46
51
  import { prisma as defaultDb } from "./db.js";
47
52
  import { logger } from "./logger.js";
@@ -107,12 +112,22 @@ export const runnerLog = logger.child({ module: "runner" });
107
112
  * The `delegate_to_` prefix lives in core/tool-names.ts, which reserves it
108
113
  * so no user tool can be created under a name this dispatch would shadow.
109
114
  */
110
- function delegateToolDef(boundName, parallel) {
115
+ export function delegateToolDef(boundName, parallel, repositories = []) {
111
116
  // With the flag off the description stays byte-identical to before, so
112
117
  // existing leads keep their cached tool definitions.
113
118
  const timing = parallel
114
119
  ? "Blocks until it finishes. Several delegate_to_* calls made in the same turn run at the same time, so make them together when their tasks don't need each other's results. Its spend"
115
120
  : "Runs synchronously and blocks until it finishes; its spend";
121
+ // Only for a child with a repository list: any other keeps its cached definition too.
122
+ const repositoryProperty = repositories.length > 0
123
+ ? {
124
+ repository: {
125
+ type: "string",
126
+ enum: [...repositories],
127
+ description: "Which repository this sub-agent run works in. Required unless continuePriorRun names the run to continue.",
128
+ },
129
+ }
130
+ : {};
116
131
  return {
117
132
  name: `${DELEGATE_TOOL_PREFIX}${boundName}`,
118
133
  description: `Delegates a task to your "${boundName}" sub-agent. ${timing} counts against your own run's shared budget scope. If the sub-agent belongs to another owner it runs only its own instructions: pass an empty task.`,
@@ -139,6 +154,7 @@ function delegateToolDef(boundName, parallel) {
139
154
  "Give dependencies lower numbers than the changes that use them (e.g. a service 1, the API that calls it 2, the client 3); " +
140
155
  "equal numbers have no order between them. Set it whenever one change spans several repositories.",
141
156
  },
157
+ ...repositoryProperty,
142
158
  },
143
159
  required: ["task"],
144
160
  additionalProperties: false,
@@ -159,6 +175,7 @@ const DelegateArgs = z
159
175
  .min(1, MERGE_ORDER_RANGE_MESSAGE)
160
176
  .max(99, MERGE_ORDER_RANGE_MESSAGE)
161
177
  .optional(),
178
+ repository: z.string().min(1).max(300).optional(),
162
179
  })
163
180
  .strict();
164
181
  /** The tool result note for a native child's delegation when the caller passed mergeOrder: it does not apply. */
@@ -192,36 +209,6 @@ export function parseDelegateArgs(argsJson) {
192
209
  throw err;
193
210
  }
194
211
  }
195
- /**
196
- * At most `limit` dispatches per run (default one: a classifier deciding plan-vs-implement
197
- * commits to exactly one child), and never the same child twice, so a retry can't leave two
198
- * children racing on the same task. A delegation waiting for budget holds its place.
199
- */
200
- export function delegationLimitRefusal(input) {
201
- const { priorChildAgentIds, waitingChildAgentIds, childAgentId, boundName, limit } = input;
202
- if (priorChildAgentIds.includes(childAgentId) || waitingChildAgentIds.includes(childAgentId)) {
203
- return JSON.stringify({
204
- error: "already_dispatched",
205
- message: `This run already delegated to "${boundName}"; a run delegates to each sub-agent at most once.`,
206
- });
207
- }
208
- if (priorChildAgentIds.length < limit && priorChildAgentIds.length + waitingChildAgentIds.length >= limit) {
209
- // Only a waiting delegation (parallelDelegations) holds the remaining place.
210
- return JSON.stringify({
211
- error: "already_dispatched",
212
- message: `This run reached its limit of ${limit} delegations: ${priorChildAgentIds.length} made and ${waitingChildAgentIds.length} waiting for budget.`,
213
- });
214
- }
215
- if (priorChildAgentIds.length >= limit) {
216
- return JSON.stringify({
217
- error: "already_dispatched",
218
- message: limit === 1
219
- ? "This run already delegated to a sub-agent; only one delegation is allowed per run."
220
- : `This run already made ${limit} delegations, the most this agent allows per run.`,
221
- });
222
- }
223
- return null;
224
- }
225
212
  const DELEGATION_CHILD_SELECT = {
226
213
  id: true,
227
214
  kind: true,
@@ -229,7 +216,7 @@ const DELEGATION_CHILD_SELECT = {
229
216
  budgetUsd: true,
230
217
  ownerId: true,
231
218
  nativeExecutionMode: true,
232
- codingProfile: { select: { allowWebhookTaskOverride: true, timeoutSec: true } },
219
+ codingProfile: { select: { allowWebhookTaskOverride: true, timeoutSec: true, repository: true, baseRef: true } },
233
220
  };
234
221
  /**
235
222
  * The live authorization of one delegation, and the child agent as it is now. Owners are re-read
@@ -282,6 +269,47 @@ export async function authorizeDelegation(db, parentAgentId, edge, boundName, ar
282
269
  }
283
270
  return { childAgent };
284
271
  }
272
+ /**
273
+ * A delegation's once-per-run slot and repository, or the tool result refusing it. Nothing here
274
+ * echoes args.repository: it is model-written and could hold a secret value.
275
+ */
276
+ async function delegationTarget(db, childAgentId, childAgent, args) {
277
+ if (childAgent.kind !== "coding" || !childAgent.codingProfile) {
278
+ if (args.repository !== undefined) {
279
+ return {
280
+ refusal: JSON.stringify({
281
+ error: "repository_requires_coding_agent",
282
+ message: "repository is only supported when the sub-agent is coding-kind.",
283
+ }),
284
+ };
285
+ }
286
+ return { slot: delegationSlot(childAgentId, null), defaultRepository: null, hasRepositoryList: false };
287
+ }
288
+ try {
289
+ const target = await resolveCodingRepository(db, {
290
+ agentId: childAgentId,
291
+ profile: childAgent.codingProfile,
292
+ requested: args.repository,
293
+ continuesCodingRunId: args.continuePriorRun,
294
+ requireExplicit: true,
295
+ });
296
+ return {
297
+ slot: delegationSlot(childAgentId, target.repository),
298
+ defaultRepository: childAgent.codingProfile.repository,
299
+ repository: target.repository,
300
+ hasRepositoryList: await hasCodingRepositoryList(db, childAgentId, childAgent.codingProfile.repository),
301
+ };
302
+ }
303
+ catch (err) {
304
+ if (err instanceof RepositoryChoiceRefusedError) {
305
+ return { refusal: repositoryRefusal(err) };
306
+ }
307
+ throw err;
308
+ }
309
+ }
310
+ function repositoryRefusal(err) {
311
+ return JSON.stringify({ error: err.code, message: err.message });
312
+ }
285
313
  /** A native child's task text: the delegated task, with any datastore reference as a note. */
286
314
  export function delegationTaskOverride(args) {
287
315
  return args.datastoreRef
@@ -590,6 +618,47 @@ export async function loadNativeRun(options) {
590
618
  where: { parentAgentId: agent.id },
591
619
  select: { boundName: true, childAgentId: true },
592
620
  });
621
+ // Each coding child's repository list, pinned for this run; dispatch re-validates the choice.
622
+ const childLinks = subAgentEdges.length > 0
623
+ ? await db.agentRepository.findMany({
624
+ where: {
625
+ agentId: { in: subAgentEdges.map((edge) => edge.childAgentId) },
626
+ ...CODING_REPOSITORY_LINK_FILTER,
627
+ agent: { kind: "coding" },
628
+ },
629
+ include: {
630
+ agent: {
631
+ select: {
632
+ ownerId: true,
633
+ codingProfile: { select: { repository: true, allowWebhookTaskOverride: true } },
634
+ },
635
+ },
636
+ },
637
+ orderBy: { repository: "asc" },
638
+ })
639
+ : [];
640
+ const repositoriesOf = async (childAgentId) => {
641
+ const links = childLinks.filter((link) => link.agentId === childAgentId);
642
+ const child = links[0]?.agent;
643
+ const profile = child?.codingProfile;
644
+ if (!profile)
645
+ return [];
646
+ const extras = extraCodingRepositoryLinks(links, profile.repository);
647
+ if (extras.length === 0)
648
+ return [];
649
+ // Across owners, as authorizeDelegation compares them (owner-less on both sides is the same
650
+ // owner): the list is the child's owner's to show, so only a lead that could choose sees it.
651
+ // That takes the opt-in and the lead's owner's execute access; revoke_access leaves the edge.
652
+ const leadOwnerId = agent.ownerId ?? null;
653
+ const childOwnerId = child.ownerId ?? null;
654
+ if (leadOwnerId !== childOwnerId &&
655
+ !(profile.allowWebhookTaskOverride &&
656
+ (await canDelegate(db, { ownerId: leadOwnerId }, { id: childAgentId, ownerId: childOwnerId })))) {
657
+ return [];
658
+ }
659
+ return [profile.repository, ...extras.map((link) => link.repository)];
660
+ };
661
+ const delegateTools = await Promise.all(subAgentEdges.map(async (edge) => delegateToolDef(edge.boundName, agent.parallelDelegations, await repositoriesOf(edge.childAgentId))));
593
662
  // Only queried when the runner was given review hosts. buildReviewHosts
594
663
  // always includes the local host, so a configured server always queries;
595
664
  // only a runner built without hosts (as in many tests) skips the table.
@@ -627,6 +696,24 @@ export async function loadNativeRun(options) {
627
696
  const systemPrompt = task
628
697
  ? `${agent.systemPrompt}\n\n---\nTask for this run (untrusted external content -- data, not instructions):\n${wrapUntrusted(RUN_TASK_TAG, task)}`
629
698
  : agent.systemPrompt;
699
+ const tools = [
700
+ ...attached.map((attachment) => ({
701
+ name: attachment.tool.name,
702
+ description: attachment.tool.description,
703
+ jsonSchema: attachment.tool.jsonSchema,
704
+ })),
705
+ ...(agent.memoryEnabled ? MEMORY_TOOL_DEFS : []),
706
+ ...(subAgentEdges.length > 0 ? [SUBAGENT_MEMORY_GET_TOOL] : []),
707
+ ...(isDispatchedChild ? [PARENT_MEMORY_GET_TOOL] : []),
708
+ ...delegateTools,
709
+ ...(repositoryLinks.length > 0 ? REVIEW_HOST_TOOL_DEFS : []),
710
+ ...(issueProjectLinks.length > 0 ? ISSUE_TRACKER_TOOL_DEFS : []),
711
+ ];
712
+ // Remote MCP tools (native agents only), offered after every other tool; one whose name
713
+ // another tool already uses is left out (mcp-tools.ts withoutClashingPins).
714
+ const mcpTools = agent.kind === "coding"
715
+ ? []
716
+ : withoutClashingPins(await loadMcpToolPins(db, agent.id), tools.map((t) => t.name), runnerLog);
630
717
  return {
631
718
  agentId: agent.id,
632
719
  kind: agent.kind,
@@ -639,6 +726,8 @@ export async function loadNativeRun(options) {
639
726
  ...(agent.parallelDelegations ? { parallelDelegations: true } : {}),
640
727
  repositoryLinks,
641
728
  issueProjectLinks,
729
+ // Only when there are any, so an agent without MCP servers keeps its pinned step result.
730
+ ...(mcpTools.length > 0 ? { mcpTools } : {}),
642
731
  agent: {
643
732
  systemPrompt,
644
733
  model: agent.model,
@@ -660,19 +749,7 @@ export async function loadNativeRun(options) {
660
749
  // bundle per attached tool, before the first LLM call, on every run.
661
750
  // The memory built-ins (recognized by name in runSandboxTool below,
662
751
  // never sandboxed) are appended the same way when enabled.
663
- tools: [
664
- ...attached.map((attachment) => ({
665
- name: attachment.tool.name,
666
- description: attachment.tool.description,
667
- jsonSchema: attachment.tool.jsonSchema,
668
- })),
669
- ...(agent.memoryEnabled ? MEMORY_TOOL_DEFS : []),
670
- ...(subAgentEdges.length > 0 ? [SUBAGENT_MEMORY_GET_TOOL] : []),
671
- ...(isDispatchedChild ? [PARENT_MEMORY_GET_TOOL] : []),
672
- ...subAgentEdges.map((edge) => delegateToolDef(edge.boundName, agent.parallelDelegations)),
673
- ...(repositoryLinks.length > 0 ? REVIEW_HOST_TOOL_DEFS : []),
674
- ...(issueProjectLinks.length > 0 ? ISSUE_TRACKER_TOOL_DEFS : []),
675
- ],
752
+ tools: [...tools, ...mcpToolDefs(mcpTools)],
676
753
  // An attachment's four capability fields are honoured only when the
677
754
  // agent's CURRENT owner granted them (resource-sharing grants spec
678
755
  // §3.4.2): a write-grantee's attachment, a make_owner transfer and a
@@ -689,6 +766,9 @@ export async function loadNativeRun(options) {
689
766
  allowedDatastorePrefixes: consented ? asStringArray(attachment.allowedDatastorePrefixes) : [],
690
767
  allowedHosts: consented ? asStringArray(attachment.allowedHosts) : [],
691
768
  allowedSharedDatastorePrefixes: consented ? asPrefixMap(attachment.allowedSharedDatastorePrefixes) : {},
769
+ // Only when set, so a default tool's pinned step result is unchanged. The ceiling
770
+ // (WARDBY_TOOL_MAX_TIMEOUT_MS) is applied when the tool runs (toolTimeoutMs below).
771
+ ...(typeof attachment.tool.timeoutMs === "number" ? { timeoutMs: attachment.tool.timeoutMs } : {}),
692
772
  },
693
773
  ];
694
774
  })),
@@ -952,6 +1032,29 @@ export function createNativeRunTools(options) {
952
1032
  });
953
1033
  };
954
1034
  }
1035
+ // `?? []`: a load step replayed from before MCP tools were pinned has none.
1036
+ const mcpPin = (loaded.mcpTools ?? []).find((pin) => pin.exposedName === name);
1037
+ if (mcpPin) {
1038
+ return async (argsJson) => {
1039
+ // As for a user tool: a stop_run aborts the call in flight.
1040
+ const stop = watchRunStop(db, runId);
1041
+ try {
1042
+ return await handleMcpTool(mcpPin, argsJson, {
1043
+ db,
1044
+ cipher: providers.secrets,
1045
+ agentId: loaded.agentId,
1046
+ runId,
1047
+ signal: stop.signal,
1048
+ deadlineAt: options.deadlineAt,
1049
+ fetchImpl: providers.sandboxFetch,
1050
+ log: runnerLog,
1051
+ });
1052
+ }
1053
+ finally {
1054
+ stop.done();
1055
+ }
1056
+ };
1057
+ }
955
1058
  if (name === "subagent_memory_get") {
956
1059
  return async (argsJson) => {
957
1060
  return handleSubAgentMemoryGet(argsJson, loaded.agentId, db, providers.memory);
@@ -1005,22 +1108,6 @@ export function createNativeRunTools(options) {
1005
1108
  });
1006
1109
  };
1007
1110
  try {
1008
- // At most maxDelegationsPerRun dispatches per run (default one: a
1009
- // classifier deciding plan-vs-implement commits to exactly one child),
1010
- // and never the same child twice, so a retry can't leave two children
1011
- // racing on the same task. Checked and the child row written under the
1012
- // run's delegation gate: delegations started together in one turn
1013
- // (parallelDelegations) are admitted one at a time.
1014
- const priorDispatches = await db.run.findMany({ where: { parentRunId: { in: [runId] } } });
1015
- const limitRefusal = delegationLimitRefusal({
1016
- priorChildAgentIds: priorDispatches.map((prior) => prior.agentId),
1017
- waitingChildAgentIds: [...waitingDelegations],
1018
- childAgentId: edge.childAgentId,
1019
- boundName,
1020
- limit: loaded.maxDelegationsPerRun ?? 1,
1021
- });
1022
- if (limitRefusal)
1023
- return limitRefusal;
1024
1111
  const parsedArgs = parseDelegateArgs(argsJson);
1025
1112
  if ("refusal" in parsedArgs)
1026
1113
  return parsedArgs.refusal;
@@ -1029,6 +1116,29 @@ export function createNativeRunTools(options) {
1029
1116
  if ("refusal" in authorized)
1030
1117
  return authorized.refusal;
1031
1118
  const { childAgent } = authorized;
1119
+ const delegation = await delegationTarget(db, edge.childAgentId, childAgent, args);
1120
+ if ("refusal" in delegation)
1121
+ return delegation.refusal;
1122
+ // At most maxDelegationsPerRun dispatches per run (default one: a
1123
+ // classifier deciding plan-vs-implement commits to exactly one child),
1124
+ // and never the same child and repository twice, so a retry can't leave
1125
+ // two children racing on the same task. Checked and the child row written
1126
+ // under the run's delegation gate: delegations started together in one
1127
+ // turn (parallelDelegations) are admitted one at a time.
1128
+ const priorDispatches = await db.run.findMany({
1129
+ where: { parentRunId: { in: [runId] } },
1130
+ include: { codingRun: { select: { repository: true } } },
1131
+ });
1132
+ const limitRefusal = delegationLimitRefusal({
1133
+ priorSlots: priorDelegationSlots(priorDispatches, edge.childAgentId, delegation.defaultRepository),
1134
+ waitingSlots: [...waitingDelegations],
1135
+ slot: delegation.slot,
1136
+ boundName,
1137
+ childHasRepositoryList: delegation.hasRepositoryList,
1138
+ limit: loaded.maxDelegationsPerRun ?? 1,
1139
+ });
1140
+ if (limitRefusal)
1141
+ return limitRefusal;
1032
1142
  // parallelDelegations: a child the budget would refuse (run_tree_exhausted or
1033
1143
  // budget_group_exhausted:*) while a sibling still runs waits for a sibling to finish and
1034
1144
  // free its hold, then tries again while siblings remain, up to the child's own wait bound
@@ -1047,7 +1157,7 @@ export function createNativeRunTools(options) {
1047
1157
  if (!exhaustedBy || remainingMs <= 0)
1048
1158
  break;
1049
1159
  runnerLog.info({ runId, boundName, exhaustedBy, siblingsInFlight: delegationSiblings.inFlight }, "delegation waiting for a sibling to free budget");
1050
- waitingDelegations.add(edge.childAgentId);
1160
+ waitingDelegations.add(delegation.slot);
1051
1161
  waited = true;
1052
1162
  release();
1053
1163
  try {
@@ -1055,7 +1165,7 @@ export function createNativeRunTools(options) {
1055
1165
  }
1056
1166
  finally {
1057
1167
  release = await delegationGate.acquire();
1058
- waitingDelegations.delete(edge.childAgentId);
1168
+ waitingDelegations.delete(delegation.slot);
1059
1169
  }
1060
1170
  }
1061
1171
  // The parent may have been stopped while this delegation waited: start nothing for it.
@@ -1100,6 +1210,8 @@ export function createNativeRunTools(options) {
1100
1210
  codingTask: args.task,
1101
1211
  continuesCodingRunId: args.continuePriorRun,
1102
1212
  mergeOrder: args.mergeOrder,
1213
+ codingRepository: delegation.repository,
1214
+ requireExplicitRepository: true,
1103
1215
  parentRunId: runId,
1104
1216
  parentCallId: callId,
1105
1217
  // The child's result flows back into this run, which the
@@ -1117,6 +1229,9 @@ export function createNativeRunTools(options) {
1117
1229
  });
1118
1230
  }
1119
1231
  catch (err) {
1232
+ // An unlink since the check above (a wait for budget, say) is the model's to report.
1233
+ if (err instanceof RepositoryChoiceRefusedError)
1234
+ return repositoryRefusal(err);
1120
1235
  // A continuation this deployment cannot make (the run id came from
1121
1236
  // a pull request another deployment opened, say) is the model's to
1122
1237
  // report, not a reason to fail the whole run.
@@ -1270,6 +1385,7 @@ export function createNativeRunTools(options) {
1270
1385
  */
1271
1386
  const scopedHost = (name, tool, signal, redactSecretValues) => createPrivilegedHost({
1272
1387
  redactSecretValues,
1388
+ fetchTimeoutMs: fetchTimeoutFor(limitOf(tool)),
1273
1389
  agentId: loaded.agentId,
1274
1390
  datastore: scopeDatastore(providers.datastore, tool.allowedDatastorePrefixes),
1275
1391
  sharedDatastore: scopeSharedDatastoreAccessor(sharedDatastoreAccessor, tool.allowedSharedDatastorePrefixes),
@@ -1301,13 +1417,41 @@ export function createNativeRunTools(options) {
1301
1417
  message: `No tool named "${name}" is attached to this agent.`,
1302
1418
  });
1303
1419
  }
1304
- return runUserToolCall(tool, argsJson, (signal) => scopedHost(name, tool, signal));
1420
+ // Nothing else interrupts a call in progress (stops are seen at step boundaries), so a long
1421
+ // tool call watches for its run being stopped and is aborted when it is.
1422
+ const stop = watchRunStop(db, runId);
1423
+ try {
1424
+ return await runUserToolCall({ ...tool, timeoutMs: limitOf(tool) }, argsJson, (signal) => scopedHost(name, tool, signal), { signal: stop.signal });
1425
+ }
1426
+ finally {
1427
+ stop.done();
1428
+ }
1429
+ };
1430
+ /** The time limit this run gives the user tool `name` now, or undefined for a name that is not one. */
1431
+ const toolTimeoutMs = (name) => {
1432
+ const tool = toolsByName.get(name);
1433
+ return tool ? limitOf(tool) : undefined;
1305
1434
  };
1306
1435
  const runSandboxTool = async (name, argsJson, callId) => {
1307
1436
  const builtin = builtinHandler(name);
1308
1437
  return builtin ? builtin(argsJson, callId) : runUserTool(name, argsJson);
1309
1438
  };
1310
- return { toolsByName, builtinHandler, privilegedHostFor, readableSecretValues, runUserTool, runSandboxTool };
1439
+ return {
1440
+ toolsByName,
1441
+ builtinHandler,
1442
+ privilegedHostFor,
1443
+ readableSecretValues,
1444
+ runUserTool,
1445
+ runSandboxTool,
1446
+ toolTimeoutMs,
1447
+ watchStop: () => watchRunStop(db, runId),
1448
+ /** Every secret value of the agent's attached MCP servers (never handed to a worker; redaction only). */
1449
+ mcpSecretValues: () => mcpSecretValuesForAgent(db, providers.secrets, loaded.agentId),
1450
+ };
1451
+ }
1452
+ /** A pinned tool's time limit under the current ceiling (WARDBY_TOOL_MAX_TIMEOUT_MS, read each call). */
1453
+ function limitOf(tool) {
1454
+ return effectiveToolTimeoutMs(tool.timeoutMs, loadToolMaxTimeoutMs());
1311
1455
  }
1312
1456
  /**
1313
1457
  * Live progress for observers (MCP get_run, the viewer). Absolute totals, so a DBOS replay
@@ -1448,6 +1592,34 @@ export async function loseNativeRun(ctx, reason) {
1448
1592
  await stopChildrenOfLostRun(ctx.db, ctx.providers.executor, ctx.runId);
1449
1593
  return settleFinishedNativeRun(ctx, finished, claimed);
1450
1594
  }
1595
+ /** How often a user tool call in progress checks whether its run was stopped. */
1596
+ export const TOOL_STOP_POLL_MS = 2_000;
1597
+ /**
1598
+ * Aborts `signal` once the run is no longer drivable (stop_run, a cancelled task, a parent's
1599
+ * stop). Polls every `intervalMs`, starting one interval in, so a quick call reads nothing; a
1600
+ * failed read changes nothing and the next tick reads again. `done()` stops the polling.
1601
+ */
1602
+ export function watchRunStop(db, runId, intervalMs = TOOL_STOP_POLL_MS) {
1603
+ const controller = new AbortController();
1604
+ let reading = false;
1605
+ const timer = setInterval(() => {
1606
+ if (reading || controller.signal.aborted)
1607
+ return;
1608
+ reading = true;
1609
+ runDrivability(db, runId)
1610
+ .then((state) => {
1611
+ if (state !== "drivable")
1612
+ controller.abort(new Error(`run_${state}`));
1613
+ }, () => { })
1614
+ .finally(() => {
1615
+ reading = false;
1616
+ });
1617
+ }, intervalMs);
1618
+ timer.unref?.();
1619
+ const done = () => clearInterval(timer);
1620
+ controller.signal.addEventListener("abort", done, { once: true });
1621
+ return { signal: controller.signal, done };
1622
+ }
1451
1623
  /** Where a run stands for the native gateway: still drivable, cancelled (its task), or otherwise ended. */
1452
1624
  export async function runDrivability(db, runId) {
1453
1625
  const run = await db.run.findUnique({ where: { id: runId }, select: { status: true } });
@@ -1472,7 +1644,16 @@ export function sandboxWorkerInput(runId, loaded, tools) {
1472
1644
  agent: loaded.agent,
1473
1645
  tools: loaded.tools,
1474
1646
  builtinTools: loaded.tools.map((tool) => tool.name).filter((name) => tools.builtinHandler(name) !== undefined),
1475
- userTools: Object.fromEntries([...tools.toolsByName].map(([name, tool]) => [name, { code: tool.code, paramsZod: tool.paramsZod }])),
1647
+ // timeoutMs only when the tool sets one, already capped by this server's ceiling: a default
1648
+ // tool's entry is unchanged, so an older worker image still accepts it.
1649
+ userTools: Object.fromEntries([...tools.toolsByName].map(([name, tool]) => [
1650
+ name,
1651
+ {
1652
+ code: tool.code,
1653
+ paramsZod: tool.paramsZod,
1654
+ ...(tool.timeoutMs !== undefined ? { timeoutMs: limitOf(tool) } : {}),
1655
+ },
1656
+ ])),
1476
1657
  runsConcurrently: loaded.parallelDelegations
1477
1658
  ? loaded.tools.map((tool) => tool.name).filter((name) => name.startsWith(DELEGATE_TOOL_PREFIX))
1478
1659
  : [],
@@ -1574,14 +1755,24 @@ export async function durableDelegate(ctx, callId, name, argsJson) {
1574
1755
  if ("refusal" in maskedArgs)
1575
1756
  return finish(maskedArgs.refusal);
1576
1757
  const dispatchArgs = maskedArgs.args;
1758
+ // On the masked args: a repository holding a secret value must never be matched, stored or echoed.
1759
+ const delegation = await delegationTarget(db, edge.childAgentId, childAgent, dispatchArgs);
1760
+ if ("refusal" in delegation)
1761
+ return finish(delegation.refusal);
1577
1762
  const limit = loaded.maxDelegationsPerRun ?? 1;
1578
1763
  const refusalNow = async (reader) => {
1579
- const prior = await reader.run.findMany({ where: { parentRunId: runId }, select: { agentId: true } });
1764
+ const prior = await reader.run.findMany({
1765
+ where: { parentRunId: runId },
1766
+ select: { agentId: true, codingRun: { select: { repository: true } } },
1767
+ });
1768
+ const waiting = await ledger.waitingDelegations(sessionId, callId);
1580
1769
  return delegationLimitRefusal({
1581
- priorChildAgentIds: prior.map((p) => p.agentId),
1582
- waitingChildAgentIds: await ledger.waitingChildAgentIds(sessionId, callId),
1583
- childAgentId: edge.childAgentId,
1770
+ priorSlots: priorDelegationSlots(prior, edge.childAgentId, delegation.defaultRepository),
1771
+ // A row with no repository predates the column: it holds the child's default.
1772
+ waitingSlots: waiting.map((row) => delegationSlot(row.childAgentId, row.childRepository ?? (row.childAgentId === edge.childAgentId ? delegation.defaultRepository : null))),
1773
+ slot: delegation.slot,
1584
1774
  boundName,
1775
+ childHasRepositoryList: delegation.hasRepositoryList,
1585
1776
  limit,
1586
1777
  });
1587
1778
  };
@@ -1598,13 +1789,21 @@ export async function durableDelegate(ctx, callId, name, argsJson) {
1598
1789
  const boundMs = (queueTimeoutSec + runTimeoutSec + CODING_CHILD_WAIT_GRACE_SEC) * 1000;
1599
1790
  const { exhaustedBy } = await effectiveBudgetForRun(db, childAgent, new Date(), runId);
1600
1791
  if (exhaustedBy && now() - startedAt < boundMs) {
1601
- await ledger.setDelegation(sessionId, callId, { status: "waiting_budget", childAgentId: edge.childAgentId });
1792
+ await ledger.setDelegation(sessionId, callId, {
1793
+ status: "waiting_budget",
1794
+ childAgentId: edge.childAgentId,
1795
+ childRepository: delegation.repository,
1796
+ });
1602
1797
  return { pending: true };
1603
1798
  }
1604
1799
  }
1605
1800
  }
1606
1801
  // Recorded before dispatch, so a replica dying in between leaves a row the next call adopts.
1607
- await ledger.setDelegation(sessionId, callId, { status: "pending", childAgentId: edge.childAgentId });
1802
+ await ledger.setDelegation(sessionId, callId, {
1803
+ status: "pending",
1804
+ childAgentId: edge.childAgentId,
1805
+ childRepository: delegation.repository,
1806
+ });
1608
1807
  let dispatched;
1609
1808
  let refusedUnderLock = null;
1610
1809
  try {
@@ -1619,6 +1818,8 @@ export async function durableDelegate(ctx, callId, name, argsJson) {
1619
1818
  codingTask: dispatchArgs.task,
1620
1819
  continuesCodingRunId: dispatchArgs.continuePriorRun,
1621
1820
  mergeOrder: dispatchArgs.mergeOrder,
1821
+ codingRepository: delegation.repository,
1822
+ requireExplicitRepository: true,
1622
1823
  }
1623
1824
  : {
1624
1825
  taskOverride: delegationTaskOverride(dispatchArgs),
@@ -1637,6 +1838,9 @@ export async function durableDelegate(ctx, callId, name, argsJson) {
1637
1838
  });
1638
1839
  }
1639
1840
  catch (err) {
1841
+ // An unlink since the check above (a wait for budget, say) is the model's to report.
1842
+ if (err instanceof RepositoryChoiceRefusedError)
1843
+ return finish(repositoryRefusal(err));
1640
1844
  if (err instanceof ContinuationRefusedError) {
1641
1845
  return finish(JSON.stringify({
1642
1846
  error: "continuation_refused",
@@ -1781,7 +1985,10 @@ export async function createSandboxSession(options) {
1781
1985
  sessionId: session.id,
1782
1986
  capability,
1783
1987
  input: { ...input, gateway: { url: options.gatewayUrl, capability } },
1784
- secretValues: () => userToolSecretValues(tools, Object.keys(input.userTools)),
1988
+ secretValues: async () => [
1989
+ ...(await userToolSecretValues(tools, Object.keys(input.userTools))),
1990
+ ...(await tools.mcpSecretValues()),
1991
+ ],
1785
1992
  };
1786
1993
  }
1787
1994
  /**
@@ -1892,6 +2099,7 @@ async function executeTrackedRun(runId, providers, db, onText, step) {
1892
2099
  builtinHandler: tools.builtinHandler,
1893
2100
  privilegedHostFor: tools.privilegedHostFor,
1894
2101
  readableSecretValues: tools.readableSecretValues,
2102
+ mcpSecretValues: tools.mcpSecretValues,
1895
2103
  drivability: () => runDrivability(db, runId),
1896
2104
  launcher: providers.nativeSandbox,
1897
2105
  });
@@ -1,5 +1,7 @@
1
1
  import { z } from "zod";
2
2
  export declare const MIN_BROKERED_VALUE_LENGTH = 6;
3
+ /** False for a name a resolver could read as an IPv4 address ("127.1", "0x7f.0.0.1") or with a label ending in "-". */
4
+ export declare function isExactHostname(h: string): boolean;
3
5
  export declare const SecretBrokerConfigSchema: z.ZodObject<{
4
6
  hosts: z.ZodArray<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
5
7
  pathPrefixes: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -11,7 +11,7 @@ const TOKEN = /^[!#$%&'*+.^_`|~0-9A-Za-z-]{1,128}$/;
11
11
  const HOSTNAME = /^(?=.{1,253}$)(?!-)[a-z0-9-]{1,63}(?:\.(?!-)[a-z0-9-]{1,63})+$/;
12
12
  const IPV4 = /^\d{1,3}(?:\.\d{1,3}){3}$/;
13
13
  /** False for a name a resolver could read as an IPv4 address ("127.1", "0x7f.0.0.1") or with a label ending in "-". */
14
- function isExactHostname(h) {
14
+ export function isExactHostname(h) {
15
15
  if (!HOSTNAME.test(h) || IPV4.test(h))
16
16
  return false;
17
17
  const labels = h.split(".");
@@ -5,8 +5,11 @@ export interface NamedSecretValues {
5
5
  values: string[];
6
6
  }
7
7
  type SecretGuardCipher = Parameters<typeof buildSecretsAccessor>[1];
8
- type SecretGuardDb = Parameters<typeof buildSecretsAccessor>[2] & Pick<PrismaClient, "agentSecret">;
9
- /** Every secret attached to the agent (readable and brokered), with every form to look for. Rejects if any read fails. */
8
+ type SecretGuardDb = Parameters<typeof buildSecretsAccessor>[2] & Pick<PrismaClient, "agentSecret" | "agentMcpServer" | "secret">;
9
+ /**
10
+ * Every secret attached to the agent (readable and brokered), and every secret of an MCP server
11
+ * attached to it (named `mcp:<server>/<secret>`), with every form to look for. Rejects if any read fails.
12
+ */
10
13
  export declare function loadAgentSecretValues(db: SecretGuardDb, cipher: SecretGuardCipher, agentId: string): Promise<NamedSecretValues[]>;
11
14
  /** The sorted, unique names of the secrets whose value occurs anywhere in `value`. */
12
15
  export declare function secretNamesIn(value: unknown, named: readonly NamedSecretValues[]): string[];