@pellux/goodvibes-sdk 0.33.24 → 0.33.26

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.
Files changed (49) hide show
  1. package/dist/contracts/artifacts/operator-contract.json +1 -1
  2. package/dist/events/agents.d.ts +3 -3
  3. package/dist/events/agents.d.ts.map +1 -1
  4. package/dist/events/orchestration.d.ts +1 -1
  5. package/dist/events/orchestration.d.ts.map +1 -1
  6. package/dist/events/workflows.d.ts +1 -1
  7. package/dist/events/workflows.d.ts.map +1 -1
  8. package/dist/platform/agents/archetypes.d.ts.map +1 -1
  9. package/dist/platform/agents/archetypes.js +14 -0
  10. package/dist/platform/agents/communication-policy.d.ts.map +1 -1
  11. package/dist/platform/agents/communication-policy.js +4 -0
  12. package/dist/platform/agents/orchestrator-prompts.d.ts.map +1 -1
  13. package/dist/platform/agents/orchestrator-prompts.js +2 -0
  14. package/dist/platform/agents/worktree.d.ts +3 -0
  15. package/dist/platform/agents/worktree.d.ts.map +1 -1
  16. package/dist/platform/agents/worktree.js +64 -2
  17. package/dist/platform/agents/wrfc-controller.d.ts +16 -1
  18. package/dist/platform/agents/wrfc-controller.d.ts.map +1 -1
  19. package/dist/platform/agents/wrfc-controller.js +484 -16
  20. package/dist/platform/agents/wrfc-runtime-events.d.ts +1 -1
  21. package/dist/platform/agents/wrfc-runtime-events.d.ts.map +1 -1
  22. package/dist/platform/agents/wrfc-types.d.ts +29 -4
  23. package/dist/platform/agents/wrfc-types.d.ts.map +1 -1
  24. package/dist/platform/agents/wrfc-workmap.d.ts +2 -1
  25. package/dist/platform/agents/wrfc-workmap.d.ts.map +1 -1
  26. package/dist/platform/git/service.d.ts +4 -0
  27. package/dist/platform/git/service.d.ts.map +1 -1
  28. package/dist/platform/git/service.js +33 -0
  29. package/dist/platform/runtime/emitters/agents.d.ts +3 -3
  30. package/dist/platform/runtime/emitters/agents.d.ts.map +1 -1
  31. package/dist/platform/runtime/emitters/orchestration.d.ts +1 -1
  32. package/dist/platform/runtime/emitters/orchestration.d.ts.map +1 -1
  33. package/dist/platform/runtime/store/domains/agents.d.ts +2 -2
  34. package/dist/platform/runtime/store/domains/agents.d.ts.map +1 -1
  35. package/dist/platform/runtime/store/domains/orchestration.d.ts +1 -1
  36. package/dist/platform/runtime/store/domains/orchestration.d.ts.map +1 -1
  37. package/dist/platform/tools/agent/index.d.ts.map +1 -1
  38. package/dist/platform/tools/agent/index.js +2 -0
  39. package/dist/platform/tools/agent/manager.d.ts +2 -0
  40. package/dist/platform/tools/agent/manager.d.ts.map +1 -1
  41. package/dist/platform/tools/agent/manager.js +22 -3
  42. package/dist/platform/tools/agent/schema.d.ts +2 -0
  43. package/dist/platform/tools/agent/schema.d.ts.map +1 -1
  44. package/dist/platform/tools/agent/schema.js +4 -4
  45. package/dist/platform/tools/agent/wrfc-batch-policy.d.ts +1 -0
  46. package/dist/platform/tools/agent/wrfc-batch-policy.d.ts.map +1 -1
  47. package/dist/platform/tools/agent/wrfc-batch-policy.js +95 -1
  48. package/dist/platform/version.js +1 -1
  49. package/package.json +9 -9
@@ -16,7 +16,8 @@ import { runWrfcGateChecks } from './wrfc-gate-runtime.js';
16
16
  export { extractScoreFromText, extractPassedFromText, extractIssuesFromText } from './wrfc-reporting.js';
17
17
  const VALID_TRANSITIONS = {
18
18
  pending: ['engineering'],
19
- engineering: ['reviewing', 'failed'],
19
+ engineering: ['integrating', 'reviewing', 'failed'],
20
+ integrating: ['reviewing', 'failed'],
20
21
  reviewing: ['fixing', 'awaiting_gates', 'failed'],
21
22
  fixing: ['reviewing', 'failed'],
22
23
  awaiting_gates: ['gating', 'failed'],
@@ -149,10 +150,13 @@ export class WrfcController {
149
150
  emitWrfcStateChanged(this.runtimeBus, this.sessionId, chain.id, from, to);
150
151
  logger.debug('WrfcController.transition', { chainId: chain.id, from, to });
151
152
  }
152
- applyWrfcAgentMetadata(chain, record, role) {
153
+ applyWrfcAgentMetadata(chain, record, role, subtaskId) {
153
154
  record.wrfcId = chain.id;
154
155
  record.wrfcRole = role;
155
156
  record.wrfcPhaseOrder = this.wrfcPhaseOrder(role);
157
+ if (subtaskId) {
158
+ record.wrfcSubtaskId = subtaskId;
159
+ }
156
160
  if (role === 'owner') {
157
161
  record.progress = this.ownerProgress(chain);
158
162
  }
@@ -207,20 +211,28 @@ export class WrfcController {
207
211
  });
208
212
  }
209
213
  ownerProgress(chain) {
214
+ if (chain.subtasks && chain.subtasks.length > 0) {
215
+ const passed = chain.subtasks.filter((subtask) => subtask.state === 'passed').length;
216
+ return `WRFC owner supervising compound chain (${chain.state}, ${passed}/${chain.subtasks.length} deliverables passed)`;
217
+ }
210
218
  return `WRFC owner supervising child agents (${chain.state})`;
211
219
  }
212
220
  wrfcPhaseOrder(role) {
213
221
  switch (role) {
214
222
  case 'owner':
215
223
  return 0;
224
+ case 'orchestrator':
225
+ return 0;
216
226
  case 'engineer':
217
227
  return 1;
218
228
  case 'reviewer':
219
229
  return 2;
220
230
  case 'fixer':
221
231
  return 3;
222
- case 'verifier':
232
+ case 'integrator':
223
233
  return 4;
234
+ case 'verifier':
235
+ return 5;
224
236
  }
225
237
  }
226
238
  setupListeners() {
@@ -264,6 +276,23 @@ export class WrfcController {
264
276
  state: chain.state,
265
277
  outputLength: rawOutput.length,
266
278
  });
279
+ const subtask = this.findSubtaskByAgentId(chain, agentId);
280
+ if (subtask) {
281
+ await this.onCompoundSubtaskAgentComplete(chain, subtask, agentId, rawOutput, record ?? undefined);
282
+ if (this.planManager) {
283
+ completePlanItemsForAgent(agentId, this.planManager);
284
+ }
285
+ return;
286
+ }
287
+ if (agentId === chain.integratorAgentId) {
288
+ const report = parseEngineerCompletionReport(rawOutput, record?.template);
289
+ this.setWrfcWorkPlanTaskStatus(chain, agentId, 'done');
290
+ this.handleIntegratorCompletion(chain, agentId, report);
291
+ if (this.planManager) {
292
+ completePlanItemsForAgent(agentId, this.planManager);
293
+ }
294
+ return;
295
+ }
267
296
  if (chain.state === 'pending') {
268
297
  chain.bufferedCompletion = { agentId, fullOutput: rawOutput };
269
298
  logger.debug('WrfcController.onAgentComplete: chain pending, buffering completion', {
@@ -541,6 +570,47 @@ export class WrfcController {
541
570
  constraintFailure: unsatisfiedConstraintIds.length > 0,
542
571
  };
543
572
  }
573
+ evaluateSubtaskConstraints(subtask, review) {
574
+ if (subtask.constraints.length === 0) {
575
+ return {
576
+ constraintsSatisfied: 0,
577
+ constraintsTotal: 0,
578
+ unsatisfiedConstraintIds: [],
579
+ ignoredConstraintFindingIds: [],
580
+ constraintFailure: false,
581
+ };
582
+ }
583
+ const expectedIds = new Set(subtask.constraints.map((constraint) => constraint.id));
584
+ const findingMap = new Map();
585
+ const ignoredConstraintFindingIds = [];
586
+ for (const finding of review.constraintFindings ?? []) {
587
+ if (!expectedIds.has(finding.constraintId)) {
588
+ ignoredConstraintFindingIds.push(finding.constraintId);
589
+ continue;
590
+ }
591
+ if (!findingMap.has(finding.constraintId)) {
592
+ findingMap.set(finding.constraintId, finding);
593
+ }
594
+ }
595
+ let constraintsSatisfied = 0;
596
+ const unsatisfiedConstraintIds = [];
597
+ for (const constraint of subtask.constraints) {
598
+ const finding = findingMap.get(constraint.id);
599
+ if (finding?.satisfied === true) {
600
+ constraintsSatisfied += 1;
601
+ }
602
+ else {
603
+ unsatisfiedConstraintIds.push(constraint.id);
604
+ }
605
+ }
606
+ return {
607
+ constraintsSatisfied,
608
+ constraintsTotal: subtask.constraints.length,
609
+ unsatisfiedConstraintIds,
610
+ ignoredConstraintFindingIds,
611
+ constraintFailure: unsatisfiedConstraintIds.length > 0,
612
+ };
613
+ }
544
614
  async processGateResults(chain, results) {
545
615
  if (!chain.currentNodeId?.includes(':gate:')) {
546
616
  chain.currentNodeId = startWrfcOrchestrationNode(this.runtimeBus, this.sessionId, chain.id, `gate:${chain.reviewCycles}:${chain.fixAttempts}`, 'verifier', 'Quality gates');
@@ -635,6 +705,7 @@ export class WrfcController {
635
705
  const allChains = Array.from(this.chains.values()).filter((chain) => chain.state !== 'passed' && chain.state !== 'failed');
636
706
  const activeWorkChains = allChains.filter((chain) => (chain.state === 'pending'
637
707
  || chain.state === 'engineering'
708
+ || chain.state === 'integrating'
638
709
  || chain.state === 'reviewing'
639
710
  || chain.state === 'fixing'));
640
711
  if (activeWorkChains.length > 0) {
@@ -662,11 +733,9 @@ export class WrfcController {
662
733
  }
663
734
  async autoCommit(chain) {
664
735
  this.transition(chain, 'committing');
665
- const agentId = chain.allAgentIds.length > 0
666
- ? chain.allAgentIds[chain.allAgentIds.length - 1]
667
- : (chain.fixerAgentId ?? chain.engineerAgentId);
668
- if (!agentId) {
669
- this.failChain(chain, 'autoCommit: no agent ID found on chain');
736
+ const commitCandidateIds = this.autoCommitCandidateAgentIds(chain);
737
+ if (commitCandidateIds.length === 0) {
738
+ this.failChain(chain, 'autoCommit: no write-capable WRFC agent found on chain');
670
739
  return;
671
740
  }
672
741
  if (!existsSync(join(this.projectRoot, '.git'))) {
@@ -675,11 +744,29 @@ export class WrfcController {
675
744
  return;
676
745
  }
677
746
  const worktree = this.createWorktree();
747
+ let completed = false;
678
748
  try {
679
- const merged = await worktree.merge(agentId);
680
- emitWrfcAutoCommitted(this.runtimeBus, this.sessionId, chain.id);
749
+ const commitMessage = this.autoCommitMessage(chain);
750
+ const directCommitHash = worktree.commitWorkingTree
751
+ ? await worktree.commitWorkingTree(commitMessage)
752
+ : null;
753
+ let mergedCount = 0;
754
+ for (const agentId of commitCandidateIds) {
755
+ if (await worktree.merge(agentId)) {
756
+ mergedCount += 1;
757
+ }
758
+ }
759
+ const headHash = mergedCount > 0 && worktree.currentHead ? await worktree.currentHead() : directCommitHash;
760
+ emitWrfcAutoCommitted(this.runtimeBus, this.sessionId, chain.id, headHash ?? undefined);
681
761
  this.completeChainAsPassed(chain);
682
- logger.debug('WrfcController.autoCommit: success', { chainId: chain.id, agentId, merged });
762
+ completed = true;
763
+ logger.debug('WrfcController.autoCommit: success', {
764
+ chainId: chain.id,
765
+ commitCandidateIds,
766
+ directCommitHash,
767
+ mergedCount,
768
+ headHash,
769
+ });
683
770
  }
684
771
  catch (error) {
685
772
  const reason = summarizeError(error);
@@ -687,7 +774,10 @@ export class WrfcController {
687
774
  this.failChain(chain, `autoCommit failed: ${reason}`);
688
775
  }
689
776
  finally {
690
- for (const id of chain.allAgentIds) {
777
+ const cleanupIds = completed
778
+ ? chain.allAgentIds
779
+ : chain.allAgentIds.filter((id) => !commitCandidateIds.includes(id));
780
+ for (const id of cleanupIds) {
691
781
  worktree.cleanup(id).catch((error) => {
692
782
  logger.warn('WrfcController.autoCommit: cleanup failed', {
693
783
  agentId: id,
@@ -697,12 +787,49 @@ export class WrfcController {
697
787
  }
698
788
  }
699
789
  }
790
+ autoCommitCandidateAgentIds(chain) {
791
+ const candidates = [];
792
+ const add = (agentId) => {
793
+ if (agentId && !candidates.includes(agentId))
794
+ candidates.push(agentId);
795
+ };
796
+ if (chain.subtasks && chain.subtasks.length > 0) {
797
+ for (const subtask of chain.subtasks) {
798
+ add(subtask.fixerAgentId ?? subtask.engineerAgentId);
799
+ }
800
+ add(chain.integratorAgentId);
801
+ return candidates;
802
+ }
803
+ add(chain.fixerAgentId ?? chain.engineerAgentId);
804
+ add(chain.integratorAgentId);
805
+ if (candidates.length > 0) {
806
+ return candidates;
807
+ }
808
+ const writeRoles = new Set(['engineer', 'fixer', 'integrator']);
809
+ for (const agentId of chain.allAgentIds) {
810
+ const recordRole = this.agentManager.getStatus(agentId)?.wrfcRole;
811
+ const role = recordRole ?? this.workPlanRoleForAgent(chain, agentId);
812
+ if (role && writeRoles.has(role))
813
+ add(agentId);
814
+ }
815
+ return candidates;
816
+ }
817
+ autoCommitMessage(chain) {
818
+ const firstLine = chain.task.trim().replace(/\s+/g, ' ').slice(0, 72) || chain.id;
819
+ return `WRFC: ${firstLine}`;
820
+ }
700
821
  failChain(chain, reason) {
701
822
  if (chain.state === 'pending') {
702
823
  this.chainQueue = this.chainQueue.filter((queued) => queued.record.id !== chain.ownerAgentId);
703
824
  }
704
825
  const wasActive = chain.state !== 'passed' && chain.state !== 'failed' && chain.state !== 'pending';
705
826
  this.failCurrentNode(chain, reason);
827
+ for (const subtask of chain.subtasks ?? []) {
828
+ if (subtask.currentNodeId) {
829
+ failWrfcOrchestrationNode(this.runtimeBus, this.sessionId, chain.id, subtask.currentNodeId, reason);
830
+ subtask.currentNodeId = undefined;
831
+ }
832
+ }
706
833
  try {
707
834
  this.transition(chain, 'failed');
708
835
  }
@@ -854,7 +981,26 @@ export class WrfcController {
854
981
  failWrfcOrchestrationNode(this.runtimeBus, this.sessionId, chain.id, chain.currentNodeId, error);
855
982
  chain.currentNodeId = undefined;
856
983
  }
984
+ completeSubtaskNode(chain, subtask, summary) {
985
+ if (!subtask.currentNodeId)
986
+ return;
987
+ completeWrfcOrchestrationNode(this.runtimeBus, this.sessionId, chain.id, subtask.currentNodeId, summary);
988
+ subtask.currentNodeId = undefined;
989
+ }
857
990
  createBaseChain(ownerRecord) {
991
+ const subtasks = (ownerRecord.wrfcSubtasks ?? [])
992
+ .filter((task) => typeof task.task === 'string' && task.task.trim().length > 0)
993
+ .map((task, index) => ({
994
+ id: `deliverable-${index + 1}`,
995
+ title: task.task.trim().slice(0, 80),
996
+ task: task.task.trim(),
997
+ state: 'pending',
998
+ fixAttempts: 0,
999
+ reviewCycles: 0,
1000
+ reviewScores: [],
1001
+ constraints: [],
1002
+ constraintsEnumerated: false,
1003
+ }));
858
1004
  const chain = {
859
1005
  id: this.generateWrfcId(),
860
1006
  state: 'pending',
@@ -869,6 +1015,7 @@ export class WrfcController {
869
1015
  constraints: [],
870
1016
  constraintsEnumerated: false,
871
1017
  createdAt: Date.now(),
1018
+ ...(subtasks.length > 1 ? { subtasks } : {}),
872
1019
  };
873
1020
  this.chains.set(chain.id, chain);
874
1021
  emitWrfcGraphCreated(this.runtimeBus, this.sessionId, chain.id, `WRFC: ${ownerRecord.task}`);
@@ -886,6 +1033,10 @@ export class WrfcController {
886
1033
  return chain;
887
1034
  }
888
1035
  startEngineeringChain(chain, emitCreated) {
1036
+ if (chain.subtasks && chain.subtasks.length > 1) {
1037
+ this.startCompoundEngineeringChain(chain, emitCreated);
1038
+ return;
1039
+ }
889
1040
  this.activeChainCount += 1;
890
1041
  this.transition(chain, 'engineering');
891
1042
  this.setWrfcWorkPlanTaskStatus(chain, chain.ownerAgentId, 'in_progress');
@@ -909,6 +1060,34 @@ export class WrfcController {
909
1060
  });
910
1061
  this.upsertWrfcWorkPlanTask(chain, 'engineer', engineerRecord, 'in_progress');
911
1062
  }
1063
+ startCompoundEngineeringChain(chain, emitCreated) {
1064
+ this.activeChainCount += 1;
1065
+ this.transition(chain, 'engineering');
1066
+ this.setWrfcWorkPlanTaskStatus(chain, chain.ownerAgentId, 'in_progress');
1067
+ if (emitCreated) {
1068
+ emitWrfcChainCreated(this.runtimeBus, this.sessionId, chain.id, chain.task);
1069
+ }
1070
+ this.appendOwnerDecision(chain, 'compound_started', `Compound WRFC owner supervising ${chain.subtasks?.length ?? 0} deliverables under one chain`, { agentId: chain.ownerAgentId });
1071
+ for (const subtask of chain.subtasks ?? []) {
1072
+ subtask.state = 'engineering';
1073
+ const engineerRecord = this.spawnWrfcAgent(chain, 'engineer', 'engineer', this.buildSubtaskEngineerTask(chain, subtask), true, subtask.id);
1074
+ this.applyWrfcAgentMetadata(chain, engineerRecord, 'engineer', subtask.id);
1075
+ subtask.engineerAgentId = engineerRecord.id;
1076
+ chain.allAgentIds.push(engineerRecord.id);
1077
+ this.messageBus.registerAgent({
1078
+ agentId: engineerRecord.id,
1079
+ role: 'engineer',
1080
+ wrfcId: chain.id,
1081
+ });
1082
+ subtask.currentNodeId = startWrfcOrchestrationNode(this.runtimeBus, this.sessionId, chain.id, `subtask:${subtask.id}:engineer:0`, 'engineer', `Engineer ${subtask.title}`, engineerRecord.id);
1083
+ this.appendOwnerDecision(chain, 'spawn_engineer', this.withRouteReason(`Start compound WRFC engineer child for ${subtask.id}`, engineerRecord), {
1084
+ agentId: engineerRecord.id,
1085
+ role: 'engineer',
1086
+ record: engineerRecord,
1087
+ });
1088
+ this.upsertWrfcWorkPlanTask(chain, 'engineer', engineerRecord, 'in_progress', subtask.id);
1089
+ }
1090
+ }
912
1091
  handleEngineerCompletion(chain, agentId, report) {
913
1092
  let reportForReview = report;
914
1093
  this.completeCurrentNode(chain, report.summary);
@@ -981,6 +1160,278 @@ export class WrfcController {
981
1160
  }
982
1161
  this.startReview(chain, reportForReview);
983
1162
  }
1163
+ buildSubtaskEngineerTask(chain, subtask) {
1164
+ return [
1165
+ `Compound WRFC engineer task`,
1166
+ `Parent WRFC ask (authoritative whole):`,
1167
+ chain.task,
1168
+ ``,
1169
+ `Sub-deliverable ${subtask.id}:`,
1170
+ subtask.task,
1171
+ ``,
1172
+ `Instructions:`,
1173
+ `1. Implement only this sub-deliverable, but keep the parent ask in mind for compatibility.`,
1174
+ `2. Do not review or verify sibling deliverables. The WRFC owner controls review/fix phases after your output exists.`,
1175
+ `3. Return a structured EngineerReport JSON block.`,
1176
+ ].join('\n');
1177
+ }
1178
+ buildCompoundIntegrationTask(chain) {
1179
+ const subtaskSummaries = (chain.subtasks ?? []).map((subtask) => [
1180
+ `## ${subtask.id}: ${subtask.title}`,
1181
+ `Task: ${subtask.task}`,
1182
+ `Review cycles: ${subtask.reviewCycles}`,
1183
+ `Last score: ${subtask.reviewScores.at(-1) ?? 'n/a'}`,
1184
+ `Engineer summary: ${subtask.engineerReport?.summary ?? '(no summary)'}`,
1185
+ `Reviewer summary: ${subtask.reviewerReport?.summary ?? '(no review)'}`,
1186
+ ].join('\n')).join('\n\n');
1187
+ return [
1188
+ `Compound WRFC integration task`,
1189
+ `Parent WRFC ask (authoritative full scope):`,
1190
+ chain.task,
1191
+ ``,
1192
+ `All sub-deliverables have individually passed review. Integrate them into one coherent final result.`,
1193
+ ``,
1194
+ subtaskSummaries,
1195
+ ``,
1196
+ `Instructions:`,
1197
+ `1. Inspect the current workspace and the sub-deliverable outputs before editing.`,
1198
+ `2. Resolve cross-deliverable API, export, documentation, and test consistency issues.`,
1199
+ `3. Preserve all accepted sub-deliverable behavior; do not start unrelated new work.`,
1200
+ `4. Return a structured EngineerReport JSON block so the final reviewer can inspect integration changes.`,
1201
+ ].join('\n');
1202
+ }
1203
+ findSubtaskByAgentId(chain, agentId) {
1204
+ for (const subtask of chain.subtasks ?? []) {
1205
+ if (subtask.engineerAgentId === agentId
1206
+ || subtask.reviewerAgentId === agentId
1207
+ || subtask.fixerAgentId === agentId) {
1208
+ return subtask;
1209
+ }
1210
+ }
1211
+ return null;
1212
+ }
1213
+ async onCompoundSubtaskAgentComplete(chain, subtask, agentId, rawOutput, record) {
1214
+ if (agentId === subtask.engineerAgentId || agentId === subtask.fixerAgentId) {
1215
+ const report = parseEngineerCompletionReport(rawOutput, record?.template);
1216
+ this.setWrfcWorkPlanTaskStatus(chain, agentId, 'done');
1217
+ this.handleCompoundEngineerCompletion(chain, subtask, agentId, report);
1218
+ return;
1219
+ }
1220
+ if (agentId === subtask.reviewerAgentId) {
1221
+ const review = parseReviewerCompletionReport(chain.id, rawOutput, getWrfcScoreThreshold(this.configManager));
1222
+ subtask.reviewerReport = review;
1223
+ subtask.reviewCycles += 1;
1224
+ this.setWrfcWorkPlanTaskStatus(chain, agentId, 'done');
1225
+ await this.processCompoundSubtaskReview(chain, subtask, review);
1226
+ }
1227
+ }
1228
+ handleCompoundEngineerCompletion(chain, subtask, agentId, report) {
1229
+ let reportForReview = report;
1230
+ this.completeSubtaskNode(chain, subtask, report.summary);
1231
+ if (subtask.state === 'engineering') {
1232
+ subtask.engineerReport = report;
1233
+ this.workmap.append({
1234
+ ts: new Date().toISOString(),
1235
+ wrfcId: chain.id,
1236
+ event: 'engineer_complete',
1237
+ agentId,
1238
+ task: subtask.task,
1239
+ subtaskId: subtask.id,
1240
+ });
1241
+ }
1242
+ const isEngineerReportShape = (r) => r.archetype === 'engineer';
1243
+ if (!subtask.constraintsEnumerated) {
1244
+ subtask.constraints = isEngineerReportShape(report) ? (report.constraints ?? []) : [];
1245
+ subtask.constraintsEnumerated = true;
1246
+ }
1247
+ else if (isEngineerReportShape(report)) {
1248
+ const fixerConstraints = report.constraints ?? [];
1249
+ reportForReview = this.canonicalizeFixerReportConstraints(report, subtask.constraints);
1250
+ if (subtask.constraints.length === 0) {
1251
+ if (fixerConstraints.length > 0) {
1252
+ logger.warn('WrfcController: ignored compound fixer-invented constraints for unconstrained subtask', {
1253
+ chainId: chain.id,
1254
+ subtaskId: subtask.id,
1255
+ extra: fixerConstraints.map((constraint) => constraint.id),
1256
+ });
1257
+ }
1258
+ }
1259
+ else {
1260
+ const expectedIds = new Set(subtask.constraints.map((constraint) => constraint.id));
1261
+ const actualIds = new Set(fixerConstraints.map((constraint) => constraint.id));
1262
+ const missing = [...expectedIds].filter((id) => !actualIds.has(id));
1263
+ const extra = [...actualIds].filter((id) => !expectedIds.has(id));
1264
+ if (missing.length > 0 || extra.length > 0) {
1265
+ const description = `Fixer regressed constraint continuity for ${subtask.id}: missing=[${missing.join(',')}] extra=[${extra.join(',')}]`;
1266
+ logger.warn('WrfcController: compound fixer constraint-continuity violation', {
1267
+ chainId: chain.id,
1268
+ subtaskId: subtask.id,
1269
+ missing,
1270
+ extra,
1271
+ });
1272
+ subtask.syntheticIssues ??= [];
1273
+ subtask.syntheticIssues.push({ severity: 'critical', description });
1274
+ }
1275
+ }
1276
+ }
1277
+ else if (subtask.constraints.length > 0) {
1278
+ const description = `Fixer regressed constraint continuity for ${subtask.id}: missing=[${subtask.constraints.map((constraint) => constraint.id).join(',')}] extra=[]`;
1279
+ logger.warn('WrfcController: compound fixer constraint-continuity violation', {
1280
+ chainId: chain.id,
1281
+ subtaskId: subtask.id,
1282
+ missing: subtask.constraints.map((constraint) => constraint.id),
1283
+ extra: [],
1284
+ });
1285
+ subtask.syntheticIssues ??= [];
1286
+ subtask.syntheticIssues.push({ severity: 'critical', description });
1287
+ }
1288
+ subtask.engineerReport = reportForReview;
1289
+ this.startCompoundSubtaskReview(chain, subtask, reportForReview);
1290
+ }
1291
+ startCompoundSubtaskReview(chain, subtask, report) {
1292
+ subtask.state = 'reviewing';
1293
+ let reviewTask = buildReviewTask(chain.id, `Parent WRFC ask:\n${chain.task}\n\nSub-deliverable ${subtask.id}:\n${subtask.task}`, report, getWrfcScoreThreshold(this.configManager), subtask.constraints);
1294
+ if (subtask.syntheticIssues && subtask.syntheticIssues.length > 0) {
1295
+ const syntheticBlock = [
1296
+ `## Synthetic issues from controller`,
1297
+ ``,
1298
+ ...subtask.syntheticIssues.map((issue) => `- [${issue.severity.toUpperCase()}] ${issue.description}`),
1299
+ ].join('\n');
1300
+ reviewTask = syntheticBlock + '\n\n---\n\n' + reviewTask;
1301
+ subtask.syntheticIssues = [];
1302
+ }
1303
+ const reviewerRecord = this.spawnWrfcAgent(chain, 'reviewer', 'reviewer', reviewTask, true, subtask.id);
1304
+ subtask.reviewerAgentId = reviewerRecord.id;
1305
+ this.applyWrfcAgentMetadata(chain, reviewerRecord, 'reviewer', subtask.id);
1306
+ chain.allAgentIds.push(reviewerRecord.id);
1307
+ this.messageBus.registerAgent({
1308
+ agentId: reviewerRecord.id,
1309
+ role: 'reviewer',
1310
+ wrfcId: chain.id,
1311
+ });
1312
+ subtask.currentNodeId = startWrfcOrchestrationNode(this.runtimeBus, this.sessionId, chain.id, `subtask:${subtask.id}:review:${subtask.reviewCycles + 1}`, 'reviewer', `Review ${subtask.title}`, reviewerRecord.id);
1313
+ this.appendOwnerDecision(chain, 'spawn_reviewer', this.withRouteReason(`Review compound sub-deliverable ${subtask.id} after engineer output exists`, reviewerRecord), {
1314
+ agentId: reviewerRecord.id,
1315
+ role: 'reviewer',
1316
+ record: reviewerRecord,
1317
+ });
1318
+ this.upsertWrfcWorkPlanTask(chain, 'reviewer', reviewerRecord, 'in_progress', subtask.id);
1319
+ }
1320
+ async processCompoundSubtaskReview(chain, subtask, review) {
1321
+ const threshold = getWrfcScoreThreshold(this.configManager);
1322
+ const constraintEvaluation = this.evaluateSubtaskConstraints(subtask, review);
1323
+ const passed = review.score >= threshold && !constraintEvaluation.constraintFailure;
1324
+ this.completeSubtaskNode(chain, subtask, `Score ${review.score}/10${passed ? ' passed' : ' needs fixes'}`);
1325
+ emitWorkflowReviewCompleted(this.runtimeBus, createWrfcWorkflowContext(this.sessionId, chain.id), {
1326
+ chainId: chain.id,
1327
+ score: review.score,
1328
+ passed,
1329
+ ...(subtask.constraints.length > 0
1330
+ ? {
1331
+ constraintsSatisfied: constraintEvaluation.constraintsSatisfied,
1332
+ constraintsTotal: constraintEvaluation.constraintsTotal,
1333
+ unsatisfiedConstraintIds: constraintEvaluation.unsatisfiedConstraintIds,
1334
+ }
1335
+ : {}),
1336
+ });
1337
+ this.workmap.append({
1338
+ ts: new Date().toISOString(),
1339
+ wrfcId: chain.id,
1340
+ event: 'review_complete',
1341
+ agentId: subtask.reviewerAgentId,
1342
+ score: review.score,
1343
+ passed,
1344
+ subtaskId: subtask.id,
1345
+ issues: review.issues?.slice(0, 10).map((issue) => ({
1346
+ severity: issue.severity,
1347
+ description: issue.description,
1348
+ file: issue.file,
1349
+ })),
1350
+ });
1351
+ subtask.reviewScores.push(review.score);
1352
+ if (passed) {
1353
+ subtask.state = 'passed';
1354
+ this.appendOwnerDecision(chain, 'subtask_review_passed', `Sub-deliverable ${subtask.id} passed review with ${review.score}/10`, {
1355
+ agentId: subtask.reviewerAgentId,
1356
+ role: 'reviewer',
1357
+ reviewScore: review.score,
1358
+ });
1359
+ if ((chain.subtasks ?? []).every((candidate) => candidate.state === 'passed')) {
1360
+ this.startIntegration(chain);
1361
+ }
1362
+ return;
1363
+ }
1364
+ const maxFixAttempts = getWrfcMaxFixAttempts(this.configManager);
1365
+ if (subtask.fixAttempts >= maxFixAttempts) {
1366
+ subtask.state = 'failed';
1367
+ this.failChain(chain, `Sub-deliverable ${subtask.id} review score ${review.score}/10 below threshold ${threshold}/10 after ${subtask.fixAttempts} fix attempt${subtask.fixAttempts !== 1 ? 's' : ''}`);
1368
+ return;
1369
+ }
1370
+ this.appendOwnerDecision(chain, 'subtask_review_failed', `Sub-deliverable ${subtask.id} review did not pass`, {
1371
+ agentId: subtask.reviewerAgentId,
1372
+ role: 'reviewer',
1373
+ reviewScore: review.score,
1374
+ });
1375
+ this.startCompoundSubtaskFix(chain, subtask, review);
1376
+ }
1377
+ startCompoundSubtaskFix(chain, subtask, review) {
1378
+ subtask.fixAttempts += 1;
1379
+ subtask.state = 'fixing';
1380
+ const targetConstraintIds = this.evaluateSubtaskConstraints(subtask, review).unsatisfiedConstraintIds;
1381
+ emitWorkflowFixAttempted(this.runtimeBus, createWrfcWorkflowContext(this.sessionId, chain.id), {
1382
+ chainId: chain.id,
1383
+ attempt: subtask.fixAttempts,
1384
+ maxAttempts: getWrfcMaxFixAttempts(this.configManager),
1385
+ ...(targetConstraintIds.length > 0 ? { targetConstraintIds } : {}),
1386
+ });
1387
+ const fixerRecord = this.spawnWrfcAgent(chain, 'fixer', 'engineer', buildFixTask(chain.id, `Parent WRFC ask:\n${chain.task}\n\nSub-deliverable ${subtask.id}:\n${subtask.task}`, review, getWrfcScoreThreshold(this.configManager), subtask.fixAttempts, subtask.constraints, review.constraintFindings ?? []), true, subtask.id);
1388
+ subtask.fixerAgentId = fixerRecord.id;
1389
+ this.applyWrfcAgentMetadata(chain, fixerRecord, 'fixer', subtask.id);
1390
+ chain.allAgentIds.push(fixerRecord.id);
1391
+ this.messageBus.registerAgent({
1392
+ agentId: fixerRecord.id,
1393
+ role: 'fixer',
1394
+ wrfcId: chain.id,
1395
+ });
1396
+ subtask.currentNodeId = startWrfcOrchestrationNode(this.runtimeBus, this.sessionId, chain.id, `subtask:${subtask.id}:fix:${subtask.fixAttempts}`, 'fixer', `Fix ${subtask.title}`, fixerRecord.id);
1397
+ this.appendOwnerDecision(chain, 'spawn_fixer', this.withRouteReason(`Fix compound sub-deliverable ${subtask.id}`, fixerRecord), {
1398
+ agentId: fixerRecord.id,
1399
+ role: 'fixer',
1400
+ record: fixerRecord,
1401
+ });
1402
+ this.upsertWrfcWorkPlanTask(chain, 'fixer', fixerRecord, 'in_progress', subtask.id);
1403
+ }
1404
+ startIntegration(chain) {
1405
+ this.transition(chain, 'integrating');
1406
+ const integratorRecord = this.spawnWrfcAgent(chain, 'integrator', 'integrator', this.buildCompoundIntegrationTask(chain), true);
1407
+ chain.integratorAgentId = integratorRecord.id;
1408
+ this.applyWrfcAgentMetadata(chain, integratorRecord, 'integrator');
1409
+ chain.allAgentIds.push(integratorRecord.id);
1410
+ this.messageBus.registerAgent({
1411
+ agentId: integratorRecord.id,
1412
+ role: 'integrator',
1413
+ wrfcId: chain.id,
1414
+ });
1415
+ chain.currentNodeId = startWrfcOrchestrationNode(this.runtimeBus, this.sessionId, chain.id, `integrator:${Date.now()}`, 'integrator', 'Integrate passed deliverables', integratorRecord.id);
1416
+ this.appendOwnerDecision(chain, 'spawn_integrator', this.withRouteReason('Integrate all passed compound WRFC deliverables before final full-scope review', integratorRecord), {
1417
+ agentId: integratorRecord.id,
1418
+ role: 'integrator',
1419
+ record: integratorRecord,
1420
+ });
1421
+ this.upsertWrfcWorkPlanTask(chain, 'integrator', integratorRecord, 'in_progress');
1422
+ }
1423
+ handleIntegratorCompletion(chain, agentId, report) {
1424
+ chain.integratorReport = report;
1425
+ this.completeCurrentNode(chain, report.summary);
1426
+ this.workmap.append({
1427
+ ts: new Date().toISOString(),
1428
+ wrfcId: chain.id,
1429
+ event: 'integrator_complete',
1430
+ agentId,
1431
+ task: chain.task,
1432
+ });
1433
+ this.startReview(chain, report);
1434
+ }
984
1435
  canonicalizeFixerReportConstraints(report, constraints) {
985
1436
  const canonical = {
986
1437
  ...report,
@@ -997,7 +1448,7 @@ export class WrfcController {
997
1448
  ].join('\n');
998
1449
  return canonical;
999
1450
  }
1000
- spawnWrfcAgent(chain, role, template, task, dangerouslyDisableWrfc) {
1451
+ spawnWrfcAgent(chain, role, template, task, dangerouslyDisableWrfc, subtaskId) {
1001
1452
  const owner = this.agentManager.getStatus(chain.ownerAgentId);
1002
1453
  const selectedRoute = this.selectChildRoute?.({ chain, role, task, ownerAgent: owner }) ?? null;
1003
1454
  const model = selectedRoute?.model ?? owner?.model;
@@ -1016,9 +1467,13 @@ export class WrfcController {
1016
1467
  ...(routing ? { routing } : {}),
1017
1468
  ...(reasoningEffort ? { reasoningEffort } : {}),
1018
1469
  ...(template === 'engineer' ? { systemPromptAddendum: '\n\n---\n\n' + buildEngineerConstraintAddendum() } : {}),
1470
+ ...(template === 'integrator' ? { systemPromptAddendum: '\n\n---\n\n' + buildEngineerConstraintAddendum() } : {}),
1019
1471
  ...(dangerouslyDisableWrfc ? { dangerously_disable_wrfc: true } : {}),
1020
1472
  });
1021
1473
  record.wrfcId = chain.id;
1474
+ if (subtaskId) {
1475
+ record.wrfcSubtaskId = subtaskId;
1476
+ }
1022
1477
  if (selectedRoute?.reason) {
1023
1478
  record.wrfcRouteReason = selectedRoute.reason;
1024
1479
  }
@@ -1075,16 +1530,18 @@ export class WrfcController {
1075
1530
  });
1076
1531
  }
1077
1532
  }
1078
- upsertWrfcWorkPlanTask(chain, role, record, status) {
1533
+ upsertWrfcWorkPlanTask(chain, role, record, status, subtaskId) {
1079
1534
  if (!this.workPlanService)
1080
1535
  return;
1081
1536
  const taskId = this.workPlanTaskIdForAgent(chain, record.id, role);
1082
1537
  const task = {
1083
1538
  taskId,
1084
- title: this.workPlanTaskTitle(role, chain.task),
1539
+ title: this.workPlanTaskTitle(role, role === 'owner' ? chain.task : record.task),
1085
1540
  notes: role === 'owner'
1086
1541
  ? 'WRFC owner chain supervising lifecycle child agents.'
1087
- : `WRFC ${role} phase for the owner chain.`,
1542
+ : subtaskId
1543
+ ? `WRFC ${role} phase for compound deliverable ${subtaskId}.`
1544
+ : `WRFC ${role} phase for the owner chain.`,
1088
1545
  owner: role,
1089
1546
  status,
1090
1547
  source: 'wrfc',
@@ -1097,6 +1554,7 @@ export class WrfcController {
1097
1554
  metadata: {
1098
1555
  wrfcState: chain.state,
1099
1556
  agentTemplate: record.template,
1557
+ ...(subtaskId ? { wrfcSubtaskId: subtaskId } : {}),
1100
1558
  },
1101
1559
  };
1102
1560
  this.enqueueWrfcWorkPlanTaskOperation(taskId, async () => {
@@ -1177,6 +1635,16 @@ export class WrfcController {
1177
1635
  return 'reviewer';
1178
1636
  if (agentId === chain.fixerAgentId)
1179
1637
  return 'fixer';
1638
+ if (agentId === chain.integratorAgentId)
1639
+ return 'integrator';
1640
+ for (const subtask of chain.subtasks ?? []) {
1641
+ if (agentId === subtask.engineerAgentId)
1642
+ return 'engineer';
1643
+ if (agentId === subtask.reviewerAgentId)
1644
+ return 'reviewer';
1645
+ if (agentId === subtask.fixerAgentId)
1646
+ return 'fixer';
1647
+ }
1180
1648
  return null;
1181
1649
  }
1182
1650
  workPlanTaskIdForAgent(chain, agentId, role) {
@@ -6,7 +6,7 @@ export type WorkflowContext = {
6
6
  traceId: string;
7
7
  source: string;
8
8
  };
9
- export type WrfcNodeRole = 'engineer' | 'reviewer' | 'fixer' | 'verifier';
9
+ export type WrfcNodeRole = 'orchestrator' | 'engineer' | 'reviewer' | 'fixer' | 'integrator' | 'verifier';
10
10
  export declare function createWrfcWorkflowContext(sessionId: string, chainId: string): WorkflowContext;
11
11
  export declare function createWrfcOrchestrationGraphId(chainId: string): string;
12
12
  export declare function emitWrfcStateChanged(runtimeBus: RuntimeEventBus, sessionId: string, chainId: string, from: WrfcState, to: WrfcState): void;