@cat-factory/orchestration 0.70.1 → 0.71.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/container.d.ts +23 -2
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +43 -10
- package/dist/container.js.map +1 -1
- package/dist/modules/clarity/ClarityReviewService.d.ts +21 -1
- package/dist/modules/clarity/ClarityReviewService.d.ts.map +1 -1
- package/dist/modules/clarity/ClarityReviewService.js +27 -1
- package/dist/modules/clarity/ClarityReviewService.js.map +1 -1
- package/dist/modules/clarity/clarity.logic.d.ts +8 -0
- package/dist/modules/clarity/clarity.logic.d.ts.map +1 -1
- package/dist/modules/clarity/clarity.logic.js +23 -0
- package/dist/modules/clarity/clarity.logic.js.map +1 -1
- package/dist/modules/execution/AgentContextBuilder.d.ts +3 -0
- package/dist/modules/execution/AgentContextBuilder.d.ts.map +1 -1
- package/dist/modules/execution/AgentContextBuilder.js +2 -1
- package/dist/modules/execution/AgentContextBuilder.js.map +1 -1
- package/dist/modules/execution/ExecutionService.d.ts +60 -10
- package/dist/modules/execution/ExecutionService.d.ts.map +1 -1
- package/dist/modules/execution/ExecutionService.js +192 -27
- package/dist/modules/execution/ExecutionService.js.map +1 -1
- package/dist/modules/execution/MergeResolver.d.ts +21 -2
- package/dist/modules/execution/MergeResolver.d.ts.map +1 -1
- package/dist/modules/execution/MergeResolver.js +9 -3
- package/dist/modules/execution/MergeResolver.js.map +1 -1
- package/dist/modules/execution/RunDispatcher.d.ts +33 -1
- package/dist/modules/execution/RunDispatcher.d.ts.map +1 -1
- package/dist/modules/execution/RunDispatcher.js +144 -14
- package/dist/modules/execution/RunDispatcher.js.map +1 -1
- package/dist/modules/execution/RunStateMachine.d.ts +5 -2
- package/dist/modules/execution/RunStateMachine.d.ts.map +1 -1
- package/dist/modules/execution/RunStateMachine.js +4 -1
- package/dist/modules/execution/RunStateMachine.js.map +1 -1
- package/dist/modules/execution/bugInvestigation.logic.d.ts +3 -0
- package/dist/modules/execution/bugInvestigation.logic.d.ts.map +1 -0
- package/dist/modules/execution/bugInvestigation.logic.js +44 -0
- package/dist/modules/execution/bugInvestigation.logic.js.map +1 -0
- package/dist/modules/execution/mergeOrder.logic.d.ts +23 -0
- package/dist/modules/execution/mergeOrder.logic.d.ts.map +1 -0
- package/dist/modules/execution/mergeOrder.logic.js +30 -0
- package/dist/modules/execution/mergeOrder.logic.js.map +1 -0
- package/dist/modules/initiative/InitiativeLoopService.d.ts +7 -1
- package/dist/modules/initiative/InitiativeLoopService.d.ts.map +1 -1
- package/dist/modules/initiative/InitiativeLoopService.js +16 -3
- package/dist/modules/initiative/InitiativeLoopService.js.map +1 -1
- package/dist/modules/initiative/InitiativeService.d.ts +15 -1
- package/dist/modules/initiative/InitiativeService.d.ts.map +1 -1
- package/dist/modules/initiative/InitiativeService.js +46 -1
- package/dist/modules/initiative/InitiativeService.js.map +1 -1
- package/dist/modules/initiative/initiative.logic.d.ts +59 -1
- package/dist/modules/initiative/initiative.logic.d.ts.map +1 -1
- package/dist/modules/initiative/initiative.logic.js +241 -19
- package/dist/modules/initiative/initiative.logic.js.map +1 -1
- package/dist/modules/pipelines/PipelineService.d.ts.map +1 -1
- package/dist/modules/pipelines/PipelineService.js +15 -1
- package/dist/modules/pipelines/PipelineService.js.map +1 -1
- package/dist/modules/pipelines/pipelineShape.d.ts +15 -0
- package/dist/modules/pipelines/pipelineShape.d.ts.map +1 -1
- package/dist/modules/pipelines/pipelineShape.js +14 -4
- package/dist/modules/pipelines/pipelineShape.js.map +1 -1
- package/dist/modules/recurring/RecurringPipelineService.d.ts +18 -0
- package/dist/modules/recurring/RecurringPipelineService.d.ts.map +1 -1
- package/dist/modules/recurring/RecurringPipelineService.js +50 -4
- package/dist/modules/recurring/RecurringPipelineService.js.map +1 -1
- package/dist/modules/review/IterativeReviewService.d.ts +12 -0
- package/dist/modules/review/IterativeReviewService.d.ts.map +1 -1
- package/dist/modules/review/IterativeReviewService.js +23 -5
- package/dist/modules/review/IterativeReviewService.js.map +1 -1
- package/dist/modules/sandbox/SandboxRunService.d.ts +3 -0
- package/dist/modules/sandbox/SandboxRunService.d.ts.map +1 -1
- package/dist/modules/sandbox/SandboxRunService.js +1 -1
- package/dist/modules/sandbox/SandboxRunService.js.map +1 -1
- package/dist/modules/sandbox/SandboxService.d.ts +3 -0
- package/dist/modules/sandbox/SandboxService.d.ts.map +1 -1
- package/dist/modules/sandbox/SandboxService.js +3 -3
- package/dist/modules/sandbox/SandboxService.js.map +1 -1
- package/dist/validation/validateRegistrations.d.ts +9 -3
- package/dist/validation/validateRegistrations.d.ts.map +1 -1
- package/dist/validation/validateRegistrations.js +7 -7
- package/dist/validation/validateRegistrations.js.map +1 -1
- package/package.json +11 -11
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { DEFAULT_COMPANION_MAX_ATTEMPTS, frameAllowsVisualPipeline, isLocalRunner, pipelineHasVisualStep, } from '@cat-factory/contracts';
|
|
2
|
-
import { BINARY_STORAGE_TRAIT, companionFor, companionTargets, hasTrait, isCompanionKind, isInlineModelStep, } from '@cat-factory/agents';
|
|
1
|
+
import { allPullRequests, DEFAULT_COMPANION_MAX_ATTEMPTS, frameAllowsVisualPipeline, isLocalRunner, pipelineHasVisualStep, } from '@cat-factory/contracts';
|
|
2
|
+
import { BINARY_STORAGE_TRAIT, bugInvestigation, companionFor, companionTargets, hasTrait, isCompanionKind, isInlineModelStep, } from '@cat-factory/agents';
|
|
3
3
|
import { assertPipelineLaunchable, validatePipelineShape, } from '../pipelines/pipelineShape.js';
|
|
4
4
|
import { shouldRunGatedStep } from './stepGating.logic.js';
|
|
5
5
|
import { resolveIndividualVendors, } from './individualVendors.logic.js';
|
|
@@ -14,13 +14,14 @@ import { RunStateMachine } from './RunStateMachine.js';
|
|
|
14
14
|
import { RunDispatcher } from './RunDispatcher.js';
|
|
15
15
|
import { inferTechnicalLabel } from './technical.logic.js';
|
|
16
16
|
import { MergeResolver } from './MergeResolver.js';
|
|
17
|
+
import { orderPrsForMerge } from './mergeOrder.logic.js';
|
|
17
18
|
import { ReviewGateController } from './ReviewGateController.js';
|
|
18
19
|
import { BrainstormActions, ClarityReviewActions, RequirementReviewActions, } from './gate-window-facades.js';
|
|
19
20
|
import { TesterController } from './TesterController.js';
|
|
20
21
|
import { HumanTestController } from './HumanTestController.js';
|
|
21
22
|
import { VisualConfirmationController } from './VisualConfirmationController.js';
|
|
22
23
|
import { InitiativeInterviewController } from './InitiativeInterviewController.js';
|
|
23
|
-
import { assertInitiativeShapeAllowed } from '../initiative/initiative.logic.js';
|
|
24
|
+
import { assertInitiativeShapeAllowed, } from '../initiative/initiative.logic.js';
|
|
24
25
|
import { isAsyncAgentExecutor } from '@cat-factory/kernel';
|
|
25
26
|
import { dependenciesMet, descendantIds, serviceOf, unmetDependencies, } from '../board/board.logic.js';
|
|
26
27
|
import { requireWorkspace } from '@cat-factory/kernel';
|
|
@@ -48,6 +49,8 @@ export class ExecutionService {
|
|
|
48
49
|
/** The async instance/block state-machine spine (persist/emit/park/advance/finalize/fail). */
|
|
49
50
|
runStateMachine;
|
|
50
51
|
agentExecutor;
|
|
52
|
+
/** App-owned agent-kind registry (custom-kind traits/inline-surface + pre/post-op hooks). */
|
|
53
|
+
agentKindRegistry;
|
|
51
54
|
workRunner;
|
|
52
55
|
events;
|
|
53
56
|
board;
|
|
@@ -92,6 +95,7 @@ export class ExecutionService {
|
|
|
92
95
|
// so the constructor forwards the destructured params straight to those collaborators.
|
|
93
96
|
workspaceSettingsService;
|
|
94
97
|
prMerger;
|
|
98
|
+
notifications;
|
|
95
99
|
mergePresetRepository;
|
|
96
100
|
issueWriteback;
|
|
97
101
|
subscriptionActivations;
|
|
@@ -118,7 +122,7 @@ export class ExecutionService {
|
|
|
118
122
|
* / `resolveGatePollExhaustion` + the follow-up human-action API are thin pass-throughs.
|
|
119
123
|
*/
|
|
120
124
|
runDispatcher;
|
|
121
|
-
constructor({ workspaceRepository, blockRepository, pipelineRepository, executionRepository, accountRepository, idGenerator, clock, agentExecutor, workRunner, executionEventPublisher, boardService, spendService, documentRepository, documentUrlResolver, taskRepository, requirementReviewRepository, requirementReviewService, testerQualityReviewer, kaizenScheduler, clarityReviewRepository, clarityReviewService, brainstormServices, brainstormSessionRepository, fragmentResolver, environmentProvisioning, environmentTeardown, branchUpdater, blueprintReconciler, initiativeService, initiativeRepository, initiativeInterviewService, notificationService, resolveBinaryArtifactStore, workspaceSettingsService, llmObservability, pullRequestMerger, mergePresetRepository, ticketTrackerProvider, issueWriteback, subscriptionActivationRepository, resolveWorkspaceModelDefault, resolveProviderCapabilities, inlineHarnessRef, localTestInfraSupported, resolveRunRepoContext, assertAgentBackendConfigured, runInitiatorScope, pokeInitiativeLoop, }) {
|
|
125
|
+
constructor({ workspaceRepository, blockRepository, pipelineRepository, executionRepository, accountRepository, idGenerator, clock, agentExecutor, workRunner, executionEventPublisher, boardService, spendService, documentRepository, documentUrlResolver, taskRepository, requirementReviewRepository, requirementReviewService, testerQualityReviewer, kaizenScheduler, clarityReviewRepository, clarityReviewService, brainstormServices, brainstormSessionRepository, fragmentResolver, environmentProvisioning, environmentTeardown, branchUpdater, blueprintReconciler, initiativeService, initiativeRepository, initiativeInterviewService, notificationService, resolveBinaryArtifactStore, workspaceSettingsService, llmObservability, pullRequestMerger, mergePresetRepository, ticketTrackerProvider, issueWriteback, bugIntakeService, subscriptionActivationRepository, resolveWorkspaceModelDefault, resolveProviderCapabilities, inlineHarnessRef, localTestInfraSupported, resolveRunRepoContext, assertAgentBackendConfigured, runInitiatorScope, pokeInitiativeLoop, agentKindRegistry, }) {
|
|
122
126
|
// Forward-only: the run-initiator scope is consumed solely by RunDispatcher (below), so it
|
|
123
127
|
// is hoisted to a local with its default applied rather than stored as a `this.` field.
|
|
124
128
|
const runInitiatorScopeFn = runInitiatorScope ?? ((_initiatedBy, fn) => fn());
|
|
@@ -145,6 +149,7 @@ export class ExecutionService {
|
|
|
145
149
|
pokeInitiativeLoop,
|
|
146
150
|
});
|
|
147
151
|
this.agentExecutor = agentExecutor;
|
|
152
|
+
this.agentKindRegistry = agentKindRegistry;
|
|
148
153
|
this.workRunner = workRunner;
|
|
149
154
|
this.events = executionEventPublisher;
|
|
150
155
|
this.board = boardService;
|
|
@@ -157,6 +162,7 @@ export class ExecutionService {
|
|
|
157
162
|
workspaceRepository,
|
|
158
163
|
blockRepository,
|
|
159
164
|
accountRepository,
|
|
165
|
+
agentKindRegistry,
|
|
160
166
|
documents: documentRepository,
|
|
161
167
|
documentUrlResolver,
|
|
162
168
|
tasks: taskRepository,
|
|
@@ -281,6 +287,7 @@ export class ExecutionService {
|
|
|
281
287
|
blockRepository,
|
|
282
288
|
executionRepository,
|
|
283
289
|
agentExecutor,
|
|
290
|
+
agentKindRegistry,
|
|
284
291
|
workRunner,
|
|
285
292
|
events: executionEventPublisher,
|
|
286
293
|
idGenerator,
|
|
@@ -304,6 +311,7 @@ export class ExecutionService {
|
|
|
304
311
|
environmentProvisioning,
|
|
305
312
|
ticketTrackerProvider,
|
|
306
313
|
issueWriteback,
|
|
314
|
+
bugIntakeService,
|
|
307
315
|
notificationService,
|
|
308
316
|
blueprintReconciler,
|
|
309
317
|
initiativeService,
|
|
@@ -320,6 +328,7 @@ export class ExecutionService {
|
|
|
320
328
|
this.brainstormActions = new BrainstormActions(this.reviewGate, (stage) => this.brainstormKindFor(stage));
|
|
321
329
|
this.workspaceSettingsService = workspaceSettingsService;
|
|
322
330
|
this.prMerger = pullRequestMerger;
|
|
331
|
+
this.notifications = notificationService;
|
|
323
332
|
this.mergePresetRepository = mergePresetRepository;
|
|
324
333
|
this.issueWriteback = issueWriteback;
|
|
325
334
|
this.subscriptionActivations = subscriptionActivationRepository;
|
|
@@ -507,7 +516,7 @@ export class ExecutionService {
|
|
|
507
516
|
* no storage) — matching the other optional start guards.
|
|
508
517
|
*/
|
|
509
518
|
async assertBinaryStorageConfigured(workspaceId, agentKinds) {
|
|
510
|
-
if (!agentKinds.some((kind) => hasTrait(kind, BINARY_STORAGE_TRAIT)))
|
|
519
|
+
if (!agentKinds.some((kind) => hasTrait(kind, BINARY_STORAGE_TRAIT, this.agentKindRegistry)))
|
|
511
520
|
return;
|
|
512
521
|
const resolve = this.resolveBinaryArtifactStore;
|
|
513
522
|
if (!resolve)
|
|
@@ -553,12 +562,12 @@ export class ExecutionService {
|
|
|
553
562
|
if (block.modelId) {
|
|
554
563
|
// A block-level pin applies to every step; it must satisfy an inline step too when the
|
|
555
564
|
// pipeline has one.
|
|
556
|
-
check(block.modelId, agentKinds.some(isInlineModelStep));
|
|
565
|
+
check(block.modelId, agentKinds.some((kind) => isInlineModelStep(kind, this.agentKindRegistry)));
|
|
557
566
|
}
|
|
558
567
|
else if (this.resolveWorkspaceModelDefault) {
|
|
559
568
|
// Independent per-kind resolutions on the start path — run them concurrently.
|
|
560
569
|
const ids = await Promise.all(agentKinds.map((kind) => this.resolveWorkspaceModelDefault(workspaceId, kind, block.modelPresetId)));
|
|
561
|
-
agentKinds.forEach((kind, i) => check(ids[i], isInlineModelStep(kind)));
|
|
570
|
+
agentKinds.forEach((kind, i) => check(ids[i], isInlineModelStep(kind, this.agentKindRegistry)));
|
|
562
571
|
}
|
|
563
572
|
if (unconfigured.size > 0) {
|
|
564
573
|
throw new ConflictError(`This pipeline uses models with no configured provider: ${[...unconfigured].join(', ')}. ` +
|
|
@@ -1227,25 +1236,81 @@ export class ExecutionService {
|
|
|
1227
1236
|
*/
|
|
1228
1237
|
buildClarityKind() {
|
|
1229
1238
|
const require = () => {
|
|
1230
|
-
if (!this.clarityReviewService
|
|
1239
|
+
if (!this.clarityReviewService) {
|
|
1231
1240
|
throw new ConflictError('The clarity reviewer is not configured');
|
|
1232
1241
|
}
|
|
1233
1242
|
return this.clarityReviewService;
|
|
1234
1243
|
};
|
|
1235
1244
|
return {
|
|
1236
1245
|
agentKind: CLARITY_REVIEW_AGENT_KIND,
|
|
1246
|
+
// Enabled whenever the clarity STORE is wired — the bug-triage seed/auto-pass path is
|
|
1247
|
+
// deterministic (driven by the upstream investigator's structured triage) and needs no
|
|
1248
|
+
// reviewer model, so the gate must activate even with no model configured. The LLM
|
|
1249
|
+
// review/incorporate/re-review paths still resolve their own model (and degrade gracefully
|
|
1250
|
+
// when unwired: no investigation + no model ⇒ the review closure auto-passes).
|
|
1237
1251
|
entityName: 'Clarity review',
|
|
1238
|
-
enabled: () => !!this.clarityReviewService
|
|
1252
|
+
enabled: () => !!this.clarityReviewService,
|
|
1239
1253
|
getForBlock: (ws, blockId) => require().getForBlock(ws, blockId),
|
|
1240
|
-
review: async (ws, block, preset) =>
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1254
|
+
review: async (ws, block, preset) => {
|
|
1255
|
+
const svc = require();
|
|
1256
|
+
const structured = await this.structuredInvestigationForBlock(ws, block.id);
|
|
1257
|
+
let review;
|
|
1258
|
+
if (structured) {
|
|
1259
|
+
// An upstream structured `bug-investigator`: seed the gate from its triage — NO reviewer
|
|
1260
|
+
// LLM. `clear` → auto-pass; `needs_clarification` → one blocking finding per question.
|
|
1261
|
+
// The investigator explicitly asked for clarification, so those questions ALWAYS park
|
|
1262
|
+
// for a human — the requirements-review concern tolerance (`maxRequirementConcernAllowed`,
|
|
1263
|
+
// which governs the requirements reviewer, not bug triage) must not silently auto-pass
|
|
1264
|
+
// them — hence a fixed `none` threshold here rather than the preset's.
|
|
1265
|
+
review = await svc.seedReview(ws, block.id, {
|
|
1266
|
+
clarity: structured.clarity,
|
|
1267
|
+
questions: structured.questions,
|
|
1268
|
+
maxIterations: preset.maxRequirementIterations,
|
|
1269
|
+
concernThreshold: 'none',
|
|
1270
|
+
});
|
|
1271
|
+
}
|
|
1272
|
+
else if (!svc.enabled) {
|
|
1273
|
+
// No structured investigation and no reviewer model: nothing to review against, so
|
|
1274
|
+
// auto-pass (equivalent to the old pass-through when the reviewer wasn't configured).
|
|
1275
|
+
review = await svc.seedReview(ws, block.id, {
|
|
1276
|
+
clarity: 'clear',
|
|
1277
|
+
questions: [],
|
|
1278
|
+
maxIterations: preset.maxRequirementIterations,
|
|
1279
|
+
concernThreshold: preset.maxRequirementConcernAllowed,
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
else {
|
|
1283
|
+
review = await svc.review(ws, block.id, {
|
|
1284
|
+
maxIterations: preset.maxRequirementIterations,
|
|
1285
|
+
concernThreshold: preset.maxRequirementConcernAllowed,
|
|
1286
|
+
investigation: await this.investigationForBlock(ws, block.id),
|
|
1287
|
+
});
|
|
1288
|
+
}
|
|
1289
|
+
// Whenever the gate parks with open questions — from the deterministic seed OR the LLM
|
|
1290
|
+
// reviewer — best-effort echo them onto the linked tracker issue (answers still arrive
|
|
1291
|
+
// in-app). A settled/auto-passed review echoes nothing; a tracker outage never fails the run.
|
|
1292
|
+
await this.echoClarityQuestions(ws, block.id, review);
|
|
1293
|
+
return review;
|
|
1294
|
+
},
|
|
1295
|
+
reReview: async (ws, reviewId, preset) => {
|
|
1296
|
+
const svc = require();
|
|
1297
|
+
// No reviewer model wired: a re-review can't run, so settle the loop (converge) instead of
|
|
1298
|
+
// throwing — the deterministic seed path can reach a park with no model configured.
|
|
1299
|
+
if (!svc.enabled)
|
|
1300
|
+
return svc.markIncorporated(ws, reviewId);
|
|
1301
|
+
return svc.reReview(ws, reviewId, { concernThreshold: preset.maxRequirementConcernAllowed });
|
|
1302
|
+
},
|
|
1246
1303
|
incorporate: async (ws, blockId, reviewId, feedback) => {
|
|
1304
|
+
const svc = require();
|
|
1305
|
+
// No reviewer model: can't LLM-fold the answers into a clarified report, so settle the
|
|
1306
|
+
// review as-is (the run advances on the raw report + the recorded answers) instead of
|
|
1307
|
+
// throwing — keeps the model-free seed path resolvable.
|
|
1308
|
+
if (!svc.enabled) {
|
|
1309
|
+
await svc.markIncorporated(ws, reviewId);
|
|
1310
|
+
return;
|
|
1311
|
+
}
|
|
1247
1312
|
const investigation = await this.investigationForBlock(ws, blockId);
|
|
1248
|
-
await
|
|
1313
|
+
await svc.incorporate(ws, reviewId, { feedback, investigation });
|
|
1249
1314
|
},
|
|
1250
1315
|
markIncorporated: (ws, reviewId) => require().markIncorporated(ws, reviewId),
|
|
1251
1316
|
markReReviewing: (ws, reviewId) => require().markReReviewing(ws, reviewId),
|
|
@@ -1400,6 +1465,49 @@ export class ExecutionService {
|
|
|
1400
1465
|
const instance = await this.executionRepository.get(workspaceId, block.executionId);
|
|
1401
1466
|
return instance ? this.investigationFor(instance) : undefined;
|
|
1402
1467
|
}
|
|
1468
|
+
/**
|
|
1469
|
+
* The latest `bug-investigator` step's STRUCTURED triage on a run — its `clarity` verdict +
|
|
1470
|
+
* `questions` — parsed leniently from `step.custom`. Drives the clarity gate's seed/auto-pass
|
|
1471
|
+
* (see {@link buildClarityKind}): a structured investigator upstream means the gate seeds its
|
|
1472
|
+
* findings from `questions` (or auto-passes on `clarity === 'clear'`) instead of running its
|
|
1473
|
+
* own reviewer LLM. Undefined when no investigator ran or its result wasn't structured (an
|
|
1474
|
+
* older prose investigator, or an unparseable reply) — the gate then falls back to the LLM path.
|
|
1475
|
+
*/
|
|
1476
|
+
structuredInvestigationFor(instance) {
|
|
1477
|
+
for (let i = instance.steps.length - 1; i >= 0; i--) {
|
|
1478
|
+
const s = instance.steps[i];
|
|
1479
|
+
if (s.agentKind !== BUG_INVESTIGATOR_AGENT_KIND || s.custom === undefined)
|
|
1480
|
+
continue;
|
|
1481
|
+
const parsed = bugInvestigation.safeParse(s.custom);
|
|
1482
|
+
if (!parsed)
|
|
1483
|
+
return undefined;
|
|
1484
|
+
return { clarity: parsed.clarity, questions: parsed.questions };
|
|
1485
|
+
}
|
|
1486
|
+
return undefined;
|
|
1487
|
+
}
|
|
1488
|
+
/** Resolve a block's structured investigator triage via its current execution. */
|
|
1489
|
+
async structuredInvestigationForBlock(workspaceId, blockId) {
|
|
1490
|
+
const block = await this.blockRepository.get(workspaceId, blockId);
|
|
1491
|
+
if (!block?.executionId)
|
|
1492
|
+
return undefined;
|
|
1493
|
+
const instance = await this.executionRepository.get(workspaceId, block.executionId);
|
|
1494
|
+
return instance ? this.structuredInvestigationFor(instance) : undefined;
|
|
1495
|
+
}
|
|
1496
|
+
/**
|
|
1497
|
+
* Best-effort echo of a parked clarity review's open questions onto the block's linked tracker
|
|
1498
|
+
* issue (see {@link IssueWritebackProvider.postQuestions}). Fires for BOTH the deterministic
|
|
1499
|
+
* investigator seed and the LLM reviewer, so identical human-parked states behave the same. A
|
|
1500
|
+
* settled/auto-passed review (status `incorporated`) or one with no open items echoes nothing,
|
|
1501
|
+
* and a tracker outage never fails the run.
|
|
1502
|
+
*/
|
|
1503
|
+
async echoClarityQuestions(workspaceId, blockId, review) {
|
|
1504
|
+
if (!this.issueWriteback || review.status === 'incorporated')
|
|
1505
|
+
return;
|
|
1506
|
+
const questions = review.items.filter((i) => i.status === 'open').map((i) => i.detail);
|
|
1507
|
+
if (questions.length === 0)
|
|
1508
|
+
return;
|
|
1509
|
+
await this.issueWriteback.postQuestions(workspaceId, blockId, questions).catch(() => { });
|
|
1510
|
+
}
|
|
1403
1511
|
// The clarity / human-testing / visual-confirmation gate-window actions now live on the
|
|
1404
1512
|
// per-feature sub-facades (`clarityReview` / `humanTest` / `visualConfirm`); see the getters
|
|
1405
1513
|
// above and {@link gate-window-facades}.
|
|
@@ -1453,26 +1561,59 @@ export class ExecutionService {
|
|
|
1453
1561
|
return instance;
|
|
1454
1562
|
}
|
|
1455
1563
|
/**
|
|
1456
|
-
* Merge a block's PR for real, then mark it `done`. The remote merge happens
|
|
1457
|
-
*
|
|
1458
|
-
*
|
|
1459
|
-
*
|
|
1460
|
-
*
|
|
1461
|
-
*
|
|
1564
|
+
* Merge a block's PR(s) for real, then mark it `done`. The remote merge happens FIRST (via
|
|
1565
|
+
* the {@link PullRequestMerger} port) and only on its success does the block flip to `done`
|
|
1566
|
+
* — so `done` provably means "merged", not a board-only status. When no merger is wired
|
|
1567
|
+
* (tests) this degrades to the old board-only flip.
|
|
1568
|
+
*
|
|
1569
|
+
* Multi-repo (service-connections phase 4): a cross-service task opens one PR per changed
|
|
1570
|
+
* repo. All of them are merged in provider-before-consumer order (see {@link orderPrsForMerge}),
|
|
1571
|
+
* stopping at the first failure. A COMPLETE failure (nothing merged) THROWS so the caller
|
|
1572
|
+
* falls back to a review notification, exactly as the single-repo path did. A PARTIAL failure
|
|
1573
|
+
* (some merged, then one failed — cross-repo merges are non-atomic) leaves the block `blocked`
|
|
1574
|
+
* and raises an enumerated `merge_review` notification, and is reported to the caller as
|
|
1575
|
+
* `partial` so it labels the decision without raising a second card.
|
|
1462
1576
|
*/
|
|
1463
1577
|
async finalizeMerge(workspaceId, blockId) {
|
|
1464
1578
|
const block = await this.blockRepository.get(workspaceId, blockId);
|
|
1465
1579
|
if (!block)
|
|
1466
|
-
return;
|
|
1580
|
+
return { kind: 'merged' };
|
|
1467
1581
|
// Idempotent under durable-driver replays: a crash between the real merge and the
|
|
1468
1582
|
// instance persist re-runs the merger resolver, and re-merging an already-merged PR
|
|
1469
1583
|
// throws — which the resolver's fall-through would then misread as a failed merge
|
|
1470
1584
|
// and downgrade the block to `pr_ready`. `done` already means "merged"; keep it.
|
|
1471
1585
|
if (block.status === 'done')
|
|
1472
|
-
return;
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1586
|
+
return { kind: 'merged' };
|
|
1587
|
+
// Same idempotency guard for a PARTIALLY-merged multi-repo task: the first pass merged some
|
|
1588
|
+
// PRs, then one failed, so it left the block `blocked` and raised the enumerated card. A
|
|
1589
|
+
// durable-driver replay must NOT re-run the merge — re-merging the already-merged PRs throws
|
|
1590
|
+
// (GitHub 405) and would be misread as a TOTAL failure (`merged.length === 0` → throw → the
|
|
1591
|
+
// resolver downgrades the block to `pr_ready` + raises a SECOND card). The merger step only
|
|
1592
|
+
// ever enters `finalizeMerge` on an already-`blocked` block on such a replay (the manual
|
|
1593
|
+
// `mergePr` path gates on `pr_ready`), so return the already-recorded partial outcome.
|
|
1594
|
+
if (block.status === 'blocked')
|
|
1595
|
+
return { kind: 'partial', merged: [], unmerged: [] };
|
|
1596
|
+
// Merge every PR the task opened (own-service + peers) — not just `block.pullRequest`, since a
|
|
1597
|
+
// multi-repo task can have changed ONLY peer repos (own service untouched, no own PR).
|
|
1598
|
+
const ordered = orderPrsForMerge(allPullRequests(block).map((p) => ({
|
|
1599
|
+
...(p.repo ? { repo: p.repo } : {}),
|
|
1600
|
+
...(p.frameId ? { frameId: p.frameId } : {}),
|
|
1601
|
+
ref: p.ref,
|
|
1602
|
+
})));
|
|
1603
|
+
if (this.prMerger && ordered.length > 0) {
|
|
1604
|
+
const outcome = await this.prMerger.mergePullRequests(workspaceId, blockId, ordered);
|
|
1605
|
+
if (outcome.failed) {
|
|
1606
|
+
// Nothing merged → behave like the old single-PR throw so the caller raises a review.
|
|
1607
|
+
if (outcome.merged.length === 0)
|
|
1608
|
+
throw new Error(outcome.failed.error);
|
|
1609
|
+
// Partial: leave the block blocked and enumerate the split for a human to finish/revert.
|
|
1610
|
+
const label = (e) => e.repo ?? 'own service';
|
|
1611
|
+
const merged = outcome.merged.map(label);
|
|
1612
|
+
const unmerged = [outcome.failed.entry, ...outcome.skipped].map(label);
|
|
1613
|
+
await this.blockRepository.update(workspaceId, blockId, { status: 'blocked' });
|
|
1614
|
+
await this.raisePartialMergeNotification(workspaceId, block, merged, unmerged);
|
|
1615
|
+
return { kind: 'partial', merged, unmerged };
|
|
1616
|
+
}
|
|
1476
1617
|
}
|
|
1477
1618
|
await this.blockRepository.update(workspaceId, blockId, { status: 'done', progress: 1 });
|
|
1478
1619
|
// Best-effort writeback: comment + close the task's linked tracker issue(s) as
|
|
@@ -1498,6 +1639,30 @@ export class ExecutionService {
|
|
|
1498
1639
|
if (block.initiativeId)
|
|
1499
1640
|
this.pokeInitiativeLoop?.(workspaceId, block.initiativeId);
|
|
1500
1641
|
}
|
|
1642
|
+
return { kind: 'merged' };
|
|
1643
|
+
}
|
|
1644
|
+
/**
|
|
1645
|
+
* Raise the `merge_review` card for a PARTIALLY-merged multi-repo task: some PRs merged, then
|
|
1646
|
+
* an intermediate one failed. Cross-repo merges can't be atomic, so the human finishes or
|
|
1647
|
+
* reverts the split by hand; the card enumerates which repos merged vs are still open.
|
|
1648
|
+
*/
|
|
1649
|
+
async raisePartialMergeNotification(workspaceId, block, merged, unmerged) {
|
|
1650
|
+
if (!this.notifications)
|
|
1651
|
+
return;
|
|
1652
|
+
await this.notifications.raise(workspaceId, {
|
|
1653
|
+
type: 'merge_review',
|
|
1654
|
+
blockId: block.id,
|
|
1655
|
+
executionId: block.executionId ?? null,
|
|
1656
|
+
title: `Finish the multi-repo merge for "${block.title}"`,
|
|
1657
|
+
body: `Merged ${merged.length} PR(s) (${merged.join(', ')}) but could not merge ` +
|
|
1658
|
+
`${unmerged.length} more (${unmerged.join(', ')}). Cross-repo merges aren't atomic — ` +
|
|
1659
|
+
`merge the rest or revert the merged PR(s) by hand.`,
|
|
1660
|
+
payload: {
|
|
1661
|
+
mergedRepos: merged,
|
|
1662
|
+
unmergedRepos: unmerged,
|
|
1663
|
+
...(block.pullRequest?.url ? { prUrl: block.pullRequest.url } : {}),
|
|
1664
|
+
},
|
|
1665
|
+
});
|
|
1501
1666
|
}
|
|
1502
1667
|
/**
|
|
1503
1668
|
* After a task with `autoStartDependents` merges, start every task that `dependsOn` it
|