taskplane 0.20.6 → 0.21.1
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.
- package/bin/rpc-wrapper.mjs +31 -0
- package/bin/taskplane.mjs +48 -12
- package/extensions/task-runner.ts +44 -16
- package/package.json +1 -1
package/bin/rpc-wrapper.mjs
CHANGED
|
@@ -331,6 +331,8 @@ function createSessionState() {
|
|
|
331
331
|
currentTool: null,
|
|
332
332
|
error: null,
|
|
333
333
|
agentEnded: false,
|
|
334
|
+
/** Authoritative context usage from pi get_session_stats (null if unavailable) */
|
|
335
|
+
contextUsage: null,
|
|
334
336
|
};
|
|
335
337
|
}
|
|
336
338
|
|
|
@@ -414,6 +416,10 @@ function applyEvent(state, event) {
|
|
|
414
416
|
if (event.success === false && event.error) {
|
|
415
417
|
state.error = event.error;
|
|
416
418
|
}
|
|
419
|
+
// get_session_stats response — extract authoritative contextUsage
|
|
420
|
+
if (event.success === true && event.data?.contextUsage) {
|
|
421
|
+
state.contextUsage = event.data.contextUsage;
|
|
422
|
+
}
|
|
417
423
|
break;
|
|
418
424
|
}
|
|
419
425
|
|
|
@@ -455,6 +461,8 @@ function buildExitSummary(state, exitCode, exitSignal, errorOverride, startTime)
|
|
|
455
461
|
durationSec,
|
|
456
462
|
lastToolCall: state.lastToolCall,
|
|
457
463
|
error: finalError,
|
|
464
|
+
// Authoritative context usage from pi ≥ 0.63.0 (null if unavailable)
|
|
465
|
+
contextUsage: state.contextUsage || null,
|
|
458
466
|
};
|
|
459
467
|
|
|
460
468
|
return redactSummary(rawSummary);
|
|
@@ -614,6 +622,22 @@ function closeStdin() {
|
|
|
614
622
|
}
|
|
615
623
|
}
|
|
616
624
|
|
|
625
|
+
/**
|
|
626
|
+
* Query pi for authoritative session stats including contextUsage.
|
|
627
|
+
* Available in pi ≥ 0.63.0 (RPC get_session_stats exposes contextUsage).
|
|
628
|
+
* Safe to call on older versions — the command is ignored or returns
|
|
629
|
+
* without the field, and state.contextUsage stays null.
|
|
630
|
+
*/
|
|
631
|
+
function querySessionStats() {
|
|
632
|
+
try {
|
|
633
|
+
if (proc.stdin && !proc.stdin.destroyed) {
|
|
634
|
+
proc.stdin.write(JSON.stringify({ type: "get_session_stats" }) + "\n");
|
|
635
|
+
}
|
|
636
|
+
} catch {
|
|
637
|
+
// stdin may be closed — ignore
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
617
641
|
// ── Route RPC events ─────────────────────────────────────────────────
|
|
618
642
|
|
|
619
643
|
function handleEvent(event) {
|
|
@@ -628,6 +652,13 @@ function handleEvent(event) {
|
|
|
628
652
|
// Side effects that depend on the event type (IO, stdin lifecycle, display)
|
|
629
653
|
switch (event.type) {
|
|
630
654
|
case "message_end":
|
|
655
|
+
displayProgress(state);
|
|
656
|
+
// Query pi for authoritative context usage (pi ≥ 0.63.0).
|
|
657
|
+
// Falls back gracefully: older pi versions ignore the command
|
|
658
|
+
// or return a response without contextUsage — state.contextUsage stays null.
|
|
659
|
+
querySessionStats();
|
|
660
|
+
break;
|
|
661
|
+
|
|
631
662
|
case "tool_execution_start":
|
|
632
663
|
displayProgress(state);
|
|
633
664
|
break;
|
package/bin/taskplane.mjs
CHANGED
|
@@ -1112,9 +1112,33 @@ async function cmdInit(args) {
|
|
|
1112
1112
|
if (effectiveAlreadyInitialized && resolvedMode === "workspace" && effectiveConfigPath) {
|
|
1113
1113
|
const configRepo = path.basename(path.dirname(effectiveConfigPath));
|
|
1114
1114
|
const configRepoRoot = path.join(projectRoot, configRepo);
|
|
1115
|
+
// Read existing routing from config repo first, then fall back to
|
|
1116
|
+
// the workspace root's .pi/taskplane-workspace.yaml (which --force may overwrite).
|
|
1117
|
+
// This preserves user's tasks_root and default_repo on reinit.
|
|
1115
1118
|
const existingWorkspaceJson = readWorkspaceJson(configRepoRoot);
|
|
1116
|
-
const
|
|
1117
|
-
|
|
1119
|
+
const existingRootYaml = (() => {
|
|
1120
|
+
try {
|
|
1121
|
+
const yamlPath = path.join(projectRoot, ".pi", "taskplane-workspace.yaml");
|
|
1122
|
+
if (fs.existsSync(yamlPath)) {
|
|
1123
|
+
const raw = fs.readFileSync(yamlPath, "utf-8");
|
|
1124
|
+
const tasksMatch = raw.match(/tasks_root:\s*"?([^"\n]+)"?/);
|
|
1125
|
+
const defaultMatch = raw.match(/default_repo:\s*"?([^"\n]+)"?/);
|
|
1126
|
+
return {
|
|
1127
|
+
routing: {
|
|
1128
|
+
tasks_root: tasksMatch?.[1]?.trim() || null,
|
|
1129
|
+
default_repo: defaultMatch?.[1]?.trim() || null,
|
|
1130
|
+
},
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
1133
|
+
} catch {}
|
|
1134
|
+
return null;
|
|
1135
|
+
})();
|
|
1136
|
+
const workspaceTasksRoot = existingWorkspaceJson?.routing?.tasks_root
|
|
1137
|
+
|| existingRootYaml?.routing?.tasks_root
|
|
1138
|
+
|| "taskplane-tasks";
|
|
1139
|
+
const workspaceDefaultRepo = existingWorkspaceJson?.routing?.default_repo
|
|
1140
|
+
|| existingRootYaml?.routing?.default_repo
|
|
1141
|
+
|| configRepo;
|
|
1118
1142
|
const workspaceRepoNames = Array.from(
|
|
1119
1143
|
new Set([
|
|
1120
1144
|
...detection.subRepos,
|
|
@@ -1193,6 +1217,15 @@ async function cmdInit(args) {
|
|
|
1193
1217
|
{ skipIfExists: !force, label: ".pi/taskplane-workspace.yaml" },
|
|
1194
1218
|
);
|
|
1195
1219
|
|
|
1220
|
+
// ── Gitignore enforcement in config repo (Scenario D) ───
|
|
1221
|
+
// Ensure .gitignore exists even when reusing existing config
|
|
1222
|
+
const gitignoreResult = ensureGitignoreEntries(configRepoRoot, { dryRun: false, prefix: ".taskplane/" });
|
|
1223
|
+
if (gitignoreResult.created) {
|
|
1224
|
+
console.log(` ${c.green}create${c.reset} ${configRepo}/.gitignore`);
|
|
1225
|
+
} else if (gitignoreResult.added.length > 0) {
|
|
1226
|
+
console.log(` ${c.green}update${c.reset} ${configRepo}/.gitignore (${gitignoreResult.added.length} entries added)`);
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1196
1229
|
console.log(`\n${OK} ${c.bold}Workspace pointer created.${c.reset}\n`);
|
|
1197
1230
|
console.log(` Config: ${c.cyan}${configRepo}/.taskplane/${c.reset}`);
|
|
1198
1231
|
console.log(` Pointer: ${c.cyan}.pi/taskplane-pointer.json${c.reset}`);
|
|
@@ -2510,18 +2543,20 @@ function cmdDoctor() {
|
|
|
2510
2543
|
console.log();
|
|
2511
2544
|
const hasUnifiedJson = fs.existsSync(path.join(configLocation.root, configLocation.prefix, "taskplane-config.json"));
|
|
2512
2545
|
const configFiles = [
|
|
2513
|
-
{ path: "taskplane-config.json", required: false },
|
|
2514
|
-
|
|
2515
|
-
{ path: "task-
|
|
2516
|
-
{ path: "
|
|
2517
|
-
{ path: "agents/task-
|
|
2518
|
-
{ path: "agents/task-
|
|
2519
|
-
{ path: "agents/
|
|
2520
|
-
|
|
2546
|
+
{ path: "taskplane-config.json", required: false, hide: false },
|
|
2547
|
+
// YAML configs are legacy fallback — hide when taskplane-config.json exists
|
|
2548
|
+
{ path: "task-runner.yaml", required: !hasUnifiedJson, hide: hasUnifiedJson },
|
|
2549
|
+
{ path: "task-orchestrator.yaml", required: !hasUnifiedJson, hide: hasUnifiedJson },
|
|
2550
|
+
{ path: "agents/task-worker.md", required: true, hide: false },
|
|
2551
|
+
{ path: "agents/task-reviewer.md", required: true, hide: false },
|
|
2552
|
+
{ path: "agents/task-merger.md", required: true, hide: false },
|
|
2553
|
+
// supervisor.md is created by /orch; taskplane.json is created at runtime
|
|
2554
|
+
{ path: "agents/supervisor.md", required: false, hide: true },
|
|
2555
|
+
{ path: "taskplane.json", required: false, hide: true },
|
|
2521
2556
|
];
|
|
2522
2557
|
|
|
2523
2558
|
let missingRequiredConfigs = 0;
|
|
2524
|
-
for (const { path: relPath, required } of configFiles) {
|
|
2559
|
+
for (const { path: relPath, required, hide } of configFiles) {
|
|
2525
2560
|
const fullPath = path.join(configLocation.root, configLocation.prefix, relPath);
|
|
2526
2561
|
const displayPath = `${configLocation.label}/${relPath}`;
|
|
2527
2562
|
const exists = fs.existsSync(fullPath);
|
|
@@ -2531,7 +2566,8 @@ function cmdDoctor() {
|
|
|
2531
2566
|
console.log(` ${FAIL} ${displayPath} missing`);
|
|
2532
2567
|
missingRequiredConfigs++;
|
|
2533
2568
|
issues++;
|
|
2534
|
-
} else {
|
|
2569
|
+
} else if (!hide) {
|
|
2570
|
+
// Show optional files only when they're relevant (not superseded)
|
|
2535
2571
|
console.log(` ${WARN} ${displayPath} missing ${c.dim}(optional)${c.reset}`);
|
|
2536
2572
|
}
|
|
2537
2573
|
}
|
|
@@ -1367,6 +1367,8 @@ interface SidecarTelemetryDelta {
|
|
|
1367
1367
|
lastRetryError: string;
|
|
1368
1368
|
/** Whether any sidecar events were parsed in this tick (used for callback gating) */
|
|
1369
1369
|
hadEvents: boolean;
|
|
1370
|
+
/** Authoritative context usage from pi get_session_stats (pi ≥ 0.63.0, null if unavailable) */
|
|
1371
|
+
contextUsage: { percentUsed: number; totalTokens: number; maxTokens: number } | null;
|
|
1370
1372
|
}
|
|
1371
1373
|
|
|
1372
1374
|
/**
|
|
@@ -1385,7 +1387,7 @@ function tailSidecarJsonl(filePath: string, tailState: SidecarTailState): Sideca
|
|
|
1385
1387
|
inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0,
|
|
1386
1388
|
cost: 0, latestTotalTokens: 0, toolCalls: 0, lastTool: "",
|
|
1387
1389
|
retryActive: tailState.retryActive, retriesStarted: 0, lastRetryError: "",
|
|
1388
|
-
hadEvents: false,
|
|
1390
|
+
hadEvents: false, contextUsage: null,
|
|
1389
1391
|
};
|
|
1390
1392
|
|
|
1391
1393
|
// Gracefully handle missing file (wrapper hasn't written yet)
|
|
@@ -1496,6 +1498,21 @@ function tailSidecarJsonl(filePath: string, tailState: SidecarTailState): Sideca
|
|
|
1496
1498
|
tailState.retryActive = false;
|
|
1497
1499
|
break;
|
|
1498
1500
|
}
|
|
1501
|
+
|
|
1502
|
+
case "response": {
|
|
1503
|
+
// get_session_stats response from pi ≥ 0.63.0 — authoritative context usage
|
|
1504
|
+
if (event.success === true && event.data?.contextUsage) {
|
|
1505
|
+
const cu = event.data.contextUsage;
|
|
1506
|
+
if (typeof cu.percentUsed === "number") {
|
|
1507
|
+
delta.contextUsage = {
|
|
1508
|
+
percentUsed: cu.percentUsed,
|
|
1509
|
+
totalTokens: cu.totalTokens || 0,
|
|
1510
|
+
maxTokens: cu.maxTokens || 0,
|
|
1511
|
+
};
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
break;
|
|
1515
|
+
}
|
|
1499
1516
|
}
|
|
1500
1517
|
}
|
|
1501
1518
|
|
|
@@ -2405,8 +2422,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
2405
2422
|
state.reviewerLastTool = delta.lastTool;
|
|
2406
2423
|
}
|
|
2407
2424
|
|
|
2408
|
-
// Context %
|
|
2409
|
-
if (delta.
|
|
2425
|
+
// Context % — prefer authoritative contextUsage (pi ≥ 0.63.0)
|
|
2426
|
+
if (delta.contextUsage) {
|
|
2427
|
+
state.reviewerContextPct = delta.contextUsage.percentUsed;
|
|
2428
|
+
} else if (delta.latestTotalTokens > 0 && contextWindow > 0) {
|
|
2410
2429
|
state.reviewerContextPct = (delta.latestTotalTokens / contextWindow) * 100;
|
|
2411
2430
|
}
|
|
2412
2431
|
|
|
@@ -2566,7 +2585,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
2566
2585
|
state.reviewerCostUsd += delta.cost;
|
|
2567
2586
|
state.reviewerToolCount += delta.toolCalls;
|
|
2568
2587
|
if (delta.lastTool) state.reviewerLastTool = delta.lastTool;
|
|
2569
|
-
|
|
2588
|
+
// Context % — prefer authoritative contextUsage (pi ≥ 0.63.0)
|
|
2589
|
+
if (delta.contextUsage) {
|
|
2590
|
+
state.reviewerContextPct = delta.contextUsage.percentUsed;
|
|
2591
|
+
} else if (delta.latestTotalTokens > 0 && contextWindow > 0) {
|
|
2570
2592
|
state.reviewerContextPct = (delta.latestTotalTokens / contextWindow) * 100;
|
|
2571
2593
|
}
|
|
2572
2594
|
writeLaneState(state);
|
|
@@ -3140,18 +3162,24 @@ export default function (pi: ExtensionAPI) {
|
|
|
3140
3162
|
state.workerLastRetryError = delta.lastRetryError;
|
|
3141
3163
|
}
|
|
3142
3164
|
|
|
3143
|
-
// Context %
|
|
3144
|
-
//
|
|
3145
|
-
|
|
3146
|
-
const pct =
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
if (pct
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3165
|
+
// Context % — prefer authoritative contextUsage from pi ≥ 0.63.0,
|
|
3166
|
+
// fall back to manual calculation from totalTokens + cacheRead.
|
|
3167
|
+
{
|
|
3168
|
+
const pct = delta.contextUsage
|
|
3169
|
+
? delta.contextUsage.percentUsed
|
|
3170
|
+
: (delta.latestTotalTokens > 0 && contextWindow > 0)
|
|
3171
|
+
? (delta.latestTotalTokens / contextWindow) * 100
|
|
3172
|
+
: 0;
|
|
3173
|
+
if (pct > 0) {
|
|
3174
|
+
state.workerContextPct = pct;
|
|
3175
|
+
if (pct >= warnPct) {
|
|
3176
|
+
writeWrapUpSignal(`Wrap up (context ${Math.round(pct)}%)`);
|
|
3177
|
+
}
|
|
3178
|
+
if (pct >= killPct && state.workerStatus === "running") {
|
|
3179
|
+
console.error(`[task-runner] tmux worker: context limit (${Math.round(pct)}%) — killing session '${sessionName}'`);
|
|
3180
|
+
killReason = "context";
|
|
3181
|
+
spawned.kill();
|
|
3182
|
+
}
|
|
3155
3183
|
}
|
|
3156
3184
|
}
|
|
3157
3185
|
|