pi-subagents 0.13.4 ā 0.14.1
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 +23 -4
- package/README.md +35 -14
- package/agent-management.ts +15 -6
- package/agent-manager-detail.ts +13 -3
- package/agent-manager-edit.ts +75 -23
- package/agent-manager-list.ts +12 -5
- package/agent-manager.ts +199 -11
- package/agents.ts +315 -20
- package/artifacts.ts +11 -5
- package/async-execution.ts +92 -73
- package/chain-clarify.ts +49 -160
- package/chain-execution.ts +38 -76
- package/execution.ts +53 -48
- package/index.ts +1 -1
- package/install.mjs +3 -3
- package/model-fallback.ts +8 -2
- package/package.json +1 -1
- package/parallel-utils.ts +5 -5
- package/prompt-template-bridge.ts +19 -8
- package/render.ts +23 -50
- package/schemas.ts +1 -1
- package/settings.ts +6 -4
- package/single-output.ts +2 -2
- package/skills.ts +165 -75
- package/subagent-executor.ts +52 -18
- package/subagent-runner.ts +171 -54
- package/types.ts +65 -14
- package/utils.ts +52 -21
package/subagent-executor.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { executeChain } from "./chain-execution.ts";
|
|
|
10
10
|
import { resolveExecutionAgentScope } from "./agent-scope.ts";
|
|
11
11
|
import { handleManagementAction } from "./agent-management.ts";
|
|
12
12
|
import { runSync } from "./execution.ts";
|
|
13
|
+
import { resolveModelCandidate } from "./model-fallback.ts";
|
|
13
14
|
import { aggregateParallelOutputs } from "./parallel-utils.ts";
|
|
14
15
|
import { recordRun } from "./run-history.ts";
|
|
15
16
|
import {
|
|
@@ -24,7 +25,7 @@ import { executeAsyncChain, executeAsyncSingle, isAsyncAvailable } from "./async
|
|
|
24
25
|
import { createForkContextResolver } from "./fork-context.ts";
|
|
25
26
|
import { applyIntercomBridgeToAgent, resolveIntercomBridge, resolveIntercomSessionTarget } from "./intercom-bridge.ts";
|
|
26
27
|
import { finalizeSingleOutput, injectSingleOutputInstruction, resolveSingleOutputPath } from "./single-output.ts";
|
|
27
|
-
import { getSingleResultOutput, mapConcurrent } from "./utils.ts";
|
|
28
|
+
import { compactForegroundDetails, getSingleResultOutput, mapConcurrent } from "./utils.ts";
|
|
28
29
|
import {
|
|
29
30
|
cleanupWorktrees,
|
|
30
31
|
createWorktrees,
|
|
@@ -364,7 +365,12 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
364
365
|
};
|
|
365
366
|
}
|
|
366
367
|
const id = randomUUID();
|
|
367
|
-
const asyncCtx = {
|
|
368
|
+
const asyncCtx = {
|
|
369
|
+
pi: deps.pi,
|
|
370
|
+
cwd: ctx.cwd,
|
|
371
|
+
currentSessionId: deps.state.currentSessionId!,
|
|
372
|
+
currentModelProvider: ctx.model?.provider,
|
|
373
|
+
};
|
|
368
374
|
const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map((m) => ({
|
|
369
375
|
provider: m.provider,
|
|
370
376
|
id: m.id,
|
|
@@ -409,6 +415,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
409
415
|
const normalizedSkills = normalizeSkillInput(params.skill);
|
|
410
416
|
const skills = normalizedSkills === false ? [] : normalizedSkills;
|
|
411
417
|
const maxSubagentDepth = resolveChildMaxSubagentDepth(currentMaxSubagentDepth, a.maxSubagentDepth);
|
|
418
|
+
const modelOverride = resolveModelCandidate((params.model as string | undefined) ?? a.model, availableModels, ctx.model?.provider);
|
|
412
419
|
return executeAsyncSingle(id, {
|
|
413
420
|
agent: params.agent!,
|
|
414
421
|
task: params.context === "fork" ? wrapForkTask(params.task!) : params.task!,
|
|
@@ -424,7 +431,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
424
431
|
sessionFile: sessionFileForIndex(0),
|
|
425
432
|
skills,
|
|
426
433
|
output: effectiveOutput,
|
|
427
|
-
modelOverride
|
|
434
|
+
modelOverride,
|
|
428
435
|
maxSubagentDepth,
|
|
429
436
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
430
437
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
@@ -485,7 +492,12 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
485
492
|
};
|
|
486
493
|
}
|
|
487
494
|
const id = randomUUID();
|
|
488
|
-
const asyncCtx = {
|
|
495
|
+
const asyncCtx = {
|
|
496
|
+
pi: deps.pi,
|
|
497
|
+
cwd: ctx.cwd,
|
|
498
|
+
currentSessionId: deps.state.currentSessionId!,
|
|
499
|
+
currentModelProvider: ctx.model?.provider,
|
|
500
|
+
};
|
|
489
501
|
const asyncChain = wrapChainTasksForFork(chainResult.requestedAsync.chain, params.context);
|
|
490
502
|
return executeAsyncChain(id, {
|
|
491
503
|
chain: asyncChain,
|
|
@@ -632,6 +644,7 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
632
644
|
maxSubagentDepth: input.maxSubagentDepths[index],
|
|
633
645
|
modelOverride: input.modelOverrides[index],
|
|
634
646
|
availableModels: input.availableModels,
|
|
647
|
+
preferredModelProvider: input.ctx.model?.provider,
|
|
635
648
|
skills: effectiveSkills === false ? [] : effectiveSkills,
|
|
636
649
|
onUpdate: input.onUpdate
|
|
637
650
|
? (progressUpdate) => {
|
|
@@ -707,13 +720,16 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
707
720
|
if (worktreeTaskCwdError) return buildParallelModeError(worktreeTaskCwdError);
|
|
708
721
|
}
|
|
709
722
|
|
|
723
|
+
const currentProvider = ctx.model?.provider;
|
|
710
724
|
const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map((m) => ({
|
|
711
725
|
provider: m.provider,
|
|
712
726
|
id: m.id,
|
|
713
727
|
fullId: `${m.provider}/${m.id}`,
|
|
714
728
|
}));
|
|
715
729
|
let taskTexts = tasks.map((t) => t.task);
|
|
716
|
-
const modelOverrides: (string | undefined)[] = tasks.map((t) =>
|
|
730
|
+
const modelOverrides: (string | undefined)[] = tasks.map((t, i) =>
|
|
731
|
+
resolveModelCandidate(t.model ?? agentConfigs[i]?.model, availableModels, currentProvider),
|
|
732
|
+
);
|
|
717
733
|
const skillOverrides: (string[] | false | undefined)[] = tasks.map((t) =>
|
|
718
734
|
normalizeSkillInput(t.skill),
|
|
719
735
|
);
|
|
@@ -722,7 +738,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
722
738
|
const behaviors = agentConfigs.map((c, i) =>
|
|
723
739
|
resolveStepBehavior(c, { skills: skillOverrides[i] }),
|
|
724
740
|
);
|
|
725
|
-
const availableSkills = discoverAvailableSkills(ctx.cwd);
|
|
741
|
+
const availableSkills = discoverAvailableSkills(params.cwd ?? ctx.cwd);
|
|
726
742
|
|
|
727
743
|
const result = await ctx.ui.custom<ChainClarifyResult>(
|
|
728
744
|
(tui, theme, _kb, done) =>
|
|
@@ -734,6 +750,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
734
750
|
undefined,
|
|
735
751
|
behaviors,
|
|
736
752
|
availableModels,
|
|
753
|
+
currentProvider,
|
|
737
754
|
availableSkills,
|
|
738
755
|
done,
|
|
739
756
|
"parallel",
|
|
@@ -761,7 +778,12 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
761
778
|
};
|
|
762
779
|
}
|
|
763
780
|
const id = randomUUID();
|
|
764
|
-
const asyncCtx = {
|
|
781
|
+
const asyncCtx = {
|
|
782
|
+
pi: deps.pi,
|
|
783
|
+
cwd: ctx.cwd,
|
|
784
|
+
currentSessionId: deps.state.currentSessionId!,
|
|
785
|
+
currentModelProvider: ctx.model?.provider,
|
|
786
|
+
};
|
|
765
787
|
const parallelTasks = tasks.map((t, i) => ({
|
|
766
788
|
agent: t.agent,
|
|
767
789
|
task: params.context === "fork" ? wrapForkTask(taskTexts[i]!) : taskTexts[i]!,
|
|
@@ -863,12 +885,12 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
863
885
|
|
|
864
886
|
return {
|
|
865
887
|
content: [{ type: "text", text: fullContent }],
|
|
866
|
-
details: {
|
|
888
|
+
details: compactForegroundDetails({
|
|
867
889
|
mode: "parallel",
|
|
868
890
|
results,
|
|
869
891
|
progress: params.includeProgress ? allProgress : undefined,
|
|
870
892
|
artifacts: allArtifactPaths.length ? { dir: artifactsDir, files: allArtifactPaths } : undefined,
|
|
871
|
-
},
|
|
893
|
+
}),
|
|
872
894
|
};
|
|
873
895
|
} finally {
|
|
874
896
|
if (worktreeSetup) cleanupWorktrees(worktreeSetup);
|
|
@@ -901,13 +923,18 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
901
923
|
};
|
|
902
924
|
}
|
|
903
925
|
|
|
926
|
+
const currentProvider = ctx.model?.provider;
|
|
904
927
|
const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map((m) => ({
|
|
905
928
|
provider: m.provider,
|
|
906
929
|
id: m.id,
|
|
907
930
|
fullId: `${m.provider}/${m.id}`,
|
|
908
931
|
}));
|
|
909
932
|
let task = params.task!;
|
|
910
|
-
let modelOverride: string | undefined =
|
|
933
|
+
let modelOverride: string | undefined = resolveModelCandidate(
|
|
934
|
+
(params.model as string | undefined) ?? agentConfig.model,
|
|
935
|
+
availableModels,
|
|
936
|
+
currentProvider,
|
|
937
|
+
);
|
|
911
938
|
let skillOverride: string[] | false | undefined = normalizeSkillInput(params.skill);
|
|
912
939
|
const rawOutput = params.output !== undefined ? params.output : agentConfig.output;
|
|
913
940
|
let effectiveOutput: string | false | undefined = rawOutput === true ? agentConfig.output : (rawOutput as string | false | undefined);
|
|
@@ -916,7 +943,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
916
943
|
|
|
917
944
|
if (params.clarify === true && ctx.hasUI) {
|
|
918
945
|
const behavior = resolveStepBehavior(agentConfig, { output: effectiveOutput, skills: skillOverride });
|
|
919
|
-
const availableSkills = discoverAvailableSkills(ctx.cwd);
|
|
946
|
+
const availableSkills = discoverAvailableSkills(params.cwd ?? ctx.cwd);
|
|
920
947
|
|
|
921
948
|
const result = await ctx.ui.custom<ChainClarifyResult>(
|
|
922
949
|
(tui, theme, _kb, done) =>
|
|
@@ -928,6 +955,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
928
955
|
undefined,
|
|
929
956
|
[behavior],
|
|
930
957
|
availableModels,
|
|
958
|
+
currentProvider,
|
|
931
959
|
availableSkills,
|
|
932
960
|
done,
|
|
933
961
|
"single",
|
|
@@ -954,7 +982,12 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
954
982
|
};
|
|
955
983
|
}
|
|
956
984
|
const id = randomUUID();
|
|
957
|
-
const asyncCtx = {
|
|
985
|
+
const asyncCtx = {
|
|
986
|
+
pi: deps.pi,
|
|
987
|
+
cwd: ctx.cwd,
|
|
988
|
+
currentSessionId: deps.state.currentSessionId!,
|
|
989
|
+
currentModelProvider: ctx.model?.provider,
|
|
990
|
+
};
|
|
958
991
|
return executeAsyncSingle(id, {
|
|
959
992
|
agent: params.agent!,
|
|
960
993
|
task: params.context === "fork" ? wrapForkTask(task) : task,
|
|
@@ -1009,6 +1042,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
1009
1042
|
onUpdate,
|
|
1010
1043
|
modelOverride,
|
|
1011
1044
|
availableModels,
|
|
1045
|
+
preferredModelProvider: currentProvider,
|
|
1012
1046
|
skills: effectiveSkills,
|
|
1013
1047
|
});
|
|
1014
1048
|
recordRun(params.agent!, cleanTask, r.exitCode, r.progressSummary?.durationMs ?? 0);
|
|
@@ -1029,37 +1063,37 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
1029
1063
|
if (r.detached) {
|
|
1030
1064
|
return {
|
|
1031
1065
|
content: [{ type: "text", text: `Detached for intercom coordination: ${params.agent}` }],
|
|
1032
|
-
details: {
|
|
1066
|
+
details: compactForegroundDetails({
|
|
1033
1067
|
mode: "single",
|
|
1034
1068
|
results: [r],
|
|
1035
1069
|
progress: params.includeProgress ? allProgress : undefined,
|
|
1036
1070
|
artifacts: allArtifactPaths.length ? { dir: artifactsDir, files: allArtifactPaths } : undefined,
|
|
1037
1071
|
truncation: r.truncation,
|
|
1038
|
-
},
|
|
1072
|
+
}),
|
|
1039
1073
|
};
|
|
1040
1074
|
}
|
|
1041
1075
|
|
|
1042
1076
|
if (r.exitCode !== 0)
|
|
1043
1077
|
return {
|
|
1044
1078
|
content: [{ type: "text", text: r.error || "Failed" }],
|
|
1045
|
-
details: {
|
|
1079
|
+
details: compactForegroundDetails({
|
|
1046
1080
|
mode: "single",
|
|
1047
1081
|
results: [r],
|
|
1048
1082
|
progress: params.includeProgress ? allProgress : undefined,
|
|
1049
1083
|
artifacts: allArtifactPaths.length ? { dir: artifactsDir, files: allArtifactPaths } : undefined,
|
|
1050
1084
|
truncation: r.truncation,
|
|
1051
|
-
},
|
|
1085
|
+
}),
|
|
1052
1086
|
isError: true,
|
|
1053
1087
|
};
|
|
1054
1088
|
return {
|
|
1055
1089
|
content: [{ type: "text", text: finalizedOutput.displayOutput || "(no output)" }],
|
|
1056
|
-
details: {
|
|
1090
|
+
details: compactForegroundDetails({
|
|
1057
1091
|
mode: "single",
|
|
1058
1092
|
results: [r],
|
|
1059
1093
|
progress: params.includeProgress ? allProgress : undefined,
|
|
1060
1094
|
artifacts: allArtifactPaths.length ? { dir: artifactsDir, files: allArtifactPaths } : undefined,
|
|
1061
1095
|
truncation: r.truncation,
|
|
1062
|
-
},
|
|
1096
|
+
}),
|
|
1063
1097
|
};
|
|
1064
1098
|
}
|
|
1065
1099
|
|
package/subagent-runner.ts
CHANGED
|
@@ -3,6 +3,7 @@ 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 type { Message } from "@mariozechner/pi-ai";
|
|
6
7
|
import { appendJsonl, getArtifactPaths } from "./artifacts.ts";
|
|
7
8
|
import { getPiSpawnCommand } from "./pi-spawn.ts";
|
|
8
9
|
import { captureSingleOutputSnapshot, resolveSingleOutput } from "./single-output.ts";
|
|
@@ -27,6 +28,7 @@ import {
|
|
|
27
28
|
} from "./parallel-utils.ts";
|
|
28
29
|
import { buildPiArgs, cleanupTempDir } from "./pi-args.ts";
|
|
29
30
|
import { formatModelAttemptNote, isRetryableModelFailure } from "./model-fallback.ts";
|
|
31
|
+
import { detectSubagentError, extractTextFromContent, extractToolArgsPreview, getFinalOutput } from "./utils.ts";
|
|
30
32
|
import {
|
|
31
33
|
cleanupWorktrees,
|
|
32
34
|
createWorktrees,
|
|
@@ -123,32 +125,44 @@ function emptyUsage(): Usage {
|
|
|
123
125
|
return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 };
|
|
124
126
|
}
|
|
125
127
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
128
|
+
interface ChildEventContext {
|
|
129
|
+
eventsPath: string;
|
|
130
|
+
runId: string;
|
|
131
|
+
stepIndex: number;
|
|
132
|
+
agent: string;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
interface ChildUsage {
|
|
136
|
+
input?: number;
|
|
137
|
+
inputTokens?: number;
|
|
138
|
+
output?: number;
|
|
139
|
+
outputTokens?: number;
|
|
140
|
+
cacheRead?: number;
|
|
141
|
+
cacheWrite?: number;
|
|
142
|
+
cost?: { total?: number };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
type ChildMessage = Message & {
|
|
146
|
+
model?: string;
|
|
147
|
+
errorMessage?: string;
|
|
148
|
+
usage?: ChildUsage;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
interface ChildEvent {
|
|
152
|
+
type?: string;
|
|
153
|
+
message?: ChildMessage;
|
|
154
|
+
toolName?: string;
|
|
155
|
+
args?: Record<string, unknown>;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
interface RunPiStreamingResult {
|
|
159
|
+
stderr: string;
|
|
160
|
+
exitCode: number | null;
|
|
161
|
+
messages: Message[];
|
|
162
|
+
usage: Usage;
|
|
163
|
+
model?: string;
|
|
164
|
+
error?: string;
|
|
165
|
+
finalOutput: string;
|
|
152
166
|
}
|
|
153
167
|
|
|
154
168
|
function runPiStreaming(
|
|
@@ -159,7 +173,8 @@ function runPiStreaming(
|
|
|
159
173
|
piPackageRoot?: string,
|
|
160
174
|
piArgv1?: string,
|
|
161
175
|
maxSubagentDepth?: number,
|
|
162
|
-
|
|
176
|
+
childEventContext?: ChildEventContext,
|
|
177
|
+
): Promise<RunPiStreamingResult> {
|
|
163
178
|
return new Promise((resolve) => {
|
|
164
179
|
const outputStream = fs.createWriteStream(outputFile, { flags: "w" });
|
|
165
180
|
const spawnEnv = { ...process.env, ...(env ?? {}), ...getSubagentDepthEnv(maxSubagentDepth) };
|
|
@@ -168,29 +183,119 @@ function runPiStreaming(
|
|
|
168
183
|
...(piArgv1 ? { argv1: piArgv1 } : {}),
|
|
169
184
|
});
|
|
170
185
|
const child = spawn(spawnSpec.command, spawnSpec.args, { cwd, stdio: ["ignore", "pipe", "pipe"], env: spawnEnv });
|
|
171
|
-
let stdout = "";
|
|
172
186
|
let stderr = "";
|
|
187
|
+
let stdoutBuf = "";
|
|
188
|
+
let stderrBuf = "";
|
|
189
|
+
const messages: Message[] = [];
|
|
190
|
+
const usage = emptyUsage();
|
|
191
|
+
let model: string | undefined;
|
|
192
|
+
let error: string | undefined;
|
|
193
|
+
const rawStdoutLines: string[] = [];
|
|
194
|
+
|
|
195
|
+
const writeOutputLine = (line: string) => {
|
|
196
|
+
if (!line.trim()) return;
|
|
197
|
+
outputStream.write(`${line}\n`);
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const writeOutputText = (text: string) => {
|
|
201
|
+
for (const line of text.split("\n")) {
|
|
202
|
+
writeOutputLine(line);
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const appendChildEvent = (event: Record<string, unknown>) => {
|
|
207
|
+
if (!childEventContext) return;
|
|
208
|
+
appendJsonl(childEventContext.eventsPath, JSON.stringify({
|
|
209
|
+
...event,
|
|
210
|
+
subagentSource: "child",
|
|
211
|
+
subagentRunId: childEventContext.runId,
|
|
212
|
+
subagentStepIndex: childEventContext.stepIndex,
|
|
213
|
+
subagentAgent: childEventContext.agent,
|
|
214
|
+
observedAt: Date.now(),
|
|
215
|
+
}));
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const appendChildLine = (type: "subagent.child.stdout" | "subagent.child.stderr", line: string) => {
|
|
219
|
+
appendChildEvent({ type, line });
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const processStdoutLine = (line: string) => {
|
|
223
|
+
if (!line.trim()) return;
|
|
224
|
+
let event: ChildEvent;
|
|
225
|
+
try {
|
|
226
|
+
event = JSON.parse(line) as ChildEvent;
|
|
227
|
+
} catch {
|
|
228
|
+
rawStdoutLines.push(line);
|
|
229
|
+
writeOutputLine(line);
|
|
230
|
+
appendChildLine("subagent.child.stdout", line);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
appendChildEvent(event);
|
|
235
|
+
|
|
236
|
+
if (event.type === "tool_execution_start" && event.toolName) {
|
|
237
|
+
const toolArgs = extractToolArgsPreview(event.args ?? {});
|
|
238
|
+
writeOutputLine(toolArgs ? `${event.toolName}: ${toolArgs}` : event.toolName);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if ((event.type === "message_end" || event.type === "tool_result_end") && event.message) {
|
|
243
|
+
messages.push(event.message);
|
|
244
|
+
const text = extractTextFromContent(event.message.content);
|
|
245
|
+
if (text) writeOutputText(text);
|
|
246
|
+
|
|
247
|
+
if (event.type !== "message_end" || event.message.role !== "assistant") return;
|
|
248
|
+
if (event.message.model) model = event.message.model;
|
|
249
|
+
if (event.message.errorMessage) error = event.message.errorMessage;
|
|
250
|
+
const eventUsage = event.message.usage;
|
|
251
|
+
if (!eventUsage) return;
|
|
252
|
+
usage.turns++;
|
|
253
|
+
usage.input += eventUsage.input ?? eventUsage.inputTokens ?? 0;
|
|
254
|
+
usage.output += eventUsage.output ?? eventUsage.outputTokens ?? 0;
|
|
255
|
+
usage.cacheRead += eventUsage.cacheRead ?? 0;
|
|
256
|
+
usage.cacheWrite += eventUsage.cacheWrite ?? 0;
|
|
257
|
+
usage.cost += eventUsage.cost?.total ?? 0;
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const processStderrText = (text: string) => {
|
|
262
|
+
stderr += text;
|
|
263
|
+
stderrBuf += text;
|
|
264
|
+
outputStream.write(text);
|
|
265
|
+
if (!childEventContext) return;
|
|
266
|
+
const lines = stderrBuf.split("\n");
|
|
267
|
+
stderrBuf = lines.pop() || "";
|
|
268
|
+
for (const line of lines) {
|
|
269
|
+
if (!line.trim()) continue;
|
|
270
|
+
appendChildLine("subagent.child.stderr", line);
|
|
271
|
+
}
|
|
272
|
+
};
|
|
173
273
|
|
|
174
274
|
child.stdout.on("data", (chunk: Buffer) => {
|
|
175
275
|
const text = chunk.toString();
|
|
176
|
-
|
|
177
|
-
|
|
276
|
+
stdoutBuf += text;
|
|
277
|
+
const lines = stdoutBuf.split("\n");
|
|
278
|
+
stdoutBuf = lines.pop() || "";
|
|
279
|
+
for (const line of lines) processStdoutLine(line);
|
|
178
280
|
});
|
|
179
281
|
|
|
180
282
|
child.stderr.on("data", (chunk: Buffer) => {
|
|
181
|
-
|
|
182
|
-
stderr += text;
|
|
183
|
-
outputStream.write(text);
|
|
283
|
+
processStderrText(chunk.toString());
|
|
184
284
|
});
|
|
185
285
|
|
|
186
286
|
child.on("close", (exitCode) => {
|
|
287
|
+
if (stdoutBuf.trim()) processStdoutLine(stdoutBuf);
|
|
288
|
+
if (stderrBuf.trim()) appendChildLine("subagent.child.stderr", stderrBuf);
|
|
187
289
|
outputStream.end();
|
|
188
|
-
|
|
290
|
+
const finalOutput = getFinalOutput(messages) || rawStdoutLines.join("\n").trim();
|
|
291
|
+
resolve({ stderr, exitCode, messages, usage, model, error, finalOutput });
|
|
189
292
|
});
|
|
190
293
|
|
|
191
|
-
child.on("error", () => {
|
|
294
|
+
child.on("error", (spawnError) => {
|
|
192
295
|
outputStream.end();
|
|
193
|
-
|
|
296
|
+
const finalOutput = getFinalOutput(messages) || rawStdoutLines.join("\n").trim();
|
|
297
|
+
const spawnErrorMessage = spawnError instanceof Error ? spawnError.message : String(spawnError);
|
|
298
|
+
resolve({ stderr, exitCode: 1, messages, usage, model, error: error ?? spawnErrorMessage, finalOutput });
|
|
194
299
|
});
|
|
195
300
|
});
|
|
196
301
|
}
|
|
@@ -386,14 +491,13 @@ async function runSingleStep(
|
|
|
386
491
|
const attemptedModels: string[] = [];
|
|
387
492
|
const modelAttempts: ModelAttempt[] = [];
|
|
388
493
|
const attemptNotes: string[] = [];
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
| undefined;
|
|
494
|
+
const eventsPath = path.join(path.dirname(ctx.outputFile), "events.jsonl");
|
|
495
|
+
let finalResult: RunPiStreamingResult | undefined;
|
|
392
496
|
|
|
393
497
|
for (let index = 0; index < candidates.length; index++) {
|
|
394
498
|
const candidate = candidates[index];
|
|
395
499
|
const { args, env, tempDir } = buildPiArgs({
|
|
396
|
-
baseArgs: ["-p"],
|
|
500
|
+
baseArgs: ["--mode", "json", "-p"],
|
|
397
501
|
task,
|
|
398
502
|
sessionEnabled,
|
|
399
503
|
sessionDir,
|
|
@@ -406,28 +510,41 @@ async function runSingleStep(
|
|
|
406
510
|
mcpDirectTools: step.mcpDirectTools,
|
|
407
511
|
promptFileStem: step.agent,
|
|
408
512
|
});
|
|
409
|
-
const
|
|
410
|
-
|
|
513
|
+
const run = await runPiStreaming(
|
|
514
|
+
args,
|
|
515
|
+
step.cwd ?? ctx.cwd,
|
|
516
|
+
ctx.outputFile,
|
|
517
|
+
env,
|
|
518
|
+
ctx.piPackageRoot,
|
|
519
|
+
ctx.piArgv1,
|
|
520
|
+
step.maxSubagentDepth,
|
|
521
|
+
{ eventsPath, runId: ctx.id, stepIndex: ctx.flatIndex, agent: step.agent },
|
|
522
|
+
);
|
|
411
523
|
cleanupTempDir(tempDir);
|
|
412
524
|
|
|
413
|
-
const
|
|
414
|
-
const
|
|
525
|
+
const hiddenError = run.exitCode === 0 && !run.error ? detectSubagentError(run.messages) : null;
|
|
526
|
+
const effectiveExitCode = hiddenError?.hasError ? (hiddenError.exitCode ?? 1) : run.exitCode;
|
|
527
|
+
const error = hiddenError?.hasError
|
|
528
|
+
? hiddenError.details
|
|
529
|
+
? `${hiddenError.errorType} failed (exit ${effectiveExitCode}): ${hiddenError.details}`
|
|
530
|
+
: `${hiddenError.errorType} failed with exit code ${effectiveExitCode}`
|
|
531
|
+
: run.error || (run.exitCode !== 0 && run.stderr.trim() ? run.stderr.trim() : undefined);
|
|
415
532
|
const attempt: ModelAttempt = {
|
|
416
|
-
model: candidate ??
|
|
417
|
-
success:
|
|
418
|
-
exitCode:
|
|
533
|
+
model: candidate ?? run.model ?? step.model ?? "default",
|
|
534
|
+
success: effectiveExitCode === 0 && !error,
|
|
535
|
+
exitCode: effectiveExitCode,
|
|
419
536
|
error,
|
|
420
|
-
usage:
|
|
537
|
+
usage: run.usage,
|
|
421
538
|
};
|
|
422
539
|
modelAttempts.push(attempt);
|
|
423
540
|
if (candidate) attemptedModels.push(candidate);
|
|
424
|
-
finalResult = { ...run,
|
|
541
|
+
finalResult = { ...run, exitCode: effectiveExitCode, model: candidate ?? run.model, error };
|
|
425
542
|
if (attempt.success) break;
|
|
426
543
|
if (!isRetryableModelFailure(error) || index === candidates.length - 1) break;
|
|
427
544
|
attemptNotes.push(formatModelAttemptNote(attempt, candidates[index + 1]));
|
|
428
545
|
}
|
|
429
546
|
|
|
430
|
-
const rawOutput =
|
|
547
|
+
const rawOutput = finalResult?.finalOutput ?? "";
|
|
431
548
|
const resolvedOutput = step.outputPath && finalResult?.exitCode === 0
|
|
432
549
|
? resolveSingleOutput(step.outputPath, rawOutput, outputSnapshot)
|
|
433
550
|
: { fullOutput: rawOutput };
|
|
@@ -438,12 +555,12 @@ async function runSingleStep(
|
|
|
438
555
|
}
|
|
439
556
|
if (resolvedOutput.savedPath) {
|
|
440
557
|
outputForSummary = outputForSummary
|
|
441
|
-
? `${outputForSummary}\n\
|
|
442
|
-
:
|
|
558
|
+
? `${outputForSummary}\n\nOutput saved to: ${resolvedOutput.savedPath}`
|
|
559
|
+
: `Output saved to: ${resolvedOutput.savedPath}`;
|
|
443
560
|
} else if (resolvedOutput.saveError && step.outputPath && finalResult?.exitCode === 0) {
|
|
444
561
|
outputForSummary = outputForSummary
|
|
445
|
-
? `${outputForSummary}\n\
|
|
446
|
-
:
|
|
562
|
+
? `${outputForSummary}\n\nFailed to save output to: ${step.outputPath}\n${resolvedOutput.saveError}`
|
|
563
|
+
: `Failed to save output to: ${step.outputPath}\n${resolvedOutput.saveError}`;
|
|
447
564
|
}
|
|
448
565
|
|
|
449
566
|
if (artifactPaths && ctx.artifactConfig?.enabled !== false) {
|
package/types.ts
CHANGED
|
@@ -40,17 +40,6 @@ export interface TokenUsage {
|
|
|
40
40
|
total: number;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
// ============================================================================
|
|
44
|
-
// Skills
|
|
45
|
-
// ============================================================================
|
|
46
|
-
|
|
47
|
-
export interface ResolvedSkill {
|
|
48
|
-
name: string;
|
|
49
|
-
path: string;
|
|
50
|
-
content: string;
|
|
51
|
-
source: "project" | "user";
|
|
52
|
-
}
|
|
53
|
-
|
|
54
43
|
// ============================================================================
|
|
55
44
|
// Progress Tracking
|
|
56
45
|
// ============================================================================
|
|
@@ -96,7 +85,7 @@ export interface SingleResult {
|
|
|
96
85
|
exitCode: number;
|
|
97
86
|
detached?: boolean;
|
|
98
87
|
detachedReason?: string;
|
|
99
|
-
messages
|
|
88
|
+
messages?: Message[];
|
|
100
89
|
usage: Usage;
|
|
101
90
|
model?: string;
|
|
102
91
|
attemptedModels?: string[];
|
|
@@ -271,6 +260,8 @@ export interface RunSyncOptions {
|
|
|
271
260
|
modelOverride?: string;
|
|
272
261
|
/** Registry models available for heuristic bare-model resolution */
|
|
273
262
|
availableModels?: Array<{ provider: string; id: string; fullId: string }>;
|
|
263
|
+
/** Current parent-session provider to prefer for ambiguous bare model ids */
|
|
264
|
+
preferredModelProvider?: string;
|
|
274
265
|
/** Skills to inject (overrides agent default if provided) */
|
|
275
266
|
skills?: string[];
|
|
276
267
|
}
|
|
@@ -309,10 +300,66 @@ export const DEFAULT_ARTIFACT_CONFIG: ArtifactConfig = {
|
|
|
309
300
|
cleanupDays: 7,
|
|
310
301
|
};
|
|
311
302
|
|
|
303
|
+
function sanitizeTempScopeSegment(value: string): string {
|
|
304
|
+
const sanitized = value
|
|
305
|
+
.trim()
|
|
306
|
+
.replace(/[^A-Za-z0-9._-]+/g, "-")
|
|
307
|
+
.replace(/^-+|-+$/g, "");
|
|
308
|
+
return sanitized || "unknown";
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export function resolveTempScopeId(options?: {
|
|
312
|
+
env?: NodeJS.ProcessEnv;
|
|
313
|
+
getuid?: (() => number) | undefined;
|
|
314
|
+
userInfo?: (() => { username?: string | null }) | undefined;
|
|
315
|
+
homedir?: (() => string) | undefined;
|
|
316
|
+
}): string {
|
|
317
|
+
const env = options?.env ?? process.env;
|
|
318
|
+
const getuid = options && Object.hasOwn(options, "getuid")
|
|
319
|
+
? options.getuid
|
|
320
|
+
: process.getuid?.bind(process);
|
|
321
|
+
if (typeof getuid === "function") {
|
|
322
|
+
return `uid-${getuid()}`;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
for (const key of ["USERNAME", "USER", "LOGNAME"] as const) {
|
|
326
|
+
const value = env[key];
|
|
327
|
+
if (value) return `user-${sanitizeTempScopeSegment(value)}`;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const userInfo = options && Object.hasOwn(options, "userInfo")
|
|
331
|
+
? options.userInfo
|
|
332
|
+
: os.userInfo;
|
|
333
|
+
try {
|
|
334
|
+
const username = userInfo?.().username;
|
|
335
|
+
if (username) return `user-${sanitizeTempScopeSegment(username)}`;
|
|
336
|
+
} catch {
|
|
337
|
+
// Fall through to home-directory-based scoping.
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const homedir = env.USERPROFILE ?? env.HOME;
|
|
341
|
+
if (homedir) return `home-${sanitizeTempScopeSegment(homedir)}`;
|
|
342
|
+
|
|
343
|
+
const resolveHomedir = options && Object.hasOwn(options, "homedir")
|
|
344
|
+
? options.homedir
|
|
345
|
+
: os.homedir;
|
|
346
|
+
try {
|
|
347
|
+
const fallbackHomedir = resolveHomedir?.();
|
|
348
|
+
if (fallbackHomedir) return `home-${sanitizeTempScopeSegment(fallbackHomedir)}`;
|
|
349
|
+
} catch {
|
|
350
|
+
// Fall through to the last-resort shared scope.
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return "shared";
|
|
354
|
+
}
|
|
355
|
+
|
|
312
356
|
export const MAX_PARALLEL = 8;
|
|
313
357
|
export const MAX_CONCURRENCY = 4;
|
|
314
|
-
export const
|
|
315
|
-
export const
|
|
358
|
+
export const TEMP_ROOT_DIR = path.join(os.tmpdir(), `pi-subagents-${resolveTempScopeId()}`);
|
|
359
|
+
export const RESULTS_DIR = path.join(TEMP_ROOT_DIR, "async-subagent-results");
|
|
360
|
+
export const ASYNC_DIR = path.join(TEMP_ROOT_DIR, "async-subagent-runs");
|
|
361
|
+
export const CHAIN_RUNS_DIR = path.join(TEMP_ROOT_DIR, "chain-runs");
|
|
362
|
+
export const TEMP_ARTIFACTS_DIR = path.join(TEMP_ROOT_DIR, "artifacts");
|
|
316
363
|
export const WIDGET_KEY = "subagent-async";
|
|
317
364
|
export const SLASH_RESULT_TYPE = "subagent-slash-result";
|
|
318
365
|
export const SLASH_SUBAGENT_REQUEST_EVENT = "subagent:slash:request";
|
|
@@ -329,6 +376,10 @@ export const DEFAULT_FORK_PREAMBLE =
|
|
|
329
376
|
"Your sole job is to execute the task below. Do not continue or respond to the prior conversation " +
|
|
330
377
|
"ā focus exclusively on completing this task using your tools.";
|
|
331
378
|
|
|
379
|
+
export function getAsyncConfigPath(suffix: string): string {
|
|
380
|
+
return path.join(TEMP_ROOT_DIR, `async-cfg-${suffix}.json`);
|
|
381
|
+
}
|
|
382
|
+
|
|
332
383
|
export function wrapForkTask(task: string, preamble?: string | false): string {
|
|
333
384
|
if (preamble === false) return task;
|
|
334
385
|
const effectivePreamble = preamble ?? DEFAULT_FORK_PREAMBLE;
|