pi-subagents 0.12.4 → 0.13.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 +15 -0
- package/README.md +22 -4
- package/agent-management.ts +27 -0
- package/agent-manager-edit.ts +4 -2
- package/agent-serializer.ts +3 -0
- package/agents.ts +6 -0
- package/async-execution.ts +15 -2
- package/async-status.ts +7 -0
- package/chain-execution.ts +7 -21
- package/execution.ts +203 -119
- package/model-fallback.ts +100 -0
- package/package.json +1 -1
- package/parallel-utils.ts +3 -0
- package/pi-args.ts +1 -4
- package/render.ts +6 -0
- package/slash-commands.ts +6 -1
- package/subagent-executor.ts +30 -12
- package/subagent-runner.ts +163 -32
- package/subagents-status.ts +8 -1
- package/types.ts +23 -1
package/subagent-executor.ts
CHANGED
|
@@ -364,6 +364,11 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
364
364
|
}
|
|
365
365
|
const id = randomUUID();
|
|
366
366
|
const asyncCtx = { pi: deps.pi, cwd: ctx.cwd, currentSessionId: deps.state.currentSessionId! };
|
|
367
|
+
const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map((m) => ({
|
|
368
|
+
provider: m.provider,
|
|
369
|
+
id: m.id,
|
|
370
|
+
fullId: `${m.provider}/${m.id}`,
|
|
371
|
+
}));
|
|
367
372
|
const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
|
|
368
373
|
|
|
369
374
|
if (hasChain && params.chain) {
|
|
@@ -374,6 +379,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
374
379
|
chain,
|
|
375
380
|
agents,
|
|
376
381
|
ctx: asyncCtx,
|
|
382
|
+
availableModels,
|
|
377
383
|
cwd: params.cwd,
|
|
378
384
|
maxOutput: params.maxOutput,
|
|
379
385
|
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
@@ -407,6 +413,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
407
413
|
task: params.context === "fork" ? wrapForkTask(params.task!) : params.task!,
|
|
408
414
|
agentConfig: a,
|
|
409
415
|
ctx: asyncCtx,
|
|
416
|
+
availableModels,
|
|
410
417
|
cwd: params.cwd,
|
|
411
418
|
maxOutput: params.maxOutput,
|
|
412
419
|
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
@@ -416,6 +423,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
416
423
|
sessionFile: sessionFileForIndex(0),
|
|
417
424
|
skills,
|
|
418
425
|
output: effectiveOutput,
|
|
426
|
+
modelOverride: params.model as string | undefined,
|
|
419
427
|
maxSubagentDepth,
|
|
420
428
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
421
429
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
@@ -482,6 +490,11 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
482
490
|
chain: asyncChain,
|
|
483
491
|
agents,
|
|
484
492
|
ctx: asyncCtx,
|
|
493
|
+
availableModels: ctx.modelRegistry.getAvailable().map((m) => ({
|
|
494
|
+
provider: m.provider,
|
|
495
|
+
id: m.id,
|
|
496
|
+
fullId: `${m.provider}/${m.id}`,
|
|
497
|
+
})),
|
|
485
498
|
cwd: params.cwd,
|
|
486
499
|
maxOutput: params.maxOutput,
|
|
487
500
|
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
@@ -514,6 +527,7 @@ interface ForegroundParallelRunInput {
|
|
|
514
527
|
maxOutput?: MaxOutputConfig;
|
|
515
528
|
paramsCwd?: string;
|
|
516
529
|
maxSubagentDepths: number[];
|
|
530
|
+
availableModels: ModelInfo[];
|
|
517
531
|
modelOverrides: (string | undefined)[];
|
|
518
532
|
skillOverrides: (string[] | false | undefined)[];
|
|
519
533
|
behaviors: Array<ReturnType<typeof resolveStepBehavior>>;
|
|
@@ -616,6 +630,7 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
616
630
|
maxOutput: input.maxOutput,
|
|
617
631
|
maxSubagentDepth: input.maxSubagentDepths[index],
|
|
618
632
|
modelOverride: input.modelOverrides[index],
|
|
633
|
+
availableModels: input.availableModels,
|
|
619
634
|
skills: effectiveSkills === false ? [] : effectiveSkills,
|
|
620
635
|
onUpdate: input.onUpdate
|
|
621
636
|
? (progressUpdate) => {
|
|
@@ -691,6 +706,11 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
691
706
|
if (worktreeTaskCwdError) return buildParallelModeError(worktreeTaskCwdError);
|
|
692
707
|
}
|
|
693
708
|
|
|
709
|
+
const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map((m) => ({
|
|
710
|
+
provider: m.provider,
|
|
711
|
+
id: m.id,
|
|
712
|
+
fullId: `${m.provider}/${m.id}`,
|
|
713
|
+
}));
|
|
694
714
|
let taskTexts = tasks.map((t) => t.task);
|
|
695
715
|
const modelOverrides: (string | undefined)[] = tasks.map((t) => t.model);
|
|
696
716
|
const skillOverrides: (string[] | false | undefined)[] = tasks.map((t) =>
|
|
@@ -698,12 +718,6 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
698
718
|
);
|
|
699
719
|
|
|
700
720
|
if (params.clarify === true && ctx.hasUI) {
|
|
701
|
-
const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map((m) => ({
|
|
702
|
-
provider: m.provider,
|
|
703
|
-
id: m.id,
|
|
704
|
-
fullId: `${m.provider}/${m.id}`,
|
|
705
|
-
}));
|
|
706
|
-
|
|
707
721
|
const behaviors = agentConfigs.map((c, i) =>
|
|
708
722
|
resolveStepBehavior(c, { skills: skillOverrides[i] }),
|
|
709
723
|
);
|
|
@@ -758,6 +772,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
758
772
|
chain: [{ parallel: parallelTasks, worktree: params.worktree }],
|
|
759
773
|
agents,
|
|
760
774
|
ctx: asyncCtx,
|
|
775
|
+
availableModels,
|
|
761
776
|
cwd: params.cwd,
|
|
762
777
|
maxOutput: params.maxOutput,
|
|
763
778
|
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
@@ -807,6 +822,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
807
822
|
artifactsDir,
|
|
808
823
|
maxOutput: params.maxOutput,
|
|
809
824
|
paramsCwd: params.cwd,
|
|
825
|
+
availableModels,
|
|
810
826
|
modelOverrides,
|
|
811
827
|
skillOverrides,
|
|
812
828
|
behaviors,
|
|
@@ -884,6 +900,11 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
884
900
|
};
|
|
885
901
|
}
|
|
886
902
|
|
|
903
|
+
const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map((m) => ({
|
|
904
|
+
provider: m.provider,
|
|
905
|
+
id: m.id,
|
|
906
|
+
fullId: `${m.provider}/${m.id}`,
|
|
907
|
+
}));
|
|
887
908
|
let task = params.task!;
|
|
888
909
|
let modelOverride: string | undefined = params.model as string | undefined;
|
|
889
910
|
let skillOverride: string[] | false | undefined = normalizeSkillInput(params.skill);
|
|
@@ -893,12 +914,6 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
893
914
|
const maxSubagentDepth = resolveChildMaxSubagentDepth(currentMaxSubagentDepth, agentConfig.maxSubagentDepth);
|
|
894
915
|
|
|
895
916
|
if (params.clarify === true && ctx.hasUI) {
|
|
896
|
-
const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map((m) => ({
|
|
897
|
-
provider: m.provider,
|
|
898
|
-
id: m.id,
|
|
899
|
-
fullId: `${m.provider}/${m.id}`,
|
|
900
|
-
}));
|
|
901
|
-
|
|
902
917
|
const behavior = resolveStepBehavior(agentConfig, { output: effectiveOutput, skills: skillOverride });
|
|
903
918
|
const availableSkills = discoverAvailableSkills(ctx.cwd);
|
|
904
919
|
|
|
@@ -944,6 +959,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
944
959
|
task: params.context === "fork" ? wrapForkTask(task) : task,
|
|
945
960
|
agentConfig,
|
|
946
961
|
ctx: asyncCtx,
|
|
962
|
+
availableModels,
|
|
947
963
|
cwd: params.cwd,
|
|
948
964
|
maxOutput: params.maxOutput,
|
|
949
965
|
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
@@ -953,6 +969,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
953
969
|
sessionFile: sessionFileForIndex(0),
|
|
954
970
|
skills: skillOverride === false ? [] : skillOverride,
|
|
955
971
|
output: effectiveOutput,
|
|
972
|
+
modelOverride,
|
|
956
973
|
maxSubagentDepth,
|
|
957
974
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
958
975
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
@@ -988,6 +1005,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
988
1005
|
maxSubagentDepth,
|
|
989
1006
|
onUpdate,
|
|
990
1007
|
modelOverride,
|
|
1008
|
+
availableModels,
|
|
991
1009
|
skills: effectiveSkills,
|
|
992
1010
|
});
|
|
993
1011
|
recordRun(params.agent!, cleanTask, r.exitCode, r.progressSummary?.durationMs ?? 0);
|
package/subagent-runner.ts
CHANGED
|
@@ -9,6 +9,8 @@ import { captureSingleOutputSnapshot, resolveSingleOutput } from "./single-outpu
|
|
|
9
9
|
import {
|
|
10
10
|
type ArtifactConfig,
|
|
11
11
|
type ArtifactPaths,
|
|
12
|
+
type ModelAttempt,
|
|
13
|
+
type Usage,
|
|
12
14
|
DEFAULT_MAX_OUTPUT,
|
|
13
15
|
type MaxOutputConfig,
|
|
14
16
|
truncateOutput,
|
|
@@ -24,6 +26,7 @@ import {
|
|
|
24
26
|
MAX_PARALLEL_CONCURRENCY,
|
|
25
27
|
} from "./parallel-utils.ts";
|
|
26
28
|
import { buildPiArgs, cleanupTempDir } from "./pi-args.ts";
|
|
29
|
+
import { formatModelAttemptNote, isRetryableModelFailure } from "./model-fallback.ts";
|
|
27
30
|
import {
|
|
28
31
|
cleanupWorktrees,
|
|
29
32
|
createWorktrees,
|
|
@@ -59,6 +62,9 @@ interface StepResult {
|
|
|
59
62
|
output: string;
|
|
60
63
|
success: boolean;
|
|
61
64
|
skipped?: boolean;
|
|
65
|
+
model?: string;
|
|
66
|
+
attemptedModels?: string[];
|
|
67
|
+
modelAttempts?: ModelAttempt[];
|
|
62
68
|
artifactPaths?: ArtifactPaths;
|
|
63
69
|
truncated?: boolean;
|
|
64
70
|
}
|
|
@@ -112,6 +118,38 @@ function parseSessionTokens(sessionDir: string): TokenUsage | null {
|
|
|
112
118
|
}
|
|
113
119
|
}
|
|
114
120
|
|
|
121
|
+
function emptyUsage(): Usage {
|
|
122
|
+
return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function parseRunOutput(output: string): { usage: Usage; model?: string; error?: string } {
|
|
126
|
+
const usage = emptyUsage();
|
|
127
|
+
let model: string | undefined;
|
|
128
|
+
let error: string | undefined;
|
|
129
|
+
for (const line of output.split("\n")) {
|
|
130
|
+
if (!line.trim()) continue;
|
|
131
|
+
try {
|
|
132
|
+
const evt = JSON.parse(line) as { type?: string; message?: { role?: string; model?: string; errorMessage?: string; usage?: any } };
|
|
133
|
+
if (evt.type !== "message_end" || evt.message?.role !== "assistant") continue;
|
|
134
|
+
const msg = evt.message;
|
|
135
|
+
if (msg.model) model = msg.model;
|
|
136
|
+
if (msg.errorMessage) error = msg.errorMessage;
|
|
137
|
+
const u = msg.usage;
|
|
138
|
+
if (u) {
|
|
139
|
+
usage.turns++;
|
|
140
|
+
usage.input += u.input ?? u.inputTokens ?? 0;
|
|
141
|
+
usage.output += u.output ?? u.outputTokens ?? 0;
|
|
142
|
+
usage.cacheRead += u.cacheRead ?? 0;
|
|
143
|
+
usage.cacheWrite += u.cacheWrite ?? 0;
|
|
144
|
+
usage.cost += u.cost?.total ?? 0;
|
|
145
|
+
}
|
|
146
|
+
} catch {
|
|
147
|
+
// Ignore malformed stdout lines.
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return { usage, model, error };
|
|
151
|
+
}
|
|
152
|
+
|
|
115
153
|
function runPiStreaming(
|
|
116
154
|
args: string[],
|
|
117
155
|
cwd: string,
|
|
@@ -119,13 +157,14 @@ function runPiStreaming(
|
|
|
119
157
|
env?: Record<string, string | undefined>,
|
|
120
158
|
piPackageRoot?: string,
|
|
121
159
|
maxSubagentDepth?: number,
|
|
122
|
-
): Promise<{ stdout: string; exitCode: number | null }> {
|
|
160
|
+
): Promise<{ stdout: string; stderr: string; exitCode: number | null }> {
|
|
123
161
|
return new Promise((resolve) => {
|
|
124
162
|
const outputStream = fs.createWriteStream(outputFile, { flags: "w" });
|
|
125
163
|
const spawnEnv = { ...process.env, ...(env ?? {}), ...getSubagentDepthEnv(maxSubagentDepth) };
|
|
126
164
|
const spawnSpec = getPiSpawnCommand(args, piPackageRoot ? { piPackageRoot } : undefined);
|
|
127
165
|
const child = spawn(spawnSpec.command, spawnSpec.args, { cwd, stdio: ["ignore", "pipe", "pipe"], env: spawnEnv });
|
|
128
166
|
let stdout = "";
|
|
167
|
+
let stderr = "";
|
|
129
168
|
|
|
130
169
|
child.stdout.on("data", (chunk: Buffer) => {
|
|
131
170
|
const text = chunk.toString();
|
|
@@ -134,17 +173,19 @@ function runPiStreaming(
|
|
|
134
173
|
});
|
|
135
174
|
|
|
136
175
|
child.stderr.on("data", (chunk: Buffer) => {
|
|
137
|
-
|
|
176
|
+
const text = chunk.toString();
|
|
177
|
+
stderr += text;
|
|
178
|
+
outputStream.write(text);
|
|
138
179
|
});
|
|
139
180
|
|
|
140
181
|
child.on("close", (exitCode) => {
|
|
141
182
|
outputStream.end();
|
|
142
|
-
resolve({ stdout, exitCode });
|
|
183
|
+
resolve({ stdout, stderr, exitCode });
|
|
143
184
|
});
|
|
144
185
|
|
|
145
186
|
child.on("error", () => {
|
|
146
187
|
outputStream.end();
|
|
147
|
-
resolve({ stdout, exitCode: 1 });
|
|
188
|
+
resolve({ stdout, stderr, exitCode: 1 });
|
|
148
189
|
});
|
|
149
190
|
});
|
|
150
191
|
}
|
|
@@ -305,26 +346,21 @@ interface SingleStepContext {
|
|
|
305
346
|
async function runSingleStep(
|
|
306
347
|
step: SubagentStep,
|
|
307
348
|
ctx: SingleStepContext,
|
|
308
|
-
): Promise<{
|
|
349
|
+
): Promise<{
|
|
350
|
+
agent: string;
|
|
351
|
+
output: string;
|
|
352
|
+
exitCode: number | null;
|
|
353
|
+
error?: string;
|
|
354
|
+
model?: string;
|
|
355
|
+
attemptedModels?: string[];
|
|
356
|
+
modelAttempts?: ModelAttempt[];
|
|
357
|
+
artifactPaths?: ArtifactPaths;
|
|
358
|
+
}> {
|
|
309
359
|
const placeholderRegex = new RegExp(ctx.placeholder.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g");
|
|
310
360
|
const task = step.task.replace(placeholderRegex, () => ctx.previousOutput);
|
|
311
361
|
const sessionEnabled = Boolean(step.sessionFile) || ctx.sessionEnabled;
|
|
312
362
|
const sessionDir = step.sessionFile ? undefined : ctx.sessionDir;
|
|
313
363
|
const outputSnapshot = captureSingleOutputSnapshot(step.outputPath);
|
|
314
|
-
const { args, env, tempDir } = buildPiArgs({
|
|
315
|
-
baseArgs: ["-p"],
|
|
316
|
-
task,
|
|
317
|
-
sessionEnabled,
|
|
318
|
-
sessionDir,
|
|
319
|
-
sessionFile: step.sessionFile,
|
|
320
|
-
model: step.model,
|
|
321
|
-
tools: step.tools,
|
|
322
|
-
extensions: step.extensions,
|
|
323
|
-
skills: step.skills,
|
|
324
|
-
systemPrompt: step.systemPrompt,
|
|
325
|
-
mcpDirectTools: step.mcpDirectTools,
|
|
326
|
-
promptFileStem: step.agent,
|
|
327
|
-
});
|
|
328
364
|
|
|
329
365
|
let artifactPaths: ArtifactPaths | undefined;
|
|
330
366
|
if (ctx.artifactsDir && ctx.artifactConfig?.enabled !== false) {
|
|
@@ -336,22 +372,71 @@ async function runSingleStep(
|
|
|
336
372
|
}
|
|
337
373
|
}
|
|
338
374
|
|
|
339
|
-
const
|
|
340
|
-
|
|
375
|
+
const candidates = step.modelCandidates && step.modelCandidates.length > 0
|
|
376
|
+
? step.modelCandidates
|
|
377
|
+
: step.model
|
|
378
|
+
? [step.model]
|
|
379
|
+
: [undefined];
|
|
380
|
+
const attemptedModels: string[] = [];
|
|
381
|
+
const modelAttempts: ModelAttempt[] = [];
|
|
382
|
+
const attemptNotes: string[] = [];
|
|
383
|
+
let finalResult:
|
|
384
|
+
| { stdout: string; stderr: string; exitCode: number | null; usage: Usage; model?: string; error?: string }
|
|
385
|
+
| undefined;
|
|
386
|
+
|
|
387
|
+
for (let index = 0; index < candidates.length; index++) {
|
|
388
|
+
const candidate = candidates[index];
|
|
389
|
+
const { args, env, tempDir } = buildPiArgs({
|
|
390
|
+
baseArgs: ["-p"],
|
|
391
|
+
task,
|
|
392
|
+
sessionEnabled,
|
|
393
|
+
sessionDir,
|
|
394
|
+
sessionFile: step.sessionFile,
|
|
395
|
+
model: candidate,
|
|
396
|
+
tools: step.tools,
|
|
397
|
+
extensions: step.extensions,
|
|
398
|
+
skills: step.skills,
|
|
399
|
+
systemPrompt: step.systemPrompt,
|
|
400
|
+
mcpDirectTools: step.mcpDirectTools,
|
|
401
|
+
promptFileStem: step.agent,
|
|
402
|
+
});
|
|
403
|
+
const outputFile = index === 0 ? ctx.outputFile : `${ctx.outputFile}.attempt-${index + 1}`;
|
|
404
|
+
const run = await runPiStreaming(args, step.cwd ?? ctx.cwd, outputFile, env, ctx.piPackageRoot, step.maxSubagentDepth);
|
|
405
|
+
cleanupTempDir(tempDir);
|
|
406
|
+
|
|
407
|
+
const parsed = parseRunOutput(run.stdout);
|
|
408
|
+
const error = parsed.error || (run.exitCode !== 0 && run.stderr.trim() ? run.stderr.trim() : undefined);
|
|
409
|
+
const attempt: ModelAttempt = {
|
|
410
|
+
model: candidate ?? parsed.model ?? step.model ?? "default",
|
|
411
|
+
success: run.exitCode === 0 && !error,
|
|
412
|
+
exitCode: run.exitCode,
|
|
413
|
+
error,
|
|
414
|
+
usage: parsed.usage,
|
|
415
|
+
};
|
|
416
|
+
modelAttempts.push(attempt);
|
|
417
|
+
if (candidate) attemptedModels.push(candidate);
|
|
418
|
+
finalResult = { ...run, usage: parsed.usage, model: candidate ?? parsed.model, error };
|
|
419
|
+
if (attempt.success) break;
|
|
420
|
+
if (!isRetryableModelFailure(error) || index === candidates.length - 1) break;
|
|
421
|
+
attemptNotes.push(formatModelAttemptNote(attempt, candidates[index + 1]));
|
|
422
|
+
}
|
|
341
423
|
|
|
342
|
-
const rawOutput = (
|
|
343
|
-
const resolvedOutput = step.outputPath &&
|
|
424
|
+
const rawOutput = (finalResult?.stdout || "").trim();
|
|
425
|
+
const resolvedOutput = step.outputPath && finalResult?.exitCode === 0
|
|
344
426
|
? resolveSingleOutput(step.outputPath, rawOutput, outputSnapshot)
|
|
345
427
|
: { fullOutput: rawOutput };
|
|
346
428
|
const output = resolvedOutput.fullOutput;
|
|
347
429
|
let outputForSummary = output;
|
|
430
|
+
if (attemptNotes.length > 0) {
|
|
431
|
+
outputForSummary = `${attemptNotes.join("\n")}\n\n${outputForSummary}`.trim();
|
|
432
|
+
}
|
|
348
433
|
if (resolvedOutput.savedPath) {
|
|
349
|
-
outputForSummary =
|
|
350
|
-
? `${
|
|
434
|
+
outputForSummary = outputForSummary
|
|
435
|
+
? `${outputForSummary}\n\n📄 Output saved to: ${resolvedOutput.savedPath}`
|
|
351
436
|
: `📄 Output saved to: ${resolvedOutput.savedPath}`;
|
|
352
|
-
} else if (resolvedOutput.saveError && step.outputPath &&
|
|
353
|
-
outputForSummary =
|
|
354
|
-
? `${
|
|
437
|
+
} else if (resolvedOutput.saveError && step.outputPath && finalResult?.exitCode === 0) {
|
|
438
|
+
outputForSummary = outputForSummary
|
|
439
|
+
? `${outputForSummary}\n\n⚠️ Failed to save output to: ${step.outputPath}\n${resolvedOutput.saveError}`
|
|
355
440
|
: `⚠️ Failed to save output to: ${step.outputPath}\n${resolvedOutput.saveError}`;
|
|
356
441
|
}
|
|
357
442
|
|
|
@@ -366,7 +451,10 @@ async function runSingleStep(
|
|
|
366
451
|
runId: ctx.id,
|
|
367
452
|
agent: step.agent,
|
|
368
453
|
task,
|
|
369
|
-
exitCode:
|
|
454
|
+
exitCode: finalResult?.exitCode,
|
|
455
|
+
model: finalResult?.model,
|
|
456
|
+
attemptedModels: attemptedModels.length > 0 ? attemptedModels : undefined,
|
|
457
|
+
modelAttempts,
|
|
370
458
|
skills: step.skills,
|
|
371
459
|
timestamp: Date.now(),
|
|
372
460
|
}, null, 2),
|
|
@@ -375,7 +463,16 @@ async function runSingleStep(
|
|
|
375
463
|
}
|
|
376
464
|
}
|
|
377
465
|
|
|
378
|
-
return {
|
|
466
|
+
return {
|
|
467
|
+
agent: step.agent,
|
|
468
|
+
output: outputForSummary,
|
|
469
|
+
exitCode: finalResult?.exitCode ?? 1,
|
|
470
|
+
error: finalResult?.error,
|
|
471
|
+
model: finalResult?.model,
|
|
472
|
+
attemptedModels: attemptedModels.length > 0 ? attemptedModels : undefined,
|
|
473
|
+
modelAttempts,
|
|
474
|
+
artifactPaths,
|
|
475
|
+
};
|
|
379
476
|
}
|
|
380
477
|
|
|
381
478
|
type RunnerStatusPayload = {
|
|
@@ -397,6 +494,10 @@ type RunnerStatusPayload = {
|
|
|
397
494
|
exitCode?: number | null;
|
|
398
495
|
tokens?: TokenUsage;
|
|
399
496
|
skills?: string[];
|
|
497
|
+
model?: string;
|
|
498
|
+
attemptedModels?: string[];
|
|
499
|
+
modelAttempts?: ModelAttempt[];
|
|
500
|
+
error?: string;
|
|
400
501
|
}>;
|
|
401
502
|
artifactsDir?: string;
|
|
402
503
|
sessionDir?: string;
|
|
@@ -530,7 +631,13 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
530
631
|
pid: process.pid,
|
|
531
632
|
cwd,
|
|
532
633
|
currentStep: 0,
|
|
533
|
-
steps: flatSteps.map((step) => ({
|
|
634
|
+
steps: flatSteps.map((step) => ({
|
|
635
|
+
agent: step.agent,
|
|
636
|
+
status: "pending",
|
|
637
|
+
skills: step.skills,
|
|
638
|
+
model: step.model,
|
|
639
|
+
attemptedModels: step.modelCandidates && step.modelCandidates.length > 0 ? step.modelCandidates : step.model ? [step.model] : undefined,
|
|
640
|
+
})),
|
|
534
641
|
artifactsDir,
|
|
535
642
|
sessionDir: config.sessionDir,
|
|
536
643
|
outputFile: path.join(asyncDir, "output-0.log"),
|
|
@@ -664,6 +771,10 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
664
771
|
statusPayload.steps[fi].endedAt = taskEndTime;
|
|
665
772
|
statusPayload.steps[fi].durationMs = taskDuration;
|
|
666
773
|
statusPayload.steps[fi].exitCode = singleResult.exitCode;
|
|
774
|
+
statusPayload.steps[fi].model = singleResult.model;
|
|
775
|
+
statusPayload.steps[fi].attemptedModels = singleResult.attemptedModels;
|
|
776
|
+
statusPayload.steps[fi].modelAttempts = singleResult.modelAttempts;
|
|
777
|
+
statusPayload.steps[fi].error = singleResult.error;
|
|
667
778
|
statusPayload.lastUpdate = taskEndTime;
|
|
668
779
|
writeJson(statusPath, statusPayload);
|
|
669
780
|
|
|
@@ -707,12 +818,22 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
707
818
|
output: pr.output,
|
|
708
819
|
success: pr.exitCode === 0,
|
|
709
820
|
skipped: pr.skipped,
|
|
821
|
+
model: pr.model,
|
|
822
|
+
attemptedModels: pr.attemptedModels,
|
|
823
|
+
modelAttempts: pr.modelAttempts,
|
|
710
824
|
artifactPaths: pr.artifactPaths,
|
|
711
825
|
});
|
|
712
826
|
}
|
|
713
827
|
|
|
714
828
|
previousOutput = aggregateParallelOutputs(
|
|
715
|
-
parallelResults.map((r) => ({
|
|
829
|
+
parallelResults.map((r) => ({
|
|
830
|
+
agent: r.agent,
|
|
831
|
+
output: r.output,
|
|
832
|
+
exitCode: r.exitCode,
|
|
833
|
+
error: r.error,
|
|
834
|
+
model: r.model,
|
|
835
|
+
attemptedModels: r.attemptedModels,
|
|
836
|
+
})),
|
|
716
837
|
);
|
|
717
838
|
previousOutput = appendParallelWorktreeSummary(previousOutput, worktreeSetup, asyncDir, stepIndex, group);
|
|
718
839
|
|
|
@@ -768,6 +889,9 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
768
889
|
agent: singleResult.agent,
|
|
769
890
|
output: singleResult.output,
|
|
770
891
|
success: singleResult.exitCode === 0,
|
|
892
|
+
model: singleResult.model,
|
|
893
|
+
attemptedModels: singleResult.attemptedModels,
|
|
894
|
+
modelAttempts: singleResult.modelAttempts,
|
|
771
895
|
artifactPaths: singleResult.artifactPaths,
|
|
772
896
|
});
|
|
773
897
|
|
|
@@ -788,6 +912,10 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
788
912
|
statusPayload.steps[flatIndex].endedAt = stepEndTime;
|
|
789
913
|
statusPayload.steps[flatIndex].durationMs = stepEndTime - stepStartTime;
|
|
790
914
|
statusPayload.steps[flatIndex].exitCode = singleResult.exitCode;
|
|
915
|
+
statusPayload.steps[flatIndex].model = singleResult.model;
|
|
916
|
+
statusPayload.steps[flatIndex].attemptedModels = singleResult.attemptedModels;
|
|
917
|
+
statusPayload.steps[flatIndex].modelAttempts = singleResult.modelAttempts;
|
|
918
|
+
statusPayload.steps[flatIndex].error = singleResult.error;
|
|
791
919
|
if (stepTokens) {
|
|
792
920
|
statusPayload.steps[flatIndex].tokens = stepTokens;
|
|
793
921
|
statusPayload.totalTokens = { ...previousCumulativeTokens };
|
|
@@ -915,6 +1043,9 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
915
1043
|
output: r.output,
|
|
916
1044
|
success: r.success,
|
|
917
1045
|
skipped: r.skipped || undefined,
|
|
1046
|
+
model: r.model,
|
|
1047
|
+
attemptedModels: r.attemptedModels,
|
|
1048
|
+
modelAttempts: r.modelAttempts,
|
|
918
1049
|
artifactPaths: r.artifactPaths,
|
|
919
1050
|
truncated: r.truncated,
|
|
920
1051
|
})),
|
package/subagents-status.ts
CHANGED
|
@@ -158,10 +158,17 @@ export class SubagentsStatusComponent implements Component {
|
|
|
158
158
|
row(`cwd: ${truncateToWidth(shortenPath(run.cwd ?? run.asyncDir), innerW - 5)}`, width, this.theme),
|
|
159
159
|
];
|
|
160
160
|
for (const step of run.steps) {
|
|
161
|
+
const model = step.model ? ` | ${step.model}` : "";
|
|
162
|
+
const attempts = step.attemptedModels && step.attemptedModels.length > 1
|
|
163
|
+
? ` | attempts ${step.attemptedModels.length}`
|
|
164
|
+
: "";
|
|
161
165
|
const duration = step.durationMs !== undefined ? ` | ${formatDuration(step.durationMs)}` : "";
|
|
162
166
|
const tokens = step.tokens ? ` | ${formatTokens(step.tokens.total)} tok` : "";
|
|
163
|
-
const line = ` ${step.index + 1}. ${step.agent} | ${stepStatusColor(this.theme, step.status)}${duration}${tokens}`;
|
|
167
|
+
const line = ` ${step.index + 1}. ${step.agent} | ${stepStatusColor(this.theme, step.status)}${model}${attempts}${duration}${tokens}`;
|
|
164
168
|
lines.push(row(truncateToWidth(line, innerW), width, this.theme));
|
|
169
|
+
if (step.error) {
|
|
170
|
+
lines.push(row(truncateToWidth(` ${step.error}`, innerW), width, this.theme));
|
|
171
|
+
}
|
|
165
172
|
}
|
|
166
173
|
if (run.steps.length === 0) {
|
|
167
174
|
lines.push(row(this.theme.fg("dim", " No step details available yet."), width, this.theme));
|
package/types.ts
CHANGED
|
@@ -82,6 +82,14 @@ export interface ProgressSummary {
|
|
|
82
82
|
// Results
|
|
83
83
|
// ============================================================================
|
|
84
84
|
|
|
85
|
+
export interface ModelAttempt {
|
|
86
|
+
model: string;
|
|
87
|
+
success: boolean;
|
|
88
|
+
exitCode?: number | null;
|
|
89
|
+
error?: string;
|
|
90
|
+
usage?: Usage;
|
|
91
|
+
}
|
|
92
|
+
|
|
85
93
|
export interface SingleResult {
|
|
86
94
|
agent: string;
|
|
87
95
|
task: string;
|
|
@@ -89,6 +97,8 @@ export interface SingleResult {
|
|
|
89
97
|
messages: Message[];
|
|
90
98
|
usage: Usage;
|
|
91
99
|
model?: string;
|
|
100
|
+
attemptedModels?: string[];
|
|
101
|
+
modelAttempts?: ModelAttempt[];
|
|
92
102
|
error?: string;
|
|
93
103
|
sessionFile?: string;
|
|
94
104
|
skills?: string[];
|
|
@@ -159,7 +169,17 @@ export interface AsyncStatus {
|
|
|
159
169
|
lastUpdate?: number;
|
|
160
170
|
cwd?: string;
|
|
161
171
|
currentStep?: number;
|
|
162
|
-
steps?: Array<{
|
|
172
|
+
steps?: Array<{
|
|
173
|
+
agent: string;
|
|
174
|
+
status: string;
|
|
175
|
+
durationMs?: number;
|
|
176
|
+
tokens?: TokenUsage;
|
|
177
|
+
skills?: string[];
|
|
178
|
+
model?: string;
|
|
179
|
+
attemptedModels?: string[];
|
|
180
|
+
modelAttempts?: ModelAttempt[];
|
|
181
|
+
error?: string;
|
|
182
|
+
}>;
|
|
163
183
|
sessionDir?: string;
|
|
164
184
|
outputFile?: string;
|
|
165
185
|
totalTokens?: TokenUsage;
|
|
@@ -237,6 +257,8 @@ export interface RunSyncOptions {
|
|
|
237
257
|
maxSubagentDepth?: number;
|
|
238
258
|
/** Override the agent's default model (format: "provider/id" or just "id") */
|
|
239
259
|
modelOverride?: string;
|
|
260
|
+
/** Registry models available for heuristic bare-model resolution */
|
|
261
|
+
availableModels?: Array<{ provider: string; id: string; fullId: string }>;
|
|
240
262
|
/** Skills to inject (overrides agent default if provided) */
|
|
241
263
|
skills?: string[];
|
|
242
264
|
}
|