taskplane 0.23.3 → 0.23.5
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.
|
@@ -2253,28 +2253,57 @@ export async function executeOrchBatch(
|
|
|
2253
2253
|
|
|
2254
2254
|
// ── Save batch history (before cleanup deletes sidecar files) ────
|
|
2255
2255
|
try {
|
|
2256
|
-
// Read token data from sidecar files
|
|
2256
|
+
// Read token data from sidecar files or V2 lane snapshots
|
|
2257
2257
|
const piDir = join(stateRoot, ".pi");
|
|
2258
2258
|
const laneTokens = new Map<string, TokenCounts>();
|
|
2259
|
+
|
|
2260
|
+
// TP-115: Try V2 lane snapshots first (authoritative for Runtime V2)
|
|
2259
2261
|
try {
|
|
2260
|
-
const
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2262
|
+
const lanesDir = join(piDir, "runtime", batchState.batchId, "lanes");
|
|
2263
|
+
if (existsSync(lanesDir)) {
|
|
2264
|
+
const files = readdirSync(lanesDir).filter(f => f.startsWith("lane-") && f.endsWith(".json"));
|
|
2265
|
+
for (const f of files) {
|
|
2266
|
+
try {
|
|
2267
|
+
const snap = JSON.parse(readFileSync(join(lanesDir, f), "utf-8"));
|
|
2268
|
+
const w = snap.worker || {};
|
|
2269
|
+
const r = snap.reviewer || {};
|
|
2270
|
+
// Key by session name (match lane record) for per-task lookup
|
|
2271
|
+
const laneRec = batchState.lanes.find((l: { laneNumber: number }) => l.laneNumber === snap.laneNumber);
|
|
2272
|
+
const key = laneRec?.tmuxSessionName || `lane-${snap.laneNumber}`;
|
|
2273
|
+
laneTokens.set(key, {
|
|
2274
|
+
input: (w.inputTokens || 0) + (r.inputTokens || 0),
|
|
2275
|
+
output: (w.outputTokens || 0) + (r.outputTokens || 0),
|
|
2276
|
+
cacheRead: (w.cacheReadTokens || 0) + (r.cacheReadTokens || 0),
|
|
2277
|
+
cacheWrite: (w.cacheWriteTokens || 0) + (r.cacheWriteTokens || 0),
|
|
2278
|
+
costUsd: (w.costUsd || 0) + (r.costUsd || 0),
|
|
2273
2279
|
});
|
|
2274
|
-
}
|
|
2275
|
-
}
|
|
2280
|
+
} catch { /* skip invalid files */ }
|
|
2281
|
+
}
|
|
2276
2282
|
}
|
|
2277
|
-
} catch { /*
|
|
2283
|
+
} catch { /* runtime dir may not exist */ }
|
|
2284
|
+
|
|
2285
|
+
// Legacy fallback: lane-state-*.json sidecars (only if V2 found nothing)
|
|
2286
|
+
if (laneTokens.size === 0) {
|
|
2287
|
+
try {
|
|
2288
|
+
const files = readdirSync(piDir).filter(f => f.startsWith("lane-state-") && f.endsWith(".json"));
|
|
2289
|
+
for (const f of files) {
|
|
2290
|
+
try {
|
|
2291
|
+
const raw = readFileSync(join(piDir, f), "utf-8").trim();
|
|
2292
|
+
if (!raw) continue;
|
|
2293
|
+
const data = JSON.parse(raw);
|
|
2294
|
+
if (data.prefix) {
|
|
2295
|
+
laneTokens.set(data.prefix, {
|
|
2296
|
+
input: data.workerInputTokens || 0,
|
|
2297
|
+
output: data.workerOutputTokens || 0,
|
|
2298
|
+
cacheRead: data.workerCacheReadTokens || 0,
|
|
2299
|
+
cacheWrite: data.workerCacheWriteTokens || 0,
|
|
2300
|
+
costUsd: data.workerCostUsd || 0,
|
|
2301
|
+
});
|
|
2302
|
+
}
|
|
2303
|
+
} catch { /* skip invalid files */ }
|
|
2304
|
+
}
|
|
2305
|
+
} catch { /* .pi dir may not exist */ }
|
|
2306
|
+
}
|
|
2278
2307
|
|
|
2279
2308
|
// Build per-task summaries from allTaskOutcomes + wave plan
|
|
2280
2309
|
const taskSummaries: BatchTaskSummary[] = allTaskOutcomes.map((to) => {
|
|
@@ -1758,13 +1758,19 @@ export async function resolveTaskMonitorState(
|
|
|
1758
1758
|
// TP-115: Backend-aware liveness check.
|
|
1759
1759
|
// V2: read the lane snapshot file written by lane-runner every second.
|
|
1760
1760
|
// Snapshot status is authoritative — no PID probing needed.
|
|
1761
|
+
// If snapshot doesn't exist yet, assume alive (lane-runner startup race).
|
|
1761
1762
|
// Legacy: check TMUX session.
|
|
1762
1763
|
let sessionAlive: boolean;
|
|
1763
1764
|
if (runtimeBackend === "v2" && v2Context) {
|
|
1764
1765
|
const snap = readLaneSnapshot(v2Context.stateRoot, v2Context.batchId, v2Context.laneNumber);
|
|
1765
|
-
|
|
1766
|
+
if (snap == null) {
|
|
1767
|
+
// Snapshot not written yet — lane-runner is still starting up.
|
|
1768
|
+
// Assume alive to avoid false "failed" from monitor racing lane startup.
|
|
1769
|
+
sessionAlive = true;
|
|
1770
|
+
} else {
|
|
1771
|
+
sessionAlive = snap.status === "running";
|
|
1772
|
+
}
|
|
1766
1773
|
} else if (runtimeBackend === "v2") {
|
|
1767
|
-
// Fallback to registry-based check if no v2Context
|
|
1768
1774
|
sessionAlive = isV2AgentAlive(sessionName, runtimeBackend);
|
|
1769
1775
|
} else {
|
|
1770
1776
|
sessionAlive = await tmuxHasSessionAsync(sessionName);
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
|
|
29
29
|
import { join, dirname } from "path";
|
|
30
30
|
import { fileURLToPath } from "url";
|
|
31
|
-
import { existsSync, readFileSync, writeFileSync, unlinkSync, mkdirSync, renameSync, statSync, openSync, readSync, closeSync, appendFileSync } from "fs";
|
|
31
|
+
import { existsSync, readFileSync, readdirSync, writeFileSync, unlinkSync, mkdirSync, renameSync, statSync, openSync, readSync, closeSync, appendFileSync } from "fs";
|
|
32
32
|
import { stat as fsStat, open as fsOpen, readFile as fsReadFile, writeFile as fsWriteFile, rename as fsRename } from "fs/promises";
|
|
33
33
|
import { execFileSync } from "child_process";
|
|
34
34
|
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
@@ -1264,6 +1264,30 @@ function formatDurationMs(ms: number): string {
|
|
|
1264
1264
|
*
|
|
1265
1265
|
* @since TP-043
|
|
1266
1266
|
*/
|
|
1267
|
+
|
|
1268
|
+
/**
|
|
1269
|
+
* TP-115: Compute batch cost from V2 lane snapshots.
|
|
1270
|
+
* Reads .pi/runtime/{batchId}/lanes/*.json and sums worker + reviewer costUsd.
|
|
1271
|
+
* Returns 0 if no V2 data exists.
|
|
1272
|
+
* @since TP-115
|
|
1273
|
+
*/
|
|
1274
|
+
function computeV2BatchCost(stateRoot: string, batchId: string): number {
|
|
1275
|
+
try {
|
|
1276
|
+
const lanesDir = join(stateRoot, ".pi", "runtime", batchId, "lanes");
|
|
1277
|
+
if (!existsSync(lanesDir)) return 0;
|
|
1278
|
+
const files = readdirSync(lanesDir).filter(f => f.startsWith("lane-") && f.endsWith(".json"));
|
|
1279
|
+
let total = 0;
|
|
1280
|
+
for (const f of files) {
|
|
1281
|
+
try {
|
|
1282
|
+
const snap = JSON.parse(readFileSync(join(lanesDir, f), "utf-8"));
|
|
1283
|
+
total += snap.worker?.costUsd || 0;
|
|
1284
|
+
total += snap.reviewer?.costUsd || 0;
|
|
1285
|
+
} catch { /* skip */ }
|
|
1286
|
+
}
|
|
1287
|
+
return total;
|
|
1288
|
+
} catch { return 0; }
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1267
1291
|
export function collectBatchSummaryData(
|
|
1268
1292
|
batchState: OrchBatchRuntimeState,
|
|
1269
1293
|
stateRoot: string,
|
|
@@ -1297,7 +1321,9 @@ export function collectBatchSummaryData(
|
|
|
1297
1321
|
failedTasks: batchState.failedTasks,
|
|
1298
1322
|
skippedTasks: batchState.skippedTasks,
|
|
1299
1323
|
blockedTasks: batchState.blockedTasks,
|
|
1300
|
-
batchCost: diagnostics?.batchCost ?? 0
|
|
1324
|
+
batchCost: (diagnostics?.batchCost ?? 0) > 0
|
|
1325
|
+
? diagnostics!.batchCost
|
|
1326
|
+
: computeV2BatchCost(stateRoot, batchState.batchId),
|
|
1301
1327
|
wavePlan: [], // Not directly available on runtime state — use waveResults
|
|
1302
1328
|
waveResults,
|
|
1303
1329
|
taskExits: diagnostics?.taskExits ?? {},
|