pi-cohort 3.0.2 → 4.1.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 +10 -0
- package/README.md +79 -1156
- package/agents/reviewer.md +1 -1
- package/package.json +1 -1
- package/src/extension/fanout-child.ts +3 -1
- package/src/extension/index.ts +3 -1
- package/src/extension/schemas.ts +2 -2
- package/src/runs/background/async-execution.ts +6 -0
- package/src/runs/background/subagent-runner.ts +6 -0
- package/src/runs/foreground/chain-clarify.ts +24 -8
- package/src/runs/foreground/chain-execution.ts +6 -0
- package/src/runs/foreground/execution.ts +1 -0
- package/src/runs/foreground/subagent-executor.ts +22 -11
- package/src/runs/shared/forward-flags.ts +82 -0
- package/src/runs/shared/pi-args.ts +6 -0
- package/src/runs/shared/single-output.ts +9 -0
- package/src/shared/types.ts +4 -0
package/agents/reviewer.md
CHANGED
|
@@ -64,7 +64,7 @@ When asked to review for over-engineering, bloat, or "what can we delete", run a
|
|
|
64
64
|
|
|
65
65
|
## Working rules
|
|
66
66
|
- Read the plan, progress, and relevant files first when available.
|
|
67
|
-
-
|
|
67
|
+
- Top-level progress files exist only when the caller explicitly enables them. Do not ask consumer repositories to add cohort scratch files to `.gitignore`.
|
|
68
68
|
- Use `bash` only for read-only inspection (e.g., `git diff`, `git log`, `git show`, test runs).
|
|
69
69
|
- Do not invent issues. Only report problems you can justify from evidence.
|
|
70
70
|
- Prefer small corrective edits over broad rewrites.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-cohort",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.1.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",
|
|
@@ -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 {
|
|
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
|
|
package/src/extension/index.ts
CHANGED
|
@@ -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 {
|
|
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) => {
|
package/src/extension/schemas.ts
CHANGED
|
@@ -119,7 +119,7 @@ const TaskItem = Type.Object({
|
|
|
119
119
|
output: Type.Optional(OutputOverride),
|
|
120
120
|
outputMode: Type.Optional(OutputModeOverride),
|
|
121
121
|
reads: Type.Optional(ReadsOverride),
|
|
122
|
-
progress: Type.Optional(Type.Boolean({ description: "
|
|
122
|
+
progress: Type.Optional(Type.Boolean({ description: "Omit or set false to disable progress.md tracking for this task; true enables it." })),
|
|
123
123
|
model: Type.Optional(Type.String({ description: "Override model for this task (e.g. 'google/gemini-3-pro')" })),
|
|
124
124
|
skill: Type.Optional(SkillOverride),
|
|
125
125
|
acceptance: Type.Optional(AcceptanceOverride),
|
|
@@ -297,7 +297,7 @@ export const SubagentParams = Type.Object({
|
|
|
297
297
|
{ type: "string" },
|
|
298
298
|
{ type: "boolean" },
|
|
299
299
|
],
|
|
300
|
-
description: "
|
|
300
|
+
description: "Omit or set false to disable file output; true uses the agent-configured filename; a string sets the output path. Relative paths resolve against cwd.",
|
|
301
301
|
})),
|
|
302
302
|
outputMode: Type.Optional(OutputModeOverride),
|
|
303
303
|
skill: Type.Optional(SkillOverride),
|
|
@@ -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,
|
|
@@ -494,7 +494,7 @@ export class ChainClarifyComponent implements Component {
|
|
|
494
494
|
return;
|
|
495
495
|
}
|
|
496
496
|
|
|
497
|
-
if (data === "w"
|
|
497
|
+
if (data === "w") {
|
|
498
498
|
this.enterEditMode("output");
|
|
499
499
|
return;
|
|
500
500
|
}
|
|
@@ -504,11 +504,15 @@ export class ChainClarifyComponent implements Component {
|
|
|
504
504
|
return;
|
|
505
505
|
}
|
|
506
506
|
|
|
507
|
-
if (data === "p" && this.mode
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
507
|
+
if (data === "p" && this.mode !== 'single') {
|
|
508
|
+
if (this.mode === 'chain') {
|
|
509
|
+
const anyEnabled = this.agentConfigs.some((_, i) => this.getEffectiveBehavior(i).progress);
|
|
510
|
+
const newState = !anyEnabled;
|
|
511
|
+
for (let i = 0; i < this.agentConfigs.length; i++) {
|
|
512
|
+
this.updateBehavior(i, "progress", newState);
|
|
513
|
+
}
|
|
514
|
+
} else {
|
|
515
|
+
this.updateBehavior(this.selectedStep, "progress", !this.getEffectiveBehavior(this.selectedStep).progress);
|
|
512
516
|
}
|
|
513
517
|
this.tui.requestRender();
|
|
514
518
|
return;
|
|
@@ -532,7 +536,9 @@ export class ChainClarifyComponent implements Component {
|
|
|
532
536
|
buffer = template.split("\n")[0] ?? "";
|
|
533
537
|
} else if (mode === "output") {
|
|
534
538
|
const behavior = this.getEffectiveBehavior(this.selectedStep);
|
|
535
|
-
buffer = behavior.output === false
|
|
539
|
+
buffer = behavior.output === false
|
|
540
|
+
? (this.mode === "chain" ? "" : this.agentConfigs[this.selectedStep]?.output ?? "")
|
|
541
|
+
: (behavior.output || "");
|
|
536
542
|
} else if (mode === "reads") {
|
|
537
543
|
const behavior = this.getEffectiveBehavior(this.selectedStep);
|
|
538
544
|
buffer = behavior.reads === false ? "" : (behavior.reads?.join(", ") || "");
|
|
@@ -1108,7 +1114,7 @@ export class ChainClarifyComponent implements Component {
|
|
|
1108
1114
|
case 'single':
|
|
1109
1115
|
return ` [Enter] Run • [Esc] Cancel • e m t w s ${bgLabel} `;
|
|
1110
1116
|
case 'parallel':
|
|
1111
|
-
return ` [Enter] Run • [Esc] Cancel • e m t s ${bgLabel} • ↑↓ Nav `;
|
|
1117
|
+
return ` [Enter] Run • [Esc] Cancel • e m t w p s ${bgLabel} • ↑↓ Nav `;
|
|
1112
1118
|
case 'chain':
|
|
1113
1119
|
return ` [Enter] Run • [Esc] Cancel • e m t w r p s ${bgLabel} • ↑↓ Nav `;
|
|
1114
1120
|
}
|
|
@@ -1207,6 +1213,16 @@ export class ChainClarifyComponent implements Component {
|
|
|
1207
1213
|
lines.push(this.row(` ${modelLabel}${truncateToWidth(modelValue, innerW - 13)}`));
|
|
1208
1214
|
|
|
1209
1215
|
const behavior = this.getEffectiveBehavior(i);
|
|
1216
|
+
const writesValue = behavior.output === false
|
|
1217
|
+
? th.fg("dim", "(disabled)")
|
|
1218
|
+
: (behavior.output || th.fg("dim", "(none)"));
|
|
1219
|
+
const writesLabel = th.fg("dim", "writes: ");
|
|
1220
|
+
lines.push(this.row(` ${writesLabel}${truncateToWidth(writesValue, innerW - 14)}`));
|
|
1221
|
+
|
|
1222
|
+
const progressValue = behavior.progress ? th.fg("success", "on") : th.fg("dim", "off");
|
|
1223
|
+
const progressLabel = th.fg("dim", "progress: ");
|
|
1224
|
+
lines.push(this.row(` ${progressLabel}${progressValue}`));
|
|
1225
|
+
|
|
1210
1226
|
const skillsValue = behavior.skills === false
|
|
1211
1227
|
? th.fg("dim", "(disabled)")
|
|
1212
1228
|
: (behavior.skills?.length ? behavior.skills.join(", ") : th.fg("dim", "(none)"));
|
|
@@ -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 {
|
|
@@ -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 = {
|
|
@@ -3,7 +3,7 @@ import * as fs from "node:fs";
|
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
5
5
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
6
|
-
import {
|
|
6
|
+
import type { AgentConfig, AgentScope } from "../../agents/agents.ts";
|
|
7
7
|
import { getArtifactsDir } from "../../shared/artifacts.ts";
|
|
8
8
|
import { ChainClarifyComponent, type ChainClarifyResult } from "./chain-clarify.ts";
|
|
9
9
|
import { toModelInfo, type ModelInfo } from "../../shared/model-info.ts";
|
|
@@ -37,7 +37,7 @@ import { createForkContextResolver } from "../../shared/fork-context.ts";
|
|
|
37
37
|
import { resolveCurrentSessionId } from "../../shared/session-identity.ts";
|
|
38
38
|
import { applyIntercomBridgeToAgent, INTERCOM_BRIDGE_MARKER, resolveIntercomBridge, resolveIntercomSessionTarget, resolveSubagentIntercomTarget, type IntercomBridgeState } from "../../intercom/intercom-bridge.ts";
|
|
39
39
|
import { formatControlIntercomMessage, formatControlNoticeMessage, resolveControlConfig, shouldNotifyControlEvent } from "../shared/subagent-control.ts";
|
|
40
|
-
import { finalizeSingleOutput, injectSingleOutputInstruction,
|
|
40
|
+
import { finalizeSingleOutput, injectSingleOutputInstruction, normalizeTopLevelOutput, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
|
|
41
41
|
import { compactForegroundDetails, getSingleResultOutput, mapConcurrent, readStatus, resolveChildCwd } from "../../shared/utils.ts";
|
|
42
42
|
import {
|
|
43
43
|
attachNestedChildrenToResultChildren,
|
|
@@ -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
|
|
|
@@ -1102,10 +1104,10 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1102
1104
|
cwd: task.cwd,
|
|
1103
1105
|
...(modelOverrides[index] ? { model: modelOverrides[index] } : {}),
|
|
1104
1106
|
...(skillOverrides[index] !== undefined ? { skill: skillOverrides[index] } : {}),
|
|
1105
|
-
|
|
1107
|
+
output: normalizeTopLevelOutput(task.output, agentConfigs[index]?.output),
|
|
1106
1108
|
...(task.outputMode !== undefined ? { outputMode: task.outputMode } : {}),
|
|
1107
1109
|
...(task.reads !== undefined && task.reads !== true ? { reads: task.reads } : {}),
|
|
1108
|
-
|
|
1110
|
+
progress: task.progress ?? false,
|
|
1109
1111
|
...(task.acceptance !== undefined ? { acceptance: task.acceptance } : {}),
|
|
1110
1112
|
}));
|
|
1111
1113
|
return executeAsyncChain(id, {
|
|
@@ -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
|
|
|
@@ -1174,8 +1178,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1174
1178
|
details: { mode: "single" as const, results: [] },
|
|
1175
1179
|
};
|
|
1176
1180
|
}
|
|
1177
|
-
const
|
|
1178
|
-
const effectiveOutput = normalizeSingleOutputOverride(rawOutput, a.output);
|
|
1181
|
+
const effectiveOutput = normalizeTopLevelOutput(params.output, a.output);
|
|
1179
1182
|
const effectiveOutputMode = params.outputMode ?? "inline";
|
|
1180
1183
|
const normalizedSkills = normalizeSkillInput(params.skill);
|
|
1181
1184
|
const skills = normalizedSkills === false ? [] : normalizedSkills;
|
|
@@ -1206,6 +1209,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1206
1209
|
childIntercomTarget: childIntercomTarget ? (agent, index) => childIntercomTarget(agent, index) : undefined,
|
|
1207
1210
|
nestedRoute,
|
|
1208
1211
|
acceptance: params.acceptance,
|
|
1212
|
+
forwardedFlags: deps.forwardedFlags,
|
|
1209
1213
|
});
|
|
1210
1214
|
}
|
|
1211
1215
|
|
|
@@ -1265,6 +1269,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
1265
1269
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
1266
1270
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
1267
1271
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
1272
|
+
forwardedFlags: deps.forwardedFlags,
|
|
1268
1273
|
});
|
|
1269
1274
|
|
|
1270
1275
|
if (chainResult.requestedAsync) {
|
|
@@ -1305,6 +1310,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
1305
1310
|
controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
1306
1311
|
childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
|
|
1307
1312
|
nestedRoute: data.nestedRoute,
|
|
1313
|
+
forwardedFlags: deps.forwardedFlags,
|
|
1308
1314
|
});
|
|
1309
1315
|
}
|
|
1310
1316
|
|
|
@@ -1364,6 +1370,7 @@ interface ForegroundParallelRunInput {
|
|
|
1364
1370
|
liveProgress: (AgentProgress | undefined)[];
|
|
1365
1371
|
onUpdate?: (r: AgentToolResult<Details>) => void;
|
|
1366
1372
|
worktreeSetup?: WorktreeSetup;
|
|
1373
|
+
forwardedFlags?: string[];
|
|
1367
1374
|
}
|
|
1368
1375
|
|
|
1369
1376
|
function buildParallelModeError(message: string): AgentToolResult<Details> {
|
|
@@ -1524,6 +1531,7 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
1524
1531
|
skills: effectiveSkills === false ? [] : effectiveSkills,
|
|
1525
1532
|
acceptance: task.acceptance,
|
|
1526
1533
|
acceptanceContext: { mode: "parallel" },
|
|
1534
|
+
forwardedFlags: input.forwardedFlags,
|
|
1527
1535
|
onUpdate: input.onUpdate
|
|
1528
1536
|
? (progressUpdate) => {
|
|
1529
1537
|
const stepResults = progressUpdate.details?.results || [];
|
|
@@ -1630,10 +1638,10 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
1630
1638
|
normalizeSkillInput(t.skill),
|
|
1631
1639
|
);
|
|
1632
1640
|
const behaviorOverrides: StepOverrides[] = tasks.map((task, index) => ({
|
|
1633
|
-
|
|
1641
|
+
output: normalizeTopLevelOutput(task.output, agentConfigs[index]?.output),
|
|
1634
1642
|
...(task.outputMode !== undefined ? { outputMode: task.outputMode } : {}),
|
|
1635
1643
|
...(task.reads !== undefined && task.reads !== true ? { reads: task.reads } : {}),
|
|
1636
|
-
|
|
1644
|
+
progress: task.progress ?? false,
|
|
1637
1645
|
...(skillOverrides[index] !== undefined ? { skills: skillOverrides[index] } : {}),
|
|
1638
1646
|
...(task.model ? { model: task.model } : {}),
|
|
1639
1647
|
}));
|
|
@@ -1736,6 +1744,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
1736
1744
|
controlConfig,
|
|
1737
1745
|
controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
1738
1746
|
childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
|
|
1747
|
+
forwardedFlags: deps.forwardedFlags,
|
|
1739
1748
|
});
|
|
1740
1749
|
}
|
|
1741
1750
|
}
|
|
@@ -1819,6 +1828,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
1819
1828
|
liveProgress,
|
|
1820
1829
|
onUpdate: onUpdateWithCost,
|
|
1821
1830
|
worktreeSetup,
|
|
1831
|
+
forwardedFlags: deps.forwardedFlags,
|
|
1822
1832
|
});
|
|
1823
1833
|
if (foregroundControl) updateForegroundNestedProjection(foregroundControl);
|
|
1824
1834
|
recordSyncCost(deps.state.grandTotal, runId, results.reduce((sum, r) => sum + r.usage.cost, 0) + sumNestedCost(foregroundControl?.nestedChildren));
|
|
@@ -1939,8 +1949,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
1939
1949
|
currentProvider,
|
|
1940
1950
|
);
|
|
1941
1951
|
let skillOverride: string[] | false | undefined = normalizeSkillInput(params.skill);
|
|
1942
|
-
|
|
1943
|
-
let effectiveOutput = normalizeSingleOutputOverride(rawOutput, agentConfig.output);
|
|
1952
|
+
let effectiveOutput = normalizeTopLevelOutput(params.output, agentConfig.output);
|
|
1944
1953
|
const effectiveOutputMode = params.outputMode ?? "inline";
|
|
1945
1954
|
const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
|
|
1946
1955
|
const maxSubagentDepth = resolveChildMaxSubagentDepth(currentMaxSubagentDepth, agentConfig.maxSubagentDepth);
|
|
@@ -1974,7 +1983,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
1974
1983
|
task = result.templates[0]!;
|
|
1975
1984
|
const override = result.behaviorOverrides[0];
|
|
1976
1985
|
if (override?.model) modelOverride = override.model;
|
|
1977
|
-
if (override?.output !== undefined) effectiveOutput =
|
|
1986
|
+
if (override?.output !== undefined) effectiveOutput = normalizeTopLevelOutput(override.output, agentConfig.output);
|
|
1978
1987
|
if (override?.skills !== undefined) skillOverride = override.skills;
|
|
1979
1988
|
|
|
1980
1989
|
if (result.runInBackground) {
|
|
@@ -2015,6 +2024,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2015
2024
|
controlConfig,
|
|
2016
2025
|
controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
2017
2026
|
childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
|
|
2027
|
+
forwardedFlags: deps.forwardedFlags,
|
|
2018
2028
|
});
|
|
2019
2029
|
}
|
|
2020
2030
|
}
|
|
@@ -2103,6 +2113,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2103
2113
|
skills: effectiveSkills,
|
|
2104
2114
|
acceptance: params.acceptance,
|
|
2105
2115
|
acceptanceContext: { mode: "single" },
|
|
2116
|
+
forwardedFlags: deps.forwardedFlags,
|
|
2106
2117
|
});
|
|
2107
2118
|
if (foregroundControl?.currentIndex === 0) {
|
|
2108
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 });
|
|
@@ -18,6 +18,15 @@ export function normalizeSingleOutputOverride(
|
|
|
18
18
|
return undefined;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
export function normalizeTopLevelOutput(
|
|
22
|
+
output: string | boolean | undefined,
|
|
23
|
+
defaultOutput: string | undefined,
|
|
24
|
+
): string | false {
|
|
25
|
+
if (output === undefined || (typeof output === "string" && output.trim().length === 0)) return false;
|
|
26
|
+
const normalizedOutput = normalizeSingleOutputOverride(output, defaultOutput);
|
|
27
|
+
return typeof normalizedOutput === "string" && normalizedOutput.trim().length === 0 ? false : normalizedOutput ?? false;
|
|
28
|
+
}
|
|
29
|
+
|
|
21
30
|
export function resolveSingleOutputPath(
|
|
22
31
|
output: string | boolean | undefined,
|
|
23
32
|
runtimeCwd: string,
|
package/src/shared/types.ts
CHANGED
|
@@ -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
|
// ============================================================================
|