@wardby/cli 0.4.1 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +8 -1
- package/README.md +3 -1
- package/deploy/local/docker-compose.quickstart-coding.yml +27 -0
- package/dist/claude-coding-worker/driver.d.ts +2 -0
- package/dist/claude-coding-worker/driver.js +19 -3
- package/dist/claude-coding-worker/sdk.js +1 -1
- package/dist/cli.js +41 -22
- package/dist/coding/continuation-wording.d.ts +10 -0
- package/dist/coding/continuation-wording.js +13 -0
- package/dist/coding/docker-preflight.d.ts +12 -0
- package/dist/coding/docker-preflight.js +40 -0
- package/dist/coding/local-git.d.ts +22 -0
- package/dist/coding/local-git.js +102 -0
- package/dist/coding/local-repo-status.d.ts +7 -0
- package/dist/coding/local-repo-status.js +56 -0
- package/dist/coding/local-repo.d.ts +17 -0
- package/dist/coding/local-repo.js +65 -0
- package/dist/coding/observability.d.ts +1 -1
- package/dist/coding/observability.js +1 -0
- package/dist/coding/profile.d.ts +6 -0
- package/dist/coding/profile.js +13 -4
- package/dist/coding/protocol.d.ts +32 -7
- package/dist/coding/protocol.js +66 -4
- package/dist/coding-worker/artifact.d.ts +1 -0
- package/dist/coding-worker/errors.js +2 -0
- package/dist/config/providers.d.ts +13 -0
- package/dist/config/providers.js +21 -9
- package/dist/core/attribution.d.ts +13 -0
- package/dist/core/attribution.js +30 -0
- package/dist/core/budget-groups.d.ts +3 -1
- package/dist/core/budget-groups.js +53 -5
- package/dist/core/ci-context.d.ts +8 -0
- package/dist/core/ci-context.js +59 -0
- package/dist/core/delegation-siblings.d.ts +22 -0
- package/dist/core/delegation-siblings.js +40 -0
- package/dist/core/dispatch.d.ts +28 -4
- package/dist/core/dispatch.js +57 -15
- package/dist/core/engine-native.d.ts +16 -0
- package/dist/core/engine-native.js +49 -2
- package/dist/core/host-events.d.ts +25 -2
- package/dist/core/host-events.js +361 -19
- package/dist/core/host-status.d.ts +8 -3
- package/dist/core/host-status.js +38 -7
- package/dist/core/issue-events.d.ts +2 -6
- package/dist/core/issue-events.js +13 -19
- package/dist/core/pull-request-state-sync.d.ts +17 -0
- package/dist/core/pull-request-state-sync.js +53 -0
- package/dist/core/reconciler.d.ts +10 -1
- package/dist/core/reconciler.js +15 -2
- package/dist/core/related-pull-requests.d.ts +95 -0
- package/dist/core/related-pull-requests.js +338 -0
- package/dist/core/repo-access.d.ts +5 -1
- package/dist/core/repo-access.js +17 -1
- package/dist/core/review-fix-ledger.d.ts +20 -0
- package/dist/core/review-fix-ledger.js +41 -0
- package/dist/core/review-fix.d.ts +60 -0
- package/dist/core/review-fix.js +155 -0
- package/dist/core/review-host-tools.d.ts +13 -2
- package/dist/core/review-host-tools.js +72 -11
- package/dist/core/runner.d.ts +2 -2
- package/dist/core/runner.js +379 -184
- package/dist/core/serial-gate.d.ts +11 -0
- package/dist/core/serial-gate.js +19 -0
- package/dist/core/timing.d.ts +7 -0
- package/dist/core/timing.js +7 -0
- package/dist/generated/prisma/browser.d.ts +23 -0
- package/dist/generated/prisma/client.d.ts +23 -0
- package/dist/generated/prisma/commonInputTypes.d.ts +22 -0
- package/dist/generated/prisma/internal/class.d.ts +33 -0
- package/dist/generated/prisma/internal/class.js +4 -4
- package/dist/generated/prisma/internal/prismaNamespace.d.ts +274 -1
- package/dist/generated/prisma/internal/prismaNamespace.js +47 -2
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +48 -0
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +47 -2
- package/dist/generated/prisma/models/Agent.d.ts +422 -1
- package/dist/generated/prisma/models/AgentRepository.d.ts +120 -2
- package/dist/generated/prisma/models/CodingAgentProfile.d.ts +42 -1
- package/dist/generated/prisma/models/CodingRun.d.ts +205 -1
- package/dist/generated/prisma/models/DeferredReview.d.ts +1336 -0
- package/dist/generated/prisma/models/DeferredReview.js +1 -0
- package/dist/generated/prisma/models/LocalPullRequest.d.ts +1384 -0
- package/dist/generated/prisma/models/LocalPullRequest.js +1 -0
- package/dist/generated/prisma/models/LocalReview.d.ts +1315 -0
- package/dist/generated/prisma/models/LocalReview.js +1 -0
- package/dist/generated/prisma/models/Run.d.ts +223 -0
- package/dist/generated/prisma/models/RunHostCheck.d.ts +148 -1
- package/dist/generated/prisma/models.d.ts +3 -0
- package/dist/help-index.json +486 -37
- package/dist/import/neutral-schema.d.ts +2 -2
- package/dist/mcp/auth/access.d.ts +3 -1
- package/dist/mcp/auth/host-account-cli.js +2 -2
- package/dist/mcp/auth/repo-authorization.d.ts +6 -7
- package/dist/mcp/auth/repo-authorization.js +21 -0
- package/dist/mcp/index.js +4 -1
- package/dist/mcp/tools/agents.js +46 -4
- package/dist/mcp/tools/host-accounts.js +2 -2
- package/dist/mcp/tools/repositories.js +71 -12
- package/dist/mcp/tools/runs.js +23 -0
- package/dist/mcp/tools/trigger.js +153 -24
- package/dist/providers/engine/types.d.ts +11 -0
- package/dist/providers/executor/composition.js +12 -7
- package/dist/providers/executor/container.d.ts +40 -3
- package/dist/providers/executor/container.js +140 -9
- package/dist/providers/executor/types.d.ts +1 -1
- package/dist/providers/jobs/fake-kubernetes-api.d.ts +3 -2
- package/dist/providers/jobs/fake-kubernetes-api.js +3 -0
- package/dist/providers/jobs/kubernetes-api.d.ts +3 -1
- package/dist/providers/jobs/kubernetes-client.d.ts +2 -1
- package/dist/providers/jobs/kubernetes-client.js +3 -0
- package/dist/providers/jobs/kubernetes-isolation.d.ts +7 -0
- package/dist/providers/jobs/kubernetes-isolation.js +8 -3
- package/dist/providers/jobs/kubernetes-preflight.d.ts +8 -0
- package/dist/providers/jobs/kubernetes-preflight.js +7 -0
- package/dist/providers/jobs/kubernetes-quota.d.ts +31 -0
- package/dist/providers/jobs/kubernetes-quota.js +110 -0
- package/dist/providers/jobs/kubernetes.d.ts +9 -0
- package/dist/providers/jobs/kubernetes.js +50 -0
- package/dist/providers/jobs/types.d.ts +7 -0
- package/dist/providers/review-host/ci.d.ts +7 -0
- package/dist/providers/review-host/ci.js +20 -0
- package/dist/providers/review-host/github-events.d.ts +7 -0
- package/dist/providers/review-host/github-events.js +31 -7
- package/dist/providers/review-host/github.d.ts +19 -2
- package/dist/providers/review-host/github.js +145 -2
- package/dist/providers/review-host/index.d.ts +8 -2
- package/dist/providers/review-host/index.js +14 -3
- package/dist/providers/review-host/local.d.ts +72 -0
- package/dist/providers/review-host/local.js +434 -0
- package/dist/providers/review-host/types.d.ts +65 -3
- package/dist/providers/review-host/types.js +5 -3
- package/dist/providers/vcs/git.d.ts +10 -20
- package/dist/providers/vcs/git.js +71 -127
- package/dist/providers/vcs/github-remote.d.ts +67 -0
- package/dist/providers/vcs/github-remote.js +201 -0
- package/dist/providers/vcs/github.d.ts +81 -0
- package/dist/providers/vcs/github.js +203 -6
- package/dist/providers/vcs/index.d.ts +16 -1
- package/dist/providers/vcs/index.js +43 -15
- package/dist/providers/vcs/local-remote.d.ts +44 -0
- package/dist/providers/vcs/local-remote.js +108 -0
- package/dist/providers/vcs/remote.d.ts +58 -0
- package/dist/providers/vcs/remote.js +1 -0
- package/dist/providers/vcs/routing.d.ts +33 -0
- package/dist/providers/vcs/routing.js +52 -0
- package/dist/providers/vcs/types.d.ts +12 -1
- package/dist/quickstart/coding-db.d.ts +11 -0
- package/dist/quickstart/coding-db.js +75 -0
- package/dist/quickstart/coding-doctor.d.ts +13 -0
- package/dist/quickstart/coding-doctor.js +88 -0
- package/dist/quickstart/coding-images.d.ts +15 -0
- package/dist/quickstart/coding-images.js +79 -0
- package/dist/quickstart/coding-seed.d.ts +63 -0
- package/dist/quickstart/coding-seed.js +85 -0
- package/dist/quickstart/coding.d.ts +66 -0
- package/dist/quickstart/coding.js +430 -0
- package/dist/quickstart/images.d.ts +19 -0
- package/dist/quickstart/images.js +63 -0
- package/dist/quickstart/index.js +61 -12
- package/dist/quickstart/starter-services.d.ts +45 -0
- package/dist/quickstart/starter-services.js +186 -0
- package/dist/quickstart-images.json +1 -0
- package/dist/serve.js +14 -1
- package/dist/viewer/api-schema.d.ts +176 -0
- package/dist/viewer/api-schema.js +27 -2
- package/dist/viewer/graph.d.ts +7 -2
- package/dist/viewer/graph.js +25 -11
- package/dist/viewer/http.d.ts +7 -1
- package/dist/viewer/http.js +8 -3
- package/dist/viewer/infra.d.ts +2 -0
- package/dist/viewer/infra.js +23 -0
- package/dist/viewer/run-detail.d.ts +2 -1
- package/dist/viewer/run-detail.js +2 -2
- package/docs/agent-recipes.md +127 -5
- package/docs/code-review-agents.md +293 -38
- package/docs/coding-agent-setup.md +141 -2
- package/docs/coding-packages.md +21 -0
- package/docs/coding-services.md +3 -0
- package/docs/coding-worker-isolation.md +56 -20
- package/docs/getting-started-gke.md +24 -0
- package/docs/getting-started.md +196 -10
- package/docs/jira-agents.md +26 -5
- package/docs/security-deployment.md +9 -0
- package/docs/viewer-api.md +21 -2
- package/help/admin-viewer.md +11 -1
- package/help/agent-recipes.md +30 -4
- package/help/builder-agent.md +4 -0
- package/help/code-review-agents.md +82 -2
- package/help/coding-packages.md +12 -1
- package/help/coding-services.md +4 -0
- package/help/cost-attribution.md +3 -1
- package/help/creating-agents.md +3 -0
- package/help/deploy-gke.md +2 -1
- package/help/errors/coding-provider-not-configured.md +60 -0
- package/help/errors/coding-turn-limit.md +25 -0
- package/help/errors/continuation-closed.md +75 -0
- package/help/errors/local-branch-conflict.md +37 -0
- package/help/errors/local-path-invalid.md +30 -0
- package/help/errors/local-ref-invalid.md +36 -0
- package/help/errors/local-ref-not-found.md +41 -0
- package/help/errors/local-repo-not-allowed.md +46 -0
- package/help/errors/local-repo-not-found.md +39 -0
- package/help/errors/model-unavailable.md +7 -6
- package/help/errors/vcs-github-not-configured.md +34 -0
- package/help/getting-started.md +28 -0
- package/help/github.md +22 -5
- package/help/jira.md +9 -2
- package/help/local-repositories.md +182 -0
- package/help/related-pull-requests.md +89 -0
- package/help/review-fix-rounds.md +69 -0
- package/help/troubleshooting/budgets.md +11 -0
- package/help/troubleshooting/coding-workers.md +22 -0
- package/help/troubleshooting/repository-access.md +5 -0
- package/package.json +3 -2
- package/prisma/migrations/20261004100000_delegations_and_coding_turns/migration.sql +12 -0
- package/prisma/migrations/20261005000000_review_fix_rounds/migration.sql +5 -0
- package/prisma/migrations/20261006000000_parallel_delegations/migration.sql +6 -0
- package/prisma/migrations/20261007000000_ci_rereview/migration.sql +4 -0
- package/prisma/migrations/20261008000000_review_after_ci/migration.sql +33 -0
- package/prisma/migrations/20261009000001_coding_run_local_branch/migration.sql +7 -0
- package/prisma/migrations/20261009000002_local_review/migration.sql +50 -0
- package/prisma/schema.prisma +98 -1
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What repo_pr_read tells a reviewer about CI. The note is deterministic and
|
|
3
|
+
* written here, not by the host, so every reviewer prompt gets the same rule:
|
|
4
|
+
* CI on the head commit is authoritative about build and tests; the
|
|
5
|
+
* description's Tests come from Wardby's sandbox, which may have had an
|
|
6
|
+
* incomplete install.
|
|
7
|
+
*/
|
|
8
|
+
import { FAILING_CONCLUSIONS } from "../providers/review-host/ci.js";
|
|
9
|
+
import { DEPENDENCY_INSTALL_INCOMPLETE } from "../providers/vcs/github.js";
|
|
10
|
+
const MAX_NAMED = 10;
|
|
11
|
+
function named(ci, pick) {
|
|
12
|
+
const names = ci.checks.filter(pick).map((c) => c.name);
|
|
13
|
+
const shown = names.slice(0, MAX_NAMED).join(", ");
|
|
14
|
+
return names.length > MAX_NAMED ? `${shown} and ${names.length - MAX_NAMED} more` : shown;
|
|
15
|
+
}
|
|
16
|
+
export function ciNote(ci, sandboxInstallIncomplete) {
|
|
17
|
+
const n = ci.checks.length;
|
|
18
|
+
const plural = `${n} check${n === 1 ? "" : "s"}`;
|
|
19
|
+
const sandbox = sandboxInstallIncomplete
|
|
20
|
+
? ` The description's ${DEPENDENCY_INSTALL_INCOMPLETE.replace(/\.$/, "")} warning means Wardby's sandbox could not install every package, so a failure under its Tests is not evidence against this change.`
|
|
21
|
+
: "";
|
|
22
|
+
const statuses = ci.statusesUnavailable && ci.state !== "unavailable"
|
|
23
|
+
? " Commit statuses could not be read (often because the App lacks Commit statuses: Read), so only check runs are listed."
|
|
24
|
+
: "";
|
|
25
|
+
let main;
|
|
26
|
+
switch (ci.state) {
|
|
27
|
+
case "passing":
|
|
28
|
+
main = `CI passed on this head (${plural}). The description's Tests were run in Wardby's sandbox; where they disagree with CI, CI is authoritative.`;
|
|
29
|
+
break;
|
|
30
|
+
case "failing":
|
|
31
|
+
main = `CI failed on this head: ${named(ci, (c) => c.status === "completed" && FAILING_CONCLUSIONS.has(c.conclusion ?? ""))}. Base build and test findings on these checks, not on the description's Tests.`;
|
|
32
|
+
break;
|
|
33
|
+
case "pending":
|
|
34
|
+
main = `CI is still running on this head (${named(ci, (c) => c.status !== "completed")}). These results are not final: say they were pending; do not report them as passed or failed, and do not use the description's Tests in their place.`;
|
|
35
|
+
break;
|
|
36
|
+
case "inconclusive":
|
|
37
|
+
main = `CI finished without a clear result on this head (${plural}; some were cancelled, stale, or skipped). Do not report the change as failing CI.`;
|
|
38
|
+
break;
|
|
39
|
+
case "none":
|
|
40
|
+
main = "No CI checks or statuses are reported on this head: CI may not be configured, or has not started yet.";
|
|
41
|
+
break;
|
|
42
|
+
case "unavailable":
|
|
43
|
+
main = `CI results could not be read (${ci.unavailableReason ?? "unknown"}). Do not infer CI results from the description.`;
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
return `${main}${statuses}${sandbox}`;
|
|
47
|
+
}
|
|
48
|
+
export function ciForAgent(view) {
|
|
49
|
+
const ci = view.ci ?? {
|
|
50
|
+
headSha: view.headSha,
|
|
51
|
+
state: "unavailable",
|
|
52
|
+
checks: [],
|
|
53
|
+
truncated: false,
|
|
54
|
+
statusesUnavailable: true,
|
|
55
|
+
unavailableReason: "host_unsupported",
|
|
56
|
+
};
|
|
57
|
+
const sandboxInstallIncomplete = (view.body ?? "").includes(`**${DEPENDENCY_INSTALL_INCOMPLETE}**`);
|
|
58
|
+
return { ...ci, sandboxInstallIncomplete, note: ciNote(ci, sandboxInstallIncomplete) };
|
|
59
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The children one run attempt's delegations have running right now, and a
|
|
3
|
+
* signal for when any of them finishes. Used with the delegation gate
|
|
4
|
+
* (serial-gate.ts) under Agent.parallelDelegations: a delegation refused for
|
|
5
|
+
* budget while a sibling still runs waits for that sibling to finish and
|
|
6
|
+
* free its hold, then tries again. A delegation that is waiting is not in
|
|
7
|
+
* flight itself, so two waiting delegations never wait on each other.
|
|
8
|
+
*/
|
|
9
|
+
export interface DelegationSiblings {
|
|
10
|
+
/** Children started and not yet finished. */
|
|
11
|
+
readonly inFlight: number;
|
|
12
|
+
/** How many children have finished so far: pass it to nextFinish to not miss a finish in between. */
|
|
13
|
+
readonly finishes: number;
|
|
14
|
+
/** Counts one child as running. Returns its finish, which is idempotent. */
|
|
15
|
+
start(): () => void;
|
|
16
|
+
/**
|
|
17
|
+
* Resolves true once a child finishes after `sinceFinishes` was read (at once if one already
|
|
18
|
+
* has), or after this call when it is omitted; false after timeoutMs.
|
|
19
|
+
*/
|
|
20
|
+
nextFinish(timeoutMs: number, sinceFinishes?: number): Promise<boolean>;
|
|
21
|
+
}
|
|
22
|
+
export declare function createDelegationSiblings(): DelegationSiblings;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export function createDelegationSiblings() {
|
|
2
|
+
let inFlight = 0;
|
|
3
|
+
let finishes = 0;
|
|
4
|
+
const waiters = new Set();
|
|
5
|
+
return {
|
|
6
|
+
get inFlight() {
|
|
7
|
+
return inFlight;
|
|
8
|
+
},
|
|
9
|
+
get finishes() {
|
|
10
|
+
return finishes;
|
|
11
|
+
},
|
|
12
|
+
start() {
|
|
13
|
+
inFlight += 1;
|
|
14
|
+
let finished = false;
|
|
15
|
+
return () => {
|
|
16
|
+
if (finished)
|
|
17
|
+
return;
|
|
18
|
+
finished = true;
|
|
19
|
+
inFlight -= 1;
|
|
20
|
+
finishes += 1;
|
|
21
|
+
for (const wake of [...waiters])
|
|
22
|
+
wake();
|
|
23
|
+
};
|
|
24
|
+
},
|
|
25
|
+
nextFinish(timeoutMs, sinceFinishes = finishes) {
|
|
26
|
+
if (finishes > sinceFinishes)
|
|
27
|
+
return Promise.resolve(true);
|
|
28
|
+
return new Promise((resolve) => {
|
|
29
|
+
const settle = (woken) => {
|
|
30
|
+
clearTimeout(timer);
|
|
31
|
+
waiters.delete(wake);
|
|
32
|
+
resolve(woken);
|
|
33
|
+
};
|
|
34
|
+
const wake = () => settle(true);
|
|
35
|
+
const timer = setTimeout(() => settle(false), Math.max(0, timeoutMs));
|
|
36
|
+
waiters.add(wake);
|
|
37
|
+
});
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
package/dist/core/dispatch.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { type AttributionIntent } from "./attribution.js";
|
|
|
4
4
|
import { type BudgetConstraint } from "./budget-groups.js";
|
|
5
5
|
import { type SelfDefectSink } from "./self-defects.js";
|
|
6
6
|
export type DispatchDb = Pick<PrismaClient, "agent" | "run" | "runHostCheck" | "codingRun" | "task" | "webhook" | "budgetGroup" | "$transaction" | "$queryRaw">;
|
|
7
|
-
export type DispatchTx = Pick<Prisma.TransactionClient, "agent" | "run" | "runHostCheck" | "runHostStatus" | "runIssueStatus" | "codingRun" | "codingService" | "task" | "webhook" | "budgetGroup" | "resourceGrant" | "workItem" | "runAttribution" | "$queryRaw" | "$executeRawUnsafe">;
|
|
7
|
+
export type DispatchTx = Pick<Prisma.TransactionClient, "agent" | "run" | "runHostCheck" | "runHostStatus" | "runIssueStatus" | "codingRun" | "codingService" | "task" | "webhook" | "budgetGroup" | "resourceGrant" | "workItem" | "localPullRequest" | "runAttribution" | "$queryRaw" | "$executeRawUnsafe">;
|
|
8
8
|
type DispatchAgent = Prisma.AgentGetPayload<{
|
|
9
9
|
include: {
|
|
10
10
|
codingProfile: true;
|
|
@@ -83,6 +83,14 @@ export interface DispatchRunOptions {
|
|
|
83
83
|
* e.g. a sub-agent dispatch tool blocking the parent's own turn.
|
|
84
84
|
*/
|
|
85
85
|
awaitExecution?: boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Called once, right after the persist transaction commits and before the
|
|
88
|
+
* executor starts the run (refused and failed rows included): the moment
|
|
89
|
+
* the run row is visible to other transactions. The runner releases its
|
|
90
|
+
* delegation admission gate here, so a sibling delegation is admitted
|
|
91
|
+
* while this one's coding run is still starting or queued.
|
|
92
|
+
*/
|
|
93
|
+
onPersisted?: (run: Run) => void;
|
|
86
94
|
}
|
|
87
95
|
export interface DispatchRunResult {
|
|
88
96
|
run: Run;
|
|
@@ -107,6 +115,13 @@ export interface DispatchRunResult {
|
|
|
107
115
|
* @internal Exported only for the real-PostgreSQL tests.
|
|
108
116
|
*/
|
|
109
117
|
export declare function isSerializationConflict(err: unknown): boolean;
|
|
118
|
+
/**
|
|
119
|
+
* Prisma's interactive-transaction error (P2028): no pooled connection within
|
|
120
|
+
* `maxWait` ("Unable to start a transaction in the given time"), or the
|
|
121
|
+
* transaction outlived its timeout. Either way nothing was committed, so the
|
|
122
|
+
* caller can retry or queue.
|
|
123
|
+
*/
|
|
124
|
+
export declare function isTransactionUnavailable(err: unknown): boolean;
|
|
110
125
|
/**
|
|
111
126
|
* Record an executor-level failure on a run that never reached a terminal
|
|
112
127
|
* state itself. Deliberately idempotent: the same failure can arrive twice —
|
|
@@ -124,12 +139,13 @@ export declare function markRunFailedFromExecutorError(db: Pick<DispatchDb, "run
|
|
|
124
139
|
export declare function budgetExhaustedError(constraint: BudgetConstraint): string;
|
|
125
140
|
/**
|
|
126
141
|
* Attempts for the persist transaction, and the jittered backoff between
|
|
127
|
-
* them, for serialization failures and
|
|
142
|
+
* them, for serialization failures, deadlocks and transactions that could
|
|
143
|
+
* not start in time (P2028).
|
|
128
144
|
*
|
|
129
145
|
* @internal Exported only for the real-PostgreSQL tests.
|
|
130
146
|
*/
|
|
131
147
|
export declare const PERSIST_ATTEMPTS = 8;
|
|
132
|
-
export type ContinuationRefusal = "unknown_run" | "other_repository" | "no_pull_request";
|
|
148
|
+
export type ContinuationRefusal = "unknown_run" | "other_repository" | "no_pull_request" | "other_owner";
|
|
133
149
|
/** A continuation refused because of the run it names. Its message is fixed text, safe to show the model. */
|
|
134
150
|
export declare class ContinuationRefusedError extends Error {
|
|
135
151
|
readonly reason: ContinuationRefusal;
|
|
@@ -152,8 +168,16 @@ export type ContinuationCheck = {
|
|
|
152
168
|
* run (or its root) is in this database, in that repository, and opened a pull
|
|
153
169
|
* request. A run id read from a pull request can name a run that another
|
|
154
170
|
* deployment sharing the same App made, so check before relying on one.
|
|
171
|
+
*
|
|
172
|
+
* `dispatchingOwnerId`, when given (even `null`), additionally fails closed
|
|
173
|
+
* unless the root's opening agent has that exact owner: a sibling-hint or a
|
|
174
|
+
* PR's own marker names a run id, but nothing stops it naming a run opened by
|
|
175
|
+
* another owner's agent in the same repository, and continuing that run would
|
|
176
|
+
* let this dispatch push to a PR another owner's agent controls. `undefined`
|
|
177
|
+
* skips the check for callers that only ask "does this deployment know this
|
|
178
|
+
* run at all" (a user-facing refusal message, not a dispatch).
|
|
155
179
|
*/
|
|
156
|
-
export declare function checkContinuation(reader: Pick<DispatchTx, "codingRun">, runId: string, repository: string): Promise<ContinuationCheck>;
|
|
180
|
+
export declare function checkContinuation(reader: Pick<DispatchTx, "codingRun">, runId: string, repository: string, dispatchingOwnerId?: string | null): Promise<ContinuationCheck>;
|
|
157
181
|
/**
|
|
158
182
|
* Persists every detached run input in one transaction, then invokes the
|
|
159
183
|
* executor only after commit. A null result means the caller's transactional
|
package/dist/core/dispatch.js
CHANGED
|
@@ -12,6 +12,7 @@ import { DEFAULT_KNOWLEDGE_BUNDLE_PATH } from "../knowledge/concept.js";
|
|
|
12
12
|
import { KNOWLEDGE_INDEX_READ_MAX_BYTES, knowledgeSection } from "../knowledge/note.js";
|
|
13
13
|
import { logger } from "./logger.js";
|
|
14
14
|
import { CodingModelProviderMismatchError, resolveCodingEntry } from "./run-pricing.js";
|
|
15
|
+
import { CONTENDED_TX_MAX_WAIT_MS } from "./timing.js";
|
|
15
16
|
import { fileSelfDefectForRun } from "./self-defects.js";
|
|
16
17
|
const dispatchLog = logger.child({ module: "dispatch" });
|
|
17
18
|
/** SQLSTATEs worth retrying the persist transaction for: serialization failure, deadlock. */
|
|
@@ -52,6 +53,15 @@ export function isSerializationConflict(err) {
|
|
|
52
53
|
}
|
|
53
54
|
return false;
|
|
54
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Prisma's interactive-transaction error (P2028): no pooled connection within
|
|
58
|
+
* `maxWait` ("Unable to start a transaction in the given time"), or the
|
|
59
|
+
* transaction outlived its timeout. Either way nothing was committed, so the
|
|
60
|
+
* caller can retry or queue.
|
|
61
|
+
*/
|
|
62
|
+
export function isTransactionUnavailable(err) {
|
|
63
|
+
return Boolean(err && typeof err === "object" && err.code === "P2028");
|
|
64
|
+
}
|
|
55
65
|
/**
|
|
56
66
|
* Record an executor-level failure on a run that never reached a terminal
|
|
57
67
|
* state itself. Deliberately idempotent: the same failure can arrive twice —
|
|
@@ -115,7 +125,8 @@ async function reserveCodingBudget(tx, agent, now, options) {
|
|
|
115
125
|
}
|
|
116
126
|
/**
|
|
117
127
|
* Attempts for the persist transaction, and the jittered backoff between
|
|
118
|
-
* them, for serialization failures and
|
|
128
|
+
* them, for serialization failures, deadlocks and transactions that could
|
|
129
|
+
* not start in time (P2028).
|
|
119
130
|
*
|
|
120
131
|
* @internal Exported only for the real-PostgreSQL tests.
|
|
121
132
|
*/
|
|
@@ -130,6 +141,7 @@ const CONTINUATION_REFUSALS = {
|
|
|
130
141
|
unknown_run: "Cannot continue an unknown coding run.",
|
|
131
142
|
other_repository: "Cannot continue a coding run from a different repository.",
|
|
132
143
|
no_pull_request: "Cannot continue a coding run that never opened a pull request.",
|
|
144
|
+
other_owner: "Cannot continue a coding run opened by a different owner's agent.",
|
|
133
145
|
};
|
|
134
146
|
/** A continuation refused because of the run it names. Its message is fixed text, safe to show the model. */
|
|
135
147
|
export class ContinuationRefusedError extends Error {
|
|
@@ -140,18 +152,31 @@ export class ContinuationRefusedError extends Error {
|
|
|
140
152
|
this.name = "ContinuationRefusedError";
|
|
141
153
|
}
|
|
142
154
|
}
|
|
155
|
+
/** Carries the root's opening agent's owner along with the row, for the same-owner check below. */
|
|
156
|
+
const CONTINUATION_ROOT_INCLUDE = { run: { select: { agent: { select: { ownerId: true } } } } };
|
|
143
157
|
/**
|
|
144
158
|
* Whether this deployment can continue coding run `runId` in `repository`: the
|
|
145
159
|
* run (or its root) is in this database, in that repository, and opened a pull
|
|
146
160
|
* request. A run id read from a pull request can name a run that another
|
|
147
161
|
* deployment sharing the same App made, so check before relying on one.
|
|
162
|
+
*
|
|
163
|
+
* `dispatchingOwnerId`, when given (even `null`), additionally fails closed
|
|
164
|
+
* unless the root's opening agent has that exact owner: a sibling-hint or a
|
|
165
|
+
* PR's own marker names a run id, but nothing stops it naming a run opened by
|
|
166
|
+
* another owner's agent in the same repository, and continuing that run would
|
|
167
|
+
* let this dispatch push to a PR another owner's agent controls. `undefined`
|
|
168
|
+
* skips the check for callers that only ask "does this deployment know this
|
|
169
|
+
* run at all" (a user-facing refusal message, not a dispatch).
|
|
148
170
|
*/
|
|
149
|
-
export async function checkContinuation(reader, runId, repository) {
|
|
150
|
-
const candidate = await reader.codingRun.findUnique({ where: { runId } });
|
|
171
|
+
export async function checkContinuation(reader, runId, repository, dispatchingOwnerId) {
|
|
172
|
+
const candidate = await reader.codingRun.findUnique({ where: { runId }, include: CONTINUATION_ROOT_INCLUDE });
|
|
151
173
|
if (!candidate)
|
|
152
174
|
return { ok: false, reason: "unknown_run" };
|
|
153
175
|
const root = candidate.rootCodingRunId
|
|
154
|
-
? await reader.codingRun.findUnique({
|
|
176
|
+
? await reader.codingRun.findUnique({
|
|
177
|
+
where: { runId: candidate.rootCodingRunId },
|
|
178
|
+
include: CONTINUATION_ROOT_INCLUDE,
|
|
179
|
+
})
|
|
155
180
|
: candidate;
|
|
156
181
|
if (!root)
|
|
157
182
|
return { ok: false, reason: "unknown_run" };
|
|
@@ -163,6 +188,14 @@ export async function checkContinuation(reader, runId, repository) {
|
|
|
163
188
|
rootResult.pullRequestNumber === undefined) {
|
|
164
189
|
return { ok: false, reason: "no_pull_request" };
|
|
165
190
|
}
|
|
191
|
+
if (dispatchingOwnerId !== undefined) {
|
|
192
|
+
const rootOwnerId = root.run?.agent?.ownerId ?? null;
|
|
193
|
+
// A null owner on either side is never treated as a match: an owner-less
|
|
194
|
+
// agent gets no continuation trust, fail closed either direction.
|
|
195
|
+
if (dispatchingOwnerId === null || rootOwnerId === null || dispatchingOwnerId !== rootOwnerId) {
|
|
196
|
+
return { ok: false, reason: "other_owner" };
|
|
197
|
+
}
|
|
198
|
+
}
|
|
166
199
|
return {
|
|
167
200
|
ok: true,
|
|
168
201
|
root: {
|
|
@@ -177,13 +210,15 @@ export async function checkContinuation(reader, runId, repository) {
|
|
|
177
210
|
* Resolves a coding run's branch: an override, else a continuation root's
|
|
178
211
|
* (see checkContinuation), else the profile's base.
|
|
179
212
|
*/
|
|
180
|
-
async function resolveCodingBranch(reader, options, profile
|
|
213
|
+
async function resolveCodingBranch(reader, options, profile,
|
|
214
|
+
/** The dispatched (continuing) agent's current owner; checkContinuation fails closed against it. */
|
|
215
|
+
ownerId) {
|
|
181
216
|
if (options.continuesCodingRunId === undefined)
|
|
182
217
|
return { baseRef: options.codingBaseRef ?? profile.baseRef };
|
|
183
218
|
if (options.codingBaseRef !== undefined) {
|
|
184
219
|
throw new Error("continuesCodingRunId cannot be combined with codingBaseRef.");
|
|
185
220
|
}
|
|
186
|
-
const check = await checkContinuation(reader, options.continuesCodingRunId, profile.repository);
|
|
221
|
+
const check = await checkContinuation(reader, options.continuesCodingRunId, profile.repository, ownerId);
|
|
187
222
|
if (!check.ok)
|
|
188
223
|
throw new ContinuationRefusedError(check.reason);
|
|
189
224
|
const { root } = check;
|
|
@@ -199,19 +234,22 @@ const UNREADABLE_DECLARATION = {
|
|
|
199
234
|
github_file_too_large: `it is larger than ${MAX_SERVICE_DECLARATION_BYTES} bytes`,
|
|
200
235
|
github_file_not_a_file: "it is not a file",
|
|
201
236
|
github_file_not_utf8: "it is not UTF-8 text",
|
|
237
|
+
local_file_too_large: `it is larger than ${MAX_SERVICE_DECLARATION_BYTES} bytes`,
|
|
238
|
+
local_file_not_a_file: "it is not a file",
|
|
239
|
+
local_file_not_utf8: "it is not UTF-8 text",
|
|
202
240
|
};
|
|
203
241
|
/**
|
|
204
242
|
* The knowledge note's input (docs/knowledge.md): the repository's bundle
|
|
205
243
|
* index at the run's base, read before the transaction like the services
|
|
206
244
|
* declaration. Any failure means no note, never a failed dispatch.
|
|
207
245
|
*/
|
|
208
|
-
async function readKnowledgeIndex(options, profile, resolvedBranch) {
|
|
246
|
+
async function readKnowledgeIndex(options, profile, resolvedBranch, ownerId) {
|
|
209
247
|
const executor = options.executor;
|
|
210
248
|
if (!executor.readCodingRepositoryFile)
|
|
211
249
|
return undefined;
|
|
212
250
|
try {
|
|
213
251
|
// Resolved here, inside the try: a branch problem is the transaction's to report, not the note's.
|
|
214
|
-
const branch = resolvedBranch ?? (await resolveCodingBranch(options.db, options, profile));
|
|
252
|
+
const branch = resolvedBranch ?? (await resolveCodingBranch(options.db, options, profile, ownerId));
|
|
215
253
|
const indexText = await executor.readCodingRepositoryFile({
|
|
216
254
|
repository: profile.repository,
|
|
217
255
|
baseRef: normalizeGitRef(branch.baseRef),
|
|
@@ -235,8 +273,8 @@ async function readKnowledgeIndex(options, profile, resolvedBranch) {
|
|
|
235
273
|
* call, so it runs before the Serializable transaction, which reuses the
|
|
236
274
|
* branch and resolves the services against the catalog and the agent.
|
|
237
275
|
*/
|
|
238
|
-
async function readServiceDeclaration(options, profile) {
|
|
239
|
-
const branch = await resolveCodingBranch(options.db, options, profile);
|
|
276
|
+
async function readServiceDeclaration(options, profile, ownerId) {
|
|
277
|
+
const branch = await resolveCodingBranch(options.db, options, profile, ownerId);
|
|
240
278
|
const read = { repository: profile.repository, profileBaseRef: profile.baseRef, branch };
|
|
241
279
|
const executor = options.executor;
|
|
242
280
|
if (!executor.readCodingServiceDeclaration)
|
|
@@ -312,6 +350,7 @@ export async function dispatchRun(options) {
|
|
|
312
350
|
where: { id: options.agentId },
|
|
313
351
|
select: {
|
|
314
352
|
kind: true,
|
|
353
|
+
ownerId: true,
|
|
315
354
|
budgetGroupId: true,
|
|
316
355
|
codingProfile: { select: { repository: true, baseRef: true, services: true } },
|
|
317
356
|
},
|
|
@@ -322,11 +361,11 @@ export async function dispatchRun(options) {
|
|
|
322
361
|
const declarationRead = preview?.kind === "coding" &&
|
|
323
362
|
preview.codingProfile &&
|
|
324
363
|
parseAllowedServiceNames(preview.codingProfile.services).length > 0
|
|
325
|
-
? await readServiceDeclaration(options, preview.codingProfile)
|
|
364
|
+
? await readServiceDeclaration(options, preview.codingProfile, preview.ownerId ?? null)
|
|
326
365
|
: undefined;
|
|
327
366
|
// The knowledge note's input: the repository's bundle index at the run's base (a network call).
|
|
328
367
|
const knowledgeIndex = preview?.kind === "coding" && preview.codingProfile && options.executor.readCodingRepositoryFile
|
|
329
|
-
? await readKnowledgeIndex(options, preview.codingProfile, declarationRead?.branch)
|
|
368
|
+
? await readKnowledgeIndex(options, preview.codingProfile, declarationRead?.branch, preview.ownerId ?? null)
|
|
330
369
|
: undefined;
|
|
331
370
|
const persistOnce = () => options.db.$transaction(async (tx) => {
|
|
332
371
|
if (lockGroups)
|
|
@@ -438,7 +477,7 @@ export async function dispatchRun(options) {
|
|
|
438
477
|
const task = composeCodingTask(agent.systemPrompt, request, servicesInstructionNote(services), knowledgeIndex ? knowledgeSection(knowledgeIndex) : undefined);
|
|
439
478
|
const budgetUsd = codingBudget.budgetUsd;
|
|
440
479
|
// Resolved once: before the transaction when the declaration was read from it.
|
|
441
|
-
const branch = declarationRead?.branch ?? (await resolveCodingBranch(tx, options, agent.codingProfile));
|
|
480
|
+
const branch = declarationRead?.branch ?? (await resolveCodingBranch(tx, options, agent.codingProfile, agent.ownerId));
|
|
442
481
|
const { baseRef, continuationOf, rootCodingRunId } = branch;
|
|
443
482
|
const headRef = branch.headRef ?? `wardby/run-${run.id}`;
|
|
444
483
|
const input = CodingTaskInputSchema.parse({
|
|
@@ -485,6 +524,7 @@ export async function dispatchRun(options) {
|
|
|
485
524
|
budgetReservedUsd: budgetUsd,
|
|
486
525
|
rootCodingRunId,
|
|
487
526
|
workspaceDiskMb: agent.codingProfile.workspaceDiskMb,
|
|
527
|
+
maxTurns: agent.codingProfile.maxTurns,
|
|
488
528
|
// Fixed here so a run's tracing never changes mid-run.
|
|
489
529
|
debugTrace: agent.codingProfile.debugTraceUntil != null &&
|
|
490
530
|
now.getTime() < agent.codingProfile.debugTraceUntil.getTime(),
|
|
@@ -510,7 +550,7 @@ export async function dispatchRun(options) {
|
|
|
510
550
|
})
|
|
511
551
|
: undefined;
|
|
512
552
|
return { run, task };
|
|
513
|
-
}, { isolationLevel: "Serializable" });
|
|
553
|
+
}, { isolationLevel: "Serializable", maxWait: CONTENDED_TX_MAX_WAIT_MS });
|
|
514
554
|
let persisted = null;
|
|
515
555
|
for (let attempt = 0; attempt < PERSIST_ATTEMPTS; attempt += 1) {
|
|
516
556
|
try {
|
|
@@ -518,13 +558,15 @@ export async function dispatchRun(options) {
|
|
|
518
558
|
break;
|
|
519
559
|
}
|
|
520
560
|
catch (err) {
|
|
521
|
-
|
|
561
|
+
const retryable = isSerializationConflict(err) || isTransactionUnavailable(err);
|
|
562
|
+
if (!retryable || attempt === PERSIST_ATTEMPTS - 1)
|
|
522
563
|
throw err;
|
|
523
564
|
await new Promise((resolve) => setTimeout(resolve, retryDelayMs(attempt)));
|
|
524
565
|
}
|
|
525
566
|
}
|
|
526
567
|
if (!persisted)
|
|
527
568
|
return null;
|
|
569
|
+
options.onPersisted?.(persisted.run);
|
|
528
570
|
if (persisted.run.status === "refused" || persisted.run.status === "failed") {
|
|
529
571
|
dispatchLog.warn({ runId: persisted.run.id, agentId: options.agentId, reason: persisted.run.error }, `coding run ${persisted.run.status} at dispatch`);
|
|
530
572
|
return persisted;
|
|
@@ -17,6 +17,22 @@ export declare class NativeEngine implements Engine {
|
|
|
17
17
|
*/
|
|
18
18
|
private windDown;
|
|
19
19
|
private finish;
|
|
20
|
+
/**
|
|
21
|
+
* One turn's tool calls, results in call order. The calls are split into
|
|
22
|
+
* runs of consecutive calls that `ctx.runsConcurrently` accepts; each such
|
|
23
|
+
* run of two or more is one concurrent batch, and every other call
|
|
24
|
+
* (including a lone accepted one) runs alone. Groups run strictly in call
|
|
25
|
+
* order and nothing is reordered: `[a, x, b]` runs a, then x, then b.
|
|
26
|
+
* Without `runsConcurrently` every call runs alone, exactly as before.
|
|
27
|
+
*
|
|
28
|
+
* Durability (DBOS): a step's id is assigned when the step starts, so every
|
|
29
|
+
* step here is started in an order fixed by the turn's recorded tool calls
|
|
30
|
+
* alone, never by timing. A batch's steps are started in one synchronous
|
|
31
|
+
* pass in call order and awaited with allSettled, not Promise.all, so a
|
|
32
|
+
* rejection never leaves sibling steps running unawaited (DBOS guidance);
|
|
33
|
+
* once all have settled, the first rejection by call order is rethrown.
|
|
34
|
+
*/
|
|
35
|
+
private runToolCalls;
|
|
20
36
|
/**
|
|
21
37
|
* Streams one model turn, applying the mid-stream cutoff against the
|
|
22
38
|
* *cumulative* running total (this extends Phase 1's single-turn
|
|
@@ -117,13 +117,13 @@ export class NativeEngine {
|
|
|
117
117
|
assistantMessage.toolCalls = turn.toolCalls;
|
|
118
118
|
messages.push(assistantMessage);
|
|
119
119
|
if (turn.toolCalls.length > 0) {
|
|
120
|
+
const results = await this.runToolCalls(ctx, step, turns, turn.toolCalls);
|
|
120
121
|
for (const [index, toolCall] of turn.toolCalls.entries()) {
|
|
121
|
-
const resultJson = await step(`turn:${turns}:tool:${index}`, () => ctx.runSandboxTool(toolCall.name, toolCall.argsJson));
|
|
122
122
|
messages.push({
|
|
123
123
|
role: "tool",
|
|
124
124
|
toolCallId: toolCall.id,
|
|
125
125
|
name: toolCall.name,
|
|
126
|
-
content: wrapUntrustedToolOutput(
|
|
126
|
+
content: wrapUntrustedToolOutput(results[index]),
|
|
127
127
|
});
|
|
128
128
|
}
|
|
129
129
|
continue;
|
|
@@ -162,6 +162,53 @@ export class NativeEngine {
|
|
|
162
162
|
finish(status, finalText, turns, usage, error) {
|
|
163
163
|
return { status, finalText, turns, usage, ...(error ? { error } : {}) };
|
|
164
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* One turn's tool calls, results in call order. The calls are split into
|
|
167
|
+
* runs of consecutive calls that `ctx.runsConcurrently` accepts; each such
|
|
168
|
+
* run of two or more is one concurrent batch, and every other call
|
|
169
|
+
* (including a lone accepted one) runs alone. Groups run strictly in call
|
|
170
|
+
* order and nothing is reordered: `[a, x, b]` runs a, then x, then b.
|
|
171
|
+
* Without `runsConcurrently` every call runs alone, exactly as before.
|
|
172
|
+
*
|
|
173
|
+
* Durability (DBOS): a step's id is assigned when the step starts, so every
|
|
174
|
+
* step here is started in an order fixed by the turn's recorded tool calls
|
|
175
|
+
* alone, never by timing. A batch's steps are started in one synchronous
|
|
176
|
+
* pass in call order and awaited with allSettled, not Promise.all, so a
|
|
177
|
+
* rejection never leaves sibling steps running unawaited (DBOS guidance);
|
|
178
|
+
* once all have settled, the first rejection by call order is rethrown.
|
|
179
|
+
*/
|
|
180
|
+
async runToolCalls(ctx, step, turn, toolCalls) {
|
|
181
|
+
const runCall = (index) => step(`turn:${turn}:tool:${index}`, () => ctx.runSandboxTool(toolCalls[index].name, toolCalls[index].argsJson));
|
|
182
|
+
const concurrent = ctx.runsConcurrently;
|
|
183
|
+
const results = new Array(toolCalls.length);
|
|
184
|
+
let index = 0;
|
|
185
|
+
while (index < toolCalls.length) {
|
|
186
|
+
let end = index;
|
|
187
|
+
if (concurrent) {
|
|
188
|
+
while (end < toolCalls.length && concurrent(toolCalls[end].name))
|
|
189
|
+
end += 1;
|
|
190
|
+
}
|
|
191
|
+
if (end - index < 2) {
|
|
192
|
+
results[index] = await runCall(index);
|
|
193
|
+
index += 1;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
const batch = Array.from({ length: end - index }, (_, offset) => index + offset);
|
|
197
|
+
engineLog.info({ turn, toolCallIds: batch.map((i) => toolCalls[i].id) }, "running tool calls concurrently");
|
|
198
|
+
const settled = await Promise.allSettled(batch.map((i) => runCall(i)));
|
|
199
|
+
const rejections = settled.filter((outcome) => outcome.status === "rejected");
|
|
200
|
+
for (const extra of rejections.slice(1)) {
|
|
201
|
+
engineLog.warn({ turn, err: extra.reason }, "concurrent tool call also failed");
|
|
202
|
+
}
|
|
203
|
+
if (rejections.length > 0)
|
|
204
|
+
throw rejections[0].reason;
|
|
205
|
+
settled.forEach((outcome, position) => {
|
|
206
|
+
results[batch[position]] = outcome.value;
|
|
207
|
+
});
|
|
208
|
+
index = end;
|
|
209
|
+
}
|
|
210
|
+
return results;
|
|
211
|
+
}
|
|
165
212
|
/**
|
|
166
213
|
* Streams one model turn, applying the mid-stream cutoff against the
|
|
167
214
|
* *cumulative* running total (this extends Phase 1's single-turn
|
|
@@ -9,10 +9,11 @@ import type { PrismaClient } from "#prisma";
|
|
|
9
9
|
import type { Executor } from "../providers/executor/types.js";
|
|
10
10
|
import type { IssueTrackerRegistry } from "../providers/issue-tracker/types.js";
|
|
11
11
|
import type { HostEvent, ReviewHostRegistry } from "../providers/review-host/types.js";
|
|
12
|
+
import { type OpenSibling } from "./related-pull-requests.js";
|
|
12
13
|
import { type RepoAccessGate } from "./repo-access.js";
|
|
13
14
|
import { type DriftScope } from "../knowledge/relevance.js";
|
|
14
15
|
export type { HostEvent };
|
|
15
|
-
export type HostEventDb = Pick<PrismaClient, "agentRepository" | "agent" | "run" | "runHostCheck" | "runHostStatus" | "codingRun" | "task" | "webhook" | "budgetGroup" | "issuePullRequest" | "workItem" | "runAttribution" | "agentIssueProject" | "$transaction" | "$queryRaw">;
|
|
16
|
+
export type HostEventDb = Pick<PrismaClient, "agentRepository" | "deferredReview" | "agent" | "run" | "runHostCheck" | "runHostStatus" | "codingRun" | "task" | "webhook" | "budgetGroup" | "issuePullRequest" | "workItem" | "runAttribution" | "agentIssueProject" | "$transaction" | "$queryRaw">;
|
|
16
17
|
export interface RouteHostEventDeps {
|
|
17
18
|
db: HostEventDb;
|
|
18
19
|
executor: Executor;
|
|
@@ -24,6 +25,14 @@ export interface RouteHostEventDeps {
|
|
|
24
25
|
/** Issue trackers for moving/commenting on the issue a closed PR was opened for; absent → pr_closed is ignored. */
|
|
25
26
|
issueTrackers?: IssueTrackerRegistry;
|
|
26
27
|
}
|
|
28
|
+
/** What starting a review needs: everything routing needs except the mention handle. */
|
|
29
|
+
export type ReviewStartDeps = Omit<RouteHostEventDeps, "mentionHandle">;
|
|
30
|
+
/** A deferred review whose head's CI has not finished after this long is started anyway (by the reconciler sweep). */
|
|
31
|
+
export declare const DEFERRED_REVIEW_MAX_WAIT_MS: number;
|
|
32
|
+
/** A deferred review row older than this is dropped without starting. */
|
|
33
|
+
export declare const DEFERRED_REVIEW_MAX_AGE_MS: number;
|
|
34
|
+
/** Upper bound on deferred reviews the sweep starts per pass, so a backlog drains over several passes. */
|
|
35
|
+
export declare const DEFERRED_REVIEW_BATCH = 50;
|
|
27
36
|
export interface RouteResult {
|
|
28
37
|
runIds: string[];
|
|
29
38
|
/** Cosmetic work to do after the webhook response is sent (reactions, status comments). */
|
|
@@ -51,6 +60,12 @@ export declare function mergeTaskText(event: PushEvent, scope: DriftScope | null
|
|
|
51
60
|
type MentionEvent = Extract<HostEvent, {
|
|
52
61
|
kind: "mention";
|
|
53
62
|
}>;
|
|
63
|
+
/** The exact phrase every continuation hint uses (a prompt may match it). */
|
|
64
|
+
export declare function continuePriorRunPhrase(runId: string): string;
|
|
65
|
+
/** The continuation hint a router agent follows to continue a PR's branch instead of opening a new one. */
|
|
66
|
+
export declare function continuationHint(prNumber: number, runId: string): string;
|
|
67
|
+
/** One hint per open sibling (stored rows only; links built, ids re-validated), then the shared guidance. */
|
|
68
|
+
export declare function siblingContinuationHints(siblings: readonly OpenSibling[]): string | undefined;
|
|
54
69
|
/**
|
|
55
70
|
* The mention agent's task text. Deterministic so a prompt can parse it.
|
|
56
71
|
*
|
|
@@ -67,6 +82,14 @@ type MentionEvent = Extract<HostEvent, {
|
|
|
67
82
|
* never in the system prompt. composeTaskOverride stores both in the one
|
|
68
83
|
* taskOverride column.
|
|
69
84
|
*/
|
|
70
|
-
export declare function mentionTaskText(event: MentionEvent): string;
|
|
85
|
+
export declare function mentionTaskText(event: MentionEvent, siblings?: readonly OpenSibling[]): string;
|
|
71
86
|
export declare function unknownPriorRunBody(priorRunId: string): string;
|
|
87
|
+
/**
|
|
88
|
+
* The reconciler's fallback for deferred reviews: drops rows older than
|
|
89
|
+
* DEFERRED_REVIEW_MAX_AGE_MS, and starts (regardless of CI) those waiting
|
|
90
|
+
* longer than DEFERRED_REVIEW_MAX_WAIT_MS, at most DEFERRED_REVIEW_BATCH per
|
|
91
|
+
* pass, oldest first. Covers CI that never reported, ran very long, or whose
|
|
92
|
+
* completion webhook was missed. Never throws; returns the runs started.
|
|
93
|
+
*/
|
|
94
|
+
export declare function startDeferredReviews(deps: ReviewStartDeps, now?: Date): Promise<string[]>;
|
|
72
95
|
export declare function routeHostEvent(event: HostEvent, deps: RouteHostEventDeps): Promise<RouteResult>;
|