@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
@@ -36,6 +36,7 @@ import { parseWorkerDiagnosticLine } from "./docker.js";
36
36
  import { KubernetesAlreadyExistsError, KubernetesConflictError, KubernetesQuotaExceededError, } from "./kubernetes-api.js";
37
37
  import { ENFORCEMENT_PROBE_DENY_REACHABLE, ENFORCEMENT_PROBE_PROVEN, ENFORCEMENT_PROBE_DENY_REFUSED, ENFORCEMENT_PROBE_PROXY_UNREACHABLE, KEEPER_CONTAINER, KEEPER_SEEDED_MARKER, KUBERNETES_ISOLATION_ERROR, STORAGE_ROOT, TOOL_RUNNER_CONTAINER, WORKER_CONTAINER, assertRunNetworkPolicyMatches, assertRunPodMatches, buildCapabilitySecret, buildRunNetworkPolicy, buildRunPod, enforcementStreakScript, kubernetesRunNames, kubernetesRunNamesForToken, runLabels, serviceContainerName, validateKubernetesSpec, } from "./kubernetes-isolation.js";
38
38
  import { claudeToolSetup } from "./claude-tool-setup.js";
39
+ import { isMcpToken } from "../../coding/mcp-token.js";
39
40
  import { podQuotaUsage, quotaShortfall } from "./kubernetes-quota.js";
40
41
  import { readProxyWitness } from "./kubernetes-witness.js";
41
42
  import { safeExtract } from "./safe-extract.js";
@@ -838,6 +839,10 @@ export class KubernetesJobLauncher {
838
839
  const capability = await this.options.resolveCapability(spec.runId);
839
840
  if (!CAPABILITY.test(capability))
840
841
  throw new Error("kubernetes_capability_invalid");
842
+ const mcpToken = spec.mcpTools ? await this.options.resolveMcpToken?.(spec.runId) : undefined;
843
+ if (spec.mcpTools && !isMcpToken(mcpToken)) {
844
+ throw new Error("kubernetes_mcp_token_invalid");
845
+ }
841
846
  // Re-read (not just the ClusterIP): the gate below reads "deny port unreachable" as evidence,
842
847
  // which is only meaningful against a Service that actually exposes it with a ready backend.
843
848
  const witness = await readProxyWitness(this.api, this.namespace, proxyService);
@@ -851,7 +856,7 @@ export class KubernetesJobLauncher {
851
856
  });
852
857
  const policy = buildRunNetworkPolicy(spec, this.namespace);
853
858
  const toolSetup = spec.provider === "claude-code" ? claudeToolSetup(spec, capability) : undefined;
854
- await this.createIfMissing(() => this.api.createSecret(this.namespace, buildCapabilitySecret(spec, this.namespace, capability, toolSetup)));
859
+ await this.createIfMissing(() => this.api.createSecret(this.namespace, buildCapabilitySecret(spec, this.namespace, capability, toolSetup, mcpToken)));
855
860
  await this.createIfMissing(() => this.api.createNetworkPolicy(this.namespace, policy));
856
861
  await this.createIfMissing(() => this.api.createPod(this.namespace, pod));
857
862
  await this.waitForKeeper(names, spec.runId, (spec.services ?? []).map((service) => service.name), spec.provider === "claude-code");
@@ -29,6 +29,12 @@ export interface JobSpec {
29
29
  * scratch so a long session never fills the worker's shared /tmp.
30
30
  */
31
31
  sessionResume?: true;
32
+ /**
33
+ * Set only when the run has pinned MCP tools (remote MCP servers); absent otherwise, never false.
34
+ * The launcher then delivers the run's MCP token, from the capability vault, as WARDBY_MCP_TOKEN to
35
+ * the agent container only (never Claude's tool runner). The token itself is never in the spec.
36
+ */
37
+ mcpTools?: true;
32
38
  timeoutSec: number;
33
39
  limits: JobResourceLimits;
34
40
  labels: Record<string, string>;
@@ -1,14 +1,16 @@
1
1
  /**
2
2
  * Host-neutral code-review surface for native agents. GitHub (./github.ts) and
3
- * local git repositories (./local.ts) implement it; Bitbucket Cloud is anticipated. Every method
3
+ * local git repositories (./local.ts) implement it, and registered host providers add their own. Every method
4
4
  * takes the repository and mints its own least-permission credential — no
5
5
  * method accepts a token or a permission. See
6
6
  * docs/private/2026-09-25-code-review-host-design.md §5.
7
7
  */
8
- export type ReviewHostProvider = "github" | "local";
9
- export declare const REVIEW_HOST_PROVIDERS: readonly ReviewHostProvider[];
8
+ /** Compiled-in hosts. Registered host providers add their own names at runtime. */
9
+ export type BuiltInHostProvider = "github" | "local";
10
+ export type ReviewHostProvider = BuiltInHostProvider | (string & {});
11
+ export declare const REVIEW_HOST_PROVIDERS: readonly BuiltInHostProvider[];
10
12
  /** Providers with host user accounts to link (link_host_account); a local repository has none. */
11
- export declare const HOST_ACCOUNT_PROVIDERS: readonly ReviewHostProvider[];
13
+ export declare const HOST_ACCOUNT_PROVIDERS: readonly BuiltInHostProvider[];
12
14
  export type ReviewVerdict = "APPROVE" | "CHANGES_REQUESTED" | "COMMENT";
13
15
  export type CheckConclusion = "success" | "failure" | "neutral";
14
16
  export type CommentSide = "LEFT" | "RIGHT";
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Host-neutral code-review surface for native agents. GitHub (./github.ts) and
3
- * local git repositories (./local.ts) implement it; Bitbucket Cloud is anticipated. Every method
3
+ * local git repositories (./local.ts) implement it, and registered host providers add their own. Every method
4
4
  * takes the repository and mints its own least-permission credential — no
5
5
  * method accepts a token or a permission. See
6
6
  * docs/private/2026-09-25-code-review-host-design.md §5.
@@ -1,11 +1,13 @@
1
- import { type RepositoryFileInput } from "./github.js";
2
- import type { GitRemote } from "./remote.js";
1
+ import type { RepositoryFileInput } from "./types.js";
2
+ import { type GitRemote } from "./remote.js";
3
3
  import type { ContinuationFinishedDetails, ContinuationOutcome, ContinuationTarget, DescriptionWriteOutcome, FinalizeChangesDetails, FinalizeChangesResult, PreparedWorkspace, VcsPrepareInput, VcsProvider } from "./types.js";
4
4
  export declare const DEFAULT_MAX_CHANGED_FILES = 1000;
5
5
  export declare const DEFAULT_MAX_DIFF_BYTES: number;
6
6
  export interface GitCommandOptions {
7
7
  cwd?: string;
8
8
  authToken?: string;
9
+ /** The username git sends with the token (default: git-askpass.sh's GitHub default). Not secret. */
10
+ authUsername?: string;
9
11
  maxOutputBytes?: number;
10
12
  /** Non-zero exit codes that still resolve with the output (`merge-tree` exits 1 on conflicts). */
11
13
  acceptExitCodes?: readonly number[];
@@ -127,6 +129,8 @@ export declare class GitVcsProvider implements VcsProvider {
127
129
  * problem).
128
130
  */
129
131
  private assertSafeLocalConfig;
132
+ /** The git credentials for a remote's access token; none when the remote needs no token. */
133
+ private gitAuth;
130
134
  private pushOnce;
131
135
  private remoteHead;
132
136
  private revParse;
@@ -10,12 +10,13 @@ import { simulatorGitEnvironment } from "../../config/test-simulators.js";
10
10
  import { globRegex } from "../../core/glob.js";
11
11
  import { redactExactSecretValues } from "../../core/secret-spans.js";
12
12
  import { MAX_REDACTED_SPAN, redactTokenShapedValues, normalizeGitRef } from "../../coding/protocol.js";
13
- import { isSafeGitHubInstallationToken } from "./github.js";
13
+ import { isSafeGitAuthToken } from "./remote.js";
14
14
  export const DEFAULT_MAX_CHANGED_FILES = 1_000;
15
15
  export const DEFAULT_MAX_DIFF_BYTES = 1024 * 1024;
16
16
  const MAX_GIT_OUTPUT_BYTES = 2 * 1024 * 1024;
17
17
  const MAX_WORKSPACE_ENTRIES = 100_000;
18
18
  const SAFE_COMMIT_SHA = /^[0-9a-f]{40}$/;
19
+ const SAFE_GIT_USERNAME = /^[A-Za-z0-9._-]{1,64}$/;
19
20
  const SAFE_RUN_ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
20
21
  /** Where a continuation's target branch is fetched to (never under refs/remotes, so baseCommit's ref is untouched). */
21
22
  const MERGE_TARGET_REF = "refs/wardby/merge-target";
@@ -82,9 +83,12 @@ export class NodeGitCommandRunner {
82
83
  options.askPassPath ?? fileURLToPath(new URL("../../../scripts/git-askpass.sh", import.meta.url));
83
84
  }
84
85
  async run(args, options = {}) {
85
- if (options.authToken !== undefined && !isSafeGitHubInstallationToken(options.authToken)) {
86
+ if (options.authToken !== undefined && !isSafeGitAuthToken(options.authToken)) {
86
87
  throw new Error("git_auth_token_invalid");
87
88
  }
89
+ if (options.authUsername !== undefined && !SAFE_GIT_USERNAME.test(options.authUsername)) {
90
+ throw new Error("git_auth_username_invalid");
91
+ }
88
92
  const env = {
89
93
  PATH: this.path,
90
94
  HOME: this.options.homeDir,
@@ -97,6 +101,7 @@ export class NodeGitCommandRunner {
97
101
  GIT_TERMINAL_PROMPT: "0",
98
102
  GCM_INTERACTIVE: "never",
99
103
  GIT_ASKPASS: options.authToken ? this.askPassPath : "/bin/false",
104
+ ...(options.authToken && options.authUsername ? { WARDBY_GIT_USERNAME: options.authUsername } : {}),
100
105
  SSH_ASKPASS: "/bin/false",
101
106
  GIT_LFS_SKIP_SMUDGE: "1",
102
107
  // WARDBY_TEST_SIMULATORS (test only): the simulator's throwaway CA; empty otherwise.
@@ -293,7 +298,7 @@ export class GitVcsProvider {
293
298
  let mergeFetched = false;
294
299
  try {
295
300
  await this.remote.withAccess(normalized.repository, async (token) => {
296
- const auth = token !== undefined ? { authToken: token } : {};
301
+ const auth = this.gitAuth(token);
297
302
  if (this.remote.refNotFoundError) {
298
303
  const listed = await this.git.run([
299
304
  ...HARDENED_GIT_CONFIG,
@@ -937,10 +942,16 @@ export class GitVcsProvider {
937
942
  throw new Error("vcs_git_config_unsafe");
938
943
  }
939
944
  }
945
+ /** The git credentials for a remote's access token; none when the remote needs no token. */
946
+ gitAuth(token) {
947
+ if (token === undefined)
948
+ return {};
949
+ return { authToken: token, ...(this.remote.gitUsername ? { authUsername: this.remote.gitUsername } : {}) };
950
+ }
940
951
  async pushOnce(workspace, commitSha) {
941
952
  this.remote.assertPushRef?.(workspace.headRef);
942
953
  await this.remote.withAccess(workspace.repository, async (token) => {
943
- const auth = token !== undefined ? { authToken: token } : {};
954
+ const auth = this.gitAuth(token);
944
955
  const remote = await this.remoteHead(workspace, auth);
945
956
  if (remote === commitSha)
946
957
  return;
@@ -1,5 +1,7 @@
1
1
  import { type PackageAuditReport } from "../../coding/registry/audit-mode.js";
2
2
  import { RELATED_SECTION_END, RELATED_SECTION_START } from "./pr-description.js";
3
+ import type { DescriptionBaseline, DescriptionUpdateResult, RelatedPullRequestEntry, RelatedPullRequestState, RelatedPullRequestsInput, RepositoryFileInput } from "./types.js";
4
+ export type { DescriptionBaseline, DescriptionUpdateResult, RelatedPullRequestEntry, RelatedPullRequestState, RelatedPullRequestsInput, RepositoryFileInput, };
3
5
  export { RELATED_SECTION_END, RELATED_SECTION_START };
4
6
  export interface GitHubAppConfig {
5
7
  appId: string;
@@ -106,22 +108,6 @@ export interface PullRequestResult {
106
108
  */
107
109
  descriptionSha256?: string;
108
110
  }
109
- /** What wardby last wrote into a pull request description (control-plane records). */
110
- export interface DescriptionBaseline {
111
- /** Null = legacy pull request (no recorded write). */
112
- recordedSha256: string | null;
113
- /**
114
- * Legacy pull requests only: the hash of the body wardby would have written
115
- * for the root run, rebuilt from stored records. A current body with this
116
- * hash is unedited. Absent when the records can't rebuild it (then the
117
- * description counts as edited).
118
- */
119
- legacySha256?: string;
120
- /** Stored summary of the run whose text the description carries (root run for legacy). */
121
- previousAgentText?: string;
122
- /** That run's id. */
123
- agentTextRunId: string;
124
- }
125
111
  /**
126
112
  * A fix round's description update. `runId` is this (latest) run; the pull
127
113
  * request is re-found by `rootRunId`'s marker. `related` is ignored: the
@@ -138,23 +124,6 @@ export interface UpdateDescriptionInput extends Omit<PullRequestInput, "runId" |
138
124
  */
139
125
  headSha?: string;
140
126
  }
141
- /** `agentTextRunId` is the run whose summary the description now carries. */
142
- export type DescriptionUpdateResult = {
143
- kind: "replaced" | "unchanged";
144
- sha256: string;
145
- agentTextRunId: string;
146
- } | {
147
- kind: "refreshed";
148
- reason: "empty" | "too_short" | "missing_closing_keyword";
149
- sha256: string;
150
- agentTextRunId: string;
151
- } | {
152
- kind: "edited_by_hand";
153
- commentPosted: boolean;
154
- } | {
155
- kind: "skipped";
156
- reason: "not_open" | "too_long" | "error";
157
- };
158
127
  /** See VcsProvider.notifyContinuationStarted/Finished (types.ts) — these back that GitHub-specific mechanism. */
159
128
  export interface ContinuationStatusCommentInput {
160
129
  /** This continuation round's own run id — the comment's identity/marker key. */
@@ -185,15 +154,6 @@ export interface ContinuationCheckRunCompleteInput {
185
154
  */
186
155
  headRef?: string;
187
156
  }
188
- /** One file at one ref, read through the API without a clone. */
189
- export interface RepositoryFileInput {
190
- repository: string;
191
- ref: string;
192
- /** Repository-relative POSIX path. */
193
- path: string;
194
- /** Refused above this many bytes. */
195
- maxBytes: number;
196
- }
197
157
  export interface GitHubRepositoryAccess {
198
158
  withRepositoryToken<T>(repository: string, action: (token: string) => Promise<T>): Promise<T>;
199
159
  createOrFindDraftPullRequest(input: PullRequestInput): Promise<PullRequestResult>;
@@ -264,8 +224,6 @@ export interface GitHubRepositoryAccess {
264
224
  readFileAtRef?(input: RepositoryFileInput): Promise<string | null>;
265
225
  }
266
226
  type Fetch = typeof globalThis.fetch;
267
- /** Accepts bounded opaque installation tokens without allowing control characters. */
268
- export declare function isSafeGitHubInstallationToken(value: unknown): value is string;
269
227
  /**
270
228
  * Same shape as ISSUE_KEY (issue-tracker/types.ts); duplicated so the VCS layer
271
229
  * stays free of issue-tracker imports (a test keeps the two in step). The
@@ -279,33 +237,6 @@ export declare function pullRequestTitle(input: PullRequestInput): string;
279
237
  export declare const MAX_RELATED_PULL_REQUESTS = 20;
280
238
  /** GitHub rejects a pull request body longer than this. */
281
239
  export declare const MAX_PULL_REQUEST_BODY_CHARS = 65536;
282
- export type RelatedPullRequestState = "open" | "draft" | "merged" | "closed";
283
- /** One pull request of the request's set, from stored control-plane rows only. */
284
- export interface RelatedPullRequestEntry {
285
- repository: string;
286
- /** Absent only for `self` while the pull request is being created. */
287
- number?: number;
288
- /** Live state; absent when unknown (at creation). */
289
- state?: RelatedPullRequestState;
290
- /** The pull request whose description this section is in. */
291
- self?: boolean;
292
- /**
293
- * Step in the delegating agent's declared merge order (1-99), from
294
- * CodingRun.mergeOrder; absent = the delegating agent set no order
295
- * (an open entry is listed under "Not ordered:" when any other entry,
296
- * open or done, has one).
297
- */
298
- mergeOrder?: number;
299
- }
300
- export interface RelatedPullRequestsInput {
301
- /** In merge order: by mergeOrder when set, else the order the request's coding runs were dispatched. */
302
- entries: readonly RelatedPullRequestEntry[];
303
- issue?: {
304
- key: string;
305
- url?: string;
306
- trackerName?: string;
307
- };
308
- }
309
240
  /**
310
241
  * The "Related pull requests" block, markers included, built only from
311
242
  * stored rows (never agent text). Open (and draft, and not-yet-known) pull
@@ -2,8 +2,10 @@ import { createPrivateKey } from "node:crypto";
2
2
  import { importPKCS8, SignJWT } from "jose";
3
3
  import { isGitHubLogin } from "../../config/github-allowed-accounts.js";
4
4
  import { testSimulatorBase } from "../../config/test-simulators.js";
5
+ import { codeProviderOf, normalizeHostedRepository, pullRequestWebUrl } from "../../coding/code-host.js";
5
6
  import { ACCEPTED_ADVISORY_ID } from "../../coding/registry/allowlist.js";
6
7
  import { packageAuditRows, packageAuditWording, summarizePackageAudit, } from "../../coding/registry/audit-mode.js";
8
+ import { isSafeGitAuthToken } from "./remote.js";
7
9
  import { refusalMeaning } from "../../coding/registry/refusal-hints.js";
8
10
  import { DEPENDENCY_CACHE_PATH, normalizeGitHubRepository, normalizeGitRef, truncateText, } from "../../coding/protocol.js";
9
11
  import { LATEST_RUN_MARKER_PREFIX, RELATED_SECTION_END, RELATED_SECTION_START, assessFinalMessage, descriptionAppliedComment, descriptionFallbackComment, descriptionSha256, extractRelatedBlock, findDescriptionComments, hasClosingKeyword, isUneditedDescription, supersededDescriptionComment, } from "./pr-description.js";
@@ -33,13 +35,6 @@ function safeApiError(response) {
33
35
  const requestId = response.headers.get("x-github-request-id");
34
36
  return new Error(`github_api_error:${response.status}${requestId ? `:${requestId}` : ""}`);
35
37
  }
36
- /** Accepts bounded opaque installation tokens without allowing control characters. */
37
- export function isSafeGitHubInstallationToken(value) {
38
- return (typeof value === "string" &&
39
- value.length >= 20 &&
40
- Buffer.byteLength(value, "utf8") <= 512 &&
41
- /^[A-Za-z0-9._-]+$/.test(value));
42
- }
43
38
  /**
44
39
  * Same shape as ISSUE_KEY (issue-tracker/types.ts); duplicated so the VCS layer
45
40
  * stays free of issue-tracker imports (a test keeps the two in step). The
@@ -109,10 +104,10 @@ export const MAX_RELATED_PULL_REQUESTS = 20;
109
104
  /** GitHub rejects a pull request body longer than this. */
110
105
  export const MAX_PULL_REQUEST_BODY_CHARS = 65_536;
111
106
  const RELATED_STATES = new Set(["open", "draft", "merged", "closed"]);
112
- /** The repository normalized, or undefined when it isn't a plain owner/name safe inside a link. */
107
+ /** The repository normalized, or undefined when it isn't a hosted repository id safe inside a link. */
113
108
  function safeRepository(value) {
114
109
  try {
115
- const repository = normalizeGitHubRepository(value);
110
+ const repository = normalizeHostedRepository(value);
116
111
  return isSafeMarkdownFragment(repository) && !/[[\]()<>\s]/.test(repository) ? repository : undefined;
117
112
  }
118
113
  catch {
@@ -129,7 +124,9 @@ function relatedLine(entry, prefix) {
129
124
  return `${prefix} **This pull request**${state}`;
130
125
  if (!Number.isSafeInteger(entry.number) || (entry.number ?? 0) <= 0)
131
126
  return undefined;
132
- return `${prefix} [${repository}#${entry.number}](https://github.com/${repository}/pull/${entry.number})${state}`;
127
+ const label = `${repository}#${entry.number}`;
128
+ const url = pullRequestWebUrl(codeProviderOf(repository), repository, entry.number);
129
+ return `${prefix} ${url ? `[${label}](${url})` : label}${state}`;
133
130
  }
134
131
  const isDone = (entry) => entry.state === "merged" || entry.state === "closed";
135
132
  /**
@@ -1103,7 +1100,7 @@ export class GitHubAppClient {
1103
1100
  const expiresAt = typeof payload.expires_at === "string" ? Date.parse(payload.expires_at) : Number.NaN;
1104
1101
  const unexpectedPermission = Object.entries(grantedPermissions).some(([permissionName, level]) => !isAllowedPermission(permissionName, level));
1105
1102
  const requestedGranted = Object.entries(permissions).every(([permissionName, level]) => grantedPermissions[permissionName] === level);
1106
- if (!isSafeGitHubInstallationToken(payload.token) ||
1103
+ if (!isSafeGitAuthToken(payload.token) ||
1107
1104
  !Number.isFinite(expiresAt) ||
1108
1105
  expiresAt <= this.now().getTime() + 60_000 ||
1109
1106
  !requestedGranted ||
@@ -5,7 +5,7 @@ export { GitHubAppClient } from "./github.js";
5
5
  export { GitVcsProvider, NodeGitCommandRunner } from "./git.js";
6
6
  export { GitHubRemote } from "./github-remote.js";
7
7
  export { LocalRemote } from "./local-remote.js";
8
- export { RoutingVcsProvider, VCS_GITHUB_NOT_CONFIGURED_ERROR } from "./routing.js";
8
+ export { RoutingVcsProvider, VCS_GITHUB_NOT_CONFIGURED_ERROR, VCS_HOST_PROVIDER_NOT_CONFIGURED_ERROR, } from "./routing.js";
9
9
  export type { GitRemote, PublishInput, PublishResult } from "./remote.js";
10
10
  export interface BuildVcsProviderOptions {
11
11
  /** Startup warnings; the module logger by default. */
@@ -14,7 +14,7 @@ export { GitHubAppClient } from "./github.js";
14
14
  export { GitVcsProvider, NodeGitCommandRunner } from "./git.js";
15
15
  export { GitHubRemote } from "./github-remote.js";
16
16
  export { LocalRemote } from "./local-remote.js";
17
- export { RoutingVcsProvider, VCS_GITHUB_NOT_CONFIGURED_ERROR } from "./routing.js";
17
+ export { RoutingVcsProvider, VCS_GITHUB_NOT_CONFIGURED_ERROR, VCS_HOST_PROVIDER_NOT_CONFIGURED_ERROR, } from "./routing.js";
18
18
  /**
19
19
  * GitHub is built only with App credentials, local repositories only with
20
20
  * LOCAL_REPO_ROOTS; neither being set is allowed (the server still starts) and
@@ -1,4 +1,6 @@
1
1
  import type { DescriptionWriteOutcome, FinalizeChangesDetails, PreparedWorkspace, VcsProvider } from "./types.js";
2
+ /** Accepts bounded opaque git auth tokens without allowing control characters. */
3
+ export declare function isSafeGitAuthToken(value: unknown): value is string;
2
4
  /** What a remote needs to publish a pushed commit (GitHub: open or find the draft PR). */
3
5
  export interface PublishInput {
4
6
  /** The validated workspace whose headRef was just pushed. */
@@ -25,6 +27,8 @@ export type PublishResult = {
25
27
  * fetch-compare-push) stays in GitVcsProvider and applies to every remote.
26
28
  */
27
29
  export interface GitRemote {
30
+ /** The username git sends with the token (GitHub: unset, git-askpass.sh's default). */
31
+ readonly gitUsername?: string;
28
32
  /** Extra `-c` pairs appended after HARDENED_GIT_CONFIG for clone/push/ls-remote (GitHub: none). */
29
33
  readonly gitConfig: readonly string[];
30
34
  /** Syntactic normalization of a repository id for this remote (GitHub: owner/name; local: local:/abs/path). */
@@ -1 +1,7 @@
1
- export {};
1
+ /** Accepts bounded opaque git auth tokens without allowing control characters. */
2
+ export function isSafeGitAuthToken(value) {
3
+ return (typeof value === "string" &&
4
+ value.length >= 20 &&
5
+ Buffer.byteLength(value, "utf8") <= 512 &&
6
+ /^[A-Za-z0-9._-]+$/.test(value));
7
+ }
@@ -1,19 +1,25 @@
1
- import type { RepositoryFileInput } from "./github.js";
1
+ import type { RepositoryFileInput } from "./types.js";
2
2
  import type { ContinuationFinishedDetails, ContinuationOutcome, ContinuationTarget, DescriptionWriteOutcome, FinalizeChangesDetails, FinalizeChangesResult, PreparedWorkspace, VcsPrepareInput, VcsProvider } from "./types.js";
3
3
  /** Keeps the `vcs_github_not_configured` code prefix that error matching and categorization key on. */
4
4
  export declare const VCS_GITHUB_NOT_CONFIGURED_ERROR = "vcs_github_not_configured: set GITHUB_APP_ID and GITHUB_APP_PRIVATE_KEY to run coding agents on GitHub repositories";
5
+ /** Keeps a stable code prefix for error matching. The provider name comes from a validated repository id. */
6
+ export declare const VCS_HOST_PROVIDER_NOT_CONFIGURED_ERROR = "vcs_host_provider_not_configured";
5
7
  export interface RoutingVcsOptions {
6
8
  /** Null when no GitHub App is configured. */
7
9
  github: VcsProvider | null;
8
10
  /** Null when no local repository roots are configured. */
9
11
  local: VcsProvider | null;
12
+ /** Registered host providers, by name. */
13
+ hosted?: ReadonlyMap<string, VcsProvider>;
10
14
  }
11
15
  /**
12
- * One VcsProvider over both repository kinds: `local:/abs/path` goes to the
13
- * local provider, everything else to GitHub. A kind that is not configured
14
- * fails only when a repository of that kind is actually used, so a server with
15
- * only one of the two still starts. The repository name is not re-resolved
16
- * here: the local provider verifies it against the trusted roots.
16
+ * One VcsProvider over every repository kind: `local:/abs/path` goes to the
17
+ * local provider, `<provider>:<workspace>/<repo>` to the registered host
18
+ * provider of that name, and everything else to GitHub. A kind that is not
19
+ * configured fails only when a repository of that kind is actually used, so a
20
+ * server with only some of them still starts. A hosted repository never falls
21
+ * back to GitHub. The repository name is not re-resolved here: the local
22
+ * provider verifies it against the trusted roots.
17
23
  */
18
24
  export declare class RoutingVcsProvider implements VcsProvider {
19
25
  private readonly options;
@@ -1,12 +1,17 @@
1
+ import { codeProviderOf } from "../../coding/code-host.js";
1
2
  import { LocalRepoError, isLocalRepository } from "../../coding/local-repo.js";
2
3
  /** Keeps the `vcs_github_not_configured` code prefix that error matching and categorization key on. */
3
4
  export const VCS_GITHUB_NOT_CONFIGURED_ERROR = "vcs_github_not_configured: set GITHUB_APP_ID and GITHUB_APP_PRIVATE_KEY to run coding agents on GitHub repositories";
5
+ /** Keeps a stable code prefix for error matching. The provider name comes from a validated repository id. */
6
+ export const VCS_HOST_PROVIDER_NOT_CONFIGURED_ERROR = "vcs_host_provider_not_configured";
4
7
  /**
5
- * One VcsProvider over both repository kinds: `local:/abs/path` goes to the
6
- * local provider, everything else to GitHub. A kind that is not configured
7
- * fails only when a repository of that kind is actually used, so a server with
8
- * only one of the two still starts. The repository name is not re-resolved
9
- * here: the local provider verifies it against the trusted roots.
8
+ * One VcsProvider over every repository kind: `local:/abs/path` goes to the
9
+ * local provider, `<provider>:<workspace>/<repo>` to the registered host
10
+ * provider of that name, and everything else to GitHub. A kind that is not
11
+ * configured fails only when a repository of that kind is actually used, so a
12
+ * server with only some of them still starts. A hosted repository never falls
13
+ * back to GitHub. The repository name is not re-resolved here: the local
14
+ * provider verifies it against the trusted roots.
10
15
  */
11
16
  export class RoutingVcsProvider {
12
17
  options;
@@ -20,13 +25,23 @@ export class RoutingVcsProvider {
20
25
  }
21
26
  return this.options.local;
22
27
  }
28
+ const provider = codeProviderOf(repository);
29
+ if (provider !== "github") {
30
+ const hosted = this.options.hosted?.get(provider);
31
+ if (!hosted)
32
+ throw new Error(`${VCS_HOST_PROVIDER_NOT_CONFIGURED_ERROR}: no host provider named ${provider}`);
33
+ return hosted;
34
+ }
23
35
  if (!this.options.github)
24
36
  throw new Error(VCS_GITHUB_NOT_CONFIGURED_ERROR);
25
37
  return this.options.github;
26
38
  }
27
39
  /** The target when configured, else null (best-effort calls that must never throw). */
28
40
  optionalTarget(repository) {
29
- return (isLocalRepository(repository) ? this.options.local : this.options.github) ?? null;
41
+ if (isLocalRepository(repository))
42
+ return this.options.local ?? null;
43
+ const provider = codeProviderOf(repository);
44
+ return (provider === "github" ? this.options.github : this.options.hosted?.get(provider)) ?? null;
30
45
  }
31
46
  async prepareWorkspace(input) {
32
47
  return this.target(input.repository).prepareWorkspace(input);
@@ -1,5 +1,4 @@
1
1
  import type { PackageAuditReport } from "../../coding/registry/audit-mode.js";
2
- import type { DescriptionBaseline, DescriptionUpdateResult, RelatedPullRequestsInput, RepositoryFileInput } from "./github.js";
3
2
  /** What happened to a pull request description: wardby just wrote it (a new PR) or updated a fix round's. */
4
3
  export type DescriptionWriteOutcome = {
5
4
  kind: "written";
@@ -168,8 +167,8 @@ export interface VcsProvider {
168
167
  * Best-effort "wardby is working on this" signal for a continuation
169
168
  * a no-op when `workspace.continuation` is unset, since a fresh run has
170
169
  * no PR to attach anything to until its one commit lands. Optional
171
- * because this is a GitHub-specific concept, not a universal VCS one --
172
- * a future non-GitHub provider (or a test double) can simply omit it,
170
+ * because not every host has this signal (GitHub does; others may too) --
171
+ * a provider (or a test double) can simply omit it,
173
172
  * mirroring `Executor.resolveCodingWorkerImage?` (dispatch.ts calls it
174
173
  * with `?.()`). Implementations MUST NOT throw: this is strictly
175
174
  * observability, never allowed to affect the real coding run.
@@ -248,3 +247,72 @@ export interface ContinuationFinishedDetails {
248
247
  pullRequestNumber?: number;
249
248
  };
250
249
  }
250
+ /** What wardby last wrote into a pull request description (control-plane records). */
251
+ export interface DescriptionBaseline {
252
+ /** Null = legacy pull request (no recorded write). */
253
+ recordedSha256: string | null;
254
+ /**
255
+ * Legacy pull requests only: the hash of the body wardby would have written
256
+ * for the root run, rebuilt from stored records. A current body with this
257
+ * hash is unedited. Absent when the records can't rebuild it (then the
258
+ * description counts as edited).
259
+ */
260
+ legacySha256?: string;
261
+ /** Stored summary of the run whose text the description carries (root run for legacy). */
262
+ previousAgentText?: string;
263
+ /** That run's id. */
264
+ agentTextRunId: string;
265
+ }
266
+ /** `agentTextRunId` is the run whose summary the description now carries. */
267
+ export type DescriptionUpdateResult = {
268
+ kind: "replaced" | "unchanged";
269
+ sha256: string;
270
+ agentTextRunId: string;
271
+ } | {
272
+ kind: "refreshed";
273
+ reason: "empty" | "too_short" | "missing_closing_keyword";
274
+ sha256: string;
275
+ agentTextRunId: string;
276
+ } | {
277
+ kind: "edited_by_hand";
278
+ commentPosted: boolean;
279
+ } | {
280
+ kind: "skipped";
281
+ reason: "not_open" | "too_long" | "error";
282
+ };
283
+ /** One file at one ref, read through the API without a clone. */
284
+ export interface RepositoryFileInput {
285
+ repository: string;
286
+ ref: string;
287
+ /** Repository-relative POSIX path. */
288
+ path: string;
289
+ /** Refused above this many bytes. */
290
+ maxBytes: number;
291
+ }
292
+ export type RelatedPullRequestState = "open" | "draft" | "merged" | "closed";
293
+ /** One pull request of the request's set, from stored control-plane rows only. */
294
+ export interface RelatedPullRequestEntry {
295
+ repository: string;
296
+ /** Absent only for `self` while the pull request is being created. */
297
+ number?: number;
298
+ /** Live state; absent when unknown (at creation). */
299
+ state?: RelatedPullRequestState;
300
+ /** The pull request whose description this section is in. */
301
+ self?: boolean;
302
+ /**
303
+ * Step in the delegating agent's declared merge order (1-99), from
304
+ * CodingRun.mergeOrder; absent = the delegating agent set no order
305
+ * (an open entry is listed under "Not ordered:" when any other entry,
306
+ * open or done, has one).
307
+ */
308
+ mergeOrder?: number;
309
+ }
310
+ export interface RelatedPullRequestsInput {
311
+ /** In merge order: by mergeOrder when set, else the order the request's coding runs were dispatched. */
312
+ entries: readonly RelatedPullRequestEntry[];
313
+ issue?: {
314
+ key: string;
315
+ url?: string;
316
+ trackerName?: string;
317
+ };
318
+ }
@@ -5,6 +5,11 @@
5
5
  */
6
6
  export declare function mcpAddArgs(client: "codex" | "claude", projectDir: string, packageSpec: string): string[];
7
7
  export declare function quickstartCommand(args: string[]): Promise<void>;
8
+ /**
9
+ * Doctor's Claude Code pane section. The pane is optional, so its lines are information: a "✗"
10
+ * from them would be read as a failure, so none reaches the output.
11
+ */
12
+ export declare function claudePaneDoctorLines<D>(lines: (deps: D) => string[], deps: D): string[];
8
13
  export declare function doctorCommand(args: string[]): Promise<void>;
9
14
  export declare function statusCommand(args: string[]): Promise<void>;
10
15
  export declare function logsCommand(args: string[]): Promise<void>;
@@ -360,6 +360,7 @@ export async function quickstartCommand(args) {
360
360
  client: { type: "string" },
361
361
  "non-interactive": { type: "boolean" },
362
362
  "skip-demo": { type: "boolean" },
363
+ "claude-plugin": { type: "boolean" },
363
364
  yes: { type: "boolean", short: "y" },
364
365
  },
365
366
  });
@@ -447,6 +448,15 @@ export async function quickstartCommand(args) {
447
448
  const codingRan = coding?.seed !== undefined && coding.seed.builder.status !== "skipped" && coding.seed.reviewer.status !== "skipped";
448
449
  const client = parseClient(values.client) ?? (await chooseClient(nonInteractive));
449
450
  const mcpConfigured = client !== "none" && configureMcpClients(client, paths);
451
+ // With Claude Code connected, offer the runs pane (the wardby-runs plugin): off unless asked for.
452
+ {
453
+ const { claudePluginDeps, installPlugin, quickstartPaneStep } = await import("../claude-plugin/command.js");
454
+ await quickstartPaneStep({ client, mcpConfigured, flag: values["claude-plugin"], nonInteractive, yes: values.yes ?? false }, {
455
+ ask: (question) => promptYesNo(question, false),
456
+ install: (options) => installPlugin(options, claudePluginDeps()),
457
+ warn: (line) => console.warn(line),
458
+ });
459
+ }
450
460
  console.log("\nWardby is ready.");
451
461
  console.log(" npx @wardby/cli@latest status");
452
462
  console.log(" npx @wardby/cli@latest doctor");
@@ -471,6 +481,13 @@ async function databaseHealthy(paths) {
471
481
  await db.$disconnect();
472
482
  }
473
483
  }
484
+ /**
485
+ * Doctor's Claude Code pane section. The pane is optional, so its lines are information: a "✗"
486
+ * from them would be read as a failure, so none reaches the output.
487
+ */
488
+ export function claudePaneDoctorLines(lines, deps) {
489
+ return lines(deps).map((line) => (line.startsWith("✗") ? `!${line.slice(1)}` : line));
490
+ }
474
491
  export async function doctorCommand(args) {
475
492
  parseArgs({ args, options: {} });
476
493
  const paths = quickstartPaths(resolveProjectDir());
@@ -520,6 +537,10 @@ export async function doctorCommand(args) {
520
537
  failed ||= line.startsWith("✗");
521
538
  console.log(line);
522
539
  }
540
+ // The Claude Code runs pane is optional: its lines never fail doctor.
541
+ const { claudePluginDeps, claudePluginDoctorLines } = await import("../claude-plugin/command.js");
542
+ for (const line of claudePaneDoctorLines(claudePluginDoctorLines, claudePluginDeps()))
543
+ console.log(line);
523
544
  const base = baseImageLine(resolveQuickstartImages({ env: runtimeEnv(paths), packageRoot }));
524
545
  if (base)
525
546
  console.log(base);
@@ -1 +1 @@
1
- {"runtime":"ghcr.io/wardby/wardby/wardby-runtime@sha256:e7458c9fba898c8315f3478b1ad0585d54e099d266aad65efe13d14d000fb49c","worker":"ghcr.io/wardby/wardby/wardby-coding-worker@sha256:e0fb40eff1583a63830c2647c4397e8553f4cc1f3005dd7a745b7936cc79d18c","claudeWorker":"ghcr.io/wardby/wardby/wardby-claude-coding-worker@sha256:f6ecf8c2195f4a8b7fcd859db2e6c101f0494e5eb5447a4613030ba9e3d966f2","claudeToolRunner":"ghcr.io/wardby/wardby/wardby-claude-tool-runner@sha256:efd0987be3a2ed67695fd8b4309762a33f0d0eb8dadd03bf5d6bda12108f282a","workerNodePython":"ghcr.io/wardby/wardby/wardby-coding-worker-node-python@sha256:ea42d825288b34da8d309fda3f344a8bdfb66be50c6179018daef5a98f7938f0","claudeToolRunnerNodePython":"ghcr.io/wardby/wardby/wardby-claude-tool-runner-node-python@sha256:876b387f0ada205ccf5cc5f6c0a07e5faf8d8dd47d21611df0905bfba5dd99f4","nativeWorker":"ghcr.io/wardby/wardby/wardby-native-worker@sha256:4ecf9ebf498de9f5e70f313e750e38bcba72a71c334c5f68413a71242c0bf18c","driver":"ghcr.io/wardby/wardby/wardby-coding-worker-driver@sha256:a9af4a2561c1aeb5876159fcbb28a3ae2ce386fe27c161ebb9855bd092ec3fc8"}
1
+ {"runtime":"ghcr.io/wardby/wardby/wardby-runtime@sha256:8efd21e571c4da6529494ef1a422f208b3d81764dab83e581abe2e5a2eaf08d3","worker":"ghcr.io/wardby/wardby/wardby-coding-worker@sha256:93c8191a4d89077f0aa60eb698543a9c390bba9cc5319bf814734e2075d39c20","claudeWorker":"ghcr.io/wardby/wardby/wardby-claude-coding-worker@sha256:464176af56a8692ca1d1202d04fc0311ea98cd0ba9d0671433f91912c2699492","claudeToolRunner":"ghcr.io/wardby/wardby/wardby-claude-tool-runner@sha256:e475ef7c7070c4a913bde2665bfe65a65fc0ff0db82a2c58953b9b4a453b277d","workerNodePython":"ghcr.io/wardby/wardby/wardby-coding-worker-node-python@sha256:02b04ccf6348706240c27a28615a3e17b3d8c0f828f20da0ae8d3af75e3eb9e5","claudeToolRunnerNodePython":"ghcr.io/wardby/wardby/wardby-claude-tool-runner-node-python@sha256:868eb258552ac23f17bc275e2bfe0619b9cf70e4bef075f93fa75d085ee735b4","nativeWorker":"ghcr.io/wardby/wardby/wardby-native-worker@sha256:8dd742964dcaea1a6da078b12fd2091d0b6e1783c3deb1f45d691727c63b4dd0","driver":"ghcr.io/wardby/wardby/wardby-coding-worker-driver@sha256:281a7cabc53d6ff1977d1b68c1420b2ef598f90da3995cb13df27395a58a60ef"}