taskplane 0.23.1 → 0.23.3

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.
@@ -1015,6 +1015,39 @@ function buildDashboardState() {
1015
1015
  const runtimeLaneSnapshots = loadRuntimeLaneSnapshots(state.batchId);
1016
1016
  const mailboxData = loadMailboxData(state.batchId);
1017
1017
 
1018
+ // TP-115: Synthesize laneStates from V2 snapshots so the dashboard
1019
+ // pipeline works without legacy lane-state-*.json sidecar files.
1020
+ // V2 snapshots are authoritative when present.
1021
+ if (Object.keys(runtimeLaneSnapshots).length > 0) {
1022
+ for (const [laneNum, snap] of Object.entries(runtimeLaneSnapshots)) {
1023
+ // Find the matching lane record to get the session name key
1024
+ const laneRec = (state.lanes || []).find(l => l.laneNumber === Number(laneNum));
1025
+ const key = laneRec ? laneRec.tmuxSessionName : `lane-${laneNum}`;
1026
+ if (!laneStates[key] || (snap.updatedAt && snap.updatedAt > (laneStates[key].timestamp || 0))) {
1027
+ const w = snap.worker || {};
1028
+ const statusMap = { running: "running", spawning: "running", exited: "done", crashed: "error", killed: "error", timed_out: "error", wrapping_up: "running" };
1029
+ laneStates[key] = {
1030
+ prefix: key,
1031
+ taskId: snap.taskId || null,
1032
+ phase: snap.status === "running" ? "worker-active" : snap.status === "complete" ? "complete" : "idle",
1033
+ workerStatus: statusMap[w.status] || w.status || "idle",
1034
+ workerElapsed: w.elapsedMs || 0,
1035
+ workerContextPct: w.contextPct || 0,
1036
+ workerLastTool: w.lastTool || "",
1037
+ workerToolCount: w.toolCalls || 0,
1038
+ workerInputTokens: w.inputTokens || 0,
1039
+ workerOutputTokens: w.outputTokens || 0,
1040
+ workerCacheReadTokens: w.cacheReadTokens || 0,
1041
+ workerCacheWriteTokens: w.cacheWriteTokens || 0,
1042
+ workerCostUsd: w.costUsd || 0,
1043
+ reviewerStatus: "idle",
1044
+ batchId: snap.batchId || state.batchId,
1045
+ timestamp: snap.updatedAt || Date.now(),
1046
+ };
1047
+ }
1048
+ }
1049
+ }
1050
+
1018
1051
  return {
1019
1052
  laneStates,
1020
1053
  telemetry,
@@ -11,8 +11,9 @@ import { userInfo } from "os";
11
11
  import { DONE_GRACE_MS, EXECUTION_POLL_INTERVAL_MS, ExecutionError, SESSION_SPAWN_RETRY_MAX } from "./types.ts";
12
12
  import type { AllocatedLane, AllocatedTask, DependencyGraph, LaneExecutionResult, LaneMonitorSnapshot, LaneTaskOutcome, LaneTaskStatus, MonitorState, MtimeTracker, OrchestratorConfig, ParsedTask, TaskMonitorSnapshot, WaveExecutionResult, WorkspaceConfig, ExecutionUnit, PacketPaths, RuntimeAgentId, RuntimeAgentRole, SupervisorAlertCallback } from "./types.ts";
13
13
  import { resolvePacketPaths, buildRuntimeAgentId } from "./types.ts";
14
- import { readRegistrySnapshot, isTerminalStatus, isProcessAlive } from "./process-registry.ts";
14
+ import { readRegistrySnapshot, readLaneSnapshot, isTerminalStatus, isProcessAlive } from "./process-registry.ts";
15
15
  import { allocateLanes } from "./waves.ts";
16
+ import { resolveOperatorId } from "./naming.ts";
16
17
  import { runGit } from "./git.ts";
17
18
 
18
19
  // ── Taskplane Package File Resolution ────────────────────────────────
@@ -1752,11 +1753,18 @@ export async function resolveTaskMonitorState(
1752
1753
  stallTimeoutMs: number,
1753
1754
  now: number,
1754
1755
  runtimeBackend?: RuntimeBackend,
1756
+ v2Context?: { stateRoot: string; batchId: string; laneNumber: number },
1755
1757
  ): Promise<TaskMonitorSnapshot> {
1756
- // TP-112: Backend-aware liveness check.
1757
- // V2: check process registry (PID liveness). Legacy: check TMUX session.
1758
+ // TP-115: Backend-aware liveness check.
1759
+ // V2: read the lane snapshot file written by lane-runner every second.
1760
+ // Snapshot status is authoritative — no PID probing needed.
1761
+ // Legacy: check TMUX session.
1758
1762
  let sessionAlive: boolean;
1759
- if (runtimeBackend === "v2") {
1763
+ if (runtimeBackend === "v2" && v2Context) {
1764
+ const snap = readLaneSnapshot(v2Context.stateRoot, v2Context.batchId, v2Context.laneNumber);
1765
+ sessionAlive = snap != null && snap.status === "running";
1766
+ } else if (runtimeBackend === "v2") {
1767
+ // Fallback to registry-based check if no v2Context
1760
1768
  sessionAlive = isV2AgentAlive(sessionName, runtimeBackend);
1761
1769
  } else {
1762
1770
  sessionAlive = await tmuxHasSessionAsync(sessionName);
@@ -2080,6 +2088,11 @@ export async function monitorLanes(
2080
2088
  stallTimeoutMs,
2081
2089
  now,
2082
2090
  runtimeBackend,
2091
+ (runtimeBackend === "v2" && batchId) ? {
2092
+ stateRoot: stateRootForRegistry ?? repoRoot,
2093
+ batchId,
2094
+ laneNumber: lane.laneNumber,
2095
+ } : undefined,
2083
2096
  );
2084
2097
 
2085
2098
  currentTaskSnapshot = snapshot;
@@ -2957,9 +2970,10 @@ export async function executeLaneV2(
2957
2970
  const stateRoot = resolveRuntimeStateRoot(repoRoot, workspaceRoot);
2958
2971
  const batchId = config.orchestrator?.batchId || extraEnvVars?.ORCH_BATCH_ID || String(Date.now());
2959
2972
 
2960
- // Build agent ID prefix from orchestrator config
2973
+ // Build agent ID prefix must match the wave planner's naming (TP-115).
2974
+ // Uses resolveOperatorId() so agent registry keys align with tmuxSessionName.
2961
2975
  const tmuxPrefix = config.orchestrator?.tmux_prefix ?? "orch";
2962
- const opId = config.orchestrator?.operatorId || "op";
2976
+ const opId = resolveOperatorId(config);
2963
2977
  const agentIdPrefix = `${tmuxPrefix}-${opId}`;
2964
2978
 
2965
2979
  // Load worker agent definition for system prompt
@@ -572,3 +572,4 @@ function emitSnapshot(
572
572
 
573
573
  writeLaneSnapshot(config.stateRoot, config.batchId, config.laneNumber, snapshot as any);
574
574
  }
575
+
@@ -343,3 +343,22 @@ export function writeLaneSnapshot(
343
343
  writeFileSync(tmpPath, JSON.stringify(snapshot, null, 2) + "\n", "utf-8");
344
344
  renameSync(tmpPath, path);
345
345
  }
346
+
347
+ /**
348
+ * Read a V2 lane snapshot from disk.
349
+ * Returns null if the file doesn't exist or is unreadable.
350
+ * @since TP-115
351
+ */
352
+ export function readLaneSnapshot(
353
+ stateRoot: string,
354
+ batchId: string,
355
+ laneNumber: number,
356
+ ): { status: string; updatedAt?: number } | null {
357
+ try {
358
+ const p = runtimeLaneSnapshotPath(stateRoot, batchId, laneNumber);
359
+ if (!existsSync(p)) return null;
360
+ return JSON.parse(readFileSync(p, "utf-8"));
361
+ } catch {
362
+ return null;
363
+ }
364
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taskplane",
3
- "version": "0.23.1",
3
+ "version": "0.23.3",
4
4
  "description": "AI agent orchestration for pi — parallel task execution with checkpoint discipline",
5
5
  "keywords": [
6
6
  "pi-package",