taskplane 0.26.1 → 0.27.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/README.md +4 -1
- package/bin/taskplane.mjs +12 -5
- package/extensions/taskplane/abort.ts +2 -1
- package/extensions/taskplane/agent-host.ts +100 -1
- package/extensions/taskplane/cleanup.ts +272 -10
- package/extensions/taskplane/engine.ts +182 -47
- package/extensions/taskplane/execution.ts +139 -49
- package/extensions/taskplane/extension.ts +5125 -5125
- package/extensions/taskplane/formatting.ts +70 -11
- package/extensions/taskplane/git.ts +34 -0
- package/extensions/taskplane/lane-runner.ts +219 -10
- package/extensions/taskplane/merge.ts +3128 -2917
- package/extensions/taskplane/persistence.ts +3 -0
- package/extensions/taskplane/resume.ts +86 -30
- package/extensions/taskplane/supervisor-primer.md +55 -0
- package/extensions/taskplane/types.ts +27 -2
- package/package.json +1 -1
- package/templates/agents/task-worker.md +51 -9
|
@@ -6,7 +6,7 @@ import { existsSync, readdirSync, readFileSync, renameSync, unlinkSync } from "f
|
|
|
6
6
|
import { join, resolve } from "path";
|
|
7
7
|
|
|
8
8
|
import { formatDiscoveryResults, runDiscovery } from "./discovery.ts";
|
|
9
|
-
import { buildReviewerEnv, computeTransitiveDependents, execLog, executeLaneV2, executeWave, killV2LaneAgents } from "./execution.ts";
|
|
9
|
+
import { buildReviewerEnv, computeTransitiveDependents, execLog, executeLaneV2, executeWave, killV2LaneAgents, resolveCanonicalTaskPaths } from "./execution.ts";
|
|
10
10
|
import type { RuntimeBackend } from "./execution.ts";
|
|
11
11
|
import type { MonitorUpdateCallback } from "./execution.ts";
|
|
12
12
|
// classifyExit no longer called directly — Tier 0 uses exitDiagnostic.classification
|
|
@@ -23,7 +23,7 @@ import { buildBatchProgressSnapshot, buildEngineEventBase, buildSegmentId, build
|
|
|
23
23
|
import type { AllocatedLane, AllocatedTask, BatchHistorySummary, BatchTaskSummary, BatchWaveSummary, DiscoveryResult, EngineEventCallback, EscalationContext, LaneExecutionResult, LaneTaskOutcome, MergeWaveResult, OrchBatchPhase, OrchBatchRuntimeState, OrchestratorConfig, ParsedTask, PersistedSegmentRecord, SegmentExpansionRequest, SupervisorAlert, SupervisorAlertCallback, TaskRunnerConfig, TaskSegmentPlan, TaskSegmentPlanMap, TaskSegmentNode, Tier0EscalationPattern, Tier0RecoveryPattern, TokenCounts, WaveExecutionResult, WorkspaceConfig } from "./types.ts";
|
|
24
24
|
import { buildDependencyGraph, computeWaveAssignments, resolveBaseBranch, resolveRepoRoot, validateGraph } from "./waves.ts";
|
|
25
25
|
import { deleteBranchBestEffort, forceCleanupWorktree, formatPreflightResults, listWorktrees, preserveFailedLaneProgress, preserveSkippedLaneProgress, removeAllWorktrees, removeWorktree, runPreflight, safeResetWorktree, sleepSync } from "./worktree.ts";
|
|
26
|
-
import { runPreflightCleanup, formatPreflightCleanup } from "./cleanup.ts";
|
|
26
|
+
import { runPreflightCleanup, formatPreflightCleanup, enforceTelemetrySizeCap, formatSizeCap, cleanupPriorBatchArtifacts, formatPriorBatchCleanup } from "./cleanup.ts";
|
|
27
27
|
|
|
28
28
|
// ── Tier 0: Automatic Recovery Helpers (TP-039) ─────────────────────
|
|
29
29
|
|
|
@@ -147,17 +147,31 @@ function buildSegmentDependencyMap(plan: TaskSegmentPlan): Map<string, string[]>
|
|
|
147
147
|
return depsBySegmentId;
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
function resolveTaskWorkerAgentId(
|
|
150
|
+
export function resolveTaskWorkerAgentId(
|
|
151
151
|
taskId: string,
|
|
152
152
|
allTaskOutcomes: LaneTaskOutcome[],
|
|
153
153
|
laneByTaskId: Map<string, AllocatedLane>,
|
|
154
|
+
agentIdPrefix?: string,
|
|
154
155
|
): string | null {
|
|
155
156
|
const outcome = allTaskOutcomes.find((candidate) => candidate.taskId === taskId);
|
|
156
157
|
if (outcome?.sessionName) {
|
|
157
158
|
return outcome.sessionName;
|
|
158
159
|
}
|
|
160
|
+
// TP-165: The fallback must derive the *worker* agent ID, not the lane
|
|
161
|
+
// session ID. The outbox lives under the worker agent ID
|
|
162
|
+
// (e.g., "orch-op-lane-2-worker"), not the lane session
|
|
163
|
+
// (e.g., "orch-op-api-lane-1"). In workspace mode these differ because
|
|
164
|
+
// laneSessionId uses repo-scoped local numbering while the worker ID
|
|
165
|
+
// uses the global laneNumber.
|
|
159
166
|
const lane = laneByTaskId.get(taskId);
|
|
160
|
-
|
|
167
|
+
if (!lane) return null;
|
|
168
|
+
if (agentIdPrefix) {
|
|
169
|
+
// Canonical path: reconstruct the exact same ID that executeLaneV2 builds
|
|
170
|
+
// via buildRuntimeAgentId(agentIdPrefix, lane.laneNumber, "worker").
|
|
171
|
+
return `${agentIdPrefix}-lane-${lane.laneNumber}-worker`;
|
|
172
|
+
}
|
|
173
|
+
// Legacy/defensive fallback when prefix is unavailable.
|
|
174
|
+
return `${lane.laneSessionId}-worker`;
|
|
161
175
|
}
|
|
162
176
|
|
|
163
177
|
function listPendingSegmentExpansionRequestFiles(stateRoot: string, batchId: string, agentId: string): string[] {
|
|
@@ -1069,12 +1083,65 @@ export function linearizeTaskSegmentPlan(plan: TaskSegmentPlan): TaskSegmentNode
|
|
|
1069
1083
|
return ordered;
|
|
1070
1084
|
}
|
|
1071
1085
|
|
|
1086
|
+
/**
|
|
1087
|
+
* Result of `buildSegmentFrontierWaves()`. Contains both the expanded
|
|
1088
|
+
* segment rounds and task-level wave metadata for correct display.
|
|
1089
|
+
*
|
|
1090
|
+
* @since TP-166
|
|
1091
|
+
*/
|
|
1092
|
+
export interface SegmentFrontierResult {
|
|
1093
|
+
/** Expanded segment rounds (execution-level) */
|
|
1094
|
+
waves: string[][];
|
|
1095
|
+
/** Per-task segment frontier state */
|
|
1096
|
+
taskStateById: Map<string, SegmentFrontierTaskState>;
|
|
1097
|
+
/**
|
|
1098
|
+
* Number of original dependency-driven task-level waves.
|
|
1099
|
+
* Use this for operator-facing "Wave X of Y" display.
|
|
1100
|
+
*/
|
|
1101
|
+
taskLevelWaveCount: number;
|
|
1102
|
+
/**
|
|
1103
|
+
* Maps each segment round index (0-based) to its parent task-level
|
|
1104
|
+
* wave index (0-based). When continuation rounds are dynamically
|
|
1105
|
+
* inserted via `scheduleContinuationSegmentRound`, the caller must
|
|
1106
|
+
* also insert the corresponding task-level wave index into this array.
|
|
1107
|
+
*/
|
|
1108
|
+
roundToTaskWave: number[];
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
/**
|
|
1112
|
+
* Resolve the 1-indexed task-level wave number for display from a
|
|
1113
|
+
* segment-round index. Falls back to `roundIdx + 1` when the mapping
|
|
1114
|
+
* is missing or out of bounds.
|
|
1115
|
+
*
|
|
1116
|
+
* @param roundIdx - Current segment round index (0-based)
|
|
1117
|
+
* @param roundToTaskWave - Mapping from round index to task-level wave (0-based)
|
|
1118
|
+
* @param taskLevelWaveCount - Number of original task-level waves
|
|
1119
|
+
* @param fallbackTotal - Optional fallback total (e.g., batchState.totalWaves) for
|
|
1120
|
+
* legacy state files that lack TP-166 metadata
|
|
1121
|
+
* @since TP-166
|
|
1122
|
+
*/
|
|
1123
|
+
export function resolveDisplayWaveNumber(
|
|
1124
|
+
roundIdx: number,
|
|
1125
|
+
roundToTaskWave: number[] | undefined,
|
|
1126
|
+
taskLevelWaveCount: number | undefined,
|
|
1127
|
+
fallbackTotal?: number,
|
|
1128
|
+
): { displayWave: number; displayTotal: number } {
|
|
1129
|
+
const taskWaveIdx = roundToTaskWave?.[roundIdx];
|
|
1130
|
+
const displayWave = (taskWaveIdx != null) ? taskWaveIdx + 1 : roundIdx + 1;
|
|
1131
|
+
const displayTotal = taskLevelWaveCount ?? fallbackTotal ?? (roundIdx + 1);
|
|
1132
|
+
return { displayWave, displayTotal };
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1072
1135
|
/**
|
|
1073
1136
|
* Expand task waves into segment-frontier rounds.
|
|
1074
1137
|
*
|
|
1075
1138
|
* Each original task-wave becomes N rounds where N is the max segment count
|
|
1076
1139
|
* among tasks in that wave. A task with fewer segments simply drops out once
|
|
1077
1140
|
* its segment list is exhausted.
|
|
1141
|
+
*
|
|
1142
|
+
* Returns both the expanded rounds and a mapping from segment round index
|
|
1143
|
+
* to task-level wave index, enabling correct "Wave X of Y" display
|
|
1144
|
+
* without inflating wave count with segment rounds (TP-166).
|
|
1078
1145
|
*/
|
|
1079
1146
|
export function buildSegmentFrontierWaves(
|
|
1080
1147
|
baseTaskWaves: string[][],
|
|
@@ -1082,7 +1149,7 @@ export function buildSegmentFrontierWaves(
|
|
|
1082
1149
|
segmentPlans?: TaskSegmentPlanMap,
|
|
1083
1150
|
packetRepoId?: string,
|
|
1084
1151
|
workspaceRoot?: string,
|
|
1085
|
-
):
|
|
1152
|
+
): SegmentFrontierResult {
|
|
1086
1153
|
const taskStateById = new Map<string, SegmentFrontierTaskState>();
|
|
1087
1154
|
|
|
1088
1155
|
for (const [taskId, task] of pending.entries()) {
|
|
@@ -1112,7 +1179,11 @@ export function buildSegmentFrontierWaves(
|
|
|
1112
1179
|
}
|
|
1113
1180
|
|
|
1114
1181
|
const expanded: string[][] = [];
|
|
1115
|
-
|
|
1182
|
+
// TP-166: Track which task-level wave each segment round belongs to.
|
|
1183
|
+
// roundToTaskWave[i] = 0-based task-level wave index for segment round i.
|
|
1184
|
+
const roundToTaskWave: number[] = [];
|
|
1185
|
+
for (let taskWaveIdx = 0; taskWaveIdx < baseTaskWaves.length; taskWaveIdx++) {
|
|
1186
|
+
const waveTasks = baseTaskWaves[taskWaveIdx];
|
|
1116
1187
|
let maxSegmentsInWave = 0;
|
|
1117
1188
|
for (const taskId of waveTasks) {
|
|
1118
1189
|
const state = taskStateById.get(taskId);
|
|
@@ -1131,6 +1202,7 @@ export function buildSegmentFrontierWaves(
|
|
|
1131
1202
|
}
|
|
1132
1203
|
if (segmentRound.length > 0) {
|
|
1133
1204
|
expanded.push(segmentRound);
|
|
1205
|
+
roundToTaskWave.push(taskWaveIdx);
|
|
1134
1206
|
}
|
|
1135
1207
|
}
|
|
1136
1208
|
}
|
|
@@ -1138,6 +1210,8 @@ export function buildSegmentFrontierWaves(
|
|
|
1138
1210
|
return {
|
|
1139
1211
|
waves: expanded,
|
|
1140
1212
|
taskStateById,
|
|
1213
|
+
taskLevelWaveCount: baseTaskWaves.length,
|
|
1214
|
+
roundToTaskWave,
|
|
1141
1215
|
};
|
|
1142
1216
|
}
|
|
1143
1217
|
|
|
@@ -2009,11 +2083,12 @@ export async function executeOrchBatch(
|
|
|
2009
2083
|
return;
|
|
2010
2084
|
}
|
|
2011
2085
|
|
|
2012
|
-
// ── TP-065: Preflight artifact cleanup (
|
|
2013
|
-
// Sweep stale artifacts
|
|
2086
|
+
// ── TP-065/TP-168: Preflight artifact cleanup (Layers 2–5) ───
|
|
2087
|
+
// Sweep stale artifacts, rotate oversized logs, enforce size cap,
|
|
2088
|
+
// and clean prior batch artifacts before batch starts.
|
|
2014
2089
|
// Always non-fatal — failures warn but never block batch execution.
|
|
2015
2090
|
try {
|
|
2016
|
-
// Layer 2: Age-based sweep of stale telemetry/merge artifacts (>
|
|
2091
|
+
// Layer 2: Age-based sweep of stale telemetry/merge/verification/conversation artifacts (>3 days)
|
|
2017
2092
|
const sweepResult = sweepStaleArtifacts(stateRoot, {
|
|
2018
2093
|
isBatchActive: () => {
|
|
2019
2094
|
// Check persisted state — a prior batch may still be active
|
|
@@ -2038,6 +2113,21 @@ export async function executeOrchBatch(
|
|
|
2038
2113
|
if (rotationMsg) {
|
|
2039
2114
|
onNotify(rotationMsg, "info");
|
|
2040
2115
|
}
|
|
2116
|
+
// Layer 4: Telemetry directory size cap (TP-168)
|
|
2117
|
+
const sizeCapResult = enforceTelemetrySizeCap(stateRoot);
|
|
2118
|
+
const sizeCapMsg = formatSizeCap(sizeCapResult);
|
|
2119
|
+
if (sizeCapMsg) {
|
|
2120
|
+
onNotify(sizeCapMsg, "info");
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
// Layer 5: Clean up prior batch artifacts (TP-168)
|
|
2124
|
+
if (batchState.batchId) {
|
|
2125
|
+
const priorCleanup = cleanupPriorBatchArtifacts(stateRoot, batchState.batchId);
|
|
2126
|
+
const priorMsg = formatPriorBatchCleanup(priorCleanup);
|
|
2127
|
+
if (priorMsg) {
|
|
2128
|
+
onNotify(priorMsg, "info");
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2041
2131
|
} catch {
|
|
2042
2132
|
// Non-fatal — never block batch start for cleanup errors
|
|
2043
2133
|
}
|
|
@@ -2140,7 +2230,14 @@ export async function executeOrchBatch(
|
|
|
2140
2230
|
const rawWaves = frontier.waves;
|
|
2141
2231
|
segmentStateByTask = frontier.taskStateById;
|
|
2142
2232
|
|
|
2233
|
+
// TP-166: Track task-level wave metadata for correct display.
|
|
2234
|
+
// roundToTaskWave maps each segment round index to its parent task-level wave.
|
|
2235
|
+
let roundToTaskWave = frontier.roundToTaskWave;
|
|
2236
|
+
const taskLevelWaveCount = frontier.taskLevelWaveCount;
|
|
2237
|
+
|
|
2143
2238
|
batchState.totalWaves = rawWaves.length;
|
|
2239
|
+
batchState.taskLevelWaveCount = taskLevelWaveCount;
|
|
2240
|
+
batchState.roundToTaskWave = [...roundToTaskWave];
|
|
2144
2241
|
batchState.totalTasks = discovery.pending.size;
|
|
2145
2242
|
|
|
2146
2243
|
// Store wave plan and discovery for state persistence
|
|
@@ -2154,6 +2251,8 @@ export async function executeOrchBatch(
|
|
|
2154
2251
|
// The orch branch isolates all batch work from the user's current branch.
|
|
2155
2252
|
// Worktrees branch from it; merges target it via update-ref.
|
|
2156
2253
|
const opId = resolveOperatorId(orchConfig);
|
|
2254
|
+
const sessionPrefix = orchConfig.orchestrator?.sessionPrefix ?? "orch";
|
|
2255
|
+
const agentIdPrefix = `${sessionPrefix}-${opId}`;
|
|
2157
2256
|
const orchBranch = `orch/${opId}-${batchState.batchId}`;
|
|
2158
2257
|
|
|
2159
2258
|
// In workspace mode, create the orch branch in every repo that might
|
|
@@ -2193,8 +2292,9 @@ export async function executeOrchBatch(
|
|
|
2193
2292
|
}
|
|
2194
2293
|
batchState.orchBranch = orchBranch;
|
|
2195
2294
|
|
|
2295
|
+
// TP-166: Report task-level wave count, not segment round count
|
|
2196
2296
|
onNotify(
|
|
2197
|
-
ORCH_MESSAGES.orchStarting(batchState.batchId,
|
|
2297
|
+
ORCH_MESSAGES.orchStarting(batchState.batchId, taskLevelWaveCount, batchState.totalTasks),
|
|
2198
2298
|
"info",
|
|
2199
2299
|
);
|
|
2200
2300
|
|
|
@@ -2224,7 +2324,10 @@ export async function executeOrchBatch(
|
|
|
2224
2324
|
if (batchState.pauseSignal.paused) {
|
|
2225
2325
|
batchState.phase = "paused";
|
|
2226
2326
|
execLog("batch", batchState.batchId, `batch paused before wave ${waveIdx + 1}`);
|
|
2227
|
-
|
|
2327
|
+
{
|
|
2328
|
+
const { displayWave } = resolveDisplayWaveNumber(waveIdx, roundToTaskWave, taskLevelWaveCount);
|
|
2329
|
+
onNotify(`⏸️ Batch paused before wave ${displayWave}. Resume not yet implemented (TS-009).`, "warning");
|
|
2330
|
+
}
|
|
2228
2331
|
// ── TS-009: Persist state on pause ──
|
|
2229
2332
|
persistRuntimeState("pause-before-wave", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discoveryRef, stateRoot);
|
|
2230
2333
|
// TP-040: Emit batch_paused event (via terminal helper for dedup)
|
|
@@ -2309,9 +2412,10 @@ export async function executeOrchBatch(
|
|
|
2309
2412
|
latestAllocatedLanes = lanes;
|
|
2310
2413
|
batchState.currentLanes = lanes;
|
|
2311
2414
|
|
|
2312
|
-
//
|
|
2415
|
+
// TP-166: Use task-level wave number for operator display
|
|
2416
|
+
const { displayWave, displayTotal } = resolveDisplayWaveNumber(waveIdx, roundToTaskWave, taskLevelWaveCount);
|
|
2313
2417
|
onNotify(
|
|
2314
|
-
ORCH_MESSAGES.orchWaveStart(
|
|
2418
|
+
ORCH_MESSAGES.orchWaveStart(displayWave, displayTotal, waveTasks.length, lanes.length),
|
|
2315
2419
|
"info",
|
|
2316
2420
|
);
|
|
2317
2421
|
// TP-148: Build per-task segment context for the wave_start event
|
|
@@ -2579,7 +2683,7 @@ export async function executeOrchBatch(
|
|
|
2579
2683
|
segmentState.statusBySegmentId.set(activeSegmentId, "succeeded");
|
|
2580
2684
|
upsertTerminalSegmentRecord(batchState, task, segmentState, activeSegmentId, "succeeded", outcome, laneByTaskId.get(taskId));
|
|
2581
2685
|
|
|
2582
|
-
const workerAgentId = resolveTaskWorkerAgentId(taskId, allTaskOutcomes, laneByTaskId);
|
|
2686
|
+
const workerAgentId = resolveTaskWorkerAgentId(taskId, allTaskOutcomes, laneByTaskId, agentIdPrefix);
|
|
2583
2687
|
if (workerAgentId) {
|
|
2584
2688
|
const pendingExpansionFiles = listPendingSegmentExpansionRequestFiles(stateRoot, batchState.batchId, workerAgentId);
|
|
2585
2689
|
if (pendingExpansionFiles.length > 0) {
|
|
@@ -2669,16 +2773,30 @@ export async function executeOrchBatch(
|
|
|
2669
2773
|
// segments have been added and must execute first.
|
|
2670
2774
|
// Only delete if segments were actually inserted (avoid
|
|
2671
2775
|
// reopening a completed task on no-op mutations).
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2776
|
+
//
|
|
2777
|
+
// TP-165: Resolve .DONE path via the lane worktree, not
|
|
2778
|
+
// task.packetTaskPath/task.taskFolder (which may point to the
|
|
2779
|
+
// workspace root, not the worktree where .DONE was created).
|
|
2780
|
+
if (mutation.insertedSegmentIds.length > 0) {
|
|
2781
|
+
const lane = laneByTaskId.get(taskId);
|
|
2782
|
+
const doneDir = lane
|
|
2783
|
+
? resolveCanonicalTaskPaths(
|
|
2784
|
+
task.taskFolder,
|
|
2785
|
+
lane.worktreePath,
|
|
2786
|
+
repoRoot,
|
|
2787
|
+
!!workspaceConfig,
|
|
2788
|
+
).taskFolderResolved
|
|
2789
|
+
: task.packetTaskPath || task.taskFolder;
|
|
2790
|
+
if (doneDir) {
|
|
2791
|
+
const donePath = join(doneDir, ".DONE");
|
|
2792
|
+
if (existsSync(donePath)) {
|
|
2793
|
+
try {
|
|
2794
|
+
unlinkSync(donePath);
|
|
2795
|
+
execLog("batch", batchState.batchId, "removed premature .DONE after segment expansion", {
|
|
2796
|
+
taskId, donePath, requestId,
|
|
2797
|
+
});
|
|
2798
|
+
} catch { /* non-fatal */ }
|
|
2799
|
+
}
|
|
2682
2800
|
}
|
|
2683
2801
|
}
|
|
2684
2802
|
|
|
@@ -2733,6 +2851,11 @@ export async function executeOrchBatch(
|
|
|
2733
2851
|
}
|
|
2734
2852
|
if (continuationTaskIds.size > 0) {
|
|
2735
2853
|
const continuationWave = scheduleContinuationSegmentRound(runtimeSegmentRounds, waveIdx, continuationTaskIds);
|
|
2854
|
+
// TP-166: Maintain roundToTaskWave mapping for the inserted continuation round.
|
|
2855
|
+
// The continuation belongs to the same task-level wave as the current round.
|
|
2856
|
+
const parentTaskWave = roundToTaskWave[waveIdx] ?? 0;
|
|
2857
|
+
roundToTaskWave.splice(waveIdx + 1, 0, parentTaskWave);
|
|
2858
|
+
batchState.roundToTaskWave = [...roundToTaskWave];
|
|
2736
2859
|
execLog("batch", batchState.batchId, "scheduled continuation segment round for expanded task frontier", {
|
|
2737
2860
|
waveIndex: waveIdx,
|
|
2738
2861
|
taskIds: continuationWave.join(","),
|
|
@@ -2750,7 +2873,7 @@ export async function executeOrchBatch(
|
|
|
2750
2873
|
segmentState.statusBySegmentId.set(activeSegmentId, "failed");
|
|
2751
2874
|
upsertTerminalSegmentRecord(batchState, task, segmentState, activeSegmentId, "failed", failOutcome, laneByTaskId.get(taskId));
|
|
2752
2875
|
|
|
2753
|
-
const workerAgentId = resolveTaskWorkerAgentId(taskId, allTaskOutcomes, laneByTaskId);
|
|
2876
|
+
const workerAgentId = resolveTaskWorkerAgentId(taskId, allTaskOutcomes, laneByTaskId, agentIdPrefix);
|
|
2754
2877
|
if (workerAgentId) {
|
|
2755
2878
|
const pendingExpansionFiles = listPendingSegmentExpansionRequestFiles(stateRoot, batchState.batchId, workerAgentId);
|
|
2756
2879
|
if (pendingExpansionFiles.length > 0) {
|
|
@@ -2900,7 +3023,7 @@ export async function executeOrchBatch(
|
|
|
2900
3023
|
frontierSummary +
|
|
2901
3024
|
` Lane: ${laneForTask?.laneId ?? "unknown"} (lane ${laneForTask?.laneNumber ?? "?"})\n` +
|
|
2902
3025
|
` Partial progress preserved: ${hasPartialProgress ? "yes" : "no"}\n` +
|
|
2903
|
-
` Batch: wave ${waveIdx
|
|
3026
|
+
` Batch: wave ${resolveDisplayWaveNumber(waveIdx, roundToTaskWave, taskLevelWaveCount).displayWave}/${taskLevelWaveCount}, ` +
|
|
2904
3027
|
`${batchState.succeededTasks} succeeded, ${batchState.failedTasks} failed\n\n` +
|
|
2905
3028
|
`Available actions:\n` +
|
|
2906
3029
|
` - orch_status() to inspect current state\n` +
|
|
@@ -2925,16 +3048,19 @@ export async function executeOrchBatch(
|
|
|
2925
3048
|
persistRuntimeState("wave-execution-complete", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discoveryRef, stateRoot);
|
|
2926
3049
|
|
|
2927
3050
|
const elapsedSec = Math.round((waveResult.endedAt - waveResult.startedAt) / 1000);
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
3051
|
+
{
|
|
3052
|
+
const { displayWave: completeDisplayWave } = resolveDisplayWaveNumber(waveIdx, roundToTaskWave, taskLevelWaveCount);
|
|
3053
|
+
onNotify(
|
|
3054
|
+
ORCH_MESSAGES.orchWaveComplete(
|
|
3055
|
+
completeDisplayWave,
|
|
3056
|
+
waveResult.succeededTaskIds.length,
|
|
3057
|
+
waveResult.failedTaskIds.length,
|
|
3058
|
+
waveResult.skippedTaskIds.length,
|
|
3059
|
+
elapsedSec,
|
|
3060
|
+
),
|
|
3061
|
+
waveResult.failedTaskIds.length > 0 ? "warning" : "info",
|
|
3062
|
+
);
|
|
3063
|
+
}
|
|
2938
3064
|
|
|
2939
3065
|
// NOTE: No explicit wave_complete event in the spec event set. The supervisor
|
|
2940
3066
|
// infers wave completion from the sequence of task_complete/task_failed events
|
|
@@ -3037,7 +3163,7 @@ export async function executeOrchBatch(
|
|
|
3037
3163
|
batchState.phase = "merging";
|
|
3038
3164
|
// ── TS-009: Persist state on executing→merging transition ──
|
|
3039
3165
|
persistRuntimeState("merge-start", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discoveryRef, stateRoot);
|
|
3040
|
-
onNotify(ORCH_MESSAGES.orchMergeStart(waveIdx
|
|
3166
|
+
onNotify(ORCH_MESSAGES.orchMergeStart(resolveDisplayWaveNumber(waveIdx, roundToTaskWave, taskLevelWaveCount).displayWave, mergeableLaneCount), "info");
|
|
3041
3167
|
// TP-040: Emit merge_start event
|
|
3042
3168
|
emitEvent(stateRoot, {
|
|
3043
3169
|
...buildEngineEventBase("merge_start", batchState.batchId, waveIdx, batchState.phase),
|
|
@@ -3132,18 +3258,19 @@ export async function executeOrchBatch(
|
|
|
3132
3258
|
const mergeTotalSec = Math.round(mergeResult.totalDurationMs / 1000);
|
|
3133
3259
|
|
|
3134
3260
|
if (mergeResult.status === "succeeded") {
|
|
3135
|
-
|
|
3261
|
+
const { displayWave: mergeDisplayWave } = resolveDisplayWaveNumber(waveIdx, roundToTaskWave, taskLevelWaveCount);
|
|
3262
|
+
onNotify(ORCH_MESSAGES.orchMergeComplete(mergeDisplayWave, mergedCount, mergeTotalSec), "info");
|
|
3136
3263
|
|
|
3137
3264
|
// TP-040: Emit merge_success event
|
|
3138
3265
|
emitEvent(stateRoot, {
|
|
3139
3266
|
...buildEngineEventBase("merge_success", batchState.batchId, waveIdx, batchState.phase),
|
|
3140
3267
|
laneCount: mergedCount,
|
|
3141
3268
|
durationMs: mergeResult.totalDurationMs,
|
|
3142
|
-
totalWaves:
|
|
3269
|
+
totalWaves: taskLevelWaveCount,
|
|
3143
3270
|
}, onEngineEvent);
|
|
3144
3271
|
} else {
|
|
3145
3272
|
onNotify(
|
|
3146
|
-
ORCH_MESSAGES.orchMergeFailed(waveIdx
|
|
3273
|
+
ORCH_MESSAGES.orchMergeFailed(resolveDisplayWaveNumber(waveIdx, roundToTaskWave, taskLevelWaveCount).displayWave, mergeResult.failedLane ?? 0, mergeResult.failureReason || "unknown"),
|
|
3147
3274
|
"error",
|
|
3148
3275
|
);
|
|
3149
3276
|
|
|
@@ -3184,7 +3311,7 @@ export async function executeOrchBatch(
|
|
|
3184
3311
|
allMergeResults.push(mergeResult);
|
|
3185
3312
|
batchState.mergeResults.push(mergeResult);
|
|
3186
3313
|
onNotify(
|
|
3187
|
-
ORCH_MESSAGES.orchMergeFailed(waveIdx
|
|
3314
|
+
ORCH_MESSAGES.orchMergeFailed(resolveDisplayWaveNumber(waveIdx, roundToTaskWave, taskLevelWaveCount).displayWave, mergeResult.failedLane, mergeResult.failureReason || "unknown"),
|
|
3188
3315
|
"error",
|
|
3189
3316
|
);
|
|
3190
3317
|
|
|
@@ -3196,11 +3323,11 @@ export async function executeOrchBatch(
|
|
|
3196
3323
|
}, onEngineEvent);
|
|
3197
3324
|
} else {
|
|
3198
3325
|
// No mergeable lanes and no mixed outcomes (e.g., only skipped tasks)
|
|
3199
|
-
onNotify(ORCH_MESSAGES.orchMergeSkipped(waveIdx
|
|
3326
|
+
onNotify(ORCH_MESSAGES.orchMergeSkipped(resolveDisplayWaveNumber(waveIdx, roundToTaskWave, taskLevelWaveCount).displayWave), "info");
|
|
3200
3327
|
}
|
|
3201
3328
|
} else {
|
|
3202
3329
|
// No succeeded tasks — skip merge entirely
|
|
3203
|
-
onNotify(ORCH_MESSAGES.orchMergeSkipped(waveIdx
|
|
3330
|
+
onNotify(ORCH_MESSAGES.orchMergeSkipped(resolveDisplayWaveNumber(waveIdx, roundToTaskWave, taskLevelWaveCount).displayWave), "info");
|
|
3204
3331
|
}
|
|
3205
3332
|
|
|
3206
3333
|
// ── TP-033: Safe-stop on rollback failure ─────────────────
|
|
@@ -3644,7 +3771,7 @@ export async function executeOrchBatch(
|
|
|
3644
3771
|
|
|
3645
3772
|
if (totalResetWorktrees > 0) {
|
|
3646
3773
|
onNotify(
|
|
3647
|
-
ORCH_MESSAGES.orchWorktreeReset(waveIdx
|
|
3774
|
+
ORCH_MESSAGES.orchWorktreeReset(resolveDisplayWaveNumber(waveIdx, roundToTaskWave, taskLevelWaveCount).displayWave, totalResetWorktrees),
|
|
3648
3775
|
"info",
|
|
3649
3776
|
);
|
|
3650
3777
|
}
|
|
@@ -3900,10 +4027,18 @@ export async function executeOrchBatch(
|
|
|
3900
4027
|
legacyLaneTokensByKey,
|
|
3901
4028
|
);
|
|
3902
4029
|
|
|
4030
|
+
// TP-171: Map outcome status to valid BatchTaskSummary status.
|
|
4031
|
+
// Non-terminal statuses ("running", "pending") can appear if batch
|
|
4032
|
+
// was paused/aborted mid-wave. Map them to appropriate history values.
|
|
4033
|
+
const validStatuses: Set<string> = new Set(["succeeded", "failed", "skipped", "blocked", "stalled", "pending"]);
|
|
4034
|
+
const historyStatus: BatchTaskSummary["status"] = validStatuses.has(to.status)
|
|
4035
|
+
? (to.status as BatchTaskSummary["status"])
|
|
4036
|
+
: "pending"; // "running" or unknown → "pending" in history
|
|
4037
|
+
|
|
3903
4038
|
return {
|
|
3904
4039
|
taskId: to.taskId,
|
|
3905
4040
|
taskName: to.taskId,
|
|
3906
|
-
status:
|
|
4041
|
+
status: historyStatus,
|
|
3907
4042
|
wave,
|
|
3908
4043
|
lane,
|
|
3909
4044
|
durationMs,
|
|
@@ -3993,7 +4128,7 @@ export async function executeOrchBatch(
|
|
|
3993
4128
|
startedAt: batchState.startedAt,
|
|
3994
4129
|
endedAt: Date.now(),
|
|
3995
4130
|
durationMs: Date.now() - batchState.startedAt,
|
|
3996
|
-
totalWaves:
|
|
4131
|
+
totalWaves: taskLevelWaveCount,
|
|
3997
4132
|
totalTasks: actualTotalTasks,
|
|
3998
4133
|
succeededTasks: batchState.succeededTasks,
|
|
3999
4134
|
failedTasks: batchState.failedTasks,
|
|
@@ -4306,7 +4441,7 @@ export async function executeOrchBatch(
|
|
|
4306
4441
|
summary:
|
|
4307
4442
|
`✅ Batch ${batchState.batchId} completed\n` +
|
|
4308
4443
|
` ${batchState.succeededTasks}/${batchState.totalTasks} tasks succeeded\n` +
|
|
4309
|
-
` ${batchState.totalWaves} wave(s), duration: ${durationStr}\n` +
|
|
4444
|
+
` ${batchState.taskLevelWaveCount ?? batchState.totalWaves} wave(s), duration: ${durationStr}\n` +
|
|
4310
4445
|
` Merged to orch branch: ${batchState.orchBranch}\n\n` +
|
|
4311
4446
|
`Ready for integration. Run orch_integrate() or review first.`,
|
|
4312
4447
|
context: {
|