pi-cohort 4.0.0 → 5.0.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## [5.0.0] - 2026-08-06
4
+
5
+ ### Changed
6
+ - **BREAKING:** Programmatic chains now launch directly when `clarify` is omitted. Pass `clarify: true` to keep the foreground preview/edit flow.
7
+
8
+ ## [4.1.0] - 2026-07-19
9
+
10
+ ### Added
11
+ - **Forward parent pi CLI flags into children.** Extension flags the parent `pi` was launched with (e.g. pi-lens's `--no-autofix`) are now auto-forwarded into spawned children - foreground, sync-background, and detached-async, at any nesting depth - derived universally from the parent's argv. Pi core flags are never forwarded. Opt out with `forwardParentFlags: false`. ([#2](https://github.com/jjuraszek/pi-cohort/issues/2))
12
+
3
13
  ## [4.0.0] - 2026-07-16
4
14
 
5
15
  ### Changed
package/README.md CHANGED
@@ -117,6 +117,8 @@ Full reference: agent/chain authoring in [doc/agents-and-chains.md](doc/agents-a
117
117
 
118
118
  Most installs need zero configuration. Full env/JSON reference: [doc/configuration.md](doc/configuration.md).
119
119
 
120
+ Parent extension CLI flags (e.g. pi-lens's `--no-autofix`) are forwarded into spawned children by default, so subagents inherit the same tooling behavior you launched with; toggle with [`forwardParentFlags`](doc/configuration.md#forwardparentflags).
121
+
120
122
  Optional companions:
121
123
 
122
124
  - [pi-intercom](https://github.com/jjuraszek/pi-intercom) - lets a blocked child ask the parent a question instead of guessing. [doc/skills-and-companions.md](doc/skills-and-companions.md#optional-pi-intercom-companion)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-cohort",
3
- "version": "4.0.0",
3
+ "version": "5.0.0",
4
4
  "description": "Delegate Pi work to focused child agents: code review, scouting, implementation, parallel audits, saved chains, and background jobs.",
5
5
  "author": "Jacek Juraszek",
6
6
  "license": "MIT",
@@ -400,8 +400,8 @@ subagent({
400
400
  })
401
401
  ```
402
402
 
403
- Chains default to clarify mode; set `clarify: false` to skip it. Clarify edits affect only the next run; use management actions, settings, or markdown files for persistent changes.
404
- For programmatic background launches, use `async: true`. Set `clarify: false` when you want to bypass chain clarification explicitly; `clarify: true` keeps the run foreground for the clarify UI.
403
+ Programmatic chains launch directly by default. Set `clarify: true` to preview or edit a sequential chain before launch; chains containing parallel steps skip the UI. Clarify edits affect only the next run; use management actions, settings, or markdown files for persistent changes.
404
+ For programmatic background launches, use `async: true`. Explicit `clarify: true` keeps the run foreground for the clarify UI.
405
405
 
406
406
 
407
407
  ## Worktree Isolation
@@ -11,7 +11,8 @@ import { deliverSubagentIntercomMessageEvent } from "../intercom/result-intercom
11
11
  import { resolveSubagentIntercomTarget } from "../intercom/intercom-bridge.ts";
12
12
  import { SubagentParams } from "./schemas.ts";
13
13
  import { loadConfig } from "./config.ts";
14
- import { type Details, type SubagentState } from "../shared/types.ts";
14
+ import { deriveForwardedFlags } from "../runs/shared/forward-flags.ts";
15
+ import type { Details, SubagentState } from "../shared/types.ts";
15
16
 
16
17
  function getSubagentSessionRoot(parentSessionFile: string | null): string {
17
18
  if (parentSessionFile) {
@@ -148,6 +149,7 @@ export default function registerFanoutChildSubagentExtension(pi: ExtensionAPI):
148
149
  getSubagentSessionRoot,
149
150
  expandTilde,
150
151
  discoverAgents,
152
+ forwardedFlags: deriveForwardedFlags(process.argv, config),
151
153
  allowMutatingManagementActions: false,
152
154
  });
153
155
 
@@ -16,7 +16,7 @@ import * as fs from "node:fs";
16
16
  import * as os from "node:os";
17
17
  import * as path from "node:path";
18
18
  import type { AgentToolResult } from "@earendil-works/pi-agent-core";
19
- import { type ExtensionAPI, type ExtensionContext, type ToolDefinition } from "@earendil-works/pi-coding-agent";
19
+ import type { ExtensionAPI, ExtensionContext, ToolDefinition } from "@earendil-works/pi-coding-agent";
20
20
  import { Box, Container, Spacer, Text, truncateToWidth, visibleWidth, wrapTextWithAnsi, type Component } from "@earendil-works/pi-tui";
21
21
  import { discoverAgents, discoverAgentsAll } from "../agents/agents.ts";
22
22
  import { cleanupAllArtifactDirs, cleanupOldArtifacts, getArtifactsDir } from "../shared/artifacts.ts";
@@ -37,6 +37,7 @@ import { SUBAGENT_CHILD_ENV, SUBAGENT_FANOUT_CHILD_ENV } from "../runs/shared/pi
37
37
  import registerFanoutChildSubagentExtension from "./fanout-child.ts";
38
38
  import { formatDuration, shortenPath } from "../shared/formatters.ts";
39
39
  import { loadConfig } from "./config.ts";
40
+ import { deriveForwardedFlags } from "../runs/shared/forward-flags.ts";
40
41
  import {
41
42
  type Details,
42
43
  type SubagentState,
@@ -315,6 +316,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
315
316
  getSubagentSessionRoot,
316
317
  expandTilde,
317
318
  discoverAgents,
319
+ forwardedFlags: deriveForwardedFlags(process.argv, config),
318
320
  });
319
321
 
320
322
  pi.registerMessageRenderer<SlashMessageDetails>(SLASH_RESULT_TYPE, (message, options, theme) => {
@@ -289,7 +289,7 @@ export const SubagentParams = Type.Object({
289
289
  Type.String({ description: "Directory to store session logs (default: temp; enables sessions even if share=false)" }),
290
290
  ),
291
291
  // Clarification TUI
292
- clarify: Type.Optional(Type.Boolean({ description: "Show TUI to preview/edit before execution. Explicit clarify: true keeps the run foreground for the clarify UI; omitted clarify can still run in the background when async: true is set." })),
292
+ clarify: Type.Optional(Type.Boolean({ description: "Show TUI to preview/edit before execution. Omitted or false launches directly. Explicit clarify: true keeps the run foreground for the clarify UI when supported; chains containing parallel steps skip the UI." })),
293
293
  control: Type.Optional(ControlOverrides),
294
294
  // Solo agent overrides
295
295
  output: Type.Optional(Type.Unsafe({
@@ -120,6 +120,7 @@ interface AsyncChainParams {
120
120
  controlIntercomTarget?: string;
121
121
  childIntercomTarget?: (agent: string, index: number) => string | undefined;
122
122
  nestedRoute?: NestedRouteInfo;
123
+ forwardedFlags?: string[];
123
124
  acceptance?: AcceptanceInput;
124
125
  }
125
126
 
@@ -147,6 +148,7 @@ interface AsyncSingleParams {
147
148
  controlIntercomTarget?: string;
148
149
  childIntercomTarget?: (agent: string, index: number) => string | undefined;
149
150
  nestedRoute?: NestedRouteInfo;
151
+ forwardedFlags?: string[];
150
152
  acceptance?: AcceptanceInput;
151
153
  }
152
154
 
@@ -249,6 +251,7 @@ export function executeAsyncChain(
249
251
  controlIntercomTarget,
250
252
  childIntercomTarget,
251
253
  nestedRoute,
254
+ forwardedFlags,
252
255
  } = params;
253
256
  const resultMode = params.resultMode ?? "chain";
254
257
  const chainSkills = params.chainSkills ?? [];
@@ -481,6 +484,7 @@ export function executeAsyncChain(
481
484
  sessionId: ctx.currentSessionId,
482
485
  piPackageRoot,
483
486
  piArgv1: process.argv[1],
487
+ forwardedFlags,
484
488
  worktreeSetupHook,
485
489
  worktreeSetupHookTimeoutMs,
486
490
  controlConfig,
@@ -629,6 +633,7 @@ export function executeAsyncSingle(
629
633
  controlIntercomTarget,
630
634
  childIntercomTarget,
631
635
  nestedRoute,
636
+ forwardedFlags,
632
637
  } = params;
633
638
  const task = params.task ?? "";
634
639
  const runnerCwd = resolveChildCwd(ctx.cwd, cwd);
@@ -716,6 +721,7 @@ export function executeAsyncSingle(
716
721
  sessionId: ctx.currentSessionId,
717
722
  piPackageRoot,
718
723
  piArgv1: process.argv[1],
724
+ forwardedFlags,
719
725
  worktreeSetupHook,
720
726
  worktreeSetupHookTimeoutMs,
721
727
  controlConfig,
@@ -97,6 +97,7 @@ interface SubagentRunConfig {
97
97
  sessionId?: string | null;
98
98
  piPackageRoot?: string;
99
99
  piArgv1?: string;
100
+ forwardedFlags?: string[];
100
101
  worktreeSetupHook?: string;
101
102
  worktreeSetupHookTimeoutMs?: number;
102
103
  controlConfig?: ResolvedControlConfig;
@@ -574,6 +575,7 @@ interface SingleStepContext {
574
575
  outputFile: string;
575
576
  piPackageRoot?: string;
576
577
  piArgv1?: string;
578
+ forwardedFlags?: string[];
577
579
  registerInterrupt?: (interrupt: (() => void) | undefined) => void;
578
580
  childIntercomTarget?: string;
579
581
  orchestratorIntercomTarget?: string;
@@ -677,6 +679,7 @@ async function runSingleStep(
677
679
  parentRootRunId: ctx.nestedRoute?.rootRunId,
678
680
  parentCapabilityToken: ctx.nestedRoute?.capabilityToken,
679
681
  structuredOutput: effectiveStructuredOutput,
682
+ forwardedFlags: ctx.forwardedFlags,
680
683
  });
681
684
  const run = await runPiStreaming(
682
685
  args,
@@ -1610,6 +1613,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
1610
1613
  outputFile: path.join(asyncDir, `output-${fi}.log`),
1611
1614
  piPackageRoot: config.piPackageRoot,
1612
1615
  piArgv1: config.piArgv1,
1616
+ forwardedFlags: config.forwardedFlags,
1613
1617
  childIntercomTarget: config.childIntercomTargets?.[fi],
1614
1618
  orchestratorIntercomTarget: config.controlIntercomTarget,
1615
1619
  nestedRoute: config.nestedRoute,
@@ -1857,6 +1861,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
1857
1861
  outputFile: path.join(asyncDir, `output-${fi}.log`),
1858
1862
  piPackageRoot: config.piPackageRoot,
1859
1863
  piArgv1: config.piArgv1,
1864
+ forwardedFlags: config.forwardedFlags,
1860
1865
  childIntercomTarget: config.childIntercomTargets?.[fi],
1861
1866
  orchestratorIntercomTarget: config.controlIntercomTarget,
1862
1867
  nestedRoute: config.nestedRoute,
@@ -2024,6 +2029,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
2024
2029
  outputFile: path.join(asyncDir, `output-${flatIndex}.log`),
2025
2030
  piPackageRoot: config.piPackageRoot,
2026
2031
  piArgv1: config.piArgv1,
2032
+ forwardedFlags: config.forwardedFlags,
2027
2033
  childIntercomTarget: config.childIntercomTargets?.[flatIndex],
2028
2034
  orchestratorIntercomTarget: config.controlIntercomTarget,
2029
2035
  nestedRoute: config.nestedRoute,
@@ -136,6 +136,7 @@ interface ParallelChainRunInput {
136
136
  worktreeSetup?: WorktreeSetup;
137
137
  maxSubagentDepth: number;
138
138
  nestedRoute?: NestedRouteInfo;
139
+ forwardedFlags?: string[];
139
140
  }
140
141
 
141
142
  function buildChainExecutionDetails(input: ChainExecutionDetailsInput): Details {
@@ -290,6 +291,7 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
290
291
  structuredOutput: structuredRuntime,
291
292
  acceptance: task.acceptance,
292
293
  acceptanceContext: { mode: "chain" },
294
+ forwardedFlags: input.forwardedFlags,
293
295
  onUpdate: input.onUpdate
294
296
  ? (progressUpdate) => {
295
297
  const stepResults = progressUpdate.details?.results || [];
@@ -392,6 +394,7 @@ interface ChainExecutionParams {
392
394
  nestedRoute?: NestedRouteInfo;
393
395
  worktreeSetupHook?: string;
394
396
  worktreeSetupHookTimeoutMs?: number;
397
+ forwardedFlags?: string[];
395
398
  }
396
399
 
397
400
  interface ChainExecutionResult {
@@ -490,7 +493,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
490
493
  const chainDir = createChainDir(runId, chainDirBase);
491
494
  const hasParallelSteps = chainSteps.some((step) => isParallelStep(step) || isDynamicParallelStep(step));
492
495
  let templates: ResolvedTemplates = resolveChainTemplates(chainSteps);
493
- const shouldClarify = clarify !== false && ctx.hasUI && !hasParallelSteps;
496
+ const shouldClarify = clarify === true && ctx.hasUI && !hasParallelSteps;
494
497
  let tuiBehaviorOverrides: (BehaviorOverride | undefined)[] | undefined;
495
498
  const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map(toModelInfo);
496
499
  const availableSkills = discoverAvailableSkills(cwd ?? ctx.cwd);
@@ -644,6 +647,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
644
647
  cwd,
645
648
  runId,
646
649
  globalTaskIndex,
650
+ forwardedFlags: params.forwardedFlags,
647
651
  sessionDirForIndex,
648
652
  sessionFileForIndex,
649
653
  shareEnabled,
@@ -850,6 +854,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
850
854
  cwd,
851
855
  runId,
852
856
  globalTaskIndex,
857
+ forwardedFlags: params.forwardedFlags,
853
858
  sessionDirForIndex,
854
859
  sessionFileForIndex,
855
860
  shareEnabled,
@@ -1062,6 +1067,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
1062
1067
  artifactConfig,
1063
1068
  outputPath,
1064
1069
  outputMode: behavior.outputMode,
1070
+ forwardedFlags: params.forwardedFlags,
1065
1071
  maxSubagentDepth,
1066
1072
  controlConfig,
1067
1073
  onControlEvent,
@@ -179,6 +179,7 @@ async function runSingleAttempt(
179
179
  parentRootRunId: options.nestedRoute?.rootRunId,
180
180
  parentCapabilityToken: options.nestedRoute?.capabilityToken,
181
181
  structuredOutput: options.structuredOutput,
182
+ forwardedFlags: options.forwardedFlags,
182
183
  });
183
184
 
184
185
  const result: SingleResult = {
@@ -145,6 +145,7 @@ interface ExecutorDeps {
145
145
  pi: ExtensionAPI;
146
146
  state: SubagentState;
147
147
  config: ExtensionConfig;
148
+ forwardedFlags: string[];
148
149
  asyncByDefault: boolean;
149
150
  tempArtifactsDir: string;
150
151
  getSubagentSessionRoot: (parentSessionFile: string | null) => string;
@@ -665,6 +666,7 @@ async function resumeAsyncRun(input: {
665
666
  controlIntercomTarget: intercomBridge.active ? intercomBridge.orchestratorTarget : undefined,
666
667
  childIntercomTarget: intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(runId, agent, index) : undefined,
667
668
  availableModels,
669
+ forwardedFlags: input.deps.forwardedFlags,
668
670
  });
669
671
  if (result.isError) return result;
670
672
 
@@ -1133,6 +1135,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
1133
1135
  controlIntercomTarget,
1134
1136
  childIntercomTarget,
1135
1137
  nestedRoute,
1138
+ forwardedFlags: deps.forwardedFlags,
1136
1139
  });
1137
1140
  }
1138
1141
 
@@ -1162,6 +1165,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
1162
1165
  controlIntercomTarget,
1163
1166
  childIntercomTarget,
1164
1167
  nestedRoute,
1168
+ forwardedFlags: deps.forwardedFlags,
1165
1169
  });
1166
1170
  }
1167
1171
 
@@ -1205,6 +1209,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
1205
1209
  childIntercomTarget: childIntercomTarget ? (agent, index) => childIntercomTarget(agent, index) : undefined,
1206
1210
  nestedRoute,
1207
1211
  acceptance: params.acceptance,
1212
+ forwardedFlags: deps.forwardedFlags,
1208
1213
  });
1209
1214
  }
1210
1215
 
@@ -1264,6 +1269,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
1264
1269
  maxSubagentDepth: currentMaxSubagentDepth,
1265
1270
  worktreeSetupHook: deps.config.worktreeSetupHook,
1266
1271
  worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
1272
+ forwardedFlags: deps.forwardedFlags,
1267
1273
  });
1268
1274
 
1269
1275
  if (chainResult.requestedAsync) {
@@ -1304,6 +1310,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
1304
1310
  controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
1305
1311
  childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
1306
1312
  nestedRoute: data.nestedRoute,
1313
+ forwardedFlags: deps.forwardedFlags,
1307
1314
  });
1308
1315
  }
1309
1316
 
@@ -1363,6 +1370,7 @@ interface ForegroundParallelRunInput {
1363
1370
  liveProgress: (AgentProgress | undefined)[];
1364
1371
  onUpdate?: (r: AgentToolResult<Details>) => void;
1365
1372
  worktreeSetup?: WorktreeSetup;
1373
+ forwardedFlags?: string[];
1366
1374
  }
1367
1375
 
1368
1376
  function buildParallelModeError(message: string): AgentToolResult<Details> {
@@ -1523,6 +1531,7 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
1523
1531
  skills: effectiveSkills === false ? [] : effectiveSkills,
1524
1532
  acceptance: task.acceptance,
1525
1533
  acceptanceContext: { mode: "parallel" },
1534
+ forwardedFlags: input.forwardedFlags,
1526
1535
  onUpdate: input.onUpdate
1527
1536
  ? (progressUpdate) => {
1528
1537
  const stepResults = progressUpdate.details?.results || [];
@@ -1735,6 +1744,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
1735
1744
  controlConfig,
1736
1745
  controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
1737
1746
  childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
1747
+ forwardedFlags: deps.forwardedFlags,
1738
1748
  });
1739
1749
  }
1740
1750
  }
@@ -1818,6 +1828,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
1818
1828
  liveProgress,
1819
1829
  onUpdate: onUpdateWithCost,
1820
1830
  worktreeSetup,
1831
+ forwardedFlags: deps.forwardedFlags,
1821
1832
  });
1822
1833
  if (foregroundControl) updateForegroundNestedProjection(foregroundControl);
1823
1834
  recordSyncCost(deps.state.grandTotal, runId, results.reduce((sum, r) => sum + r.usage.cost, 0) + sumNestedCost(foregroundControl?.nestedChildren));
@@ -2013,6 +2024,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
2013
2024
  controlConfig,
2014
2025
  controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
2015
2026
  childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
2027
+ forwardedFlags: deps.forwardedFlags,
2016
2028
  });
2017
2029
  }
2018
2030
  }
@@ -2101,6 +2113,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
2101
2113
  skills: effectiveSkills,
2102
2114
  acceptance: params.acceptance,
2103
2115
  acceptanceContext: { mode: "single" },
2116
+ forwardedFlags: deps.forwardedFlags,
2104
2117
  });
2105
2118
  if (foregroundControl?.currentIndex === 0) {
2106
2119
  foregroundControl.interrupt = undefined;
@@ -0,0 +1,82 @@
1
+ export interface ForwardFlagsConfig {
2
+ forwardParentFlags?: boolean;
3
+ }
4
+
5
+ // Core pi flag names -> value arity. This is the UNION of recognized core flags
6
+ // across the supported pi range (0.74-0.80), transcribed from
7
+ // @earendil-works/pi-coding-agent dist/cli/args.js. A name present here is never
8
+ // forwarded to children. Direction matters: an entry that is core in a NEWER pi
9
+ // but absent from an OLDER installed pi is safe (worst case we skip forwarding a
10
+ // hypothetical extension flag of the same name); a MISSING entry is the real
11
+ // hazard - it would leak a real core flag into the child. The drift tripwire in
12
+ // test/unit/forward-flags.test.ts asserts installed-core is a subset of this table.
13
+ type Arity = "boolean" | "value" | "value-guarded" | "value-guarded-print";
14
+ export const RECOGNIZED_PI_FLAGS: Record<string, Arity> = {
15
+ help: "boolean", version: "boolean", continue: "boolean", resume: "boolean",
16
+ "no-session": "boolean", "no-tools": "boolean", "no-builtin-tools": "boolean",
17
+ "no-extensions": "boolean", "no-skills": "boolean", "no-prompt-templates": "boolean",
18
+ "no-themes": "boolean", "no-context-files": "boolean", verbose: "boolean",
19
+ approve: "boolean", "no-approve": "boolean", offline: "boolean",
20
+ mode: "value", provider: "value", model: "value", "api-key": "value",
21
+ "system-prompt": "value", "append-system-prompt": "value", name: "value",
22
+ session: "value", "session-id": "value", fork: "value", "session-dir": "value",
23
+ models: "value", tools: "value", "exclude-tools": "value", thinking: "value",
24
+ export: "value", extension: "value", skill: "value", "prompt-template": "value",
25
+ theme: "value",
26
+ print: "value-guarded-print", "list-models": "value-guarded",
27
+ };
28
+ // Short aliases -> long name, so `-p foo` / `-e x` classify correctly.
29
+ const SHORT_ALIASES: Record<string, string> = {
30
+ h: "help", v: "version", c: "continue", r: "resume", nt: "no-tools",
31
+ nbt: "no-builtin-tools", ne: "no-extensions", ns: "no-skills",
32
+ np: "no-prompt-templates", nc: "no-context-files", a: "approve", na: "no-approve",
33
+ n: "name", t: "tools", xt: "exclude-tools", p: "print", e: "extension",
34
+ };
35
+ const EXTENSION_CUSTOMIZATION = new Set(["--extension", "-e", "--no-extensions", "-ne"]);
36
+
37
+ function consumesNext(arity: Arity, next: string | undefined): boolean {
38
+ if (arity === "value") return next !== undefined;
39
+ if (arity === "value-guarded") return next !== undefined && !next.startsWith("-") && !next.startsWith("@");
40
+ if (arity === "value-guarded-print") return next !== undefined && !next.startsWith("@") && (!next.startsWith("-") || next.startsWith("---"));
41
+ return false;
42
+ }
43
+
44
+ export function deriveForwardedFlags(argv: string[], config: ForwardFlagsConfig): string[] {
45
+ if (config.forwardParentFlags === false) return [];
46
+ if (argv.some((tok) => EXTENSION_CUSTOMIZATION.has(tok))) return [];
47
+
48
+ const collected: Array<{ name: string; tokens: string[] }> = [];
49
+ for (let i = 2; i < argv.length; i++) {
50
+ const tok = argv[i]!;
51
+ if (tok.startsWith("@") || !tok.startsWith("-")) continue;
52
+ if (tok.startsWith("-") && !tok.startsWith("--")) {
53
+ const alias = SHORT_ALIASES[tok.slice(1)];
54
+ if (alias && consumesNext(RECOGNIZED_PI_FLAGS[alias]!, argv[i + 1])) i++;
55
+ continue;
56
+ }
57
+ const body = tok.slice(2);
58
+ const eq = body.indexOf("=");
59
+ const name = eq === -1 ? body : body.slice(0, eq);
60
+ const arity = RECOGNIZED_PI_FLAGS[name];
61
+ if (arity) {
62
+ if (eq === -1 && consumesNext(arity, argv[i + 1])) i++;
63
+ continue;
64
+ }
65
+ if (eq !== -1) {
66
+ collected.push({ name, tokens: [tok] });
67
+ continue;
68
+ }
69
+ const next = argv[i + 1];
70
+ if (next !== undefined && !next.startsWith("-") && !next.startsWith("@")) {
71
+ collected.push({ name, tokens: [tok, next] });
72
+ i++;
73
+ } else {
74
+ collected.push({ name, tokens: [tok] });
75
+ }
76
+ }
77
+
78
+ // Dedup by name, last value wins; first-occurrence position is preserved.
79
+ const lastByName = new Map<string, string[]>();
80
+ for (const { name, tokens } of collected) lastByName.set(name, tokens);
81
+ return [...lastByName.values()].flat();
82
+ }
@@ -38,6 +38,7 @@ interface BuildPiArgsInput {
38
38
  inheritSkills: boolean;
39
39
  tools?: string[];
40
40
  extensions?: string[];
41
+ forwardedFlags?: string[];
41
42
  systemPrompt?: string | null;
42
43
  cwd?: string;
43
44
  promptFileStem?: string;
@@ -76,6 +77,11 @@ export function applyThinkingSuffix(model: string | undefined, thinking: string
76
77
 
77
78
  export function buildPiArgs(input: BuildPiArgsInput): BuildPiArgsResult {
78
79
  const args = [...input.baseArgs];
80
+ // Forwarded flags are already name-deduped by deriveForwardedFlags; append only
81
+ // when the child inherits full extension discovery (extensions === undefined).
82
+ if (input.extensions === undefined && input.forwardedFlags?.length) {
83
+ args.push(...input.forwardedFlags);
84
+ }
79
85
 
80
86
  if (input.sessionFile) {
81
87
  fs.mkdirSync(path.dirname(input.sessionFile), { recursive: true });
@@ -796,6 +796,8 @@ export interface RunSyncOptions {
796
796
  outputMode?: OutputMode;
797
797
  maxSubagentDepth?: number;
798
798
  nestedRoute?: NestedRouteInfo;
799
+ /** Parent extension flags to forward into the child (already derived; empty = none). */
800
+ forwardedFlags?: string[];
799
801
  /** Override the agent's default model (format: "provider/id" or just "id") */
800
802
  modelOverride?: string;
801
803
  /** Registry models available for heuristic bare-model resolution */
@@ -849,6 +851,8 @@ export interface ExtensionConfig {
849
851
  intercomBridge?: IntercomBridgeConfig;
850
852
  /** Print a roster of discovered subagents/chains into chat at session start. Default: true. */
851
853
  showRosterOnStart?: boolean;
854
+ /** Forward the parent pi's extension CLI flags (e.g. --no-autofix) into spawned children. Default: true. */
855
+ forwardParentFlags?: boolean;
852
856
  }
853
857
 
854
858
  // ============================================================================