pi-subagents 0.12.2 ā 0.12.4
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 +16 -0
- package/README.md +66 -6
- package/agent-management.ts +12 -5
- package/agent-manager-chain-detail.ts +2 -2
- package/agent-manager-detail.ts +9 -7
- package/agent-manager-edit.ts +4 -4
- package/agent-manager-list.ts +2 -2
- package/agent-manager-parallel.ts +3 -3
- package/agent-manager.ts +28 -14
- package/agent-scope.ts +1 -1
- package/agent-selection.ts +1 -1
- package/agent-serializer.ts +5 -1
- package/agent-templates.ts +1 -1
- package/agents.ts +31 -9
- package/artifacts.ts +1 -1
- package/async-execution.ts +28 -9
- package/chain-clarify.ts +39 -17
- package/chain-execution.ts +37 -12
- package/chain-serializer.ts +2 -2
- package/execution.ts +20 -11
- package/formatters.ts +3 -3
- package/index.ts +16 -16
- package/notify.ts +1 -1
- package/package.json +8 -1
- package/parallel-utils.ts +1 -0
- package/pi-spawn.ts +6 -9
- package/render.ts +7 -7
- package/schemas.ts +1 -1
- package/settings.ts +2 -2
- package/single-output.ts +50 -10
- package/skills.ts +5 -2
- package/subagent-executor.ts +55 -7
- package/subagent-runner.ts +32 -22
- package/types.ts +31 -5
- package/utils.ts +5 -1
- package/worktree.ts +240 -17
package/subagent-executor.ts
CHANGED
|
@@ -23,7 +23,7 @@ import { discoverAvailableSkills, normalizeSkillInput } from "./skills.js";
|
|
|
23
23
|
import { executeAsyncChain, executeAsyncSingle, isAsyncAvailable } from "./async-execution.js";
|
|
24
24
|
import { createForkContextResolver } from "./fork-context.js";
|
|
25
25
|
import { finalizeSingleOutput, injectSingleOutputInstruction, resolveSingleOutputPath } from "./single-output.js";
|
|
26
|
-
import {
|
|
26
|
+
import { getSingleResultOutput, mapConcurrent } from "./utils.js";
|
|
27
27
|
import {
|
|
28
28
|
cleanupWorktrees,
|
|
29
29
|
createWorktrees,
|
|
@@ -46,6 +46,8 @@ import {
|
|
|
46
46
|
MAX_CONCURRENCY,
|
|
47
47
|
MAX_PARALLEL,
|
|
48
48
|
checkSubagentDepth,
|
|
49
|
+
resolveChildMaxSubagentDepth,
|
|
50
|
+
resolveCurrentMaxSubagentDepth,
|
|
49
51
|
wrapForkTask,
|
|
50
52
|
} from "./types.js";
|
|
51
53
|
|
|
@@ -362,6 +364,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
362
364
|
}
|
|
363
365
|
const id = randomUUID();
|
|
364
366
|
const asyncCtx = { pi: deps.pi, cwd: ctx.cwd, currentSessionId: deps.state.currentSessionId! };
|
|
367
|
+
const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
|
|
365
368
|
|
|
366
369
|
if (hasChain && params.chain) {
|
|
367
370
|
const normalized = normalizeSkillInput(params.skill);
|
|
@@ -379,6 +382,9 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
379
382
|
sessionRoot,
|
|
380
383
|
chainSkills,
|
|
381
384
|
sessionFilesByFlatIndex: collectChainSessionFiles(chain, sessionFileForIndex),
|
|
385
|
+
maxSubagentDepth: currentMaxSubagentDepth,
|
|
386
|
+
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
387
|
+
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
382
388
|
});
|
|
383
389
|
}
|
|
384
390
|
|
|
@@ -395,6 +401,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
395
401
|
const effectiveOutput: string | false | undefined = rawOutput === true ? a.output : (rawOutput as string | false | undefined);
|
|
396
402
|
const normalizedSkills = normalizeSkillInput(params.skill);
|
|
397
403
|
const skills = normalizedSkills === false ? [] : normalizedSkills;
|
|
404
|
+
const maxSubagentDepth = resolveChildMaxSubagentDepth(currentMaxSubagentDepth, a.maxSubagentDepth);
|
|
398
405
|
return executeAsyncSingle(id, {
|
|
399
406
|
agent: params.agent!,
|
|
400
407
|
task: params.context === "fork" ? wrapForkTask(params.task!) : params.task!,
|
|
@@ -409,6 +416,9 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
409
416
|
sessionFile: sessionFileForIndex(0),
|
|
410
417
|
skills,
|
|
411
418
|
output: effectiveOutput,
|
|
419
|
+
maxSubagentDepth,
|
|
420
|
+
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
421
|
+
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
412
422
|
});
|
|
413
423
|
}
|
|
414
424
|
|
|
@@ -433,6 +443,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
433
443
|
const normalized = normalizeSkillInput(params.skill);
|
|
434
444
|
const chainSkills = normalized === false ? [] : (normalized ?? []);
|
|
435
445
|
const chain = wrapChainTasksForFork(params.chain as ChainStep[], params.context);
|
|
446
|
+
const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
|
|
436
447
|
const chainResult = await executeChain({
|
|
437
448
|
chain,
|
|
438
449
|
task: params.task,
|
|
@@ -451,6 +462,9 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
451
462
|
onUpdate,
|
|
452
463
|
chainSkills,
|
|
453
464
|
chainDir: params.chainDir,
|
|
465
|
+
maxSubagentDepth: currentMaxSubagentDepth,
|
|
466
|
+
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
467
|
+
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
454
468
|
});
|
|
455
469
|
|
|
456
470
|
if (chainResult.requestedAsync) {
|
|
@@ -476,6 +490,9 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
476
490
|
sessionRoot,
|
|
477
491
|
chainSkills: chainResult.requestedAsync.chainSkills,
|
|
478
492
|
sessionFilesByFlatIndex: collectChainSessionFiles(asyncChain, sessionFileForIndex),
|
|
493
|
+
maxSubagentDepth: currentMaxSubagentDepth,
|
|
494
|
+
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
495
|
+
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
479
496
|
});
|
|
480
497
|
}
|
|
481
498
|
|
|
@@ -496,6 +513,7 @@ interface ForegroundParallelRunInput {
|
|
|
496
513
|
artifactsDir: string;
|
|
497
514
|
maxOutput?: MaxOutputConfig;
|
|
498
515
|
paramsCwd?: string;
|
|
516
|
+
maxSubagentDepths: number[];
|
|
499
517
|
modelOverrides: (string | undefined)[];
|
|
500
518
|
skillOverrides: (string[] | false | undefined)[];
|
|
501
519
|
behaviors: Array<ReturnType<typeof resolveStepBehavior>>;
|
|
@@ -517,11 +535,20 @@ function createParallelWorktreeSetup(
|
|
|
517
535
|
enabled: boolean | undefined,
|
|
518
536
|
cwd: string,
|
|
519
537
|
runId: string,
|
|
520
|
-
|
|
538
|
+
tasks: TaskParam[],
|
|
539
|
+
setupHook: ExtensionConfig["worktreeSetupHook"],
|
|
540
|
+
setupHookTimeoutMs: ExtensionConfig["worktreeSetupHookTimeoutMs"],
|
|
521
541
|
): { setup?: WorktreeSetup; errorResult?: AgentToolResult<Details> } {
|
|
522
542
|
if (!enabled) return {};
|
|
523
543
|
try {
|
|
524
|
-
return {
|
|
544
|
+
return {
|
|
545
|
+
setup: createWorktrees(cwd, runId, tasks.length, {
|
|
546
|
+
agents: tasks.map((task) => task.agent),
|
|
547
|
+
setupHook: setupHook
|
|
548
|
+
? { hookPath: setupHook, timeoutMs: setupHookTimeoutMs }
|
|
549
|
+
: undefined,
|
|
550
|
+
}),
|
|
551
|
+
};
|
|
525
552
|
} catch (error) {
|
|
526
553
|
const message = error instanceof Error ? error.message : String(error);
|
|
527
554
|
return { errorResult: buildParallelModeError(message) };
|
|
@@ -587,6 +614,7 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
587
614
|
artifactsDir: input.artifactConfig.enabled ? input.artifactsDir : undefined,
|
|
588
615
|
artifactConfig: input.artifactConfig,
|
|
589
616
|
maxOutput: input.maxOutput,
|
|
617
|
+
maxSubagentDepth: input.maxSubagentDepths[index],
|
|
590
618
|
modelOverride: input.modelOverrides[index],
|
|
591
619
|
skills: effectiveSkills === false ? [] : effectiveSkills,
|
|
592
620
|
onUpdate: input.onUpdate
|
|
@@ -652,6 +680,11 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
652
680
|
agentConfigs.push(config);
|
|
653
681
|
}
|
|
654
682
|
|
|
683
|
+
const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
|
|
684
|
+
const maxSubagentDepths = agentConfigs.map((config) =>
|
|
685
|
+
resolveChildMaxSubagentDepth(currentMaxSubagentDepth, config.maxSubagentDepth),
|
|
686
|
+
);
|
|
687
|
+
|
|
655
688
|
const effectiveCwd = params.cwd ?? ctx.cwd;
|
|
656
689
|
if (params.worktree) {
|
|
657
690
|
const worktreeTaskCwdError = buildParallelWorktreeTaskCwdError(tasks, effectiveCwd);
|
|
@@ -733,6 +766,9 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
733
766
|
sessionRoot,
|
|
734
767
|
chainSkills: [],
|
|
735
768
|
sessionFilesByFlatIndex: tasks.map((_, index) => sessionFileForIndex(index)),
|
|
769
|
+
maxSubagentDepth: currentMaxSubagentDepth,
|
|
770
|
+
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
771
|
+
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
736
772
|
});
|
|
737
773
|
}
|
|
738
774
|
}
|
|
@@ -744,7 +780,9 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
744
780
|
params.worktree,
|
|
745
781
|
effectiveCwd,
|
|
746
782
|
runId,
|
|
747
|
-
tasks
|
|
783
|
+
tasks,
|
|
784
|
+
deps.config.worktreeSetupHook,
|
|
785
|
+
deps.config.worktreeSetupHookTimeoutMs,
|
|
748
786
|
);
|
|
749
787
|
if (errorResult) return errorResult;
|
|
750
788
|
|
|
@@ -772,6 +810,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
772
810
|
modelOverrides,
|
|
773
811
|
skillOverrides,
|
|
774
812
|
behaviors,
|
|
813
|
+
maxSubagentDepths,
|
|
775
814
|
liveResults,
|
|
776
815
|
liveProgress,
|
|
777
816
|
onUpdate,
|
|
@@ -793,7 +832,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
793
832
|
const aggregatedOutput = aggregateParallelOutputs(
|
|
794
833
|
results.map((result) => ({
|
|
795
834
|
agent: result.agent,
|
|
796
|
-
output: result.truncation?.text ||
|
|
835
|
+
output: result.truncation?.text || getSingleResultOutput(result),
|
|
797
836
|
exitCode: result.exitCode,
|
|
798
837
|
error: result.error,
|
|
799
838
|
})),
|
|
@@ -850,6 +889,8 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
850
889
|
let skillOverride: string[] | false | undefined = normalizeSkillInput(params.skill);
|
|
851
890
|
const rawOutput = params.output !== undefined ? params.output : agentConfig.output;
|
|
852
891
|
let effectiveOutput: string | false | undefined = rawOutput === true ? agentConfig.output : (rawOutput as string | false | undefined);
|
|
892
|
+
const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
|
|
893
|
+
const maxSubagentDepth = resolveChildMaxSubagentDepth(currentMaxSubagentDepth, agentConfig.maxSubagentDepth);
|
|
853
894
|
|
|
854
895
|
if (params.clarify === true && ctx.hasUI) {
|
|
855
896
|
const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map((m) => ({
|
|
@@ -912,6 +953,9 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
912
953
|
sessionFile: sessionFileForIndex(0),
|
|
913
954
|
skills: skillOverride === false ? [] : skillOverride,
|
|
914
955
|
output: effectiveOutput,
|
|
956
|
+
maxSubagentDepth,
|
|
957
|
+
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
958
|
+
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
915
959
|
});
|
|
916
960
|
}
|
|
917
961
|
}
|
|
@@ -940,6 +984,8 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
940
984
|
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
941
985
|
artifactConfig,
|
|
942
986
|
maxOutput: params.maxOutput,
|
|
987
|
+
outputPath,
|
|
988
|
+
maxSubagentDepth,
|
|
943
989
|
onUpdate,
|
|
944
990
|
modelOverride,
|
|
945
991
|
skills: effectiveSkills,
|
|
@@ -949,12 +995,14 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
949
995
|
if (r.progress) allProgress.push(r.progress);
|
|
950
996
|
if (r.artifactPaths) allArtifactPaths.push(r.artifactPaths);
|
|
951
997
|
|
|
952
|
-
const fullOutput =
|
|
998
|
+
const fullOutput = getSingleResultOutput(r);
|
|
953
999
|
const finalizedOutput = finalizeSingleOutput({
|
|
954
1000
|
fullOutput,
|
|
955
1001
|
truncatedOutput: r.truncation?.text,
|
|
956
1002
|
outputPath,
|
|
957
1003
|
exitCode: r.exitCode,
|
|
1004
|
+
savedPath: r.savedOutputPath,
|
|
1005
|
+
saveError: r.outputSaveError,
|
|
958
1006
|
});
|
|
959
1007
|
|
|
960
1008
|
if (r.exitCode !== 0)
|
|
@@ -1010,7 +1058,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
1010
1058
|
return handleManagementAction(params.action, params, ctx);
|
|
1011
1059
|
}
|
|
1012
1060
|
|
|
1013
|
-
const { blocked, depth, maxDepth } = checkSubagentDepth();
|
|
1061
|
+
const { blocked, depth, maxDepth } = checkSubagentDepth(deps.config.maxSubagentDepth);
|
|
1014
1062
|
if (blocked) {
|
|
1015
1063
|
return {
|
|
1016
1064
|
content: [
|
package/subagent-runner.ts
CHANGED
|
@@ -3,9 +3,9 @@ import * as fs from "node:fs";
|
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import * as path from "node:path";
|
|
5
5
|
import { pathToFileURL } from "node:url";
|
|
6
|
-
import { appendJsonl, getArtifactPaths } from "./artifacts.
|
|
7
|
-
import { getPiSpawnCommand } from "./pi-spawn.
|
|
8
|
-
import {
|
|
6
|
+
import { appendJsonl, getArtifactPaths } from "./artifacts.ts";
|
|
7
|
+
import { getPiSpawnCommand } from "./pi-spawn.ts";
|
|
8
|
+
import { captureSingleOutputSnapshot, resolveSingleOutput } from "./single-output.ts";
|
|
9
9
|
import {
|
|
10
10
|
type ArtifactConfig,
|
|
11
11
|
type ArtifactPaths,
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
type MaxOutputConfig,
|
|
14
14
|
truncateOutput,
|
|
15
15
|
getSubagentDepthEnv,
|
|
16
|
-
} from "./types.
|
|
16
|
+
} from "./types.ts";
|
|
17
17
|
import {
|
|
18
18
|
type RunnerSubagentStep as SubagentStep,
|
|
19
19
|
type RunnerStep,
|
|
@@ -22,8 +22,8 @@ import {
|
|
|
22
22
|
mapConcurrent,
|
|
23
23
|
aggregateParallelOutputs,
|
|
24
24
|
MAX_PARALLEL_CONCURRENCY,
|
|
25
|
-
} from "./parallel-utils.
|
|
26
|
-
import { buildPiArgs, cleanupTempDir } from "./pi-args.
|
|
25
|
+
} from "./parallel-utils.ts";
|
|
26
|
+
import { buildPiArgs, cleanupTempDir } from "./pi-args.ts";
|
|
27
27
|
import {
|
|
28
28
|
cleanupWorktrees,
|
|
29
29
|
createWorktrees,
|
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
formatWorktreeDiffSummary,
|
|
33
33
|
formatWorktreeTaskCwdConflict,
|
|
34
34
|
type WorktreeSetup,
|
|
35
|
-
} from "./worktree.
|
|
35
|
+
} from "./worktree.ts";
|
|
36
36
|
|
|
37
37
|
interface SubagentRunConfig {
|
|
38
38
|
id: string;
|
|
@@ -50,6 +50,8 @@ interface SubagentRunConfig {
|
|
|
50
50
|
asyncDir: string;
|
|
51
51
|
sessionId?: string | null;
|
|
52
52
|
piPackageRoot?: string;
|
|
53
|
+
worktreeSetupHook?: string;
|
|
54
|
+
worktreeSetupHookTimeoutMs?: number;
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
interface StepResult {
|
|
@@ -116,10 +118,11 @@ function runPiStreaming(
|
|
|
116
118
|
outputFile: string,
|
|
117
119
|
env?: Record<string, string | undefined>,
|
|
118
120
|
piPackageRoot?: string,
|
|
121
|
+
maxSubagentDepth?: number,
|
|
119
122
|
): Promise<{ stdout: string; exitCode: number | null }> {
|
|
120
123
|
return new Promise((resolve) => {
|
|
121
124
|
const outputStream = fs.createWriteStream(outputFile, { flags: "w" });
|
|
122
|
-
const spawnEnv = { ...process.env, ...(env ?? {}), ...getSubagentDepthEnv() };
|
|
125
|
+
const spawnEnv = { ...process.env, ...(env ?? {}), ...getSubagentDepthEnv(maxSubagentDepth) };
|
|
123
126
|
const spawnSpec = getPiSpawnCommand(args, piPackageRoot ? { piPackageRoot } : undefined);
|
|
124
127
|
const child = spawn(spawnSpec.command, spawnSpec.args, { cwd, stdio: ["ignore", "pipe", "pipe"], env: spawnEnv });
|
|
125
128
|
let stdout = "";
|
|
@@ -307,6 +310,7 @@ async function runSingleStep(
|
|
|
307
310
|
const task = step.task.replace(placeholderRegex, () => ctx.previousOutput);
|
|
308
311
|
const sessionEnabled = Boolean(step.sessionFile) || ctx.sessionEnabled;
|
|
309
312
|
const sessionDir = step.sessionFile ? undefined : ctx.sessionDir;
|
|
313
|
+
const outputSnapshot = captureSingleOutputSnapshot(step.outputPath);
|
|
310
314
|
const { args, env, tempDir } = buildPiArgs({
|
|
311
315
|
baseArgs: ["-p"],
|
|
312
316
|
task,
|
|
@@ -332,22 +336,23 @@ async function runSingleStep(
|
|
|
332
336
|
}
|
|
333
337
|
}
|
|
334
338
|
|
|
335
|
-
const result = await runPiStreaming(args, step.cwd ?? ctx.cwd, ctx.outputFile, env, ctx.piPackageRoot);
|
|
339
|
+
const result = await runPiStreaming(args, step.cwd ?? ctx.cwd, ctx.outputFile, env, ctx.piPackageRoot, step.maxSubagentDepth);
|
|
336
340
|
cleanupTempDir(tempDir);
|
|
337
341
|
|
|
338
|
-
const
|
|
342
|
+
const rawOutput = (result.stdout || "").trim();
|
|
343
|
+
const resolvedOutput = step.outputPath && result.exitCode === 0
|
|
344
|
+
? resolveSingleOutput(step.outputPath, rawOutput, outputSnapshot)
|
|
345
|
+
: { fullOutput: rawOutput };
|
|
346
|
+
const output = resolvedOutput.fullOutput;
|
|
339
347
|
let outputForSummary = output;
|
|
340
|
-
if (
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
? `${output}\n\nā ļø Failed to save output to: ${step.outputPath}\n${persisted.error}`
|
|
349
|
-
: `ā ļø Failed to save output to: ${step.outputPath}\n${persisted.error}`;
|
|
350
|
-
}
|
|
348
|
+
if (resolvedOutput.savedPath) {
|
|
349
|
+
outputForSummary = output
|
|
350
|
+
? `${output}\n\nš Output saved to: ${resolvedOutput.savedPath}`
|
|
351
|
+
: `š Output saved to: ${resolvedOutput.savedPath}`;
|
|
352
|
+
} else if (resolvedOutput.saveError && step.outputPath && result.exitCode === 0) {
|
|
353
|
+
outputForSummary = output
|
|
354
|
+
? `${output}\n\nā ļø Failed to save output to: ${step.outputPath}\n${resolvedOutput.saveError}`
|
|
355
|
+
: `ā ļø Failed to save output to: ${step.outputPath}\n${resolvedOutput.saveError}`;
|
|
351
356
|
}
|
|
352
357
|
|
|
353
358
|
if (artifactPaths && ctx.artifactConfig?.enabled !== false) {
|
|
@@ -580,7 +585,12 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
580
585
|
break;
|
|
581
586
|
}
|
|
582
587
|
try {
|
|
583
|
-
worktreeSetup = createWorktrees(cwd, `${id}-s${stepIndex}`, group.parallel.length
|
|
588
|
+
worktreeSetup = createWorktrees(cwd, `${id}-s${stepIndex}`, group.parallel.length, {
|
|
589
|
+
agents: group.parallel.map((task) => task.agent),
|
|
590
|
+
setupHook: config.worktreeSetupHook
|
|
591
|
+
? { hookPath: config.worktreeSetupHook, timeoutMs: config.worktreeSetupHookTimeoutMs }
|
|
592
|
+
: undefined,
|
|
593
|
+
});
|
|
584
594
|
} catch (error) {
|
|
585
595
|
const setupError = error instanceof Error ? error.message : String(error);
|
|
586
596
|
const failedAt = Date.now();
|
package/types.ts
CHANGED
|
@@ -97,6 +97,9 @@ export interface SingleResult {
|
|
|
97
97
|
progressSummary?: ProgressSummary;
|
|
98
98
|
artifactPaths?: ArtifactPaths;
|
|
99
99
|
truncation?: TruncationResult;
|
|
100
|
+
finalOutput?: string;
|
|
101
|
+
savedOutputPath?: string;
|
|
102
|
+
outputSaveError?: string;
|
|
100
103
|
}
|
|
101
104
|
|
|
102
105
|
export interface Details {
|
|
@@ -230,6 +233,8 @@ export interface RunSyncOptions {
|
|
|
230
233
|
sessionDir?: string;
|
|
231
234
|
sessionFile?: string;
|
|
232
235
|
share?: boolean;
|
|
236
|
+
outputPath?: string;
|
|
237
|
+
maxSubagentDepth?: number;
|
|
233
238
|
/** Override the agent's default model (format: "provider/id" or just "id") */
|
|
234
239
|
modelOverride?: string;
|
|
235
240
|
/** Skills to inject (overrides agent default if provided) */
|
|
@@ -239,6 +244,9 @@ export interface RunSyncOptions {
|
|
|
239
244
|
export interface ExtensionConfig {
|
|
240
245
|
asyncByDefault?: boolean;
|
|
241
246
|
defaultSessionDir?: string;
|
|
247
|
+
maxSubagentDepth?: number;
|
|
248
|
+
worktreeSetupHook?: string;
|
|
249
|
+
worktreeSetupHookTimeoutMs?: number;
|
|
242
250
|
}
|
|
243
251
|
|
|
244
252
|
// ============================================================================
|
|
@@ -291,19 +299,37 @@ export function wrapForkTask(task: string, preamble?: string | false): string {
|
|
|
291
299
|
// Recursion Depth Guard
|
|
292
300
|
// ============================================================================
|
|
293
301
|
|
|
294
|
-
export function
|
|
302
|
+
export function normalizeMaxSubagentDepth(value: unknown): number | undefined {
|
|
303
|
+
const parsed = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
|
|
304
|
+
if (!Number.isInteger(parsed) || parsed < 0) return undefined;
|
|
305
|
+
return parsed;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export function resolveCurrentMaxSubagentDepth(configMaxDepth?: number): number {
|
|
309
|
+
return normalizeMaxSubagentDepth(process.env.PI_SUBAGENT_MAX_DEPTH)
|
|
310
|
+
?? normalizeMaxSubagentDepth(configMaxDepth)
|
|
311
|
+
?? DEFAULT_SUBAGENT_MAX_DEPTH;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export function resolveChildMaxSubagentDepth(parentMaxDepth: number, agentMaxDepth?: number): number {
|
|
315
|
+
const normalizedParent = normalizeMaxSubagentDepth(parentMaxDepth) ?? DEFAULT_SUBAGENT_MAX_DEPTH;
|
|
316
|
+
const normalizedAgent = normalizeMaxSubagentDepth(agentMaxDepth);
|
|
317
|
+
return normalizedAgent === undefined ? normalizedParent : Math.min(normalizedParent, normalizedAgent);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export function checkSubagentDepth(configMaxDepth?: number): { blocked: boolean; depth: number; maxDepth: number } {
|
|
295
321
|
const depth = Number(process.env.PI_SUBAGENT_DEPTH ?? "0");
|
|
296
|
-
const maxDepth =
|
|
297
|
-
const blocked = Number.isFinite(depth) &&
|
|
322
|
+
const maxDepth = resolveCurrentMaxSubagentDepth(configMaxDepth);
|
|
323
|
+
const blocked = Number.isFinite(depth) && depth >= maxDepth;
|
|
298
324
|
return { blocked, depth, maxDepth };
|
|
299
325
|
}
|
|
300
326
|
|
|
301
|
-
export function getSubagentDepthEnv(): Record<string, string> {
|
|
327
|
+
export function getSubagentDepthEnv(maxDepth?: number): Record<string, string> {
|
|
302
328
|
const parentDepth = Number(process.env.PI_SUBAGENT_DEPTH ?? "0");
|
|
303
329
|
const nextDepth = Number.isFinite(parentDepth) ? parentDepth + 1 : 1;
|
|
304
330
|
return {
|
|
305
331
|
PI_SUBAGENT_DEPTH: String(nextDepth),
|
|
306
|
-
PI_SUBAGENT_MAX_DEPTH:
|
|
332
|
+
PI_SUBAGENT_MAX_DEPTH: String(normalizeMaxSubagentDepth(maxDepth) ?? resolveCurrentMaxSubagentDepth()),
|
|
307
333
|
};
|
|
308
334
|
}
|
|
309
335
|
|
package/utils.ts
CHANGED
|
@@ -6,7 +6,7 @@ import * as fs from "node:fs";
|
|
|
6
6
|
import * as os from "node:os";
|
|
7
7
|
import * as path from "node:path";
|
|
8
8
|
import type { Message } from "@mariozechner/pi-ai";
|
|
9
|
-
import type { AsyncStatus, DisplayItem, ErrorInfo } from "./types.
|
|
9
|
+
import type { AsyncStatus, DisplayItem, ErrorInfo, SingleResult } from "./types.ts";
|
|
10
10
|
|
|
11
11
|
// ============================================================================
|
|
12
12
|
// File System Utilities
|
|
@@ -200,6 +200,10 @@ export function getFinalOutput(messages: Message[]): string {
|
|
|
200
200
|
return "";
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
+
export function getSingleResultOutput(result: Pick<SingleResult, "finalOutput" | "messages">): string {
|
|
204
|
+
return result.finalOutput ?? getFinalOutput(result.messages);
|
|
205
|
+
}
|
|
206
|
+
|
|
203
207
|
/**
|
|
204
208
|
* Extract display items (text and tool calls) from messages
|
|
205
209
|
*/
|