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.
@@ -15,7 +15,7 @@ import { injectSingleOutputInstruction, resolveSingleOutputPath } from "./single
15
15
  import { isParallelStep, resolveStepBehavior, type ChainStep, type SequentialStep, type StepOverrides } from "./settings.ts";
16
16
  import type { RunnerStep } from "./parallel-utils.ts";
17
17
  import { resolvePiPackageRoot } from "./pi-spawn.ts";
18
- import { buildSkillInjection, normalizeSkillInput, resolveSkills } from "./skills.ts";
18
+ import { buildSkillInjection, normalizeSkillInput, resolveSkillsWithFallback } from "./skills.ts";
19
19
  import { buildModelCandidates, resolveModelCandidate, type AvailableModelInfo } from "./model-fallback.ts";
20
20
  import {
21
21
  type ArtifactConfig,
@@ -23,6 +23,8 @@ import {
23
23
  type MaxOutputConfig,
24
24
  ASYNC_DIR,
25
25
  RESULTS_DIR,
26
+ TEMP_ROOT_DIR,
27
+ getAsyncConfigPath,
26
28
  resolveChildMaxSubagentDepth,
27
29
  } from "./types.ts";
28
30
 
@@ -53,6 +55,7 @@ export interface AsyncExecutionContext {
53
55
  pi: ExtensionAPI;
54
56
  cwd: string;
55
57
  currentSessionId: string;
58
+ currentModelProvider?: string;
56
59
  }
57
60
 
58
61
  export interface AsyncChainParams {
@@ -113,7 +116,8 @@ export function isAsyncAvailable(): boolean {
113
116
  function spawnRunner(cfg: object, suffix: string, cwd: string): number | undefined {
114
117
  if (!jitiCliPath) return undefined;
115
118
 
116
- const cfgPath = path.join(os.tmpdir(), `pi-async-cfg-${suffix}.json`);
119
+ fs.mkdirSync(TEMP_ROOT_DIR, { recursive: true });
120
+ const cfgPath = getAsyncConfigPath(suffix);
117
121
  fs.writeFileSync(cfgPath, JSON.stringify(cfg));
118
122
  const runner = path.join(path.dirname(fileURLToPath(import.meta.url)), "subagent-runner.ts");
119
123
 
@@ -127,6 +131,14 @@ function spawnRunner(cfg: object, suffix: string, cwd: string): number | undefin
127
131
  return proc.pid;
128
132
  }
129
133
 
134
+ function formatAsyncStartError(mode: "single" | "chain", message: string): AsyncExecutionResult {
135
+ return {
136
+ content: [{ type: "text", text: message }],
137
+ isError: true,
138
+ details: { mode, results: [] },
139
+ };
140
+ }
141
+
130
142
  /**
131
143
  * Execute a chain asynchronously
132
144
  */
@@ -152,7 +164,6 @@ export function executeAsyncChain(
152
164
  const chainSkills = params.chainSkills ?? [];
153
165
  const availableModels = params.availableModels;
154
166
 
155
- // Validate all agents exist before building steps
156
167
  for (const s of chain) {
157
168
  const stepAgents = isParallelStep(s)
158
169
  ? s.parallel.map((t) => t.agent)
@@ -180,14 +191,14 @@ export function executeAsyncChain(
180
191
  };
181
192
  }
182
193
 
183
- /** Build a resolved runner step from a SequentialStep */
184
194
  const buildSeqStep = (s: SequentialStep, sessionFile?: string) => {
185
195
  const a = agents.find((x) => x.name === s.agent)!;
186
196
  const stepSkillInput = normalizeSkillInput(s.skill);
187
197
  const stepOverrides: StepOverrides = { skills: stepSkillInput };
188
198
  const behavior = resolveStepBehavior(a, stepOverrides, chainSkills);
189
199
  const skillNames = behavior.skills === false ? [] : behavior.skills;
190
- const { resolved: resolvedSkills } = resolveSkills(skillNames, ctx.cwd);
200
+ const skillCwd = s.cwd ?? cwd ?? ctx.cwd;
201
+ const { resolved: resolvedSkills } = resolveSkillsWithFallback(skillNames, skillCwd, ctx.cwd);
191
202
 
192
203
  let systemPrompt = a.systemPrompt?.trim() || null;
193
204
  if (resolvedSkills.length > 0) {
@@ -195,18 +206,16 @@ export function executeAsyncChain(
195
206
  systemPrompt = systemPrompt ? `${systemPrompt}\n\n${injection}` : injection;
196
207
  }
197
208
 
198
- // Resolve output path and inject instruction into task
199
- // Use step's cwd if specified, otherwise fall back to chain-level cwd
200
209
  const outputPath = resolveSingleOutputPath(s.output, ctx.cwd, s.cwd ?? cwd);
201
210
  const task = injectSingleOutputInstruction(s.task ?? "{previous}", outputPath);
202
211
 
203
- const primaryModel = resolveModelCandidate(s.model ?? a.model, availableModels);
212
+ const primaryModel = resolveModelCandidate(s.model ?? a.model, availableModels, ctx.currentModelProvider);
204
213
  return {
205
214
  agent: s.agent,
206
215
  task,
207
216
  cwd: s.cwd,
208
217
  model: applyThinkingSuffix(primaryModel, a.thinking),
209
- modelCandidates: buildModelCandidates(s.model ?? a.model, a.fallbackModels, availableModels).map((candidate) =>
218
+ modelCandidates: buildModelCandidates(s.model ?? a.model, a.fallbackModels, availableModels, ctx.currentModelProvider).map((candidate) =>
210
219
  applyThinkingSuffix(candidate, a.thinking),
211
220
  ),
212
221
  tools: a.tools,
@@ -227,8 +236,6 @@ export function executeAsyncChain(
227
236
  return sessionFile;
228
237
  };
229
238
 
230
- // Build runner steps — sequential steps become flat objects,
231
- // parallel steps become { parallel: [...], concurrency?, failFast? }
232
239
  const steps: RunnerStep[] = chain.map((s) => {
233
240
  if (isParallelStep(s)) {
234
241
  return {
@@ -249,28 +256,34 @@ export function executeAsyncChain(
249
256
  });
250
257
 
251
258
  const runnerCwd = cwd ?? ctx.cwd;
252
- const pid = spawnRunner(
253
- {
259
+ let pid: number | undefined;
260
+ try {
261
+ pid = spawnRunner(
262
+ {
263
+ id,
264
+ steps,
265
+ resultPath: path.join(RESULTS_DIR, `${id}.json`),
266
+ cwd: runnerCwd,
267
+ placeholder: "{previous}",
268
+ maxOutput,
269
+ artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
270
+ artifactConfig,
271
+ share: shareEnabled,
272
+ sessionDir: sessionRoot ? path.join(sessionRoot, `async-${id}`) : undefined,
273
+ asyncDir,
274
+ sessionId: ctx.currentSessionId,
275
+ piPackageRoot,
276
+ piArgv1: process.argv[1],
277
+ worktreeSetupHook,
278
+ worktreeSetupHookTimeoutMs,
279
+ },
254
280
  id,
255
- steps,
256
- resultPath: path.join(RESULTS_DIR, `${id}.json`),
257
- cwd: runnerCwd,
258
- placeholder: "{previous}",
259
- maxOutput,
260
- artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
261
- artifactConfig,
262
- share: shareEnabled,
263
- sessionDir: sessionRoot ? path.join(sessionRoot, `async-${id}`) : undefined,
264
- asyncDir,
265
- sessionId: ctx.currentSessionId,
266
- piPackageRoot,
267
- piArgv1: process.argv[1],
268
- worktreeSetupHook,
269
- worktreeSetupHookTimeoutMs,
270
- },
271
- id,
272
- runnerCwd,
273
- );
281
+ runnerCwd,
282
+ );
283
+ } catch (error) {
284
+ const message = error instanceof Error ? error.message : String(error);
285
+ return formatAsyncStartError("chain", `Failed to start async chain '${id}': ${message}`);
286
+ }
274
287
 
275
288
  if (pid) {
276
289
  const firstStep = chain[0];
@@ -292,7 +305,6 @@ export function executeAsyncChain(
292
305
  });
293
306
  }
294
307
 
295
- // Build chain description with parallel groups shown as [agent1+agent2]
296
308
  const chainDesc = chain
297
309
  .map((s) =>
298
310
  isParallelStep(s) ? `[${s.parallel.map((t) => t.agent).join("+")}]` : (s as SequentialStep).agent,
@@ -330,7 +342,8 @@ export function executeAsyncSingle(
330
342
  } = params;
331
343
  const skillNames = params.skills ?? agentConfig.skills ?? [];
332
344
  const availableModels = params.availableModels;
333
- const { resolved: resolvedSkills } = resolveSkills(skillNames, ctx.cwd);
345
+ const skillCwd = cwd ?? ctx.cwd;
346
+ const { resolved: resolvedSkills } = resolveSkillsWithFallback(skillNames, skillCwd, ctx.cwd);
334
347
  let systemPrompt = agentConfig.systemPrompt?.trim() || null;
335
348
  if (resolvedSkills.length > 0) {
336
349
  const injection = buildSkillInjection(resolvedSkills);
@@ -352,46 +365,52 @@ export function executeAsyncSingle(
352
365
  const runnerCwd = cwd ?? ctx.cwd;
353
366
  const outputPath = resolveSingleOutputPath(params.output, ctx.cwd, cwd);
354
367
  const taskWithOutputInstruction = injectSingleOutputInstruction(task, outputPath);
355
- const pid = spawnRunner(
356
- {
368
+ let pid: number | undefined;
369
+ try {
370
+ pid = spawnRunner(
371
+ {
372
+ id,
373
+ steps: [
374
+ {
375
+ agent,
376
+ task: taskWithOutputInstruction,
377
+ cwd,
378
+ model: applyThinkingSuffix(resolveModelCandidate(params.modelOverride ?? agentConfig.model, availableModels, ctx.currentModelProvider), agentConfig.thinking),
379
+ modelCandidates: buildModelCandidates(params.modelOverride ?? agentConfig.model, agentConfig.fallbackModels, availableModels, ctx.currentModelProvider).map((candidate) =>
380
+ applyThinkingSuffix(candidate, agentConfig.thinking),
381
+ ),
382
+ tools: agentConfig.tools,
383
+ extensions: agentConfig.extensions,
384
+ mcpDirectTools: agentConfig.mcpDirectTools,
385
+ systemPrompt,
386
+ skills: resolvedSkills.map((r) => r.name),
387
+ outputPath,
388
+ sessionFile,
389
+ maxSubagentDepth: resolveChildMaxSubagentDepth(maxSubagentDepth, agentConfig.maxSubagentDepth),
390
+ },
391
+ ],
392
+ resultPath: path.join(RESULTS_DIR, `${id}.json`),
393
+ cwd: runnerCwd,
394
+ placeholder: "{previous}",
395
+ maxOutput,
396
+ artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
397
+ artifactConfig,
398
+ share: shareEnabled,
399
+ sessionDir: sessionRoot ? path.join(sessionRoot, `async-${id}`) : undefined,
400
+ asyncDir,
401
+ sessionId: ctx.currentSessionId,
402
+ piPackageRoot,
403
+ piArgv1: process.argv[1],
404
+ worktreeSetupHook,
405
+ worktreeSetupHookTimeoutMs,
406
+ },
357
407
  id,
358
- steps: [
359
- {
360
- agent,
361
- task: taskWithOutputInstruction,
362
- cwd,
363
- model: applyThinkingSuffix(resolveModelCandidate(params.modelOverride ?? agentConfig.model, availableModels), agentConfig.thinking),
364
- modelCandidates: buildModelCandidates(params.modelOverride ?? agentConfig.model, agentConfig.fallbackModels, availableModels).map((candidate) =>
365
- applyThinkingSuffix(candidate, agentConfig.thinking),
366
- ),
367
- tools: agentConfig.tools,
368
- extensions: agentConfig.extensions,
369
- mcpDirectTools: agentConfig.mcpDirectTools,
370
- systemPrompt,
371
- skills: resolvedSkills.map((r) => r.name),
372
- outputPath,
373
- sessionFile,
374
- maxSubagentDepth: resolveChildMaxSubagentDepth(maxSubagentDepth, agentConfig.maxSubagentDepth),
375
- },
376
- ],
377
- resultPath: path.join(RESULTS_DIR, `${id}.json`),
378
- cwd: runnerCwd,
379
- placeholder: "{previous}",
380
- maxOutput,
381
- artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
382
- artifactConfig,
383
- share: shareEnabled,
384
- sessionDir: sessionRoot ? path.join(sessionRoot, `async-${id}`) : undefined,
385
- asyncDir,
386
- sessionId: ctx.currentSessionId,
387
- piPackageRoot,
388
- piArgv1: process.argv[1],
389
- worktreeSetupHook,
390
- worktreeSetupHookTimeoutMs,
391
- },
392
- id,
393
- runnerCwd,
394
- );
408
+ runnerCwd,
409
+ );
410
+ } catch (error) {
411
+ const message = error instanceof Error ? error.message : String(error);
412
+ return formatAsyncStartError("single", `Failed to start async run '${id}': ${message}`);
413
+ }
395
414
 
396
415
  if (pid) {
397
416
  ctx.pi.events.emit("subagent:started", {