@wardby/cli 0.4.1 → 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.
- 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 +476 -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 +123 -2
- 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 +82 -8
- 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 +25 -1
- 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 +4 -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
package/dist/coding/profile.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export declare const CodingProfileSchema: z.ZodObject<{
|
|
|
16
16
|
toolchainVersion: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
17
17
|
workerImageRef: z.ZodDefault<z.ZodNullable<z.ZodEffects<z.ZodString, string, string>>>;
|
|
18
18
|
workspaceDiskMb: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
|
|
19
|
+
maxTurns: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
|
|
19
20
|
packageAllowlist: z.ZodDefault<z.ZodEffects<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString, "many">>, Record<string, string[]>, Record<string, string[]>>>;
|
|
20
21
|
packagePolicy: z.ZodDefault<z.ZodEffects<z.ZodObject<{
|
|
21
22
|
minReleaseAgeDays: z.ZodOptional<z.ZodNumber>;
|
|
@@ -32,6 +33,7 @@ export declare const CodingProfileSchema: z.ZodObject<{
|
|
|
32
33
|
}, "strict", z.ZodTypeAny, {
|
|
33
34
|
repository: string;
|
|
34
35
|
baseRef: string;
|
|
36
|
+
maxTurns: number | null;
|
|
35
37
|
services: string[];
|
|
36
38
|
provider: "codex" | "claude-code";
|
|
37
39
|
timeoutSec: number;
|
|
@@ -50,6 +52,7 @@ export declare const CodingProfileSchema: z.ZodObject<{
|
|
|
50
52
|
}, {
|
|
51
53
|
repository: string;
|
|
52
54
|
baseRef?: string | undefined;
|
|
55
|
+
maxTurns?: number | null | undefined;
|
|
53
56
|
services?: string[] | undefined;
|
|
54
57
|
provider?: "codex" | "claude-code" | undefined;
|
|
55
58
|
timeoutSec?: number | undefined;
|
|
@@ -79,6 +82,7 @@ export declare const CodingProfilePatchSchema: z.ZodObject<{
|
|
|
79
82
|
toolchainVersion: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
80
83
|
workerImageRef: z.ZodOptional<z.ZodNullable<z.ZodEffects<z.ZodString, string, string>>>;
|
|
81
84
|
workspaceDiskMb: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
85
|
+
maxTurns: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
82
86
|
packageAllowlist: z.ZodOptional<z.ZodEffects<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString, "many">>, Record<string, string[]>, Record<string, string[]>>>;
|
|
83
87
|
packagePolicy: z.ZodOptional<z.ZodEffects<z.ZodObject<{
|
|
84
88
|
minReleaseAgeDays: z.ZodOptional<z.ZodNumber>;
|
|
@@ -95,6 +99,7 @@ export declare const CodingProfilePatchSchema: z.ZodObject<{
|
|
|
95
99
|
}, "strict", z.ZodTypeAny, {
|
|
96
100
|
repository?: string | undefined;
|
|
97
101
|
baseRef?: string | undefined;
|
|
102
|
+
maxTurns?: number | null | undefined;
|
|
98
103
|
services?: string[] | undefined;
|
|
99
104
|
provider?: "codex" | "claude-code" | undefined;
|
|
100
105
|
timeoutSec?: number | undefined;
|
|
@@ -113,6 +118,7 @@ export declare const CodingProfilePatchSchema: z.ZodObject<{
|
|
|
113
118
|
}, {
|
|
114
119
|
repository?: string | undefined;
|
|
115
120
|
baseRef?: string | undefined;
|
|
121
|
+
maxTurns?: number | null | undefined;
|
|
116
122
|
services?: string[] | undefined;
|
|
117
123
|
provider?: "codex" | "claude-code" | undefined;
|
|
118
124
|
timeoutSec?: number | undefined;
|
package/dist/coding/profile.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { isImmutableDockerImage } from "../providers/jobs/docker-isolation.js";
|
|
3
|
-
import { MAX_CODING_TASK_BYTES, normalizeGitHubRepository, normalizeGitRef } from "./protocol.js";
|
|
3
|
+
import { MAX_CODING_TASK_BYTES, MAX_CODING_TURNS, isLocalRepository, normalizeGitHubRepository, normalizeGitRef, normalizeLocalRepository, } from "./protocol.js";
|
|
4
4
|
import { MAX_COLLECT_EXCLUDE_PATHS, validateCollectExcludePath } from "./collect-exclude.js";
|
|
5
5
|
import { CODING_PROVIDERS } from "./provider.js";
|
|
6
6
|
import { WARDBY_PROTECTED_PATHS, isWellFormedProtectedPath, protectsSomePath } from "./protected-paths.js";
|
|
@@ -24,18 +24,22 @@ const INVALID_SINGLE_LINE_CONTROL = /[\u0000-\u001F\u007F]/;
|
|
|
24
24
|
function byteLength(value) {
|
|
25
25
|
return Buffer.byteLength(value, "utf8");
|
|
26
26
|
}
|
|
27
|
+
/** GitHub names, or `local:/abs/path` (syntactic only; trusted roots are enforced where it is authorized and used). */
|
|
28
|
+
function normalizeProfileRepository(value) {
|
|
29
|
+
return isLocalRepository(value) ? normalizeLocalRepository(value) : normalizeGitHubRepository(value);
|
|
30
|
+
}
|
|
27
31
|
const repositorySchema = z
|
|
28
32
|
.string()
|
|
29
33
|
.refine((value) => {
|
|
30
34
|
try {
|
|
31
|
-
|
|
35
|
+
normalizeProfileRepository(value);
|
|
32
36
|
return true;
|
|
33
37
|
}
|
|
34
38
|
catch {
|
|
35
39
|
return false;
|
|
36
40
|
}
|
|
37
|
-
}, "must be a canonical name
|
|
38
|
-
.transform(
|
|
41
|
+
}, "must be a canonical name, an uncredentialed github.com repository, or local:/absolute/path")
|
|
42
|
+
.transform(normalizeProfileRepository);
|
|
39
43
|
const baseRefSchema = z
|
|
40
44
|
.string()
|
|
41
45
|
.refine((value) => {
|
|
@@ -80,6 +84,8 @@ const workerImageRefSchema = z
|
|
|
80
84
|
.refine(isImmutableDockerImage, "must be an immutable repository digest")
|
|
81
85
|
.nullable();
|
|
82
86
|
const workspaceDiskMbSchema = z.number().int().min(64).max(32_768).nullable();
|
|
87
|
+
/** Claude Code runs only: the most agent turns a run may take; null = the worker default (DEFAULT_CLAUDE_MAX_TURNS). */
|
|
88
|
+
const maxTurnsSchema = z.number().int().min(1).max(MAX_CODING_TURNS).nullable();
|
|
83
89
|
const packageAllowlistSchema = z
|
|
84
90
|
.record(z.string(), z.array(z.string().min(1).max(256)).max(256))
|
|
85
91
|
.superRefine((value, ctx) => {
|
|
@@ -115,6 +121,7 @@ const codingProfileFields = {
|
|
|
115
121
|
toolchainVersion: toolchainVersionSchema,
|
|
116
122
|
workerImageRef: workerImageRefSchema,
|
|
117
123
|
workspaceDiskMb: workspaceDiskMbSchema,
|
|
124
|
+
maxTurns: maxTurnsSchema,
|
|
118
125
|
protectedPaths: z
|
|
119
126
|
.array(protectedPathSchema)
|
|
120
127
|
.min(1)
|
|
@@ -144,6 +151,7 @@ export const CodingProfileSchema = z
|
|
|
144
151
|
toolchainVersion: codingProfileFields.toolchainVersion.default(null),
|
|
145
152
|
workerImageRef: codingProfileFields.workerImageRef.default(null),
|
|
146
153
|
workspaceDiskMb: codingProfileFields.workspaceDiskMb.default(null),
|
|
154
|
+
maxTurns: codingProfileFields.maxTurns.default(null),
|
|
147
155
|
packageAllowlist: codingProfileFields.packageAllowlist.default({}),
|
|
148
156
|
packagePolicy: codingProfileFields.packagePolicy.default({}),
|
|
149
157
|
services: codingProfileFields.services.default([]),
|
|
@@ -163,6 +171,7 @@ export const CodingProfilePatchSchema = z
|
|
|
163
171
|
toolchainVersion: codingProfileFields.toolchainVersion.optional(),
|
|
164
172
|
workerImageRef: codingProfileFields.workerImageRef.optional(),
|
|
165
173
|
workspaceDiskMb: codingProfileFields.workspaceDiskMb.optional(),
|
|
174
|
+
maxTurns: codingProfileFields.maxTurns.optional(),
|
|
166
175
|
packageAllowlist: codingProfileFields.packageAllowlist.optional(),
|
|
167
176
|
packagePolicy: codingProfileFields.packagePolicy.optional(),
|
|
168
177
|
services: codingProfileFields.services.optional(),
|
|
@@ -37,6 +37,17 @@ export declare function isReservedServiceEnvName(name: string): boolean;
|
|
|
37
37
|
export declare const CodingServiceTestEnvSchema: z.ZodEffects<z.ZodRecord<z.ZodString, z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>>, Record<string, string>, Record<string, string>>;
|
|
38
38
|
export declare function normalizeGitHubRepository(value: string): string;
|
|
39
39
|
export declare function normalizeGitRef(value: string): string;
|
|
40
|
+
/** Repository identity prefix for a git folder on the wardby host: `local:/abs/path`. */
|
|
41
|
+
export declare const LOCAL_REPO_PREFIX = "local:";
|
|
42
|
+
export declare function isLocalRepository(value: string): boolean;
|
|
43
|
+
/** Upper bound on a whole `local:/abs/path` value, in UTF-8 bytes. */
|
|
44
|
+
export declare const MAX_LOCAL_REPOSITORY_BYTES = 4096;
|
|
45
|
+
/**
|
|
46
|
+
* Syntactic only: at most MAX_LOCAL_REPOSITORY_BYTES, no ASCII control
|
|
47
|
+
* characters (NUL included), absolute, normalized; returns "local:/abs/path"
|
|
48
|
+
* (no trailing slash).
|
|
49
|
+
*/
|
|
50
|
+
export declare function normalizeLocalRepository(value: string): string;
|
|
40
51
|
export declare const CodingBaseRefSchema: z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>;
|
|
41
52
|
export declare const CodingTaskOverrideSchema: z.ZodEffects<z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>, string, string>;
|
|
42
53
|
/**
|
|
@@ -69,6 +80,10 @@ export declare function composeCodingTask(instructions: string | null | undefine
|
|
|
69
80
|
* re-validates whatever survives.
|
|
70
81
|
*/
|
|
71
82
|
export declare function normalizeCodingTag(value: unknown): string | null | undefined;
|
|
83
|
+
/** Upper bound on CodingAgentProfile.maxTurns (Claude Code runs). */
|
|
84
|
+
export declare const MAX_CODING_TURNS = 1000;
|
|
85
|
+
/** The Claude Code worker's turn limit when the agent sets none; the run's budget and timeout are the real limits. */
|
|
86
|
+
export declare const DEFAULT_CLAUDE_MAX_TURNS = 200;
|
|
72
87
|
/** Longest line a debug-traced worker writes to its log (src/coding-worker/debug-trace.ts). */
|
|
73
88
|
export declare const MAX_DEBUG_TRACE_LINE_BYTES: number;
|
|
74
89
|
export declare const CodingTaskInputSchema: z.ZodEffects<z.ZodObject<{
|
|
@@ -103,6 +118,12 @@ export declare const CodingTaskInputSchema: z.ZodEffects<z.ZodObject<{
|
|
|
103
118
|
* untraced run is unchanged for workers that predate it.
|
|
104
119
|
*/
|
|
105
120
|
debugTrace: z.ZodOptional<z.ZodBoolean>;
|
|
121
|
+
/**
|
|
122
|
+
* Claude Code turn limit (CodingAgentProfile.maxTurns, fixed on the run at
|
|
123
|
+
* dispatch). Absent means the worker default; written only when the agent
|
|
124
|
+
* sets one, so other runs' input is unchanged for workers that predate it.
|
|
125
|
+
*/
|
|
126
|
+
maxTurns: z.ZodOptional<z.ZodNumber>;
|
|
106
127
|
/**
|
|
107
128
|
* Coding-run services started next to this run (docs/coding-services.md):
|
|
108
129
|
* each one's catalog name and version, and the variables the agent's shells
|
|
@@ -137,6 +158,7 @@ export declare const CodingTaskInputSchema: z.ZodEffects<z.ZodObject<{
|
|
|
137
158
|
runId: string;
|
|
138
159
|
} | undefined;
|
|
139
160
|
debugTrace?: boolean | undefined;
|
|
161
|
+
maxTurns?: number | undefined;
|
|
140
162
|
services?: {
|
|
141
163
|
name: string;
|
|
142
164
|
version: string;
|
|
@@ -156,6 +178,7 @@ export declare const CodingTaskInputSchema: z.ZodEffects<z.ZodObject<{
|
|
|
156
178
|
runId: string;
|
|
157
179
|
} | undefined;
|
|
158
180
|
debugTrace?: boolean | undefined;
|
|
181
|
+
maxTurns?: number | undefined;
|
|
159
182
|
services?: {
|
|
160
183
|
name: string;
|
|
161
184
|
version: string;
|
|
@@ -175,6 +198,7 @@ export declare const CodingTaskInputSchema: z.ZodEffects<z.ZodObject<{
|
|
|
175
198
|
runId: string;
|
|
176
199
|
} | undefined;
|
|
177
200
|
debugTrace?: boolean | undefined;
|
|
201
|
+
maxTurns?: number | undefined;
|
|
178
202
|
services?: {
|
|
179
203
|
name: string;
|
|
180
204
|
version: string;
|
|
@@ -194,6 +218,7 @@ export declare const CodingTaskInputSchema: z.ZodEffects<z.ZodObject<{
|
|
|
194
218
|
runId: string;
|
|
195
219
|
} | undefined;
|
|
196
220
|
debugTrace?: boolean | undefined;
|
|
221
|
+
maxTurns?: number | undefined;
|
|
197
222
|
services?: {
|
|
198
223
|
name: string;
|
|
199
224
|
version: string;
|
|
@@ -259,7 +284,7 @@ export declare const CodingAgentOutputSchema: z.ZodEffects<z.ZodObject<{
|
|
|
259
284
|
}>;
|
|
260
285
|
export declare const CodingRunResultSchema: z.ZodEffects<z.ZodEffects<z.ZodObject<{
|
|
261
286
|
schemaVersion: z.ZodLiteral<1>;
|
|
262
|
-
outcome: z.ZodEnum<["pull_request_opened", "pull_request_updated", "no_changes", "budget_exhausted"]>;
|
|
287
|
+
outcome: z.ZodEnum<["pull_request_opened", "pull_request_updated", "branch_pushed", "no_changes", "budget_exhausted"]>;
|
|
263
288
|
repository: z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>;
|
|
264
289
|
baseRef: z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>;
|
|
265
290
|
headRef: z.ZodOptional<z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>>;
|
|
@@ -293,7 +318,7 @@ export declare const CodingRunResultSchema: z.ZodEffects<z.ZodEffects<z.ZodObjec
|
|
|
293
318
|
tag: z.ZodEffects<z.ZodOptional<z.ZodNullable<z.ZodString>>, string | undefined, string | null | undefined>;
|
|
294
319
|
}, "strict", z.ZodTypeAny, {
|
|
295
320
|
repository: string;
|
|
296
|
-
outcome: "no_changes" | "budget_exhausted" | "pull_request_opened" | "pull_request_updated";
|
|
321
|
+
outcome: "no_changes" | "budget_exhausted" | "pull_request_opened" | "pull_request_updated" | "branch_pushed";
|
|
297
322
|
schemaVersion: 1;
|
|
298
323
|
baseRef: string;
|
|
299
324
|
summary: string;
|
|
@@ -313,7 +338,7 @@ export declare const CodingRunResultSchema: z.ZodEffects<z.ZodEffects<z.ZodObjec
|
|
|
313
338
|
pullRequestNumber?: number | undefined;
|
|
314
339
|
}, {
|
|
315
340
|
repository: string;
|
|
316
|
-
outcome: "no_changes" | "budget_exhausted" | "pull_request_opened" | "pull_request_updated";
|
|
341
|
+
outcome: "no_changes" | "budget_exhausted" | "pull_request_opened" | "pull_request_updated" | "branch_pushed";
|
|
317
342
|
schemaVersion: 1;
|
|
318
343
|
baseRef: string;
|
|
319
344
|
summary: string;
|
|
@@ -333,7 +358,7 @@ export declare const CodingRunResultSchema: z.ZodEffects<z.ZodEffects<z.ZodObjec
|
|
|
333
358
|
pullRequestNumber?: number | undefined;
|
|
334
359
|
}>, {
|
|
335
360
|
repository: string;
|
|
336
|
-
outcome: "no_changes" | "budget_exhausted" | "pull_request_opened" | "pull_request_updated";
|
|
361
|
+
outcome: "no_changes" | "budget_exhausted" | "pull_request_opened" | "pull_request_updated" | "branch_pushed";
|
|
337
362
|
schemaVersion: 1;
|
|
338
363
|
baseRef: string;
|
|
339
364
|
summary: string;
|
|
@@ -353,7 +378,7 @@ export declare const CodingRunResultSchema: z.ZodEffects<z.ZodEffects<z.ZodObjec
|
|
|
353
378
|
pullRequestNumber?: number | undefined;
|
|
354
379
|
}, {
|
|
355
380
|
repository: string;
|
|
356
|
-
outcome: "no_changes" | "budget_exhausted" | "pull_request_opened" | "pull_request_updated";
|
|
381
|
+
outcome: "no_changes" | "budget_exhausted" | "pull_request_opened" | "pull_request_updated" | "branch_pushed";
|
|
357
382
|
schemaVersion: 1;
|
|
358
383
|
baseRef: string;
|
|
359
384
|
summary: string;
|
|
@@ -379,7 +404,7 @@ export declare const CodingRunResultSchema: z.ZodEffects<z.ZodEffects<z.ZodObjec
|
|
|
379
404
|
outcome: "passed" | "failed" | "skipped";
|
|
380
405
|
}[];
|
|
381
406
|
repository: string;
|
|
382
|
-
outcome: "no_changes" | "budget_exhausted" | "pull_request_opened" | "pull_request_updated";
|
|
407
|
+
outcome: "no_changes" | "budget_exhausted" | "pull_request_opened" | "pull_request_updated" | "branch_pushed";
|
|
383
408
|
schemaVersion: 1;
|
|
384
409
|
baseRef: string;
|
|
385
410
|
usage: {
|
|
@@ -393,7 +418,7 @@ export declare const CodingRunResultSchema: z.ZodEffects<z.ZodEffects<z.ZodObjec
|
|
|
393
418
|
pullRequestNumber?: number | undefined;
|
|
394
419
|
}, {
|
|
395
420
|
repository: string;
|
|
396
|
-
outcome: "no_changes" | "budget_exhausted" | "pull_request_opened" | "pull_request_updated";
|
|
421
|
+
outcome: "no_changes" | "budget_exhausted" | "pull_request_opened" | "pull_request_updated" | "branch_pushed";
|
|
397
422
|
schemaVersion: 1;
|
|
398
423
|
baseRef: string;
|
|
399
424
|
summary: string;
|
package/dist/coding/protocol.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { isAbsolute, resolve } from "node:path";
|
|
2
3
|
export const CODING_PROTOCOL_VERSION = 1;
|
|
3
4
|
/** The code host every coding run's repository and pull request live on (see pullRequestUrlSchema); the one place to change when another is added. */
|
|
4
5
|
export const CODING_CODE_PROVIDER = "github";
|
|
@@ -192,10 +193,50 @@ function isGitRef(value) {
|
|
|
192
193
|
return false;
|
|
193
194
|
}
|
|
194
195
|
}
|
|
196
|
+
/** Repository identity prefix for a git folder on the wardby host: `local:/abs/path`. */
|
|
197
|
+
export const LOCAL_REPO_PREFIX = "local:";
|
|
198
|
+
export function isLocalRepository(value) {
|
|
199
|
+
return value.startsWith(LOCAL_REPO_PREFIX);
|
|
200
|
+
}
|
|
201
|
+
/** Upper bound on a whole `local:/abs/path` value, in UTF-8 bytes. */
|
|
202
|
+
export const MAX_LOCAL_REPOSITORY_BYTES = 4096;
|
|
203
|
+
/**
|
|
204
|
+
* Syntactic only: at most MAX_LOCAL_REPOSITORY_BYTES, no ASCII control
|
|
205
|
+
* characters (NUL included), absolute, normalized; returns "local:/abs/path"
|
|
206
|
+
* (no trailing slash).
|
|
207
|
+
*/
|
|
208
|
+
export function normalizeLocalRepository(value) {
|
|
209
|
+
if (!isLocalRepository(value))
|
|
210
|
+
throw new Error("local repository must start with local:");
|
|
211
|
+
if (byteLength(value) > MAX_LOCAL_REPOSITORY_BYTES) {
|
|
212
|
+
throw new Error(`local repository must be at most ${MAX_LOCAL_REPOSITORY_BYTES} UTF-8 bytes`);
|
|
213
|
+
}
|
|
214
|
+
const path = value.slice(LOCAL_REPO_PREFIX.length);
|
|
215
|
+
if (INVALID_SINGLE_LINE_CONTROL.test(path)) {
|
|
216
|
+
throw new Error("local repository path must not contain control characters");
|
|
217
|
+
}
|
|
218
|
+
if (!isAbsolute(path))
|
|
219
|
+
throw new Error("local repository path must be absolute");
|
|
220
|
+
return `${LOCAL_REPO_PREFIX}${resolve(path)}`;
|
|
221
|
+
}
|
|
222
|
+
function isCodingRepository(value) {
|
|
223
|
+
if (!isLocalRepository(value))
|
|
224
|
+
return isGitHubRepository(value);
|
|
225
|
+
try {
|
|
226
|
+
normalizeLocalRepository(value);
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
function normalizeCodingRepository(value) {
|
|
234
|
+
return isLocalRepository(value) ? normalizeLocalRepository(value) : normalizeGitHubRepository(value);
|
|
235
|
+
}
|
|
195
236
|
const repositorySchema = z
|
|
196
237
|
.string()
|
|
197
|
-
.refine(
|
|
198
|
-
.transform(
|
|
238
|
+
.refine(isCodingRepository, "must be a canonical name, an uncredentialed github.com repository, or local:/absolute/path")
|
|
239
|
+
.transform(normalizeCodingRepository);
|
|
199
240
|
export const CodingBaseRefSchema = z.string().refine(isGitRef, "must be a safe branch ref").transform(normalizeGitRef);
|
|
200
241
|
export const CodingTaskOverrideSchema = boundedText(MAX_CODING_TASK_BYTES);
|
|
201
242
|
/**
|
|
@@ -276,6 +317,10 @@ const testResultSchema = z
|
|
|
276
317
|
outcome: z.enum(["passed", "failed", "skipped"]),
|
|
277
318
|
})
|
|
278
319
|
.strict();
|
|
320
|
+
/** Upper bound on CodingAgentProfile.maxTurns (Claude Code runs). */
|
|
321
|
+
export const MAX_CODING_TURNS = 1000;
|
|
322
|
+
/** The Claude Code worker's turn limit when the agent sets none; the run's budget and timeout are the real limits. */
|
|
323
|
+
export const DEFAULT_CLAUDE_MAX_TURNS = 200;
|
|
279
324
|
/** Longest line a debug-traced worker writes to its log (src/coding-worker/debug-trace.ts). */
|
|
280
325
|
export const MAX_DEBUG_TRACE_LINE_BYTES = 16 * 1024;
|
|
281
326
|
export const CodingTaskInputSchema = z
|
|
@@ -305,6 +350,12 @@ export const CodingTaskInputSchema = z
|
|
|
305
350
|
* untraced run is unchanged for workers that predate it.
|
|
306
351
|
*/
|
|
307
352
|
debugTrace: z.boolean().optional(),
|
|
353
|
+
/**
|
|
354
|
+
* Claude Code turn limit (CodingAgentProfile.maxTurns, fixed on the run at
|
|
355
|
+
* dispatch). Absent means the worker default; written only when the agent
|
|
356
|
+
* sets one, so other runs' input is unchanged for workers that predate it.
|
|
357
|
+
*/
|
|
358
|
+
maxTurns: z.number().int().min(1).max(MAX_CODING_TURNS).optional(),
|
|
308
359
|
/**
|
|
309
360
|
* Coding-run services started next to this run (docs/coding-services.md):
|
|
310
361
|
* each one's catalog name and version, and the variables the agent's shells
|
|
@@ -367,7 +418,7 @@ const pullRequestUrlSchema = z
|
|
|
367
418
|
export const CodingRunResultSchema = z
|
|
368
419
|
.object({
|
|
369
420
|
schemaVersion: z.literal(CODING_PROTOCOL_VERSION),
|
|
370
|
-
outcome: z.enum(["pull_request_opened", "pull_request_updated", "no_changes", "budget_exhausted"]),
|
|
421
|
+
outcome: z.enum(["pull_request_opened", "pull_request_updated", "branch_pushed", "no_changes", "budget_exhausted"]),
|
|
371
422
|
repository: repositorySchema,
|
|
372
423
|
baseRef: CodingBaseRefSchema,
|
|
373
424
|
headRef: CodingBaseRefSchema.optional(),
|
|
@@ -390,7 +441,18 @@ export const CodingRunResultSchema = z
|
|
|
390
441
|
if (isPrOutcome && prFields.some((field) => field === undefined)) {
|
|
391
442
|
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "a PR outcome requires all PR fields" });
|
|
392
443
|
}
|
|
393
|
-
if (
|
|
444
|
+
if (value.outcome === "branch_pushed") {
|
|
445
|
+
if (value.headRef === undefined || value.commitSha === undefined) {
|
|
446
|
+
ctx.addIssue({
|
|
447
|
+
code: z.ZodIssueCode.custom,
|
|
448
|
+
message: "a pushed-branch outcome requires headRef and commitSha",
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
if (value.pullRequestUrl !== undefined || value.pullRequestNumber !== undefined) {
|
|
452
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "a pushed-branch outcome has no pull request" });
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
else if (!isPrOutcome && prFields.some((field) => field !== undefined)) {
|
|
394
456
|
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "non-PR outcomes must not include PR fields" });
|
|
395
457
|
}
|
|
396
458
|
if (value.pullRequestUrl && value.pullRequestNumber) {
|
|
@@ -10,6 +10,8 @@ const SAFE_WORKER_ERROR_CODES = new Set([
|
|
|
10
10
|
"coding_artifact_nesting_limit",
|
|
11
11
|
"coding_artifact_duplicate_key",
|
|
12
12
|
"coding_turn_failed",
|
|
13
|
+
// Claude Code reached the run's turn limit (CodingAgentProfile.maxTurns) before finishing.
|
|
14
|
+
"coding_turn_limit",
|
|
13
15
|
"coding_stream_failed",
|
|
14
16
|
"coding_stream_proxy_denied",
|
|
15
17
|
"coding_stream_rate_limited",
|
|
@@ -37,6 +37,8 @@ export interface GitHubVcsConfig {
|
|
|
37
37
|
maxChangedFiles?: number;
|
|
38
38
|
maxDiffBytes?: number;
|
|
39
39
|
}
|
|
40
|
+
/** Trusted roots for `local:` repositories (LOCAL_REPO_ROOTS, path-delimiter separated); unset = local repositories disabled. */
|
|
41
|
+
export { loadLocalRepoRoots } from "../coding/local-repo.js";
|
|
40
42
|
export declare function loadGitHubVcsConfig(env?: NodeJS.ProcessEnv): GitHubVcsConfig;
|
|
41
43
|
/** GitHub App webhook settings for the code-review host ingress; unset = ingress disabled. */
|
|
42
44
|
export declare function loadGitHubEventConfig(env?: NodeJS.ProcessEnv): {
|
|
@@ -100,6 +102,11 @@ export interface ContainerExecutorConfig {
|
|
|
100
102
|
maxDiskMb: number;
|
|
101
103
|
additionalWorkerImages: Record<string, Record<string, string>>;
|
|
102
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* An image variable's value, trimmed; empty or blank is unset. An env file that ships
|
|
107
|
+
* `CODING_WORKER_IMAGE=` must read as "no Codex worker", not as an invalid image.
|
|
108
|
+
*/
|
|
109
|
+
export declare function imageVariable(value: string | undefined): string | undefined;
|
|
103
110
|
export declare function loadContainerExecutorConfig(env?: NodeJS.ProcessEnv): ContainerExecutorConfig;
|
|
104
111
|
/**
|
|
105
112
|
* Caps coding runs holding a concurrency slot across every control-plane
|
|
@@ -146,6 +153,12 @@ export interface KubernetesJobConfig {
|
|
|
146
153
|
* alone is sufficient — no separate overall-timeout knob needs to move in lockstep.
|
|
147
154
|
*/
|
|
148
155
|
enforcementExecTimeoutMs?: number;
|
|
156
|
+
/**
|
|
157
|
+
* The namespace ResourceQuota coding-run pods count against (KUBERNETES_RESOURCE_QUOTA). When
|
|
158
|
+
* set, a run whose pod would not fit the quota's free room waits in the coding queue instead of
|
|
159
|
+
* failing when the API server refuses the pod. Unset = no check (the RBAC grant is by name).
|
|
160
|
+
*/
|
|
161
|
+
resourceQuota?: string;
|
|
149
162
|
}
|
|
150
163
|
export declare function loadKubernetesJobConfig(env?: NodeJS.ProcessEnv): KubernetesJobConfig;
|
|
151
164
|
export interface McpConfig {
|
package/dist/config/providers.js
CHANGED
|
@@ -29,6 +29,8 @@ function optionalPositiveInteger(value, name) {
|
|
|
29
29
|
throw new Error(`${name} must be a positive integer.`);
|
|
30
30
|
return parsed;
|
|
31
31
|
}
|
|
32
|
+
/** Trusted roots for `local:` repositories (LOCAL_REPO_ROOTS, path-delimiter separated); unset = local repositories disabled. */
|
|
33
|
+
export { loadLocalRepoRoots } from "../coding/local-repo.js";
|
|
32
34
|
export function loadGitHubVcsConfig(env = process.env) {
|
|
33
35
|
return {
|
|
34
36
|
appId: env.GITHUB_APP_ID,
|
|
@@ -152,15 +154,22 @@ function optionalBoundedInteger(value, name, min, max) {
|
|
|
152
154
|
}
|
|
153
155
|
return parsed;
|
|
154
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* An image variable's value, trimmed; empty or blank is unset. An env file that ships
|
|
159
|
+
* `CODING_WORKER_IMAGE=` must read as "no Codex worker", not as an invalid image.
|
|
160
|
+
*/
|
|
161
|
+
export function imageVariable(value) {
|
|
162
|
+
return value?.trim() || undefined;
|
|
163
|
+
}
|
|
155
164
|
export function loadContainerExecutorConfig(env = process.env) {
|
|
156
165
|
const additionalWorkerImages = {};
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
166
|
+
const nodePythonWorker = imageVariable(env.CODING_WORKER_IMAGE_NODE_PYTHON_3_12);
|
|
167
|
+
if (nodePythonWorker)
|
|
168
|
+
additionalWorkerImages["node-python"] = { "3.12": nodePythonWorker };
|
|
160
169
|
const claudeToolRunnerImages = {};
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
170
|
+
const nodePythonToolRunner = imageVariable(env.CODING_CLAUDE_TOOL_RUNNER_IMAGE_NODE_PYTHON_3_12);
|
|
171
|
+
if (nodePythonToolRunner)
|
|
172
|
+
claudeToolRunnerImages["node-python"] = { "3.12": nodePythonToolRunner };
|
|
164
173
|
const diskMb = optionalPositiveInteger(env.CODING_DISK_MB, "CODING_DISK_MB") ?? 2048;
|
|
165
174
|
// Defaults to the effective diskMb: raising the ceiling an agents:write caller can request is an
|
|
166
175
|
// explicit operator choice, so upgrading with an unchanged environment changes nothing.
|
|
@@ -169,9 +178,9 @@ export function loadContainerExecutorConfig(env = process.env) {
|
|
|
169
178
|
throw new Error(`CODING_MAX_DISK_MB (${maxDiskMb}) must be at least the effective CODING_DISK_MB (${diskMb}).`);
|
|
170
179
|
}
|
|
171
180
|
return {
|
|
172
|
-
workerImage: env.CODING_WORKER_IMAGE,
|
|
173
|
-
claudeWorkerImage: env.CODING_CLAUDE_WORKER_IMAGE,
|
|
174
|
-
claudeToolRunnerImage: env.CODING_CLAUDE_TOOL_RUNNER_IMAGE,
|
|
181
|
+
workerImage: imageVariable(env.CODING_WORKER_IMAGE),
|
|
182
|
+
claudeWorkerImage: imageVariable(env.CODING_CLAUDE_WORKER_IMAGE),
|
|
183
|
+
claudeToolRunnerImage: imageVariable(env.CODING_CLAUDE_TOOL_RUNNER_IMAGE),
|
|
175
184
|
claudeToolRunnerImages,
|
|
176
185
|
proxyContainer: env.CODING_PROXY_CONTAINER,
|
|
177
186
|
stateRoot: env.CODING_JOB_STATE_ROOT,
|
|
@@ -228,6 +237,9 @@ export function loadKubernetesJobConfig(env = process.env) {
|
|
|
228
237
|
};
|
|
229
238
|
if (env.KUBERNETES_CONTEXT)
|
|
230
239
|
config.context = env.KUBERNETES_CONTEXT;
|
|
240
|
+
if (env.KUBERNETES_RESOURCE_QUOTA) {
|
|
241
|
+
config.resourceQuota = dnsLabel(env.KUBERNETES_RESOURCE_QUOTA, "KUBERNETES_RESOURCE_QUOTA", "");
|
|
242
|
+
}
|
|
231
243
|
if (env.KUBERNETES_RUNTIME_CLASS) {
|
|
232
244
|
config.runtimeClassName = dnsLabel(env.KUBERNETES_RUNTIME_CLASS, "KUBERNETES_RUNTIME_CLASS", "");
|
|
233
245
|
}
|
|
@@ -77,6 +77,19 @@ export declare function linkedPullRequestAttribution(db: Pick<PrismaClient, "iss
|
|
|
77
77
|
timeoutMs?: number;
|
|
78
78
|
retryOn429?: boolean;
|
|
79
79
|
}): Promise<AttributionIntent | undefined>;
|
|
80
|
+
/**
|
|
81
|
+
* Attribution for a run on a pull request a wardby coding run opened, taken
|
|
82
|
+
* from that run's own issue. Covers a review that starts before the pull
|
|
83
|
+
* request is linked to its issue: the link is recorded when the lead run that
|
|
84
|
+
* delegated the work finishes, and a review can start earlier. The run id must
|
|
85
|
+
* come from the marker on an App-authored pull request
|
|
86
|
+
* (CodeReviewHost.pullRequestOrigin), never from pull request text. Undefined
|
|
87
|
+
* when that run is not this deployment's or has no issue.
|
|
88
|
+
*/
|
|
89
|
+
export declare function openingRunAttribution(db: Pick<PrismaClient, "codingRun" | "workItem">, trackers: IssueTrackerRegistry | undefined, openingRunId: string, opts?: {
|
|
90
|
+
timeoutMs?: number;
|
|
91
|
+
retryOn429?: boolean;
|
|
92
|
+
}): Promise<AttributionIntent | undefined>;
|
|
80
93
|
/** A refusal whose message is safe to return to the caller. */
|
|
81
94
|
export declare class AttributionError extends Error {
|
|
82
95
|
constructor(message: string);
|
package/dist/core/attribution.js
CHANGED
|
@@ -167,6 +167,36 @@ export async function linkedPullRequestAttribution(db, trackers, pr, opts) {
|
|
|
167
167
|
return undefined;
|
|
168
168
|
return { source: "linked_pr", item: await resolveWorkItem(db, trackers, issue.provider, issue.key, opts) };
|
|
169
169
|
}
|
|
170
|
+
/**
|
|
171
|
+
* Attribution for a run on a pull request a wardby coding run opened, taken
|
|
172
|
+
* from that run's own issue. Covers a review that starts before the pull
|
|
173
|
+
* request is linked to its issue: the link is recorded when the lead run that
|
|
174
|
+
* delegated the work finishes, and a review can start earlier. The run id must
|
|
175
|
+
* come from the marker on an App-authored pull request
|
|
176
|
+
* (CodeReviewHost.pullRequestOrigin), never from pull request text. Undefined
|
|
177
|
+
* when that run is not this deployment's or has no issue.
|
|
178
|
+
*/
|
|
179
|
+
export async function openingRunAttribution(db, trackers, openingRunId, opts) {
|
|
180
|
+
try {
|
|
181
|
+
const row = await db.codingRun.findUnique({
|
|
182
|
+
where: { runId: openingRunId },
|
|
183
|
+
select: {
|
|
184
|
+
issueProvider: true,
|
|
185
|
+
issueKey: true,
|
|
186
|
+
rootCodingRun: { select: { issueProvider: true, issueKey: true } },
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
const provider = row?.issueProvider ?? row?.rootCodingRun?.issueProvider;
|
|
190
|
+
const key = row?.issueKey ?? row?.rootCodingRun?.issueKey;
|
|
191
|
+
if (!provider || !key)
|
|
192
|
+
return undefined;
|
|
193
|
+
return { source: "linked_pr", item: await resolveWorkItem(db, trackers, provider, key, opts) };
|
|
194
|
+
}
|
|
195
|
+
catch (err) {
|
|
196
|
+
log.warn({ err, openingRunId }, "opening run lookup failed; the run is unattributed");
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
170
200
|
/** A refusal whose message is safe to return to the caller. */
|
|
171
201
|
export class AttributionError extends Error {
|
|
172
202
|
constructor(message) {
|
|
@@ -141,7 +141,9 @@ export interface RunTreeSpend {
|
|
|
141
141
|
* twice, and the tree spends inside the root's hold, so counting that hold
|
|
142
142
|
* would leave the tree nothing. It also leaves out holds of runs that started
|
|
143
143
|
* after the root (they already counted the root's hold). capUsd is therefore
|
|
144
|
-
* the tree's whole ceiling, and remainingUsd = capUsd - spentUsd
|
|
144
|
+
* the tree's whole ceiling, and remainingUsd = capUsd - spentUsd - live
|
|
145
|
+
* sibling holds. remainingUsd also nets the unspent holds of in-flight
|
|
146
|
+
* sibling branches (parallel delegations, siblingHoldsUsd).
|
|
145
147
|
*/
|
|
146
148
|
export declare function computeRunTreeSpend(db: BudgetGroupsDb, parentRunId: string, now?: Date): Promise<RunTreeSpend>;
|
|
147
149
|
export type BudgetConstraint = Period | "run-tree";
|
|
@@ -163,17 +163,65 @@ async function collectTreeRunIds(db, rootRunId) {
|
|
|
163
163
|
* twice, and the tree spends inside the root's hold, so counting that hold
|
|
164
164
|
* would leave the tree nothing. It also leaves out holds of runs that started
|
|
165
165
|
* after the root (they already counted the root's hold). capUsd is therefore
|
|
166
|
-
* the tree's whole ceiling, and remainingUsd = capUsd - spentUsd
|
|
166
|
+
* the tree's whole ceiling, and remainingUsd = capUsd - spentUsd - live
|
|
167
|
+
* sibling holds. remainingUsd also nets the unspent holds of in-flight
|
|
168
|
+
* sibling branches (parallel delegations, siblingHoldsUsd).
|
|
167
169
|
*/
|
|
168
170
|
export async function computeRunTreeSpend(db, parentRunId, now = new Date()) {
|
|
169
171
|
const { rootRunId, capUsd, spentUsd, remainingUsd } = await runTreeSpend(db, parentRunId, now);
|
|
170
172
|
return { rootRunId, capUsd, spentUsd, remainingUsd };
|
|
171
173
|
}
|
|
172
|
-
|
|
174
|
+
/**
|
|
175
|
+
* The unspent holds of the tree's in-flight runs beside the asking run: every
|
|
176
|
+
* live run (isHoldLive) not on its ancestor chain -- siblings of the asking
|
|
177
|
+
* run or of an ancestor, and everything below them. Sequential delegation
|
|
178
|
+
* never has one; parallel delegations do, and without this each concurrent
|
|
179
|
+
* child would claim the same remainder. Each run holds its own reservation (a
|
|
180
|
+
* coding run's CodingRun.budgetReservedUsd, a native run's agent budgetUsd)
|
|
181
|
+
* minus its own cost: a native branch's budgetUsd bounds only its own model
|
|
182
|
+
* spend, so a coding grandchild under it holds its reservation separately. A
|
|
183
|
+
* finished run holds nothing and counts its real spend only, so the tree's
|
|
184
|
+
* remainder rises when a sibling finishes. With `self`, only runs that
|
|
185
|
+
* started before it count (first come, first served, as in computeGroupSpend).
|
|
186
|
+
*/
|
|
187
|
+
function siblingHoldsUsd(rows, parentRunId, now, self) {
|
|
188
|
+
const byId = new Map(rows.map((r) => [r.id, r]));
|
|
189
|
+
const ancestors = new Set();
|
|
190
|
+
for (let id = parentRunId; id && !ancestors.has(id); id = byId.get(id)?.parentRunId ?? null) {
|
|
191
|
+
ancestors.add(id);
|
|
192
|
+
}
|
|
193
|
+
const queueTimeoutSec = loadCodingConcurrencyConfig().queueTimeoutSec;
|
|
194
|
+
let total = 0;
|
|
195
|
+
for (const r of rows) {
|
|
196
|
+
if (ancestors.has(r.id))
|
|
197
|
+
continue;
|
|
198
|
+
if (self && (r.id === self.id || !isOrderedBefore(r, self)))
|
|
199
|
+
continue;
|
|
200
|
+
if (!isHoldLive(r, now, queueTimeoutSec))
|
|
201
|
+
continue;
|
|
202
|
+
const reservation = r.codingRun ? Number(r.codingRun.budgetReservedUsd) : Number(r.agent.budgetUsd);
|
|
203
|
+
total += Math.max(0, reservation - Number(r.costUsd));
|
|
204
|
+
}
|
|
205
|
+
return total;
|
|
206
|
+
}
|
|
207
|
+
async function runTreeSpend(db, parentRunId, now, self) {
|
|
173
208
|
const root = await findRootRun(db, parentRunId);
|
|
174
209
|
const treeRunIds = await collectTreeRunIds(db, root.id);
|
|
175
|
-
const rows = await db.run.findMany({
|
|
210
|
+
const rows = await db.run.findMany({
|
|
211
|
+
where: { id: { in: treeRunIds } },
|
|
212
|
+
select: {
|
|
213
|
+
id: true,
|
|
214
|
+
parentRunId: true,
|
|
215
|
+
status: true,
|
|
216
|
+
costUsd: true,
|
|
217
|
+
startedAt: true,
|
|
218
|
+
heartbeatAt: true,
|
|
219
|
+
agent: { select: { budgetUsd: true } },
|
|
220
|
+
codingRun: { select: { budgetReservedUsd: true, timeoutSec: true } },
|
|
221
|
+
},
|
|
222
|
+
});
|
|
176
223
|
const spentUsd = rows.reduce((sum, r) => sum + Number(r.costUsd), 0);
|
|
224
|
+
const heldUsd = siblingHoldsUsd(rows, parentRunId, now, self);
|
|
177
225
|
const rootAgent = await db.agent.findUniqueOrThrow({
|
|
178
226
|
where: { id: root.agentId },
|
|
179
227
|
select: { id: true, budgetGroupId: true, budgetUsd: true },
|
|
@@ -190,7 +238,7 @@ async function runTreeSpend(db, parentRunId, now) {
|
|
|
190
238
|
rootRunId: root.id,
|
|
191
239
|
capUsd,
|
|
192
240
|
spentUsd,
|
|
193
|
-
remainingUsd: Math.max(0, capUsd - spentUsd),
|
|
241
|
+
remainingUsd: Math.max(0, capUsd - spentUsd - heldUsd),
|
|
194
242
|
treeRunIds,
|
|
195
243
|
root: rootOrder,
|
|
196
244
|
rootBudgetGroupId: rootAgent.budgetGroupId,
|
|
@@ -219,7 +267,7 @@ export async function effectiveBudgetForRun(db, agent, now = new Date(), parentR
|
|
|
219
267
|
const ownSpend = { excludeReservationRunIds: self ? [self.id] : [], holdsBefore: self };
|
|
220
268
|
if (!parentRunId)
|
|
221
269
|
return groupCappedBudget(db, agent, now, ownSpend);
|
|
222
|
-
const tree = await runTreeSpend(db, parentRunId, now);
|
|
270
|
+
const tree = await runTreeSpend(db, parentRunId, now, self);
|
|
223
271
|
const sharesRootGroup = agent.budgetGroupId !== null && agent.budgetGroupId === tree.rootBudgetGroupId;
|
|
224
272
|
const spend = sharesRootGroup
|
|
225
273
|
? { excludeReservationRunIds: [...tree.treeRunIds, ...(self ? [self.id] : [])], holdsBefore: tree.root }
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { CiView, PullRequestView } from "../providers/review-host/types.js";
|
|
2
|
+
export interface AgentCiView extends CiView {
|
|
3
|
+
/** The description carries Wardby's Dependency install incomplete warning. Description text: informational only. */
|
|
4
|
+
sandboxInstallIncomplete: boolean;
|
|
5
|
+
note: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function ciNote(ci: CiView, sandboxInstallIncomplete: boolean): string;
|
|
8
|
+
export declare function ciForAgent(view: Pick<PullRequestView, "headSha" | "body" | "ci">): AgentCiView;
|