taskplane 0.28.1 → 0.28.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.
- package/dashboard/public/app.js +24 -18
- package/extensions/taskplane/agent-bridge-extension.ts +22 -0
- package/extensions/taskplane/config-loader.ts +3 -0
- package/extensions/taskplane/config-schema.ts +9 -2
- package/extensions/taskplane/engine.ts +7 -3
- package/extensions/taskplane/execution.ts +38 -2
- package/extensions/taskplane/extension.ts +5135 -5135
- package/extensions/taskplane/lane-runner.ts +15 -1
- package/extensions/taskplane/merge.ts +9 -2
- package/extensions/taskplane/resume.ts +4 -3
- package/extensions/taskplane/settings-loader.ts +136 -0
- package/extensions/taskplane/settings-tui.ts +147 -3
- package/extensions/taskplane/types.ts +6 -0
- package/package.json +1 -1
package/dashboard/public/app.js
CHANGED
|
@@ -517,9 +517,11 @@ function renderSummary(batch) {
|
|
|
517
517
|
const checkboxDone = ws.checked === ws.total && ws.total > 0;
|
|
518
518
|
const pastWave = ws.waveIdx < currentWaveIdx;
|
|
519
519
|
const batchDone = batch.phase === "completed";
|
|
520
|
-
// TP-178: During merging, only past waves are done
|
|
520
|
+
// TP-178: During merging, only past waves are truly done. The current wave's
|
|
521
|
+
// checkboxDone/allSucceeded can be true (tasks finished) but the wave itself
|
|
522
|
+
// isn't done until the merge completes. (#493)
|
|
521
523
|
const isMerging = batch.phase === "merging";
|
|
522
|
-
const isDone =
|
|
524
|
+
const isDone = batchDone || pastWave || (!isMerging && (checkboxDone || ws.allSucceeded));
|
|
523
525
|
const isMergingWave = isMerging && ws.waveIdx === currentWaveIdx;
|
|
524
526
|
const isCurrent = ws.waveIdx === currentWaveIdx && (batch.phase === "executing" || isMerging);
|
|
525
527
|
const isFuture = ws.waveIdx > currentWaveIdx && (batch.phase === "executing" || isMerging);
|
|
@@ -711,7 +713,10 @@ function renderLanesTasks(batch, sessions) {
|
|
|
711
713
|
// TP-176: Succeeded tasks always show 100% regardless of sidecar/statusData (#491).
|
|
712
714
|
let progressHtml = "";
|
|
713
715
|
const v2p = ls && ls._v2Progress;
|
|
714
|
-
const
|
|
716
|
+
const taskMatch = v2p && ls.taskId === task.taskId;
|
|
717
|
+
// Split V2 usage: progress needs totals > 0, but step/iter can be used whenever present
|
|
718
|
+
const useV2Progress = taskMatch && v2p.total > 0;
|
|
719
|
+
const useV2Step = taskMatch && !!v2p.currentStep;
|
|
715
720
|
if (task.status === "succeeded") {
|
|
716
721
|
// #491 fix: succeeded tasks always show 100%
|
|
717
722
|
progressHtml = `
|
|
@@ -719,9 +724,9 @@ function renderLanesTasks(batch, sessions) {
|
|
|
719
724
|
<div class="task-progress-bar"><div class="task-progress-fill pct-hi" style="width:100%"></div></div>
|
|
720
725
|
<span class="task-progress-text">100%</span>
|
|
721
726
|
</div>`;
|
|
722
|
-
} else if (
|
|
723
|
-
const displayChecked =
|
|
724
|
-
const displayTotal =
|
|
727
|
+
} else if (useV2Progress || (sd && sd.total > 0)) {
|
|
728
|
+
const displayChecked = useV2Progress ? v2p.checked : sd.checked;
|
|
729
|
+
const displayTotal = useV2Progress ? v2p.total : sd.total;
|
|
725
730
|
const displayProgress = displayTotal > 0 ? Math.round((displayChecked / displayTotal) * 100) : 0;
|
|
726
731
|
const fillClass = pctClass(displayProgress);
|
|
727
732
|
progressHtml = `
|
|
@@ -731,20 +736,20 @@ function renderLanesTasks(batch, sessions) {
|
|
|
731
736
|
</div>
|
|
732
737
|
<span class="task-progress-text">${displayProgress}% ${displayChecked}/${displayTotal}</span>
|
|
733
738
|
</div>`;
|
|
734
|
-
} else if (task.status === "pending") {
|
|
735
|
-
progressHtml = `
|
|
736
|
-
<div class="task-progress">
|
|
737
|
-
<div class="task-progress-bar"><div class="task-progress-fill pct-0" style="width:0%"></div></div>
|
|
738
|
-
<span class="task-progress-text">0%</span>
|
|
739
|
-
</div>`;
|
|
740
739
|
} else if (task.status === "running") {
|
|
741
|
-
//
|
|
742
|
-
// This covers non-final
|
|
740
|
+
// #494 fix: running tasks without meaningful totals show executing indicator
|
|
741
|
+
// This covers non-final segments, early execution before sidecar captures, and stale 0/0 data
|
|
743
742
|
progressHtml = `
|
|
744
743
|
<div class="task-progress">
|
|
745
744
|
<div class="task-progress-bar"><div class="task-progress-fill pct-low task-progress-executing" style="width:100%"></div></div>
|
|
746
745
|
<span class="task-progress-text">executing…</span>
|
|
747
746
|
</div>`;
|
|
747
|
+
} else if (task.status === "pending") {
|
|
748
|
+
progressHtml = `
|
|
749
|
+
<div class="task-progress">
|
|
750
|
+
<div class="task-progress-bar"><div class="task-progress-fill pct-0" style="width:0%"></div></div>
|
|
751
|
+
<span class="task-progress-text">0%</span>
|
|
752
|
+
</div>`;
|
|
748
753
|
} else {
|
|
749
754
|
progressHtml = '<span style="color:var(--text-faint)">—</span>';
|
|
750
755
|
}
|
|
@@ -756,10 +761,11 @@ function renderLanesTasks(batch, sessions) {
|
|
|
756
761
|
if (task.status === "succeeded") {
|
|
757
762
|
// TP-178: Succeeded tasks always show "Complete" regardless of sidecar data (#491)
|
|
758
763
|
stepHtml = '<span style="color:var(--green)">Complete</span>';
|
|
759
|
-
} else if (sd ||
|
|
760
|
-
|
|
761
|
-
const
|
|
762
|
-
const
|
|
764
|
+
} else if (sd || useV2Step) {
|
|
765
|
+
// #488 fix: prefer V2 step name whenever present (even if totals are 0)
|
|
766
|
+
const stepName = useV2Step ? v2p.currentStep : (sd ? sd.currentStep : "Unknown");
|
|
767
|
+
const iter = (useV2Step && v2p.iteration != null) ? v2p.iteration : (sd ? sd.iteration : 0);
|
|
768
|
+
const revs = (useV2Step && v2p.reviews != null) ? v2p.reviews : (sd ? sd.reviews : 0);
|
|
763
769
|
stepHtml = escapeHtml(stepName);
|
|
764
770
|
if (iter > 0) stepHtml += `<span class="task-iter">i${iter}</span>`;
|
|
765
771
|
if (revs > 0) stepHtml += `<span class="task-iter">r${revs}</span>`;
|
|
@@ -27,6 +27,7 @@ import { writeFileSync, readFileSync, existsSync, mkdirSync, renameSync, unlinkS
|
|
|
27
27
|
import { join, dirname } from "path";
|
|
28
28
|
import { spawn as nodeSpawn } from "child_process";
|
|
29
29
|
import { resolvePiCliPath, resolveTaskplaneAgentTemplate } from "./path-resolver.ts";
|
|
30
|
+
import { loadPiSettingsPackages, filterExcludedExtensions } from "./settings-loader.ts";
|
|
30
31
|
import { randomBytes } from "crypto";
|
|
31
32
|
import { buildExpansionRequestId, type SegmentExpansionRequest } from "./types.ts";
|
|
32
33
|
|
|
@@ -445,6 +446,27 @@ export default function (pi: ExtensionAPI) {
|
|
|
445
446
|
];
|
|
446
447
|
if (reviewerModel) args.push("--model", reviewerModel);
|
|
447
448
|
if (reviewerThinking) args.push("--thinking", reviewerThinking);
|
|
449
|
+
|
|
450
|
+
// TP-180: Forward user-installed extensions to reviewer agent
|
|
451
|
+
// Use TASKPLANE_STATE_ROOT (canonical project root) for settings resolution,
|
|
452
|
+
// falling back to cwd (which may be a worktree without .pi/settings.json).
|
|
453
|
+
const settingsRoot = process.env.TASKPLANE_STATE_ROOT || cwd;
|
|
454
|
+
const reviewerPackages = loadPiSettingsPackages(settingsRoot);
|
|
455
|
+
// Apply reviewer-specific exclusions from config (JSON array via env)
|
|
456
|
+
let reviewerExclusions: string[] = [];
|
|
457
|
+
try {
|
|
458
|
+
const rawExclude = process.env.TASKPLANE_REVIEWER_EXCLUDE_EXTENSIONS;
|
|
459
|
+
if (rawExclude) {
|
|
460
|
+
const parsed = JSON.parse(rawExclude);
|
|
461
|
+
if (Array.isArray(parsed)) {
|
|
462
|
+
reviewerExclusions = parsed.filter((v: unknown): v is string => typeof v === "string");
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
} catch { /* ignore malformed */ }
|
|
466
|
+
const filteredReviewerPackages = filterExcludedExtensions(reviewerPackages, reviewerExclusions);
|
|
467
|
+
for (const pkg of filteredReviewerPackages) {
|
|
468
|
+
args.push("-e", pkg);
|
|
469
|
+
}
|
|
448
470
|
const proc = nodeSpawn(process.execPath, args, {
|
|
449
471
|
shell: false,
|
|
450
472
|
cwd,
|
|
@@ -1125,6 +1125,7 @@ export function toOrchestratorConfig(config: TaskplaneConfig): import("./types.t
|
|
|
1125
1125
|
verify: [...o.merge.verify],
|
|
1126
1126
|
order: o.merge.order,
|
|
1127
1127
|
timeout_minutes: o.merge.timeoutMinutes ?? 90,
|
|
1128
|
+
exclude_extensions: [...(o.merge.excludeExtensions ?? [])],
|
|
1128
1129
|
},
|
|
1129
1130
|
failure: {
|
|
1130
1131
|
on_task_failure: o.failure.onTaskFailure,
|
|
@@ -1183,7 +1184,9 @@ export function toTaskRunnerConfig(config: TaskplaneConfig): import("./types.ts"
|
|
|
1183
1184
|
model: config.taskRunner.reviewer.model,
|
|
1184
1185
|
thinking: config.taskRunner.reviewer.thinking,
|
|
1185
1186
|
tools: config.taskRunner.reviewer.tools,
|
|
1187
|
+
excludeExtensions: [...(config.taskRunner.reviewer.excludeExtensions ?? [])],
|
|
1186
1188
|
},
|
|
1189
|
+
workerExcludeExtensions: [...(config.taskRunner.worker.excludeExtensions ?? [])],
|
|
1187
1190
|
};
|
|
1188
1191
|
}
|
|
1189
1192
|
|
|
@@ -111,6 +111,8 @@ export interface WorkerConfig {
|
|
|
111
111
|
thinking: string;
|
|
112
112
|
/** Optional spawn mode override for task-runner (Runtime V2 subprocess-only). */
|
|
113
113
|
spawnMode?: "subprocess";
|
|
114
|
+
/** Package specifiers to exclude from extension forwarding for worker agents (exact match). @since TP-180 */
|
|
115
|
+
excludeExtensions?: string[];
|
|
114
116
|
}
|
|
115
117
|
|
|
116
118
|
/** Reviewer agent configuration */
|
|
@@ -121,6 +123,8 @@ export interface ReviewerConfig {
|
|
|
121
123
|
tools: string;
|
|
122
124
|
/** Thinking mode for reviewer */
|
|
123
125
|
thinking: string;
|
|
126
|
+
/** Package specifiers to exclude from extension forwarding for reviewer agents (exact match). @since TP-180 */
|
|
127
|
+
excludeExtensions?: string[];
|
|
124
128
|
}
|
|
125
129
|
|
|
126
130
|
/** Context/resource limits for task execution */
|
|
@@ -310,6 +314,8 @@ export interface MergeConfig {
|
|
|
310
314
|
order: "fewest-files-first" | "sequential";
|
|
311
315
|
/** Merge-agent timeout in minutes */
|
|
312
316
|
timeoutMinutes?: number;
|
|
317
|
+
/** Package specifiers to exclude from extension forwarding for merge agents (exact match). @since TP-180 */
|
|
318
|
+
excludeExtensions?: string[];
|
|
313
319
|
}
|
|
314
320
|
|
|
315
321
|
/** Failure policy settings */
|
|
@@ -588,8 +594,8 @@ export const DEFAULT_TASK_RUNNER_SECTION: TaskRunnerSection = {
|
|
|
588
594
|
testing: { commands: {} },
|
|
589
595
|
standards: { docs: [], rules: [] },
|
|
590
596
|
standardsOverrides: {},
|
|
591
|
-
worker: { model: "", tools: "read,write,edit,bash,grep,find,ls", thinking: "" },
|
|
592
|
-
reviewer: { model: "", tools: "read,bash,grep,find,ls", thinking: "on" },
|
|
597
|
+
worker: { model: "", tools: "read,write,edit,bash,grep,find,ls", thinking: "", excludeExtensions: [] },
|
|
598
|
+
reviewer: { model: "", tools: "read,bash,grep,find,ls", thinking: "on", excludeExtensions: [] },
|
|
593
599
|
context: {
|
|
594
600
|
workerContextWindow: 0,
|
|
595
601
|
warnPercent: 85,
|
|
@@ -645,6 +651,7 @@ export const DEFAULT_ORCHESTRATOR_SECTION: OrchestratorSection = {
|
|
|
645
651
|
verify: [],
|
|
646
652
|
order: "fewest-files-first",
|
|
647
653
|
timeoutMinutes: 90,
|
|
654
|
+
excludeExtensions: [],
|
|
648
655
|
},
|
|
649
656
|
failure: {
|
|
650
657
|
onTaskFailure: "skip-dependents",
|
|
@@ -6,7 +6,7 @@ import { existsSync, readdirSync, readFileSync, renameSync, unlinkSync } from "f
|
|
|
6
6
|
import { join, resolve } from "path";
|
|
7
7
|
|
|
8
8
|
import { formatDiscoveryResults, runDiscovery } from "./discovery.ts";
|
|
9
|
-
import { buildReviewerEnv, computeTransitiveDependents, execLog, executeLaneV2, executeWave, killV2LaneAgents, resolveCanonicalTaskPaths } from "./execution.ts";
|
|
9
|
+
import { buildReviewerEnv, buildWorkerExcludeEnv, computeTransitiveDependents, execLog, executeLaneV2, executeWave, killV2LaneAgents, resolveCanonicalTaskPaths } from "./execution.ts";
|
|
10
10
|
import type { RuntimeBackend } from "./execution.ts";
|
|
11
11
|
import type { MonitorUpdateCallback } from "./execution.ts";
|
|
12
12
|
// classifyExit no longer called directly — Tier 0 uses exitDiagnostic.classification
|
|
@@ -1381,7 +1381,7 @@ async function attemptWorkerCrashRetry(
|
|
|
1381
1381
|
retryPauseSignal,
|
|
1382
1382
|
wsRoot,
|
|
1383
1383
|
isWsMode,
|
|
1384
|
-
{ ORCH_BATCH_ID: batchState.batchId, ...buildReviewerEnv(runnerConfig?.reviewer) }, // TP-089: ensure mailbox works for retries
|
|
1384
|
+
{ ORCH_BATCH_ID: batchState.batchId, ...buildReviewerEnv(runnerConfig?.reviewer), ...buildWorkerExcludeEnv(runnerConfig?.workerExcludeExtensions) }, // TP-089: ensure mailbox works for retries
|
|
1385
1385
|
);
|
|
1386
1386
|
|
|
1387
1387
|
const retryOutcome = retryResult.tasks[0];
|
|
@@ -1640,7 +1640,7 @@ async function attemptModelFallbackRetry(
|
|
|
1640
1640
|
// Pass TASKPLANE_MODEL_FALLBACK=1 as extra env var to signal
|
|
1641
1641
|
// the task-runner to use the session model instead of configured model.
|
|
1642
1642
|
// TP-089: Also include ORCH_BATCH_ID so mailbox steering works for retries.
|
|
1643
|
-
const modelFallbackEnv = { TASKPLANE_MODEL_FALLBACK: "1", ORCH_BATCH_ID: batchState.batchId, ...buildReviewerEnv(runnerConfig?.reviewer) };
|
|
1643
|
+
const modelFallbackEnv = { TASKPLANE_MODEL_FALLBACK: "1", ORCH_BATCH_ID: batchState.batchId, ...buildReviewerEnv(runnerConfig?.reviewer), ...buildWorkerExcludeEnv(runnerConfig?.workerExcludeExtensions) };
|
|
1644
1644
|
const retryResult = await executeLaneV2(
|
|
1645
1645
|
retryLane,
|
|
1646
1646
|
orchConfig,
|
|
@@ -1887,7 +1887,9 @@ async function attemptStaleWorktreeRecovery(
|
|
|
1887
1887
|
model: runnerConfig?.reviewer?.model || "",
|
|
1888
1888
|
thinking: runnerConfig?.reviewer?.thinking || "",
|
|
1889
1889
|
tools: runnerConfig?.reviewer?.tools || "",
|
|
1890
|
+
excludeExtensions: runnerConfig?.reviewer?.excludeExtensions ?? [],
|
|
1890
1891
|
},
|
|
1892
|
+
runnerConfig?.workerExcludeExtensions ?? [],
|
|
1891
1893
|
);
|
|
1892
1894
|
|
|
1893
1895
|
return retryResult;
|
|
@@ -2490,7 +2492,9 @@ export async function executeOrchBatch(
|
|
|
2490
2492
|
model: runnerConfig?.reviewer?.model || "",
|
|
2491
2493
|
thinking: runnerConfig?.reviewer?.thinking || "",
|
|
2492
2494
|
tools: runnerConfig?.reviewer?.tools || "",
|
|
2495
|
+
excludeExtensions: runnerConfig?.reviewer?.excludeExtensions ?? [],
|
|
2493
2496
|
},
|
|
2497
|
+
runnerConfig?.workerExcludeExtensions ?? [],
|
|
2494
2498
|
);
|
|
2495
2499
|
|
|
2496
2500
|
// ── TP-039: Tier 0 — Stale worktree recovery ────────────
|
|
@@ -1754,7 +1754,8 @@ export async function executeWave(
|
|
|
1754
1754
|
runtimeBackend?: RuntimeBackend,
|
|
1755
1755
|
onSupervisorAlert?: SupervisorAlertCallback,
|
|
1756
1756
|
supervisorAutonomy: "interactive" | "supervised" | "autonomous" = "autonomous",
|
|
1757
|
-
reviewerConfig?: { model?: string; thinking?: string; tools?: string },
|
|
1757
|
+
reviewerConfig?: { model?: string; thinking?: string; tools?: string; excludeExtensions?: string[] },
|
|
1758
|
+
workerExcludeExtensions?: string[],
|
|
1758
1759
|
): Promise<WaveExecutionResult> {
|
|
1759
1760
|
const startedAt = Date.now();
|
|
1760
1761
|
const policy = config.failure.on_task_failure;
|
|
@@ -1862,6 +1863,7 @@ export async function executeWave(
|
|
|
1862
1863
|
ORCH_BATCH_ID: batchId,
|
|
1863
1864
|
TASKPLANE_SUPERVISOR_AUTONOMY: supervisorAutonomy,
|
|
1864
1865
|
...buildReviewerEnv(reviewerConfig),
|
|
1866
|
+
...buildWorkerExcludeEnv(workerExcludeExtensions),
|
|
1865
1867
|
}, onSupervisorAlert),
|
|
1866
1868
|
);
|
|
1867
1869
|
|
|
@@ -2503,13 +2505,44 @@ import { executeTaskV2, type LaneRunnerConfig, type LaneRunnerTaskResult } from
|
|
|
2503
2505
|
*
|
|
2504
2506
|
* @since TP-160
|
|
2505
2507
|
*/
|
|
2508
|
+
/**
|
|
2509
|
+
* Parse a JSON string array from an env var value, returning empty array on failure.
|
|
2510
|
+
* @since TP-180
|
|
2511
|
+
*/
|
|
2512
|
+
function parseJsonArrayEnv(value?: string): string[] {
|
|
2513
|
+
if (!value) return [];
|
|
2514
|
+
try {
|
|
2515
|
+
const parsed = JSON.parse(value);
|
|
2516
|
+
if (Array.isArray(parsed)) return parsed.filter((v: unknown): v is string => typeof v === "string");
|
|
2517
|
+
} catch { /* ignore malformed */ }
|
|
2518
|
+
return [];
|
|
2519
|
+
}
|
|
2520
|
+
|
|
2506
2521
|
export function buildReviewerEnv(
|
|
2507
|
-
reviewerConfig?: { model?: string; thinking?: string; tools?: string } | null,
|
|
2522
|
+
reviewerConfig?: { model?: string; thinking?: string; tools?: string; excludeExtensions?: string[] } | null,
|
|
2508
2523
|
): Record<string, string> {
|
|
2509
2524
|
const env: Record<string, string> = {};
|
|
2510
2525
|
if (reviewerConfig?.model) env.TASKPLANE_REVIEWER_MODEL = reviewerConfig.model;
|
|
2511
2526
|
if (reviewerConfig?.thinking) env.TASKPLANE_REVIEWER_THINKING = reviewerConfig.thinking;
|
|
2512
2527
|
if (reviewerConfig?.tools) env.TASKPLANE_REVIEWER_TOOLS = reviewerConfig.tools;
|
|
2528
|
+
// TP-180: Forward reviewer extension exclusions as JSON array
|
|
2529
|
+
if (reviewerConfig?.excludeExtensions && reviewerConfig.excludeExtensions.length > 0) {
|
|
2530
|
+
env.TASKPLANE_REVIEWER_EXCLUDE_EXTENSIONS = JSON.stringify(reviewerConfig.excludeExtensions);
|
|
2531
|
+
}
|
|
2532
|
+
return env;
|
|
2533
|
+
}
|
|
2534
|
+
|
|
2535
|
+
/**
|
|
2536
|
+
* Build worker extension exclusion env vars from config.
|
|
2537
|
+
* @since TP-180
|
|
2538
|
+
*/
|
|
2539
|
+
export function buildWorkerExcludeEnv(
|
|
2540
|
+
workerExcludeExtensions?: string[] | null,
|
|
2541
|
+
): Record<string, string> {
|
|
2542
|
+
const env: Record<string, string> = {};
|
|
2543
|
+
if (workerExcludeExtensions && workerExcludeExtensions.length > 0) {
|
|
2544
|
+
env.TASKPLANE_WORKER_EXCLUDE_EXTENSIONS = JSON.stringify(workerExcludeExtensions);
|
|
2545
|
+
}
|
|
2513
2546
|
return env;
|
|
2514
2547
|
}
|
|
2515
2548
|
|
|
@@ -2606,6 +2639,9 @@ export async function executeLaneV2(
|
|
|
2606
2639
|
reviewerModel: extraEnvVars?.TASKPLANE_REVIEWER_MODEL || "",
|
|
2607
2640
|
reviewerThinking: extraEnvVars?.TASKPLANE_REVIEWER_THINKING || "",
|
|
2608
2641
|
reviewerTools: extraEnvVars?.TASKPLANE_REVIEWER_TOOLS || "",
|
|
2642
|
+
// TP-180: Extension exclusion lists from config
|
|
2643
|
+
workerExcludeExtensions: parseJsonArrayEnv(extraEnvVars?.TASKPLANE_WORKER_EXCLUDE_EXTENSIONS),
|
|
2644
|
+
reviewerExcludeExtensions: parseJsonArrayEnv(extraEnvVars?.TASKPLANE_REVIEWER_EXCLUDE_EXTENSIONS),
|
|
2609
2645
|
supervisorAutonomy,
|
|
2610
2646
|
projectName: config.project?.name || "project",
|
|
2611
2647
|
maxIterations: 20,
|