pi-subagents 0.14.1 → 0.15.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.
@@ -3,35 +3,33 @@ name: researcher
3
3
  description: Autonomous web researcher — searches, evaluates, and synthesizes a focused research brief
4
4
  tools: read, write, web_search, fetch_content, get_search_content
5
5
  model: anthropic/claude-sonnet-4-6
6
+ systemPromptMode: replace
7
+ inheritProjectContext: true
8
+ inheritSkills: false
6
9
  output: research.md
7
10
  defaultProgress: true
8
11
  ---
9
12
 
10
- You are a research specialist. Given a question or topic, conduct thorough web research and produce a focused, well-sourced brief.
13
+ You are a research subagent.
11
14
 
12
- Process:
13
- 1. Break the question into 2-4 searchable facets
14
- 2. Search with `web_search` using `queries` (parallel, varied angles) and `curate: false`
15
- 3. Read the answers. Identify what's well-covered, what has gaps, what's noise.
16
- 4. For the 2-3 most promising source URLs, use `fetch_content` to get full page content
17
- 5. Synthesize everything into a brief that directly answers the question
15
+ Given a question or topic, run focused web research and produce a concise, well-sourced brief that answers the question directly.
18
16
 
19
- Search strategy — always vary your angles:
20
- - Direct answer query (the obvious one)
21
- - Authoritative source query (official docs, specs, primary sources)
22
- - Practical experience query (case studies, benchmarks, real-world usage)
23
- - Recent developments query (only if the topic is time-sensitive)
17
+ Working rules:
18
+ - Break the problem into 2-4 distinct research angles.
19
+ - Use `web_search` with `queries` so the search covers multiple angles instead of one generic query.
20
+ - Use `workflow: "none"` unless the task explicitly needs the interactive curator.
21
+ - Read the search results first. Then fetch full content only for the most promising source URLs.
22
+ - Prefer primary sources, official docs, specs, benchmarks, and direct evidence over commentary.
23
+ - Drop stale, redundant, or SEO-heavy sources.
24
+ - If the first search pass leaves important gaps, search again with tighter follow-up queries.
24
25
 
25
- Evaluation — what to keep vs drop:
26
- - Official docs and primary sources outweigh blog posts and forum threads
27
- - Recent sources outweigh stale ones (check URL path for dates like /2025/01/)
28
- - Sources that directly address the question outweigh tangentially related ones
29
- - Diverse perspectives outweigh redundant coverage of the same point
30
- - Drop: SEO filler, outdated info, beginner tutorials (unless that's the audience)
26
+ Search strategy:
27
+ - direct answer query
28
+ - authoritative source query
29
+ - practical experience or benchmark query
30
+ - recent developments query when the topic is time-sensitive
31
31
 
32
- If the first round of searches doesn't fully answer the question, search again with refined queries targeting the gaps. Don't settle for partial answers when a follow-up search could fill them.
33
-
34
- Output format (research.md):
32
+ Output format (`research.md`):
35
33
 
36
34
  # Research: [topic]
37
35
 
@@ -39,13 +37,13 @@ Output format (research.md):
39
37
  2-3 sentence direct answer.
40
38
 
41
39
  ## Findings
42
- Numbered findings with inline source citations:
40
+ Numbered findings with inline source citations.
43
41
  1. **Finding** — explanation. [Source](url)
44
42
  2. **Finding** — explanation. [Source](url)
45
43
 
46
44
  ## Sources
47
- - Kept: Source Title (url) — why relevant
48
- - Dropped: Source Title — why excluded
45
+ - Kept: Source Title (url) — why it matters
46
+ - Dropped: Source Title — why it was excluded
49
47
 
50
48
  ## Gaps
51
- What couldn't be answered. Suggested next steps.
49
+ What could not be answered confidently. Suggested next steps.
@@ -1,30 +1,38 @@
1
1
  ---
2
2
  name: reviewer
3
3
  description: Code review specialist that validates implementation and fixes issues
4
- tools: read, grep, find, ls, bash
4
+ tools: read, grep, find, ls, bash, edit, write
5
5
  model: openai-codex/gpt-5.3-codex
6
6
  thinking: high
7
+ systemPromptMode: replace
8
+ inheritProjectContext: true
9
+ inheritSkills: false
7
10
  defaultReads: plan.md, progress.md
8
11
  defaultProgress: true
9
12
  ---
10
13
 
11
- You are a senior code reviewer. Analyze implementation against the plan.
14
+ You are a review-and-fix subagent.
12
15
 
13
- When running in a chain, you'll receive instructions about which files to read (plan and progress) and where to update progress.
16
+ Review the implementation against the plan, inspect the actual code, and fix any real problems you find.
14
17
 
15
- Bash is for read-only commands only: `git diff`, `git log`, `git show`.
18
+ Working rules:
19
+ - Read the plan and current progress first when they are provided.
20
+ - Use `bash` only for read-only inspection commands like `git diff`, `git log`, `git show`, or test commands.
21
+ - Do not invent issues. Only report or fix problems you can justify from the code, tests, or requirements.
22
+ - Prefer small corrective edits over broad rewrites.
23
+ - If everything looks good, say so plainly and leave the code unchanged.
24
+ - If you are asked to maintain progress, record what you checked and what you fixed.
16
25
 
17
26
  Review checklist:
18
- 1. Implementation matches plan requirements
19
- 2. Code quality and correctness
20
- 3. Edge cases handled
21
- 4. Security considerations
27
+ 1. Implementation matches the plan and task requirements.
28
+ 2. Code is correct and coherent.
29
+ 3. Important edge cases are handled.
30
+ 4. Tests and validation still make sense.
31
+ 5. The final code is readable and minimal.
22
32
 
23
- If issues found, fix them directly.
24
-
25
- Update progress.md with:
33
+ When updating `progress.md`, add a review section like this:
26
34
 
27
35
  ## Review
28
- - What's correct
29
- - Fixed: Issue and resolution
30
- - Note: Observations
36
+ - Correct: what is already good
37
+ - Fixed: issue and resolution
38
+ - Note: observations or follow-up items
package/agents/scout.md CHANGED
@@ -3,40 +3,45 @@ name: scout
3
3
  description: Fast codebase recon that returns compressed context for handoff
4
4
  tools: read, grep, find, ls, bash, write
5
5
  model: anthropic/claude-haiku-4-5
6
+ systemPromptMode: replace
7
+ inheritProjectContext: true
8
+ inheritSkills: false
6
9
  output: context.md
7
10
  defaultProgress: true
8
11
  ---
9
12
 
10
- You are a scout. Quickly investigate a codebase and return structured findings.
13
+ You are a scouting subagent running inside pi.
11
14
 
12
- When running in a chain, you'll receive instructions about where to write your output.
13
- When running solo, write to the provided output path and summarize what you found.
15
+ Use the provided tools directly. Move fast, but do not guess. Prefer targeted search and selective reading over reading whole files unless the task clearly needs broader coverage.
14
16
 
15
- Thoroughness (infer from task, default medium):
16
- - Quick: Targeted lookups, key files only
17
- - Medium: Follow imports, read critical sections
18
- - Thorough: Trace all dependencies, check tests/types
17
+ Focus on the minimum context another agent needs in order to act:
18
+ - relevant entry points
19
+ - key types, interfaces, and functions
20
+ - data flow and dependencies
21
+ - files that are likely to need changes
22
+ - constraints, risks, and open questions
19
23
 
20
- Strategy:
21
- 1. grep/find to locate relevant code
22
- 2. Read key sections (not entire files)
23
- 3. Identify types, interfaces, key functions
24
- 4. Note dependencies between files
24
+ Working rules:
25
+ - Use `grep`, `find`, `ls`, and `read` to map the area before diving deeper.
26
+ - Use `bash` only for non-interactive inspection commands.
27
+ - When you cite code, use exact file paths and line ranges.
28
+ - If you are told to write output, write it to the provided path and keep the final response short.
29
+ - When running solo, summarize what you found after writing the output.
25
30
 
26
- Your output format (context.md):
31
+ Output format (`context.md`):
27
32
 
28
33
  # Code Context
29
34
 
30
35
  ## Files Retrieved
31
- List with exact line ranges:
32
- 1. `path/to/file.ts` (lines 10-50) - Description
33
- 2. `path/to/other.ts` (lines 100-150) - Description
36
+ List exact files and line ranges.
37
+ 1. `path/to/file.ts` (lines 10-50) - why it matters
38
+ 2. `path/to/other.ts` (lines 100-150) - why it matters
34
39
 
35
40
  ## Key Code
36
- Critical types, interfaces, or functions with actual code snippets.
41
+ Include the critical types, interfaces, functions, and small code snippets that matter.
37
42
 
38
43
  ## Architecture
39
- Brief explanation of how the pieces connect.
44
+ Explain how the pieces connect.
40
45
 
41
46
  ## Start Here
42
- Which file to look at first and why.
47
+ Name the first file another agent should open and why.
package/agents/worker.md CHANGED
@@ -1,20 +1,32 @@
1
1
  ---
2
2
  name: worker
3
- description: General-purpose subagent with full capabilities, isolated context
3
+ description: General-purpose subagent with full capabilities
4
4
  model: claude-sonnet-4-6
5
+ systemPromptMode: replace
6
+ inheritProjectContext: true
7
+ inheritSkills: false
5
8
  defaultReads: context.md, plan.md
6
9
  defaultProgress: true
7
10
  ---
8
11
 
9
- You are a worker agent with full capabilities. You operate in an isolated context window.
12
+ You are an implementation subagent.
10
13
 
11
- When running in a chain, you'll receive instructions about:
12
- - Which files to read (context from previous steps)
13
- - Where to maintain progress tracking
14
+ Use the provided tools directly to complete the task. Read the supplied context first, then make the smallest correct set of changes needed to finish the job.
14
15
 
15
- Work autonomously to complete the assigned task. Use all available tools as needed.
16
+ Working rules:
17
+ - Follow existing patterns in the codebase.
18
+ - Prefer simple changes over clever ones.
19
+ - Do not leave speculative scaffolding, placeholder code, or TODOs unless the task explicitly requires them.
20
+ - Run relevant tests or validation commands when you can.
21
+ - If you are asked to maintain progress, keep it accurate and up to date.
22
+ - When you finish, summarize what changed, what you verified, and anything still unresolved.
16
23
 
17
- Progress.md format:
24
+ When running in a chain, expect instructions about:
25
+ - which files to read first
26
+ - where to maintain progress tracking
27
+ - where to write output if a file target is provided
28
+
29
+ Suggested `progress.md` structure when asked to maintain it:
18
30
 
19
31
  # Progress
20
32
 
@@ -29,4 +41,4 @@ Progress.md format:
29
41
  - `path/to/file.ts` - what changed
30
42
 
31
43
  ## Notes
32
- Any blockers or decisions.
44
+ Key decisions, blockers, or follow-up items.
package/agents.ts CHANGED
@@ -14,11 +14,27 @@ import { parseFrontmatter } from "./frontmatter.ts";
14
14
  export type AgentScope = "user" | "project" | "both";
15
15
 
16
16
  export type AgentSource = "builtin" | "user" | "project";
17
+ export type SystemPromptMode = "append" | "replace";
18
+
19
+ export function defaultSystemPromptMode(name: string): SystemPromptMode {
20
+ return name === "delegate" ? "append" : "replace";
21
+ }
22
+
23
+ export function defaultInheritProjectContext(name: string): boolean {
24
+ return name === "delegate";
25
+ }
26
+
27
+ export function defaultInheritSkills(): boolean {
28
+ return false;
29
+ }
17
30
 
18
31
  export interface BuiltinAgentOverrideBase {
19
32
  model?: string;
20
33
  fallbackModels?: string[];
21
34
  thinking?: string;
35
+ systemPromptMode: SystemPromptMode;
36
+ inheritProjectContext: boolean;
37
+ inheritSkills: boolean;
22
38
  systemPrompt: string;
23
39
  skills?: string[];
24
40
  tools?: string[];
@@ -29,6 +45,9 @@ export interface BuiltinAgentOverrideConfig {
29
45
  model?: string | false;
30
46
  fallbackModels?: string[] | false;
31
47
  thinking?: string | false;
48
+ systemPromptMode?: SystemPromptMode;
49
+ inheritProjectContext?: boolean;
50
+ inheritSkills?: boolean;
32
51
  systemPrompt?: string;
33
52
  skills?: string[] | false;
34
53
  tools?: string[] | false;
@@ -48,6 +67,9 @@ export interface AgentConfig {
48
67
  model?: string;
49
68
  fallbackModels?: string[];
50
69
  thinking?: string;
70
+ systemPromptMode: SystemPromptMode;
71
+ inheritProjectContext: boolean;
72
+ inheritSkills: boolean;
51
73
  systemPrompt: string;
52
74
  source: AgentSource;
53
75
  filePath: string;
@@ -125,6 +147,9 @@ function cloneOverrideBase(agent: AgentConfig): BuiltinAgentOverrideBase {
125
147
  model: agent.model,
126
148
  fallbackModels: agent.fallbackModels ? [...agent.fallbackModels] : undefined,
127
149
  thinking: agent.thinking,
150
+ systemPromptMode: agent.systemPromptMode,
151
+ inheritProjectContext: agent.inheritProjectContext,
152
+ inheritSkills: agent.inheritSkills,
128
153
  systemPrompt: agent.systemPrompt,
129
154
  skills: agent.skills ? [...agent.skills] : undefined,
130
155
  tools: agent.tools ? [...agent.tools] : undefined,
@@ -139,6 +164,9 @@ function cloneOverrideValue(override: BuiltinAgentOverrideConfig): BuiltinAgentO
139
164
  ? { fallbackModels: override.fallbackModels === false ? false : [...override.fallbackModels] }
140
165
  : {}),
141
166
  ...(override.thinking !== undefined ? { thinking: override.thinking } : {}),
167
+ ...(override.systemPromptMode !== undefined ? { systemPromptMode: override.systemPromptMode } : {}),
168
+ ...(override.inheritProjectContext !== undefined ? { inheritProjectContext: override.inheritProjectContext } : {}),
169
+ ...(override.inheritSkills !== undefined ? { inheritSkills: override.inheritSkills } : {}),
142
170
  ...(override.systemPrompt !== undefined ? { systemPrompt: override.systemPrompt } : {}),
143
171
  ...(override.skills !== undefined ? { skills: override.skills === false ? false : [...override.skills] } : {}),
144
172
  ...(override.tools !== undefined ? { tools: override.tools === false ? false : [...override.tools] } : {}),
@@ -187,29 +215,85 @@ function writeSettingsFile(filePath: string, settings: Record<string, unknown>):
187
215
  fs.writeFileSync(filePath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
188
216
  }
189
217
 
190
- function parseStringArrayOrFalse(value: unknown): string[] | false | undefined {
218
+ function parseOverrideStringArrayOrFalse(
219
+ value: unknown,
220
+ meta: { filePath: string; name: string; field: string },
221
+ ): string[] | false | undefined {
222
+ if (value === undefined) return undefined;
191
223
  if (value === false) return false;
192
- if (!Array.isArray(value)) return undefined;
193
- const items = value.filter((item): item is string => typeof item === "string").map((item) => item.trim()).filter(Boolean);
224
+ if (!Array.isArray(value)) {
225
+ throw new Error(`Builtin override '${meta.name}' in '${meta.filePath}' has invalid '${meta.field}'; expected an array of strings or false.`);
226
+ }
227
+
228
+ const items: string[] = [];
229
+ for (const item of value) {
230
+ if (typeof item !== "string") {
231
+ throw new Error(`Builtin override '${meta.name}' in '${meta.filePath}' has invalid '${meta.field}'; expected an array of strings or false.`);
232
+ }
233
+ const trimmed = item.trim();
234
+ if (trimmed) items.push(trimmed);
235
+ }
194
236
  return items;
195
237
  }
196
238
 
197
- function parseBuiltinOverrideEntry(value: unknown): BuiltinAgentOverrideConfig | undefined {
198
- if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
239
+ function parseBuiltinOverrideEntry(
240
+ name: string,
241
+ value: unknown,
242
+ filePath: string,
243
+ ): BuiltinAgentOverrideConfig | undefined {
244
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
245
+ throw new Error(`Builtin override '${name}' in '${filePath}' must be an object.`);
246
+ }
247
+
199
248
  const input = value as Record<string, unknown>;
200
249
  const override: BuiltinAgentOverrideConfig = {};
201
250
 
202
- if (typeof input.model === "string" || input.model === false) override.model = input.model;
203
- if (typeof input.thinking === "string" || input.thinking === false) override.thinking = input.thinking;
204
- if (typeof input.systemPrompt === "string") override.systemPrompt = input.systemPrompt;
251
+ if ("model" in input) {
252
+ if (typeof input.model === "string" || input.model === false) override.model = input.model;
253
+ else throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'model'; expected a string or false.`);
254
+ }
255
+
256
+ if ("thinking" in input) {
257
+ if (typeof input.thinking === "string" || input.thinking === false) override.thinking = input.thinking;
258
+ else throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'thinking'; expected a string or false.`);
259
+ }
260
+
261
+ if ("systemPromptMode" in input) {
262
+ if (input.systemPromptMode === "append" || input.systemPromptMode === "replace") {
263
+ override.systemPromptMode = input.systemPromptMode;
264
+ } else {
265
+ throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'systemPromptMode'; expected 'append' or 'replace'.`);
266
+ }
267
+ }
268
+
269
+ if ("inheritProjectContext" in input) {
270
+ if (typeof input.inheritProjectContext === "boolean") {
271
+ override.inheritProjectContext = input.inheritProjectContext;
272
+ } else {
273
+ throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'inheritProjectContext'; expected a boolean.`);
274
+ }
275
+ }
276
+
277
+ if ("inheritSkills" in input) {
278
+ if (typeof input.inheritSkills === "boolean") {
279
+ override.inheritSkills = input.inheritSkills;
280
+ } else {
281
+ throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'inheritSkills'; expected a boolean.`);
282
+ }
283
+ }
284
+
285
+ if ("systemPrompt" in input) {
286
+ if (typeof input.systemPrompt === "string") override.systemPrompt = input.systemPrompt;
287
+ else throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'systemPrompt'; expected a string.`);
288
+ }
205
289
 
206
- const fallbackModels = parseStringArrayOrFalse(input.fallbackModels);
290
+ const fallbackModels = parseOverrideStringArrayOrFalse(input.fallbackModels, { filePath, name, field: "fallbackModels" });
207
291
  if (fallbackModels !== undefined) override.fallbackModels = fallbackModels;
208
292
 
209
- const skills = parseStringArrayOrFalse(input.skills);
293
+ const skills = parseOverrideStringArrayOrFalse(input.skills, { filePath, name, field: "skills" });
210
294
  if (skills !== undefined) override.skills = skills;
211
295
 
212
- const tools = parseStringArrayOrFalse(input.tools);
296
+ const tools = parseOverrideStringArrayOrFalse(input.tools, { filePath, name, field: "tools" });
213
297
  if (tools !== undefined) override.tools = tools;
214
298
 
215
299
  return Object.keys(override).length > 0 ? override : undefined;
@@ -225,7 +309,7 @@ function readBuiltinOverrides(filePath: string | null): Record<string, BuiltinAg
225
309
 
226
310
  const parsed: Record<string, BuiltinAgentOverrideConfig> = {};
227
311
  for (const [name, value] of Object.entries(agentOverrides)) {
228
- const override = parseBuiltinOverrideEntry(value);
312
+ const override = parseBuiltinOverrideEntry(name, value, filePath);
229
313
  if (override) parsed[name] = override;
230
314
  }
231
315
  return parsed;
@@ -246,6 +330,9 @@ function applyBuiltinOverride(
246
330
  next.fallbackModels = override.fallbackModels === false ? undefined : [...override.fallbackModels];
247
331
  }
248
332
  if (override.thinking !== undefined) next.thinking = override.thinking === false ? undefined : override.thinking;
333
+ if (override.systemPromptMode !== undefined) next.systemPromptMode = override.systemPromptMode;
334
+ if (override.inheritProjectContext !== undefined) next.inheritProjectContext = override.inheritProjectContext;
335
+ if (override.inheritSkills !== undefined) next.inheritSkills = override.inheritSkills;
249
336
  if (override.systemPrompt !== undefined) next.systemPrompt = override.systemPrompt;
250
337
  if (override.skills !== undefined) next.skills = override.skills === false ? undefined : [...override.skills];
251
338
  if (override.tools !== undefined) {
@@ -281,13 +368,16 @@ function applyBuiltinOverrides(
281
368
 
282
369
  export function buildBuiltinOverrideConfig(
283
370
  base: BuiltinAgentOverrideBase,
284
- draft: Pick<AgentConfig, "model" | "fallbackModels" | "thinking" | "systemPrompt" | "skills" | "tools" | "mcpDirectTools">,
371
+ draft: Pick<AgentConfig, "model" | "fallbackModels" | "thinking" | "systemPromptMode" | "inheritProjectContext" | "inheritSkills" | "systemPrompt" | "skills" | "tools" | "mcpDirectTools">,
285
372
  ): BuiltinAgentOverrideConfig | undefined {
286
373
  const override: BuiltinAgentOverrideConfig = {};
287
374
 
288
375
  if (draft.model !== base.model) override.model = draft.model ?? false;
289
376
  if (!arraysEqual(draft.fallbackModels, base.fallbackModels)) override.fallbackModels = draft.fallbackModels ? [...draft.fallbackModels] : false;
290
377
  if (draft.thinking !== base.thinking) override.thinking = draft.thinking ?? false;
378
+ if (draft.systemPromptMode !== base.systemPromptMode) override.systemPromptMode = draft.systemPromptMode;
379
+ if (draft.inheritProjectContext !== base.inheritProjectContext) override.inheritProjectContext = draft.inheritProjectContext;
380
+ if (draft.inheritSkills !== base.inheritSkills) override.inheritSkills = draft.inheritSkills;
291
381
  if (draft.systemPrompt !== base.systemPrompt) override.systemPrompt = draft.systemPrompt;
292
382
  if (!arraysEqual(draft.skills, base.skills)) override.skills = draft.skills ? [...draft.skills] : false;
293
383
 
@@ -410,6 +500,21 @@ function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {
410
500
  ?.split(",")
411
501
  .map((model) => model.trim())
412
502
  .filter(Boolean);
503
+ const systemPromptMode = frontmatter.systemPromptMode === "replace"
504
+ ? "replace"
505
+ : frontmatter.systemPromptMode === "append"
506
+ ? "append"
507
+ : defaultSystemPromptMode(frontmatter.name);
508
+ const inheritProjectContext = frontmatter.inheritProjectContext === "true"
509
+ ? true
510
+ : frontmatter.inheritProjectContext === "false"
511
+ ? false
512
+ : defaultInheritProjectContext(frontmatter.name);
513
+ const inheritSkills = frontmatter.inheritSkills === "true"
514
+ ? true
515
+ : frontmatter.inheritSkills === "false"
516
+ ? false
517
+ : defaultInheritSkills();
413
518
 
414
519
  let extensions: string[] | undefined;
415
520
  if (frontmatter.extensions !== undefined) {
@@ -434,6 +539,9 @@ function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {
434
539
  model: frontmatter.model,
435
540
  fallbackModels: fallbackModels && fallbackModels.length > 0 ? fallbackModels : undefined,
436
541
  thinking: frontmatter.thinking,
542
+ systemPromptMode,
543
+ inheritProjectContext,
544
+ inheritSkills,
437
545
  systemPrompt: body,
438
546
  source,
439
547
  filePath,
@@ -16,6 +16,7 @@ import { isParallelStep, resolveStepBehavior, type ChainStep, type SequentialSte
16
16
  import type { RunnerStep } from "./parallel-utils.ts";
17
17
  import { resolvePiPackageRoot } from "./pi-spawn.ts";
18
18
  import { buildSkillInjection, normalizeSkillInput, resolveSkillsWithFallback } from "./skills.ts";
19
+ import { resolveChildCwd } from "./utils.ts";
19
20
  import { buildModelCandidates, resolveModelCandidate, type AvailableModelInfo } from "./model-fallback.ts";
20
21
  import {
21
22
  type ArtifactConfig,
@@ -163,6 +164,7 @@ export function executeAsyncChain(
163
164
  } = params;
164
165
  const chainSkills = params.chainSkills ?? [];
165
166
  const availableModels = params.availableModels;
167
+ const runnerCwd = resolveChildCwd(ctx.cwd, cwd);
166
168
 
167
169
  for (const s of chain) {
168
170
  const stepAgents = isParallelStep(s)
@@ -193,27 +195,27 @@ export function executeAsyncChain(
193
195
 
194
196
  const buildSeqStep = (s: SequentialStep, sessionFile?: string) => {
195
197
  const a = agents.find((x) => x.name === s.agent)!;
198
+ const stepCwd = resolveChildCwd(runnerCwd, s.cwd);
196
199
  const stepSkillInput = normalizeSkillInput(s.skill);
197
200
  const stepOverrides: StepOverrides = { skills: stepSkillInput };
198
201
  const behavior = resolveStepBehavior(a, stepOverrides, chainSkills);
199
202
  const skillNames = behavior.skills === false ? [] : behavior.skills;
200
- const skillCwd = s.cwd ?? cwd ?? ctx.cwd;
201
- const { resolved: resolvedSkills } = resolveSkillsWithFallback(skillNames, skillCwd, ctx.cwd);
203
+ const { resolved: resolvedSkills } = resolveSkillsWithFallback(skillNames, stepCwd, ctx.cwd);
202
204
 
203
- let systemPrompt = a.systemPrompt?.trim() || null;
205
+ let systemPrompt = a.systemPrompt?.trim() ?? "";
204
206
  if (resolvedSkills.length > 0) {
205
207
  const injection = buildSkillInjection(resolvedSkills);
206
208
  systemPrompt = systemPrompt ? `${systemPrompt}\n\n${injection}` : injection;
207
209
  }
208
210
 
209
- const outputPath = resolveSingleOutputPath(s.output, ctx.cwd, s.cwd ?? cwd);
211
+ const outputPath = resolveSingleOutputPath(s.output, ctx.cwd, stepCwd);
210
212
  const task = injectSingleOutputInstruction(s.task ?? "{previous}", outputPath);
211
213
 
212
214
  const primaryModel = resolveModelCandidate(s.model ?? a.model, availableModels, ctx.currentModelProvider);
213
215
  return {
214
216
  agent: s.agent,
215
217
  task,
216
- cwd: s.cwd,
218
+ cwd: stepCwd,
217
219
  model: applyThinkingSuffix(primaryModel, a.thinking),
218
220
  modelCandidates: buildModelCandidates(s.model ?? a.model, a.fallbackModels, availableModels, ctx.currentModelProvider).map((candidate) =>
219
221
  applyThinkingSuffix(candidate, a.thinking),
@@ -222,6 +224,9 @@ export function executeAsyncChain(
222
224
  extensions: a.extensions,
223
225
  mcpDirectTools: a.mcpDirectTools,
224
226
  systemPrompt,
227
+ systemPromptMode: a.systemPromptMode,
228
+ inheritProjectContext: a.inheritProjectContext,
229
+ inheritSkills: a.inheritSkills,
225
230
  skills: resolvedSkills.map((r) => r.name),
226
231
  outputPath,
227
232
  sessionFile,
@@ -255,7 +260,6 @@ export function executeAsyncChain(
255
260
  return buildSeqStep(s as SequentialStep, nextSessionFile());
256
261
  });
257
262
 
258
- const runnerCwd = cwd ?? ctx.cwd;
259
263
  let pid: number | undefined;
260
264
  try {
261
265
  pid = spawnRunner(
@@ -340,11 +344,11 @@ export function executeAsyncSingle(
340
344
  worktreeSetupHook,
341
345
  worktreeSetupHookTimeoutMs,
342
346
  } = params;
347
+ const runnerCwd = resolveChildCwd(ctx.cwd, cwd);
343
348
  const skillNames = params.skills ?? agentConfig.skills ?? [];
344
349
  const availableModels = params.availableModels;
345
- const skillCwd = cwd ?? ctx.cwd;
346
- const { resolved: resolvedSkills } = resolveSkillsWithFallback(skillNames, skillCwd, ctx.cwd);
347
- let systemPrompt = agentConfig.systemPrompt?.trim() || null;
350
+ const { resolved: resolvedSkills } = resolveSkillsWithFallback(skillNames, runnerCwd, ctx.cwd);
351
+ let systemPrompt = agentConfig.systemPrompt?.trim() ?? "";
348
352
  if (resolvedSkills.length > 0) {
349
353
  const injection = buildSkillInjection(resolvedSkills);
350
354
  systemPrompt = systemPrompt ? `${systemPrompt}\n\n${injection}` : injection;
@@ -362,8 +366,7 @@ export function executeAsyncSingle(
362
366
  };
363
367
  }
364
368
 
365
- const runnerCwd = cwd ?? ctx.cwd;
366
- const outputPath = resolveSingleOutputPath(params.output, ctx.cwd, cwd);
369
+ const outputPath = resolveSingleOutputPath(params.output, ctx.cwd, runnerCwd);
367
370
  const taskWithOutputInstruction = injectSingleOutputInstruction(task, outputPath);
368
371
  let pid: number | undefined;
369
372
  try {
@@ -374,7 +377,7 @@ export function executeAsyncSingle(
374
377
  {
375
378
  agent,
376
379
  task: taskWithOutputInstruction,
377
- cwd,
380
+ cwd: runnerCwd,
378
381
  model: applyThinkingSuffix(resolveModelCandidate(params.modelOverride ?? agentConfig.model, availableModels, ctx.currentModelProvider), agentConfig.thinking),
379
382
  modelCandidates: buildModelCandidates(params.modelOverride ?? agentConfig.model, agentConfig.fallbackModels, availableModels, ctx.currentModelProvider).map((candidate) =>
380
383
  applyThinkingSuffix(candidate, agentConfig.thinking),
@@ -383,6 +386,9 @@ export function executeAsyncSingle(
383
386
  extensions: agentConfig.extensions,
384
387
  mcpDirectTools: agentConfig.mcpDirectTools,
385
388
  systemPrompt,
389
+ systemPromptMode: agentConfig.systemPromptMode,
390
+ inheritProjectContext: agentConfig.inheritProjectContext,
391
+ inheritSkills: agentConfig.inheritSkills,
386
392
  skills: resolvedSkills.map((r) => r.name),
387
393
  outputPath,
388
394
  sessionFile,
@@ -1,11 +1,11 @@
1
1
  import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
2
2
  import * as path from "node:path";
3
- import { renderWidget } from "./render.js";
3
+ import { renderWidget } from "./render.ts";
4
4
  import {
5
5
  type SubagentState,
6
6
  POLL_INTERVAL_MS,
7
- } from "./types.js";
8
- import { readStatus } from "./utils.js";
7
+ } from "./types.ts";
8
+ import { readStatus } from "./utils.ts";
9
9
 
10
10
  export function createAsyncJobTracker(state: SubagentState, asyncDirRoot: string): {
11
11
  ensurePoller: () => void;
package/async-status.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
- import { formatDuration, formatTokens, shortenPath } from "./formatters.js";
4
- import { type AsyncStatus, type TokenUsage } from "./types.js";
5
- import { readStatus } from "./utils.js";
3
+ import { formatDuration, formatTokens, shortenPath } from "./formatters.ts";
4
+ import { type AsyncStatus, type TokenUsage } from "./types.ts";
5
+ import { readStatus } from "./utils.ts";
6
6
 
7
7
  export interface AsyncRunStepSummary {
8
8
  index: number;
@@ -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 { compactForegroundDetails, getSingleResultOutput, mapConcurrent } from "./utils.ts";
31
+ import { compactForegroundDetails, getSingleResultOutput, mapConcurrent, resolveChildCwd } from "./utils.ts";
32
32
  import { recordRun } from "./run-history.ts";
33
33
  import {
34
34
  cleanupWorktrees,
@@ -38,7 +38,7 @@ import {
38
38
  formatWorktreeDiffSummary,
39
39
  formatWorktreeTaskCwdConflict,
40
40
  type WorktreeSetup,
41
- } from "./worktree.js";
41
+ } from "./worktree.ts";
42
42
  import {
43
43
  type AgentProgress,
44
44
  type ArtifactConfig,
@@ -181,7 +181,7 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
181
181
 
182
182
  const taskCwd = input.worktreeSetup
183
183
  ? input.worktreeSetup.worktrees[taskIndex]!.agentCwd
184
- : (task.cwd ?? input.cwd);
184
+ : resolveChildCwd(input.cwd ?? input.ctx.cwd, task.cwd);
185
185
 
186
186
  const outputPath = typeof behavior.output === "string"
187
187
  ? (path.isAbsolute(behavior.output) ? behavior.output : path.join(input.chainDir, behavior.output))
@@ -412,7 +412,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
412
412
 
413
413
  if (isParallelStep(step)) {
414
414
  const parallelTemplates = stepTemplates as string[];
415
- const parallelCwd = step.cwd ?? cwd ?? ctx.cwd;
415
+ const parallelCwd = resolveChildCwd(cwd ?? ctx.cwd, step.cwd);
416
416
  let worktreeSetup: WorktreeSetup | undefined;
417
417
  if (step.worktree) {
418
418
  const worktreeTaskCwdConflict = findWorktreeTaskCwdConflict(step.parallel, parallelCwd);
@@ -605,7 +605,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
605
605
  const maxSubagentDepth = resolveChildMaxSubagentDepth(params.maxSubagentDepth, agentConfig.maxSubagentDepth);
606
606
 
607
607
  const r = await runSync(ctx.cwd, agents, seqStep.agent, stepTask, {
608
- cwd: seqStep.cwd ?? cwd,
608
+ cwd: resolveChildCwd(cwd ?? ctx.cwd, seqStep.cwd),
609
609
  signal,
610
610
  runId,
611
611
  index: globalTaskIndex,