@cat-factory/orchestration 0.109.0 → 0.111.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/dist/modules/execution/ExecutionService.d.ts +6 -1
- package/dist/modules/execution/ExecutionService.d.ts.map +1 -1
- package/dist/modules/execution/ExecutionService.js +20 -0
- package/dist/modules/execution/ExecutionService.js.map +1 -1
- package/dist/modules/execution/PrReviewController.d.ts +78 -0
- package/dist/modules/execution/PrReviewController.d.ts.map +1 -0
- package/dist/modules/execution/PrReviewController.js +203 -0
- package/dist/modules/execution/PrReviewController.js.map +1 -0
- package/dist/modules/execution/RunDispatcher.d.ts +44 -1
- package/dist/modules/execution/RunDispatcher.d.ts.map +1 -1
- package/dist/modules/execution/RunDispatcher.js +178 -3
- package/dist/modules/execution/RunDispatcher.js.map +1 -1
- package/dist/modules/execution/prReview.logic.d.ts +38 -0
- package/dist/modules/execution/prReview.logic.d.ts.map +1 -0
- package/dist/modules/execution/prReview.logic.js +162 -0
- package/dist/modules/execution/prReview.logic.js.map +1 -0
- package/dist/modules/notifications/NotificationService.d.ts.map +1 -1
- package/dist/modules/notifications/NotificationService.js +1 -0
- package/dist/modules/notifications/NotificationService.js.map +1 -1
- package/package.json +11 -11
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { ConflictError } from '@cat-factory/kernel';
|
|
2
|
+
import { PR_REVIEWER_KIND } from '@cat-factory/agents';
|
|
3
|
+
import { coercePrReview } from './prReview.logic.js';
|
|
4
|
+
/** The step kind the PR-review park loop runs on (the read-only reviewer agent). */
|
|
5
|
+
export const PR_REVIEW_STEP_KIND = PR_REVIEWER_KIND;
|
|
6
|
+
/**
|
|
7
|
+
* Drives the human-facing half of the PR deep-review flow. The read-only `pr-reviewer`
|
|
8
|
+
* container agent slices an open PR's diff and returns prioritized findings; rather than
|
|
9
|
+
* finishing the run the moment it returns, {@link recordFindings} (run as the completion
|
|
10
|
+
* interceptor's body) records the sliced findings onto `step.prReview` and PARKS the run for a
|
|
11
|
+
* human to SELECT which findings matter through the dedicated window, then {@link resolve}
|
|
12
|
+
* records the selection and advances past the gate. A clean PR (no findings) — or an unwired
|
|
13
|
+
* reviewer — passes through: `recordFindings` records an empty `done` review and lets the
|
|
14
|
+
* normal completion finish the step.
|
|
15
|
+
*
|
|
16
|
+
* All state rides the run's `pr-reviewer` step (`step.prReview`) — no side table — so it is
|
|
17
|
+
* runtime-symmetric by construction, exactly like the fork-decision flow. {@link resolve}
|
|
18
|
+
* supports three terminal actions: `finish` (advance past the gate), `fix` (re-arm the step so
|
|
19
|
+
* the driver re-dispatches it as the Fixer against the reviewed PR's head branch) and `post`
|
|
20
|
+
* (re-arm so the driver publishes the selected findings as inline PR comments). The `fix`/`post`
|
|
21
|
+
* driver-side work lives in {@link RunDispatcher.handlePrReviewResolution}.
|
|
22
|
+
*/
|
|
23
|
+
export class PrReviewController {
|
|
24
|
+
deps;
|
|
25
|
+
constructor(deps) {
|
|
26
|
+
this.deps = deps;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Record the completed reviewer's findings onto the `pr-reviewer` step and decide the flow.
|
|
30
|
+
* Runs as the completion interceptor's body (short-circuiting `recordStepResult` when it
|
|
31
|
+
* parks). With findings: coerce + record them (`awaiting_selection`), raise the
|
|
32
|
+
* `pr_review_ready` card, and park on a durable decision-wait. With none (clean PR / degenerate
|
|
33
|
+
* output): record an empty `done` review in place and return `null` so the normal
|
|
34
|
+
* finish/advance spine completes the read-only step. Idempotent: a step already carrying a
|
|
35
|
+
* recorded review is left alone — an already-parked review stays parked (no re-coercion,
|
|
36
|
+
* which would re-mint the finding ids the human may be mid-selection on, nor a duplicate
|
|
37
|
+
* card), and a resolved/clean review falls through to the normal spine.
|
|
38
|
+
*/
|
|
39
|
+
async recordFindings(workspaceId, instance, step, output, model, block) {
|
|
40
|
+
// A double-fire of the completion interceptor (a durable retry/replay) must not re-coerce:
|
|
41
|
+
// re-minting finding ids would strand the human's in-flight selection and re-raise the card.
|
|
42
|
+
// Keep an unresolved review parked; leave a resolved/clean one to the normal spine.
|
|
43
|
+
if (step.prReview && step.prReview.status !== 'reviewing') {
|
|
44
|
+
if (step.prReview.status === 'awaiting_selection') {
|
|
45
|
+
return this.deps.stateMachine.parkStepOnDecision(workspaceId, instance, step);
|
|
46
|
+
}
|
|
47
|
+
// This completion is the FIXER the `fix` resolution re-dispatched on this step (it pushed
|
|
48
|
+
// its fixes onto the PR branch): mark the review resolved, then fall through so the normal
|
|
49
|
+
// finish/advance spine completes the step. Any other terminal status (done / posting /
|
|
50
|
+
// skipped) already settled — leave it alone.
|
|
51
|
+
if (step.prReview.status === 'fixing') {
|
|
52
|
+
step.prReview = { ...step.prReview, status: 'done' };
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
const prUrl = block?.taskTypeFields?.prUrl?.trim() || null;
|
|
57
|
+
const { summary, slices, findings } = coercePrReview(output, () => this.deps.idGenerator.next('prs'), () => this.deps.idGenerator.next('prf'));
|
|
58
|
+
if (findings.length === 0) {
|
|
59
|
+
// A clean PR (or an unwired/degenerate reviewer): nothing to select. Record the review in
|
|
60
|
+
// place and let the normal completion spine finish the read-only step (no park).
|
|
61
|
+
step.prReview = {
|
|
62
|
+
status: 'done',
|
|
63
|
+
summary,
|
|
64
|
+
slices,
|
|
65
|
+
findings: [],
|
|
66
|
+
selectedFindingIds: [],
|
|
67
|
+
resolution: 'finish',
|
|
68
|
+
prUrl: prUrl ?? null,
|
|
69
|
+
model: model ?? null,
|
|
70
|
+
};
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
step.prReview = {
|
|
74
|
+
status: 'awaiting_selection',
|
|
75
|
+
summary,
|
|
76
|
+
slices,
|
|
77
|
+
findings,
|
|
78
|
+
selectedFindingIds: [],
|
|
79
|
+
resolution: null,
|
|
80
|
+
prUrl: prUrl ?? null,
|
|
81
|
+
model: model ?? null,
|
|
82
|
+
};
|
|
83
|
+
await this.raisePrReviewReady(workspaceId, instance, block, findings.length, slices.length);
|
|
84
|
+
return this.deps.stateMachine.parkStepOnDecision(workspaceId, instance, step);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Resolve a parked PR review: record the human's curated selection (`findingIds`, validated
|
|
88
|
+
* against the live findings) + the resolution, then act on it.
|
|
89
|
+
*
|
|
90
|
+
* - `finish` — mark the review `done` and advance the run past the gate. Mirrors the review
|
|
91
|
+
* gate's resolved-gate advance: the pure in-memory advance runs inside the CAS, its side
|
|
92
|
+
* effects (finalize / signal / emit) run once after, on the winning snapshot.
|
|
93
|
+
* - `fix` / `post` — RE-ARM the same `pr-reviewer` step (mirroring the fork-decision `choose`
|
|
94
|
+
* phase-B re-dispatch) so the durable driver, on re-entry, either dispatches the Fixer
|
|
95
|
+
* against the reviewed PR's head branch (`fix` → `status: 'fixing'`) or posts the selected
|
|
96
|
+
* findings as inline PR comments (`post` → `status: 'posting'` + the `pendingPrReviewPost`
|
|
97
|
+
* at-most-once marker). Both require ≥1 selected finding. `prReview` survives
|
|
98
|
+
* `resetStepForRerun`, exactly like `forkDecision`.
|
|
99
|
+
*/
|
|
100
|
+
async resolve(workspaceId, executionId, input) {
|
|
101
|
+
const action = input.action ?? 'finish';
|
|
102
|
+
let stepIndex = -1;
|
|
103
|
+
let approvalId;
|
|
104
|
+
let state;
|
|
105
|
+
const instance = await this.deps.stateMachine.mutateInstance(workspaceId, executionId, (inst) => {
|
|
106
|
+
stepIndex = inst.steps.findIndex((s) => s.agentKind === PR_REVIEW_STEP_KIND &&
|
|
107
|
+
s.state === 'waiting_decision' &&
|
|
108
|
+
s.approval?.status === 'pending' &&
|
|
109
|
+
s.prReview?.status === 'awaiting_selection');
|
|
110
|
+
const step = stepIndex === -1 ? undefined : inst.steps[stepIndex];
|
|
111
|
+
if (!step?.approval || !step.prReview) {
|
|
112
|
+
throw new ConflictError('The run is no longer awaiting a PR-review selection');
|
|
113
|
+
}
|
|
114
|
+
const known = new Set(step.prReview.findings?.map((f) => f.id) ?? []);
|
|
115
|
+
const selectedFindingIds = (input.findingIds ?? []).filter((id) => known.has(id));
|
|
116
|
+
if ((action === 'fix' || action === 'post') && selectedFindingIds.length === 0) {
|
|
117
|
+
throw new ConflictError(`Select at least one finding to ${action}.`);
|
|
118
|
+
}
|
|
119
|
+
if (action === 'finish') {
|
|
120
|
+
step.prReview = {
|
|
121
|
+
...step.prReview,
|
|
122
|
+
status: 'done',
|
|
123
|
+
resolution: 'finish',
|
|
124
|
+
selectedFindingIds,
|
|
125
|
+
};
|
|
126
|
+
step.approval.status = 'approved';
|
|
127
|
+
this.deps.stateMachine.advanceRunPastGate(inst, stepIndex);
|
|
128
|
+
state = step.prReview;
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
// fix / post: re-arm the SAME step for a second dispatch (the driver's pr-review
|
|
132
|
+
// resolution handler picks it up by status). Capture the approval id BEFORE the reset
|
|
133
|
+
// (which clears `step.approval`) so we can signal the parked driver afterwards.
|
|
134
|
+
approvalId = step.approval.id;
|
|
135
|
+
step.prReview = {
|
|
136
|
+
...step.prReview,
|
|
137
|
+
status: action === 'fix' ? 'fixing' : 'posting',
|
|
138
|
+
resolution: action,
|
|
139
|
+
selectedFindingIds,
|
|
140
|
+
};
|
|
141
|
+
if (action === 'post')
|
|
142
|
+
step.pendingPrReviewPost = true;
|
|
143
|
+
this.deps.stepGraph.resetStepForRerun(step);
|
|
144
|
+
this.deps.stepGraph.startStep(step);
|
|
145
|
+
if (inst.status === 'blocked')
|
|
146
|
+
inst.status = 'running';
|
|
147
|
+
state = step.prReview;
|
|
148
|
+
});
|
|
149
|
+
await this.deps.stateMachine.clearWaitingNotification(workspaceId, instance);
|
|
150
|
+
if (action === 'finish') {
|
|
151
|
+
if (stepIndex !== -1) {
|
|
152
|
+
await this.deps.stateMachine.settleAdvancedGate(workspaceId, instance, stepIndex);
|
|
153
|
+
}
|
|
154
|
+
return state;
|
|
155
|
+
}
|
|
156
|
+
// fix / post: drive the re-armed step. The block is back in progress; wake the parked
|
|
157
|
+
// driver on the captured approval id so it re-enters and dispatches / posts (mirrors the
|
|
158
|
+
// fork-decision `choose` settle).
|
|
159
|
+
await this.deps.stateMachine.updateBlockProgress(workspaceId, instance, 'in_progress');
|
|
160
|
+
await this.deps.stateMachine.emitInstance(workspaceId, instance);
|
|
161
|
+
if (approvalId) {
|
|
162
|
+
await this.deps.workRunner.signalDecision(workspaceId, instance.id, approvalId, 'approved');
|
|
163
|
+
}
|
|
164
|
+
return state;
|
|
165
|
+
}
|
|
166
|
+
/** The active PR-review state for a run's GET, or null when no `pr-reviewer` step carries one. */
|
|
167
|
+
async getActive(workspaceId, executionId) {
|
|
168
|
+
const instance = await this.deps.executionRepository.get(workspaceId, executionId);
|
|
169
|
+
if (!instance)
|
|
170
|
+
return null;
|
|
171
|
+
return this.activePrReviewStep(instance)?.prReview ?? null;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* The run's "active" PR-review step: prefer the step the run is currently on, else the latest
|
|
175
|
+
* `pr-reviewer` step that carries review state. Mirrors {@link ForkDecisionController.activeForkStep}.
|
|
176
|
+
*/
|
|
177
|
+
activePrReviewStep(instance) {
|
|
178
|
+
const current = instance.steps[instance.currentStep];
|
|
179
|
+
if (current?.agentKind === PR_REVIEW_STEP_KIND && current.prReview)
|
|
180
|
+
return current;
|
|
181
|
+
for (let i = instance.steps.length - 1; i >= 0; i--) {
|
|
182
|
+
const s = instance.steps[i];
|
|
183
|
+
if (s.agentKind === PR_REVIEW_STEP_KIND && s.prReview)
|
|
184
|
+
return s;
|
|
185
|
+
}
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
188
|
+
/** Raise the "select PR-review findings" inbox card when the run parks. */
|
|
189
|
+
async raisePrReviewReady(workspaceId, instance, block, findingCount, sliceCount) {
|
|
190
|
+
if (!this.deps.notificationService || !block)
|
|
191
|
+
return;
|
|
192
|
+
await this.deps.notificationService.raise(workspaceId, {
|
|
193
|
+
type: 'pr_review_ready',
|
|
194
|
+
blockId: block.id,
|
|
195
|
+
executionId: instance.id,
|
|
196
|
+
title: `"${block.title}" — ${findingCount} review finding${findingCount === 1 ? '' : 's'} to triage`,
|
|
197
|
+
body: 'The PR reviewer sliced the pull request and surfaced prioritized findings. Open the ' +
|
|
198
|
+
'task to select which findings to act on.',
|
|
199
|
+
payload: { pipelineName: instance.pipelineName, findingCount, sliceCount },
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
//# sourceMappingURL=PrReviewController.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PrReviewController.js","sourceRoot":"","sources":["../../../src/modules/execution/PrReviewController.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentExecutor, AgentRunContext, AgentRunResult, Block, BlockRepository, BrainstormSession, ClarityReview, Clock, ExecutionEventPublisher, ExecutionInstance, ExecutionRepository, FollowUpsStepState, ForkDecisionStepState, ChooseForkInput, ForkChatRequestInput, GateDefinition, IdGenerator, IssueWritebackProvider, PipelineStep, ProviderCapabilities, ProvisionContext, RequirementConcernLevel, StepGating, RequirementReview, ResolveRunRepoContext, RunInitiatorScope, TicketTrackerProvider, WorkRunner } from '@cat-factory/kernel';
|
|
1
|
+
import type { AgentExecutor, AgentRunContext, AgentRunResult, Block, BlockRepository, BrainstormSession, ClarityReview, Clock, ExecutionEventPublisher, ExecutionInstance, ExecutionRepository, FollowUpsStepState, ForkDecisionStepState, ChooseForkInput, ForkChatRequestInput, PrReviewStepState, ResolvePrReviewInput, GateDefinition, IdGenerator, IssueWritebackProvider, PipelineStep, ProviderCapabilities, ProvisionContext, RequirementConcernLevel, StepGating, RequirementReview, ResolveRunRepoContext, RunInitiatorScope, TicketTrackerProvider, WorkRunner } from '@cat-factory/kernel';
|
|
2
2
|
import type { AgentKindRegistry } from '@cat-factory/agents';
|
|
3
3
|
import type { BugIntakeService, EnvironmentProvisioningService } from '@cat-factory/integrations';
|
|
4
4
|
import { AgentContextBuilder } from './AgentContextBuilder.js';
|
|
@@ -7,6 +7,7 @@ import { HumanTestController } from './HumanTestController.js';
|
|
|
7
7
|
import { MergeResolver } from './MergeResolver.js';
|
|
8
8
|
import { ReviewGateController, type ReviewKind } from './ReviewGateController.js';
|
|
9
9
|
import { ForkDecisionController } from './ForkDecisionController.js';
|
|
10
|
+
import { PrReviewController } from './PrReviewController.js';
|
|
10
11
|
import type { InterviewGateController } from './InterviewGateController.js';
|
|
11
12
|
import { RunStateMachine } from './RunStateMachine.js';
|
|
12
13
|
import { StepGraph } from './StepGraph.js';
|
|
@@ -57,6 +58,7 @@ export interface RunDispatcherDeps {
|
|
|
57
58
|
visualConfirmationController: VisualConfirmationController;
|
|
58
59
|
reviewGate: ReviewGateController;
|
|
59
60
|
forkDecisionController: ForkDecisionController;
|
|
61
|
+
prReviewController: PrReviewController;
|
|
60
62
|
requirementsKind: ReviewKind<RequirementReview>;
|
|
61
63
|
clarityKind: ReviewKind<ClarityReview>;
|
|
62
64
|
requirementsBrainstormKind: ReviewKind<BrainstormSession>;
|
|
@@ -120,6 +122,7 @@ export declare class RunDispatcher {
|
|
|
120
122
|
private readonly visualConfirmationController;
|
|
121
123
|
private readonly reviewGate;
|
|
122
124
|
private readonly forkDecisionController;
|
|
125
|
+
private readonly prReviewController;
|
|
123
126
|
private readonly requirementsKind;
|
|
124
127
|
private readonly clarityKind;
|
|
125
128
|
private readonly requirementsBrainstormKind;
|
|
@@ -678,12 +681,52 @@ export declare class RunDispatcher {
|
|
|
678
681
|
* completion is intercepted. A re-drive while `proposing` re-attaches to the running job.
|
|
679
682
|
*/
|
|
680
683
|
private handleForkDecisionPhase;
|
|
684
|
+
/**
|
|
685
|
+
* Drive a re-armed PR-review step's RESOLUTION (PR 3). The human resolved a parked review with
|
|
686
|
+
* `fix` or `post`; {@link PrReviewController.resolve} re-armed this step and woke the driver.
|
|
687
|
+
* - `fixing`: dispatch the Fixer against the reviewed PR's head branch with the selected
|
|
688
|
+
* findings folded in (parks on the job; its completion marks the review `done`).
|
|
689
|
+
* - `posting`: publish the selected findings as inline PR review comments, then finish the step.
|
|
690
|
+
*/
|
|
691
|
+
private handlePrReviewResolution;
|
|
692
|
+
/**
|
|
693
|
+
* Dispatch the Fixer for a PR-review `fix` resolution. A `review` task carries no own work
|
|
694
|
+
* branch — it reviews an EXISTING PR — so resolve the PR's head branch (via the checkout-free
|
|
695
|
+
* `RepoFiles`) and point the Fixer's clone/push at it: fold a synthetic `pullRequest` + an
|
|
696
|
+
* apriori WORKING branch into the dispatch context so the shared `container-coding` +
|
|
697
|
+
* `clone:{branch:'pr'}` fixer body clones + pushes that branch (no new PR), and hand it the
|
|
698
|
+
* selected findings as a prior output (the same injection point the gate helpers use). Fails
|
|
699
|
+
* the run loudly when the PR branch can't be resolved (nothing to push to) rather than pushing
|
|
700
|
+
* blind. On a replay (jobId already set) it re-attaches without re-resolving.
|
|
701
|
+
*/
|
|
702
|
+
private dispatchPrReviewFixer;
|
|
703
|
+
/**
|
|
704
|
+
* Post a PR-review `post` resolution: publish the human-selected findings as a single advisory
|
|
705
|
+
* (`COMMENT`) inline review on the reviewed PR via the checkout-free `RepoFiles.createReview`,
|
|
706
|
+
* then finish the step. At-most-once: the `pendingPrReviewPost` marker is consumed (cleared +
|
|
707
|
+
* persisted) BEFORE the (side-effecting) post so a Workflows retry can't submit it twice. When
|
|
708
|
+
* no VCS review write is wired (tests / no GitHub) the findings are still recorded and the step
|
|
709
|
+
* finishes — the review pipeline never reaches this without GitHub in practice.
|
|
710
|
+
*
|
|
711
|
+
* If `createReview` itself throws (GitHub rejects the batched review — e.g. a finding anchored
|
|
712
|
+
* to a line outside the PR diff 422s the WHOLE review — or a transient network/5xx error), the
|
|
713
|
+
* marker is already consumed, so a driver retry would NOT re-post. Rather than silently
|
|
714
|
+
* completing the step as `done` with nothing actually posted (the human would believe the
|
|
715
|
+
* comments landed), fail the step LOUDLY so the failure surfaces on the board — mirroring the
|
|
716
|
+
* `fix` resolution's loud preflight failure. `post` is a fallback resolution, so a hard failure
|
|
717
|
+
* is visible and the human can re-run or choose `fix`/`finish` instead.
|
|
718
|
+
*/
|
|
719
|
+
private postPrReview;
|
|
681
720
|
/** Read a run's active implementation-fork decision state, or null. */
|
|
682
721
|
getForkDecision(workspaceId: string, executionId: string): Promise<ForkDecisionStepState | null>;
|
|
683
722
|
/** Resolve the human's implementation-fork choice, re-running the Coder with it folded in. */
|
|
684
723
|
chooseFork(workspaceId: string, executionId: string, input: ChooseForkInput): Promise<ForkDecisionStepState>;
|
|
685
724
|
/** Send a grounded chat message about the surfaced forks (the reply arrives via the stream). */
|
|
686
725
|
forkChat(workspaceId: string, executionId: string, input: ForkChatRequestInput): Promise<ForkDecisionStepState>;
|
|
726
|
+
/** Read a run's active PR deep-review state, or null. */
|
|
727
|
+
getPrReview(workspaceId: string, executionId: string): Promise<PrReviewStepState | null>;
|
|
728
|
+
/** Resolve a parked PR review: record the human's finding selection and advance the run. */
|
|
729
|
+
resolvePrReview(workspaceId: string, executionId: string, input: ResolvePrReviewInput): Promise<PrReviewStepState>;
|
|
687
730
|
/**
|
|
688
731
|
* Locate the run + the Coder step that OWNS the addressed item + the item, throwing 404
|
|
689
732
|
* when absent. Routes by item id (not "the first enabled step") so a pipeline carrying more
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RunDispatcher.d.ts","sourceRoot":"","sources":["../../../src/modules/execution/RunDispatcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EAEb,eAAe,EACf,cAAc,EAEd,KAAK,EACL,eAAe,EAEf,iBAAiB,EACjB,aAAa,EACb,KAAK,EAGL,uBAAuB,EACvB,iBAAiB,EACjB,mBAAmB,EAEnB,kBAAkB,EAElB,qBAAqB,EACrB,eAAe,EACf,oBAAoB,EAEpB,cAAc,EAEd,WAAW,EACX,sBAAsB,EACtB,YAAY,EACZ,oBAAoB,EACpB,gBAAgB,EAGhB,uBAAuB,EACvB,UAAU,EACV,iBAAiB,EACjB,qBAAqB,EAErB,iBAAiB,EAMjB,qBAAqB,EACrB,UAAU,EACX,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"RunDispatcher.d.ts","sourceRoot":"","sources":["../../../src/modules/execution/RunDispatcher.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { ConflictError, DEFAULT_RISK_POLICY, failureKindFromHarnessCause, getErrorMessage, getErrorReason, getProvider, INITIATIVE_ANALYST_AGENT_KIND, INITIATIVE_COMMITTER_AGENT_KIND, INITIATIVE_PLANNER_AGENT_KIND, isAsyncAgentExecutor, NotFoundError, parseLocalModelId, recordGateAttempt, registeredGateFactories, registeredStepResolverFactories, requireProvider, RunContendedError, sameSubtasks, } from '@cat-factory/kernel';
|
|
1
|
+
import { ConflictError, DEFAULT_RISK_POLICY, failureKindFromHarnessCause, FIXER_AGENT_KIND, getErrorMessage, getErrorReason, getProvider, INITIATIVE_ANALYST_AGENT_KIND, INITIATIVE_COMMITTER_AGENT_KIND, INITIATIVE_PLANNER_AGENT_KIND, isAsyncAgentExecutor, NotFoundError, parseLocalModelId, recordGateAttempt, registeredGateFactories, registeredStepResolverFactories, requireProvider, RunContendedError, sameSubtasks, } from '@cat-factory/kernel';
|
|
2
2
|
import { frameProfile, frontendOriginsForService, parseBlueprintService, parseSpecDoc, resolveAprioriWorkingBranch, } from '@cat-factory/contracts';
|
|
3
|
-
import { blueprintPostOp, commitInitiativeTracker, FORK_PROPOSER_KIND, hasTrait, isCompanionKind, isContainerBackedCompanion, INTERVIEW_GATE_TRAIT, moduleSlug, runRepoOps, specPostOp, TASK_ESTIMATOR_AGENT_KIND, } from '@cat-factory/agents';
|
|
3
|
+
import { blueprintPostOp, commitInitiativeTracker, FORK_PROPOSER_KIND, PR_REVIEWER_KIND, hasTrait, isCompanionKind, isContainerBackedCompanion, INTERVIEW_GATE_TRAIT, moduleSlug, runRepoOps, specPostOp, TASK_ESTIMATOR_AGENT_KIND, } from '@cat-factory/agents';
|
|
4
4
|
import { DEPLOYER_AGENT_KIND, isDeployStep } from '@cat-factory/integrations';
|
|
5
5
|
import { BUG_INTAKE_AGENT_KIND } from '../pipelines/pipelineShape.js';
|
|
6
6
|
import { coerceTaskEstimate, summarizeEstimate } from '../estimation/estimate.logic.js';
|
|
@@ -18,6 +18,8 @@ import { HumanTestController } from './HumanTestController.js';
|
|
|
18
18
|
import { MergeResolver } from './MergeResolver.js';
|
|
19
19
|
import { ReviewGateController } from './ReviewGateController.js';
|
|
20
20
|
import { ForkDecisionController } from './ForkDecisionController.js';
|
|
21
|
+
import { PrReviewController, PR_REVIEW_STEP_KIND } from './PrReviewController.js';
|
|
22
|
+
import { buildPrReviewPost, renderPrReviewFixerFeedback } from './prReview.logic.js';
|
|
21
23
|
import { DEFAULT_FORK_MAX_CHAT_TURNS, forkPhasePending, resolveForkTriState, shouldProposeForkAuto, } from './forkDecision.logic.js';
|
|
22
24
|
import { RunStateMachine } from './RunStateMachine.js';
|
|
23
25
|
import { StepGraph } from './StepGraph.js';
|
|
@@ -57,6 +59,19 @@ function parseRepoFromPullUrl(url) {
|
|
|
57
59
|
return undefined;
|
|
58
60
|
return { owner: match[1], repo: match[2] };
|
|
59
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* The PR number a `review` task targets: the explicit `prNumber` field wins, else parse it from
|
|
64
|
+
* the `prUrl` (`…/pull/42` on GitHub, `…/merge_requests/42` on GitLab). Undefined when neither
|
|
65
|
+
* yields one — the PR-review `fix`/`post` resolutions then report the PR unresolvable.
|
|
66
|
+
*/
|
|
67
|
+
function reviewPrNumber(block) {
|
|
68
|
+
const fields = block?.taskTypeFields;
|
|
69
|
+
if (typeof fields?.prNumber === 'number')
|
|
70
|
+
return fields.prNumber;
|
|
71
|
+
const url = fields?.prUrl?.trim();
|
|
72
|
+
const match = url ? /\/(?:pull|merge_requests)\/(\d+)/.exec(url) : null;
|
|
73
|
+
return match ? Number(match[1]) : undefined;
|
|
74
|
+
}
|
|
60
75
|
/**
|
|
61
76
|
* The `peerEnvUrls` provision input for the frame about to be provisioned: a comma-joined set of
|
|
62
77
|
* `slug=url` pairs for every target frame whose env is ALREADY ready this run — so a later
|
|
@@ -118,6 +133,7 @@ export class RunDispatcher {
|
|
|
118
133
|
visualConfirmationController;
|
|
119
134
|
reviewGate;
|
|
120
135
|
forkDecisionController;
|
|
136
|
+
prReviewController;
|
|
121
137
|
requirementsKind;
|
|
122
138
|
clarityKind;
|
|
123
139
|
requirementsBrainstormKind;
|
|
@@ -164,6 +180,7 @@ export class RunDispatcher {
|
|
|
164
180
|
this.visualConfirmationController = deps.visualConfirmationController;
|
|
165
181
|
this.reviewGate = deps.reviewGate;
|
|
166
182
|
this.forkDecisionController = deps.forkDecisionController;
|
|
183
|
+
this.prReviewController = deps.prReviewController;
|
|
167
184
|
this.requirementsKind = deps.requirementsKind;
|
|
168
185
|
this.clarityKind = deps.clarityKind;
|
|
169
186
|
this.requirementsBrainstormKind = deps.requirementsBrainstormKind;
|
|
@@ -212,7 +229,7 @@ export class RunDispatcher {
|
|
|
212
229
|
* what the dispatch chain falls through to; all the deterministic / gate / inline-review
|
|
213
230
|
* kinds are claimed earlier by their own handlers (see {@link buildStepHandlerRegistry}).
|
|
214
231
|
*/
|
|
215
|
-
async handleAgentStep(ctx, dispatchKind) {
|
|
232
|
+
async handleAgentStep(ctx, dispatchKind, augmentContext) {
|
|
216
233
|
const { workspaceId, instance, step, block, isFinalStep, options } = ctx;
|
|
217
234
|
// Async (container) steps don't block: dispatch the job and park. The durable
|
|
218
235
|
// driver polls `pollAgentJob` between sleeps so the run can span far longer
|
|
@@ -225,6 +242,10 @@ export class RunDispatcher {
|
|
|
225
242
|
// job as a HELPER off the coder step (Phase A). The completion still records against the
|
|
226
243
|
// coder step, and the fork-proposal interceptor keys on `step.agentKind` + the fork state.
|
|
227
244
|
const context = await this.contextBuilder.buildContext(workspaceId, instance, step, isFinalStep, block, dispatchKind ? { agentKind: dispatchKind } : undefined);
|
|
245
|
+
// A caller re-dispatching this step under an overriding kind can fold extra context in
|
|
246
|
+
// (e.g. the PR-review `fix` resolution points the Fixer at the reviewed PR's head branch and
|
|
247
|
+
// hands it the selected findings). Runs before pre-ops / dispatch so the job body sees it.
|
|
248
|
+
augmentContext?.(context);
|
|
228
249
|
// A registered custom kind's PRE-ops run deterministic backend repo work before the
|
|
229
250
|
// agent dispatches (e.g. read a baseline `spec/` shard into the prompt). Gated on the
|
|
230
251
|
// step not having dispatched yet so a Workflows replay (jobId already set) doesn't
|
|
@@ -2277,6 +2298,19 @@ export class RunDispatcher {
|
|
|
2277
2298
|
canHandle: ({ step, block }) => forkPhasePending(step, resolveForkTriState(block.agentConfig)),
|
|
2278
2299
|
handle: (ctx) => this.handleForkDecisionPhase(ctx),
|
|
2279
2300
|
},
|
|
2301
|
+
// The PR deep-review RESOLUTION phase (PR 3): after the human resolved a parked review with
|
|
2302
|
+
// `fix` / `post`, `PrReviewController.resolve` re-armed this `pr-reviewer` step and woke the
|
|
2303
|
+
// driver. Claim it by the re-armed status so it re-dispatches as the Fixer (`fixing`) or
|
|
2304
|
+
// posts the selected findings as inline PR comments (`posting`) — never the generic
|
|
2305
|
+
// pr-reviewer clone the fallthrough would run. A `reviewing`/`awaiting_selection`/resolved
|
|
2306
|
+
// step falls through (this handler doesn't claim it). See {@link handlePrReviewResolution}.
|
|
2307
|
+
{
|
|
2308
|
+
kind: 'pr-review-resolution',
|
|
2309
|
+
order: 175,
|
|
2310
|
+
canHandle: ({ step }) => step.agentKind === PR_REVIEW_STEP_KIND &&
|
|
2311
|
+
(step.prReview?.status === 'fixing' || step.prReview?.status === 'posting'),
|
|
2312
|
+
handle: (ctx) => this.handlePrReviewResolution(ctx),
|
|
2313
|
+
},
|
|
2280
2314
|
// The generic container/inline-agent step — claims every step no more-specific handler
|
|
2281
2315
|
// did. Highest order so it always runs last. See {@link handleAgentStep}.
|
|
2282
2316
|
{
|
|
@@ -2347,6 +2381,23 @@ export class RunDispatcher {
|
|
|
2347
2381
|
return this.forkDecisionController.recordProposal(workspaceId, instance, step, proposal, step.model);
|
|
2348
2382
|
},
|
|
2349
2383
|
},
|
|
2384
|
+
// The read-only `pr-reviewer` deep-review job just finished on a review task's step. Its
|
|
2385
|
+
// structured `result.custom` is the sliced, prioritized findings; record them onto the
|
|
2386
|
+
// step's `prReview` and PARK for the human to select which findings matter (≥1 finding),
|
|
2387
|
+
// rather than the normal completion. A clean PR (no findings) returns null and falls
|
|
2388
|
+
// through to the normal finish. Keyed on the `pr-reviewer` step kind.
|
|
2389
|
+
{
|
|
2390
|
+
kind: 'pr-review',
|
|
2391
|
+
order: 106,
|
|
2392
|
+
canIntercept: ({ step }) => step.agentKind === PR_REVIEW_STEP_KIND,
|
|
2393
|
+
intercept: async ({ workspaceId, instance, step, result }) => {
|
|
2394
|
+
const output = this.agentKindRegistry
|
|
2395
|
+
.structuredOutput(PR_REVIEWER_KIND)
|
|
2396
|
+
?.safeParse(result.custom);
|
|
2397
|
+
const block = await this.blockRepository.get(workspaceId, instance.blockId);
|
|
2398
|
+
return this.prReviewController.recordFindings(workspaceId, instance, step, output, result.model ?? step.model, block);
|
|
2399
|
+
},
|
|
2400
|
+
},
|
|
2350
2401
|
// A `tester` step returned a structured report. On a withheld greenlight we do NOT
|
|
2351
2402
|
// finish the step: loop the `fixer` (within the attempt budget) and re-test, mirroring
|
|
2352
2403
|
// the CI gate. A greenlight (or no provider) returns null and falls through to the
|
|
@@ -2883,6 +2934,122 @@ export class RunDispatcher {
|
|
|
2883
2934
|
// Dispatch (or re-attach to) the proposer as a helper off this coder step.
|
|
2884
2935
|
return this.handleAgentStep(ctx, FORK_PROPOSER_KIND);
|
|
2885
2936
|
}
|
|
2937
|
+
/**
|
|
2938
|
+
* Drive a re-armed PR-review step's RESOLUTION (PR 3). The human resolved a parked review with
|
|
2939
|
+
* `fix` or `post`; {@link PrReviewController.resolve} re-armed this step and woke the driver.
|
|
2940
|
+
* - `fixing`: dispatch the Fixer against the reviewed PR's head branch with the selected
|
|
2941
|
+
* findings folded in (parks on the job; its completion marks the review `done`).
|
|
2942
|
+
* - `posting`: publish the selected findings as inline PR review comments, then finish the step.
|
|
2943
|
+
*/
|
|
2944
|
+
async handlePrReviewResolution(ctx) {
|
|
2945
|
+
const { step } = ctx;
|
|
2946
|
+
if (step.prReview?.status === 'posting')
|
|
2947
|
+
return this.postPrReview(ctx);
|
|
2948
|
+
return this.dispatchPrReviewFixer(ctx);
|
|
2949
|
+
}
|
|
2950
|
+
/**
|
|
2951
|
+
* Dispatch the Fixer for a PR-review `fix` resolution. A `review` task carries no own work
|
|
2952
|
+
* branch — it reviews an EXISTING PR — so resolve the PR's head branch (via the checkout-free
|
|
2953
|
+
* `RepoFiles`) and point the Fixer's clone/push at it: fold a synthetic `pullRequest` + an
|
|
2954
|
+
* apriori WORKING branch into the dispatch context so the shared `container-coding` +
|
|
2955
|
+
* `clone:{branch:'pr'}` fixer body clones + pushes that branch (no new PR), and hand it the
|
|
2956
|
+
* selected findings as a prior output (the same injection point the gate helpers use). Fails
|
|
2957
|
+
* the run loudly when the PR branch can't be resolved (nothing to push to) rather than pushing
|
|
2958
|
+
* blind. On a replay (jobId already set) it re-attaches without re-resolving.
|
|
2959
|
+
*/
|
|
2960
|
+
async dispatchPrReviewFixer(ctx) {
|
|
2961
|
+
const { workspaceId, instance, step, block } = ctx;
|
|
2962
|
+
const review = step.prReview;
|
|
2963
|
+
const selected = (review.findings ?? []).filter((f) => review.selectedFindingIds?.includes(f.id));
|
|
2964
|
+
let headRef = null;
|
|
2965
|
+
let prNumber;
|
|
2966
|
+
if (!step.jobId) {
|
|
2967
|
+
prNumber = reviewPrNumber(block);
|
|
2968
|
+
const runRepo = prNumber != null ? await this.resolveRunRepoContext?.(workspaceId, block.id) : null;
|
|
2969
|
+
const repo = runRepo?.repo;
|
|
2970
|
+
headRef =
|
|
2971
|
+
prNumber != null && repo?.pullRequestHeadRef
|
|
2972
|
+
? await this.runInitiatorScope(instance.initiatedBy, () => repo.pullRequestHeadRef(prNumber))
|
|
2973
|
+
: null;
|
|
2974
|
+
if (prNumber == null || !headRef) {
|
|
2975
|
+
return {
|
|
2976
|
+
kind: 'job_failed',
|
|
2977
|
+
failureKind: 'preflight',
|
|
2978
|
+
error: "Can't resolve the reviewed pull request's head branch to push fixes to. The " +
|
|
2979
|
+
"'fix' resolution needs a same-repo pull request on this service's linked repository " +
|
|
2980
|
+
'(a cross-repo or fork PR is not yet supported — post the findings as comments instead).',
|
|
2981
|
+
};
|
|
2982
|
+
}
|
|
2983
|
+
}
|
|
2984
|
+
const resolvedHeadRef = headRef;
|
|
2985
|
+
const resolvedPrNumber = prNumber;
|
|
2986
|
+
return this.handleAgentStep(ctx, FIXER_AGENT_KIND, (context) => {
|
|
2987
|
+
if (resolvedHeadRef && resolvedPrNumber != null) {
|
|
2988
|
+
context.block.pullRequest = {
|
|
2989
|
+
number: resolvedPrNumber,
|
|
2990
|
+
branch: resolvedHeadRef,
|
|
2991
|
+
url: review.prUrl ?? '',
|
|
2992
|
+
};
|
|
2993
|
+
// Build inside the PR head branch (probed, never created) so the work-branch machinery
|
|
2994
|
+
// targets it rather than minting a stray `cat-factory/<blockId>` off base.
|
|
2995
|
+
context.aprioriBranches = [{ name: resolvedHeadRef, mode: 'working' }];
|
|
2996
|
+
}
|
|
2997
|
+
context.priorOutputs = [
|
|
2998
|
+
...context.priorOutputs,
|
|
2999
|
+
{ agentKind: FIXER_AGENT_KIND, output: renderPrReviewFixerFeedback(selected) },
|
|
3000
|
+
];
|
|
3001
|
+
});
|
|
3002
|
+
}
|
|
3003
|
+
/**
|
|
3004
|
+
* Post a PR-review `post` resolution: publish the human-selected findings as a single advisory
|
|
3005
|
+
* (`COMMENT`) inline review on the reviewed PR via the checkout-free `RepoFiles.createReview`,
|
|
3006
|
+
* then finish the step. At-most-once: the `pendingPrReviewPost` marker is consumed (cleared +
|
|
3007
|
+
* persisted) BEFORE the (side-effecting) post so a Workflows retry can't submit it twice. When
|
|
3008
|
+
* no VCS review write is wired (tests / no GitHub) the findings are still recorded and the step
|
|
3009
|
+
* finishes — the review pipeline never reaches this without GitHub in practice.
|
|
3010
|
+
*
|
|
3011
|
+
* If `createReview` itself throws (GitHub rejects the batched review — e.g. a finding anchored
|
|
3012
|
+
* to a line outside the PR diff 422s the WHOLE review — or a transient network/5xx error), the
|
|
3013
|
+
* marker is already consumed, so a driver retry would NOT re-post. Rather than silently
|
|
3014
|
+
* completing the step as `done` with nothing actually posted (the human would believe the
|
|
3015
|
+
* comments landed), fail the step LOUDLY so the failure surfaces on the board — mirroring the
|
|
3016
|
+
* `fix` resolution's loud preflight failure. `post` is a fallback resolution, so a hard failure
|
|
3017
|
+
* is visible and the human can re-run or choose `fix`/`finish` instead.
|
|
3018
|
+
*/
|
|
3019
|
+
async postPrReview(ctx) {
|
|
3020
|
+
const { workspaceId, instance, step, block, isFinalStep } = ctx;
|
|
3021
|
+
const review = step.prReview;
|
|
3022
|
+
const selected = (review.findings ?? []).filter((f) => review.selectedFindingIds?.includes(f.id));
|
|
3023
|
+
let posted = false;
|
|
3024
|
+
if (step.pendingPrReviewPost) {
|
|
3025
|
+
step.pendingPrReviewPost = null;
|
|
3026
|
+
await this.runStateMachine.casPersist(workspaceId, instance);
|
|
3027
|
+
const prNumber = reviewPrNumber(block);
|
|
3028
|
+
const runRepo = prNumber != null ? await this.resolveRunRepoContext?.(workspaceId, block.id) : null;
|
|
3029
|
+
const repo = runRepo?.repo;
|
|
3030
|
+
if (prNumber != null && repo?.createReview) {
|
|
3031
|
+
try {
|
|
3032
|
+
await this.runInitiatorScope(instance.initiatedBy, () => repo.createReview(prNumber, buildPrReviewPost(selected, review.summary)));
|
|
3033
|
+
}
|
|
3034
|
+
catch (error) {
|
|
3035
|
+
return {
|
|
3036
|
+
kind: 'job_failed',
|
|
3037
|
+
failureKind: 'agent',
|
|
3038
|
+
error: `Failed to post the ${selected.length} selected finding` +
|
|
3039
|
+
`${selected.length === 1 ? '' : 's'} as a pull-request review: ${getErrorMessage(error)}. ` +
|
|
3040
|
+
'GitHub rejects a review whose inline comment anchors a line outside the PR diff — ' +
|
|
3041
|
+
"try the 'fix' resolution, or re-run to post again.",
|
|
3042
|
+
};
|
|
3043
|
+
}
|
|
3044
|
+
posted = true;
|
|
3045
|
+
}
|
|
3046
|
+
}
|
|
3047
|
+
step.prReview = { ...review, status: 'done' };
|
|
3048
|
+
const output = posted
|
|
3049
|
+
? `Posted ${selected.length} review comment${selected.length === 1 ? '' : 's'} to the pull request.`
|
|
3050
|
+
: `Recorded ${selected.length} selected finding${selected.length === 1 ? '' : 's'}.`;
|
|
3051
|
+
return this.recordStepResult(workspaceId, instance, step, isFinalStep, { output });
|
|
3052
|
+
}
|
|
2886
3053
|
/** Read a run's active implementation-fork decision state, or null. */
|
|
2887
3054
|
getForkDecision(workspaceId, executionId) {
|
|
2888
3055
|
return this.forkDecisionController.getActive(workspaceId, executionId);
|
|
@@ -2895,6 +3062,14 @@ export class RunDispatcher {
|
|
|
2895
3062
|
forkChat(workspaceId, executionId, input) {
|
|
2896
3063
|
return this.forkDecisionController.chat(workspaceId, executionId, input);
|
|
2897
3064
|
}
|
|
3065
|
+
/** Read a run's active PR deep-review state, or null. */
|
|
3066
|
+
getPrReview(workspaceId, executionId) {
|
|
3067
|
+
return this.prReviewController.getActive(workspaceId, executionId);
|
|
3068
|
+
}
|
|
3069
|
+
/** Resolve a parked PR review: record the human's finding selection and advance the run. */
|
|
3070
|
+
resolvePrReview(workspaceId, executionId, input) {
|
|
3071
|
+
return this.prReviewController.resolve(workspaceId, executionId, input);
|
|
3072
|
+
}
|
|
2898
3073
|
/**
|
|
2899
3074
|
* Locate the run + the Coder step that OWNS the addressed item + the item, throwing 404
|
|
2900
3075
|
* when absent. Routes by item id (not "the first enabled step") so a pipeline carrying more
|