pi-subagents 0.13.4 → 0.14.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 +11 -0
- package/README.md +32 -11
- package/agent-management.ts +15 -6
- package/agent-manager-detail.ts +12 -2
- package/agent-manager-edit.ts +75 -23
- package/agent-manager-list.ts +9 -2
- 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 +45 -156
- package/chain-execution.ts +23 -63
- package/execution.ts +53 -48
- package/index.ts +1 -1
- package/model-fallback.ts +8 -2
- package/package.json +1 -1
- package/schemas.ts +1 -1
- package/settings.ts +6 -4
- package/skills.ts +165 -75
- package/subagent-executor.ts +43 -9
- package/subagent-runner.ts +167 -50
- package/types.ts +64 -13
- package/utils.ts +5 -10
package/chain-execution.ts
CHANGED
|
@@ -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,
|
|
@@ -724,8 +687,6 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
724
687
|
}
|
|
725
688
|
}
|
|
726
689
|
|
|
727
|
-
// Chain complete - return summary with paths
|
|
728
|
-
// Chain dir left for inspection (cleaned up after 24h)
|
|
729
690
|
const summary = buildChainSummary(chainSteps, results, chainDir, "completed");
|
|
730
691
|
|
|
731
692
|
return {
|
|
@@ -737,7 +698,6 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
737
698
|
artifacts: allArtifactPaths.length ? { dir: artifactsDir, files: allArtifactPaths } : undefined,
|
|
738
699
|
chainAgents,
|
|
739
700
|
totalSteps,
|
|
740
|
-
// currentStepIndex omitted for completed chains
|
|
741
701
|
},
|
|
742
702
|
};
|
|
743
703
|
}
|
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/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/schemas.ts
CHANGED
|
@@ -83,7 +83,7 @@ export const SubagentParams = Type.Object({
|
|
|
83
83
|
enum: ["fresh", "fork"],
|
|
84
84
|
description: "'fresh' (default) or 'fork' to branch from parent session",
|
|
85
85
|
})),
|
|
86
|
-
chainDir: Type.Optional(Type.String({ description: "Persistent directory for chain artifacts. Default: <tmpdir>/
|
|
86
|
+
chainDir: Type.Optional(Type.String({ description: "Persistent directory for chain artifacts. Default: a user-scoped temp directory under <tmpdir>/ (auto-cleaned after 24h)" })),
|
|
87
87
|
async: Type.Optional(Type.Boolean({ description: "Run in background (default: false, or per config)" })),
|
|
88
88
|
agentScope: Type.Optional(Type.String({ description: "Agent discovery scope: 'user', 'project', or 'both' (default: 'both'; project wins on name collisions)" })),
|
|
89
89
|
cwd: Type.Optional(Type.String()),
|
package/settings.ts
CHANGED
|
@@ -3,12 +3,10 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import * as fs from "node:fs";
|
|
6
|
-
import * as os from "node:os";
|
|
7
6
|
import * as path from "node:path";
|
|
8
7
|
import type { AgentConfig } from "./agents.ts";
|
|
9
8
|
import { normalizeSkillInput } from "./skills.ts";
|
|
10
|
-
|
|
11
|
-
const CHAIN_RUNS_DIR = path.join(os.tmpdir(), "pi-chain-runs");
|
|
9
|
+
import { CHAIN_RUNS_DIR } from "./types.ts";
|
|
12
10
|
const CHAIN_DIR_MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
13
11
|
|
|
14
12
|
// =============================================================================
|
|
@@ -100,7 +98,9 @@ export function createChainDir(runId: string, baseDir?: string): string {
|
|
|
100
98
|
export function removeChainDir(chainDir: string): void {
|
|
101
99
|
try {
|
|
102
100
|
fs.rmSync(chainDir, { recursive: true });
|
|
103
|
-
} catch {
|
|
101
|
+
} catch {
|
|
102
|
+
// Chain cleanup is best-effort. Runs can already have cleaned their temp dir.
|
|
103
|
+
}
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
export function cleanupOldChainDirs(): void {
|
|
@@ -110,6 +110,8 @@ export function cleanupOldChainDirs(): void {
|
|
|
110
110
|
try {
|
|
111
111
|
dirs = fs.readdirSync(CHAIN_RUNS_DIR);
|
|
112
112
|
} catch {
|
|
113
|
+
// Startup cleanup is best-effort. If the scoped temp root is unreadable,
|
|
114
|
+
// skip cleanup instead of failing extension startup.
|
|
113
115
|
return;
|
|
114
116
|
}
|
|
115
117
|
|