taskplane 0.22.12 → 0.22.13

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.
@@ -37,6 +37,8 @@ export interface PostIntegrateCleanupResult {
37
37
  promptFilesDeleted: number;
38
38
  /** Number of mailbox batch directories deleted (0 or 1) */
39
39
  mailboxDirsDeleted: number;
40
+ /** Number of context-snapshot batch directories deleted (0 or 1) */
41
+ snapshotDirsDeleted: number;
40
42
  /** Warnings from non-fatal cleanup failures */
41
43
  warnings: string[];
42
44
  }
@@ -61,6 +63,7 @@ export function cleanupPostIntegrate(stateRoot: string, batchId: string): PostIn
61
63
  mergeFilesDeleted: 0,
62
64
  promptFilesDeleted: 0,
63
65
  mailboxDirsDeleted: 0,
66
+ snapshotDirsDeleted: 0,
64
67
  warnings: [],
65
68
  };
66
69
 
@@ -134,6 +137,17 @@ export function cleanupPostIntegrate(stateRoot: string, batchId: string): PostIn
134
137
  }
135
138
  }
136
139
 
140
+ // ── Context snapshots directory (.pi/context-snapshots/{batchId}/) ──────
141
+ const snapshotBatchDir = join(stateRoot, ".pi", "context-snapshots", batchId);
142
+ if (existsSync(snapshotBatchDir)) {
143
+ try {
144
+ rmSync(snapshotBatchDir, { recursive: true, force: true });
145
+ result.snapshotDirsDeleted = 1;
146
+ } catch (err: unknown) {
147
+ result.warnings.push(`Failed to delete context-snapshots directory ${snapshotBatchDir}: ${(err as Error).message}`);
148
+ }
149
+ }
150
+
137
151
  return result;
138
152
  }
139
153
 
@@ -142,7 +156,7 @@ export function cleanupPostIntegrate(stateRoot: string, batchId: string): PostIn
142
156
  */
143
157
  export function formatPostIntegrateCleanup(result: PostIntegrateCleanupResult): string {
144
158
  const parts: string[] = [];
145
- const totalDeleted = result.telemetryFilesDeleted + result.mergeFilesDeleted + result.promptFilesDeleted + result.mailboxDirsDeleted;
159
+ const totalDeleted = result.telemetryFilesDeleted + result.mergeFilesDeleted + result.promptFilesDeleted + result.mailboxDirsDeleted + result.snapshotDirsDeleted;
146
160
 
147
161
  if (totalDeleted > 0) {
148
162
  const segments: string[] = [];
@@ -150,6 +164,7 @@ export function formatPostIntegrateCleanup(result: PostIntegrateCleanupResult):
150
164
  if (result.mergeFilesDeleted > 0) segments.push(`${result.mergeFilesDeleted} merge`);
151
165
  if (result.promptFilesDeleted > 0) segments.push(`${result.promptFilesDeleted} prompt`);
152
166
  if (result.mailboxDirsDeleted > 0) segments.push(`${result.mailboxDirsDeleted} mailbox`);
167
+ if (result.snapshotDirsDeleted > 0) segments.push(`${result.snapshotDirsDeleted} snapshots`);
153
168
  parts.push(`🧹 Cleaned up ${totalDeleted} artifact file(s): ${segments.join(", ")}`);
154
169
  }
155
170
 
@@ -274,13 +289,13 @@ export function sweepStaleArtifacts(
274
289
  (name.startsWith("merge-request-") && name.endsWith(".txt")),
275
290
  );
276
291
 
277
- // Sweep stale mailbox batch directories (.pi/mailbox/{batchId}/)
278
- const mailboxBase = join(stateRoot, ".pi", MAILBOX_DIR_NAME);
279
- if (existsSync(mailboxBase)) {
292
+ // Sweep stale batch directories under a parent (mailbox, context-snapshots)
293
+ const sweepBatchDirs = (parentDir: string, label: string): void => {
294
+ if (!existsSync(parentDir)) return;
280
295
  try {
281
- const entries = readdirSync(mailboxBase);
296
+ const entries = readdirSync(parentDir);
282
297
  for (const entry of entries) {
283
- const entryPath = join(mailboxBase, entry);
298
+ const entryPath = join(parentDir, entry);
284
299
  try {
285
300
  const stat = statSync(entryPath);
286
301
  if (!stat.isDirectory()) continue;
@@ -289,13 +304,19 @@ export function sweepStaleArtifacts(
289
304
  result.staleDirsDeleted++;
290
305
  }
291
306
  } catch (err: unknown) {
292
- result.warnings.push(`Failed to process mailbox dir ${entry}: ${(err as Error).message}`);
307
+ result.warnings.push(`Failed to process ${label} dir ${entry}: ${(err as Error).message}`);
293
308
  }
294
309
  }
295
310
  } catch (err: unknown) {
296
- result.warnings.push(`Failed to read mailbox directory ${mailboxBase}: ${(err as Error).message}`);
311
+ result.warnings.push(`Failed to read ${label} directory ${parentDir}: ${(err as Error).message}`);
297
312
  }
298
- }
313
+ };
314
+
315
+ // Sweep stale mailbox batch directories (.pi/mailbox/{batchId}/)
316
+ sweepBatchDirs(join(stateRoot, ".pi", MAILBOX_DIR_NAME), "mailbox");
317
+
318
+ // Sweep stale context-snapshot batch directories (.pi/context-snapshots/{batchId}/)
319
+ sweepBatchDirs(join(stateRoot, ".pi", "context-snapshots"), "context-snapshots");
299
320
 
300
321
  return result;
301
322
  }
@@ -1088,18 +1088,6 @@ export async function executeOrchBatch(
1088
1088
  continue;
1089
1089
  }
1090
1090
 
1091
- onNotify(
1092
- ORCH_MESSAGES.orchWaveStart(waveIdx + 1, rawWaves.length, waveTasks.length, Math.min(waveTasks.length, orchConfig.orchestrator.max_lanes)),
1093
- "info",
1094
- );
1095
-
1096
- // TP-040: Emit wave_start event
1097
- emitEvent(stateRoot, {
1098
- ...buildEngineEventBase("wave_start", batchState.batchId, waveIdx, batchState.phase),
1099
- taskIds: waveTasks,
1100
- laneCount: Math.min(waveTasks.length, orchConfig.orchestrator.max_lanes),
1101
- }, onEngineEvent);
1102
-
1103
1091
  const handleWaveMonitorUpdate: MonitorUpdateCallback = (monitorState) => {
1104
1092
  const changed = syncTaskOutcomesFromMonitor(monitorState, allTaskOutcomes);
1105
1093
  if (changed) {
@@ -1112,6 +1100,17 @@ export async function executeOrchBatch(
1112
1100
  const onLanesAllocatedCb = (lanes: AllocatedLane[]) => {
1113
1101
  latestAllocatedLanes = lanes;
1114
1102
  batchState.currentLanes = lanes;
1103
+
1104
+ // Emit wave_start with actual lane count (post-affinity grouping)
1105
+ onNotify(
1106
+ ORCH_MESSAGES.orchWaveStart(waveIdx + 1, rawWaves.length, waveTasks.length, lanes.length),
1107
+ "info",
1108
+ );
1109
+ emitEvent(stateRoot, {
1110
+ ...buildEngineEventBase("wave_start", batchState.batchId, waveIdx, batchState.phase),
1111
+ taskIds: waveTasks,
1112
+ laneCount: lanes.length,
1113
+ }, onEngineEvent);
1115
1114
  // TP-029: Track repos from newly allocated lanes for cleanup coverage
1116
1115
  for (const lane of lanes) {
1117
1116
  const laneRepoRoot = resolveRepoRoot(lane.repoId, repoRoot, workspaceConfig);
@@ -594,9 +594,26 @@ export function buildTmuxSpawnArgs(
594
594
  piCommand = `${envParts} pi --no-session -e ${shellQuote(taskRunnerExtPath)}`;
595
595
  }
596
596
 
597
- // NOTE: Do not redirect lane output here. Shell redirection has proven
598
- // fragile across Windows + tmux environments and can prevent session spawn.
599
- // Diagnostics use tmux pane capture + STATUS tail in pollUntilTaskComplete().
597
+ // TP-095: Capture lane session stderr to a log file (#339).
598
+ // When the lane session (rpc-wrapper pi task-runner) dies, stderr is
599
+ // lost to tmux scrollback. Redirect stderr to a persistent log file
600
+ // co-located with telemetry so the supervisor can diagnose lane deaths.
601
+ //
602
+ // We append stderr to a file using `2>>`. This captures all stderr output
603
+ // from rpc-wrapper (which includes pi stderr forwarding, progress display,
604
+ // and crash diagnostics). The tmux pane loses live stderr visibility, but
605
+ // the dashboard provides live monitoring and the file preserves everything
606
+ // for post-mortem analysis.
607
+ //
608
+ // Appended to piCommand (not the tmux shell wrapper) to target the
609
+ // node/rpc-wrapper process specifically. This avoids the fragile shell
610
+ // redirection issues that previously caused spawn failures on Windows.
611
+ if (sidecarPath) {
612
+ // Derive stderr log path from sidecar path:
613
+ // .pi/telemetry/{basename}.jsonl → .pi/telemetry/{basename}-stderr.log
614
+ const stderrLogPath = sidecarPath.replace(/\.jsonl$/, "-stderr.log");
615
+ piCommand = `${piCommand} 2>> ${shellQuote(stderrLogPath)}`;
616
+ }
600
617
 
601
618
  const tmuxWorktreePath = toTmuxPath(worktreePath);
602
619
  const wrappedCommand = `cd ${shellQuote(tmuxWorktreePath)} && ${piCommand}`;