taskplane 0.24.0 → 0.24.2

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.
@@ -853,18 +853,26 @@ export async function resolveTaskMonitorState(
853
853
  runtimeBackend?: RuntimeBackend,
854
854
  v2Context?: { stateRoot: string; batchId: string; laneNumber: number },
855
855
  ): Promise<TaskMonitorSnapshot> {
856
- // TP-115: Backend-aware liveness check.
856
+ // TP-115/TP-127: Backend-aware liveness check.
857
857
  // V2: read the lane snapshot file written by lane-runner every second.
858
- // Snapshot status is authoritative — no PID probing needed.
859
858
  // If snapshot doesn't exist yet, assume alive (lane-runner startup race).
859
+ // If snapshot belongs to a different task, it's stale transition data from
860
+ // the previous wave/task and should be treated like startup grace (alive).
860
861
  // Legacy: check lane-session liveness.
861
862
  let sessionAlive: boolean;
862
863
  if (runtimeBackend === "v2" && v2Context) {
863
864
  const snap = readLaneSnapshot(v2Context.stateRoot, v2Context.batchId, v2Context.laneNumber);
864
- if (snap == null) {
865
- // Snapshot not written yet lane-runner is still starting up.
866
- // Assume alive to avoid false "failed" from monitor racing lane startup.
867
- sessionAlive = true;
865
+ if (snap == null || snap.taskId !== taskId) {
866
+ // Snapshot not written yet OR snapshot still points to a prior task.
867
+ // Assume alive initially, but if stale for >30s consult the registry
868
+ // to avoid indefinite false "running" if the lane-runner died.
869
+ const staleMs = snap?.updatedAt ? (now - snap.updatedAt) : 0;
870
+ if (staleMs > 30_000) {
871
+ // Snapshot hasn't been updated for 30s+ — check registry as fallback
872
+ sessionAlive = isV2AgentAlive(sessionName, runtimeBackend);
873
+ } else {
874
+ sessionAlive = true;
875
+ }
868
876
  } else {
869
877
  sessionAlive = snap.status === "running";
870
878
  }
@@ -283,6 +283,7 @@ export async function executeTaskV2(
283
283
 
284
284
  // Context pressure: write wrap-up signal before kill
285
285
  let workerKillReason: "context" | "timer" | null = null;
286
+ let iterationTelemetry: Partial<AgentHostResult> = {};
286
287
 
287
288
  const spawned = spawnAgent(hostOpts, undefined, (telemetry) => {
288
289
  // Context pressure check
@@ -298,12 +299,25 @@ export async function executeTaskV2(
298
299
  }
299
300
  }
300
301
 
302
+ iterationTelemetry = telemetry;
301
303
  lastTelemetry = telemetry;
302
304
  // Emit lane snapshot
303
305
  emitSnapshot(config, taskId, "running", telemetry, statusPath);
304
306
  });
305
307
 
306
- const workerResult = await spawned.promise;
308
+ // Reviewer telemetry is written by the worker bridge during review_step.
309
+ // Poll snapshot refresh independently from worker message_end cadence so
310
+ // the dashboard sees reviewer activity while tool calls are in-flight.
311
+ const reviewerRefresh = setInterval(() => {
312
+ emitSnapshot(config, taskId, "running", iterationTelemetry, statusPath);
313
+ }, 1000);
314
+
315
+ let workerResult: AgentHostResult;
316
+ try {
317
+ workerResult = await spawned.promise;
318
+ } finally {
319
+ clearInterval(reviewerRefresh);
320
+ }
307
321
 
308
322
  // TP-115: Update lastTelemetry with definitive final values from AgentHostResult
309
323
  lastTelemetry = workerResult;
@@ -538,6 +552,59 @@ function makeResult(
538
552
  return result;
539
553
  }
540
554
 
555
+ /** Max age for reviewer state file before it's considered stale (2 minutes). */
556
+ const REVIEWER_STATE_STALE_MS = 120_000;
557
+
558
+ export function readReviewerTelemetrySnapshot(
559
+ config: LaneRunnerConfig,
560
+ statusPath: string,
561
+ ): (RuntimeAgentTelemetrySnapshot & { reviewType?: string; reviewStep?: number }) | null {
562
+ const reviewerPath = join(dirname(statusPath), ".reviewer-state.json");
563
+ if (!existsSync(reviewerPath)) return null;
564
+
565
+ try {
566
+ const raw = readFileSync(reviewerPath, "utf-8");
567
+ const parsed = JSON.parse(raw) as Partial<{
568
+ status: string;
569
+ elapsedMs: number;
570
+ toolCalls: number;
571
+ contextPct: number;
572
+ costUsd: number;
573
+ lastTool: string;
574
+ inputTokens: number;
575
+ outputTokens: number;
576
+ cacheReadTokens: number;
577
+ cacheWriteTokens: number;
578
+ updatedAt: number;
579
+ reviewType: string;
580
+ reviewStep: number;
581
+ }>;
582
+
583
+ if (parsed.status !== "running") return null;
584
+
585
+ // Stale guard: if updatedAt is present and older than threshold, ignore
586
+ if (parsed.updatedAt && (Date.now() - parsed.updatedAt) > REVIEWER_STATE_STALE_MS) return null;
587
+
588
+ return {
589
+ agentId: buildRuntimeAgentId(config.agentIdPrefix, config.laneNumber, "reviewer"),
590
+ status: "running",
591
+ elapsedMs: Number.isFinite(parsed.elapsedMs) ? Number(parsed.elapsedMs) : 0,
592
+ toolCalls: Number.isFinite(parsed.toolCalls) ? Number(parsed.toolCalls) : 0,
593
+ contextPct: Number.isFinite(parsed.contextPct) ? Number(parsed.contextPct) : 0,
594
+ costUsd: Number.isFinite(parsed.costUsd) ? Number(parsed.costUsd) : 0,
595
+ lastTool: typeof parsed.lastTool === "string" ? parsed.lastTool : "",
596
+ inputTokens: Number.isFinite(parsed.inputTokens) ? Number(parsed.inputTokens) : 0,
597
+ outputTokens: Number.isFinite(parsed.outputTokens) ? Number(parsed.outputTokens) : 0,
598
+ cacheReadTokens: Number.isFinite(parsed.cacheReadTokens) ? Number(parsed.cacheReadTokens) : 0,
599
+ cacheWriteTokens: Number.isFinite(parsed.cacheWriteTokens) ? Number(parsed.cacheWriteTokens) : 0,
600
+ reviewType: typeof parsed.reviewType === "string" ? parsed.reviewType : undefined,
601
+ reviewStep: Number.isFinite(parsed.reviewStep) ? Number(parsed.reviewStep) : undefined,
602
+ };
603
+ } catch {
604
+ return null;
605
+ }
606
+ }
607
+
541
608
  function emitSnapshot(
542
609
  config: LaneRunnerConfig,
543
610
  taskId: string,
@@ -562,6 +629,8 @@ function emitSnapshot(
562
629
  };
563
630
  } catch { /* best effort */ }
564
631
 
632
+ const reviewerSnapshot = readReviewerTelemetrySnapshot(config, statusPath);
633
+
565
634
  const snapshot: RuntimeLaneSnapshot = {
566
635
  batchId: config.batchId,
567
636
  laneNumber: config.laneNumber,
@@ -583,7 +652,7 @@ function emitSnapshot(
583
652
  cacheReadTokens: telemetry.cacheReadTokens ?? 0,
584
653
  cacheWriteTokens: telemetry.cacheWriteTokens ?? 0,
585
654
  },
586
- reviewer: null,
655
+ reviewer: reviewerSnapshot,
587
656
  progress,
588
657
  updatedAt: Date.now(),
589
658
  };
@@ -353,7 +353,7 @@ export function readLaneSnapshot(
353
353
  stateRoot: string,
354
354
  batchId: string,
355
355
  laneNumber: number,
356
- ): { status: string; updatedAt?: number } | null {
356
+ ): { taskId?: string | null; status: string; updatedAt?: number } | null {
357
357
  try {
358
358
  const p = runtimeLaneSnapshotPath(stateRoot, batchId, laneNumber);
359
359
  if (!existsSync(p)) return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taskplane",
3
- "version": "0.24.0",
3
+ "version": "0.24.2",
4
4
  "description": "AI agent orchestration for pi — parallel task execution with checkpoint discipline",
5
5
  "keywords": [
6
6
  "pi-package",