taskplane 0.24.21 → 0.24.23

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.
@@ -1541,6 +1541,7 @@ export async function executeWave(
1541
1541
  workspaceConfig?: WorkspaceConfig | null,
1542
1542
  runtimeBackend?: RuntimeBackend,
1543
1543
  onSupervisorAlert?: SupervisorAlertCallback,
1544
+ supervisorAutonomy: "interactive" | "supervised" | "autonomous" = "autonomous",
1544
1545
  ): Promise<WaveExecutionResult> {
1545
1546
  const startedAt = Date.now();
1546
1547
  const policy = config.failure.on_task_failure;
@@ -1642,7 +1643,10 @@ export async function executeWave(
1642
1643
  }
1643
1644
 
1644
1645
  const lanePromises = lanes.map(lane =>
1645
- executeLaneV2(lane, config, repoRoot, wavePauseSignal, wsRoot, isWsMode, { ORCH_BATCH_ID: batchId }, onSupervisorAlert),
1646
+ executeLaneV2(lane, config, repoRoot, wavePauseSignal, wsRoot, isWsMode, {
1647
+ ORCH_BATCH_ID: batchId,
1648
+ TASKPLANE_SUPERVISOR_AUTONOMY: supervisorAutonomy,
1649
+ }, onSupervisorAlert),
1646
1650
  );
1647
1651
 
1648
1652
  // Start monitoring as a sibling async loop
@@ -2235,6 +2239,12 @@ export async function executeLaneV2(
2235
2239
  // Build execution unit
2236
2240
  const unit = buildExecutionUnit(lane, task, repoRoot, isWorkspaceMode);
2237
2241
 
2242
+ const rawAutonomy = String(extraEnvVars?.TASKPLANE_SUPERVISOR_AUTONOMY ?? "autonomous").toLowerCase();
2243
+ const supervisorAutonomy: LaneRunnerConfig["supervisorAutonomy"] =
2244
+ (rawAutonomy === "interactive" || rawAutonomy === "supervised" || rawAutonomy === "autonomous")
2245
+ ? rawAutonomy as LaneRunnerConfig["supervisorAutonomy"]
2246
+ : "autonomous";
2247
+
2238
2248
  const laneRunnerConfig: LaneRunnerConfig = {
2239
2249
  batchId,
2240
2250
  agentIdPrefix,
@@ -2247,6 +2257,7 @@ export async function executeLaneV2(
2247
2257
  workerTools: "read,write,edit,bash,grep,find,ls",
2248
2258
  workerThinking: "",
2249
2259
  workerSystemPrompt,
2260
+ supervisorAutonomy,
2250
2261
  projectName: config.project?.name || "project",
2251
2262
  maxIterations: 20,
2252
2263
  noProgressLimit: 3,
@@ -1032,6 +1032,7 @@ export function startBatchInWorker(
1032
1032
  wkData.agentRoot,
1033
1033
  wkData.force ?? false,
1034
1034
  onSupervisorAlert ?? null,
1035
+ wkData.supervisorAutonomy ?? "autonomous",
1035
1036
  )
1036
1037
  : () => executeOrchBatch(
1037
1038
  wkData.args ?? "",
@@ -1046,6 +1047,7 @@ export function startBatchInWorker(
1046
1047
  wkData.agentRoot,
1047
1048
  null, // onEngineEvent
1048
1049
  onSupervisorAlert ?? null,
1050
+ wkData.supervisorAutonomy ?? "autonomous",
1049
1051
  );
1050
1052
  startBatchAsync(fallbackFn, batchState, ctx, updateWidget, onTerminal);
1051
1053
  return null;
@@ -2052,6 +2054,7 @@ export default function (pi: ExtensionAPI) {
2052
2054
  : null,
2053
2055
  workspaceRoot: execCtx!.workspaceRoot,
2054
2056
  agentRoot: execCtx!.pointer?.agentRoot,
2057
+ supervisorAutonomy: supervisorConfig.autonomy,
2055
2058
  },
2056
2059
  orchBatchState,
2057
2060
  ctx,
@@ -2396,6 +2399,7 @@ export default function (pi: ExtensionAPI) {
2396
2399
  workspaceRoot: execCtx!.workspaceRoot,
2397
2400
  agentRoot: execCtx!.pointer?.agentRoot,
2398
2401
  force,
2402
+ supervisorAutonomy: supervisorConfig.autonomy,
2399
2403
  },
2400
2404
  orchBatchState,
2401
2405
  ctx,
@@ -92,6 +92,8 @@ export interface LaneRunnerConfig {
92
92
  workerThinking: string;
93
93
  /** Worker system prompt */
94
94
  workerSystemPrompt: string;
95
+ /** Supervisor autonomy level for bridge-tool guards. */
96
+ supervisorAutonomy?: "interactive" | "supervised" | "autonomous";
95
97
  /** Project name (for review request context) */
96
98
  projectName?: string;
97
99
  /** Max worker iterations before giving up */
@@ -312,6 +314,9 @@ export async function executeTaskV2(
312
314
  TASKPLANE_REVIEWS_DIR: unit.packet.reviewsDir,
313
315
  TASKPLANE_REVIEWER_STATE_PATH: reviewerStatePath,
314
316
  TASKPLANE_PROJECT_NAME: config.projectName || "project",
317
+ TASKPLANE_TASK_ID: taskId,
318
+ TASKPLANE_ACTIVE_SEGMENT_ID: segmentId ?? "",
319
+ TASKPLANE_SUPERVISOR_AUTONOMY: config.supervisorAutonomy || "autonomous",
315
320
  ORCH_BATCH_ID: config.batchId,
316
321
  },
317
322
  };
@@ -611,7 +611,7 @@ export async function spawnMergeAgentV2(
611
611
  packet: null,
612
612
  env: {
613
613
  ORCH_BATCH_ID: bid,
614
- // Isolate merge agent from operator's user preferences to prevent
614
+ // Isolate merge agent from operator's global preferences to prevent
615
615
  // verification test contamination (e.g., stale reviewerModel pref
616
616
  // overriding schema defaults in deep-equal assertions).
617
617
  PI_CODING_AGENT_DIR: join(sidecarRoot, "runtime", bid, "merge-agent-env"),
@@ -1849,6 +1849,12 @@ export async function mergeWave(
1849
1849
 
1850
1850
  for (const lane of orderedLanes) {
1851
1851
  for (const allocTask of lane.tasks) {
1852
+ if (!allocTask.task.taskFolder?.trim()) {
1853
+ execLog("merge", `W${waveIndex}`, `skipping task with missing taskFolder (possibly dynamically expanded)`, {
1854
+ taskId: allocTask.taskId,
1855
+ });
1856
+ continue;
1857
+ }
1852
1858
  const absFolder = resolve(allocTask.task.taskFolder);
1853
1859
  const relFolder = relative(resolvedRepoRoot, absFolder).replace(/\\/g, "/");
1854
1860
 
@@ -1139,6 +1139,18 @@ export function validatePersistedState(data: unknown): PersistedBatchState {
1139
1139
  );
1140
1140
  }
1141
1141
  }
1142
+ if (s.expandedFrom !== undefined && typeof s.expandedFrom !== "string") {
1143
+ throw new StateFileError(
1144
+ "STATE_SCHEMA_INVALID",
1145
+ `segments[${i}].expandedFrom is not a string when present (got ${typeof s.expandedFrom})`,
1146
+ );
1147
+ }
1148
+ if (s.expansionRequestId !== undefined && typeof s.expansionRequestId !== "string") {
1149
+ throw new StateFileError(
1150
+ "STATE_SCHEMA_INVALID",
1151
+ `segments[${i}].expansionRequestId is not a string when present (got ${typeof s.expansionRequestId})`,
1152
+ );
1153
+ }
1142
1154
  // Optional exitDiagnostic
1143
1155
  if (s.exitDiagnostic !== undefined) {
1144
1156
  if (!s.exitDiagnostic || typeof s.exitDiagnostic !== "object" || Array.isArray(s.exitDiagnostic)) {
@@ -641,6 +641,84 @@ export function getMergeStatusForWave(
641
641
  return null;
642
642
  }
643
643
 
644
+ /**
645
+ * Expand persisted wave plan with continuation rounds required by segment counts.
646
+ *
647
+ * Groups missing rounds by the original last-occurrence wave so resumed execution
648
+ * preserves multi-task round concurrency semantics (`[A,B]`, then `[A]`, etc.).
649
+ */
650
+ export function buildResumeRuntimeWavePlan(persistedState: PersistedBatchState): string[][] {
651
+ const baseWavePlan = persistedState.wavePlan.map((wave) => [...wave]);
652
+ const runtimeWavePlan = [...baseWavePlan];
653
+ const segmentCountByTaskId = new Map<string, number>();
654
+ for (const task of persistedState.tasks) {
655
+ if (Array.isArray(task.segmentIds) && task.segmentIds.length > 0) {
656
+ segmentCountByTaskId.set(task.taskId, task.segmentIds.length);
657
+ }
658
+ }
659
+
660
+ const scheduledCountByTaskId = new Map<string, number>();
661
+ const lastWaveIndexByTaskId = new Map<string, number>();
662
+ for (let waveIdx = 0; waveIdx < baseWavePlan.length; waveIdx++) {
663
+ for (const taskId of baseWavePlan[waveIdx]) {
664
+ scheduledCountByTaskId.set(taskId, (scheduledCountByTaskId.get(taskId) ?? 0) + 1);
665
+ lastWaveIndexByTaskId.set(taskId, waveIdx);
666
+ }
667
+ }
668
+
669
+ const missingByLastWaveIndex = new Map<number, Map<string, number>>();
670
+ for (const [taskId, segmentCount] of segmentCountByTaskId.entries()) {
671
+ const scheduledCount = scheduledCountByTaskId.get(taskId) ?? 0;
672
+ if (segmentCount <= scheduledCount) continue;
673
+ const lastWaveIndex = lastWaveIndexByTaskId.get(taskId) ?? -1;
674
+ if (!missingByLastWaveIndex.has(lastWaveIndex)) {
675
+ missingByLastWaveIndex.set(lastWaveIndex, new Map<string, number>());
676
+ }
677
+ missingByLastWaveIndex.get(lastWaveIndex)!.set(taskId, segmentCount - scheduledCount);
678
+ }
679
+
680
+ let offset = 0;
681
+ for (let baseWaveIdx = 0; baseWaveIdx < baseWavePlan.length; baseWaveIdx++) {
682
+ const missingForWave = missingByLastWaveIndex.get(baseWaveIdx);
683
+ if (!missingForWave || missingForWave.size === 0) continue;
684
+ const rounds: string[][] = [];
685
+ const remaining = new Map(missingForWave);
686
+ while ([...remaining.values()].some((count) => count > 0)) {
687
+ const roundTaskIds = [...remaining.entries()]
688
+ .filter(([, count]) => count > 0)
689
+ .map(([taskId]) => taskId)
690
+ .sort((a, b) => a.localeCompare(b));
691
+ if (roundTaskIds.length === 0) break;
692
+ rounds.push(roundTaskIds);
693
+ for (const taskId of roundTaskIds) {
694
+ remaining.set(taskId, (remaining.get(taskId) ?? 0) - 1);
695
+ }
696
+ }
697
+ if (rounds.length > 0) {
698
+ runtimeWavePlan.splice(baseWaveIdx + 1 + offset, 0, ...rounds);
699
+ offset += rounds.length;
700
+ }
701
+ }
702
+
703
+ const dangling = missingByLastWaveIndex.get(-1);
704
+ if (dangling && dangling.size > 0) {
705
+ const remaining = new Map(dangling);
706
+ while ([...remaining.values()].some((count) => count > 0)) {
707
+ const roundTaskIds = [...remaining.entries()]
708
+ .filter(([, count]) => count > 0)
709
+ .map(([taskId]) => taskId)
710
+ .sort((a, b) => a.localeCompare(b));
711
+ if (roundTaskIds.length === 0) break;
712
+ runtimeWavePlan.push(roundTaskIds);
713
+ for (const taskId of roundTaskIds) {
714
+ remaining.set(taskId, (remaining.get(taskId) ?? 0) - 1);
715
+ }
716
+ }
717
+ }
718
+
719
+ return runtimeWavePlan;
720
+ }
721
+
644
722
  /**
645
723
  * Compute the resume point from reconciled task states and wave plan.
646
724
  *
@@ -662,6 +740,7 @@ export function getMergeStatusForWave(
662
740
  export function computeResumePoint(
663
741
  persistedState: PersistedBatchState,
664
742
  reconciledTasks: ReconciledTaskState[],
743
+ wavePlan: string[][] = persistedState.wavePlan,
665
744
  ): ResumePoint {
666
745
  // Build lookup: taskId → reconciled state
667
746
  const reconciledMap = new Map<string, ReconciledTaskState>();
@@ -684,8 +763,8 @@ export function computeResumePoint(
684
763
  }
685
764
  const waveSegmentIdByTaskOccurrence = new Map<string, string>();
686
765
  const occurrenceByTaskId = new Map<string, number>();
687
- for (let waveIdx = 0; waveIdx < persistedState.wavePlan.length; waveIdx++) {
688
- for (const taskId of persistedState.wavePlan[waveIdx]) {
766
+ for (let waveIdx = 0; waveIdx < wavePlan.length; waveIdx++) {
767
+ for (const taskId of wavePlan[waveIdx]) {
689
768
  const segmentIds = segmentIdsByTaskId.get(taskId);
690
769
  if (!segmentIds || segmentIds.length === 0) continue;
691
770
  const occurrence = occurrenceByTaskId.get(taskId) ?? 0;
@@ -737,11 +816,11 @@ export function computeResumePoint(
737
816
  // Find resume wave: first wave with any non-completed tasks OR missing/failed merge.
738
817
  // TP-037 (Bug #102): A wave where all tasks are terminal but the merge
739
818
  // hasn't succeeded is flagged for merge retry, not skipped.
740
- let resumeWaveIndex = persistedState.wavePlan.length; // default: past end = all done
819
+ let resumeWaveIndex = wavePlan.length; // default: past end = all done
741
820
  const mergeRetryWaveIndexes: number[] = [];
742
821
 
743
- for (let i = 0; i < persistedState.wavePlan.length; i++) {
744
- const waveTasks = persistedState.wavePlan[i];
822
+ for (let i = 0; i < wavePlan.length; i++) {
823
+ const waveTasks = wavePlan[i];
745
824
  const allDone = waveTasks.every((taskId) => {
746
825
  const waveSegmentId = waveSegmentIdByTaskOccurrence.get(`${i}:${taskId}`);
747
826
  if (waveSegmentId && segmentStatusBySegmentId.has(waveSegmentId)) {
@@ -769,7 +848,7 @@ export function computeResumePoint(
769
848
  if (!allDone) {
770
849
  // Only set resumeWaveIndex if not already set by a merge retry
771
850
  // (merge retry at an earlier wave takes precedence)
772
- if (resumeWaveIndex === persistedState.wavePlan.length) {
851
+ if (resumeWaveIndex === wavePlan.length) {
773
852
  resumeWaveIndex = i;
774
853
  }
775
854
  break;
@@ -795,7 +874,7 @@ export function computeResumePoint(
795
874
  if (mergeStatus !== "succeeded") {
796
875
  // Merge missing or failed — flag for retry, don't skip past this wave
797
876
  mergeRetryWaveIndexes.push(i);
798
- if (resumeWaveIndex === persistedState.wavePlan.length) {
877
+ if (resumeWaveIndex === wavePlan.length) {
799
878
  // This is the first wave needing attention — set resume point here
800
879
  resumeWaveIndex = i;
801
880
  }
@@ -805,8 +884,8 @@ export function computeResumePoint(
805
884
 
806
885
  // Determine pending tasks: tasks in resume wave and later that need execution
807
886
  const actualPendingTaskIds: string[] = [];
808
- for (let i = resumeWaveIndex; i < persistedState.wavePlan.length; i++) {
809
- for (const taskId of persistedState.wavePlan[i]) {
887
+ for (let i = resumeWaveIndex; i < wavePlan.length; i++) {
888
+ for (const taskId of wavePlan[i]) {
810
889
  const waveSegmentId = waveSegmentIdByTaskOccurrence.get(`${i}:${taskId}`);
811
890
  if (waveSegmentId && segmentStatusBySegmentId.has(waveSegmentId)) {
812
891
  const segmentStatus = segmentStatusBySegmentId.get(waveSegmentId)!;
@@ -985,6 +1064,7 @@ export async function resumeOrchBatch(
985
1064
  agentRoot?: string,
986
1065
  force: boolean = false,
987
1066
  onSupervisorAlert?: import("./types.ts").SupervisorAlertCallback | null,
1067
+ supervisorAutonomy: "interactive" | "supervised" | "autonomous" = "autonomous",
988
1068
  ): Promise<void> {
989
1069
  const repoRoot = cwd;
990
1070
  // State files (.pi/batch-state.json, lane-state, etc.) belong in the workspace root,
@@ -1103,11 +1183,12 @@ export async function resumeOrchBatch(
1103
1183
  });
1104
1184
  }
1105
1185
 
1186
+ const runtimeWavePlan = buildResumeRuntimeWavePlan(persistedState);
1106
1187
  // TP-108/112: Runtime V2 backend selection for resumed batches.
1107
1188
  // MUST be computed before any backend-aware branch (section 3+).
1108
1189
  const resumeBackend: RuntimeBackend = selectRuntimeBackend(
1109
1190
  "all",
1110
- persistedState.wavePlan,
1191
+ runtimeWavePlan,
1111
1192
  workspaceConfig,
1112
1193
  ).backend;
1113
1194
  execLog("resume", batchState.batchId, `runtime backend for resumed execution: ${resumeBackend}`);
@@ -1176,7 +1257,7 @@ export async function resumeOrchBatch(
1176
1257
  }
1177
1258
 
1178
1259
  // ── 5. Compute resume point ──────────────────────────────────
1179
- const resumePoint = computeResumePoint(persistedState, reconciledTasks);
1260
+ const resumePoint = computeResumePoint(persistedState, reconciledTasks, runtimeWavePlan);
1180
1261
  const completedTaskSet = new Set(resumePoint.completedTaskIds);
1181
1262
  const failedTaskSet = new Set(resumePoint.failedTaskIds);
1182
1263
  const reconnectTaskSet = new Set(resumePoint.reconnectTaskIds);
@@ -1263,8 +1344,8 @@ export async function resumeOrchBatch(
1263
1344
  // entered, so they were never counted in blockedTasks.
1264
1345
  if (persistedBlockedTaskIds.size > 0) {
1265
1346
  let uncountedBlocked = 0;
1266
- for (let wi = resumePoint.resumeWaveIndex; wi < persistedState.wavePlan.length; wi++) {
1267
- for (const taskId of persistedState.wavePlan[wi]) {
1347
+ for (let wi = resumePoint.resumeWaveIndex; wi < runtimeWavePlan.length; wi++) {
1348
+ for (const taskId of runtimeWavePlan[wi]) {
1268
1349
  if (persistedBlockedTaskIds.has(taskId)) {
1269
1350
  uncountedBlocked++;
1270
1351
  }
@@ -1284,6 +1365,8 @@ export async function resumeOrchBatch(
1284
1365
  // v3: Carry forward resilience and diagnostics from persisted state
1285
1366
  batchState.resilience = persistedState.resilience;
1286
1367
  batchState.diagnostics = persistedState.diagnostics;
1368
+ // v4: Carry forward segment records (including dynamically expanded segments)
1369
+ batchState.segments = [...(persistedState.segments ?? [])];
1287
1370
  // Carry forward unknown fields for roundtrip preservation
1288
1371
  if (persistedState._extraFields) {
1289
1372
  batchState._extraFields = persistedState._extraFields;
@@ -1303,6 +1386,28 @@ export async function resumeOrchBatch(
1303
1386
  const depGraph = buildDependencyGraph(discovery.pending, discovery.completed);
1304
1387
  batchState.dependencyGraph = depGraph;
1305
1388
 
1389
+ // Rehydrate discovered tasks with persisted segment metadata.
1390
+ // Dynamically expanded segments may reference tasks that have segment-level
1391
+ // fields (segmentIds, activeSegmentId, packetRepoId, packetTaskPath) set
1392
+ // during the prior run. Merge these back into discovered ParsedTask records
1393
+ // so execution can resume with correct segment context.
1394
+ for (const persistedTask of persistedState.tasks) {
1395
+ const parsed = discovery.pending.get(persistedTask.taskId);
1396
+ if (!parsed) continue;
1397
+ if (persistedTask.segmentIds?.length) {
1398
+ parsed.segmentIds = persistedTask.segmentIds;
1399
+ }
1400
+ if (persistedTask.activeSegmentId !== undefined) {
1401
+ parsed.activeSegmentId = persistedTask.activeSegmentId;
1402
+ }
1403
+ if (persistedTask.packetRepoId) {
1404
+ parsed.packetRepoId = persistedTask.packetRepoId;
1405
+ }
1406
+ if (persistedTask.packetTaskPath) {
1407
+ parsed.packetTaskPath = persistedTask.packetTaskPath;
1408
+ }
1409
+ }
1410
+
1306
1411
 
1307
1412
  // ── 8. Handle alive sessions (reconnect) ─────────────────────
1308
1413
  // For tasks with alive sessions, we need to wait for them to complete.
@@ -1578,7 +1683,11 @@ export async function resumeOrchBatch(
1578
1683
 
1579
1684
  // ── 9. Persist state after reconciliation ────────────────────
1580
1685
  // Track state for persistence
1581
- const wavePlan = persistedState.wavePlan;
1686
+ const wavePlan = runtimeWavePlan;
1687
+ persistedState.wavePlan = wavePlan;
1688
+ if (batchState.totalWaves < wavePlan.length) {
1689
+ batchState.totalWaves = wavePlan.length;
1690
+ }
1582
1691
  const allTaskOutcomes: LaneTaskOutcome[] = [];
1583
1692
 
1584
1693
  // Initialize latestAllocatedLanes from persisted lane records so that
@@ -1658,7 +1767,7 @@ export async function resumeOrchBatch(
1658
1767
  persistedState.tasks.map((task) => [task.taskId, task.status] as const),
1659
1768
  );
1660
1769
 
1661
- for (let waveIdx = resumePoint.resumeWaveIndex; waveIdx < persistedState.wavePlan.length; waveIdx++) {
1770
+ for (let waveIdx = resumePoint.resumeWaveIndex; waveIdx < wavePlan.length; waveIdx++) {
1662
1771
  // Check pause signal
1663
1772
  if (batchState.pauseSignal.paused) {
1664
1773
  batchState.phase = "paused";
@@ -1672,7 +1781,7 @@ export async function resumeOrchBatch(
1672
1781
 
1673
1782
  // Get wave tasks, filtering out completed/failed/skipped/blocked ones.
1674
1783
  // Persisted "skipped" tasks are terminal and must never be re-executed.
1675
- let waveTasks = persistedState.wavePlan[waveIdx].filter(
1784
+ let waveTasks = wavePlan[waveIdx].filter(
1676
1785
  taskId => !completedTaskSet.has(taskId) &&
1677
1786
  !failedTaskSet.has(taskId) &&
1678
1787
  persistedStatusByTaskId.get(taskId) !== "skipped" &&
@@ -1685,7 +1794,7 @@ export async function resumeOrchBatch(
1685
1794
  // Count only newly blocked tasks (not already persisted) to avoid double-counting.
1686
1795
  // persistedState.blockedTaskIds were already counted in persistedState.blockedTasks
1687
1796
  // which initialized batchState.blockedTasks.
1688
- const blockedInWave = persistedState.wavePlan[waveIdx].filter(
1797
+ const blockedInWave = wavePlan[waveIdx].filter(
1689
1798
  taskId => batchState.blockedTaskIds.has(taskId) &&
1690
1799
  !persistedBlockedTaskIds.has(taskId),
1691
1800
  );
@@ -1701,7 +1810,7 @@ export async function resumeOrchBatch(
1701
1810
  onNotify(`🔀 Wave ${waveIdx + 1}: retrying merge (tasks already complete, merge was missing/failed)`, "info");
1702
1811
 
1703
1812
  // Reconstruct lanes for this wave from persisted state
1704
- const waveTaskIds = new Set(persistedState.wavePlan[waveIdx]);
1813
+ const waveTaskIds = new Set(wavePlan[waveIdx]);
1705
1814
  const waveLaneRecords = persistedState.lanes.filter(
1706
1815
  lane => lane.taskIds.some(tid => waveTaskIds.has(tid)),
1707
1816
  );
@@ -1711,13 +1820,13 @@ export async function resumeOrchBatch(
1711
1820
  // Crucial for orch_force_merge: tasks intentionally marked "skipped" must
1712
1821
  // remain skipped here (not failed), otherwise mixed-outcome detection would
1713
1822
  // trigger again and block the forced merge recovery path.
1714
- const succeededTaskIds = persistedState.wavePlan[waveIdx].filter(
1823
+ const succeededTaskIds = wavePlan[waveIdx].filter(
1715
1824
  taskId => completedTaskSet.has(taskId),
1716
1825
  );
1717
- const skippedTaskIds = persistedState.wavePlan[waveIdx].filter(
1826
+ const skippedTaskIds = wavePlan[waveIdx].filter(
1718
1827
  taskId => persistedStatusByTaskId.get(taskId) === "skipped",
1719
1828
  );
1720
- const failedTaskIds = persistedState.wavePlan[waveIdx].filter(
1829
+ const failedTaskIds = wavePlan[waveIdx].filter(
1721
1830
  taskId => {
1722
1831
  const status = persistedStatusByTaskId.get(taskId);
1723
1832
  return status === "failed" || status === "stalled";
@@ -1846,7 +1955,7 @@ export async function resumeOrchBatch(
1846
1955
  }
1847
1956
 
1848
1957
  onNotify(
1849
- ORCH_MESSAGES.orchWaveStart(waveIdx + 1, persistedState.wavePlan.length, waveTasks.length, Math.min(waveTasks.length, orchConfig.orchestrator.max_lanes)),
1958
+ ORCH_MESSAGES.orchWaveStart(waveIdx + 1, wavePlan.length, waveTasks.length, Math.min(waveTasks.length, orchConfig.orchestrator.max_lanes)),
1850
1959
  "info",
1851
1960
  );
1852
1961
 
@@ -1884,6 +1993,7 @@ export async function resumeOrchBatch(
1884
1993
  workspaceConfig,
1885
1994
  resumeBackend,
1886
1995
  emitAlert,
1996
+ supervisorAutonomy,
1887
1997
  );
1888
1998
 
1889
1999
  batchState.waveResults.push(waveResult);