@urateam/core 0.1.50 → 0.1.52
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/__tests__/agentic-deep-review-provider.test.js +6 -1
- package/dist/__tests__/agentic-deep-review-provider.test.js.map +1 -1
- package/dist/__tests__/audit-session-events.test.d.ts +2 -0
- package/dist/__tests__/audit-session-events.test.d.ts.map +1 -0
- package/dist/__tests__/audit-session-events.test.js +39 -0
- package/dist/__tests__/audit-session-events.test.js.map +1 -0
- package/dist/__tests__/db-migration-agent-session-id.test.d.ts +2 -0
- package/dist/__tests__/db-migration-agent-session-id.test.d.ts.map +1 -0
- package/dist/__tests__/db-migration-agent-session-id.test.js +35 -0
- package/dist/__tests__/db-migration-agent-session-id.test.js.map +1 -0
- package/dist/__tests__/deep-review-resume.test.d.ts +18 -0
- package/dist/__tests__/deep-review-resume.test.d.ts.map +1 -0
- package/dist/__tests__/deep-review-resume.test.js +90 -0
- package/dist/__tests__/deep-review-resume.test.js.map +1 -0
- package/dist/__tests__/execute-stage-session-opts.test.d.ts +17 -0
- package/dist/__tests__/execute-stage-session-opts.test.d.ts.map +1 -0
- package/dist/__tests__/execute-stage-session-opts.test.js +188 -0
- package/dist/__tests__/execute-stage-session-opts.test.js.map +1 -0
- package/dist/__tests__/ralph-handoff-suppression.test.d.ts +2 -0
- package/dist/__tests__/ralph-handoff-suppression.test.d.ts.map +1 -0
- package/dist/__tests__/ralph-handoff-suppression.test.js +37 -0
- package/dist/__tests__/ralph-handoff-suppression.test.js.map +1 -0
- package/dist/__tests__/session-lazy-creation.test.d.ts +17 -0
- package/dist/__tests__/session-lazy-creation.test.d.ts.map +1 -0
- package/dist/__tests__/session-lazy-creation.test.js +146 -0
- package/dist/__tests__/session-lazy-creation.test.js.map +1 -0
- package/dist/__tests__/session-policy.test.d.ts +2 -0
- package/dist/__tests__/session-policy.test.d.ts.map +1 -0
- package/dist/__tests__/session-policy.test.js +44 -0
- package/dist/__tests__/session-policy.test.js.map +1 -0
- package/dist/__tests__/session-resume-fallback.test.d.ts +22 -0
- package/dist/__tests__/session-resume-fallback.test.d.ts.map +1 -0
- package/dist/__tests__/session-resume-fallback.test.js +145 -0
- package/dist/__tests__/session-resume-fallback.test.js.map +1 -0
- package/dist/__tests__/session-resume-flag.test.d.ts +2 -0
- package/dist/__tests__/session-resume-flag.test.d.ts.map +1 -0
- package/dist/__tests__/session-resume-flag.test.js +87 -0
- package/dist/__tests__/session-resume-flag.test.js.map +1 -0
- package/dist/__tests__/session-store.test.d.ts +2 -0
- package/dist/__tests__/session-store.test.d.ts.map +1 -0
- package/dist/__tests__/session-store.test.js +44 -0
- package/dist/__tests__/session-store.test.js.map +1 -0
- package/dist/__tests__/session-volume-check.test.d.ts +2 -0
- package/dist/__tests__/session-volume-check.test.d.ts.map +1 -0
- package/dist/__tests__/session-volume-check.test.js +28 -0
- package/dist/__tests__/session-volume-check.test.js.map +1 -0
- package/dist/__tests__/validate-run-mode.test.d.ts +2 -0
- package/dist/__tests__/validate-run-mode.test.d.ts.map +1 -0
- package/dist/__tests__/validate-run-mode.test.js +102 -0
- package/dist/__tests__/validate-run-mode.test.js.map +1 -0
- package/dist/__tests__/zombie-age-default.test.d.ts +2 -0
- package/dist/__tests__/zombie-age-default.test.d.ts.map +1 -0
- package/dist/__tests__/zombie-age-default.test.js +24 -0
- package/dist/__tests__/zombie-age-default.test.js.map +1 -0
- package/dist/audit/events.d.ts +45 -0
- package/dist/audit/events.d.ts.map +1 -1
- package/dist/audit/events.js +79 -0
- package/dist/audit/events.js.map +1 -1
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +4 -1
- package/dist/db/client.js.map +1 -1
- package/dist/db/schema.d.ts +19 -0
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/schema.js +2 -0
- package/dist/db/schema.js.map +1 -1
- package/dist/executor/deep-review.d.ts +28 -1
- package/dist/executor/deep-review.d.ts.map +1 -1
- package/dist/executor/deep-review.js +113 -9
- package/dist/executor/deep-review.js.map +1 -1
- package/dist/executor/executor.d.ts +33 -0
- package/dist/executor/executor.d.ts.map +1 -1
- package/dist/executor/executor.js +104 -4
- package/dist/executor/executor.js.map +1 -1
- package/dist/executor/index.d.ts +7 -0
- package/dist/executor/index.d.ts.map +1 -1
- package/dist/executor/index.js +7 -0
- package/dist/executor/index.js.map +1 -1
- package/dist/executor/prompt/assembler.d.ts +7 -1
- package/dist/executor/prompt/assembler.d.ts.map +1 -1
- package/dist/executor/prompt/assembler.js +9 -3
- package/dist/executor/prompt/assembler.js.map +1 -1
- package/dist/executor/prompt/templates.d.ts +25 -6
- package/dist/executor/prompt/templates.d.ts.map +1 -1
- package/dist/executor/prompt/templates.js +21 -12
- package/dist/executor/prompt/templates.js.map +1 -1
- package/dist/executor/review/agentic-deep-review.d.ts.map +1 -1
- package/dist/executor/review/agentic-deep-review.js +15 -1
- package/dist/executor/review/agentic-deep-review.js.map +1 -1
- package/dist/executor/review/review-provider.d.ts +21 -0
- package/dist/executor/review/review-provider.d.ts.map +1 -1
- package/dist/executor/review/review-provider.js.map +1 -1
- package/dist/executor/session-policy.d.ts +38 -0
- package/dist/executor/session-policy.d.ts.map +1 -0
- package/dist/executor/session-policy.js +66 -0
- package/dist/executor/session-policy.js.map +1 -0
- package/dist/executor/session-store.d.ts +32 -0
- package/dist/executor/session-store.d.ts.map +1 -0
- package/dist/executor/session-store.js +38 -0
- package/dist/executor/session-store.js.map +1 -0
- package/dist/executor/validate.d.ts +20 -1
- package/dist/executor/validate.d.ts.map +1 -1
- package/dist/executor/validate.js +16 -1
- package/dist/executor/validate.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/pipeline/runner.d.ts.map +1 -1
- package/dist/pipeline/runner.js +139 -6
- package/dist/pipeline/runner.js.map +1 -1
- package/dist/pipeline/session-volume-check.d.ts +38 -0
- package/dist/pipeline/session-volume-check.d.ts.map +1 -0
- package/dist/pipeline/session-volume-check.js +45 -0
- package/dist/pipeline/session-volume-check.js.map +1 -0
- package/dist/pm/scheduler.d.ts +14 -0
- package/dist/pm/scheduler.d.ts.map +1 -1
- package/dist/pm/scheduler.js +9 -5
- package/dist/pm/scheduler.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +16 -0
- package/dist/server.js.map +1 -1
- package/dist/types.d.ts +8 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +19 -0
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
package/dist/pipeline/runner.js
CHANGED
|
@@ -33,11 +33,13 @@ import { withBranchLock, createBranchLockAdapter, } from "./distributed-lock.js"
|
|
|
33
33
|
import { upsertActiveWork, removeActiveWork, checkFileOverlap, getModifiedFiles, } from "../pm/coordination.js";
|
|
34
34
|
import { eq, and, or, sql, gte, lt, inArray } from "drizzle-orm";
|
|
35
35
|
import { nanoid } from "nanoid";
|
|
36
|
+
import { randomUUID } from "node:crypto";
|
|
37
|
+
import { isAgentSessionResumeEnabled, isAlwaysFreshStage } from "../executor/session-policy.js";
|
|
36
38
|
import { createLogger, runWithLogContext } from "../logger.js";
|
|
37
39
|
import { isTransientError, MAX_TRANSIENT_RETRIES } from "./error-classifier.js";
|
|
38
40
|
import { evaluatePolicyGates } from "../policy/evaluate.js";
|
|
39
41
|
import { buildReviewerRequest, verifyApprovalsReceived } from "../policy/index.js";
|
|
40
|
-
import { logAuditEvent, policyReviewersRequestedEvent, reviewFanoutFallbackUsedEvent, pipelineScratchFilesBlockedEvent, pipelineTypecheckFailedEvent, pipelineSpecVsImplFailedEvent, pipelineAutoDeepReviewBumpedEvent, pmTriageQualityScoreEvent, } from "../audit/index.js";
|
|
42
|
+
import { logAuditEvent, policyReviewersRequestedEvent, reviewFanoutFallbackUsedEvent, pipelineScratchFilesBlockedEvent, pipelineTypecheckFailedEvent, pipelineSpecVsImplFailedEvent, pipelineAutoDeepReviewBumpedEvent, pmTriageQualityScoreEvent, agentSessionCreatedEvent, } from "../audit/index.js";
|
|
41
43
|
import { computeAffectedFilesPredictionQuality, } from "../pm/triage-prediction-quality.js";
|
|
42
44
|
import { getTriageResult } from "../pm/triage-results-store.js";
|
|
43
45
|
import { matchesAnyPattern } from "../util/glob.js";
|
|
@@ -113,6 +115,11 @@ export class PipelineRunner {
|
|
|
113
115
|
return;
|
|
114
116
|
}
|
|
115
117
|
const runId = nanoid();
|
|
118
|
+
// BEC-227: mint a per-run agent session UUID when the flag is on. The
|
|
119
|
+
// first resumable stage opens its SDK session with this id; downstream
|
|
120
|
+
// stages reuse it via `resume:`. Read env at call time so flipping the
|
|
121
|
+
// var takes effect on the next pipeline run without a daemon restart.
|
|
122
|
+
const agentSessionId = isAgentSessionResumeEnabled() ? randomUUID() : null;
|
|
116
123
|
const branch = branchName(issue.identifier, sanitizedIssue.slug);
|
|
117
124
|
const db = this.db;
|
|
118
125
|
const runLog = createLogger({ component: "PipelineRunner", runId, issueId: issue.identifier });
|
|
@@ -128,8 +135,16 @@ export class PipelineRunner {
|
|
|
128
135
|
branch,
|
|
129
136
|
status: "queued",
|
|
130
137
|
linearTeamId,
|
|
138
|
+
agentSessionId, // null when flag is off; UUID when BEC-227 is enabled
|
|
131
139
|
});
|
|
132
140
|
runLog.info({ branch }, "run queued");
|
|
141
|
+
if (agentSessionId !== null) {
|
|
142
|
+
void logAuditEvent(db, agentSessionCreatedEvent({
|
|
143
|
+
runId,
|
|
144
|
+
issueId: issue.identifier,
|
|
145
|
+
sessionId: agentSessionId,
|
|
146
|
+
}));
|
|
147
|
+
}
|
|
133
148
|
const run = this.buildPipelineRun(runId, issue, pipelineKey, repoConfig, branch);
|
|
134
149
|
// Set activeRuns BEFORE enqueue so abort() can cancel while queued
|
|
135
150
|
this.activeRuns.set(issue.identifier, runId);
|
|
@@ -139,7 +154,7 @@ export class PipelineRunner {
|
|
|
139
154
|
return;
|
|
140
155
|
runLog.info("executing pipeline");
|
|
141
156
|
try {
|
|
142
|
-
await runWithLogContext({ runId, issueId: issue.identifier }, () => this.executePipeline(runId, run, pipelineConfig, repoConfig, sanitizedIssue, branch));
|
|
157
|
+
await runWithLogContext({ runId, issueId: issue.identifier }, () => this.executePipeline(runId, run, pipelineConfig, repoConfig, sanitizedIssue, branch, undefined, agentSessionId));
|
|
143
158
|
}
|
|
144
159
|
catch (err) {
|
|
145
160
|
runLog.error({ err }, "pipeline execution failed");
|
|
@@ -300,6 +315,10 @@ export class PipelineRunner {
|
|
|
300
315
|
startStageIndex: pausedRun.currentStageIndex,
|
|
301
316
|
worktreePath,
|
|
302
317
|
initialHandoff: handoff ?? undefined,
|
|
318
|
+
// BEC-227 — carry the per-run SDK session id across the
|
|
319
|
+
// await-approval pause so the post-resume stages keep
|
|
320
|
+
// talking to the same transcript.
|
|
321
|
+
agentSessionId: pausedRun.agentSessionId ?? null,
|
|
303
322
|
}));
|
|
304
323
|
}
|
|
305
324
|
catch (err) {
|
|
@@ -468,9 +487,45 @@ export class PipelineRunner {
|
|
|
468
487
|
log.warn({ err: e }, "failed to inject CLAUDE.md — agent will run without it");
|
|
469
488
|
}
|
|
470
489
|
}
|
|
471
|
-
async executePipeline(runId, run, config, repoConfig, sanitizedIssue, branch, resumeOptions
|
|
490
|
+
async executePipeline(runId, run, config, repoConfig, sanitizedIssue, branch, resumeOptions,
|
|
491
|
+
/**
|
|
492
|
+
* BEC-227 — per-run SDK session UUID minted at start(). Null when the
|
|
493
|
+
* `URATEAM_ENABLE_AGENT_SESSION_RESUME` flag is off. On the resume path
|
|
494
|
+
* this is ignored in favour of `resumeOptions.agentSessionId`.
|
|
495
|
+
*/
|
|
496
|
+
agentSessionId = null) {
|
|
472
497
|
const db = this.db;
|
|
473
498
|
const runLog = createLogger({ component: "PipelineRunner", runId, issueId: run.issueId });
|
|
499
|
+
// BEC-227 — resolve the session id from the resume path (preferred) or
|
|
500
|
+
// the start() path. Tracks whether the first resumable stage in this run
|
|
501
|
+
// has already opened the SDK session so subsequent stages switch from
|
|
502
|
+
// `sessionId` (create) to `resume` (reuse). The flag stays scoped to a
|
|
503
|
+
// single executePipeline() invocation: on resume after await-approval the
|
|
504
|
+
// SDK session created in the pre-pause run has already been initiated, so
|
|
505
|
+
// we start this second invocation with `hasInitiatedSession = true` —
|
|
506
|
+
// every resumable stage uses the `resume:` shape, never re-creates the
|
|
507
|
+
// session.
|
|
508
|
+
const runAgentSessionId = resumeOptions
|
|
509
|
+
? resumeOptions.agentSessionId
|
|
510
|
+
: agentSessionId;
|
|
511
|
+
let hasInitiatedSession = !!resumeOptions;
|
|
512
|
+
/**
|
|
513
|
+
* Returns `true` for the first non-fresh stage in this run, flips the
|
|
514
|
+
* `hasInitiatedSession` flag as a side effect. Always-fresh stages
|
|
515
|
+
* (validate, ralph-check) never count as the first resumable stage —
|
|
516
|
+
* they don't take a session id. Returns false when the flag is off
|
|
517
|
+
* (`runAgentSessionId === null`).
|
|
518
|
+
*/
|
|
519
|
+
const claimFirstResumableStage = (stage) => {
|
|
520
|
+
if (runAgentSessionId === null)
|
|
521
|
+
return false;
|
|
522
|
+
if (hasInitiatedSession)
|
|
523
|
+
return false;
|
|
524
|
+
if (isAlwaysFreshStage(stage))
|
|
525
|
+
return false;
|
|
526
|
+
hasInitiatedSession = true;
|
|
527
|
+
return true;
|
|
528
|
+
};
|
|
474
529
|
let handoff;
|
|
475
530
|
let worktreePath;
|
|
476
531
|
let devcontainerSession;
|
|
@@ -655,6 +710,7 @@ export class PipelineRunner {
|
|
|
655
710
|
}, "file overlap detected with other active runs — proceeding with awareness");
|
|
656
711
|
}
|
|
657
712
|
}
|
|
713
|
+
const isFirstResumableStageForMain = claimFirstResumableStage(stageType);
|
|
658
714
|
let result = await executeStage({
|
|
659
715
|
runId,
|
|
660
716
|
issueId: sanitizedIssue.id,
|
|
@@ -667,6 +723,8 @@ export class PipelineRunner {
|
|
|
667
723
|
techStack,
|
|
668
724
|
devcontainerSession,
|
|
669
725
|
stageModels: config.stageModels,
|
|
726
|
+
agentSessionId: runAgentSessionId,
|
|
727
|
+
isFirstResumableStage: isFirstResumableStageForMain,
|
|
670
728
|
});
|
|
671
729
|
// Operator stop check (cancel path) — the AbortController inside the
|
|
672
730
|
// executor surfaces as result.status === "cancelled". Don't try to use
|
|
@@ -735,6 +793,17 @@ export class PipelineRunner {
|
|
|
735
793
|
}
|
|
736
794
|
runLog.info({ iteration, gaps: check.gaps.length, suggestions: check.suggestions.length }, "RALPH: gaps found, re-running implement");
|
|
737
795
|
const ralphContext = buildRalphContext(iteration, check, handoffResult.artifact);
|
|
796
|
+
// BEC-227 — RALPH re-implement runs inside the implement stage's
|
|
797
|
+
// main invocation, which already claimed the first-resumable slot
|
|
798
|
+
// if eligible. Re-claim is a no-op (returns false) so this call
|
|
799
|
+
// takes the `resume` shape when the flag is on.
|
|
800
|
+
const isFirstResumableStageForRalph = claimFirstResumableStage(stageType);
|
|
801
|
+
// BEC-227 — when this RALPH iteration is a resumed call (session
|
|
802
|
+
// active AND not the first resumable stage), the prior handoff is
|
|
803
|
+
// already in the agent's resumed SDK transcript. Suppress the
|
|
804
|
+
// `<previous-stage-context>` block to avoid duplicating that
|
|
805
|
+
// context as prompt input tokens.
|
|
806
|
+
const suppressRalphHandoff = runAgentSessionId !== null && !isFirstResumableStageForRalph;
|
|
738
807
|
result = await executeStage({
|
|
739
808
|
runId,
|
|
740
809
|
issueId: sanitizedIssue.id,
|
|
@@ -748,6 +817,9 @@ export class PipelineRunner {
|
|
|
748
817
|
devcontainerSession,
|
|
749
818
|
ralphContext,
|
|
750
819
|
stageModels: config.stageModels,
|
|
820
|
+
agentSessionId: runAgentSessionId,
|
|
821
|
+
isFirstResumableStage: isFirstResumableStageForRalph,
|
|
822
|
+
suppressHandoff: suppressRalphHandoff,
|
|
751
823
|
});
|
|
752
824
|
// Accumulate each RALPH iteration's tokens
|
|
753
825
|
run.totalInputTokens += result.inputTokens;
|
|
@@ -778,6 +850,9 @@ export class PipelineRunner {
|
|
|
778
850
|
}, "stage failed — restarting (urateam#121)");
|
|
779
851
|
run.stageRetries ??= {};
|
|
780
852
|
run.stageRetries[stageType] = (run.stageRetries[stageType] ?? 0) + 1;
|
|
853
|
+
// BEC-227 — retry of an already-attempted stage. If the main
|
|
854
|
+
// invocation above claimed the session, this is a no-op.
|
|
855
|
+
const isFirstResumableStageForRetry = claimFirstResumableStage(stageType);
|
|
781
856
|
result = await executeStage({
|
|
782
857
|
runId,
|
|
783
858
|
issueId: sanitizedIssue.id,
|
|
@@ -790,6 +865,8 @@ export class PipelineRunner {
|
|
|
790
865
|
techStack,
|
|
791
866
|
devcontainerSession,
|
|
792
867
|
stageModels: config.stageModels,
|
|
868
|
+
agentSessionId: runAgentSessionId,
|
|
869
|
+
isFirstResumableStage: isFirstResumableStageForRetry,
|
|
793
870
|
});
|
|
794
871
|
if (result.status === "completed")
|
|
795
872
|
break;
|
|
@@ -844,10 +921,19 @@ export class PipelineRunner {
|
|
|
844
921
|
!isLastStage) {
|
|
845
922
|
runLog.info({ stage }, "validating handoff");
|
|
846
923
|
let validationPassed = false;
|
|
924
|
+
// BEC-227 — runMode tells the validator whether this stage is the
|
|
925
|
+
// first resumable stage of the run (paranoia check still runs),
|
|
926
|
+
// a subsequent resumable stage (skip — agent inherits context),
|
|
927
|
+
// or a non-session run (validate as before).
|
|
928
|
+
const mainStageRunMode = runAgentSessionId === null
|
|
929
|
+
? "fallback"
|
|
930
|
+
: isFirstResumableStageForMain
|
|
931
|
+
? "first-resumed"
|
|
932
|
+
: "resumed";
|
|
847
933
|
const validation = await validateHandoff(stage, {
|
|
848
934
|
artifact: result.handoffArtifact,
|
|
849
935
|
structured: result.handoffIsStructured ?? false,
|
|
850
|
-
}, sanitizedIssue, repoConfig, worktreePath);
|
|
936
|
+
}, sanitizedIssue, repoConfig, worktreePath, mainStageRunMode);
|
|
851
937
|
validationPassed = validation.valid;
|
|
852
938
|
let lastValidationIssues = validation.issues;
|
|
853
939
|
if (!validationPassed) {
|
|
@@ -855,6 +941,9 @@ export class PipelineRunner {
|
|
|
855
941
|
// Retry with the last known-good handoff (not the failed artifact)
|
|
856
942
|
if (config.retry.strategy === "fix-and-retry") {
|
|
857
943
|
for (let attempt = 0; attempt < config.retry.maxAttempts; attempt++) {
|
|
944
|
+
// BEC-227 — validation-failed retry. Same stage as the main
|
|
945
|
+
// invocation; claim is a no-op when the session is open.
|
|
946
|
+
const isFirstResumableStageForValRetry = claimFirstResumableStage(stageType);
|
|
858
947
|
result = await executeStage({
|
|
859
948
|
runId,
|
|
860
949
|
issueId: sanitizedIssue.id,
|
|
@@ -867,12 +956,22 @@ export class PipelineRunner {
|
|
|
867
956
|
techStack,
|
|
868
957
|
devcontainerSession,
|
|
869
958
|
stageModels: config.stageModels,
|
|
959
|
+
agentSessionId: runAgentSessionId,
|
|
960
|
+
isFirstResumableStage: isFirstResumableStageForValRetry,
|
|
870
961
|
});
|
|
871
962
|
if (result.status === "completed" && result.handoffArtifact) {
|
|
963
|
+
// BEC-227 — `isFirstResumableStageForValRetry` reflects the
|
|
964
|
+
// retry execution that just produced `result`. Same formula
|
|
965
|
+
// as the main-loop validation.
|
|
966
|
+
const valRetryRunMode = runAgentSessionId === null
|
|
967
|
+
? "fallback"
|
|
968
|
+
: isFirstResumableStageForValRetry
|
|
969
|
+
? "first-resumed"
|
|
970
|
+
: "resumed";
|
|
872
971
|
const retryValidation = await validateHandoff(stage, {
|
|
873
972
|
artifact: result.handoffArtifact,
|
|
874
973
|
structured: result.handoffIsStructured ?? false,
|
|
875
|
-
}, sanitizedIssue, repoConfig, worktreePath);
|
|
974
|
+
}, sanitizedIssue, repoConfig, worktreePath, valRetryRunMode);
|
|
876
975
|
if (retryValidation.valid) {
|
|
877
976
|
validationPassed = true;
|
|
878
977
|
break;
|
|
@@ -971,6 +1070,7 @@ export class PipelineRunner {
|
|
|
971
1070
|
runLog.info({ rfIteration, maxIterations: reviewFixIterations, blockingFindings: blockingCount }, "review-fix loop: re-running stages to address blocking findings");
|
|
972
1071
|
for (const fixStage of fixStages) {
|
|
973
1072
|
runLog.info({ stage: fixStage, rfIteration }, "review-fix: executing stage");
|
|
1073
|
+
const isFirstResumableStageForFix = claimFirstResumableStage(fixStage);
|
|
974
1074
|
const fixResult = await executeStage({
|
|
975
1075
|
runId,
|
|
976
1076
|
issueId: sanitizedIssue.id,
|
|
@@ -983,6 +1083,8 @@ export class PipelineRunner {
|
|
|
983
1083
|
techStack,
|
|
984
1084
|
devcontainerSession,
|
|
985
1085
|
stageModels: config.stageModels,
|
|
1086
|
+
agentSessionId: runAgentSessionId,
|
|
1087
|
+
isFirstResumableStage: isFirstResumableStageForFix,
|
|
986
1088
|
});
|
|
987
1089
|
// BEC-134: track latest review stage_run id for fanout persistence.
|
|
988
1090
|
if (fixStage === "review") {
|
|
@@ -1007,7 +1109,16 @@ export class PipelineRunner {
|
|
|
1007
1109
|
}
|
|
1008
1110
|
// Validate handoff (same as main stage loop)
|
|
1009
1111
|
if (fixResult.handoffArtifact && config.validateHandoffs === true) {
|
|
1010
|
-
|
|
1112
|
+
// BEC-227 — runMode derived from the review-fix executeStage
|
|
1113
|
+
// claim. The review-fix loop runs after the main stage loop,
|
|
1114
|
+
// so `isFirstResumableStageForFix` is virtually always false
|
|
1115
|
+
// (session already initiated). Formula is the same.
|
|
1116
|
+
const fixStageRunMode = runAgentSessionId === null
|
|
1117
|
+
? "fallback"
|
|
1118
|
+
: isFirstResumableStageForFix
|
|
1119
|
+
? "first-resumed"
|
|
1120
|
+
: "resumed";
|
|
1121
|
+
const validation = await validateHandoff(fixStage, { artifact: fixResult.handoffArtifact, structured: fixResult.handoffIsStructured ?? false }, sanitizedIssue, repoConfig, worktreePath, fixStageRunMode);
|
|
1011
1122
|
if (!validation.valid) {
|
|
1012
1123
|
runLog.warn({ stage: fixStage, rfIteration, issues: validation.issues }, "review-fix: handoff validation failed");
|
|
1013
1124
|
}
|
|
@@ -1144,13 +1255,26 @@ export class PipelineRunner {
|
|
|
1144
1255
|
// review stage_run row (from the main stage loop or review-fix loop).
|
|
1145
1256
|
// PR doesn't exist yet here, so prNumber stays null; the runner posts
|
|
1146
1257
|
// fanout PR comments after PR creation using `pendingFanoutRuns`.
|
|
1258
|
+
//
|
|
1259
|
+
// BEC-227 Task 11 — thread agent-session info through so the
|
|
1260
|
+
// agentic deep-review provider can resume the per-run SDK session
|
|
1261
|
+
// in its 3 parallel sub-agents. `claimFirstResumableStage` flips
|
|
1262
|
+
// the runner-level latch exactly once across the whole run; deep
|
|
1263
|
+
// review may be the first resumable consumer (e.g. when the main
|
|
1264
|
+
// review stage was skipped or used a fresh model).
|
|
1265
|
+
const isFirstResumableStageForDeepReview = claimFirstResumableStage("review");
|
|
1147
1266
|
const reviewCtx = {
|
|
1148
1267
|
runId,
|
|
1268
|
+
issueId: sanitizedIssue.id,
|
|
1149
1269
|
stageRunId: lastReviewStageRunId,
|
|
1150
1270
|
workdir: worktreePath,
|
|
1151
1271
|
handoff,
|
|
1152
1272
|
baseRef: repoConfig.defaultBranch ?? "main",
|
|
1153
1273
|
prNumber: null,
|
|
1274
|
+
agentSessionId: runAgentSessionId,
|
|
1275
|
+
isFirstResumableStage: isFirstResumableStageForDeepReview,
|
|
1276
|
+
reviewModel: config.stageModels?.["review"],
|
|
1277
|
+
db: this.db,
|
|
1154
1278
|
};
|
|
1155
1279
|
const reviewResult = await runReviewProviders(reviewCtx, {
|
|
1156
1280
|
env: drPass === 1 ? process.env : {},
|
|
@@ -1203,6 +1327,7 @@ export class PipelineRunner {
|
|
|
1203
1327
|
});
|
|
1204
1328
|
const deepReviewContext = buildDeepReviewContext(drPass, deepFindingsForContext, handoff);
|
|
1205
1329
|
runLog.info({ drPass }, "deep review: re-running implement stage");
|
|
1330
|
+
const isFirstResumableStageForDrImpl = claimFirstResumableStage("implement");
|
|
1206
1331
|
const drImplementResult = await executeStage({
|
|
1207
1332
|
runId,
|
|
1208
1333
|
issueId: sanitizedIssue.id,
|
|
@@ -1216,6 +1341,8 @@ export class PipelineRunner {
|
|
|
1216
1341
|
devcontainerSession,
|
|
1217
1342
|
ralphContext: deepReviewContext,
|
|
1218
1343
|
stageModels: config.stageModels,
|
|
1344
|
+
agentSessionId: runAgentSessionId,
|
|
1345
|
+
isFirstResumableStage: isFirstResumableStageForDrImpl,
|
|
1219
1346
|
});
|
|
1220
1347
|
run.totalInputTokens += drImplementResult.inputTokens;
|
|
1221
1348
|
run.totalOutputTokens += drImplementResult.outputTokens;
|
|
@@ -1237,6 +1364,7 @@ export class PipelineRunner {
|
|
|
1237
1364
|
handoff = drImplementResult.handoffArtifact;
|
|
1238
1365
|
// Re-run review stage to verify fixes
|
|
1239
1366
|
runLog.info({ drPass }, "deep review: re-running review stage");
|
|
1367
|
+
const isFirstResumableStageForDrReview = claimFirstResumableStage("review");
|
|
1240
1368
|
const drReviewResult = await executeStage({
|
|
1241
1369
|
runId,
|
|
1242
1370
|
issueId: sanitizedIssue.id,
|
|
@@ -1249,6 +1377,8 @@ export class PipelineRunner {
|
|
|
1249
1377
|
techStack,
|
|
1250
1378
|
devcontainerSession,
|
|
1251
1379
|
stageModels: config.stageModels,
|
|
1380
|
+
agentSessionId: runAgentSessionId,
|
|
1381
|
+
isFirstResumableStage: isFirstResumableStageForDrReview,
|
|
1252
1382
|
});
|
|
1253
1383
|
// BEC-134: refresh latest review stage_run id for any subsequent
|
|
1254
1384
|
// fanout persistence inside this loop.
|
|
@@ -1586,6 +1716,7 @@ export class PipelineRunner {
|
|
|
1586
1716
|
}
|
|
1587
1717
|
else {
|
|
1588
1718
|
runLog.warn("push queue: rebase conflicts detected, running implement pass to resolve");
|
|
1719
|
+
const isFirstResumableStageForResolve = claimFirstResumableStage("implement");
|
|
1589
1720
|
const resolveResult = await executeStage({
|
|
1590
1721
|
runId,
|
|
1591
1722
|
issueId: sanitizedIssue.id,
|
|
@@ -1599,6 +1730,8 @@ export class PipelineRunner {
|
|
|
1599
1730
|
devcontainerSession,
|
|
1600
1731
|
mergeConflictContext: { defaultBranch: repoConfig.defaultBranch },
|
|
1601
1732
|
stageModels: config.stageModels,
|
|
1733
|
+
agentSessionId: runAgentSessionId,
|
|
1734
|
+
isFirstResumableStage: isFirstResumableStageForResolve,
|
|
1602
1735
|
});
|
|
1603
1736
|
run.totalInputTokens += resolveResult.inputTokens;
|
|
1604
1737
|
run.totalOutputTokens += resolveResult.outputTokens;
|