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.
@@ -33,6 +33,7 @@ import {
33
33
  } from "./task-executor-core.ts";
34
34
 
35
35
  import { spawnAgent, type AgentHostOptions, type AgentHostResult } from "./agent-host.ts";
36
+ import { loadPiSettingsPackages, filterExcludedExtensions } from "./settings-loader.ts";
36
37
 
37
38
  import {
38
39
  appendAgentEvent,
@@ -222,6 +223,10 @@ export interface LaneRunnerConfig {
222
223
  supervisorAutonomy?: "interactive" | "supervised" | "autonomous";
223
224
  /** Project name (for review request context) */
224
225
  projectName?: string;
226
+ /** Package specifiers to exclude from worker extension forwarding (exact match). @since TP-180 */
227
+ workerExcludeExtensions?: string[];
228
+ /** Package specifiers to exclude from reviewer extension forwarding (exact match). @since TP-180 */
229
+ reviewerExcludeExtensions?: string[];
225
230
  /** Max worker iterations before giving up */
226
231
  maxIterations: number;
227
232
  /** No-progress stall limit */
@@ -555,6 +560,10 @@ export async function executeTaskV2(
555
560
  const outboxDir = join(config.stateRoot, ".pi", "mailbox", config.batchId, workerAgentId, "outbox");
556
561
  const bridgeExtensionPath = join(LANE_RUNNER_DIR, "agent-bridge-extension.ts");
557
562
 
563
+ // TP-180: Forward user-installed extensions to worker agent
564
+ const allPackages = loadPiSettingsPackages(config.stateRoot);
565
+ const workerPackages = filterExcludedExtensions(allPackages, config.workerExcludeExtensions ?? []);
566
+
558
567
  const hostOpts: AgentHostOptions = {
559
568
  agentId: workerAgentId,
560
569
  role: "worker",
@@ -577,7 +586,7 @@ export async function executeTaskV2(
577
586
  timeoutMs: config.maxWorkerMinutes * 60_000,
578
587
  stateRoot: config.stateRoot,
579
588
  packet: unit.packet,
580
- extensions: [bridgeExtensionPath],
589
+ extensions: [bridgeExtensionPath, ...workerPackages],
581
590
  env: {
582
591
  TASKPLANE_OUTBOX_DIR: outboxDir,
583
592
  TASKPLANE_AGENT_ID: workerAgentId,
@@ -597,6 +606,11 @@ export async function executeTaskV2(
597
606
  ...(config.reviewerModel ? { TASKPLANE_REVIEWER_MODEL: config.reviewerModel } : {}),
598
607
  ...(config.reviewerThinking ? { TASKPLANE_REVIEWER_THINKING: config.reviewerThinking } : {}),
599
608
  ...(config.reviewerTools ? { TASKPLANE_REVIEWER_TOOLS: config.reviewerTools } : {}),
609
+ // TP-180: Pass state root and reviewer exclusions for extension forwarding
610
+ TASKPLANE_STATE_ROOT: config.stateRoot,
611
+ ...(config.reviewerExcludeExtensions && config.reviewerExcludeExtensions.length > 0
612
+ ? { TASKPLANE_REVIEWER_EXCLUDE_EXTENSIONS: JSON.stringify(config.reviewerExcludeExtensions) }
613
+ : {}),
600
614
  },
601
615
  // TP-172: Exit interception callback — escalate to supervisor when worker
602
616
  // exits without making visible progress (no checkboxes, no blocker logged).
@@ -21,6 +21,7 @@ import { loadOrchestratorConfig } from "./config.ts";
21
21
  import { captureBaseline, diffFingerprints, runVerificationCommands, parseTestOutput, deduplicateFingerprints } from "./verification.ts";
22
22
  import { spawnAgent } from "./agent-host.ts";
23
23
  import type { AgentHostOptions, AgentHostResult, AgentTelemetryCallback } from "./agent-host.ts";
24
+ import { loadPiSettingsPackages, filterExcludedExtensions } from "./settings-loader.ts";
24
25
  import type { RuntimeBackend } from "./execution.ts";
25
26
  import type { VerificationBaseline, FingerprintDiff, TestFingerprint } from "./verification.ts";
26
27
 
@@ -716,6 +717,12 @@ export async function spawnMergeAgentV2(
716
717
  mkdirSync(join(mailboxDir, "inbox"), { recursive: true });
717
718
  }
718
719
 
720
+ // TP-180: Forward user-installed extensions to merge agent
721
+ const mergeStateRoot = stateRoot ?? repoRoot;
722
+ const allMergePackages = loadPiSettingsPackages(mergeStateRoot);
723
+ const mergeExclusions = config.merge.exclude_extensions ?? [];
724
+ const mergePackages = filterExcludedExtensions(allMergePackages, mergeExclusions);
725
+
719
726
  const opts: AgentHostOptions = {
720
727
  agentId: sessionName,
721
728
  role: "merger",
@@ -733,8 +740,9 @@ export async function spawnMergeAgentV2(
733
740
  eventsPath,
734
741
  exitSummaryPath,
735
742
  timeoutMs: (config.merge.timeout_minutes ?? 10) * 60 * 1000,
736
- stateRoot: stateRoot ?? repoRoot,
743
+ stateRoot: mergeStateRoot,
737
744
  packet: null,
745
+ ...(mergePackages.length > 0 ? { extensions: mergePackages } : {}),
738
746
  env: {
739
747
  ORCH_BATCH_ID: bid,
740
748
  },
@@ -748,7 +756,6 @@ export async function spawnMergeAgentV2(
748
756
  }
749
757
  const mergeNumber = mergeNumberMatch ? parseInt(mergeNumberMatch[1], 10) : 1;
750
758
  const mergeStartedAt = Date.now();
751
- const mergeStateRoot = stateRoot ?? repoRoot;
752
759
 
753
760
  // Helper: build a RuntimeAgentTelemetrySnapshot from a partial AgentHostResult.
754
761
  const buildAgentSnap = (tel: Partial<AgentHostResult>, status: RuntimeAgentTelemetrySnapshot["status"]): RuntimeAgentTelemetrySnapshot => ({
@@ -8,7 +8,7 @@ import { join } from "path";
8
8
  import { assembleDiagnosticInput, emitDiagnosticReports } from "./diagnostic-reports.ts";
9
9
  import { runDiscovery } from "./discovery.ts";
10
10
  import { executeOrchBatch, resolveDisplayWaveNumber } from "./engine.ts";
11
- import { buildReviewerEnv, computeTransitiveDependents, execLog, executeLaneV2, executeWave, resolveCanonicalTaskPaths } from "./execution.ts";
11
+ import { buildReviewerEnv, buildWorkerExcludeEnv, computeTransitiveDependents, execLog, executeLaneV2, executeWave, resolveCanonicalTaskPaths } from "./execution.ts";
12
12
  import type { MonitorUpdateCallback, RuntimeBackend } from "./execution.ts";
13
13
  import { selectRuntimeBackend } from "./engine.ts";
14
14
  import { readRegistrySnapshot, isTerminalStatus, isProcessAlive } from "./process-registry.ts";
@@ -1506,7 +1506,7 @@ export async function resumeOrchBatch(
1506
1506
  const laneResult = await executeLaneV2(
1507
1507
  lane, orchConfig, laneRepoRoot, batchState.pauseSignal,
1508
1508
  workspaceRoot, !!workspaceConfig,
1509
- { ORCH_BATCH_ID: batchState.batchId, ...buildReviewerEnv(runnerConfig.reviewer) },
1509
+ { ORCH_BATCH_ID: batchState.batchId, ...buildReviewerEnv(runnerConfig.reviewer), ...buildWorkerExcludeEnv(runnerConfig.workerExcludeExtensions) },
1510
1510
  emitAlert,
1511
1511
  );
1512
1512
  const taskResult = laneResult.tasks.find(t => t.taskId === task.taskId);
@@ -1588,7 +1588,7 @@ export async function resumeOrchBatch(
1588
1588
  const laneResult = await executeLaneV2(
1589
1589
  lane, orchConfig, reExecRepoRoot, batchState.pauseSignal,
1590
1590
  workspaceRoot, !!workspaceConfig,
1591
- { ORCH_BATCH_ID: batchState.batchId, ...buildReviewerEnv(runnerConfig.reviewer) },
1591
+ { ORCH_BATCH_ID: batchState.batchId, ...buildReviewerEnv(runnerConfig.reviewer), ...buildWorkerExcludeEnv(runnerConfig.workerExcludeExtensions) },
1592
1592
  emitAlert,
1593
1593
  );
1594
1594
  const taskResult = laneResult.tasks.find(t => t.taskId === task.taskId);
@@ -2048,6 +2048,7 @@ export async function resumeOrchBatch(
2048
2048
  emitAlert,
2049
2049
  supervisorAutonomy,
2050
2050
  runnerConfig.reviewer,
2051
+ runnerConfig.workerExcludeExtensions ?? [],
2051
2052
  );
2052
2053
 
2053
2054
  batchState.waveResults.push(waveResult);
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Settings Loader — Read and merge Pi extension packages from settings files
3
+ *
4
+ * Reads `.pi/settings.json` from both project-level and global locations,
5
+ * extracts the `packages` arrays, merges them (project entries first,
6
+ * deduplicated), and filters out taskplane itself.
7
+ *
8
+ * Used by spawn points (worker, reviewer, merge agent) to forward
9
+ * user-installed extensions as explicit `-e` flags alongside `--no-extensions`.
10
+ *
11
+ * @module taskplane/settings-loader
12
+ * @since TP-180
13
+ */
14
+
15
+ import { readFileSync } from "fs";
16
+ import { join } from "path";
17
+ import { homedir } from "os";
18
+
19
+ // ── Constants ────────────────────────────────────────────────────────
20
+
21
+ /** Subpath under a project root for the project-level Pi settings file. */
22
+ const PROJECT_SETTINGS_SUBPATH = join(".pi", "settings.json");
23
+
24
+ /** Subpath under the global agent dir for the global Pi settings file. */
25
+ const GLOBAL_SETTINGS_SUBPATH = join(".pi", "agent", "settings.json");
26
+
27
+ // ── Internal Helpers ─────────────────────────────────────────────────
28
+
29
+ /**
30
+ * Safely read and parse a JSON file, returning null on any failure.
31
+ */
32
+ function readJsonSafe(filePath: string): Record<string, unknown> | null {
33
+ try {
34
+ const raw = readFileSync(filePath, "utf-8");
35
+ const parsed = JSON.parse(raw);
36
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
37
+ return parsed as Record<string, unknown>;
38
+ }
39
+ return null;
40
+ } catch {
41
+ return null;
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Extract the `packages` array from a parsed settings object.
47
+ * Returns an empty array if the key is missing or not an array of strings.
48
+ */
49
+ function extractPackages(settings: Record<string, unknown> | null): string[] {
50
+ if (!settings) return [];
51
+ const packages = settings.packages;
52
+ if (!Array.isArray(packages)) return [];
53
+ // Filter to strings only, skip non-string entries gracefully
54
+ return packages.filter((p): p is string => typeof p === "string" && p.length > 0);
55
+ }
56
+
57
+ /**
58
+ * Resolve the global Pi agent settings path.
59
+ *
60
+ * Resolution order:
61
+ * 1. `PI_CODING_AGENT_DIR` env → `<value>/settings.json`
62
+ * 2. `os.homedir()/.pi/agent/settings.json`
63
+ */
64
+ function resolveGlobalSettingsPath(): string {
65
+ const agentDir = process.env.PI_CODING_AGENT_DIR;
66
+ if (agentDir) {
67
+ return join(agentDir, "settings.json");
68
+ }
69
+ return join(homedir(), GLOBAL_SETTINGS_SUBPATH);
70
+ }
71
+
72
+ // ── Public API ───────────────────────────────────────────────────────
73
+
74
+ /**
75
+ * Load Pi extension packages from project and global settings files.
76
+ *
77
+ * Reads `.pi/settings.json` from the project root (stateRoot) and from
78
+ * the global agent directory, merges the package lists (project first,
79
+ * deduplicated), and filters out any package containing "taskplane"
80
+ * (which is already loaded as the bridge extension).
81
+ *
82
+ * @param stateRoot - Project root directory (used to locate `.pi/settings.json`)
83
+ * @returns Array of package specifiers (e.g., `["npm:pi-sage"]`) or empty array
84
+ */
85
+ export function loadPiSettingsPackages(stateRoot: string): string[] {
86
+ // Read project-level packages
87
+ const projectSettingsPath = join(stateRoot, PROJECT_SETTINGS_SUBPATH);
88
+ const projectSettings = readJsonSafe(projectSettingsPath);
89
+ const projectPackages = extractPackages(projectSettings);
90
+
91
+ // Read global packages
92
+ const globalSettingsPath = resolveGlobalSettingsPath();
93
+ const globalSettings = readJsonSafe(globalSettingsPath);
94
+ const globalPackages = extractPackages(globalSettings);
95
+
96
+ // Merge: project entries first, then global, deduplicated
97
+ const seen = new Set<string>();
98
+ const merged: string[] = [];
99
+
100
+ for (const pkg of projectPackages) {
101
+ if (!seen.has(pkg)) {
102
+ seen.add(pkg);
103
+ merged.push(pkg);
104
+ }
105
+ }
106
+ for (const pkg of globalPackages) {
107
+ if (!seen.has(pkg)) {
108
+ seen.add(pkg);
109
+ merged.push(pkg);
110
+ }
111
+ }
112
+
113
+ // Filter out taskplane itself (already loaded as bridge extension).
114
+ // Match known specifier patterns: "npm:taskplane", "taskplane", or scoped
115
+ // variants like "npm:@scope/taskplane". Avoid substring matching to prevent
116
+ // false positives on unrelated packages containing "taskplane" in their name.
117
+ return merged.filter((pkg) => {
118
+ // Strip npm:/git: prefix to get the bare package name
119
+ const bare = pkg.replace(/^(?:npm:|git:(?:github\.com\/[^/]+\/)?)/, "").toLowerCase();
120
+ // Exact match on bare name, or scoped exact match (@scope/taskplane)
121
+ return bare !== "taskplane" && !bare.endsWith("/taskplane");
122
+ });
123
+ }
124
+
125
+ /**
126
+ * Filter out excluded extensions from a package list.
127
+ *
128
+ * @param packages - Full list of package specifiers
129
+ * @param exclusions - Package specifiers to exclude (exact match)
130
+ * @returns Filtered list with excluded packages removed
131
+ */
132
+ export function filterExcludedExtensions(packages: string[], exclusions: string[]): string[] {
133
+ if (!exclusions || exclusions.length === 0) return packages;
134
+ const excludeSet = new Set(exclusions);
135
+ return packages.filter((pkg) => !excludeSet.has(pkg));
136
+ }
@@ -2,7 +2,7 @@
2
2
  * Settings TUI — interactive configuration viewer and editor.
3
3
  *
4
4
  * Provides a `/taskplane-settings` command that renders a two-level navigation:
5
- * 1. Section selector (13 sections)
5
+ * 1. Section selector (14 sections)
6
6
  * 2. Per-section SettingsList with field display, source badges,
7
7
  * and inline editing for enum/boolean/string/number fields
8
8
  *
@@ -38,6 +38,7 @@ import {
38
38
  resolveConfigRoot,
39
39
  resolveGlobalPreferencesPath,
40
40
  } from "./config-loader.ts";
41
+ import { loadPiSettingsPackages } from "./settings-loader.ts";
41
42
 
42
43
 
43
44
  // ── Types ────────────────────────────────────────────────────────────
@@ -87,7 +88,7 @@ export interface SectionDef {
87
88
  // ── Section & Field Definitions ──────────────────────────────────────
88
89
 
89
90
  /**
90
- * Canonical navigation map — 13 sections.
91
+ * Canonical navigation map — 14 sections.
91
92
  * Order matches the Step 1 design in STATUS.md.
92
93
  */
93
94
  export const SECTIONS: SectionDef[] = [
@@ -136,6 +137,11 @@ export const SECTIONS: SectionDef[] = [
136
137
  { configPath: "orchestrator.merge.timeoutMinutes", label: "Merge Timeout (minutes)", control: "input", layer: "L1", fieldType: "number", description: "Max time for merge agent to complete. Increase for large batches (default: 10)" },
137
138
  ],
138
139
  },
140
+ {
141
+ name: "Agent Extensions",
142
+ readOnly: true, // Dynamically handled — no fixed fields
143
+ fields: [],
144
+ },
139
145
  {
140
146
  name: "Context Limits",
141
147
  fields: [
@@ -1252,7 +1258,9 @@ async function showSectionSelectorLoop(
1252
1258
  const sectionIndex = parseInt(selectedSection, 10);
1253
1259
  const section = SECTIONS[sectionIndex];
1254
1260
 
1255
- if (section.readOnly) {
1261
+ if (section.name === "Agent Extensions") {
1262
+ await showExtensionsSection(ctx, configRoot, pointerConfigRoot, onConfigChanged);
1263
+ } else if (section.readOnly) {
1256
1264
  await showAdvancedSection(ctx, state.mergedConfig);
1257
1265
  } else {
1258
1266
  await showSectionSettingsLoop(ctx, section, configRoot, pointerConfigRoot, onConfigChanged);
@@ -1312,6 +1320,142 @@ async function showAdvancedSection(
1312
1320
  });
1313
1321
  }
1314
1322
 
1323
+ /**
1324
+ * TP-180: Agent Extensions section — toggle extensions per agent type.
1325
+ *
1326
+ * Discovers all installed Pi extension packages from project + global settings,
1327
+ * shows per-agent-type toggles (Worker, Reviewer, Merger), and saves
1328
+ * exclusion changes to project taskplane-config.json.
1329
+ */
1330
+ async function showExtensionsSection(
1331
+ ctx: ExtensionContext,
1332
+ configRoot: string,
1333
+ pointerConfigRoot?: string,
1334
+ onConfigChanged?: () => void,
1335
+ ): Promise<void> {
1336
+ while (true) {
1337
+ const resolvedRoot = resolveConfigRoot(configRoot, pointerConfigRoot);
1338
+ const mergedConfig = loadProjectConfig(configRoot, pointerConfigRoot);
1339
+
1340
+ // Discover installed packages (excluding taskplane itself)
1341
+ // Use configRoot (project/state root) for consistency with runtime forwarding,
1342
+ // not resolvedRoot (pointer-resolved config path).
1343
+ const packages = loadPiSettingsPackages(configRoot);
1344
+
1345
+ if (packages.length === 0) {
1346
+ await ctx.ui.custom((_tui, theme, _kb, done) => {
1347
+ const container = new Container();
1348
+ container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
1349
+ container.addChild(new Text(theme.fg("accent", theme.bold("Agent Extensions")), 1, 0));
1350
+ container.addChild(new Text("", 0, 0));
1351
+ container.addChild(new Text(theme.fg("dim", "No third-party extensions found."), 1, 0));
1352
+ container.addChild(new Text(theme.fg("dim", "Install extensions via pi settings to see them here."), 1, 0));
1353
+ container.addChild(new Text("", 0, 0));
1354
+ container.addChild(new Text(theme.fg("dim", "esc back"), 1, 0));
1355
+ container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
1356
+ return {
1357
+ render: (w: number) => container.render(w),
1358
+ invalidate: () => container.invalidate(),
1359
+ handleInput: (data: string) => { if (data === "\x1b" || data === "\x1b\x1b") done(undefined); },
1360
+ };
1361
+ });
1362
+ return;
1363
+ }
1364
+
1365
+ // Read current exclusion lists
1366
+ const workerExclude = new Set(mergedConfig.taskRunner.worker.excludeExtensions ?? []);
1367
+ const reviewerExclude = new Set(mergedConfig.taskRunner.reviewer.excludeExtensions ?? []);
1368
+ const mergeExclude = new Set(mergedConfig.orchestrator.merge.excludeExtensions ?? []);
1369
+
1370
+ const agentTypes = [
1371
+ { name: "Worker", exclude: workerExclude, configPath: "taskRunner.worker.excludeExtensions" },
1372
+ { name: "Reviewer", exclude: reviewerExclude, configPath: "taskRunner.reviewer.excludeExtensions" },
1373
+ { name: "Merger", exclude: mergeExclude, configPath: "orchestrator.merge.excludeExtensions" },
1374
+ ];
1375
+
1376
+ // Build toggle items: one per package per agent type
1377
+ const settingsItems: SettingItem[] = [];
1378
+ for (const pkg of packages) {
1379
+ for (const agentType of agentTypes) {
1380
+ const isExcluded = agentType.exclude.has(pkg);
1381
+ const enabled = !isExcluded;
1382
+ settingsItems.push({
1383
+ id: `${agentType.configPath}::${pkg}`,
1384
+ label: `${pkg}`,
1385
+ currentValue: enabled ? "✅ enabled" : "❌ disabled",
1386
+ description: agentType.name,
1387
+ values: [enabled ? "❌ disabled" : "✅ enabled"],
1388
+ });
1389
+ }
1390
+ }
1391
+
1392
+ const result = await ctx.ui.custom<{ id: string; value: string } | null>((tui, theme, _kb, done) => {
1393
+ const container = new Container();
1394
+ container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
1395
+ container.addChild(new Text(theme.fg("accent", theme.bold("Agent Extensions")), 1, 0));
1396
+ container.addChild(new Text(theme.fg("dim", "Toggle extensions on/off per agent type"), 1, 0));
1397
+ container.addChild(new Text("", 0, 0));
1398
+
1399
+ const settingsList = new SettingsList(
1400
+ settingsItems,
1401
+ Math.min(settingsItems.length + 2, 20),
1402
+ getSettingsListTheme(),
1403
+ (id, newValue) => done({ id, value: newValue }),
1404
+ () => done(null),
1405
+ );
1406
+ container.addChild(settingsList);
1407
+
1408
+ container.addChild(new Text("", 0, 0));
1409
+ container.addChild(new Text(theme.fg("dim", "↑↓ navigate • space toggle • esc back"), 1, 0));
1410
+ container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
1411
+
1412
+ return {
1413
+ render: (w: number) => container.render(w),
1414
+ invalidate: () => container.invalidate(),
1415
+ handleInput: (data: string) => { settingsList.handleInput?.(data); tui.requestRender(); },
1416
+ };
1417
+ });
1418
+
1419
+ if (!result) return; // User pressed Esc
1420
+
1421
+ // Parse the toggle result
1422
+ const [configPath, pkg] = result.id.split("::", 2);
1423
+ if (!configPath || !pkg) continue;
1424
+
1425
+ const enabling = result.value.includes("enabled");
1426
+
1427
+ // Read current exclusion array from merged effective config (handles YAML+JSON)
1428
+ const freshConfig = loadProjectConfig(configRoot, pointerConfigRoot);
1429
+ const currentExcludeList: string[] = (getNestedValue(freshConfig, configPath) as string[] | undefined) ?? [];
1430
+
1431
+ let newExcludeList: string[];
1432
+ if (enabling) {
1433
+ // Remove from exclusions → enable
1434
+ newExcludeList = currentExcludeList.filter((e: string) => e !== pkg);
1435
+ } else {
1436
+ // Add to exclusions → disable
1437
+ newExcludeList = currentExcludeList.includes(pkg)
1438
+ ? currentExcludeList
1439
+ : [...currentExcludeList, pkg];
1440
+ }
1441
+
1442
+ try {
1443
+ writeProjectConfigField(configRoot, configPath, newExcludeList, pointerConfigRoot);
1444
+ if (onConfigChanged) {
1445
+ try { onConfigChanged(); } catch { /* non-fatal */ }
1446
+ }
1447
+ ctx.ui.notify(
1448
+ `${enabling ? "✅ Enabled" : "❌ Disabled"} ${pkg} for ${configPath.includes("worker") ? "Worker" : configPath.includes("reviewer") ? "Reviewer" : "Merger"}`,
1449
+ "info",
1450
+ );
1451
+ } catch (err: any) {
1452
+ ctx.ui.notify(`❌ Failed to save: ${err.message}`, "error");
1453
+ }
1454
+
1455
+ // Loop continues → re-render with fresh state
1456
+ }
1457
+ }
1458
+
1315
1459
  /**
1316
1460
  * Format a source badge for display.
1317
1461
  */
@@ -43,6 +43,8 @@ export interface OrchestratorConfig {
43
43
  order: "fewest-files-first" | "sequential";
44
44
  /** Merge agent timeout in minutes. Default: 10. Increase for large batches. */
45
45
  timeout_minutes: number;
46
+ /** Package specifiers to exclude from extension forwarding (exact match). @since TP-180 */
47
+ exclude_extensions?: string[];
46
48
  };
47
49
  failure: {
48
50
  on_task_failure: "skip-dependents" | "stop-wave" | "stop-all";
@@ -323,7 +325,11 @@ export interface TaskRunnerConfig {
323
325
  thinking: string;
324
326
  /** Comma-separated tool allowlist */
325
327
  tools: string;
328
+ /** Package specifiers to exclude from extension forwarding (exact match). @since TP-180 */
329
+ excludeExtensions?: string[];
326
330
  };
331
+ /** Worker agent extension exclusion list. @since TP-180 */
332
+ workerExcludeExtensions?: string[];
327
333
  }
328
334
 
329
335
  /** Result of a preflight check */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taskplane",
3
- "version": "0.28.1",
3
+ "version": "0.28.3",
4
4
  "description": "AI agent orchestration for pi — parallel task execution with checkpoint discipline",
5
5
  "keywords": [
6
6
  "pi-package",