@wardby/cli 0.4.1 → 0.5.1

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 (221) 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/generated/prisma/browser.d.ts +23 -0
  67. package/dist/generated/prisma/client.d.ts +23 -0
  68. package/dist/generated/prisma/commonInputTypes.d.ts +22 -0
  69. package/dist/generated/prisma/internal/class.d.ts +33 -0
  70. package/dist/generated/prisma/internal/class.js +4 -4
  71. package/dist/generated/prisma/internal/prismaNamespace.d.ts +274 -1
  72. package/dist/generated/prisma/internal/prismaNamespace.js +47 -2
  73. package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +48 -0
  74. package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +47 -2
  75. package/dist/generated/prisma/models/Agent.d.ts +422 -1
  76. package/dist/generated/prisma/models/AgentRepository.d.ts +120 -2
  77. package/dist/generated/prisma/models/CodingAgentProfile.d.ts +42 -1
  78. package/dist/generated/prisma/models/CodingRun.d.ts +205 -1
  79. package/dist/generated/prisma/models/DeferredReview.d.ts +1336 -0
  80. package/dist/generated/prisma/models/DeferredReview.js +1 -0
  81. package/dist/generated/prisma/models/LocalPullRequest.d.ts +1384 -0
  82. package/dist/generated/prisma/models/LocalPullRequest.js +1 -0
  83. package/dist/generated/prisma/models/LocalReview.d.ts +1315 -0
  84. package/dist/generated/prisma/models/LocalReview.js +1 -0
  85. package/dist/generated/prisma/models/Run.d.ts +223 -0
  86. package/dist/generated/prisma/models/RunHostCheck.d.ts +148 -1
  87. package/dist/generated/prisma/models.d.ts +3 -0
  88. package/dist/help-index.json +486 -37
  89. package/dist/import/neutral-schema.d.ts +2 -2
  90. package/dist/mcp/auth/access.d.ts +3 -1
  91. package/dist/mcp/auth/host-account-cli.js +2 -2
  92. package/dist/mcp/auth/repo-authorization.d.ts +6 -7
  93. package/dist/mcp/auth/repo-authorization.js +21 -0
  94. package/dist/mcp/index.js +4 -1
  95. package/dist/mcp/tools/agents.js +46 -4
  96. package/dist/mcp/tools/host-accounts.js +2 -2
  97. package/dist/mcp/tools/repositories.js +71 -12
  98. package/dist/mcp/tools/runs.js +23 -0
  99. package/dist/mcp/tools/trigger.js +153 -24
  100. package/dist/providers/engine/types.d.ts +11 -0
  101. package/dist/providers/executor/composition.js +12 -7
  102. package/dist/providers/executor/container.d.ts +40 -3
  103. package/dist/providers/executor/container.js +140 -9
  104. package/dist/providers/executor/types.d.ts +1 -1
  105. package/dist/providers/jobs/fake-kubernetes-api.d.ts +3 -2
  106. package/dist/providers/jobs/fake-kubernetes-api.js +3 -0
  107. package/dist/providers/jobs/kubernetes-api.d.ts +3 -1
  108. package/dist/providers/jobs/kubernetes-client.d.ts +2 -1
  109. package/dist/providers/jobs/kubernetes-client.js +3 -0
  110. package/dist/providers/jobs/kubernetes-isolation.d.ts +7 -0
  111. package/dist/providers/jobs/kubernetes-isolation.js +8 -3
  112. package/dist/providers/jobs/kubernetes-preflight.d.ts +8 -0
  113. package/dist/providers/jobs/kubernetes-preflight.js +7 -0
  114. package/dist/providers/jobs/kubernetes-quota.d.ts +31 -0
  115. package/dist/providers/jobs/kubernetes-quota.js +110 -0
  116. package/dist/providers/jobs/kubernetes.d.ts +9 -0
  117. package/dist/providers/jobs/kubernetes.js +50 -0
  118. package/dist/providers/jobs/types.d.ts +7 -0
  119. package/dist/providers/review-host/ci.d.ts +7 -0
  120. package/dist/providers/review-host/ci.js +20 -0
  121. package/dist/providers/review-host/github-events.d.ts +7 -0
  122. package/dist/providers/review-host/github-events.js +31 -7
  123. package/dist/providers/review-host/github.d.ts +19 -2
  124. package/dist/providers/review-host/github.js +145 -2
  125. package/dist/providers/review-host/index.d.ts +8 -2
  126. package/dist/providers/review-host/index.js +14 -3
  127. package/dist/providers/review-host/local.d.ts +72 -0
  128. package/dist/providers/review-host/local.js +434 -0
  129. package/dist/providers/review-host/types.d.ts +65 -3
  130. package/dist/providers/review-host/types.js +5 -3
  131. package/dist/providers/vcs/git.d.ts +10 -20
  132. package/dist/providers/vcs/git.js +71 -127
  133. package/dist/providers/vcs/github-remote.d.ts +67 -0
  134. package/dist/providers/vcs/github-remote.js +201 -0
  135. package/dist/providers/vcs/github.d.ts +81 -0
  136. package/dist/providers/vcs/github.js +203 -6
  137. package/dist/providers/vcs/index.d.ts +16 -1
  138. package/dist/providers/vcs/index.js +43 -15
  139. package/dist/providers/vcs/local-remote.d.ts +44 -0
  140. package/dist/providers/vcs/local-remote.js +108 -0
  141. package/dist/providers/vcs/remote.d.ts +58 -0
  142. package/dist/providers/vcs/remote.js +1 -0
  143. package/dist/providers/vcs/routing.d.ts +33 -0
  144. package/dist/providers/vcs/routing.js +52 -0
  145. package/dist/providers/vcs/types.d.ts +12 -1
  146. package/dist/quickstart/coding-db.d.ts +11 -0
  147. package/dist/quickstart/coding-db.js +75 -0
  148. package/dist/quickstart/coding-doctor.d.ts +13 -0
  149. package/dist/quickstart/coding-doctor.js +88 -0
  150. package/dist/quickstart/coding-images.d.ts +15 -0
  151. package/dist/quickstart/coding-images.js +79 -0
  152. package/dist/quickstart/coding-seed.d.ts +63 -0
  153. package/dist/quickstart/coding-seed.js +85 -0
  154. package/dist/quickstart/coding.d.ts +66 -0
  155. package/dist/quickstart/coding.js +430 -0
  156. package/dist/quickstart/images.d.ts +19 -0
  157. package/dist/quickstart/images.js +63 -0
  158. package/dist/quickstart/index.js +61 -12
  159. package/dist/quickstart/starter-services.d.ts +45 -0
  160. package/dist/quickstart/starter-services.js +186 -0
  161. package/dist/quickstart-images.json +1 -0
  162. package/dist/serve.js +14 -1
  163. package/dist/viewer/api-schema.d.ts +176 -0
  164. package/dist/viewer/api-schema.js +27 -2
  165. package/dist/viewer/graph.d.ts +7 -2
  166. package/dist/viewer/graph.js +25 -11
  167. package/dist/viewer/http.d.ts +7 -1
  168. package/dist/viewer/http.js +8 -3
  169. package/dist/viewer/infra.d.ts +2 -0
  170. package/dist/viewer/infra.js +23 -0
  171. package/dist/viewer/run-detail.d.ts +2 -1
  172. package/dist/viewer/run-detail.js +2 -2
  173. package/docs/agent-recipes.md +127 -5
  174. package/docs/code-review-agents.md +293 -38
  175. package/docs/coding-agent-setup.md +141 -2
  176. package/docs/coding-packages.md +21 -0
  177. package/docs/coding-services.md +3 -0
  178. package/docs/coding-worker-isolation.md +56 -20
  179. package/docs/getting-started-gke.md +24 -0
  180. package/docs/getting-started.md +196 -10
  181. package/docs/jira-agents.md +26 -5
  182. package/docs/security-deployment.md +9 -0
  183. package/docs/viewer-api.md +21 -2
  184. package/help/admin-viewer.md +11 -1
  185. package/help/agent-recipes.md +30 -4
  186. package/help/builder-agent.md +4 -0
  187. package/help/code-review-agents.md +82 -2
  188. package/help/coding-packages.md +12 -1
  189. package/help/coding-services.md +4 -0
  190. package/help/cost-attribution.md +3 -1
  191. package/help/creating-agents.md +3 -0
  192. package/help/deploy-gke.md +2 -1
  193. package/help/errors/coding-provider-not-configured.md +60 -0
  194. package/help/errors/coding-turn-limit.md +25 -0
  195. package/help/errors/continuation-closed.md +75 -0
  196. package/help/errors/local-branch-conflict.md +37 -0
  197. package/help/errors/local-path-invalid.md +30 -0
  198. package/help/errors/local-ref-invalid.md +36 -0
  199. package/help/errors/local-ref-not-found.md +41 -0
  200. package/help/errors/local-repo-not-allowed.md +46 -0
  201. package/help/errors/local-repo-not-found.md +39 -0
  202. package/help/errors/model-unavailable.md +7 -6
  203. package/help/errors/vcs-github-not-configured.md +34 -0
  204. package/help/getting-started.md +28 -0
  205. package/help/github.md +22 -5
  206. package/help/jira.md +9 -2
  207. package/help/local-repositories.md +182 -0
  208. package/help/related-pull-requests.md +89 -0
  209. package/help/review-fix-rounds.md +69 -0
  210. package/help/troubleshooting/budgets.md +11 -0
  211. package/help/troubleshooting/coding-workers.md +22 -0
  212. package/help/troubleshooting/repository-access.md +5 -0
  213. package/package.json +3 -2
  214. package/prisma/migrations/20261004100000_delegations_and_coding_turns/migration.sql +12 -0
  215. package/prisma/migrations/20261005000000_review_fix_rounds/migration.sql +5 -0
  216. package/prisma/migrations/20261006000000_parallel_delegations/migration.sql +6 -0
  217. package/prisma/migrations/20261007000000_ci_rereview/migration.sql +4 -0
  218. package/prisma/migrations/20261008000000_review_after_ci/migration.sql +33 -0
  219. package/prisma/migrations/20261009000001_coding_run_local_branch/migration.sql +7 -0
  220. package/prisma/migrations/20261009000002_local_review/migration.sql +50 -0
  221. 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([
@@ -24,9 +24,10 @@ These recipes go beyond the quickstart. Check each item.
24
24
  1. **Version.** The recipes require Wardby 0.4.0 or later. The knowledge note in
25
25
  coding runs, `wardby knowledge check`, and the `push` trigger are not in
26
26
  earlier releases.
27
- 2. **Coding-agent setup.** The quickstart runs native agents only: it does not
28
- install a GitHub App or build worker images. Both recipes use coding agents,
29
- so complete [Coding-agent setup](coding-agent-setup.md) first: a GitHub App
27
+ 2. **Coding-agent setup.** The quickstart's optional coding step sets up coding
28
+ agents on a local git repository only; it does not install a GitHub App.
29
+ Both recipes use coding agents triggered by GitHub events, so complete
30
+ [Coding-agent setup](coding-agent-setup.md) first: a GitHub App
30
31
  installed on the repository, a worker image, the coding proxy, and a Docker or
31
32
  Kubernetes job launcher. Run `wardby coding preflight` to check it. For a
32
33
  hosted deployment, [Getting started on GKE](getting-started-gke.md) covers
@@ -48,7 +49,7 @@ These recipes go beyond the quickstart. Check each item.
48
49
  [GKE](getting-started-gke.md). Scheduled and manually triggered runs need no
49
50
  webhook.
50
51
  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
52
+ Pull request, Check run, and Check suite for reviews; Issue comment, Pull request review
52
53
  comment, and Issues for mentions; and **Push** for the merge watcher. Each is
53
54
  its own checkbox
54
55
  ([registering the App](code-review-agents.md#registering-the-github-app)).
@@ -358,6 +359,125 @@ pip use inside the same run, and `packageAllowlist` has no entry for other
358
359
  ecosystems. If services are allowed, the image must be built on driver v11 or
359
360
  later.
360
361
 
362
+ ### Fanning out to several builders
363
+
364
+ A router delegates once per run by default: it routes a request to exactly one
365
+ sub-agent and never retries into a second. To have one native agent split a
366
+ request across several repositories, attach one builder per repository and set
367
+ the lead's `maxDelegationsPerRun` (1 to 20) with `create_agent` or
368
+ `update_agent`:
369
+
370
+ ```json
371
+ { "id": "<lead agent id>", "maxDelegationsPerRun": 6 }
372
+ ```
373
+
374
+ - Each `delegate_to_<name>` call still goes to a different sub-agent; a second
375
+ call to the same one in the same run is refused with `already_dispatched`.
376
+ A delegation waiting for budget (see below) also holds its place against
377
+ `maxDelegationsPerRun`: a call beyond the limit is refused with
378
+ `already_dispatched` and a message naming both counts, e.g. "This run
379
+ reached its limit of 6 delegations: 4 made and 2 waiting for budget."
380
+ - By default the calls run one after another, in the order the lead makes
381
+ them. To run them at the same time, also set `parallelDelegations`:
382
+
383
+ ```json
384
+ { "id": "<lead agent id>", "maxDelegationsPerRun": 6, "parallelDelegations": true }
385
+ ```
386
+
387
+ Only the **consecutive** `delegate_to_*` calls of one model turn start
388
+ together: `[delegate_a, delegate_b]` starts both at once, but
389
+ `[delegate_a, jira_comment, delegate_b]` runs `delegate_a`, then the
390
+ `jira_comment` call, then `delegate_b` one at a time — nothing is
391
+ reordered, and a non-delegation call between two delegations splits them
392
+ into separate steps. The lead gets every call's result, in the same order
393
+ the calls were made, before its next turn. Tell the lead to make all of
394
+ its independent delegations consecutively, in one turn, with no other
395
+ tool call between them. A prompt that asks for delegations "in dependency
396
+ order", one per turn, still runs them one after another. Use
397
+ `parallelDelegations` only when every builder's task already carries the
398
+ whole cross-repository contract, so no builder needs another's output.
399
+
400
+ - With `parallelDelegations`, builders compete for coding slots: runs beyond
401
+ `CODING_MAX_CONCURRENT` (or beyond a Kubernetes quota set with
402
+ `KUBERNETES_RESOURCE_QUOTA`) wait in the queue and start as others finish.
403
+ A builder that waits longer than `CODING_QUEUE_TIMEOUT_SEC` fails with
404
+ `coding_queue_timeout`, and the lead sees that in its result.
405
+ - Each coding sub-agent's result tells the lead its `status`, its summary
406
+ (`finalText`), cost and tokens, and, when it opened or pushed to a pull
407
+ request, `pullRequest` (`outcome` `opened` or `updated`, `repository`,
408
+ `number`, `url`) taken from Wardby's own record of the run. A lead prompt can
409
+ ask the agent to report those links.
410
+ - The whole run tree shares one budget: give the lead a `budgetUsd` that
411
+ covers every builder it may start, plus its own planning. Builders started
412
+ together each reserve their own `budgetUsd` up front, so the ones admitted
413
+ last get what the earlier ones leave. A builder admitted after the tree's
414
+ budget is spent waits for a still-running sibling to finish and free its
415
+ reservation, then retries — up to its own normal wait bound, so the total
416
+ wait can run to roughly twice that bound before it gives up. With no
417
+ sibling still running, it is refused immediately with `run_tree_exhausted`.
418
+ The check behind that wait is an estimate, so a retry can still end in the
419
+ same refusal. Size the lead for the sum of its builders' budgets.
420
+ - Each builder works only in its own repository and can't see the others, so
421
+ the lead's task to each one must carry everything that crosses a repository
422
+ boundary (routes, field names and types, error codes), word for word the same.
423
+ - Wardby adds a **Related pull requests** section to each pull request the
424
+ builders open, listing the others with links, states (open, draft, merged,
425
+ closed) and the originating issue. With a Jira issue, the list covers every
426
+ pull request recorded for that issue in any run. Open ones are numbered as a
427
+ **Suggested merge order** — the order the lead delegated in, not a
428
+ guarantee; merged and closed ones are listed separately as context. A pull
429
+ request lists the ones opened before it as soon as it is opened; when the
430
+ lead run finishes, and whenever a later run pushes to one of them, every
431
+ open one is rewritten with the full list. Text you add inside the section is
432
+ replaced; the rest of the description is not touched. Only pull requests the
433
+ App opened and that are still open are edited.
434
+ - The section, and the follow-up hints below, list every pull request in the
435
+ set by repository name and number. A request that spans repositories of
436
+ different visibility can therefore show a private repository's name in a
437
+ public repository's pull request: if you mix public and private
438
+ repositories, keep that work in separate requests (separate Jira issues, or
439
+ separate lead agents).
440
+
441
+ #### Follow-ups across the set
442
+
443
+ A human-triggered follow-up task (an `@<app-slug>` mention, or a Jira issue
444
+ event — never an automatic review fix round) lists every open sibling pull
445
+ request in the set, each as `owner/name#n (url): … pass continuePriorRun set
446
+ to exactly "<run id>"`. Recommend this line in your lead agent's own prompt
447
+ too:
448
+
449
+ Continue the pull request you were asked about, and any listed open
450
+ sibling the change requires; never open a new pull request in a
451
+ repository that already has an open sibling for this request.
452
+
453
+ The usual delegation rails still apply on top of this: `maxDelegationsPerRun`
454
+ caps calls per run; a second `delegate_to_<name>` call to the same sub-agent
455
+ in one run is refused with `already_dispatched`; dispatch re-checks every
456
+ `continuePriorRun` id (refusing with `continuation_refused` when this
457
+ deployment can't continue the run it names — for example it belongs to a
458
+ different owner's agent); every push to a sibling pull request still gets its
459
+ own review; and fix rounds stay capped and run one at a time per pull
460
+ request. A continuation whose pull request has since been merged or closed
461
+ fails with category `continuation_closed` and pushes nothing — see
462
+ [Continuation's pull request is no longer open](../help/errors/continuation-closed.md).
463
+
464
+ #### Limits
465
+
466
+ - Cancelling the lead run does not stop a native sub-agent that is already
467
+ running; it keeps running to completion. A coding sub-agent is stopped.
468
+ - If Wardby restarts mid-batch, the batch's delegation calls replay and each
469
+ one already dispatched reports `already_dispatched`, so the lead does not
470
+ get those sub-agents' results. A native sub-agent that was running stops
471
+ with the restart and is marked `lost`, as is a coding sub-agent whose
472
+ replica crashed. Check the run tree with `get_run` and re-trigger the lead.
473
+ - The lead's own remaining (unspent) budget is not reserved against its
474
+ children's dispatch, so give the lead's `budgetUsd` headroom beyond the
475
+ plain sum of its builders' budgets, not just that sum exactly.
476
+ - With nested fan-out — a native sub-agent that itself has
477
+ `parallelDelegations` and delegates further — two branches dispatching at
478
+ the same time can briefly both see the same remaining run-tree budget and
479
+ both be admitted against it.
480
+
361
481
  ### Together with Recipe A
362
482
 
363
483
  Coding runs automatically receive the repository's knowledge index and base
@@ -371,7 +491,9 @@ bundle current.
371
491
  A mention from someone with write access gets a "Working on it" status comment
372
492
  from the App. The router either asks a question or delegates, the builder
373
493
  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
494
+ edits the status comment with the pull request link or the failure. The
495
+ link carries ⚠️ instead of ✅ when any of the builder's own checks failed in
496
+ the run; the pull request description says which. A person
375
497
  reviews and merges; Wardby never merges for you. A later mention on that pull
376
498
  request continues the same branch.
377
499