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/chain-execution.ts
CHANGED
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
import { discoverAvailableSkills, normalizeSkillInput } from "./skills.ts";
|
|
29
29
|
import { runSync } from "./execution.ts";
|
|
30
30
|
import { buildChainSummary } from "./formatters.ts";
|
|
31
|
-
import { getSingleResultOutput, mapConcurrent } from "./utils.ts";
|
|
31
|
+
import { compactForegroundDetails, getSingleResultOutput, mapConcurrent } from "./utils.ts";
|
|
32
32
|
import { recordRun } from "./run-history.ts";
|
|
33
33
|
import {
|
|
34
34
|
cleanupWorktrees,
|
|
@@ -91,7 +91,7 @@ interface ParallelChainRunInput {
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
function buildChainExecutionDetails(input: ChainExecutionDetailsInput): Details {
|
|
94
|
-
return {
|
|
94
|
+
return compactForegroundDetails({
|
|
95
95
|
mode: "chain",
|
|
96
96
|
results: input.results,
|
|
97
97
|
progress: input.includeProgress ? input.allProgress : undefined,
|
|
@@ -99,7 +99,7 @@ function buildChainExecutionDetails(input: ChainExecutionDetailsInput): Details
|
|
|
99
99
|
chainAgents: input.chainAgents,
|
|
100
100
|
totalSteps: input.totalSteps,
|
|
101
101
|
currentStepIndex: input.currentStepIndex,
|
|
102
|
-
};
|
|
102
|
+
});
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
function buildChainExecutionErrorResult(message: string, input: ChainExecutionDetailsInput): ChainExecutionResult {
|
|
@@ -175,8 +175,8 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
|
|
|
175
175
|
|
|
176
176
|
const taskAgentConfig = input.agents.find((agent) => agent.name === task.agent);
|
|
177
177
|
const effectiveModel =
|
|
178
|
-
(task.model ? resolveModelCandidate(task.model, input.availableModels) : null)
|
|
179
|
-
?? resolveModelCandidate(taskAgentConfig?.model, input.availableModels);
|
|
178
|
+
(task.model ? resolveModelCandidate(task.model, input.availableModels, input.ctx.model?.provider) : null)
|
|
179
|
+
?? resolveModelCandidate(taskAgentConfig?.model, input.availableModels, input.ctx.model?.provider);
|
|
180
180
|
const maxSubagentDepth = resolveChildMaxSubagentDepth(input.maxSubagentDepth, taskAgentConfig?.maxSubagentDepth);
|
|
181
181
|
|
|
182
182
|
const taskCwd = input.worktreeSetup
|
|
@@ -201,6 +201,7 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
|
|
|
201
201
|
maxSubagentDepth,
|
|
202
202
|
modelOverride: effectiveModel,
|
|
203
203
|
availableModels: input.availableModels,
|
|
204
|
+
preferredModelProvider: input.ctx.model?.provider,
|
|
204
205
|
skills: behavior.skills === false ? [] : behavior.skills,
|
|
205
206
|
onUpdate: input.onUpdate
|
|
206
207
|
? (progressUpdate) => {
|
|
@@ -293,7 +294,6 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
293
294
|
const allProgress: AgentProgress[] = [];
|
|
294
295
|
const allArtifactPaths: ArtifactPaths[] = [];
|
|
295
296
|
|
|
296
|
-
// Compute chain metadata for observability
|
|
297
297
|
const chainAgents: string[] = chainSteps.map((step) =>
|
|
298
298
|
isParallelStep(step)
|
|
299
299
|
? `[${step.parallel.map((t) => t.agent).join("+")}]`
|
|
@@ -301,39 +301,24 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
301
301
|
);
|
|
302
302
|
const totalSteps = chainSteps.length;
|
|
303
303
|
|
|
304
|
-
// Get original task from params or first step
|
|
305
304
|
const firstStep = chainSteps[0]!;
|
|
306
305
|
const originalTask = params.task
|
|
307
306
|
?? (isParallelStep(firstStep) ? firstStep.parallel[0]!.task! : (firstStep as SequentialStep).task!);
|
|
308
307
|
|
|
309
|
-
// Create chain directory
|
|
310
308
|
const chainDir = createChainDir(runId, chainDirBase);
|
|
311
|
-
|
|
312
|
-
// Check if chain has any parallel steps
|
|
313
309
|
const hasParallelSteps = chainSteps.some(isParallelStep);
|
|
314
|
-
|
|
315
|
-
// Resolve templates (parallel-aware)
|
|
316
310
|
let templates: ResolvedTemplates = resolveChainTemplates(chainSteps);
|
|
317
|
-
|
|
318
|
-
// For TUI: only show if no parallel steps (TUI v1 doesn't support parallel display)
|
|
319
311
|
const shouldClarify = clarify !== false && ctx.hasUI && !hasParallelSteps;
|
|
320
|
-
|
|
321
|
-
// Behavior overrides from TUI (set if TUI is shown, undefined otherwise)
|
|
322
312
|
let tuiBehaviorOverrides: (BehaviorOverride | undefined)[] | undefined;
|
|
323
|
-
|
|
324
|
-
// Get available models for model resolution (used in TUI and execution)
|
|
325
313
|
const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map((m) => ({
|
|
326
314
|
provider: m.provider,
|
|
327
315
|
id: m.id,
|
|
328
316
|
fullId: `${m.provider}/${m.id}`,
|
|
329
317
|
}));
|
|
330
|
-
const availableSkills = discoverAvailableSkills(ctx.cwd);
|
|
318
|
+
const availableSkills = discoverAvailableSkills(cwd ?? ctx.cwd);
|
|
331
319
|
|
|
332
320
|
if (shouldClarify) {
|
|
333
|
-
// Sequential-only chain: use existing TUI
|
|
334
321
|
const seqSteps = chainSteps as SequentialStep[];
|
|
335
|
-
|
|
336
|
-
// Load agent configs for sequential steps
|
|
337
322
|
const agentConfigs: AgentConfig[] = [];
|
|
338
323
|
for (const step of seqSteps) {
|
|
339
324
|
const config = agents.find((a) => a.name === step.agent);
|
|
@@ -348,7 +333,6 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
348
333
|
agentConfigs.push(config);
|
|
349
334
|
}
|
|
350
335
|
|
|
351
|
-
// Build step overrides
|
|
352
336
|
const stepOverrides: StepOverrides[] = seqSteps.map((step) => ({
|
|
353
337
|
output: step.output,
|
|
354
338
|
reads: step.reads,
|
|
@@ -357,12 +341,9 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
357
341
|
model: step.model,
|
|
358
342
|
}));
|
|
359
343
|
|
|
360
|
-
// Pre-resolve behaviors for TUI display
|
|
361
344
|
const resolvedBehaviors = agentConfigs.map((config, i) =>
|
|
362
345
|
resolveStepBehavior(config, stepOverrides[i]!, chainSkills),
|
|
363
346
|
);
|
|
364
|
-
|
|
365
|
-
// Flatten templates for TUI (all strings for sequential)
|
|
366
347
|
const flatTemplates = templates as string[];
|
|
367
348
|
|
|
368
349
|
const result = await ctx.ui.custom<ChainClarifyResult>(
|
|
@@ -376,6 +357,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
376
357
|
chainDir,
|
|
377
358
|
resolvedBehaviors,
|
|
378
359
|
availableModels,
|
|
360
|
+
ctx.model?.provider,
|
|
379
361
|
availableSkills,
|
|
380
362
|
done,
|
|
381
363
|
),
|
|
@@ -393,16 +375,14 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
393
375
|
};
|
|
394
376
|
}
|
|
395
377
|
|
|
396
|
-
// User requested background execution - return early so caller can dispatch to async
|
|
397
378
|
if (result.runInBackground) {
|
|
398
|
-
removeChainDir(chainDir);
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
if (isParallelStep(step)) return step; // Parallel steps unchanged (TUI skipped for parallel chains)
|
|
379
|
+
removeChainDir(chainDir);
|
|
380
|
+
const updatedChain: ChainStep[] = chainSteps.map((step, i) => {
|
|
381
|
+
if (isParallelStep(step)) return step;
|
|
402
382
|
const override = result.behaviorOverrides[i];
|
|
403
383
|
return {
|
|
404
384
|
...step,
|
|
405
|
-
task: result.templates[i]
|
|
385
|
+
task: result.templates[i]!,
|
|
406
386
|
...(override?.model ? { model: override.model } : {}),
|
|
407
387
|
...(override?.output !== undefined ? { output: override.output } : {}),
|
|
408
388
|
...(override?.reads !== undefined ? { reads: override.reads } : {}),
|
|
@@ -413,21 +393,18 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
413
393
|
return {
|
|
414
394
|
content: [{ type: "text", text: "Launching in background..." }],
|
|
415
395
|
details: { mode: "chain", results: [] },
|
|
416
|
-
requestedAsync: { chain: updatedChain
|
|
396
|
+
requestedAsync: { chain: updatedChain, chainSkills },
|
|
417
397
|
};
|
|
418
398
|
}
|
|
419
399
|
|
|
420
|
-
// Update templates from TUI result
|
|
421
400
|
templates = result.templates;
|
|
422
|
-
// Store behavior overrides from TUI (used below in sequential step execution)
|
|
423
401
|
tuiBehaviorOverrides = result.behaviorOverrides;
|
|
424
402
|
}
|
|
425
403
|
|
|
426
|
-
// Execute chain (handles both sequential and parallel steps)
|
|
427
404
|
const results: SingleResult[] = [];
|
|
428
405
|
let prev = "";
|
|
429
|
-
let globalTaskIndex = 0;
|
|
430
|
-
let progressCreated = false;
|
|
406
|
+
let globalTaskIndex = 0;
|
|
407
|
+
let progressCreated = false;
|
|
431
408
|
|
|
432
409
|
for (let stepIndex = 0; stepIndex < chainSteps.length; stepIndex++) {
|
|
433
410
|
const step = chainSteps[stepIndex]!;
|
|
@@ -572,11 +549,9 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
572
549
|
if (worktreeSetup) cleanupWorktrees(worktreeSetup);
|
|
573
550
|
}
|
|
574
551
|
} else {
|
|
575
|
-
// === SEQUENTIAL STEP EXECUTION ===
|
|
576
552
|
const seqStep = step as SequentialStep;
|
|
577
553
|
const stepTemplate = stepTemplates as string;
|
|
578
554
|
|
|
579
|
-
// Get agent config
|
|
580
555
|
const agentConfig = agents.find((a) => a.name === seqStep.agent);
|
|
581
556
|
if (!agentConfig) {
|
|
582
557
|
removeChainDir(chainDir);
|
|
@@ -587,7 +562,6 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
587
562
|
};
|
|
588
563
|
}
|
|
589
564
|
|
|
590
|
-
// Resolve behavior first (TUI overrides take precedence over step config)
|
|
591
565
|
const tuiOverride = tuiBehaviorOverrides?.[stepIndex];
|
|
592
566
|
const stepOverride: StepOverrides = {
|
|
593
567
|
output: tuiOverride?.output !== undefined ? tuiOverride.output : seqStep.output,
|
|
@@ -600,38 +574,31 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
600
574
|
};
|
|
601
575
|
const behavior = resolveStepBehavior(agentConfig, stepOverride, chainSkills);
|
|
602
576
|
|
|
603
|
-
// Determine if this is the first agent to create progress.md
|
|
604
577
|
const isFirstProgress = behavior.progress && !progressCreated;
|
|
605
578
|
if (isFirstProgress) {
|
|
606
579
|
progressCreated = true;
|
|
607
580
|
}
|
|
608
581
|
|
|
609
|
-
// Build chain instructions (prefix goes BEFORE task, suffix goes AFTER)
|
|
610
582
|
const templateHasPrevious = stepTemplate.includes("{previous}");
|
|
611
583
|
const { prefix, suffix } = buildChainInstructions(
|
|
612
|
-
behavior,
|
|
613
|
-
chainDir,
|
|
614
|
-
isFirstProgress,
|
|
615
|
-
templateHasPrevious ? undefined : prev
|
|
584
|
+
behavior,
|
|
585
|
+
chainDir,
|
|
586
|
+
isFirstProgress,
|
|
587
|
+
templateHasPrevious ? undefined : prev,
|
|
616
588
|
);
|
|
617
589
|
|
|
618
|
-
// Build task string with variable substitution
|
|
619
590
|
let stepTask = stepTemplate;
|
|
620
591
|
stepTask = stepTask.replace(/\{task\}/g, originalTask);
|
|
621
592
|
stepTask = stepTask.replace(/\{previous\}/g, prev);
|
|
622
593
|
stepTask = stepTask.replace(/\{chain_dir\}/g, chainDir);
|
|
623
594
|
const cleanTask = stepTask;
|
|
624
|
-
|
|
625
|
-
// Assemble final task: prefix (READ/WRITE instructions) + task + suffix (progress, previous summary)
|
|
626
595
|
stepTask = prefix + stepTask + suffix;
|
|
627
596
|
|
|
628
|
-
// Resolve model: TUI override (already full format) or agent's model resolved to full format
|
|
629
597
|
const effectiveModel =
|
|
630
598
|
tuiOverride?.model
|
|
631
|
-
?? (seqStep.model ? resolveModelCandidate(seqStep.model, availableModels) : null)
|
|
632
|
-
?? resolveModelCandidate(agentConfig.model, availableModels);
|
|
599
|
+
?? (seqStep.model ? resolveModelCandidate(seqStep.model, availableModels, ctx.model?.provider) : null)
|
|
600
|
+
?? resolveModelCandidate(agentConfig.model, availableModels, ctx.model?.provider);
|
|
633
601
|
|
|
634
|
-
// Run step
|
|
635
602
|
const outputPath = typeof behavior.output === "string"
|
|
636
603
|
? (path.isAbsolute(behavior.output) ? behavior.output : path.join(chainDir, behavior.output))
|
|
637
604
|
: undefined;
|
|
@@ -651,10 +618,10 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
651
618
|
maxSubagentDepth,
|
|
652
619
|
modelOverride: effectiveModel,
|
|
653
620
|
availableModels,
|
|
621
|
+
preferredModelProvider: ctx.model?.provider,
|
|
654
622
|
skills: behavior.skills === false ? [] : behavior.skills,
|
|
655
623
|
onUpdate: onUpdate
|
|
656
624
|
? (p) => {
|
|
657
|
-
// Use concat instead of spread for better performance
|
|
658
625
|
const stepResults = p.details?.results || [];
|
|
659
626
|
const stepProgress = p.details?.progress || [];
|
|
660
627
|
onUpdate({
|
|
@@ -678,28 +645,24 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
678
645
|
if (r.progress) allProgress.push(r.progress);
|
|
679
646
|
if (r.artifactPaths) allArtifactPaths.push(r.artifactPaths);
|
|
680
647
|
|
|
681
|
-
// Validate expected output file was created
|
|
682
648
|
if (behavior.output && r.exitCode === 0) {
|
|
683
649
|
try {
|
|
684
650
|
const expectedPath = path.isAbsolute(behavior.output)
|
|
685
|
-
? behavior.output
|
|
651
|
+
? behavior.output
|
|
686
652
|
: path.join(chainDir, behavior.output);
|
|
687
653
|
if (!fs.existsSync(expectedPath)) {
|
|
688
|
-
// Look for similar files that might have been created instead
|
|
689
654
|
const dirFiles = fs.readdirSync(chainDir);
|
|
690
|
-
const mdFiles = dirFiles.filter(
|
|
691
|
-
const warning = mdFiles.length > 0
|
|
655
|
+
const mdFiles = dirFiles.filter((file) => file.endsWith(".md") && file !== "progress.md");
|
|
656
|
+
const warning = mdFiles.length > 0
|
|
692
657
|
? `Agent wrote to different file(s): ${mdFiles.join(", ")} instead of ${behavior.output}`
|
|
693
658
|
: `Agent did not create expected output file: ${behavior.output}`;
|
|
694
|
-
|
|
695
|
-
r.error = r.error ? `${r.error}\n⚠️ ${warning}` : `⚠️ ${warning}`;
|
|
659
|
+
r.error = r.error ? `${r.error}\n${warning}` : warning;
|
|
696
660
|
}
|
|
697
661
|
} catch {
|
|
698
662
|
// Ignore validation errors - this is just a diagnostic
|
|
699
663
|
}
|
|
700
664
|
}
|
|
701
665
|
|
|
702
|
-
// On failure, leave chain_dir for debugging
|
|
703
666
|
if (r.exitCode !== 0) {
|
|
704
667
|
const summary = buildChainSummary(chainSteps, results, chainDir, "failed", {
|
|
705
668
|
index: stepIndex,
|
|
@@ -707,15 +670,16 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
707
670
|
});
|
|
708
671
|
return {
|
|
709
672
|
content: [{ type: "text", text: summary }],
|
|
710
|
-
details: {
|
|
711
|
-
mode: "chain",
|
|
673
|
+
details: buildChainExecutionDetails({
|
|
712
674
|
results,
|
|
713
|
-
|
|
714
|
-
|
|
675
|
+
includeProgress,
|
|
676
|
+
allProgress,
|
|
677
|
+
allArtifactPaths,
|
|
678
|
+
artifactsDir,
|
|
715
679
|
chainAgents,
|
|
716
680
|
totalSteps,
|
|
717
681
|
currentStepIndex: stepIndex,
|
|
718
|
-
},
|
|
682
|
+
}),
|
|
719
683
|
isError: true,
|
|
720
684
|
};
|
|
721
685
|
}
|
|
@@ -724,20 +688,18 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
724
688
|
}
|
|
725
689
|
}
|
|
726
690
|
|
|
727
|
-
// Chain complete - return summary with paths
|
|
728
|
-
// Chain dir left for inspection (cleaned up after 24h)
|
|
729
691
|
const summary = buildChainSummary(chainSteps, results, chainDir, "completed");
|
|
730
692
|
|
|
731
693
|
return {
|
|
732
694
|
content: [{ type: "text", text: summary }],
|
|
733
|
-
details: {
|
|
734
|
-
mode: "chain",
|
|
695
|
+
details: buildChainExecutionDetails({
|
|
735
696
|
results,
|
|
736
|
-
|
|
737
|
-
|
|
697
|
+
includeProgress,
|
|
698
|
+
allProgress,
|
|
699
|
+
allArtifactPaths,
|
|
700
|
+
artifactsDir,
|
|
738
701
|
chainAgents,
|
|
739
702
|
totalSteps,
|
|
740
|
-
|
|
741
|
-
},
|
|
703
|
+
}),
|
|
742
704
|
};
|
|
743
705
|
}
|
package/execution.ts
CHANGED
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
extractToolArgsPreview,
|
|
32
32
|
extractTextFromContent,
|
|
33
33
|
} from "./utils.ts";
|
|
34
|
-
import { buildSkillInjection,
|
|
34
|
+
import { buildSkillInjection, resolveSkillsWithFallback } from "./skills.ts";
|
|
35
35
|
import { getPiSpawnCommand } from "./pi-spawn.ts";
|
|
36
36
|
import { createJsonlWriter } from "./jsonl-writer.ts";
|
|
37
37
|
import { applyThinkingSuffix, buildPiArgs, cleanupTempDir } from "./pi-args.ts";
|
|
@@ -186,61 +186,64 @@ async function runSingleAttempt(
|
|
|
186
186
|
const processLine = (line: string) => {
|
|
187
187
|
if (!line.trim()) return;
|
|
188
188
|
jsonlWriter.writeLine(line);
|
|
189
|
+
let evt: { type?: string; message?: Message; toolName?: string; args?: unknown };
|
|
189
190
|
try {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
191
|
+
evt = JSON.parse(line) as { type?: string; message?: Message; toolName?: string; args?: unknown };
|
|
192
|
+
} catch {
|
|
193
|
+
// Non-JSON stdout lines are expected; only structured events are parsed.
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
193
196
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
intercomStarted = true;
|
|
197
|
-
}
|
|
198
|
-
progress.toolCount++;
|
|
199
|
-
progress.currentTool = evt.toolName;
|
|
200
|
-
progress.currentToolArgs = extractToolArgsPreview((evt.args || {}) as Record<string, unknown>);
|
|
201
|
-
fireUpdate();
|
|
202
|
-
}
|
|
197
|
+
const now = Date.now();
|
|
198
|
+
progress.durationMs = now - startTime;
|
|
203
199
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
tool: progress.currentTool,
|
|
208
|
-
args: progress.currentToolArgs || "",
|
|
209
|
-
endMs: now,
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
progress.currentTool = undefined;
|
|
213
|
-
progress.currentToolArgs = undefined;
|
|
214
|
-
fireUpdate();
|
|
200
|
+
if (evt.type === "tool_execution_start") {
|
|
201
|
+
if (options.allowIntercomDetach && evt.toolName === "intercom") {
|
|
202
|
+
intercomStarted = true;
|
|
215
203
|
}
|
|
204
|
+
progress.toolCount++;
|
|
205
|
+
progress.currentTool = evt.toolName;
|
|
206
|
+
progress.currentToolArgs = extractToolArgsPreview((evt.args || {}) as Record<string, unknown>);
|
|
207
|
+
fireUpdate();
|
|
208
|
+
}
|
|
216
209
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
result.usage.output += u.output || 0;
|
|
225
|
-
result.usage.cacheRead += u.cacheRead || 0;
|
|
226
|
-
result.usage.cacheWrite += u.cacheWrite || 0;
|
|
227
|
-
result.usage.cost += u.cost?.total || 0;
|
|
228
|
-
progress.tokens = result.usage.input + result.usage.output;
|
|
229
|
-
}
|
|
230
|
-
if (!result.model && evt.message.model) result.model = evt.message.model;
|
|
231
|
-
if (evt.message.errorMessage) result.error = evt.message.errorMessage;
|
|
232
|
-
appendRecentOutput(progress, extractTextFromContent(evt.message.content).split("\n").slice(-10));
|
|
233
|
-
}
|
|
234
|
-
fireUpdate();
|
|
210
|
+
if (evt.type === "tool_execution_end") {
|
|
211
|
+
if (progress.currentTool) {
|
|
212
|
+
progress.recentTools.push({
|
|
213
|
+
tool: progress.currentTool,
|
|
214
|
+
args: progress.currentToolArgs || "",
|
|
215
|
+
endMs: now,
|
|
216
|
+
});
|
|
235
217
|
}
|
|
218
|
+
progress.currentTool = undefined;
|
|
219
|
+
progress.currentToolArgs = undefined;
|
|
220
|
+
fireUpdate();
|
|
221
|
+
}
|
|
236
222
|
|
|
237
|
-
|
|
238
|
-
|
|
223
|
+
if (evt.type === "message_end" && evt.message) {
|
|
224
|
+
result.messages.push(evt.message);
|
|
225
|
+
if (evt.message.role === "assistant") {
|
|
226
|
+
result.usage.turns++;
|
|
227
|
+
const u = evt.message.usage;
|
|
228
|
+
if (u) {
|
|
229
|
+
result.usage.input += u.input || 0;
|
|
230
|
+
result.usage.output += u.output || 0;
|
|
231
|
+
result.usage.cacheRead += u.cacheRead || 0;
|
|
232
|
+
result.usage.cacheWrite += u.cacheWrite || 0;
|
|
233
|
+
result.usage.cost += u.cost?.total || 0;
|
|
234
|
+
progress.tokens = result.usage.input + result.usage.output;
|
|
235
|
+
}
|
|
236
|
+
if (!result.model && evt.message.model) result.model = evt.message.model;
|
|
237
|
+
if (evt.message.errorMessage) result.error = evt.message.errorMessage;
|
|
239
238
|
appendRecentOutput(progress, extractTextFromContent(evt.message.content).split("\n").slice(-10));
|
|
240
|
-
fireUpdate();
|
|
241
239
|
}
|
|
242
|
-
|
|
243
|
-
|
|
240
|
+
fireUpdate();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (evt.type === "tool_result_end" && evt.message) {
|
|
244
|
+
result.messages.push(evt.message);
|
|
245
|
+
appendRecentOutput(progress, extractTextFromContent(evt.message.content).split("\n").slice(-10));
|
|
246
|
+
fireUpdate();
|
|
244
247
|
}
|
|
245
248
|
};
|
|
246
249
|
|
|
@@ -368,7 +371,8 @@ export async function runSync(
|
|
|
368
371
|
const sessionEnabled = Boolean(options.sessionFile || options.sessionDir) || shareEnabled;
|
|
369
372
|
const outputSnapshot = captureSingleOutputSnapshot(options.outputPath);
|
|
370
373
|
const skillNames = options.skills ?? agent.skills ?? [];
|
|
371
|
-
const
|
|
374
|
+
const skillCwd = options.cwd ?? runtimeCwd;
|
|
375
|
+
const { resolved: resolvedSkills, missing: missingSkills } = resolveSkillsWithFallback(skillNames, skillCwd, runtimeCwd);
|
|
372
376
|
let systemPrompt = agent.systemPrompt?.trim() || "";
|
|
373
377
|
if (resolvedSkills.length > 0) {
|
|
374
378
|
const skillInjection = buildSkillInjection(resolvedSkills);
|
|
@@ -379,6 +383,7 @@ export async function runSync(
|
|
|
379
383
|
options.modelOverride ?? agent.model,
|
|
380
384
|
agent.fallbackModels,
|
|
381
385
|
options.availableModels,
|
|
386
|
+
options.preferredModelProvider,
|
|
382
387
|
);
|
|
383
388
|
const attemptedModels: string[] = [];
|
|
384
389
|
const modelAttempts: ModelAttempt[] = [];
|
package/index.ts
CHANGED
|
@@ -259,7 +259,7 @@ EXECUTION (use exactly ONE mode):
|
|
|
259
259
|
CHAIN TEMPLATE VARIABLES (use in task strings):
|
|
260
260
|
• {task} - The original task/request from the user
|
|
261
261
|
• {previous} - Text response from the previous step (empty for first step)
|
|
262
|
-
• {chain_dir} - Shared directory for chain files (e.g., <tmpdir>/pi-chain-runs/abc123/)
|
|
262
|
+
• {chain_dir} - Shared directory for chain files (e.g., <tmpdir>/pi-subagents-<scope>/chain-runs/abc123/)
|
|
263
263
|
|
|
264
264
|
Example: { chain: [{agent:"scout", task:"Analyze {task}"}, {agent:"planner", task:"Plan based on {previous}"}] }
|
|
265
265
|
|
package/install.mjs
CHANGED
|
@@ -38,7 +38,7 @@ if (isRemove) {
|
|
|
38
38
|
if (fs.existsSync(EXTENSION_DIR)) {
|
|
39
39
|
console.log(`Removing ${EXTENSION_DIR}...`);
|
|
40
40
|
fs.rmSync(EXTENSION_DIR, { recursive: true });
|
|
41
|
-
console.log("
|
|
41
|
+
console.log("pi-subagents removed");
|
|
42
42
|
} else {
|
|
43
43
|
console.log("pi-subagents is not installed");
|
|
44
44
|
}
|
|
@@ -61,7 +61,7 @@ if (fs.existsSync(EXTENSION_DIR)) {
|
|
|
61
61
|
console.log("Updating existing installation...");
|
|
62
62
|
try {
|
|
63
63
|
execSync("git pull", { cwd: EXTENSION_DIR, stdio: "inherit" });
|
|
64
|
-
console.log("\
|
|
64
|
+
console.log("\npi-subagents updated");
|
|
65
65
|
} catch (err) {
|
|
66
66
|
console.error("Failed to update. Try removing and reinstalling:");
|
|
67
67
|
console.error(" npx pi-subagents --remove && npx pi-subagents");
|
|
@@ -77,7 +77,7 @@ if (fs.existsSync(EXTENSION_DIR)) {
|
|
|
77
77
|
console.log(`Cloning to ${EXTENSION_DIR}...`);
|
|
78
78
|
try {
|
|
79
79
|
execSync(`git clone ${REPO_URL} "${EXTENSION_DIR}"`, { stdio: "inherit" });
|
|
80
|
-
console.log("\
|
|
80
|
+
console.log("\npi-subagents installed");
|
|
81
81
|
} catch (err) {
|
|
82
82
|
console.error("Failed to clone repository");
|
|
83
83
|
process.exit(1);
|
package/model-fallback.ts
CHANGED
|
@@ -14,7 +14,7 @@ export interface ModelAttemptSummary {
|
|
|
14
14
|
usage?: Usage;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
function splitThinkingSuffix(model: string): { baseModel: string; thinkingSuffix: string } {
|
|
17
|
+
export function splitThinkingSuffix(model: string): { baseModel: string; thinkingSuffix: string } {
|
|
18
18
|
const colonIdx = model.lastIndexOf(":");
|
|
19
19
|
if (colonIdx === -1) return { baseModel: model, thinkingSuffix: "" };
|
|
20
20
|
return {
|
|
@@ -26,6 +26,7 @@ function splitThinkingSuffix(model: string): { baseModel: string; thinkingSuffix
|
|
|
26
26
|
export function resolveModelCandidate(
|
|
27
27
|
model: string | undefined,
|
|
28
28
|
availableModels: AvailableModelInfo[] | undefined,
|
|
29
|
+
preferredProvider?: string,
|
|
29
30
|
): string | undefined {
|
|
30
31
|
if (!model) return undefined;
|
|
31
32
|
if (model.includes("/")) return model;
|
|
@@ -33,6 +34,10 @@ export function resolveModelCandidate(
|
|
|
33
34
|
|
|
34
35
|
const { baseModel, thinkingSuffix } = splitThinkingSuffix(model);
|
|
35
36
|
const matches = availableModels.filter((entry) => entry.id === baseModel);
|
|
37
|
+
if (preferredProvider) {
|
|
38
|
+
const preferredMatch = matches.find((entry) => entry.provider === preferredProvider);
|
|
39
|
+
if (preferredMatch) return `${preferredMatch.fullId}${thinkingSuffix}`;
|
|
40
|
+
}
|
|
36
41
|
if (matches.length !== 1) return model;
|
|
37
42
|
return `${matches[0]!.fullId}${thinkingSuffix}`;
|
|
38
43
|
}
|
|
@@ -41,12 +46,13 @@ export function buildModelCandidates(
|
|
|
41
46
|
primaryModel: string | undefined,
|
|
42
47
|
fallbackModels: string[] | undefined,
|
|
43
48
|
availableModels: AvailableModelInfo[] | undefined,
|
|
49
|
+
preferredProvider?: string,
|
|
44
50
|
): string[] {
|
|
45
51
|
const seen = new Set<string>();
|
|
46
52
|
const candidates: string[] = [];
|
|
47
53
|
for (const raw of [primaryModel, ...(fallbackModels ?? [])]) {
|
|
48
54
|
if (!raw) continue;
|
|
49
|
-
const normalized = resolveModelCandidate(raw.trim(), availableModels);
|
|
55
|
+
const normalized = resolveModelCandidate(raw.trim(), availableModels, preferredProvider);
|
|
50
56
|
if (!normalized || seen.has(normalized)) continue;
|
|
51
57
|
seen.add(normalized);
|
|
52
58
|
candidates.push(normalized);
|
package/package.json
CHANGED
package/parallel-utils.ts
CHANGED
|
@@ -102,15 +102,15 @@ export function aggregateParallelOutputs(
|
|
|
102
102
|
const hasOutput = Boolean(r.output?.trim());
|
|
103
103
|
const status =
|
|
104
104
|
r.exitCode === -1
|
|
105
|
-
? "
|
|
105
|
+
? "SKIPPED"
|
|
106
106
|
: r.exitCode !== 0 && r.exitCode !== null
|
|
107
|
-
?
|
|
107
|
+
? `FAILED (exit code ${r.exitCode})${r.error ? `: ${r.error}` : ""}`
|
|
108
108
|
: r.error
|
|
109
|
-
?
|
|
109
|
+
? `WARNING: ${r.error}`
|
|
110
110
|
: !hasOutput && r.outputTargetPath && r.outputTargetExists === false
|
|
111
|
-
?
|
|
111
|
+
? `EMPTY OUTPUT (expected output file missing: ${r.outputTargetPath})`
|
|
112
112
|
: !hasOutput && !r.outputTargetPath
|
|
113
|
-
? "
|
|
113
|
+
? "EMPTY OUTPUT (no textual response returned)"
|
|
114
114
|
: "";
|
|
115
115
|
const body = status ? (hasOutput ? `${status}\n${r.output}` : status) : r.output;
|
|
116
116
|
return `${header}\n${body}`;
|
|
@@ -78,6 +78,7 @@ interface PromptTemplateBridgeResult {
|
|
|
78
78
|
results?: Array<{
|
|
79
79
|
agent?: string;
|
|
80
80
|
messages?: unknown[];
|
|
81
|
+
finalOutput?: string;
|
|
81
82
|
exitCode?: number;
|
|
82
83
|
error?: string;
|
|
83
84
|
model?: string;
|
|
@@ -180,12 +181,13 @@ function sanitizeRecentTools(
|
|
|
180
181
|
tools: Array<{ tool?: string; args?: string }> | undefined,
|
|
181
182
|
): Array<{ tool: string; args: string }> | undefined {
|
|
182
183
|
if (!tools || tools.length === 0) return undefined;
|
|
183
|
-
const sanitized = tools
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
tool: entry.tool
|
|
184
|
+
const sanitized = tools.flatMap((entry) => {
|
|
185
|
+
if (typeof entry.tool !== "string" || entry.tool.trim().length === 0) return [];
|
|
186
|
+
return [{
|
|
187
|
+
tool: entry.tool,
|
|
187
188
|
args: typeof entry.args === "string" ? entry.args : String(entry.args ?? ""),
|
|
188
|
-
}
|
|
189
|
+
}];
|
|
190
|
+
});
|
|
189
191
|
return sanitized.length > 0 ? sanitized : undefined;
|
|
190
192
|
}
|
|
191
193
|
|
|
@@ -207,6 +209,15 @@ function resolveProgressModel(
|
|
|
207
209
|
return firstWithModel?.model;
|
|
208
210
|
}
|
|
209
211
|
|
|
212
|
+
function buildDelegationMessages(result: { messages?: unknown[]; finalOutput?: string }, fallbackText?: string): unknown[] {
|
|
213
|
+
if (Array.isArray(result.messages) && result.messages.length > 0) return result.messages;
|
|
214
|
+
const text = typeof result.finalOutput === "string" && result.finalOutput.trim().length > 0
|
|
215
|
+
? result.finalOutput.trim()
|
|
216
|
+
: fallbackText;
|
|
217
|
+
if (!text) return [];
|
|
218
|
+
return [{ role: "assistant", content: [{ type: "text", text }] }];
|
|
219
|
+
}
|
|
220
|
+
|
|
210
221
|
function toDelegationUpdate(requestId: string, update: PromptTemplateBridgeResult): PromptTemplateDelegationUpdate | undefined {
|
|
211
222
|
const progress = update.details?.progress?.[0];
|
|
212
223
|
const taskProgress = update.details?.progress?.map((entry) => {
|
|
@@ -324,7 +335,8 @@ export function registerPromptTemplateDelegationBridge<Ctx extends { cwd?: strin
|
|
|
324
335
|
options.events.emit(PROMPT_TEMPLATE_SUBAGENT_UPDATE_EVENT, payload);
|
|
325
336
|
},
|
|
326
337
|
);
|
|
327
|
-
const
|
|
338
|
+
const contentText = firstTextContent(result.content);
|
|
339
|
+
const messages = buildDelegationMessages(result.details?.results?.[0] ?? {}, contentText);
|
|
328
340
|
const parallelResults = request.tasks
|
|
329
341
|
? request.tasks.map<PromptTemplateDelegationParallelResult>((task, index) => {
|
|
330
342
|
const step = result.details?.results?.[index];
|
|
@@ -340,13 +352,12 @@ export function registerPromptTemplateDelegationBridge<Ctx extends { cwd?: strin
|
|
|
340
352
|
const errorText = step.error;
|
|
341
353
|
return {
|
|
342
354
|
agent: step.agent ?? task.agent,
|
|
343
|
-
messages: step
|
|
355
|
+
messages: buildDelegationMessages(step),
|
|
344
356
|
isError: (exitCode !== undefined && exitCode !== 0) || !!errorText,
|
|
345
357
|
errorText: errorText || undefined,
|
|
346
358
|
};
|
|
347
359
|
})
|
|
348
360
|
: undefined;
|
|
349
|
-
const contentText = firstTextContent(result.content);
|
|
350
361
|
const response: PromptTemplateDelegationResponse = {
|
|
351
362
|
...request,
|
|
352
363
|
messages,
|