taskplane 0.2.6 → 0.2.7
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.
|
@@ -48,6 +48,9 @@ export async function executeOrchBatch(
|
|
|
48
48
|
workspaceRoot?: string,
|
|
49
49
|
): Promise<void> {
|
|
50
50
|
const repoRoot = cwd;
|
|
51
|
+
// State files (.pi/batch-state.json, lane-state, etc.) belong in the workspace root,
|
|
52
|
+
// which is where .pi/ config lives. In repo mode, workspaceRoot === repoRoot.
|
|
53
|
+
const stateRoot = workspaceRoot ?? cwd;
|
|
51
54
|
|
|
52
55
|
// ── Phase 1: Planning ────────────────────────────────────────
|
|
53
56
|
batchState.phase = "planning";
|
|
@@ -187,7 +190,7 @@ export async function executeOrchBatch(
|
|
|
187
190
|
batchState.phase = "executing";
|
|
188
191
|
|
|
189
192
|
// ── TS-009: Persist state on batch start (after wave computation) ──
|
|
190
|
-
persistRuntimeState("batch-start", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discoveryRef,
|
|
193
|
+
persistRuntimeState("batch-start", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discoveryRef, stateRoot);
|
|
191
194
|
|
|
192
195
|
for (let waveIdx = 0; waveIdx < rawWaves.length; waveIdx++) {
|
|
193
196
|
// Check pause signal before starting each wave
|
|
@@ -196,14 +199,14 @@ export async function executeOrchBatch(
|
|
|
196
199
|
execLog("batch", batchState.batchId, `batch paused before wave ${waveIdx + 1}`);
|
|
197
200
|
onNotify(`⏸️ Batch paused before wave ${waveIdx + 1}. Resume not yet implemented (TS-009).`, "warning");
|
|
198
201
|
// ── TS-009: Persist state on pause ──
|
|
199
|
-
persistRuntimeState("pause-before-wave", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discoveryRef,
|
|
202
|
+
persistRuntimeState("pause-before-wave", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discoveryRef, stateRoot);
|
|
200
203
|
break;
|
|
201
204
|
}
|
|
202
205
|
|
|
203
206
|
batchState.currentWaveIndex = waveIdx;
|
|
204
207
|
|
|
205
208
|
// ── TS-009: Persist state on wave index change ──
|
|
206
|
-
persistRuntimeState("wave-index-change", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discoveryRef,
|
|
209
|
+
persistRuntimeState("wave-index-change", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discoveryRef, stateRoot);
|
|
207
210
|
|
|
208
211
|
// Filter wave tasks against blockedTaskIds
|
|
209
212
|
let waveTasks = rawWaves[waveIdx].filter(
|
|
@@ -234,7 +237,7 @@ export async function executeOrchBatch(
|
|
|
234
237
|
const handleWaveMonitorUpdate: MonitorUpdateCallback = (monitorState) => {
|
|
235
238
|
const changed = syncTaskOutcomesFromMonitor(monitorState, allTaskOutcomes);
|
|
236
239
|
if (changed) {
|
|
237
|
-
persistRuntimeState("task-transition", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discoveryRef,
|
|
240
|
+
persistRuntimeState("task-transition", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discoveryRef, stateRoot);
|
|
238
241
|
}
|
|
239
242
|
onMonitorUpdate?.(monitorState);
|
|
240
243
|
};
|
|
@@ -255,7 +258,7 @@ export async function executeOrchBatch(
|
|
|
255
258
|
latestAllocatedLanes = lanes;
|
|
256
259
|
batchState.currentLanes = lanes;
|
|
257
260
|
if (seedPendingOutcomesForAllocatedLanes(lanes, allTaskOutcomes)) {
|
|
258
|
-
persistRuntimeState("wave-lanes-allocated", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discoveryRef,
|
|
261
|
+
persistRuntimeState("wave-lanes-allocated", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discoveryRef, stateRoot);
|
|
259
262
|
}
|
|
260
263
|
},
|
|
261
264
|
workspaceConfig,
|
|
@@ -283,7 +286,7 @@ export async function executeOrchBatch(
|
|
|
283
286
|
}
|
|
284
287
|
|
|
285
288
|
// ── TS-009: Persist state after wave execution ──
|
|
286
|
-
persistRuntimeState("wave-execution-complete", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discoveryRef,
|
|
289
|
+
persistRuntimeState("wave-execution-complete", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discoveryRef, stateRoot);
|
|
287
290
|
|
|
288
291
|
const elapsedSec = Math.round((waveResult.endedAt - waveResult.startedAt) / 1000);
|
|
289
292
|
onNotify(
|
|
@@ -302,14 +305,14 @@ export async function executeOrchBatch(
|
|
|
302
305
|
if (waveResult.policyApplied === "stop-all") {
|
|
303
306
|
batchState.phase = "stopped";
|
|
304
307
|
// ── TS-009: Persist state on stop-all ──
|
|
305
|
-
persistRuntimeState("stop-all", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discoveryRef,
|
|
308
|
+
persistRuntimeState("stop-all", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discoveryRef, stateRoot);
|
|
306
309
|
onNotify(ORCH_MESSAGES.orchBatchStopped(batchState.batchId, "stop-all"), "error");
|
|
307
310
|
break;
|
|
308
311
|
}
|
|
309
312
|
if (waveResult.policyApplied === "stop-wave") {
|
|
310
313
|
batchState.phase = "stopped";
|
|
311
314
|
// ── TS-009: Persist state on stop-wave ──
|
|
312
|
-
persistRuntimeState("stop-wave", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discoveryRef,
|
|
315
|
+
persistRuntimeState("stop-wave", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discoveryRef, stateRoot);
|
|
313
316
|
onNotify(ORCH_MESSAGES.orchBatchStopped(batchState.batchId, "stop-wave"), "error");
|
|
314
317
|
break;
|
|
315
318
|
}
|
|
@@ -347,7 +350,7 @@ export async function executeOrchBatch(
|
|
|
347
350
|
if (mergeableLaneCount > 0) {
|
|
348
351
|
batchState.phase = "merging";
|
|
349
352
|
// ── TS-009: Persist state on executing→merging transition ──
|
|
350
|
-
persistRuntimeState("merge-start", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discoveryRef,
|
|
353
|
+
persistRuntimeState("merge-start", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discoveryRef, stateRoot);
|
|
351
354
|
onNotify(ORCH_MESSAGES.orchMergeStart(waveIdx + 1, mergeableLaneCount), "info");
|
|
352
355
|
|
|
353
356
|
mergeResult = mergeWaveByRepo(
|
|
@@ -359,12 +362,13 @@ export async function executeOrchBatch(
|
|
|
359
362
|
batchState.batchId,
|
|
360
363
|
batchState.baseBranch,
|
|
361
364
|
workspaceConfig,
|
|
365
|
+
stateRoot,
|
|
362
366
|
);
|
|
363
367
|
allMergeResults.push(mergeResult);
|
|
364
368
|
batchState.mergeResults.push(mergeResult);
|
|
365
369
|
|
|
366
370
|
// Persist state after merge so dashboard shows wave merge results
|
|
367
|
-
persistRuntimeState("merge-complete", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discoveryRef,
|
|
371
|
+
persistRuntimeState("merge-complete", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discoveryRef, stateRoot);
|
|
368
372
|
|
|
369
373
|
// Emit per-lane merge notifications
|
|
370
374
|
for (const lr of mergeResult.laneResults) {
|
|
@@ -424,7 +428,7 @@ export async function executeOrchBatch(
|
|
|
424
428
|
// Restore phase to executing (may be overridden below by failure handling)
|
|
425
429
|
batchState.phase = "executing";
|
|
426
430
|
// ── TS-009: Persist state after merge (merging→executing) ──
|
|
427
|
-
persistRuntimeState("merge-complete", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discoveryRef,
|
|
431
|
+
persistRuntimeState("merge-complete", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discoveryRef, stateRoot);
|
|
428
432
|
} else if (mixedOutcomeLanes.length > 0) {
|
|
429
433
|
const mixedIds = mixedOutcomeLanes.map(l => `lane-${l.laneNumber}`).join(", ");
|
|
430
434
|
mergeResult = {
|
|
@@ -460,7 +464,7 @@ export async function executeOrchBatch(
|
|
|
460
464
|
|
|
461
465
|
batchState.phase = policyResult.targetPhase;
|
|
462
466
|
batchState.errors.push(policyResult.errorMessage);
|
|
463
|
-
persistRuntimeState(policyResult.persistTrigger, batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discoveryRef,
|
|
467
|
+
persistRuntimeState(policyResult.persistTrigger, batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discoveryRef, stateRoot);
|
|
464
468
|
onNotify(policyResult.notifyMessage, policyResult.notifyLevel);
|
|
465
469
|
// DO NOT cleanup/reset worktrees — preserve state for debugging/resume
|
|
466
470
|
preserveWorktreesForResume = true;
|
|
@@ -516,7 +520,7 @@ export async function executeOrchBatch(
|
|
|
516
520
|
// ── Save batch history (before cleanup deletes sidecar files) ────
|
|
517
521
|
try {
|
|
518
522
|
// Read token data from sidecar files while they still exist
|
|
519
|
-
const piDir = join(
|
|
523
|
+
const piDir = join(stateRoot, ".pi");
|
|
520
524
|
const laneTokens = new Map<string, TokenCounts>();
|
|
521
525
|
try {
|
|
522
526
|
const files = readdirSync(piDir).filter(f => f.startsWith("lane-state-") && f.endsWith(".json"));
|
|
@@ -625,7 +629,7 @@ export async function executeOrchBatch(
|
|
|
625
629
|
waves: waveSummaries,
|
|
626
630
|
};
|
|
627
631
|
|
|
628
|
-
saveBatchHistory(
|
|
632
|
+
saveBatchHistory(stateRoot, summary);
|
|
629
633
|
} catch (err) {
|
|
630
634
|
execLog("batch", batchState.batchId, `failed to save batch history: ${err}`);
|
|
631
635
|
}
|
|
@@ -650,7 +654,7 @@ export async function executeOrchBatch(
|
|
|
650
654
|
}
|
|
651
655
|
|
|
652
656
|
// Clean up sidecar files (lane state, worker conversation, merge artifacts)
|
|
653
|
-
const piDir = join(
|
|
657
|
+
const piDir = join(stateRoot, ".pi");
|
|
654
658
|
try {
|
|
655
659
|
const sidecarFiles = readdirSync(piDir).filter(
|
|
656
660
|
f => f.startsWith("lane-state-") ||
|
|
@@ -745,7 +749,7 @@ export async function executeOrchBatch(
|
|
|
745
749
|
}
|
|
746
750
|
|
|
747
751
|
// ── TS-009: Persist terminal state ──
|
|
748
|
-
persistRuntimeState("batch-terminal", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discoveryRef,
|
|
752
|
+
persistRuntimeState("batch-terminal", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discoveryRef, stateRoot);
|
|
749
753
|
|
|
750
754
|
if (batchState.phase === "paused" || batchState.phase === "stopped") {
|
|
751
755
|
execLog("batch", batchState.batchId, "batch ended in non-terminal execution state; completion banner suppressed", {
|
|
@@ -767,7 +771,7 @@ export async function executeOrchBatch(
|
|
|
767
771
|
// ── TS-009: Delete state file on clean completion (no failures) ──
|
|
768
772
|
if (batchState.phase === "completed") {
|
|
769
773
|
try {
|
|
770
|
-
deleteBatchState(
|
|
774
|
+
deleteBatchState(stateRoot);
|
|
771
775
|
execLog("state", batchState.batchId, "state file deleted on clean completion");
|
|
772
776
|
} catch (err: unknown) {
|
|
773
777
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -134,7 +134,7 @@ export function buildLaneEnvVars(
|
|
|
134
134
|
TASK_AUTOSTART: relativePath,
|
|
135
135
|
TASK_RUNNER_SPAWN_MODE: "subprocess",
|
|
136
136
|
TASK_RUNNER_TMUX_PREFIX: lane.tmuxSessionName,
|
|
137
|
-
ORCH_SIDECAR_DIR: join(repoRoot, ".pi"),
|
|
137
|
+
ORCH_SIDECAR_DIR: join(workspaceRoot || repoRoot, ".pi"),
|
|
138
138
|
NODE_PATH: nodePath,
|
|
139
139
|
// Pi's TUI (ink/react) hangs silently with TERM=tmux-256color (tmux default).
|
|
140
140
|
// Force xterm-256color so pi can render and start execution.
|
|
@@ -269,6 +269,7 @@ export function spawnMergeAgent(
|
|
|
269
269
|
mergeWorkDir: string,
|
|
270
270
|
mergeRequestPath: string,
|
|
271
271
|
config: OrchestratorConfig,
|
|
272
|
+
stateRoot?: string,
|
|
272
273
|
): void {
|
|
273
274
|
execLog("merge", sessionName, "preparing to spawn merge agent", {
|
|
274
275
|
mergeWorkDir,
|
|
@@ -303,7 +304,7 @@ export function spawnMergeAgent(
|
|
|
303
304
|
"pi --no-session",
|
|
304
305
|
modelArgs,
|
|
305
306
|
toolsArgs,
|
|
306
|
-
`--append-system-prompt ${shellQuote(join(repoRoot, ".pi", "agents", "task-merger.md"))}`,
|
|
307
|
+
`--append-system-prompt ${shellQuote(join(stateRoot ?? repoRoot, ".pi", "agents", "task-merger.md"))}`,
|
|
307
308
|
`@${shellQuote(mergeRequestPath)}`,
|
|
308
309
|
].filter(Boolean).join(" ");
|
|
309
310
|
|
|
@@ -507,6 +508,7 @@ export function mergeWave(
|
|
|
507
508
|
repoRoot: string,
|
|
508
509
|
batchId: string,
|
|
509
510
|
baseBranch: string,
|
|
511
|
+
stateRoot?: string,
|
|
510
512
|
): MergeWaveResult {
|
|
511
513
|
const startTime = Date.now();
|
|
512
514
|
const tmuxPrefix = config.orchestrator.tmux_prefix;
|
|
@@ -613,9 +615,10 @@ export function mergeWave(
|
|
|
613
615
|
const laneStart = Date.now();
|
|
614
616
|
const sessionName = `${tmuxPrefix}-${opId}-merge-${lane.laneNumber}`;
|
|
615
617
|
const resultFileName = `merge-result-w${waveIndex}-lane${lane.laneNumber}-${opId}-${batchId}.json`;
|
|
616
|
-
const
|
|
618
|
+
const piDir = stateRoot ?? repoRoot;
|
|
619
|
+
const resultFilePath = join(piDir, ".pi", resultFileName);
|
|
617
620
|
const requestFileName = `merge-request-w${waveIndex}-lane${lane.laneNumber}-${opId}-${batchId}.txt`;
|
|
618
|
-
const requestFilePath = join(
|
|
621
|
+
const requestFilePath = join(piDir, ".pi", requestFileName);
|
|
619
622
|
|
|
620
623
|
execLog("merge", sessionName, `starting merge for lane ${lane.laneNumber}`, {
|
|
621
624
|
sourceBranch: lane.branch,
|
|
@@ -645,7 +648,7 @@ export function mergeWave(
|
|
|
645
648
|
writeFileSync(requestFilePath, mergeRequestContent, "utf-8");
|
|
646
649
|
|
|
647
650
|
// Spawn merge agent in the isolated merge worktree
|
|
648
|
-
spawnMergeAgent(sessionName, repoRoot, mergeWorkDir, requestFilePath, config);
|
|
651
|
+
spawnMergeAgent(sessionName, repoRoot, mergeWorkDir, requestFilePath, config, stateRoot);
|
|
649
652
|
|
|
650
653
|
// Wait for result
|
|
651
654
|
const mergeResult = waitForMergeResult(resultFilePath, sessionName);
|
|
@@ -890,6 +893,7 @@ export function mergeWaveByRepo(
|
|
|
890
893
|
batchId: string,
|
|
891
894
|
baseBranch: string,
|
|
892
895
|
workspaceConfig?: WorkspaceConfig | null,
|
|
896
|
+
stateRoot?: string,
|
|
893
897
|
): MergeWaveResult {
|
|
894
898
|
const startTime = Date.now();
|
|
895
899
|
|
|
@@ -942,6 +946,7 @@ export function mergeWaveByRepo(
|
|
|
942
946
|
repoRoot,
|
|
943
947
|
batchId,
|
|
944
948
|
baseBranch,
|
|
949
|
+
stateRoot,
|
|
945
950
|
);
|
|
946
951
|
// Attach empty repoResults for consistent shape
|
|
947
952
|
return { ...result, repoResults: [] };
|
|
@@ -985,6 +990,7 @@ export function mergeWaveByRepo(
|
|
|
985
990
|
groupRepoRoot,
|
|
986
991
|
batchId,
|
|
987
992
|
groupBaseBranch,
|
|
993
|
+
stateRoot,
|
|
988
994
|
);
|
|
989
995
|
|
|
990
996
|
// Accumulate lane results
|