@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
@@ -6,7 +6,7 @@
6
6
  * compilation/validation is pure computation, no host I/O at all).
7
7
  */
8
8
  import type { QuickJSContext, QuickJSRuntime } from "quickjs-emscripten";
9
- export type SandboxErrorKind = "thrown" | "timeout" | "memory" | "cpu" | "non_serializable";
9
+ export type SandboxErrorKind = "thrown" | "timeout" | "memory" | "cpu" | "non_serializable" | "cancelled";
10
10
  export type SandboxResult = {
11
11
  ok: true;
12
12
  value: unknown;
@@ -34,4 +34,14 @@ export declare function classifyEvalError(dumped: {
34
34
  name?: string;
35
35
  message?: string;
36
36
  }): SandboxResult;
37
- export declare function evalToJson(code: string, limitsOverride: Partial<SandboxLimits> | undefined, installExtras: (context: QuickJSContext, runtime: QuickJSRuntime, signal: AbortSignal) => void): Promise<SandboxResult>;
37
+ export interface EvalOptions {
38
+ /**
39
+ * Ends the evaluation early when it aborts (a stopped run): the result is `aborted(reason)`,
40
+ * and every host call still in flight is aborted with it.
41
+ */
42
+ signal?: AbortSignal;
43
+ aborted?: (reason: unknown) => SandboxResult;
44
+ /** The wall-time error's text; defaults to naming the limit. */
45
+ timeoutMessage?: (limitMs: number) => string;
46
+ }
47
+ export declare function evalToJson(code: string, limitsOverride: Partial<SandboxLimits> | undefined, installExtras: (context: QuickJSContext, runtime: QuickJSRuntime, signal: AbortSignal) => void, options?: EvalOptions): Promise<SandboxResult>;
@@ -56,9 +56,14 @@ export function classifyEvalError(dumped) {
56
56
  errorMessage: dumped.message ? `${dumped.name ?? "Error"}: ${dumped.message}` : JSON.stringify(dumped),
57
57
  };
58
58
  }
59
- export async function evalToJson(code, limitsOverride, installExtras) {
59
+ const defaultAborted = () => ({ ok: false, errorKind: "cancelled", errorMessage: "Cancelled." });
60
+ export async function evalToJson(code, limitsOverride, installExtras, options = {}) {
60
61
  const limits = { ...DEFAULT_SANDBOX_LIMITS, ...limitsOverride };
62
+ const aborted = options.aborted ?? defaultAborted;
63
+ if (options.signal?.aborted)
64
+ return aborted(options.signal.reason);
61
65
  const controller = new AbortController();
66
+ let onAbort;
62
67
  let timer;
63
68
  const deadline = Date.now() + limits.wallTimeLimitMs;
64
69
  const qjsModule = await getQuickJsModule();
@@ -68,7 +73,7 @@ export async function evalToJson(code, limitsOverride, installExtras) {
68
73
  let steps = 0;
69
74
  runtime.setInterruptHandler(() => {
70
75
  steps += 1;
71
- return steps > limits.maxInterruptChecks || Date.now() >= deadline;
76
+ return steps > limits.maxInterruptChecks || Date.now() >= deadline || options.signal?.aborted === true;
72
77
  });
73
78
  const context = runtime.newContext();
74
79
  let disposed = false;
@@ -139,10 +144,19 @@ export async function evalToJson(code, limitsOverride, installExtras) {
139
144
  timer = setTimeout(() => resolve({
140
145
  ok: false,
141
146
  errorKind: "timeout",
142
- errorMessage: `Exceeded the ${limits.wallTimeLimitMs}ms wall-time limit.`,
147
+ errorMessage: options.timeoutMessage
148
+ ? options.timeoutMessage(limits.wallTimeLimitMs)
149
+ : `Exceeded the ${limits.wallTimeLimitMs}ms wall-time limit.`,
143
150
  }), limits.wallTimeLimitMs);
144
151
  });
145
- return await Promise.race([runPromise, timeoutPromise]);
152
+ const external = options.signal;
153
+ const abortPromise = new Promise((resolve) => {
154
+ if (!external)
155
+ return;
156
+ onAbort = () => resolve(aborted(external.reason));
157
+ external.addEventListener("abort", onAbort, { once: true });
158
+ });
159
+ return await Promise.race([runPromise, timeoutPromise, abortPromise]);
146
160
  }
147
161
  catch (err) {
148
162
  return {
@@ -153,6 +167,8 @@ export async function evalToJson(code, limitsOverride, installExtras) {
153
167
  }
154
168
  finally {
155
169
  clearTimeout(timer);
170
+ if (onAbort)
171
+ options.signal?.removeEventListener("abort", onAbort);
156
172
  controller.abort();
157
173
  disposeAll();
158
174
  }
@@ -43,6 +43,8 @@ export interface HostFunctionOptions {
43
43
  redactSecretValues?: readonly string[];
44
44
  /** Overrides the network call — tests only. */
45
45
  fetchImpl?: typeof safeFetch;
46
+ /** Each fetch's timeout (fetchTimeoutFor the tool's limit); the default fetch timeout when absent. */
47
+ fetchTimeoutMs?: number;
46
48
  }
47
49
  /** One host bridge: JSON-string arguments in, a JSON-serializable value out (bridge.ts). */
48
50
  export type HostBridge = (argsJson: string) => Promise<unknown>;
@@ -63,6 +65,8 @@ export declare function createPrivilegedHost(options: PrivilegedHostOptions): Pr
63
65
  export interface SandboxApiOptions {
64
66
  privileged: PrivilegedHost;
65
67
  signal?: AbortSignal;
68
+ /** The tool's time limit, which also caps sleep(); the default wall-time cap when absent. */
69
+ wallTimeLimitMs?: number;
66
70
  /** Overrides the shared default parser-worker pool — mainly for tests. */
67
71
  parserPool?: ParserWorkerPool;
68
72
  }
@@ -95,7 +95,11 @@ export function createPrivilegedHost(options) {
95
95
  },
96
96
  async __bridge_fetch(argsJson) {
97
97
  const [url, init = {}, secretNames] = args(argsJson);
98
- const policy = { ...sandboxFetchPolicy(allowedFetchHosts ?? []), signal };
98
+ const policy = {
99
+ ...sandboxFetchPolicy(allowedFetchHosts ?? []),
100
+ signal,
101
+ ...(options.fetchTimeoutMs !== undefined ? { timeoutMs: options.fetchTimeoutMs } : {}),
102
+ };
99
103
  if (secretNames === undefined || (Array.isArray(secretNames) && secretNames.length === 0)) {
100
104
  return fetchImpl(url, init, policy);
101
105
  }
@@ -177,13 +181,14 @@ export function createPrivilegedHost(options) {
177
181
  /** Registers the whole sandbox API: the local bridges directly, the privileged ones through `privileged`. */
178
182
  export function installSandboxApi(context, runtime, options) {
179
183
  const { privileged, signal } = options;
184
+ const sleepLimitMs = options.wallTimeLimitMs ?? WALL_TIME_LIMIT_MS;
180
185
  const register = (name, fn) => registerJsonAsyncFunction(context, runtime, name, fn, signal);
181
186
  const parserPool = options.parserPool ?? (sharedParserPool ??= createParserWorkerPool());
182
187
  for (const name of PRIVILEGED_BRIDGE_NAMES)
183
188
  register(name, (argsJson) => privileged[name](argsJson));
184
189
  register("__bridge_sleep", async (argsJson) => {
185
190
  const [ms] = args(argsJson);
186
- if (!Number.isFinite(ms) || ms < 0 || ms > WALL_TIME_LIMIT_MS)
191
+ if (!Number.isFinite(ms) || ms < 0 || ms > sleepLimitMs)
187
192
  throw new Error("sleep_limit");
188
193
  await sleep(ms, undefined, { signal });
189
194
  return null;
@@ -15,7 +15,10 @@ export declare const MAX_INTERRUPT_CHECKS = 20000000;
15
15
  * awaiting a host async call (e.g. a slow `fetch`) — this does.
16
16
  */
17
17
  export declare const WALL_TIME_LIMIT_MS = 10000;
18
- /** Timeout for the sandbox's `fetch` host bridge, independent of the wall-time cap. */
18
+ /**
19
+ * safeFetch's timeout when its caller gives none. A sandboxed tool's fetches get
20
+ * fetchTimeoutFor(the tool's limit) instead (tool-timeout.ts), just past its wall time.
21
+ */
19
22
  export declare const FETCH_TIMEOUT_MS = 8000;
20
23
  /** QuickJS interpreter stack size cap (bytes). */
21
24
  export declare const MAX_STACK_SIZE_BYTES: number;
@@ -15,7 +15,10 @@ export const MAX_INTERRUPT_CHECKS = 20_000_000;
15
15
  * awaiting a host async call (e.g. a slow `fetch`) — this does.
16
16
  */
17
17
  export const WALL_TIME_LIMIT_MS = 10_000;
18
- /** Timeout for the sandbox's `fetch` host bridge, independent of the wall-time cap. */
18
+ /**
19
+ * safeFetch's timeout when its caller gives none. A sandboxed tool's fetches get
20
+ * fetchTimeoutFor(the tool's limit) instead (tool-timeout.ts), just past its wall time.
21
+ */
19
22
  export const FETCH_TIMEOUT_MS = 8_000;
20
23
  /** QuickJS interpreter stack size cap (bytes). */
21
24
  export const MAX_STACK_SIZE_BYTES = 1024 * 1024; // 1 MiB
@@ -27,6 +27,8 @@ export interface SandboxInvocation {
27
27
  toolName: string;
28
28
  /** Overrides the default limits (limits.ts) — mainly for fast, deterministic tests. */
29
29
  limits?: Partial<SandboxLimits>;
30
+ /** The tool's time limit (effectiveToolTimeoutMs); its fetches time out just before it. Overrides limits.wallTimeLimitMs. */
31
+ timeoutMs?: number;
30
32
  /** Omitted for a dry run (no real agent) — secrets.get always resolves undefined. */
31
33
  secrets?: SecretsAccessor;
32
34
  /** Overrides the default shared logger — mainly for tests that need to capture forwarded console output. */
@@ -42,12 +44,16 @@ export interface ToolCodeInvocation {
42
44
  code: string;
43
45
  params: unknown;
44
46
  limits?: Partial<SandboxLimits>;
47
+ /** Stops the call early (its run was stopped): it fails as `cancelled`, and its host calls are aborted. */
48
+ signal?: AbortSignal;
45
49
  /** Built once per invocation, with that invocation's abort signal (fetch needs it). */
46
50
  privileged: (signal: AbortSignal) => PrivilegedHost;
47
51
  parserPool?: ParserWorkerPool;
48
52
  }
49
53
  /** Evaluates one tool body in a fresh QuickJS context against the given privileged host. */
50
54
  export declare function runToolCode(invocation: ToolCodeInvocation): Promise<SandboxResult>;
55
+ /** The sandbox limits for a tool whose time limit is `timeoutMs` (absent: the defaults). */
56
+ export declare function toolLimits(timeoutMs: number | undefined, base?: Partial<SandboxLimits>): Partial<SandboxLimits>;
51
57
  /** The in-process privileged host for a sandbox invocation. */
52
58
  export declare function privilegedHostFor(invocation: SandboxInvocation, signal: AbortSignal): PrivilegedHost;
53
59
  export declare function runInSandbox(invocation: SandboxInvocation): Promise<SandboxResult>;
@@ -9,7 +9,8 @@
9
9
  */
10
10
  import { createPrivilegedHost, installSandboxApi } from "./host-functions.js";
11
11
  import { SANDBOX_PRELUDE } from "./prelude.js";
12
- import { evalToJson, NON_SERIALIZABLE_MARKER } from "./eval-core.js";
12
+ import { DEFAULT_SANDBOX_LIMITS, evalToJson, NON_SERIALIZABLE_MARKER, } from "./eval-core.js";
13
+ import { fetchTimeoutFor, TOOL_RUN_STOPPED_MESSAGE, toolTimeoutMessage } from "./tool-timeout.js";
13
14
  import { boundedJson, boundedString } from "./bounded-json.js";
14
15
  import { BRIDGE_INPUT_BYTES } from "./limits.js";
15
16
  /** Evaluates one tool body in a fresh QuickJS context against the given privileged host. */
@@ -39,14 +40,24 @@ export async function runToolCode(invocation) {
39
40
  throw new Error("${NON_SERIALIZABLE_MARKER}" + (__serializeErr && __serializeErr.message));
40
41
  }
41
42
  })()`;
43
+ const wallTimeLimitMs = invocation.limits?.wallTimeLimitMs ?? DEFAULT_SANDBOX_LIMITS.wallTimeLimitMs;
42
44
  return evalToJson(code, invocation.limits, (context, runtime, signal) => {
43
45
  installSandboxApi(context, runtime, {
44
46
  privileged: invocation.privileged(signal),
45
47
  signal,
46
48
  parserPool: invocation.parserPool,
49
+ wallTimeLimitMs,
47
50
  });
51
+ }, {
52
+ signal: invocation.signal,
53
+ aborted: () => ({ ok: false, errorKind: "cancelled", errorMessage: TOOL_RUN_STOPPED_MESSAGE }),
54
+ timeoutMessage: toolTimeoutMessage,
48
55
  });
49
56
  }
57
+ /** The sandbox limits for a tool whose time limit is `timeoutMs` (absent: the defaults). */
58
+ export function toolLimits(timeoutMs, base) {
59
+ return timeoutMs === undefined ? { ...base } : { ...base, wallTimeLimitMs: timeoutMs };
60
+ }
50
61
  /** The in-process privileged host for a sandbox invocation. */
51
62
  export function privilegedHostFor(invocation, signal) {
52
63
  return createPrivilegedHost({
@@ -59,13 +70,14 @@ export function privilegedHostFor(invocation, signal) {
59
70
  allowedFetchHosts: invocation.allowedFetchHosts,
60
71
  fetchImpl: invocation.fetchImpl,
61
72
  signal,
73
+ fetchTimeoutMs: fetchTimeoutFor(invocation.timeoutMs ?? invocation.limits?.wallTimeLimitMs ?? DEFAULT_SANDBOX_LIMITS.wallTimeLimitMs),
62
74
  });
63
75
  }
64
76
  export async function runInSandbox(invocation) {
65
77
  return runToolCode({
66
78
  code: invocation.code,
67
79
  params: invocation.params,
68
- limits: invocation.limits,
80
+ limits: toolLimits(invocation.timeoutMs, invocation.limits),
69
81
  privileged: (signal) => privilegedHostFor(invocation, signal),
70
82
  });
71
83
  }
@@ -11,6 +11,8 @@ export declare function pinnedLookup(destination: Destination): LookupFunction;
11
11
  export declare function requestPinned(destination: Destination, init: SafeFetchInit, signal: AbortSignal): Promise<IncomingMessage>;
12
12
  export interface SafeFetchOptions extends FetchPolicyOptions {
13
13
  signal?: AbortSignal;
14
+ /** This fetch's own timeout; FETCH_TIMEOUT_MS when absent. */
15
+ timeoutMs?: number;
14
16
  connect?: typeof requestPinned;
15
17
  /** False: a 3xx is returned to the caller as-is (brokered requests re-check every hop themselves). */
16
18
  followRedirects?: boolean;
@@ -39,7 +39,7 @@ async function untilAbort(promise, signal) {
39
39
  }
40
40
  export async function safeFetch(input, init = {}, options = {}) {
41
41
  const abort = new AbortController();
42
- const timer = setTimeout(() => abort.abort(new Error("fetch_timeout")), FETCH_TIMEOUT_MS);
42
+ const timer = setTimeout(() => abort.abort(new Error("fetch_timeout")), options.timeoutMs ?? FETCH_TIMEOUT_MS);
43
43
  const signal = options.signal ? AbortSignal.any([options.signal, abort.signal]) : abort.signal;
44
44
  let url = input;
45
45
  let method = init.method ?? "GET";
@@ -26,6 +26,7 @@ export declare function consoleRedactionValues(entry: {
26
26
  value: string;
27
27
  broker: SecretBrokerConfig | null;
28
28
  }): string[];
29
+ export declare function encodedForms(value: string): string[];
29
30
  export declare function scrubBrokeredResponse<T extends {
30
31
  headers: Record<string, string>;
31
32
  bodyBase64: string;
@@ -121,7 +121,7 @@ export function consoleRedactionValues(entry) {
121
121
  return [entry.value];
122
122
  }
123
123
  }
124
- function encodedForms(value) {
124
+ export function encodedForms(value) {
125
125
  const buf = Buffer.from(value, "utf8");
126
126
  // Query placement goes through URLSearchParams, which encodes ~ ! ' ( ) differently and space as "+",
127
127
  // so both percent-encodings are scrubbed, each also in lowercase-hex form (a response may echo either).
@@ -0,0 +1,32 @@
1
+ /**
2
+ * A sandboxed tool's time limit. A tool may ask for its own `timeoutMs`; the operator's
3
+ * WARDBY_TOOL_MAX_TIMEOUT_MS (config/tool-timeout.ts) caps it. Without one a tool keeps the
4
+ * default wall-time cap (limits.ts). Pure and worker-safe: no configuration is read here.
5
+ */
6
+ /** A tool that sets no `timeoutMs`. */
7
+ export declare const DEFAULT_TOOL_TIMEOUT_MS = 10000;
8
+ /** The shortest `timeoutMs` a tool may ask for. */
9
+ export declare const MIN_TOOL_TIMEOUT_MS = 1000;
10
+ /** WARDBY_TOOL_MAX_TIMEOUT_MS when unset: five minutes. */
11
+ export declare const DEFAULT_TOOL_MAX_TIMEOUT_MS = 300000;
12
+ /**
13
+ * How far past the tool's limit each fetch's own timeout lies. The wall time ends the call (and
14
+ * aborts its fetches) first, so a slow request fails with the wall-time error that names the
15
+ * limit, not a fetch error.
16
+ */
17
+ export declare const FETCH_TIMEOUT_MARGIN_MS = 1000;
18
+ /** The fetch timeout for a tool limit of `wallTimeLimitMs`. */
19
+ export declare function fetchTimeoutFor(wallTimeLimitMs: number): number;
20
+ /**
21
+ * The limit a run uses for a tool: its own `timeoutMs` (or the default when it has none), never
22
+ * above the current ceiling, so lowering the ceiling also covers tools that already exist.
23
+ */
24
+ export declare function effectiveToolTimeoutMs(timeoutMs: number | null | undefined, ceilingMs: number): number;
25
+ /** Why `timeoutMs` can't be stored on a tool under `ceilingMs`, or undefined when it can. */
26
+ export declare function toolTimeoutProblem(timeoutMs: unknown, ceilingMs: number): string | undefined;
27
+ /** The model-facing text for a tool that ran out of time. */
28
+ export declare function toolTimeoutMessage(limitMs: number): string;
29
+ /** The model-facing text for a tool call cut off at its run's deadline. */
30
+ export declare const TOOL_RUN_DEADLINE_MESSAGE = "Stopped at the run's deadline while this tool call was still running.";
31
+ /** The model-facing text for a tool call aborted because its run was stopped. */
32
+ export declare const TOOL_RUN_STOPPED_MESSAGE = "The run was stopped while this tool call was running.";
@@ -0,0 +1,52 @@
1
+ /**
2
+ * A sandboxed tool's time limit. A tool may ask for its own `timeoutMs`; the operator's
3
+ * WARDBY_TOOL_MAX_TIMEOUT_MS (config/tool-timeout.ts) caps it. Without one a tool keeps the
4
+ * default wall-time cap (limits.ts). Pure and worker-safe: no configuration is read here.
5
+ */
6
+ import { WALL_TIME_LIMIT_MS } from "./limits.js";
7
+ /** A tool that sets no `timeoutMs`. */
8
+ export const DEFAULT_TOOL_TIMEOUT_MS = WALL_TIME_LIMIT_MS;
9
+ /** The shortest `timeoutMs` a tool may ask for. */
10
+ export const MIN_TOOL_TIMEOUT_MS = 1_000;
11
+ /** WARDBY_TOOL_MAX_TIMEOUT_MS when unset: five minutes. */
12
+ export const DEFAULT_TOOL_MAX_TIMEOUT_MS = 300_000;
13
+ /**
14
+ * How far past the tool's limit each fetch's own timeout lies. The wall time ends the call (and
15
+ * aborts its fetches) first, so a slow request fails with the wall-time error that names the
16
+ * limit, not a fetch error.
17
+ */
18
+ export const FETCH_TIMEOUT_MARGIN_MS = 1_000;
19
+ /** The fetch timeout for a tool limit of `wallTimeLimitMs`. */
20
+ export function fetchTimeoutFor(wallTimeLimitMs) {
21
+ return wallTimeLimitMs + FETCH_TIMEOUT_MARGIN_MS;
22
+ }
23
+ /**
24
+ * The limit a run uses for a tool: its own `timeoutMs` (or the default when it has none), never
25
+ * above the current ceiling, so lowering the ceiling also covers tools that already exist.
26
+ */
27
+ export function effectiveToolTimeoutMs(timeoutMs, ceilingMs) {
28
+ if (timeoutMs === null || timeoutMs === undefined)
29
+ return DEFAULT_TOOL_TIMEOUT_MS;
30
+ return Math.min(timeoutMs, ceilingMs);
31
+ }
32
+ /** Why `timeoutMs` can't be stored on a tool under `ceilingMs`, or undefined when it can. */
33
+ export function toolTimeoutProblem(timeoutMs, ceilingMs) {
34
+ if (typeof timeoutMs !== "number" || !Number.isSafeInteger(timeoutMs)) {
35
+ return "timeoutMs must be a whole number of milliseconds.";
36
+ }
37
+ if (timeoutMs < MIN_TOOL_TIMEOUT_MS)
38
+ return `timeoutMs must be at least ${MIN_TOOL_TIMEOUT_MS} ms.`;
39
+ if (timeoutMs > ceilingMs) {
40
+ return `timeoutMs ${timeoutMs} is above this server's ceiling of ${ceilingMs} ms (WARDBY_TOOL_MAX_TIMEOUT_MS).`;
41
+ }
42
+ return undefined;
43
+ }
44
+ /** The model-facing text for a tool that ran out of time. */
45
+ export function toolTimeoutMessage(limitMs) {
46
+ return (`Exceeded the ${limitMs}ms wall-time limit. A tool that needs longer can set timeoutMs ` +
47
+ "(create_tool or update_tool), up to the server's WARDBY_TOOL_MAX_TIMEOUT_MS.");
48
+ }
49
+ /** The model-facing text for a tool call cut off at its run's deadline. */
50
+ export const TOOL_RUN_DEADLINE_MESSAGE = "Stopped at the run's deadline while this tool call was still running.";
51
+ /** The model-facing text for a tool call aborted because its run was stopped. */
52
+ export const TOOL_RUN_STOPPED_MESSAGE = "The run was stopped while this tool call was running.";
@@ -10,5 +10,12 @@ import type { SandboxLimits } from "./eval-core.js";
10
10
  export interface UserToolDefinition {
11
11
  code: string;
12
12
  paramsZod: string;
13
+ /** The time limit this run gives the tool (effectiveToolTimeoutMs); absent = the default. */
14
+ timeoutMs?: number;
13
15
  }
14
- export declare function runUserToolCall(tool: UserToolDefinition, argsJson: string, privileged: (signal: AbortSignal) => PrivilegedHost, limits?: Partial<SandboxLimits>): Promise<string>;
16
+ export interface UserToolCallOptions {
17
+ limits?: Partial<SandboxLimits>;
18
+ /** Aborts the call when its run is stopped. */
19
+ signal?: AbortSignal;
20
+ }
21
+ export declare function runUserToolCall(tool: UserToolDefinition, argsJson: string, privileged: (signal: AbortSignal) => PrivilegedHost, options?: UserToolCallOptions): Promise<string>;
@@ -5,9 +5,9 @@
5
5
  * JSON error result. Worker-safe: it touches no database or provider, only the
6
6
  * privileged host it is given, so the native sandbox worker runs it as is.
7
7
  */
8
- import { runToolCode } from "./run-in-sandbox.js";
8
+ import { runToolCode, toolLimits } from "./run-in-sandbox.js";
9
9
  import { validateParams } from "./zod-params.js";
10
- export async function runUserToolCall(tool, argsJson, privileged, limits) {
10
+ export async function runUserToolCall(tool, argsJson, privileged, options = {}) {
11
11
  let parsedArgs;
12
12
  try {
13
13
  // Some providers stream no JSON delta at all for a zero-parameter
@@ -24,7 +24,13 @@ export async function runUserToolCall(tool, argsJson, privileged, limits) {
24
24
  if (!validation.ok) {
25
25
  return JSON.stringify({ error: "validation_failed", message: validation.errorMessage });
26
26
  }
27
- const result = await runToolCode({ code: tool.code, params: validation.value, limits, privileged });
27
+ const result = await runToolCode({
28
+ code: tool.code,
29
+ params: validation.value,
30
+ limits: toolLimits(tool.timeoutMs, options.limits),
31
+ signal: options.signal,
32
+ privileged,
33
+ });
28
34
  if (!result.ok) {
29
35
  return JSON.stringify({ error: result.errorKind, message: result.errorMessage });
30
36
  }
@@ -1,4 +1,5 @@
1
1
  import { periodStart } from "../core/budget-groups.js";
2
+ import { codeProviderOf } from "../coding/code-host.js";
2
3
  import { publicCodingRunResult } from "../coding/protocol.js";
3
4
  const DEFAULT_SINCE_MS = 60 * 60 * 1000;
4
5
  const RELATIVE_SINCE_MS = {
@@ -102,7 +103,7 @@ function outcomesFor(row, pullRequests, sites) {
102
103
  !pullRequests.some((pr) => pr.url === result.pullRequestUrl)) {
103
104
  outcomes.push({
104
105
  kind: "pull_request",
105
- provider: "github",
106
+ provider: codeProviderOf(result.repository),
106
107
  repository: result.repository,
107
108
  number: result.pullRequestNumber,
108
109
  url: result.pullRequestUrl,
@@ -52,6 +52,11 @@ async function main() {
52
52
  const { resumeToolsCommand } = await import("./coding/transcript/resume/stand-in.js");
53
53
  await resumeToolsCommand(process.argv.slice(3));
54
54
  }
55
+ else if (command === "claude-plugin") {
56
+ // Sets up the Claude Code plugin on this machine: no environment, database, or server runtime.
57
+ const { claudePluginCommand } = await import("./claude-plugin/command.js");
58
+ process.exitCode = await claudePluginCommand(process.argv.slice(3));
59
+ }
55
60
  else if (command === "knowledge") {
56
61
  // Offline bundle validation: no environment, database, or HTTP runtime.
57
62
  const { knowledgeCommand } = await import("./knowledge/cli.js");
package/docs/README.md CHANGED
@@ -13,16 +13,26 @@
13
13
  runs are billed.
14
14
  - [Agent recipes](agent-recipes.md) gives two copyable setups, an architecture
15
15
  keeper and a per-language builder, with the prerequisites each needs.
16
+ - [Debugging an agent run](debugging-agent-runs.md) triages a run that failed,
17
+ opened no pull request, or missed what you expected.
16
18
  - [Coding-agent setup](coding-agent-setup.md) configures the local trusted proxy
17
19
  and isolated Codex or Claude Code workers.
20
+ - [Claude Code plugin](claude-code-plugin.md) (0.8.0+) installs the `wardby-runs`
21
+ pane: your runs beside the chat, grouped by pull request, with run details and
22
+ Continue locally.
18
23
  - [Architecture knowledge bundles](knowledge.md) covers `docs/knowledge/`: the
19
24
  concept format, how coding runs and reviewers use it, `wardby knowledge check`,
20
25
  and the scheduled architecture agent.
21
- - [Native sandbox](native-sandbox.md) (0.5.4+) runs a native agent's turn loop and user
26
+ - [Native sandbox](native-sandbox.md) (0.6.0+) runs a native agent's turn loop and user
22
27
  tools in a single-use, credential-free container behind a native gateway.
23
28
  - [Secrets and tools](tool-secrets.md) covers readable and brokered secrets
24
29
  (brokered secrets require 0.6.0+):
25
30
  attaching secrets to tools, `set_secret_broker`, placements, and errors.
31
+ - [Remote MCP servers](mcp-servers.md) (0.8.0+) registers a remote MCP server and
32
+ attaches an allowlist of its tools to a native agent.
33
+ - [Host providers](host-providers.md) (0.8.0+) covers adding a code host with
34
+ your own custom tools: registering, approving, testing and removing a
35
+ provider.
26
36
  - [Coding-worker isolation](coding-worker-isolation.md) documents the threat
27
37
  model and enforced worker boundary.
28
38
  - [Bring-your-own worker images](coding-worker-byo-images.md) explains how to
@@ -383,8 +383,9 @@ the lead's `maxDelegationsPerRun` (1 to 20) with `create_agent` or
383
383
  { "id": "<lead agent id>", "maxDelegationsPerRun": 6 }
384
384
  ```
385
385
 
386
- - Each `delegate_to_<name>` call still goes to a different sub-agent; a second
387
- call to the same one in the same run is refused with `already_dispatched`.
386
+ - A run delegates to each sub-agent and repository at most once: a second call
387
+ to the same sub-agent in the same run (for a coding sub-agent with a list of
388
+ repositories, in the same repository) is refused with `already_dispatched`.
388
389
  A delegation waiting for budget (see below) also holds its place against
389
390
  `maxDelegationsPerRun`: a call beyond the limit is refused with
390
391
  `already_dispatched` and a message naming both counts, e.g. "This run
@@ -507,6 +508,37 @@ the lead's `maxDelegationsPerRun` (1 to 20) with `create_agent` or
507
508
  repositories, keep that work in separate requests (separate Jira issues, or
508
509
  separate lead agents).
509
510
 
511
+ #### One coding agent, several repositories
512
+
513
+ Instead of one builder per repository, give one coding builder a list: its default in
514
+ `codingProfile.repository`, and each other repository with `link_repository`
515
+ (`access: "write"`, no triggers). The lead then names the repository on each
516
+ call, and may delegate to the same builder once per repository:
517
+
518
+ ```json
519
+ { "task": "Add the /quotes endpoint the client will call.", "repository": "your-org/api", "mergeOrder": 1 }
520
+ ```
521
+
522
+ ```json
523
+ { "task": "Add the quotes page that calls /quotes.", "repository": "your-org/web", "mergeOrder": 2 }
524
+ ```
525
+
526
+ Set `maxDelegationsPerRun` (up to 20) for the number of calls, and use
527
+ `mergeOrder` where the pull requests must land in order. A call without
528
+ `repository` is refused with `repository_required` when the builder has extra
529
+ repositories (unless it continues a prior run with `continuePriorRun`), and a
530
+ repository not on the list with `repository_not_listed`. A native sub-agent
531
+ refuses `repository` with `repository_requires_coding_agent`. The lead sees the
532
+ list in the tool definition only when it has the same owner as the builder, or
533
+ another owner with execute access to a builder that sets
534
+ `allowWebhookTaskOverride`. All repositories share the builder's toolchain,
535
+ budget and prompt.
536
+
537
+ Being on the list lets a run work in a repository; it starts nothing there. Link
538
+ the lead and the reviewer on each listed GitHub repository with the triggers
539
+ they need, or a pull request opened there gets no review, mention follow-up, fix
540
+ round or mark-ready.
541
+
510
542
  #### Follow-ups across the set
511
543
 
512
544
  A human-triggered follow-up task (an `@<app-slug>` mention, or a Jira issue
@@ -520,8 +552,9 @@ too:
520
552
  repository that already has an open sibling for this request.
521
553
 
522
554
  The usual delegation rails still apply on top of this: `maxDelegationsPerRun`
523
- caps calls per run; a second `delegate_to_<name>` call to the same sub-agent
524
- in one run is refused with `already_dispatched`; dispatch re-checks every
555
+ caps calls per run; a run delegates to each sub-agent and repository at most
556
+ once, and a second such call is refused with `already_dispatched`; dispatch
557
+ re-checks every
525
558
  `continuePriorRun` id (refusing with `continuation_refused` when this
526
559
  deployment can't continue the run it names — for example it belongs to a
527
560
  different owner's agent); every push to a sibling pull request still gets its
@@ -95,7 +95,7 @@ and GKE Autopilot overlays.
95
95
 
96
96
  ### Native agents in a sandbox
97
97
 
98
- Native sandbox requires Wardby 0.5.4 or later.
98
+ Native sandbox requires Wardby 0.6.0 or later.
99
99
 
100
100
  Native agents run in the server process by default. A native agent set to
101
101
  `nativeExecutionMode=sandbox` instead runs its turn loop and user tools in a