@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.
- package/.env.example +8 -1
- package/README.md +3 -1
- package/deploy/local/docker-compose.quickstart-coding.yml +27 -0
- package/dist/claude-coding-worker/driver.d.ts +2 -0
- package/dist/claude-coding-worker/driver.js +19 -3
- package/dist/claude-coding-worker/sdk.js +1 -1
- package/dist/cli.js +41 -22
- package/dist/coding/continuation-wording.d.ts +10 -0
- package/dist/coding/continuation-wording.js +13 -0
- package/dist/coding/docker-preflight.d.ts +12 -0
- package/dist/coding/docker-preflight.js +40 -0
- package/dist/coding/local-git.d.ts +22 -0
- package/dist/coding/local-git.js +102 -0
- package/dist/coding/local-repo-status.d.ts +7 -0
- package/dist/coding/local-repo-status.js +56 -0
- package/dist/coding/local-repo.d.ts +17 -0
- package/dist/coding/local-repo.js +65 -0
- package/dist/coding/observability.d.ts +1 -1
- package/dist/coding/observability.js +1 -0
- package/dist/coding/profile.d.ts +6 -0
- package/dist/coding/profile.js +13 -4
- package/dist/coding/protocol.d.ts +32 -7
- package/dist/coding/protocol.js +66 -4
- package/dist/coding-worker/artifact.d.ts +1 -0
- package/dist/coding-worker/errors.js +2 -0
- package/dist/config/providers.d.ts +13 -0
- package/dist/config/providers.js +21 -9
- package/dist/core/attribution.d.ts +13 -0
- package/dist/core/attribution.js +30 -0
- package/dist/core/budget-groups.d.ts +3 -1
- package/dist/core/budget-groups.js +53 -5
- package/dist/core/ci-context.d.ts +8 -0
- package/dist/core/ci-context.js +59 -0
- package/dist/core/delegation-siblings.d.ts +22 -0
- package/dist/core/delegation-siblings.js +40 -0
- package/dist/core/dispatch.d.ts +28 -4
- package/dist/core/dispatch.js +57 -15
- package/dist/core/engine-native.d.ts +16 -0
- package/dist/core/engine-native.js +49 -2
- package/dist/core/host-events.d.ts +25 -2
- package/dist/core/host-events.js +361 -19
- package/dist/core/host-status.d.ts +8 -3
- package/dist/core/host-status.js +38 -7
- package/dist/core/issue-events.d.ts +2 -6
- package/dist/core/issue-events.js +13 -19
- package/dist/core/pull-request-state-sync.d.ts +17 -0
- package/dist/core/pull-request-state-sync.js +53 -0
- package/dist/core/reconciler.d.ts +10 -1
- package/dist/core/reconciler.js +15 -2
- package/dist/core/related-pull-requests.d.ts +95 -0
- package/dist/core/related-pull-requests.js +338 -0
- package/dist/core/repo-access.d.ts +5 -1
- package/dist/core/repo-access.js +17 -1
- package/dist/core/review-fix-ledger.d.ts +20 -0
- package/dist/core/review-fix-ledger.js +41 -0
- package/dist/core/review-fix.d.ts +60 -0
- package/dist/core/review-fix.js +155 -0
- package/dist/core/review-host-tools.d.ts +13 -2
- package/dist/core/review-host-tools.js +72 -11
- package/dist/core/runner.d.ts +2 -2
- package/dist/core/runner.js +379 -184
- package/dist/core/serial-gate.d.ts +11 -0
- package/dist/core/serial-gate.js +19 -0
- package/dist/core/timing.d.ts +7 -0
- package/dist/core/timing.js +7 -0
- package/dist/generated/prisma/browser.d.ts +23 -0
- package/dist/generated/prisma/client.d.ts +23 -0
- package/dist/generated/prisma/commonInputTypes.d.ts +22 -0
- package/dist/generated/prisma/internal/class.d.ts +33 -0
- package/dist/generated/prisma/internal/class.js +4 -4
- package/dist/generated/prisma/internal/prismaNamespace.d.ts +274 -1
- package/dist/generated/prisma/internal/prismaNamespace.js +47 -2
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +48 -0
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +47 -2
- package/dist/generated/prisma/models/Agent.d.ts +422 -1
- package/dist/generated/prisma/models/AgentRepository.d.ts +120 -2
- package/dist/generated/prisma/models/CodingAgentProfile.d.ts +42 -1
- package/dist/generated/prisma/models/CodingRun.d.ts +205 -1
- package/dist/generated/prisma/models/DeferredReview.d.ts +1336 -0
- package/dist/generated/prisma/models/DeferredReview.js +1 -0
- package/dist/generated/prisma/models/LocalPullRequest.d.ts +1384 -0
- package/dist/generated/prisma/models/LocalPullRequest.js +1 -0
- package/dist/generated/prisma/models/LocalReview.d.ts +1315 -0
- package/dist/generated/prisma/models/LocalReview.js +1 -0
- package/dist/generated/prisma/models/Run.d.ts +223 -0
- package/dist/generated/prisma/models/RunHostCheck.d.ts +148 -1
- package/dist/generated/prisma/models.d.ts +3 -0
- package/dist/help-index.json +486 -37
- package/dist/import/neutral-schema.d.ts +2 -2
- package/dist/mcp/auth/access.d.ts +3 -1
- package/dist/mcp/auth/host-account-cli.js +2 -2
- package/dist/mcp/auth/repo-authorization.d.ts +6 -7
- package/dist/mcp/auth/repo-authorization.js +21 -0
- package/dist/mcp/index.js +4 -1
- package/dist/mcp/tools/agents.js +46 -4
- package/dist/mcp/tools/host-accounts.js +2 -2
- package/dist/mcp/tools/repositories.js +71 -12
- package/dist/mcp/tools/runs.js +23 -0
- package/dist/mcp/tools/trigger.js +153 -24
- package/dist/providers/engine/types.d.ts +11 -0
- package/dist/providers/executor/composition.js +12 -7
- package/dist/providers/executor/container.d.ts +40 -3
- package/dist/providers/executor/container.js +140 -9
- package/dist/providers/executor/types.d.ts +1 -1
- package/dist/providers/jobs/fake-kubernetes-api.d.ts +3 -2
- package/dist/providers/jobs/fake-kubernetes-api.js +3 -0
- package/dist/providers/jobs/kubernetes-api.d.ts +3 -1
- package/dist/providers/jobs/kubernetes-client.d.ts +2 -1
- package/dist/providers/jobs/kubernetes-client.js +3 -0
- package/dist/providers/jobs/kubernetes-isolation.d.ts +7 -0
- package/dist/providers/jobs/kubernetes-isolation.js +8 -3
- package/dist/providers/jobs/kubernetes-preflight.d.ts +8 -0
- package/dist/providers/jobs/kubernetes-preflight.js +7 -0
- package/dist/providers/jobs/kubernetes-quota.d.ts +31 -0
- package/dist/providers/jobs/kubernetes-quota.js +110 -0
- package/dist/providers/jobs/kubernetes.d.ts +9 -0
- package/dist/providers/jobs/kubernetes.js +50 -0
- package/dist/providers/jobs/types.d.ts +7 -0
- package/dist/providers/review-host/ci.d.ts +7 -0
- package/dist/providers/review-host/ci.js +20 -0
- package/dist/providers/review-host/github-events.d.ts +7 -0
- package/dist/providers/review-host/github-events.js +31 -7
- package/dist/providers/review-host/github.d.ts +19 -2
- package/dist/providers/review-host/github.js +145 -2
- package/dist/providers/review-host/index.d.ts +8 -2
- package/dist/providers/review-host/index.js +14 -3
- package/dist/providers/review-host/local.d.ts +72 -0
- package/dist/providers/review-host/local.js +434 -0
- package/dist/providers/review-host/types.d.ts +65 -3
- package/dist/providers/review-host/types.js +5 -3
- package/dist/providers/vcs/git.d.ts +10 -20
- package/dist/providers/vcs/git.js +71 -127
- package/dist/providers/vcs/github-remote.d.ts +67 -0
- package/dist/providers/vcs/github-remote.js +201 -0
- package/dist/providers/vcs/github.d.ts +81 -0
- package/dist/providers/vcs/github.js +203 -6
- package/dist/providers/vcs/index.d.ts +16 -1
- package/dist/providers/vcs/index.js +43 -15
- package/dist/providers/vcs/local-remote.d.ts +44 -0
- package/dist/providers/vcs/local-remote.js +108 -0
- package/dist/providers/vcs/remote.d.ts +58 -0
- package/dist/providers/vcs/remote.js +1 -0
- package/dist/providers/vcs/routing.d.ts +33 -0
- package/dist/providers/vcs/routing.js +52 -0
- package/dist/providers/vcs/types.d.ts +12 -1
- package/dist/quickstart/coding-db.d.ts +11 -0
- package/dist/quickstart/coding-db.js +75 -0
- package/dist/quickstart/coding-doctor.d.ts +13 -0
- package/dist/quickstart/coding-doctor.js +88 -0
- package/dist/quickstart/coding-images.d.ts +15 -0
- package/dist/quickstart/coding-images.js +79 -0
- package/dist/quickstart/coding-seed.d.ts +63 -0
- package/dist/quickstart/coding-seed.js +85 -0
- package/dist/quickstart/coding.d.ts +66 -0
- package/dist/quickstart/coding.js +430 -0
- package/dist/quickstart/images.d.ts +19 -0
- package/dist/quickstart/images.js +63 -0
- package/dist/quickstart/index.js +61 -12
- package/dist/quickstart/starter-services.d.ts +45 -0
- package/dist/quickstart/starter-services.js +186 -0
- package/dist/quickstart-images.json +1 -0
- package/dist/serve.js +14 -1
- package/dist/viewer/api-schema.d.ts +176 -0
- package/dist/viewer/api-schema.js +27 -2
- package/dist/viewer/graph.d.ts +7 -2
- package/dist/viewer/graph.js +25 -11
- package/dist/viewer/http.d.ts +7 -1
- package/dist/viewer/http.js +8 -3
- package/dist/viewer/infra.d.ts +2 -0
- package/dist/viewer/infra.js +23 -0
- package/dist/viewer/run-detail.d.ts +2 -1
- package/dist/viewer/run-detail.js +2 -2
- package/docs/agent-recipes.md +127 -5
- package/docs/code-review-agents.md +293 -38
- package/docs/coding-agent-setup.md +141 -2
- package/docs/coding-packages.md +21 -0
- package/docs/coding-services.md +3 -0
- package/docs/coding-worker-isolation.md +56 -20
- package/docs/getting-started-gke.md +24 -0
- package/docs/getting-started.md +196 -10
- package/docs/jira-agents.md +26 -5
- package/docs/security-deployment.md +9 -0
- package/docs/viewer-api.md +21 -2
- package/help/admin-viewer.md +11 -1
- package/help/agent-recipes.md +30 -4
- package/help/builder-agent.md +4 -0
- package/help/code-review-agents.md +82 -2
- package/help/coding-packages.md +12 -1
- package/help/coding-services.md +4 -0
- package/help/cost-attribution.md +3 -1
- package/help/creating-agents.md +3 -0
- package/help/deploy-gke.md +2 -1
- package/help/errors/coding-provider-not-configured.md +60 -0
- package/help/errors/coding-turn-limit.md +25 -0
- package/help/errors/continuation-closed.md +75 -0
- package/help/errors/local-branch-conflict.md +37 -0
- package/help/errors/local-path-invalid.md +30 -0
- package/help/errors/local-ref-invalid.md +36 -0
- package/help/errors/local-ref-not-found.md +41 -0
- package/help/errors/local-repo-not-allowed.md +46 -0
- package/help/errors/local-repo-not-found.md +39 -0
- package/help/errors/model-unavailable.md +7 -6
- package/help/errors/vcs-github-not-configured.md +34 -0
- package/help/getting-started.md +28 -0
- package/help/github.md +22 -5
- package/help/jira.md +9 -2
- package/help/local-repositories.md +182 -0
- package/help/related-pull-requests.md +89 -0
- package/help/review-fix-rounds.md +69 -0
- package/help/troubleshooting/budgets.md +11 -0
- package/help/troubleshooting/coding-workers.md +22 -0
- package/help/troubleshooting/repository-access.md +5 -0
- package/package.json +3 -2
- package/prisma/migrations/20261004100000_delegations_and_coding_turns/migration.sql +12 -0
- package/prisma/migrations/20261005000000_review_fix_rounds/migration.sql +5 -0
- package/prisma/migrations/20261006000000_parallel_delegations/migration.sql +6 -0
- package/prisma/migrations/20261007000000_ci_rereview/migration.sql +4 -0
- package/prisma/migrations/20261008000000_review_after_ci/migration.sql +33 -0
- package/prisma/migrations/20261009000001_coding_run_local_branch/migration.sql +7 -0
- package/prisma/migrations/20261009000002_local_review/migration.sql +50 -0
- package/prisma/schema.prisma +98 -1
|
@@ -30,7 +30,10 @@
|
|
|
30
30
|
*/
|
|
31
31
|
import { dispatchRun } from "../../core/dispatch.js";
|
|
32
32
|
import { AttributionError, explicitAttribution, RESPONSE_PATH_SNAPSHOT_BUDGET, } from "../../core/attribution.js";
|
|
33
|
-
import { CodingBaseRefSchema, CodingTaskOverrideSchema } from "../../coding/protocol.js";
|
|
33
|
+
import { CodingBaseRefSchema, CodingTaskOverrideSchema, isLocalRepository } from "../../coding/protocol.js";
|
|
34
|
+
import { loadLocalRepoRoots, resolveLocalRepository, LocalRepoError } from "../../coding/local-repo.js";
|
|
35
|
+
import { localRepoWarnings } from "../../coding/local-repo-status.js";
|
|
36
|
+
import { SHA, isSafeRefName, localGit } from "../../coding/local-git.js";
|
|
34
37
|
import { z } from "zod";
|
|
35
38
|
import { McpError } from "../errors.js";
|
|
36
39
|
import { createTaskResult, getTask, cancelTask } from "../tasks/manager.js";
|
|
@@ -44,8 +47,68 @@ const TriggerAgentSchema = z
|
|
|
44
47
|
task: CodingTaskOverrideSchema.optional(),
|
|
45
48
|
baseRef: CodingBaseRefSchema.optional(),
|
|
46
49
|
issue: z.unknown().optional(),
|
|
50
|
+
review: z
|
|
51
|
+
.object({
|
|
52
|
+
repository: z.string().optional(),
|
|
53
|
+
branch: z.string().min(1).max(250),
|
|
54
|
+
base: z.string().min(1).max(250).optional(),
|
|
55
|
+
})
|
|
56
|
+
.strict()
|
|
57
|
+
.optional(),
|
|
47
58
|
})
|
|
48
59
|
.strict();
|
|
60
|
+
/**
|
|
61
|
+
* Resolves a manual local review request to a canonical repository and the two
|
|
62
|
+
* commit SHAs. Roots are re-checked now (they can narrow after linking); every
|
|
63
|
+
* failure is a 400 whose message carries the code, before any row exists.
|
|
64
|
+
*/
|
|
65
|
+
async function resolveLocalReview(ctx, agentId, review) {
|
|
66
|
+
let repository = review.repository;
|
|
67
|
+
const links = await ctx.db.agentRepository.findMany({ where: { agentId, provider: "local" } });
|
|
68
|
+
if (repository === undefined) {
|
|
69
|
+
if (links.length !== 1)
|
|
70
|
+
throw new McpError(400, "pass review.repository");
|
|
71
|
+
repository = links[0].repository;
|
|
72
|
+
}
|
|
73
|
+
let resolved;
|
|
74
|
+
try {
|
|
75
|
+
resolved = await resolveLocalRepository(repository, loadLocalRepoRoots(process.env).roots);
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
if (err instanceof LocalRepoError)
|
|
79
|
+
throw new McpError(400, err.message);
|
|
80
|
+
throw new McpError(400, `local_repo_not_found: ${err.message}`);
|
|
81
|
+
}
|
|
82
|
+
if (!links.some((l) => l.repository === resolved.repository)) {
|
|
83
|
+
throw new McpError(400, `Agent "${agentId}" is not linked to ${resolved.repository}; link it with link_repository first.`);
|
|
84
|
+
}
|
|
85
|
+
const dir = resolved.path;
|
|
86
|
+
let base = review.base;
|
|
87
|
+
if (base === undefined) {
|
|
88
|
+
try {
|
|
89
|
+
base = (await localGit(dir, ["symbolic-ref", "--short", "HEAD"])).trim();
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
throw new McpError(400, "local_ref_invalid: the repository's HEAD is not on a branch; pass review.base");
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const sha = async (ref) => {
|
|
96
|
+
if (!(await isSafeRefName(dir, ref)))
|
|
97
|
+
throw new McpError(400, `local_ref_invalid: "${ref}" is not a valid branch name`);
|
|
98
|
+
try {
|
|
99
|
+
const out = (await localGit(dir, ["rev-parse", "--verify", "--end-of-options", `${ref}^{commit}`])).trim();
|
|
100
|
+
if (SHA.test(out))
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
// fall through
|
|
105
|
+
}
|
|
106
|
+
throw new McpError(400, `local_ref_not_found: "${ref}" does not name a commit in ${resolved.repository}`);
|
|
107
|
+
};
|
|
108
|
+
const headSha = await sha(review.branch);
|
|
109
|
+
await sha(base);
|
|
110
|
+
return { repository: resolved.repository, branch: review.branch, base, headSha };
|
|
111
|
+
}
|
|
49
112
|
function parseTriggerArgs(args) {
|
|
50
113
|
const parsed = TriggerAgentSchema.safeParse(args);
|
|
51
114
|
if (parsed.success)
|
|
@@ -87,6 +150,24 @@ export function registerTriggerTool(mcp) {
|
|
|
87
150
|
agentId: { type: "string" },
|
|
88
151
|
task: { type: "string" },
|
|
89
152
|
baseRef: { type: "string" },
|
|
153
|
+
review: {
|
|
154
|
+
type: "object",
|
|
155
|
+
additionalProperties: false,
|
|
156
|
+
description: "Native review agents only, owner only: review a branch of a linked local repository (local:/abs/path). " +
|
|
157
|
+
"The result appears in get_run as `review`.",
|
|
158
|
+
properties: {
|
|
159
|
+
repository: {
|
|
160
|
+
type: "string",
|
|
161
|
+
description: "The local repository (local:/abs/path). Default: the agent's only local link.",
|
|
162
|
+
},
|
|
163
|
+
branch: { type: "string", description: "The branch to review (resolved to its current commit)." },
|
|
164
|
+
base: {
|
|
165
|
+
type: "string",
|
|
166
|
+
description: "The branch to review against. Default: the branch currently checked out in the repository.",
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
required: ["branch"],
|
|
170
|
+
},
|
|
90
171
|
issue: {
|
|
91
172
|
type: "object",
|
|
92
173
|
description: "Count this run's cost toward an issue in a project the agent is linked to.",
|
|
@@ -99,9 +180,34 @@ export function registerTriggerTool(mcp) {
|
|
|
99
180
|
handler: async (rawArgs, ctx) => {
|
|
100
181
|
const args = parseTriggerArgs(rawArgs);
|
|
101
182
|
const agent = await requireTriggerable(ctx, await ctx.db.agent.findUnique({ where: { id: args.agentId }, include: { codingProfile: true } }), args.agentId, args);
|
|
183
|
+
let review;
|
|
184
|
+
if (args.review !== undefined) {
|
|
185
|
+
if (agent.kind !== "native")
|
|
186
|
+
throw new McpError(400, "review is only valid for native review agents.");
|
|
187
|
+
if (agent.ownerId === null || agent.ownerId !== ctx.principal.id) {
|
|
188
|
+
throw new McpError(403, `Agent "${agent.id}": only its owner can start a manual review.`);
|
|
189
|
+
}
|
|
190
|
+
review = await resolveLocalReview(ctx, agent.id, args.review);
|
|
191
|
+
}
|
|
102
192
|
if (agent.kind !== "coding" && (args.task !== undefined || args.baseRef !== undefined)) {
|
|
103
193
|
throw new McpError(400, "Task and baseRef overrides are only valid for coding agents.");
|
|
104
194
|
}
|
|
195
|
+
// A local repository is re-checked against the roots as they are now
|
|
196
|
+
// (they can narrow after the agent was saved), before any run exists.
|
|
197
|
+
let warnings = [];
|
|
198
|
+
const repository = agent.kind === "coding" ? agent.codingProfile?.repository : undefined;
|
|
199
|
+
if (repository !== undefined && isLocalRepository(repository)) {
|
|
200
|
+
try {
|
|
201
|
+
const resolved = await resolveLocalRepository(repository, loadLocalRepoRoots(process.env).roots);
|
|
202
|
+
warnings = await localRepoWarnings(resolved.path);
|
|
203
|
+
}
|
|
204
|
+
catch (err) {
|
|
205
|
+
if (err instanceof LocalRepoError)
|
|
206
|
+
throw new McpError(400, err.message);
|
|
207
|
+
throw err;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
const withWarnings = (result) => (warnings.length > 0 ? { ...result, warnings } : result);
|
|
105
211
|
// Refused before any run exists: an invalid or unlinked issue is never
|
|
106
212
|
// silently dropped into an unattributed run.
|
|
107
213
|
let attribution;
|
|
@@ -115,38 +221,61 @@ export function registerTriggerTool(mcp) {
|
|
|
115
221
|
throw err;
|
|
116
222
|
}
|
|
117
223
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
224
|
+
// The pull request row is made first (its serial number is in the task
|
|
225
|
+
// text) and removed again if the run is not dispatched.
|
|
226
|
+
const pullRequest = review
|
|
227
|
+
? await ctx.db.localPullRequest.create({
|
|
228
|
+
data: { repository: review.repository, branch: review.branch, base: review.base },
|
|
229
|
+
})
|
|
230
|
+
: undefined;
|
|
231
|
+
let dispatched;
|
|
232
|
+
try {
|
|
233
|
+
dispatched = await dispatchRun({
|
|
234
|
+
db: ctx.db,
|
|
235
|
+
executor: ctx.providers.executor,
|
|
236
|
+
selfDefects: { db: ctx.db, issueTrackers: ctx.providers.issueTrackers },
|
|
237
|
+
agentId: agent.id,
|
|
238
|
+
trigger: "manual",
|
|
239
|
+
codingTask: args.task,
|
|
240
|
+
codingBaseRef: args.baseRef,
|
|
241
|
+
attribution,
|
|
242
|
+
task: ctx.clientSupportsTasks ? { principalId: ctx.principal.id, ttlMs: DEFAULT_TASK_TTL_MS } : undefined,
|
|
243
|
+
triggeredById: ctx.principal.id,
|
|
244
|
+
...(review && pullRequest
|
|
245
|
+
? {
|
|
246
|
+
taskOverride: `Review pull request #${pullRequest.number} in ${review.repository} (head ${review.headSha}).`,
|
|
247
|
+
afterPersist: async (tx, run) => {
|
|
248
|
+
await tx.localPullRequest.update({ where: { id: pullRequest.id }, data: { runId: run.id } });
|
|
249
|
+
},
|
|
250
|
+
}
|
|
251
|
+
: {}),
|
|
252
|
+
// Re-checked through the transaction: a concurrent revoke or
|
|
253
|
+
// make_owner conflicts instead of racing the run in.
|
|
254
|
+
beforePersist: async (tx, current) => {
|
|
255
|
+
await requireTriggerable(ctx, current, agent.id, args, tx);
|
|
256
|
+
return true;
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
if (!dispatched)
|
|
260
|
+
throw new Error("Run dispatch was not claimed.");
|
|
261
|
+
}
|
|
262
|
+
catch (err) {
|
|
263
|
+
if (pullRequest)
|
|
264
|
+
await ctx.db.localPullRequest.deleteMany({ where: { id: pullRequest.id } }).catch(() => { });
|
|
265
|
+
throw err;
|
|
266
|
+
}
|
|
138
267
|
if (ctx.clientSupportsTasks) {
|
|
139
268
|
if (!dispatched.task)
|
|
140
269
|
throw new Error("Run task was not persisted.");
|
|
141
|
-
return textResult(createTaskResult(dispatched.task, DEFAULT_TASK_TTL_MS));
|
|
270
|
+
return textResult(withWarnings(createTaskResult(dispatched.task, DEFAULT_TASK_TTL_MS)));
|
|
142
271
|
}
|
|
143
272
|
// A run refused at dispatch (its budget group or run tree is spent), or
|
|
144
273
|
// failed there (a coding agent's model is unavailable), is already
|
|
145
274
|
// terminal: say so now rather than only through get_run.
|
|
146
275
|
if (dispatched.run.status === "refused" || dispatched.run.status === "failed") {
|
|
147
|
-
return textResult({ runId: dispatched.run.id, status: dispatched.run.status, error: dispatched.run.error });
|
|
276
|
+
return textResult(withWarnings({ runId: dispatched.run.id, status: dispatched.run.status, error: dispatched.run.error }));
|
|
148
277
|
}
|
|
149
|
-
return textResult({ runId: dispatched.run.id });
|
|
278
|
+
return textResult(withWarnings({ runId: dispatched.run.id }));
|
|
150
279
|
},
|
|
151
280
|
});
|
|
152
281
|
// Neither handler sets `resultType` itself: it's wire-only protocol
|
|
@@ -69,6 +69,17 @@ export interface EngineRunContext {
|
|
|
69
69
|
providers: Pick<ProviderRegistry, "llm">;
|
|
70
70
|
/** Host bridge: validates params (Zod-in-sandbox) then runs the tool body in the WASM sandbox. Never throws — a tool/validation failure is a JSON error result string, fed back to the model as the tool's result. */
|
|
71
71
|
runSandboxTool(name: string, argsJson: string): Promise<string>;
|
|
72
|
+
/**
|
|
73
|
+
* Consecutive tool calls of one model turn for which this returns true run
|
|
74
|
+
* at the same time as each other (a lead's delegate_to_* calls, when the
|
|
75
|
+
* agent opted in to parallelDelegations); every other call, including a
|
|
76
|
+
* lone eligible one, runs alone, in order. Calls are never reordered, and
|
|
77
|
+
* results still go back to the model in call order. Absent: every call runs
|
|
78
|
+
* one after another. Under a checkpointing `step`, an engine must start the
|
|
79
|
+
* concurrent steps in an order fixed by the turn's calls alone (see
|
|
80
|
+
* NativeEngine.runToolCalls).
|
|
81
|
+
*/
|
|
82
|
+
runsConcurrently?: (toolName: string) => boolean;
|
|
72
83
|
onText?: (delta: string) => void;
|
|
73
84
|
/** Called after each model turn that completed without error; the engine awaits it. */
|
|
74
85
|
onProgress?: (progress: EngineProgress) => Promise<void>;
|
|
@@ -12,7 +12,8 @@ import { DockerJobLauncher } from "../jobs/docker.js";
|
|
|
12
12
|
import { ClientNodeKubernetesApi } from "../jobs/kubernetes-client.js";
|
|
13
13
|
import { isRegistryDigest } from "../jobs/kubernetes-isolation.js";
|
|
14
14
|
import { assertPlatformConfig, platformProfile } from "../jobs/kubernetes-platform.js";
|
|
15
|
-
import { runKubernetesPreflight } from "../jobs/kubernetes-preflight.js";
|
|
15
|
+
import { preflightCanaryImage, runKubernetesPreflight } from "../jobs/kubernetes-preflight.js";
|
|
16
|
+
import { CODING_WORKER_IMAGES_REQUIRED } from "../../coding/docker-preflight.js";
|
|
16
17
|
import { KubernetesJobLauncher } from "../jobs/kubernetes.js";
|
|
17
18
|
import { buildVcsProvider } from "../vcs/index.js";
|
|
18
19
|
import { ContainerExecutor, PrismaContainerExecutionStore, RunCapabilityVault } from "./container.js";
|
|
@@ -29,15 +30,18 @@ export function buildConfiguredExecutor(options) {
|
|
|
29
30
|
return options.native;
|
|
30
31
|
const github = loadGitHubVcsConfig(env);
|
|
31
32
|
const config = loadContainerExecutorConfig(env);
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
// Each provider's images are optional, but a coding launcher with neither can run nothing.
|
|
34
|
+
const claudeConfigured = Boolean(config.claudeWorkerImage && config.claudeToolRunnerImage);
|
|
35
|
+
if (!config.workerImage && !claudeConfigured) {
|
|
36
|
+
throw new Error(`${CODING_WORKER_IMAGES_REQUIRED} when JOB_LAUNCHER=${providerConfig.jobs}.`);
|
|
37
|
+
}
|
|
34
38
|
const proxyContainer = config.proxyContainer;
|
|
35
39
|
if (providerConfig.jobs === "docker" && !proxyContainer) {
|
|
36
40
|
throw new Error("CODING_PROXY_CONTAINER is required when JOB_LAUNCHER=docker.");
|
|
37
41
|
}
|
|
38
42
|
const workspaceRoot = resolve(github.workRoot ?? resolve(tmpdir(), "wardby-vcs"));
|
|
39
43
|
const artifactRoot = resolve(config.artifactRoot ?? resolve(tmpdir(), "wardby-coding-artifacts"));
|
|
40
|
-
const vcs = buildVcsProvider(providerConfig, { ...github, workRoot: workspaceRoot });
|
|
44
|
+
const vcs = buildVcsProvider(providerConfig, { ...github, workRoot: workspaceRoot }, env);
|
|
41
45
|
const capabilities = new RunCapabilityVault();
|
|
42
46
|
const sessions = new CodingProxy({
|
|
43
47
|
ledger: new PrismaProxyLedger(options.db),
|
|
@@ -46,7 +50,7 @@ export function buildConfiguredExecutor(options) {
|
|
|
46
50
|
const onServiceState = prismaServiceStateReporter(options.db);
|
|
47
51
|
let jobs;
|
|
48
52
|
if (providerConfig.jobs === "kubernetes") {
|
|
49
|
-
if (!isRegistryDigest(config.workerImage)) {
|
|
53
|
+
if (config.workerImage !== undefined && !isRegistryDigest(config.workerImage)) {
|
|
50
54
|
throw new Error("CODING_WORKER_IMAGE must be a registry digest (repo@sha256:...) when JOB_LAUNCHER=kubernetes.");
|
|
51
55
|
}
|
|
52
56
|
for (const image of [config.claudeWorkerImage, config.claudeToolRunnerImage]) {
|
|
@@ -66,7 +70,8 @@ export function buildConfiguredExecutor(options) {
|
|
|
66
70
|
maxDiskMb: config.maxDiskMb,
|
|
67
71
|
});
|
|
68
72
|
const api = options.kubernetesApi ?? new ClientNodeKubernetesApi({ context: kubernetes.context });
|
|
69
|
-
|
|
73
|
+
// The canary needs some worker image to run node in; the Claude worker stands in on a Claude-only server.
|
|
74
|
+
const workerImage = preflightCanaryImage(config);
|
|
70
75
|
jobs = new KubernetesJobLauncher({
|
|
71
76
|
onServiceState,
|
|
72
77
|
api,
|
|
@@ -126,7 +131,7 @@ export function buildConfiguredExecutor(options) {
|
|
|
126
131
|
anthropicCredentialRef: config.anthropicCredentialRef,
|
|
127
132
|
limits: { cpus: config.cpus, memoryMb: config.memoryMb, pids: config.pids, diskMb: config.diskMb },
|
|
128
133
|
maxDiskMb: config.maxDiskMb,
|
|
129
|
-
repoAccess: options.repoAccess ?? createRepoAccessGate({ db: options.db, hosts: buildReviewHosts(env) }),
|
|
134
|
+
repoAccess: options.repoAccess ?? createRepoAccessGate({ db: options.db, hosts: buildReviewHosts(env, options.db) }),
|
|
130
135
|
issueUrl: (provider, key) => (provider === "jira" ? issueTrackers.jira?.issueUrl(key) : undefined),
|
|
131
136
|
registryReport: async (runId) => {
|
|
132
137
|
const rows = await options.db.registryFetch.findMany({ where: { runId }, orderBy: { createdAt: "asc" } });
|
|
@@ -8,12 +8,20 @@ import type { ProxyModelTerms, ProxyProtocol } from "../coding-proxy/types.js";
|
|
|
8
8
|
import type { JobHandle, JobResourceLimits, WorkspaceJobLauncher } from "../jobs/types.js";
|
|
9
9
|
import type { VcsProvider } from "../vcs/types.js";
|
|
10
10
|
import type { CodingImageSelector, ExecutionRecoveryResult, Executor, PersistedExecutionHandle } from "./types.js";
|
|
11
|
+
import type { RelatedPullRequestEntry } from "../vcs/github.js";
|
|
11
12
|
/**
|
|
12
13
|
* Claude Code's commands run in its tool runner, which never reports back through the agent, so the
|
|
13
14
|
* host rewrites registry-proxy download URLs in collected lockfiles itself (the Codex driver does it
|
|
14
15
|
* in the worker). Best effort and symlink-safe (lockfiles.ts); a Codex workspace is left alone.
|
|
15
16
|
*/
|
|
16
17
|
export declare function normalizeCollectedLockfiles(provider: string, workspacePath: string): Promise<string[]>;
|
|
18
|
+
/** Columns on CodingRun recorded when a run completes, beyond its result JSON. */
|
|
19
|
+
export interface CodingRunRecord {
|
|
20
|
+
/** The pushed branch of a run on a local repository. */
|
|
21
|
+
resultBranch?: string;
|
|
22
|
+
/** The commit the run started from. */
|
|
23
|
+
baseSha?: string;
|
|
24
|
+
}
|
|
17
25
|
export interface ContainerRunSnapshot {
|
|
18
26
|
runId: string;
|
|
19
27
|
status: string;
|
|
@@ -53,6 +61,8 @@ export interface ContainerRunSnapshot {
|
|
|
53
61
|
workspaceDiskMb: number | null;
|
|
54
62
|
/** Admin-requested debug trace, fixed at dispatch (CodingRun.debugTrace). */
|
|
55
63
|
debugTrace?: boolean;
|
|
64
|
+
/** Claude Code turn limit, fixed at dispatch (CodingRun.maxTurns); null = the worker default. */
|
|
65
|
+
maxTurns?: number | null;
|
|
56
66
|
/** Coding-run services resolved at dispatch (CodingRun.services); parsed with parseStoredServices. */
|
|
57
67
|
services?: unknown;
|
|
58
68
|
/** The terminal failure category, once the run has one (CodingRun.failureCategory). */
|
|
@@ -75,9 +85,21 @@ export type ProvisioningClaim = "claimed" | "unavailable" | "queued";
|
|
|
75
85
|
export interface ContainerExecutionStore {
|
|
76
86
|
load(runId: string): Promise<ContainerRunSnapshot | null>;
|
|
77
87
|
claimProvisioning(runId: string, claimId: string): Promise<ProvisioningClaim>;
|
|
88
|
+
/**
|
|
89
|
+
* Puts a pending, unclaimed run in the coding queue (sets CodingRun.queuedAt if unset), for a run
|
|
90
|
+
* the cluster has no room for yet. drainCodingQueue retries it when a run ends and on each
|
|
91
|
+
* scheduler tick, until CODING_QUEUE_TIMEOUT_SEC.
|
|
92
|
+
*/
|
|
93
|
+
markQueued?(runId: string): Promise<void>;
|
|
78
94
|
persistHandle(runId: string, claimId: string, handle: JobHandle): Promise<void>;
|
|
79
95
|
heartbeat(runId: string): Promise<void>;
|
|
80
|
-
|
|
96
|
+
/**
|
|
97
|
+
* The pull requests already opened for the same request as this run (same
|
|
98
|
+
* top-level run tree or tracked issue), in dispatch order, for the new PR's
|
|
99
|
+
* Related pull requests section. Optional: without it no section is written.
|
|
100
|
+
*/
|
|
101
|
+
relatedPullRequests?(runId: string): Promise<RelatedPullRequestEntry[]>;
|
|
102
|
+
complete(runId: string, status: "succeeded" | "budget_exhausted", result: CodingRunResult, record?: CodingRunRecord): Promise<void>;
|
|
81
103
|
terminate(runId: string, status: "failed" | "refused" | "lost" | "budget_exhausted" | "cancelled", error: string, audit?: CodingFailureAudit): Promise<void>;
|
|
82
104
|
}
|
|
83
105
|
export interface CodingFailureAudit {
|
|
@@ -97,10 +119,12 @@ export declare class PrismaContainerExecutionStore implements ContainerExecution
|
|
|
97
119
|
/** An empty registry (no Jira site configured) means no self-defect queries at all. */
|
|
98
120
|
private get hasTrackers();
|
|
99
121
|
load(runId: string): Promise<ContainerRunSnapshot | null>;
|
|
122
|
+
relatedPullRequests(runId: string): Promise<RelatedPullRequestEntry[]>;
|
|
100
123
|
claimProvisioning(runId: string, claimId: string): Promise<ProvisioningClaim>;
|
|
124
|
+
markQueued(runId: string): Promise<void>;
|
|
101
125
|
persistHandle(runId: string, claimId: string, handle: JobHandle): Promise<void>;
|
|
102
126
|
heartbeat(runId: string): Promise<void>;
|
|
103
|
-
complete(runId: string, status: "succeeded" | "budget_exhausted", result: CodingRunResult): Promise<void>;
|
|
127
|
+
complete(runId: string, status: "succeeded" | "budget_exhausted", result: CodingRunResult, record?: CodingRunRecord): Promise<void>;
|
|
104
128
|
terminate(runId: string, status: "failed" | "refused" | "lost" | "budget_exhausted" | "cancelled", error: string, audit?: CodingFailureAudit): Promise<void>;
|
|
105
129
|
}
|
|
106
130
|
export interface CodingSessionController {
|
|
@@ -137,7 +161,12 @@ export interface ContainerExecutorOptions {
|
|
|
137
161
|
sessions: CodingSessionController;
|
|
138
162
|
capabilities: RunCapabilityVault;
|
|
139
163
|
artifactRoot: string;
|
|
140
|
-
|
|
164
|
+
/**
|
|
165
|
+
* The Codex worker for the "node" toolchain (CODING_WORKER_IMAGE). Optional on a Claude-only
|
|
166
|
+
* deployment: a Codex run without an agent BYO image is then refused with
|
|
167
|
+
* coding_provider_not_configured:codex, as Claude Code is without its images.
|
|
168
|
+
*/
|
|
169
|
+
workerImage?: string;
|
|
141
170
|
/** Additional toolchains beyond the "node" baseline (workerImage). Keyed by toolchain, then version. */
|
|
142
171
|
additionalWorkerImages?: Record<string, Record<string, string>>;
|
|
143
172
|
credentialRef: string;
|
|
@@ -243,6 +272,8 @@ export declare class ContainerExecutor implements Executor {
|
|
|
243
272
|
private authorizeRepository;
|
|
244
273
|
/** The originating issue for the PR title/body: control-plane data from the CodingRun row, key re-validated. */
|
|
245
274
|
private issueFor;
|
|
275
|
+
/** The new PR's Related pull requests input: earlier PRs of the same request, then this one. Never throws. */
|
|
276
|
+
private relatedFor;
|
|
246
277
|
/** The run's stored model terms; a run from before the catalog resolves them from the current catalog. */
|
|
247
278
|
private modelTerms;
|
|
248
279
|
private preflight;
|
|
@@ -272,6 +303,12 @@ export declare class ContainerExecutor implements Executor {
|
|
|
272
303
|
/** The job launcher decides (Kubernetes and Docker start services for both providers). */
|
|
273
304
|
supportsCodingServices(provider: CodingProvider): boolean;
|
|
274
305
|
private requireCurrent;
|
|
306
|
+
/**
|
|
307
|
+
* False when the launcher reports this run's job would not fit the cluster right now; the run
|
|
308
|
+
* is then queued and drainCodingQueue retries it. Launchers and stores without the hooks, and
|
|
309
|
+
* any error, answer true: the launch stays the authority.
|
|
310
|
+
*/
|
|
311
|
+
private fitsCluster;
|
|
275
312
|
private jobSpec;
|
|
276
313
|
private credentialRef;
|
|
277
314
|
private writeInput;
|