@wardby/cli 0.4.0 → 0.5.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 (223) hide show
  1. package/.env.example +8 -1
  2. package/README.md +3 -1
  3. package/deploy/local/docker-compose.quickstart-coding.yml +27 -0
  4. package/dist/claude-coding-worker/driver.d.ts +2 -0
  5. package/dist/claude-coding-worker/driver.js +19 -3
  6. package/dist/claude-coding-worker/sdk.js +1 -1
  7. package/dist/cli.js +41 -22
  8. package/dist/coding/continuation-wording.d.ts +10 -0
  9. package/dist/coding/continuation-wording.js +13 -0
  10. package/dist/coding/docker-preflight.d.ts +12 -0
  11. package/dist/coding/docker-preflight.js +40 -0
  12. package/dist/coding/local-git.d.ts +22 -0
  13. package/dist/coding/local-git.js +102 -0
  14. package/dist/coding/local-repo-status.d.ts +7 -0
  15. package/dist/coding/local-repo-status.js +56 -0
  16. package/dist/coding/local-repo.d.ts +17 -0
  17. package/dist/coding/local-repo.js +65 -0
  18. package/dist/coding/observability.d.ts +1 -1
  19. package/dist/coding/observability.js +1 -0
  20. package/dist/coding/profile.d.ts +6 -0
  21. package/dist/coding/profile.js +13 -4
  22. package/dist/coding/protocol.d.ts +32 -7
  23. package/dist/coding/protocol.js +66 -4
  24. package/dist/coding-worker/artifact.d.ts +1 -0
  25. package/dist/coding-worker/errors.js +2 -0
  26. package/dist/config/providers.d.ts +13 -0
  27. package/dist/config/providers.js +21 -9
  28. package/dist/core/attribution.d.ts +13 -0
  29. package/dist/core/attribution.js +30 -0
  30. package/dist/core/budget-groups.d.ts +3 -1
  31. package/dist/core/budget-groups.js +53 -5
  32. package/dist/core/ci-context.d.ts +8 -0
  33. package/dist/core/ci-context.js +59 -0
  34. package/dist/core/delegation-siblings.d.ts +22 -0
  35. package/dist/core/delegation-siblings.js +40 -0
  36. package/dist/core/dispatch.d.ts +28 -4
  37. package/dist/core/dispatch.js +57 -15
  38. package/dist/core/engine-native.d.ts +16 -0
  39. package/dist/core/engine-native.js +49 -2
  40. package/dist/core/host-events.d.ts +25 -2
  41. package/dist/core/host-events.js +361 -19
  42. package/dist/core/host-status.d.ts +8 -3
  43. package/dist/core/host-status.js +38 -7
  44. package/dist/core/issue-events.d.ts +2 -6
  45. package/dist/core/issue-events.js +13 -19
  46. package/dist/core/pull-request-state-sync.d.ts +17 -0
  47. package/dist/core/pull-request-state-sync.js +53 -0
  48. package/dist/core/reconciler.d.ts +10 -1
  49. package/dist/core/reconciler.js +15 -2
  50. package/dist/core/related-pull-requests.d.ts +95 -0
  51. package/dist/core/related-pull-requests.js +338 -0
  52. package/dist/core/repo-access.d.ts +5 -1
  53. package/dist/core/repo-access.js +17 -1
  54. package/dist/core/review-fix-ledger.d.ts +20 -0
  55. package/dist/core/review-fix-ledger.js +41 -0
  56. package/dist/core/review-fix.d.ts +60 -0
  57. package/dist/core/review-fix.js +155 -0
  58. package/dist/core/review-host-tools.d.ts +13 -2
  59. package/dist/core/review-host-tools.js +72 -11
  60. package/dist/core/runner.d.ts +2 -2
  61. package/dist/core/runner.js +379 -184
  62. package/dist/core/serial-gate.d.ts +11 -0
  63. package/dist/core/serial-gate.js +19 -0
  64. package/dist/core/timing.d.ts +7 -0
  65. package/dist/core/timing.js +7 -0
  66. package/dist/env.js +6 -1
  67. package/dist/generated/prisma/browser.d.ts +23 -0
  68. package/dist/generated/prisma/client.d.ts +23 -0
  69. package/dist/generated/prisma/commonInputTypes.d.ts +22 -0
  70. package/dist/generated/prisma/internal/class.d.ts +33 -0
  71. package/dist/generated/prisma/internal/class.js +4 -4
  72. package/dist/generated/prisma/internal/prismaNamespace.d.ts +274 -1
  73. package/dist/generated/prisma/internal/prismaNamespace.js +47 -2
  74. package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +48 -0
  75. package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +47 -2
  76. package/dist/generated/prisma/models/Agent.d.ts +422 -1
  77. package/dist/generated/prisma/models/AgentRepository.d.ts +120 -2
  78. package/dist/generated/prisma/models/CodingAgentProfile.d.ts +42 -1
  79. package/dist/generated/prisma/models/CodingRun.d.ts +205 -1
  80. package/dist/generated/prisma/models/DeferredReview.d.ts +1336 -0
  81. package/dist/generated/prisma/models/DeferredReview.js +1 -0
  82. package/dist/generated/prisma/models/LocalPullRequest.d.ts +1384 -0
  83. package/dist/generated/prisma/models/LocalPullRequest.js +1 -0
  84. package/dist/generated/prisma/models/LocalReview.d.ts +1315 -0
  85. package/dist/generated/prisma/models/LocalReview.js +1 -0
  86. package/dist/generated/prisma/models/Run.d.ts +223 -0
  87. package/dist/generated/prisma/models/RunHostCheck.d.ts +148 -1
  88. package/dist/generated/prisma/models.d.ts +3 -0
  89. package/dist/help-index.json +476 -37
  90. package/dist/import/neutral-schema.d.ts +2 -2
  91. package/dist/mcp/auth/access.d.ts +3 -1
  92. package/dist/mcp/auth/host-account-cli.js +2 -2
  93. package/dist/mcp/auth/repo-authorization.d.ts +6 -7
  94. package/dist/mcp/auth/repo-authorization.js +21 -0
  95. package/dist/mcp/index.js +4 -1
  96. package/dist/mcp/tools/agents.js +46 -4
  97. package/dist/mcp/tools/host-accounts.js +2 -2
  98. package/dist/mcp/tools/repositories.js +71 -12
  99. package/dist/mcp/tools/runs.js +23 -0
  100. package/dist/mcp/tools/trigger.js +153 -24
  101. package/dist/providers/engine/types.d.ts +11 -0
  102. package/dist/providers/executor/composition.js +12 -7
  103. package/dist/providers/executor/container.d.ts +40 -3
  104. package/dist/providers/executor/container.js +140 -9
  105. package/dist/providers/executor/types.d.ts +1 -1
  106. package/dist/providers/jobs/fake-kubernetes-api.d.ts +3 -2
  107. package/dist/providers/jobs/fake-kubernetes-api.js +3 -0
  108. package/dist/providers/jobs/kubernetes-api.d.ts +3 -1
  109. package/dist/providers/jobs/kubernetes-client.d.ts +2 -1
  110. package/dist/providers/jobs/kubernetes-client.js +3 -0
  111. package/dist/providers/jobs/kubernetes-isolation.d.ts +7 -0
  112. package/dist/providers/jobs/kubernetes-isolation.js +8 -3
  113. package/dist/providers/jobs/kubernetes-preflight.d.ts +8 -0
  114. package/dist/providers/jobs/kubernetes-preflight.js +7 -0
  115. package/dist/providers/jobs/kubernetes-quota.d.ts +31 -0
  116. package/dist/providers/jobs/kubernetes-quota.js +110 -0
  117. package/dist/providers/jobs/kubernetes.d.ts +9 -0
  118. package/dist/providers/jobs/kubernetes.js +50 -0
  119. package/dist/providers/jobs/types.d.ts +7 -0
  120. package/dist/providers/review-host/ci.d.ts +7 -0
  121. package/dist/providers/review-host/ci.js +20 -0
  122. package/dist/providers/review-host/github-events.d.ts +7 -0
  123. package/dist/providers/review-host/github-events.js +31 -7
  124. package/dist/providers/review-host/github.d.ts +19 -2
  125. package/dist/providers/review-host/github.js +145 -2
  126. package/dist/providers/review-host/index.d.ts +8 -2
  127. package/dist/providers/review-host/index.js +14 -3
  128. package/dist/providers/review-host/local.d.ts +72 -0
  129. package/dist/providers/review-host/local.js +434 -0
  130. package/dist/providers/review-host/types.d.ts +65 -3
  131. package/dist/providers/review-host/types.js +5 -3
  132. package/dist/providers/vcs/git.d.ts +10 -20
  133. package/dist/providers/vcs/git.js +71 -127
  134. package/dist/providers/vcs/github-remote.d.ts +67 -0
  135. package/dist/providers/vcs/github-remote.js +201 -0
  136. package/dist/providers/vcs/github.d.ts +81 -0
  137. package/dist/providers/vcs/github.js +203 -6
  138. package/dist/providers/vcs/index.d.ts +16 -1
  139. package/dist/providers/vcs/index.js +43 -15
  140. package/dist/providers/vcs/local-remote.d.ts +44 -0
  141. package/dist/providers/vcs/local-remote.js +108 -0
  142. package/dist/providers/vcs/remote.d.ts +58 -0
  143. package/dist/providers/vcs/remote.js +1 -0
  144. package/dist/providers/vcs/routing.d.ts +33 -0
  145. package/dist/providers/vcs/routing.js +52 -0
  146. package/dist/providers/vcs/types.d.ts +12 -1
  147. package/dist/quickstart/coding-db.d.ts +11 -0
  148. package/dist/quickstart/coding-db.js +75 -0
  149. package/dist/quickstart/coding-doctor.d.ts +13 -0
  150. package/dist/quickstart/coding-doctor.js +88 -0
  151. package/dist/quickstart/coding-images.d.ts +15 -0
  152. package/dist/quickstart/coding-images.js +79 -0
  153. package/dist/quickstart/coding-seed.d.ts +63 -0
  154. package/dist/quickstart/coding-seed.js +85 -0
  155. package/dist/quickstart/coding.d.ts +66 -0
  156. package/dist/quickstart/coding.js +430 -0
  157. package/dist/quickstart/images.d.ts +19 -0
  158. package/dist/quickstart/images.js +63 -0
  159. package/dist/quickstart/index.d.ts +6 -0
  160. package/dist/quickstart/index.js +72 -40
  161. package/dist/quickstart/starter-services.d.ts +45 -0
  162. package/dist/quickstart/starter-services.js +186 -0
  163. package/dist/quickstart-images.json +1 -0
  164. package/dist/serve.js +14 -1
  165. package/dist/viewer/api-schema.d.ts +176 -0
  166. package/dist/viewer/api-schema.js +27 -2
  167. package/dist/viewer/graph.d.ts +7 -2
  168. package/dist/viewer/graph.js +25 -11
  169. package/dist/viewer/http.d.ts +7 -1
  170. package/dist/viewer/http.js +8 -3
  171. package/dist/viewer/infra.d.ts +2 -0
  172. package/dist/viewer/infra.js +23 -0
  173. package/dist/viewer/run-detail.d.ts +2 -1
  174. package/dist/viewer/run-detail.js +2 -2
  175. package/docs/agent-recipes.md +123 -2
  176. package/docs/code-review-agents.md +293 -38
  177. package/docs/coding-agent-setup.md +141 -2
  178. package/docs/coding-packages.md +21 -0
  179. package/docs/coding-services.md +3 -0
  180. package/docs/coding-worker-isolation.md +87 -24
  181. package/docs/getting-started-gke.md +24 -0
  182. package/docs/getting-started.md +82 -8
  183. package/docs/jira-agents.md +26 -5
  184. package/docs/security-deployment.md +9 -0
  185. package/docs/viewer-api.md +21 -2
  186. package/help/admin-viewer.md +11 -1
  187. package/help/agent-recipes.md +25 -1
  188. package/help/builder-agent.md +4 -0
  189. package/help/code-review-agents.md +82 -2
  190. package/help/coding-packages.md +12 -1
  191. package/help/coding-services.md +4 -0
  192. package/help/cost-attribution.md +3 -1
  193. package/help/creating-agents.md +3 -0
  194. package/help/deploy-gke.md +2 -1
  195. package/help/errors/coding-provider-not-configured.md +60 -0
  196. package/help/errors/coding-turn-limit.md +25 -0
  197. package/help/errors/continuation-closed.md +75 -0
  198. package/help/errors/local-branch-conflict.md +37 -0
  199. package/help/errors/local-path-invalid.md +30 -0
  200. package/help/errors/local-ref-invalid.md +36 -0
  201. package/help/errors/local-ref-not-found.md +41 -0
  202. package/help/errors/local-repo-not-allowed.md +46 -0
  203. package/help/errors/local-repo-not-found.md +39 -0
  204. package/help/errors/model-unavailable.md +7 -6
  205. package/help/errors/vcs-github-not-configured.md +34 -0
  206. package/help/getting-started.md +4 -0
  207. package/help/github.md +22 -5
  208. package/help/jira.md +9 -2
  209. package/help/local-repositories.md +182 -0
  210. package/help/related-pull-requests.md +89 -0
  211. package/help/review-fix-rounds.md +69 -0
  212. package/help/troubleshooting/budgets.md +11 -0
  213. package/help/troubleshooting/coding-workers.md +22 -0
  214. package/help/troubleshooting/repository-access.md +5 -0
  215. package/package.json +5 -3
  216. package/prisma/migrations/20261004100000_delegations_and_coding_turns/migration.sql +12 -0
  217. package/prisma/migrations/20261005000000_review_fix_rounds/migration.sql +5 -0
  218. package/prisma/migrations/20261006000000_parallel_delegations/migration.sql +6 -0
  219. package/prisma/migrations/20261007000000_ci_rereview/migration.sql +4 -0
  220. package/prisma/migrations/20261008000000_review_after_ci/migration.sql +33 -0
  221. package/prisma/migrations/20261009000001_coding_run_local_branch/migration.sql +7 -0
  222. package/prisma/migrations/20261009000002_local_review/migration.sql +50 -0
  223. package/prisma/schema.prisma +98 -1
@@ -39,7 +39,15 @@ function declaredServices(snapshot) {
39
39
  return snapshot.filter(isRecord).map((s) => ({ name: String(s.name), version: String(s.version) }));
40
40
  }
41
41
  const iso = (date) => (date ? date.toISOString() : null);
42
- function triggerFor(row) {
42
+ /** The tracker's page for an issue (and, with a comment id, that comment); null without a known site. */
43
+ export function issueUrl(sites, provider, issueKey, commentId) {
44
+ const site = sites[provider];
45
+ if (!site || provider !== "jira")
46
+ return null;
47
+ const page = `${site}/browse/${encodeURIComponent(issueKey)}`;
48
+ return commentId ? `${page}?focusedCommentId=${encodeURIComponent(commentId)}` : page;
49
+ }
50
+ function triggerFor(row, sites) {
43
51
  switch (row.trigger) {
44
52
  case "scheduled":
45
53
  return { kind: "scheduled", schedule: row.agent.schedule };
@@ -51,7 +59,12 @@ function triggerFor(row) {
51
59
  return { kind: "manual" };
52
60
  case "host_event":
53
61
  if (row.issueStatus) {
54
- return { kind: "issue", provider: row.issueStatus.provider, issueKey: row.issueStatus.issueKey };
62
+ return {
63
+ kind: "issue",
64
+ provider: row.issueStatus.provider,
65
+ issueKey: row.issueStatus.issueKey,
66
+ url: issueUrl(sites, row.issueStatus.provider, row.issueStatus.issueKey),
67
+ };
55
68
  }
56
69
  if (row.hostStatus) {
57
70
  return {
@@ -74,7 +87,7 @@ function triggerFor(row) {
74
87
  return { kind: "host_event" };
75
88
  }
76
89
  }
77
- function outcomesFor(row, pullRequests) {
90
+ function outcomesFor(row, pullRequests, sites) {
78
91
  const outcomes = pullRequests.map((pr) => ({
79
92
  kind: "pull_request",
80
93
  provider: pr.codeProvider,
@@ -112,6 +125,7 @@ function outcomesFor(row, pullRequests) {
112
125
  kind: "issue_comment",
113
126
  provider: row.issueStatus.provider,
114
127
  issueKey: row.issueStatus.issueKey,
128
+ url: issueUrl(sites, row.issueStatus.provider, row.issueStatus.issueKey, row.issueStatus.commentId),
115
129
  at: (row.issueStatus.completedAt ?? row.issueStatus.createdAt).toISOString(),
116
130
  });
117
131
  }
@@ -128,7 +142,7 @@ function outcomesFor(row, pullRequests) {
128
142
  return outcomes;
129
143
  }
130
144
  /** The one row -> GraphRun mapping (also used by run detail). `pullRequests` are the IssuePullRequest rows this run opened. */
131
- export function toGraphRun(row, pullRequests) {
145
+ export function toGraphRun(row, pullRequests, sites = {}) {
132
146
  return {
133
147
  id: row.id,
134
148
  parentRunId: row.parentRunId,
@@ -138,7 +152,7 @@ export function toGraphRun(row, pullRequests) {
138
152
  model: row.codingRun?.model ?? row.agent.model,
139
153
  codingProvider: row.codingRun?.provider ?? null,
140
154
  status: row.status,
141
- trigger: triggerFor(row),
155
+ trigger: triggerFor(row, sites),
142
156
  turns: row.turns,
143
157
  tokensIn: row.tokensIn,
144
158
  tokensOut: row.tokensOut,
@@ -147,7 +161,7 @@ export function toGraphRun(row, pullRequests) {
147
161
  startedAt: row.startedAt.toISOString(),
148
162
  finishedAt: iso(row.finishedAt),
149
163
  heartbeatAt: iso(row.heartbeatAt),
150
- outcomes: outcomesFor(row, pullRequests),
164
+ outcomes: outcomesFor(row, pullRequests, sites),
151
165
  declaredServices: declaredServices(row.codingRun?.services),
152
166
  services: row.serviceStatuses.map((s) => ({
153
167
  name: s.name,
@@ -160,7 +174,7 @@ export function toGraphRun(row, pullRequests) {
160
174
  })),
161
175
  };
162
176
  }
163
- async function mapRows(db, rows) {
177
+ async function mapRows(db, rows, sites) {
164
178
  if (rows.length === 0)
165
179
  return [];
166
180
  const pullRequests = await db.issuePullRequest.findMany({
@@ -170,14 +184,14 @@ async function mapRows(db, rows) {
170
184
  const byRun = new Map();
171
185
  for (const pr of pullRequests)
172
186
  byRun.set(pr.openedByRunId, [...(byRun.get(pr.openedByRunId) ?? []), pr]);
173
- return rows.map((row) => toGraphRun(row, byRun.get(row.id) ?? []));
187
+ return rows.map((row) => toGraphRun(row, byRun.get(row.id) ?? [], sites));
174
188
  }
175
189
  /** Loads the given runs as GraphRuns (shared with run detail); unknown ids are skipped. */
176
- export async function loadGraphRuns(db, runIds) {
190
+ export async function loadGraphRuns(db, runIds, sites = {}) {
177
191
  if (runIds.length === 0)
178
192
  return [];
179
193
  const rows = await db.run.findMany({ where: { id: { in: [...runIds] } }, include: runInclude });
180
- return mapRows(db, rows);
194
+ return mapRows(db, rows, sites);
181
195
  }
182
196
  export async function loadGraph(db, options) {
183
197
  const now = options.now ?? new Date();
@@ -207,7 +221,7 @@ export async function loadGraph(db, options) {
207
221
  }
208
222
  const dayStart = periodStart("day", now);
209
223
  const [runs, today, groups] = await Promise.all([
210
- mapRows(db, rows),
224
+ mapRows(db, rows, options.issueSites ?? {}),
211
225
  db.run.aggregate({ where: { startedAt: { gte: dayStart } }, _sum: { costUsd: true } }),
212
226
  db.budgetGroup.findMany({
213
227
  where: { dailyBudgetUsd: { not: null } },
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Viewer HTTP routes: GET /admin/api/graph, /admin/api/runs/:id and the
2
+ * Viewer HTTP routes: GET /admin/api/graph, /admin/api/runs/:id, /admin/api/infra and the
3
3
  * /admin/api/events SSE stream. Read-only and deployment-wide, so every route
4
4
  * authenticates and then requires the privileged `admin:view` scope (admin role
5
5
  * only) BEFORE touching the database or the event bus. McpErrors propagate to
@@ -8,6 +8,8 @@
8
8
  import type { IncomingMessage, ServerResponse } from "node:http";
9
9
  import type { PrismaClient } from "#prisma";
10
10
  import type { McpRequestContext } from "../mcp/context.js";
11
+ import type { InfraInfo } from "./api-schema.js";
12
+ import { type IssueSites } from "./graph.js";
11
13
  import type { ViewerEventBus } from "./event-bus.js";
12
14
  export declare const VIEWER_API_PREFIX = "/admin/api/";
13
15
  export declare const SSE_HEARTBEAT_MS = 15000;
@@ -18,6 +20,10 @@ export interface ViewerApiDeps {
18
20
  bus: ViewerEventBus;
19
21
  authenticate: (authorization: string | undefined) => Promise<McpRequestContext>;
20
22
  canonicalUri: string;
23
+ /** How this deployment runs coding jobs (built once at startup; GET /admin/api/infra). */
24
+ infra: InfraInfo;
25
+ /** Issue-tracker site origins by provider, for issue and comment links (empty when none is configured). */
26
+ issueSites?: IssueSites;
21
27
  heartbeatMs?: number;
22
28
  maxBufferedBytes?: number;
23
29
  }
@@ -77,7 +77,8 @@ export function createViewerApi(deps) {
77
77
  const runMatch = /^\/admin\/api\/runs\/([^/]*)$/.exec(path);
78
78
  const isGraph = path === "/admin/api/graph";
79
79
  const isEvents = path === "/admin/api/events";
80
- if (!isGraph && !isEvents && !runMatch)
80
+ const isInfra = path === "/admin/api/infra";
81
+ if (!isGraph && !isEvents && !isInfra && !runMatch)
81
82
  return false;
82
83
  const ctx = await deps.authenticate(req.headers.authorization);
83
84
  requireScope(ctx, deps.canonicalUri, "admin:view");
@@ -85,6 +86,10 @@ export function createViewerApi(deps) {
85
86
  sendJson(res, 405, { error: "method_not_allowed" }, { allow: "GET" });
86
87
  return true;
87
88
  }
89
+ if (isInfra) {
90
+ sendJson(res, 200, deps.infra);
91
+ return true;
92
+ }
88
93
  if (isGraph) {
89
94
  const limitParam = url.searchParams.get("limit");
90
95
  let limit = DEFAULT_GRAPH_LIMIT;
@@ -104,7 +109,7 @@ export function createViewerApi(deps) {
104
109
  sendJson(res, 400, { error: "invalid_since" });
105
110
  return true;
106
111
  }
107
- sendJson(res, 200, await loadGraph(deps.db, { since, limit, now }));
112
+ sendJson(res, 200, await loadGraph(deps.db, { since, limit, now, issueSites: deps.issueSites }));
108
113
  return true;
109
114
  }
110
115
  if (runMatch) {
@@ -115,7 +120,7 @@ export function createViewerApi(deps) {
115
120
  catch {
116
121
  /* malformed escape: fails the id check below */
117
122
  }
118
- const detail = RUN_ID.test(id) ? await loadRunDetail(deps.db, id) : null;
123
+ const detail = RUN_ID.test(id) ? await loadRunDetail(deps.db, id, deps.issueSites) : null;
119
124
  if (!detail)
120
125
  sendJson(res, 404, { error: "not_found" });
121
126
  else
@@ -0,0 +1,2 @@
1
+ import type { InfraInfo } from "./api-schema.js";
2
+ export declare function buildInfraInfo(env?: NodeJS.ProcessEnv): InfraInfo;
@@ -0,0 +1,23 @@
1
+ /** The read model behind GET /admin/api/infra (docs/viewer-api.md): built once at startup, no cluster reads. */
2
+ import { loadKubernetesJobConfig, loadProviderConfig } from "../config/providers.js";
3
+ import { RUN_COMPONENT_LABEL, RUN_MANAGED_BY_LABEL, RUN_SHA_CHARS, RUN_SHA_LABEL, } from "../providers/jobs/kubernetes-isolation.js";
4
+ export function buildInfraInfo(env = process.env) {
5
+ const raw = loadProviderConfig(env).jobs;
6
+ const launcher = raw === "docker" || raw === "kubernetes" ? raw : "local";
7
+ if (launcher !== "kubernetes")
8
+ return { launcher, kubernetes: null };
9
+ const k8s = loadKubernetesJobConfig(env);
10
+ return {
11
+ launcher,
12
+ kubernetes: {
13
+ namespace: k8s.namespace,
14
+ platform: k8s.platform,
15
+ runtimeClass: k8s.runtimeClassName ?? null,
16
+ proxyService: k8s.proxyService,
17
+ runLabel: RUN_SHA_LABEL,
18
+ runLabelHashChars: RUN_SHA_CHARS,
19
+ componentLabel: { ...RUN_COMPONENT_LABEL },
20
+ managedByLabel: { ...RUN_MANAGED_BY_LABEL },
21
+ },
22
+ };
23
+ }
@@ -1,4 +1,5 @@
1
1
  /** The run detail read model behind GET /admin/api/runs/:id (docs/viewer-api.md). */
2
2
  import type { PrismaClient } from "#prisma";
3
3
  import type { RunDetail } from "./api-schema.js";
4
- export declare function loadRunDetail(db: PrismaClient, runId: string): Promise<RunDetail | null>;
4
+ import { type IssueSites } from "./graph.js";
5
+ export declare function loadRunDetail(db: PrismaClient, runId: string, issueSites?: IssueSites): Promise<RunDetail | null>;
@@ -11,8 +11,8 @@ function serviceDetails(snapshot) {
11
11
  envNames: isRecord(s.testEnv) ? Object.keys(s.testEnv).sort() : [],
12
12
  }));
13
13
  }
14
- export async function loadRunDetail(db, runId) {
15
- const [graphRun] = await loadGraphRuns(db, [runId]);
14
+ export async function loadRunDetail(db, runId, issueSites = {}) {
15
+ const [graphRun] = await loadGraphRuns(db, [runId], issueSites);
16
16
  if (!graphRun)
17
17
  return null;
18
18
  const [row, children] = await Promise.all([
@@ -48,7 +48,7 @@ These recipes go beyond the quickstart. Check each item.
48
48
  [GKE](getting-started-gke.md). Scheduled and manually triggered runs need no
49
49
  webhook.
50
50
  5. **App events.** Tick the events each trigger needs in the App's settings:
51
- Pull request and Check run for reviews; Issue comment, Pull request review
51
+ Pull request, Check run, and Check suite for reviews; Issue comment, Pull request review
52
52
  comment, and Issues for mentions; and **Push** for the merge watcher. Each is
53
53
  its own checkbox
54
54
  ([registering the App](code-review-agents.md#registering-the-github-app)).
@@ -358,6 +358,125 @@ pip use inside the same run, and `packageAllowlist` has no entry for other
358
358
  ecosystems. If services are allowed, the image must be built on driver v11 or
359
359
  later.
360
360
 
361
+ ### Fanning out to several builders
362
+
363
+ A router delegates once per run by default: it routes a request to exactly one
364
+ sub-agent and never retries into a second. To have one native agent split a
365
+ request across several repositories, attach one builder per repository and set
366
+ the lead's `maxDelegationsPerRun` (1 to 20) with `create_agent` or
367
+ `update_agent`:
368
+
369
+ ```json
370
+ { "id": "<lead agent id>", "maxDelegationsPerRun": 6 }
371
+ ```
372
+
373
+ - Each `delegate_to_<name>` call still goes to a different sub-agent; a second
374
+ call to the same one in the same run is refused with `already_dispatched`.
375
+ A delegation waiting for budget (see below) also holds its place against
376
+ `maxDelegationsPerRun`: a call beyond the limit is refused with
377
+ `already_dispatched` and a message naming both counts, e.g. "This run
378
+ reached its limit of 6 delegations: 4 made and 2 waiting for budget."
379
+ - By default the calls run one after another, in the order the lead makes
380
+ them. To run them at the same time, also set `parallelDelegations`:
381
+
382
+ ```json
383
+ { "id": "<lead agent id>", "maxDelegationsPerRun": 6, "parallelDelegations": true }
384
+ ```
385
+
386
+ Only the **consecutive** `delegate_to_*` calls of one model turn start
387
+ together: `[delegate_a, delegate_b]` starts both at once, but
388
+ `[delegate_a, jira_comment, delegate_b]` runs `delegate_a`, then the
389
+ `jira_comment` call, then `delegate_b` one at a time — nothing is
390
+ reordered, and a non-delegation call between two delegations splits them
391
+ into separate steps. The lead gets every call's result, in the same order
392
+ the calls were made, before its next turn. Tell the lead to make all of
393
+ its independent delegations consecutively, in one turn, with no other
394
+ tool call between them. A prompt that asks for delegations "in dependency
395
+ order", one per turn, still runs them one after another. Use
396
+ `parallelDelegations` only when every builder's task already carries the
397
+ whole cross-repository contract, so no builder needs another's output.
398
+
399
+ - With `parallelDelegations`, builders compete for coding slots: runs beyond
400
+ `CODING_MAX_CONCURRENT` (or beyond a Kubernetes quota set with
401
+ `KUBERNETES_RESOURCE_QUOTA`) wait in the queue and start as others finish.
402
+ A builder that waits longer than `CODING_QUEUE_TIMEOUT_SEC` fails with
403
+ `coding_queue_timeout`, and the lead sees that in its result.
404
+ - Each coding sub-agent's result tells the lead its `status`, its summary
405
+ (`finalText`), cost and tokens, and, when it opened or pushed to a pull
406
+ request, `pullRequest` (`outcome` `opened` or `updated`, `repository`,
407
+ `number`, `url`) taken from Wardby's own record of the run. A lead prompt can
408
+ ask the agent to report those links.
409
+ - The whole run tree shares one budget: give the lead a `budgetUsd` that
410
+ covers every builder it may start, plus its own planning. Builders started
411
+ together each reserve their own `budgetUsd` up front, so the ones admitted
412
+ last get what the earlier ones leave. A builder admitted after the tree's
413
+ budget is spent waits for a still-running sibling to finish and free its
414
+ reservation, then retries — up to its own normal wait bound, so the total
415
+ wait can run to roughly twice that bound before it gives up. With no
416
+ sibling still running, it is refused immediately with `run_tree_exhausted`.
417
+ The check behind that wait is an estimate, so a retry can still end in the
418
+ same refusal. Size the lead for the sum of its builders' budgets.
419
+ - Each builder works only in its own repository and can't see the others, so
420
+ the lead's task to each one must carry everything that crosses a repository
421
+ boundary (routes, field names and types, error codes), word for word the same.
422
+ - Wardby adds a **Related pull requests** section to each pull request the
423
+ builders open, listing the others with links, states (open, draft, merged,
424
+ closed) and the originating issue. With a Jira issue, the list covers every
425
+ pull request recorded for that issue in any run. Open ones are numbered as a
426
+ **Suggested merge order** — the order the lead delegated in, not a
427
+ guarantee; merged and closed ones are listed separately as context. A pull
428
+ request lists the ones opened before it as soon as it is opened; when the
429
+ lead run finishes, and whenever a later run pushes to one of them, every
430
+ open one is rewritten with the full list. Text you add inside the section is
431
+ replaced; the rest of the description is not touched. Only pull requests the
432
+ App opened and that are still open are edited.
433
+ - The section, and the follow-up hints below, list every pull request in the
434
+ set by repository name and number. A request that spans repositories of
435
+ different visibility can therefore show a private repository's name in a
436
+ public repository's pull request: if you mix public and private
437
+ repositories, keep that work in separate requests (separate Jira issues, or
438
+ separate lead agents).
439
+
440
+ #### Follow-ups across the set
441
+
442
+ A human-triggered follow-up task (an `@<app-slug>` mention, or a Jira issue
443
+ event — never an automatic review fix round) lists every open sibling pull
444
+ request in the set, each as `owner/name#n (url): … pass continuePriorRun set
445
+ to exactly "<run id>"`. Recommend this line in your lead agent's own prompt
446
+ too:
447
+
448
+ Continue the pull request you were asked about, and any listed open
449
+ sibling the change requires; never open a new pull request in a
450
+ repository that already has an open sibling for this request.
451
+
452
+ The usual delegation rails still apply on top of this: `maxDelegationsPerRun`
453
+ caps calls per run; a second `delegate_to_<name>` call to the same sub-agent
454
+ in one run is refused with `already_dispatched`; dispatch re-checks every
455
+ `continuePriorRun` id (refusing with `continuation_refused` when this
456
+ deployment can't continue the run it names — for example it belongs to a
457
+ different owner's agent); every push to a sibling pull request still gets its
458
+ own review; and fix rounds stay capped and run one at a time per pull
459
+ request. A continuation whose pull request has since been merged or closed
460
+ fails with category `continuation_closed` and pushes nothing — see
461
+ [Continuation's pull request is no longer open](../help/errors/continuation-closed.md).
462
+
463
+ #### Limits
464
+
465
+ - Cancelling the lead run does not stop a native sub-agent that is already
466
+ running; it keeps running to completion. A coding sub-agent is stopped.
467
+ - If Wardby restarts mid-batch, the batch's delegation calls replay and each
468
+ one already dispatched reports `already_dispatched`, so the lead does not
469
+ get those sub-agents' results. A native sub-agent that was running stops
470
+ with the restart and is marked `lost`, as is a coding sub-agent whose
471
+ replica crashed. Check the run tree with `get_run` and re-trigger the lead.
472
+ - The lead's own remaining (unspent) budget is not reserved against its
473
+ children's dispatch, so give the lead's `budgetUsd` headroom beyond the
474
+ plain sum of its builders' budgets, not just that sum exactly.
475
+ - With nested fan-out — a native sub-agent that itself has
476
+ `parallelDelegations` and delegates further — two branches dispatching at
477
+ the same time can briefly both see the same remaining run-tree budget and
478
+ both be admitted against it.
479
+
361
480
  ### Together with Recipe A
362
481
 
363
482
  Coding runs automatically receive the repository's knowledge index and base
@@ -371,7 +490,9 @@ bundle current.
371
490
  A mention from someone with write access gets a "Working on it" status comment
372
491
  from the App. The router either asks a question or delegates, the builder
373
492
  pushes a unique branch and opens at most one draft pull request, and the App
374
- edits the status comment with the pull request link or the failure. A person
493
+ edits the status comment with the pull request link or the failure. The
494
+ link carries ⚠️ instead of ✅ when any of the builder's own checks failed in
495
+ the run; the pull request description says which. A person
375
496
  reviews and merges; Wardby never merges for you. A later mention on that pull
376
497
  request continues the same branch.
377
498