@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/core/runner.js
CHANGED
|
@@ -26,6 +26,8 @@ import { scopeDatastore } from "../providers/datastore/scoped.js";
|
|
|
26
26
|
import { buildSecretsAccessor, scopeSecretsAccessor } from "./secrets.js";
|
|
27
27
|
import { buildSharedDatastoreAccessor, scopeSharedDatastoreAccessor } from "./datastores.js";
|
|
28
28
|
import { effectiveBudgetForRun } from "./budget-groups.js";
|
|
29
|
+
import { createDelegationSiblings } from "./delegation-siblings.js";
|
|
30
|
+
import { createSerialGate } from "./serial-gate.js";
|
|
29
31
|
import { withRunHeartbeat } from "./run-heartbeat.js";
|
|
30
32
|
import { ContinuationRefusedError, dispatchRun } from "./dispatch.js";
|
|
31
33
|
import { canDelegate } from "./grants.js";
|
|
@@ -38,12 +40,15 @@ import { loadCodingConcurrencyConfig } from "../config/providers.js";
|
|
|
38
40
|
import { REVIEW_HOST_TOOL_DEFS, REVIEW_HOST_TOOL_NAMES, handleReviewHostTool, } from "./review-host-tools.js";
|
|
39
41
|
import { closeOpenHostCheck } from "./review-host-checks.js";
|
|
40
42
|
import { serviceRefusalSentence } from "../coding/services/wording.js";
|
|
43
|
+
import { CONTINUATION_CLOSED_SENTENCE, isContinuationClosedError } from "../coding/continuation-wording.js";
|
|
41
44
|
import { RUN_TASK_TAG, splitTaskOverride, wrapUntrusted } from "./untrusted-content.js";
|
|
42
|
-
import { completeHostStatus } from "./host-status.js";
|
|
45
|
+
import { completeHostStatus, pullRequestOutcome } from "./host-status.js";
|
|
43
46
|
import { ISSUE_TRACKER_TOOL_DEFS, ISSUE_TRACKER_TOOL_NAMES, handleIssueTrackerTool, } from "./issue-tracker-tools.js";
|
|
44
47
|
import { completeIssueStatus } from "./issue-status.js";
|
|
45
48
|
import { fileIssue } from "./issue-dedupe.js";
|
|
46
49
|
import { fileSelfDefect } from "./self-defects.js";
|
|
50
|
+
import { startReviewFixAfterReview } from "./review-fix.js";
|
|
51
|
+
import { updateRelatedPullRequests } from "./related-pull-requests.js";
|
|
47
52
|
import { recordNativeModelUsage } from "./model-usage.js";
|
|
48
53
|
import { pinNativeRunPricing } from "./run-pricing.js";
|
|
49
54
|
import { RoutingLlmProvider } from "../providers/llm/routing.js";
|
|
@@ -54,11 +59,15 @@ const runnerLog = logger.child({ module: "runner" });
|
|
|
54
59
|
/**
|
|
55
60
|
* Sub-agent dispatch uses one synthetic tool per declared child, named by the
|
|
56
61
|
* boundName it's attached
|
|
57
|
-
* under.
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
+
* under. The parent's tool call blocks until the child run reaches a
|
|
63
|
+
* terminal state, and its result comes back as this tool call's result.
|
|
64
|
+
* A run delegates at most maxDelegationsPerRun times (default one: a
|
|
65
|
+
* classifier deciding plan-vs-implement commits to exactly one child) and to
|
|
66
|
+
* each child at most once. With Agent.parallelDelegations, the delegate_to_*
|
|
67
|
+
* calls of one turn run at the same time (NativeEngine.runToolCalls); their
|
|
68
|
+
* admission (the checks below plus the child row) is still one at a time,
|
|
69
|
+
* through the run's delegation gate, and a child refused for budget while a
|
|
70
|
+
* sibling runs waits for that sibling to finish (delegation-siblings.ts).
|
|
62
71
|
*
|
|
63
72
|
* Two execution paths depending on the child's `kind`:
|
|
64
73
|
* - `native`: calls `executeRun` directly, bypassing any `Executor` — the
|
|
@@ -82,10 +91,15 @@ const runnerLog = logger.child({ module: "runner" });
|
|
|
82
91
|
* The `delegate_to_` prefix lives in core/tool-names.ts, which reserves it
|
|
83
92
|
* so no user tool can be created under a name this dispatch would shadow.
|
|
84
93
|
*/
|
|
85
|
-
function delegateToolDef(boundName) {
|
|
94
|
+
function delegateToolDef(boundName, parallel) {
|
|
95
|
+
// With the flag off the description stays byte-identical to before, so
|
|
96
|
+
// existing leads keep their cached tool definitions.
|
|
97
|
+
const timing = parallel
|
|
98
|
+
? "Blocks until it finishes. Several delegate_to_* calls made in the same turn run at the same time, so make them together when their tasks don't need each other's results. Its spend"
|
|
99
|
+
: "Runs synchronously and blocks until it finishes; its spend";
|
|
86
100
|
return {
|
|
87
101
|
name: `${DELEGATE_TOOL_PREFIX}${boundName}`,
|
|
88
|
-
description: `Delegates a task to your "${boundName}" sub-agent.
|
|
102
|
+
description: `Delegates a task to your "${boundName}" sub-agent. ${timing} counts against your own run's shared budget scope. If the sub-agent belongs to another owner it runs only its own instructions: pass an empty task.`,
|
|
89
103
|
jsonSchema: {
|
|
90
104
|
type: "object",
|
|
91
105
|
properties: {
|
|
@@ -189,7 +203,7 @@ async function finishRunClaimed(db, runId, data) {
|
|
|
189
203
|
* ("requires the Phase 5 container executor") read as if Phase 5 were unbuilt.
|
|
190
204
|
*/
|
|
191
205
|
const CODING_EXECUTOR_NOT_CONFIGURED = "Coding agents need a container executor, but this deployment runs with JOB_LAUNCHER=local. " +
|
|
192
|
-
"Set JOB_LAUNCHER=docker
|
|
206
|
+
"Set JOB_LAUNCHER=docker (or kubernetes) to run them (see docs/coding-worker-isolation.md).";
|
|
193
207
|
/**
|
|
194
208
|
* Slack past the child's own queue wait + container deadline before the
|
|
195
209
|
* parent gives up: the executor needs a moment after a deadline kill to
|
|
@@ -201,8 +215,17 @@ export const CODING_CHILD_WAIT_GRACE_SEC = 60;
|
|
|
201
215
|
* over its services carries the host sentence (never the raw Run.error), so
|
|
202
216
|
* the parent can pass it on — for a mention, that is the requester.
|
|
203
217
|
*/
|
|
204
|
-
export function codingChildResult(run) {
|
|
205
|
-
|
|
218
|
+
export function codingChildResult(run, codingResult) {
|
|
219
|
+
// Checked before the "refused" branch: a continuation whose PR closed
|
|
220
|
+
// before the run ever spent anything is stored as "refused" (a preflight
|
|
221
|
+
// refusal, container.ts), not "failed" -- but it is the same
|
|
222
|
+
// continuation_closed category either way, and gets the same sentence
|
|
223
|
+
// regardless of which status it landed on.
|
|
224
|
+
const refusal = isContinuationClosedError(run.error)
|
|
225
|
+
? CONTINUATION_CLOSED_SENTENCE
|
|
226
|
+
: run.status === "refused"
|
|
227
|
+
? serviceRefusalSentence(run.error)
|
|
228
|
+
: null;
|
|
206
229
|
return JSON.stringify({
|
|
207
230
|
status: run.status,
|
|
208
231
|
finalText: run.finalText,
|
|
@@ -210,8 +233,26 @@ export function codingChildResult(run) {
|
|
|
210
233
|
tokensIn: run.tokensIn,
|
|
211
234
|
tokensOut: run.tokensOut,
|
|
212
235
|
...(refusal ? { refusal } : {}),
|
|
236
|
+
...pullRequestOf(codingResult),
|
|
213
237
|
});
|
|
214
238
|
}
|
|
239
|
+
/**
|
|
240
|
+
* The pull request the coding sub-run opened or pushed to, from wardby's own
|
|
241
|
+
* stored result (never the sub-agent's text), so the parent can cite it.
|
|
242
|
+
*/
|
|
243
|
+
function pullRequestOf(codingResult) {
|
|
244
|
+
const pr = pullRequestOutcome(codingResult);
|
|
245
|
+
if (!pr)
|
|
246
|
+
return {};
|
|
247
|
+
return {
|
|
248
|
+
pullRequest: {
|
|
249
|
+
outcome: pr.outcome === "pull_request_opened" ? "opened" : "updated",
|
|
250
|
+
repository: pr.repository,
|
|
251
|
+
number: pr.pullRequestNumber,
|
|
252
|
+
...(pr.pullRequestUrl ? { url: pr.pullRequestUrl } : {}),
|
|
253
|
+
},
|
|
254
|
+
};
|
|
255
|
+
}
|
|
215
256
|
/**
|
|
216
257
|
* Polls a dispatched coding child until it reaches a terminal status. Needed
|
|
217
258
|
* because a queued child (every concurrency slot taken) makes Executor.start
|
|
@@ -334,14 +375,16 @@ async function executeTrackedRun(runId, providers, db, onText, step) {
|
|
|
334
375
|
where: { parentAgentId: agent.id },
|
|
335
376
|
select: { boundName: true, childAgentId: true },
|
|
336
377
|
});
|
|
337
|
-
// Only queried when
|
|
338
|
-
//
|
|
378
|
+
// Only queried when the runner was given review hosts. buildReviewHosts
|
|
379
|
+
// always includes the local host, so a configured server always queries;
|
|
380
|
+
// only a runner built without hosts (as in many tests) skips the table.
|
|
339
381
|
const repositoryLinks = reviewHosts
|
|
340
382
|
? (await db.agentRepository.findMany({ where: { agentId: agent.id } })).map((l) => ({
|
|
341
383
|
provider: l.provider,
|
|
342
384
|
repository: l.repository,
|
|
343
385
|
access: l.access === "write" ? "write" : "read",
|
|
344
386
|
checkName: l.checkName,
|
|
387
|
+
waitForCi: l.waitForCi,
|
|
345
388
|
}))
|
|
346
389
|
: [];
|
|
347
390
|
// Likewise only when a Jira site is configured.
|
|
@@ -375,6 +418,10 @@ async function executeTrackedRun(runId, providers, db, onText, step) {
|
|
|
375
418
|
pricing,
|
|
376
419
|
memoryEnabled: agent.memoryEnabled,
|
|
377
420
|
subAgentEdges,
|
|
421
|
+
// Only when above the default of one, so a default agent's pinned step result is unchanged.
|
|
422
|
+
...(agent.maxDelegationsPerRun > 1 ? { maxDelegationsPerRun: agent.maxDelegationsPerRun } : {}),
|
|
423
|
+
// Only when on, so a default agent's pinned step result is unchanged.
|
|
424
|
+
...(agent.parallelDelegations ? { parallelDelegations: true } : {}),
|
|
378
425
|
repositoryLinks,
|
|
379
426
|
issueProjectLinks,
|
|
380
427
|
agent: {
|
|
@@ -407,7 +454,7 @@ async function executeTrackedRun(runId, providers, db, onText, step) {
|
|
|
407
454
|
...(agent.memoryEnabled ? MEMORY_TOOL_DEFS : []),
|
|
408
455
|
...(subAgentEdges.length > 0 ? [SUBAGENT_MEMORY_GET_TOOL] : []),
|
|
409
456
|
...(isDispatchedChild ? [PARENT_MEMORY_GET_TOOL] : []),
|
|
410
|
-
...subAgentEdges.map((edge) => delegateToolDef(edge.boundName)),
|
|
457
|
+
...subAgentEdges.map((edge) => delegateToolDef(edge.boundName, agent.parallelDelegations)),
|
|
411
458
|
...(repositoryLinks.length > 0 ? REVIEW_HOST_TOOL_DEFS : []),
|
|
412
459
|
...(issueProjectLinks.length > 0 ? ISSUE_TRACKER_TOOL_DEFS : []),
|
|
413
460
|
],
|
|
@@ -469,6 +516,13 @@ async function executeTrackedRun(runId, providers, db, onText, step) {
|
|
|
469
516
|
// jira_create_issue's per-run cap counter: shared by every tool call of this attempt (a resumed attempt
|
|
470
517
|
// starts a fresh one, floored by the run's recorded fingerprint creates).
|
|
471
518
|
const issueCreationCounters = new Map();
|
|
519
|
+
// One per attempt: admits this run's delegations one at a time (see the delegate branch).
|
|
520
|
+
const delegationGate = createSerialGate();
|
|
521
|
+
// The children this attempt's delegations have running, and the sub-agents whose delegation is
|
|
522
|
+
// waiting for one of them to free budget (parallelDelegations): a waiting delegation keeps its
|
|
523
|
+
// place in the same-child and limit checks while the gate is open to the others.
|
|
524
|
+
const delegationSiblings = createDelegationSiblings();
|
|
525
|
+
const waitingDelegations = new Set();
|
|
472
526
|
const runSandboxTool = async (name, argsJson) => {
|
|
473
527
|
if (loaded.memoryEnabled && MEMORY_TOOL_NAMES.has(name)) {
|
|
474
528
|
return handleMemoryTool(name, argsJson, loaded.agentId, providers.memory);
|
|
@@ -488,8 +542,15 @@ async function executeTrackedRun(runId, providers, db, onText, step) {
|
|
|
488
542
|
prNumber: check.prNumber,
|
|
489
543
|
}
|
|
490
544
|
: null,
|
|
491
|
-
markRunCheckCompleted: async () => {
|
|
492
|
-
await db.runHostCheck.update({
|
|
545
|
+
markRunCheckCompleted: async (review) => {
|
|
546
|
+
await db.runHostCheck.update({
|
|
547
|
+
where: { runId },
|
|
548
|
+
data: {
|
|
549
|
+
completedAt: new Date(),
|
|
550
|
+
...(review ? { verdict: review.verdict, reviewBody: review.body } : {}),
|
|
551
|
+
...(review?.ciPending !== undefined ? { ciPendingAtReview: review.ciPending } : {}),
|
|
552
|
+
},
|
|
553
|
+
});
|
|
493
554
|
},
|
|
494
555
|
// Live, not from the pinned load: the link's current stamp and
|
|
495
556
|
// access, and the agent's CURRENT owner (a make_owner, unlink, or
|
|
@@ -560,195 +621,301 @@ async function executeTrackedRun(runId, providers, db, onText, step) {
|
|
|
560
621
|
message: `No sub-agent is bound to name "${boundName}".`,
|
|
561
622
|
});
|
|
562
623
|
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
// that leaves two children racing on the same task.
|
|
566
|
-
const priorDispatches = await db.run.findMany({ where: { parentRunId: { in: [runId] } } });
|
|
567
|
-
if (priorDispatches.length > 0) {
|
|
568
|
-
return JSON.stringify({
|
|
569
|
-
error: "already_dispatched",
|
|
570
|
-
message: "This run already delegated to a sub-agent; only one delegation is allowed per run.",
|
|
571
|
-
});
|
|
572
|
-
}
|
|
573
|
-
let parsed;
|
|
624
|
+
let release = await delegationGate.acquire();
|
|
625
|
+
let finishSibling = () => { };
|
|
574
626
|
try {
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
args = DelegateArgs.parse(parsed);
|
|
586
|
-
}
|
|
587
|
-
catch (err) {
|
|
588
|
-
if (err instanceof z.ZodError) {
|
|
627
|
+
// At most maxDelegationsPerRun dispatches per run (default one: a
|
|
628
|
+
// classifier deciding plan-vs-implement commits to exactly one child),
|
|
629
|
+
// and never the same child twice, so a retry can't leave two children
|
|
630
|
+
// racing on the same task. Checked and the child row written under the
|
|
631
|
+
// run's delegation gate: delegations started together in one turn
|
|
632
|
+
// (parallelDelegations) are admitted one at a time.
|
|
633
|
+
const limit = loaded.maxDelegationsPerRun ?? 1;
|
|
634
|
+
const priorDispatches = await db.run.findMany({ where: { parentRunId: { in: [runId] } } });
|
|
635
|
+
if (priorDispatches.some((prior) => prior.agentId === edge.childAgentId) ||
|
|
636
|
+
waitingDelegations.has(edge.childAgentId)) {
|
|
589
637
|
return JSON.stringify({
|
|
590
|
-
error: "
|
|
591
|
-
message:
|
|
638
|
+
error: "already_dispatched",
|
|
639
|
+
message: `This run already delegated to "${boundName}"; a run delegates to each sub-agent at most once.`,
|
|
592
640
|
});
|
|
593
641
|
}
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
// Owners are re-read live on every delegation (resource-sharing
|
|
597
|
-
// grants spec §3.4.4, N1): the child must have the parent's current
|
|
598
|
-
// owner, or that owner must still hold execute on the child. A
|
|
599
|
-
// revoked grant or a make_owner transfer stops the very next call.
|
|
600
|
-
const [parentNow, childAgent] = await Promise.all([
|
|
601
|
-
db.agent.findUnique({ where: { id: loaded.agentId }, select: { ownerId: true } }),
|
|
602
|
-
db.agent.findUniqueOrThrow({
|
|
603
|
-
where: { id: edge.childAgentId },
|
|
604
|
-
select: {
|
|
605
|
-
id: true,
|
|
606
|
-
kind: true,
|
|
607
|
-
budgetGroupId: true,
|
|
608
|
-
budgetUsd: true,
|
|
609
|
-
ownerId: true,
|
|
610
|
-
codingProfile: { select: { allowWebhookTaskOverride: true } },
|
|
611
|
-
},
|
|
612
|
-
}),
|
|
613
|
-
]);
|
|
614
|
-
const parentOwnerId = parentNow?.ownerId ?? null;
|
|
615
|
-
const childOwnerId = childAgent.ownerId ?? null;
|
|
616
|
-
if (!parentNow ||
|
|
617
|
-
!(await canDelegate(db, { ownerId: parentOwnerId }, { id: childAgent.id, ownerId: childOwnerId }))) {
|
|
618
|
-
return JSON.stringify({
|
|
619
|
-
error: "subagent_not_authorized",
|
|
620
|
-
message: `The "${boundName}" sub-agent belongs to another owner who has not given this agent's owner execute access to it.`,
|
|
621
|
-
});
|
|
622
|
-
}
|
|
623
|
-
if (parentOwnerId !== childOwnerId) {
|
|
624
|
-
// Across owners the edge carries execute and nothing more: no
|
|
625
|
-
// model-chosen memory grant, no continuation of another run's PR,
|
|
626
|
-
// and no task text the caller couldn't give the child directly --
|
|
627
|
-
// the trigger_agent rule (review I3): a coding child only with its
|
|
628
|
-
// owner's allowWebhookTaskOverride opt-in, a native child never
|
|
629
|
-
// (it runs its owner's fixed prompt; pass an empty task).
|
|
630
|
-
const refusal = (args.grantParentMemoryKeys?.length ?? 0) > 0
|
|
631
|
-
? "grantParentMemoryKeys is only allowed when the sub-agent has the same owner."
|
|
632
|
-
: args.continuePriorRun !== undefined
|
|
633
|
-
? "continuePriorRun is only allowed when the sub-agent has the same owner."
|
|
634
|
-
: childAgent.kind === "coding" && !childAgent.codingProfile?.allowWebhookTaskOverride
|
|
635
|
-
? "This coding sub-agent belongs to another owner and does not accept task text from others (allowWebhookTaskOverride)."
|
|
636
|
-
: childAgent.kind !== "coding" && (args.task.trim() !== "" || args.datastoreRef !== undefined)
|
|
637
|
-
? 'This sub-agent belongs to another owner and runs only its own instructions: delegate with task "" and no datastoreRef.'
|
|
638
|
-
: null;
|
|
639
|
-
if (refusal)
|
|
640
|
-
return JSON.stringify({ error: "cross_owner_not_allowed", message: refusal });
|
|
641
|
-
}
|
|
642
|
-
if (args.continuePriorRun !== undefined && childAgent.kind !== "coding") {
|
|
643
|
-
return JSON.stringify({
|
|
644
|
-
error: "continuation_requires_coding_agent",
|
|
645
|
-
message: "continuePriorRun is only supported when the sub-agent is coding-kind.",
|
|
646
|
-
});
|
|
647
|
-
}
|
|
648
|
-
if (childAgent.kind === "coding") {
|
|
649
|
-
// Coding-kind children need a real Docker container (Codex/Claude
|
|
650
|
-
// Code), which executeRun explicitly refuses to drive — go through
|
|
651
|
-
// the same dispatchRun path webhooks/trigger_agent use instead.
|
|
652
|
-
// datastoreRef/grantParentMemoryKeys have no equivalent inside a
|
|
653
|
-
// coding container's own tool surface (subagent_memory_get/
|
|
654
|
-
// parent_memory_get are native-engine built-ins only), so they're
|
|
655
|
-
// deliberately dropped here rather than silently implying a
|
|
656
|
-
// capability that doesn't exist for this path.
|
|
657
|
-
if (!providers.executor) {
|
|
642
|
+
if (priorDispatches.length < limit && priorDispatches.length + waitingDelegations.size >= limit) {
|
|
643
|
+
// Only a waiting delegation (parallelDelegations) holds the remaining place.
|
|
658
644
|
return JSON.stringify({
|
|
659
|
-
error: "
|
|
660
|
-
message:
|
|
645
|
+
error: "already_dispatched",
|
|
646
|
+
message: `This run reached its limit of ${limit} delegations: ${priorDispatches.length} made and ${waitingDelegations.size} waiting for budget.`,
|
|
661
647
|
});
|
|
662
648
|
}
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
db,
|
|
670
|
-
executor: providers.executor,
|
|
671
|
-
selfDefects: { db, issueTrackers },
|
|
672
|
-
agentId: edge.childAgentId,
|
|
673
|
-
trigger: "subagent",
|
|
674
|
-
codingTask: args.task,
|
|
675
|
-
continuesCodingRunId: args.continuePriorRun,
|
|
676
|
-
parentRunId: runId,
|
|
677
|
-
// The child's result flows back into this run, which the
|
|
678
|
-
// triggerer sees, so the child is visible to them too.
|
|
679
|
-
triggeredById: existingRun.triggeredById,
|
|
680
|
-
awaitExecution: true,
|
|
649
|
+
if (priorDispatches.length >= limit) {
|
|
650
|
+
return JSON.stringify({
|
|
651
|
+
error: "already_dispatched",
|
|
652
|
+
message: limit === 1
|
|
653
|
+
? "This run already delegated to a sub-agent; only one delegation is allowed per run."
|
|
654
|
+
: `This run already made ${limit} delegations, the most this agent allows per run.`,
|
|
681
655
|
});
|
|
682
656
|
}
|
|
657
|
+
let parsed;
|
|
658
|
+
try {
|
|
659
|
+
parsed = JSON.parse(argsJson || "{}");
|
|
660
|
+
}
|
|
683
661
|
catch (err) {
|
|
684
|
-
// A continuation this deployment cannot make (the run id came from
|
|
685
|
-
// a pull request another deployment opened, say) is the model's to
|
|
686
|
-
// report, not a reason to fail the whole run.
|
|
687
|
-
if (!(err instanceof ContinuationRefusedError))
|
|
688
|
-
throw err;
|
|
689
662
|
return JSON.stringify({
|
|
690
|
-
error: "
|
|
691
|
-
message:
|
|
692
|
-
"tell the requester that this wardby deployment cannot continue that pull request's branch.",
|
|
663
|
+
error: "invalid_arguments_json",
|
|
664
|
+
message: err instanceof Error ? err.message : String(err),
|
|
693
665
|
});
|
|
694
666
|
}
|
|
695
|
-
|
|
696
|
-
|
|
667
|
+
let args;
|
|
668
|
+
try {
|
|
669
|
+
args = DelegateArgs.parse(parsed);
|
|
697
670
|
}
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
671
|
+
catch (err) {
|
|
672
|
+
if (err instanceof z.ZodError) {
|
|
673
|
+
return JSON.stringify({
|
|
674
|
+
error: "validation_failed",
|
|
675
|
+
message: err.issues.map((issue) => `${issue.path.join(".") || "input"}: ${issue.message}`).join("; "),
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
throw err;
|
|
679
|
+
}
|
|
680
|
+
// Owners are re-read live on every delegation (resource-sharing
|
|
681
|
+
// grants spec §3.4.4, N1): the child must have the parent's current
|
|
682
|
+
// owner, or that owner must still hold execute on the child. A
|
|
683
|
+
// revoked grant or a make_owner transfer stops the very next call.
|
|
684
|
+
const [parentNow, childAgent] = await Promise.all([
|
|
685
|
+
db.agent.findUnique({ where: { id: loaded.agentId }, select: { ownerId: true } }),
|
|
686
|
+
db.agent.findUniqueOrThrow({
|
|
687
|
+
where: { id: edge.childAgentId },
|
|
688
|
+
select: {
|
|
689
|
+
id: true,
|
|
690
|
+
kind: true,
|
|
691
|
+
budgetGroupId: true,
|
|
692
|
+
budgetUsd: true,
|
|
693
|
+
ownerId: true,
|
|
694
|
+
codingProfile: { select: { allowWebhookTaskOverride: true, timeoutSec: true } },
|
|
695
|
+
},
|
|
696
|
+
}),
|
|
697
|
+
]);
|
|
698
|
+
const parentOwnerId = parentNow?.ownerId ?? null;
|
|
699
|
+
const childOwnerId = childAgent.ownerId ?? null;
|
|
700
|
+
if (!parentNow ||
|
|
701
|
+
!(await canDelegate(db, { ownerId: parentOwnerId }, { id: childAgent.id, ownerId: childOwnerId }))) {
|
|
712
702
|
return JSON.stringify({
|
|
713
|
-
error: "
|
|
714
|
-
|
|
715
|
-
message: "The coding sub-agent did not finish within its queue timeout plus run timeout; it was stopped and produced no result.",
|
|
703
|
+
error: "subagent_not_authorized",
|
|
704
|
+
message: `The "${boundName}" sub-agent belongs to another owner who has not given this agent's owner execute access to it.`,
|
|
716
705
|
});
|
|
717
706
|
}
|
|
718
|
-
if (
|
|
707
|
+
if (parentOwnerId !== childOwnerId) {
|
|
708
|
+
// Across owners the edge carries execute and nothing more: no
|
|
709
|
+
// model-chosen memory grant, no continuation of another run's PR,
|
|
710
|
+
// and no task text the caller couldn't give the child directly --
|
|
711
|
+
// the trigger_agent rule (review I3): a coding child only with its
|
|
712
|
+
// owner's allowWebhookTaskOverride opt-in, a native child never
|
|
713
|
+
// (it runs its owner's fixed prompt; pass an empty task).
|
|
714
|
+
const refusal = (args.grantParentMemoryKeys?.length ?? 0) > 0
|
|
715
|
+
? "grantParentMemoryKeys is only allowed when the sub-agent has the same owner."
|
|
716
|
+
: args.continuePriorRun !== undefined
|
|
717
|
+
? "continuePriorRun is only allowed when the sub-agent has the same owner."
|
|
718
|
+
: childAgent.kind === "coding" && !childAgent.codingProfile?.allowWebhookTaskOverride
|
|
719
|
+
? "This coding sub-agent belongs to another owner and does not accept task text from others (allowWebhookTaskOverride)."
|
|
720
|
+
: childAgent.kind !== "coding" && (args.task.trim() !== "" || args.datastoreRef !== undefined)
|
|
721
|
+
? 'This sub-agent belongs to another owner and runs only its own instructions: delegate with task "" and no datastoreRef.'
|
|
722
|
+
: null;
|
|
723
|
+
if (refusal)
|
|
724
|
+
return JSON.stringify({ error: "cross_owner_not_allowed", message: refusal });
|
|
725
|
+
}
|
|
726
|
+
if (args.continuePriorRun !== undefined && childAgent.kind !== "coding") {
|
|
719
727
|
return JSON.stringify({
|
|
720
|
-
error: "
|
|
721
|
-
|
|
722
|
-
message: "This run was cancelled while waiting for the coding sub-agent; the sub-agent was stopped.",
|
|
728
|
+
error: "continuation_requires_coding_agent",
|
|
729
|
+
message: "continuePriorRun is only supported when the sub-agent is coding-kind.",
|
|
723
730
|
});
|
|
724
731
|
}
|
|
725
|
-
|
|
732
|
+
// parallelDelegations: a child the budget would refuse (run_tree_exhausted or
|
|
733
|
+
// budget_group_exhausted:*) while a sibling still runs waits for a sibling to finish and
|
|
734
|
+
// free its hold, then tries again while siblings remain, up to the child's own wait bound
|
|
735
|
+
// (as for waitForCodingChild below). With no sibling running, it is refused as before.
|
|
736
|
+
if (loaded.parallelDelegations && delegationSiblings.inFlight > 0) {
|
|
737
|
+
const { queueTimeoutSec } = loadCodingConcurrencyConfig();
|
|
738
|
+
const runTimeoutSec = childAgent.kind === "coding" ? (childAgent.codingProfile?.timeoutSec ?? 0) : 0;
|
|
739
|
+
const deadline = Date.now() + (queueTimeoutSec + runTimeoutSec + CODING_CHILD_WAIT_GRACE_SEC) * 1000;
|
|
740
|
+
let waited = false;
|
|
741
|
+
while (delegationSiblings.inFlight > 0) {
|
|
742
|
+
// Taken before the budget read, so a sibling that finishes during it still wakes this wait.
|
|
743
|
+
const finishes = delegationSiblings.finishes;
|
|
744
|
+
const { exhaustedBy } = await effectiveBudgetForRun(db, childAgent, new Date(), runId);
|
|
745
|
+
const remainingMs = deadline - Date.now();
|
|
746
|
+
if (!exhaustedBy || remainingMs <= 0)
|
|
747
|
+
break;
|
|
748
|
+
runnerLog.info({ runId, boundName, exhaustedBy, siblingsInFlight: delegationSiblings.inFlight }, "delegation waiting for a sibling to free budget");
|
|
749
|
+
waitingDelegations.add(edge.childAgentId);
|
|
750
|
+
waited = true;
|
|
751
|
+
release();
|
|
752
|
+
try {
|
|
753
|
+
await delegationSiblings.nextFinish(remainingMs, finishes);
|
|
754
|
+
}
|
|
755
|
+
finally {
|
|
756
|
+
release = await delegationGate.acquire();
|
|
757
|
+
waitingDelegations.delete(edge.childAgentId);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
// The parent may have been stopped while this delegation waited: start nothing for it.
|
|
761
|
+
if (waited) {
|
|
762
|
+
const parent = await db.run.findUnique({ where: { id: runId }, select: { status: true } });
|
|
763
|
+
const parentEnded = !parent || !DRIVABLE.includes(parent.status);
|
|
764
|
+
if (parentEnded ||
|
|
765
|
+
(await db.task.findFirst({ where: { runId, status: "cancelled" }, select: { id: true } })) !== null) {
|
|
766
|
+
return JSON.stringify({
|
|
767
|
+
error: "parent_cancelled",
|
|
768
|
+
message: "This run was cancelled while the sub-agent waited for budget; no sub-agent run was started.",
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
if (childAgent.kind === "coding") {
|
|
774
|
+
// Coding-kind children need a real Docker container (Codex/Claude
|
|
775
|
+
// Code), which executeRun explicitly refuses to drive — go through
|
|
776
|
+
// the same dispatchRun path webhooks/trigger_agent use instead.
|
|
777
|
+
// datastoreRef/grantParentMemoryKeys have no equivalent inside a
|
|
778
|
+
// coding container's own tool surface (subagent_memory_get/
|
|
779
|
+
// parent_memory_get are native-engine built-ins only), so they're
|
|
780
|
+
// deliberately dropped here rather than silently implying a
|
|
781
|
+
// capability that doesn't exist for this path.
|
|
782
|
+
if (!providers.executor) {
|
|
783
|
+
return JSON.stringify({
|
|
784
|
+
error: "coding_dispatch_unavailable",
|
|
785
|
+
message: "This execution context has no Executor wired in, so a coding-kind sub-agent cannot be dispatched.",
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
// dispatchRun reserves the child's budget itself, inside its persist
|
|
789
|
+
// transaction: the agent's budgetUsd tightened by its budget group and
|
|
790
|
+
// by this run tree (parentRunId), or refused when either is spent.
|
|
791
|
+
let dispatched;
|
|
792
|
+
try {
|
|
793
|
+
dispatched = await dispatchRun({
|
|
794
|
+
db,
|
|
795
|
+
executor: providers.executor,
|
|
796
|
+
selfDefects: { db, issueTrackers },
|
|
797
|
+
agentId: edge.childAgentId,
|
|
798
|
+
trigger: "subagent",
|
|
799
|
+
codingTask: args.task,
|
|
800
|
+
continuesCodingRunId: args.continuePriorRun,
|
|
801
|
+
parentRunId: runId,
|
|
802
|
+
// The child's result flows back into this run, which the
|
|
803
|
+
// triggerer sees, so the child is visible to them too.
|
|
804
|
+
triggeredById: existingRun.triggeredById,
|
|
805
|
+
awaitExecution: true,
|
|
806
|
+
// The child row is committed: count it as running (unless refused at dispatch) and
|
|
807
|
+
// admit the next delegation while this one's run starts or queues.
|
|
808
|
+
onPersisted: (persistedRun) => {
|
|
809
|
+
if (persistedRun.status !== "refused" && persistedRun.status !== "failed") {
|
|
810
|
+
finishSibling = delegationSiblings.start();
|
|
811
|
+
}
|
|
812
|
+
release();
|
|
813
|
+
},
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
catch (err) {
|
|
817
|
+
// A continuation this deployment cannot make (the run id came from
|
|
818
|
+
// a pull request another deployment opened, say) is the model's to
|
|
819
|
+
// report, not a reason to fail the whole run.
|
|
820
|
+
if (!(err instanceof ContinuationRefusedError))
|
|
821
|
+
throw err;
|
|
822
|
+
return JSON.stringify({
|
|
823
|
+
error: "continuation_refused",
|
|
824
|
+
message: `${err.message} No sub-agent run was started. Do not open a new pull request in its place: ` +
|
|
825
|
+
"tell the requester that this wardby deployment cannot continue that pull request's branch.",
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
if (!dispatched) {
|
|
829
|
+
return JSON.stringify({ error: "dispatch_failed", message: "The sub-agent run could not be created." });
|
|
830
|
+
}
|
|
831
|
+
const childRunId = dispatched.run.id;
|
|
832
|
+
const startedAt = Date.now();
|
|
833
|
+
runnerLog.info({ runId, childRunId, boundName, kind: "coding" }, "delegation started");
|
|
834
|
+
const codingRun = await db.codingRun.findUnique({
|
|
835
|
+
where: { runId: childRunId },
|
|
836
|
+
select: { timeoutSec: true },
|
|
837
|
+
});
|
|
838
|
+
const { queueTimeoutSec } = loadCodingConcurrencyConfig();
|
|
839
|
+
const waited = await waitForCodingChild({
|
|
840
|
+
db,
|
|
841
|
+
executor: providers.executor,
|
|
842
|
+
childRunId,
|
|
843
|
+
parentRunId: runId,
|
|
844
|
+
boundMs: (queueTimeoutSec + (codingRun?.timeoutSec ?? 0) + CODING_CHILD_WAIT_GRACE_SEC) * 1000,
|
|
845
|
+
});
|
|
846
|
+
finishSibling();
|
|
847
|
+
runnerLog.info({
|
|
848
|
+
runId,
|
|
849
|
+
childRunId,
|
|
850
|
+
boundName,
|
|
851
|
+
outcome: waited.kind === "terminal" ? waited.run.status : waited.kind,
|
|
852
|
+
durationMs: Date.now() - startedAt,
|
|
853
|
+
}, "delegation finished");
|
|
854
|
+
if (waited.kind === "timed_out") {
|
|
855
|
+
return JSON.stringify({
|
|
856
|
+
error: "subagent_wait_timed_out",
|
|
857
|
+
runId: childRunId,
|
|
858
|
+
message: "The coding sub-agent did not finish within its queue timeout plus run timeout; it was stopped and produced no result.",
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
if (waited.kind === "parent_cancelled") {
|
|
862
|
+
return JSON.stringify({
|
|
863
|
+
error: "parent_cancelled",
|
|
864
|
+
runId: childRunId,
|
|
865
|
+
message: "This run was cancelled while waiting for the coding sub-agent; the sub-agent was stopped.",
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
const stored = await db.codingRun
|
|
869
|
+
.findUnique({ where: { runId: childRunId }, select: { result: true } })
|
|
870
|
+
.catch((err) => {
|
|
871
|
+
runnerLog.warn({ err, childRunId }, "could not read the coding sub-run's result");
|
|
872
|
+
return null;
|
|
873
|
+
});
|
|
874
|
+
return codingChildResult(waited.run, stored?.result);
|
|
875
|
+
}
|
|
876
|
+
const taskOverride = args.datastoreRef
|
|
877
|
+
? `${args.task}\n\n[Referenced datastore: name="${args.datastoreRef.name}", key="${args.datastoreRef.key}" — use your datastore tools to read it.]`
|
|
878
|
+
: args.task.trim() !== ""
|
|
879
|
+
? args.task
|
|
880
|
+
: undefined;
|
|
881
|
+
const childRun = await db.run.create({
|
|
882
|
+
data: {
|
|
883
|
+
agentId: edge.childAgentId,
|
|
884
|
+
trigger: "subagent",
|
|
885
|
+
parentRunId: runId,
|
|
886
|
+
grantedParentMemoryKeys: args.grantParentMemoryKeys ?? [],
|
|
887
|
+
taskOverride,
|
|
888
|
+
triggeredById: existingRun.triggeredById,
|
|
889
|
+
},
|
|
890
|
+
});
|
|
891
|
+
finishSibling = delegationSiblings.start();
|
|
892
|
+
// The child row exists: admit the next delegation while this child runs.
|
|
893
|
+
release();
|
|
894
|
+
const startedAt = Date.now();
|
|
895
|
+
runnerLog.info({ runId, childRunId: childRun.id, boundName, kind: "native" }, "delegation started");
|
|
896
|
+
// Runs inline, so no executor beats for it: beat here to keep its
|
|
897
|
+
// budget-group hold live while it runs (and let it lapse if we die).
|
|
898
|
+
const childResult = await withRunHeartbeat(db, childRun.id, () => executeRun(childRun.id, providers, db));
|
|
899
|
+
finishSibling();
|
|
900
|
+
runnerLog.info({
|
|
901
|
+
runId,
|
|
902
|
+
childRunId: childRun.id,
|
|
903
|
+
boundName,
|
|
904
|
+
outcome: childResult.status,
|
|
905
|
+
durationMs: Date.now() - startedAt,
|
|
906
|
+
}, "delegation finished");
|
|
907
|
+
return JSON.stringify({
|
|
908
|
+
status: childResult.status,
|
|
909
|
+
finalText: childResult.finalText,
|
|
910
|
+
costUsd: Number(childResult.costUsd),
|
|
911
|
+
tokensIn: childResult.tokensIn,
|
|
912
|
+
tokensOut: childResult.tokensOut,
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
finally {
|
|
916
|
+
finishSibling();
|
|
917
|
+
release();
|
|
726
918
|
}
|
|
727
|
-
const taskOverride = args.datastoreRef
|
|
728
|
-
? `${args.task}\n\n[Referenced datastore: name="${args.datastoreRef.name}", key="${args.datastoreRef.key}" — use your datastore tools to read it.]`
|
|
729
|
-
: args.task.trim() !== ""
|
|
730
|
-
? args.task
|
|
731
|
-
: undefined;
|
|
732
|
-
const childRun = await db.run.create({
|
|
733
|
-
data: {
|
|
734
|
-
agentId: edge.childAgentId,
|
|
735
|
-
trigger: "subagent",
|
|
736
|
-
parentRunId: runId,
|
|
737
|
-
grantedParentMemoryKeys: args.grantParentMemoryKeys ?? [],
|
|
738
|
-
taskOverride,
|
|
739
|
-
triggeredById: existingRun.triggeredById,
|
|
740
|
-
},
|
|
741
|
-
});
|
|
742
|
-
// Runs inline, so no executor beats for it: beat here to keep its
|
|
743
|
-
// budget-group hold live while it runs (and let it lapse if we die).
|
|
744
|
-
const childResult = await withRunHeartbeat(db, childRun.id, () => executeRun(childRun.id, providers, db));
|
|
745
|
-
return JSON.stringify({
|
|
746
|
-
status: childResult.status,
|
|
747
|
-
finalText: childResult.finalText,
|
|
748
|
-
costUsd: Number(childResult.costUsd),
|
|
749
|
-
tokensIn: childResult.tokensIn,
|
|
750
|
-
tokensOut: childResult.tokensOut,
|
|
751
|
-
});
|
|
752
919
|
}
|
|
753
920
|
const tool = toolsByName.get(name);
|
|
754
921
|
if (!tool) {
|
|
@@ -819,6 +986,10 @@ async function executeTrackedRun(runId, providers, db, onText, step) {
|
|
|
819
986
|
: providers.llm,
|
|
820
987
|
},
|
|
821
988
|
runSandboxTool,
|
|
989
|
+
// A replay recorded before this flag existed has none: its delegations stay sequential.
|
|
990
|
+
...(loaded.parallelDelegations
|
|
991
|
+
? { runsConcurrently: (toolName) => toolName.startsWith(DELEGATE_TOOL_PREFIX) }
|
|
992
|
+
: {}),
|
|
822
993
|
onText,
|
|
823
994
|
onProgress,
|
|
824
995
|
step,
|
|
@@ -838,6 +1009,18 @@ async function executeTrackedRun(runId, providers, db, onText, step) {
|
|
|
838
1009
|
await closeOpenHostCheck(db, finished, reviewHosts);
|
|
839
1010
|
await completeHostStatus(db, finished, reviewHosts);
|
|
840
1011
|
await completeIssueStatus(db, finished, issueTrackers);
|
|
1012
|
+
// After the issue status, so IssuePullRequest rows for this run exist. Bounded and never throws.
|
|
1013
|
+
if (claimed)
|
|
1014
|
+
await updateRelatedPullRequests(db, finished, reviewHosts, issueTrackers);
|
|
1015
|
+
if (claimed && providers.executor && reviewHosts && repoAccess) {
|
|
1016
|
+
await startReviewFixAfterReview(runId, {
|
|
1017
|
+
db,
|
|
1018
|
+
executor: providers.executor,
|
|
1019
|
+
hosts: reviewHosts,
|
|
1020
|
+
repoAccess,
|
|
1021
|
+
issueTrackers,
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
841
1024
|
// Only the call that made the row terminal files, so a run another finalizer (the reconciler) ended is not
|
|
842
1025
|
// filed twice. Bounded and never throws.
|
|
843
1026
|
if (claimed)
|
|
@@ -858,6 +1041,18 @@ async function executeTrackedRun(runId, providers, db, onText, step) {
|
|
|
858
1041
|
await closeOpenHostCheck(db, finished, reviewHosts);
|
|
859
1042
|
await completeHostStatus(db, finished, reviewHosts);
|
|
860
1043
|
await completeIssueStatus(db, finished, issueTrackers);
|
|
1044
|
+
// After the issue status, so IssuePullRequest rows for this run exist. Bounded and never throws.
|
|
1045
|
+
if (claimed)
|
|
1046
|
+
await updateRelatedPullRequests(db, finished, reviewHosts, issueTrackers);
|
|
1047
|
+
if (claimed && providers.executor && reviewHosts && repoAccess) {
|
|
1048
|
+
await startReviewFixAfterReview(runId, {
|
|
1049
|
+
db,
|
|
1050
|
+
executor: providers.executor,
|
|
1051
|
+
hosts: reviewHosts,
|
|
1052
|
+
repoAccess,
|
|
1053
|
+
issueTrackers,
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
861
1056
|
// Only the call that made the row terminal files, so a run another finalizer (the reconciler) ended is not
|
|
862
1057
|
// filed twice. Bounded and never throws.
|
|
863
1058
|
if (claimed)
|