pi-subagents 0.14.1 → 0.16.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 +31 -0
- package/README.md +113 -5
- package/agent-management.ts +28 -7
- package/agent-manager-detail.ts +4 -1
- package/agent-manager-edit.ts +46 -6
- package/agent-manager.ts +28 -2
- package/agent-serializer.ts +6 -0
- package/agents/context-builder.md +24 -26
- package/agents/delegate.md +4 -1
- package/agents/planner.md +21 -15
- package/agents/researcher.md +23 -25
- package/agents/reviewer.md +22 -14
- package/agents/scout.md +24 -19
- package/agents/worker.md +20 -8
- package/agents.ts +153 -25
- package/async-execution.ts +18 -12
- package/async-job-tracker.ts +3 -3
- package/async-status.ts +3 -3
- package/chain-execution.ts +5 -5
- package/execution.ts +3 -3
- package/fork-context.ts +7 -2
- package/formatters.ts +18 -14
- package/index.ts +2 -2
- package/package.json +1 -1
- package/parallel-utils.ts +4 -15
- package/pi-args.ts +13 -6
- package/render.ts +73 -49
- package/schemas.ts +2 -1
- package/settings.ts +2 -2
- package/slash-commands.ts +20 -25
- package/subagent-executor.ts +100 -38
- package/subagent-prompt-runtime.ts +67 -0
- package/subagent-runner.ts +3 -8
- package/types.ts +31 -0
- package/utils.ts +29 -2
- package/worktree.ts +2 -1
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
|
|
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))
|
|
193
|
-
|
|
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(
|
|
198
|
-
|
|
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 (
|
|
203
|
-
|
|
204
|
-
|
|
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
|
+
}
|
|
205
268
|
|
|
206
|
-
|
|
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
|
+
}
|
|
289
|
+
|
|
290
|
+
const fallbackModels = parseOverrideStringArrayOrFalse(input.fallbackModels, { filePath, name, field: "fallbackModels" });
|
|
207
291
|
if (fallbackModels !== undefined) override.fallbackModels = fallbackModels;
|
|
208
292
|
|
|
209
|
-
const skills =
|
|
293
|
+
const skills = parseOverrideStringArrayOrFalse(input.skills, { filePath, name, field: "skills" });
|
|
210
294
|
if (skills !== undefined) override.skills = skills;
|
|
211
295
|
|
|
212
|
-
const 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,
|
|
@@ -498,13 +606,20 @@ function isDirectory(p: string): boolean {
|
|
|
498
606
|
}
|
|
499
607
|
}
|
|
500
608
|
|
|
501
|
-
function
|
|
609
|
+
function resolveNearestProjectAgentDirs(cwd: string): { readDirs: string[]; preferredDir: string | null } {
|
|
502
610
|
const projectRoot = findNearestProjectRoot(cwd);
|
|
503
|
-
if (!projectRoot) return null;
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
const
|
|
507
|
-
|
|
611
|
+
if (!projectRoot) return { readDirs: [], preferredDir: null };
|
|
612
|
+
|
|
613
|
+
const legacyDir = path.join(projectRoot, ".agents");
|
|
614
|
+
const preferredDir = path.join(projectRoot, ".pi", "agents");
|
|
615
|
+
const readDirs: string[] = [];
|
|
616
|
+
if (isDirectory(legacyDir)) readDirs.push(legacyDir);
|
|
617
|
+
if (isDirectory(preferredDir)) readDirs.push(preferredDir);
|
|
618
|
+
|
|
619
|
+
return {
|
|
620
|
+
readDirs,
|
|
621
|
+
preferredDir,
|
|
622
|
+
};
|
|
508
623
|
}
|
|
509
624
|
|
|
510
625
|
const BUILTIN_AGENTS_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), "agents");
|
|
@@ -512,7 +627,7 @@ const BUILTIN_AGENTS_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)
|
|
|
512
627
|
export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryResult {
|
|
513
628
|
const userDirOld = path.join(os.homedir(), ".pi", "agent", "agents");
|
|
514
629
|
const userDirNew = path.join(os.homedir(), ".agents");
|
|
515
|
-
const projectAgentsDir =
|
|
630
|
+
const { readDirs: projectAgentDirs, preferredDir: projectAgentsDir } = resolveNearestProjectAgentDirs(cwd);
|
|
516
631
|
const userSettingsPath = getUserAgentSettingsPath();
|
|
517
632
|
const projectSettingsPath = getProjectAgentSettingsPath(cwd);
|
|
518
633
|
|
|
@@ -523,12 +638,12 @@ export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryRe
|
|
|
523
638
|
userSettingsPath,
|
|
524
639
|
projectSettingsPath,
|
|
525
640
|
);
|
|
526
|
-
|
|
641
|
+
|
|
527
642
|
const userAgentsOld = scope === "project" ? [] : loadAgentsFromDir(userDirOld, "user");
|
|
528
643
|
const userAgentsNew = scope === "project" ? [] : loadAgentsFromDir(userDirNew, "user");
|
|
529
644
|
const userAgents = [...userAgentsOld, ...userAgentsNew];
|
|
530
645
|
|
|
531
|
-
const projectAgents = scope === "user"
|
|
646
|
+
const projectAgents = scope === "user" ? [] : projectAgentDirs.flatMap((dir) => loadAgentsFromDir(dir, "project"));
|
|
532
647
|
const agents = mergeAgentsForScope(scope, userAgents, projectAgents, builtinAgents);
|
|
533
648
|
|
|
534
649
|
return { agents, projectAgentsDir };
|
|
@@ -546,7 +661,7 @@ export function discoverAgentsAll(cwd: string): {
|
|
|
546
661
|
} {
|
|
547
662
|
const userDirOld = path.join(os.homedir(), ".pi", "agent", "agents");
|
|
548
663
|
const userDirNew = path.join(os.homedir(), ".agents");
|
|
549
|
-
const projectDir =
|
|
664
|
+
const { readDirs: projectDirs, preferredDir: projectDir } = resolveNearestProjectAgentDirs(cwd);
|
|
550
665
|
const userSettingsPath = getUserAgentSettingsPath();
|
|
551
666
|
const projectSettingsPath = getProjectAgentSettingsPath(cwd);
|
|
552
667
|
|
|
@@ -561,11 +676,24 @@ export function discoverAgentsAll(cwd: string): {
|
|
|
561
676
|
...loadAgentsFromDir(userDirOld, "user"),
|
|
562
677
|
...loadAgentsFromDir(userDirNew, "user"),
|
|
563
678
|
];
|
|
564
|
-
const
|
|
679
|
+
const projectMap = new Map<string, AgentConfig>();
|
|
680
|
+
for (const dir of projectDirs) {
|
|
681
|
+
for (const agent of loadAgentsFromDir(dir, "project")) {
|
|
682
|
+
projectMap.set(agent.name, agent);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
const project = Array.from(projectMap.values());
|
|
686
|
+
|
|
687
|
+
const chainMap = new Map<string, ChainConfig>();
|
|
688
|
+
for (const dir of projectDirs) {
|
|
689
|
+
for (const chain of loadChainsFromDir(dir, "project")) {
|
|
690
|
+
chainMap.set(chain.name, chain);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
565
693
|
const chains = [
|
|
566
694
|
...loadChainsFromDir(userDirOld, "user"),
|
|
567
695
|
...loadChainsFromDir(userDirNew, "user"),
|
|
568
|
-
...(
|
|
696
|
+
...Array.from(chainMap.values()),
|
|
569
697
|
];
|
|
570
698
|
|
|
571
699
|
const userDir = fs.existsSync(userDirNew) ? userDirNew : userDirOld;
|
package/async-execution.ts
CHANGED
|
@@ -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
|
|
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()
|
|
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,
|
|
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:
|
|
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
|
|
346
|
-
|
|
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
|
|
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,
|
package/async-job-tracker.ts
CHANGED
|
@@ -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.
|
|
3
|
+
import { renderWidget } from "./render.ts";
|
|
4
4
|
import {
|
|
5
5
|
type SubagentState,
|
|
6
6
|
POLL_INTERVAL_MS,
|
|
7
|
-
} from "./types.
|
|
8
|
-
import { readStatus } from "./utils.
|
|
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.
|
|
4
|
-
import { type AsyncStatus, type TokenUsage } from "./types.
|
|
5
|
-
import { readStatus } from "./utils.
|
|
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;
|
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 { 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.
|
|
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
|
-
: (
|
|
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 =
|
|
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:
|
|
608
|
+
cwd: resolveChildCwd(cwd ?? ctx.cwd, seqStep.cwd),
|
|
609
609
|
signal,
|
|
610
610
|
runId,
|
|
611
611
|
index: globalTaskIndex,
|
package/execution.ts
CHANGED
|
@@ -72,7 +72,6 @@ async function runSingleAttempt(
|
|
|
72
72
|
shared: {
|
|
73
73
|
sessionEnabled: boolean;
|
|
74
74
|
systemPrompt: string;
|
|
75
|
-
skillNames: string[];
|
|
76
75
|
resolvedSkillNames?: string[];
|
|
77
76
|
skillsWarning?: string;
|
|
78
77
|
jsonlPath?: string;
|
|
@@ -89,9 +88,11 @@ async function runSingleAttempt(
|
|
|
89
88
|
sessionFile: options.sessionFile,
|
|
90
89
|
model,
|
|
91
90
|
thinking: agent.thinking,
|
|
91
|
+
systemPromptMode: agent.systemPromptMode,
|
|
92
|
+
inheritProjectContext: agent.inheritProjectContext,
|
|
93
|
+
inheritSkills: agent.inheritSkills,
|
|
92
94
|
tools: agent.tools,
|
|
93
95
|
extensions: agent.extensions,
|
|
94
|
-
skills: shared.skillNames,
|
|
95
96
|
systemPrompt: shared.systemPrompt,
|
|
96
97
|
mcpDirectTools: agent.mcpDirectTools,
|
|
97
98
|
promptFileStem: agent.name,
|
|
@@ -413,7 +414,6 @@ export async function runSync(
|
|
|
413
414
|
const result = await runSingleAttempt(runtimeCwd, agent, task, candidate, options, {
|
|
414
415
|
sessionEnabled,
|
|
415
416
|
systemPrompt,
|
|
416
|
-
skillNames,
|
|
417
417
|
resolvedSkillNames: resolvedSkills.length > 0 ? resolvedSkills.map((skill) => skill.name) : undefined,
|
|
418
418
|
skillsWarning: missingSkills.length > 0 ? `Skills not found: ${missingSkills.join(", ")}` : undefined,
|
|
419
419
|
jsonlPath,
|
package/fork-context.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
export type SubagentExecutionContext = "fresh" | "fork";
|
|
2
2
|
|
|
3
|
+
interface ForkableSessionManagerStatic {
|
|
4
|
+
open(path: string): { createBranchedSession(leafId: string): string | undefined };
|
|
5
|
+
}
|
|
6
|
+
|
|
3
7
|
export interface ForkableSessionManager {
|
|
4
8
|
getSessionFile(): string | undefined;
|
|
5
9
|
getLeafId(): string | null;
|
|
6
|
-
|
|
10
|
+
constructor: ForkableSessionManagerStatic;
|
|
7
11
|
}
|
|
8
12
|
|
|
9
13
|
export interface ForkContextResolver {
|
|
@@ -41,7 +45,8 @@ export function createForkContextResolver(
|
|
|
41
45
|
const cached = cachedSessionFiles.get(index);
|
|
42
46
|
if (cached) return cached;
|
|
43
47
|
try {
|
|
44
|
-
const
|
|
48
|
+
const sourceManager = sessionManager.constructor.open(parentSessionFile);
|
|
49
|
+
const sessionFile = sourceManager.createBranchedSession(leafId);
|
|
45
50
|
if (!sessionFile) {
|
|
46
51
|
throw new Error("Session manager did not return a session file.");
|
|
47
52
|
}
|
package/formatters.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import * as fs from "node:fs";
|
|
6
6
|
import * as path from "node:path";
|
|
7
7
|
import type { Usage, SingleResult } from "./types.ts";
|
|
8
|
-
import type { ChainStep
|
|
8
|
+
import type { ChainStep } from "./settings.ts";
|
|
9
9
|
import { isParallelStep } from "./settings.ts";
|
|
10
10
|
|
|
11
11
|
/**
|
|
@@ -49,16 +49,13 @@ export function buildChainSummary(
|
|
|
49
49
|
status: "completed" | "failed",
|
|
50
50
|
failedStep?: { index: number; error: string },
|
|
51
51
|
): string {
|
|
52
|
-
// Build step names for display
|
|
53
52
|
const stepNames = steps
|
|
54
|
-
.map((
|
|
53
|
+
.map((step) => (isParallelStep(step) ? `parallel[${step.parallel.length}]` : step.agent))
|
|
55
54
|
.join(" → ");
|
|
56
55
|
|
|
57
|
-
// Calculate total duration from results
|
|
58
56
|
const totalDuration = results.reduce((sum, r) => sum + (r.progress?.durationMs || 0), 0);
|
|
59
57
|
const durationStr = formatDuration(totalDuration);
|
|
60
58
|
|
|
61
|
-
// Check for progress.md
|
|
62
59
|
const progressPath = path.join(chainDir, "progress.md");
|
|
63
60
|
const hasProgress = fs.existsSync(progressPath);
|
|
64
61
|
const allSkills = new Set<string>();
|
|
@@ -86,19 +83,27 @@ export function buildChainSummary(
|
|
|
86
83
|
/**
|
|
87
84
|
* Format a tool call for display
|
|
88
85
|
*/
|
|
89
|
-
export function formatToolCall(name: string, args: Record<string, unknown
|
|
86
|
+
export function formatToolCall(name: string, args: Record<string, unknown>, expanded = false): string {
|
|
90
87
|
switch (name) {
|
|
91
|
-
case "bash":
|
|
92
|
-
|
|
88
|
+
case "bash": {
|
|
89
|
+
const command = typeof args.command === "string" ? args.command : "";
|
|
90
|
+
const maxLength = expanded ? 240 : 60;
|
|
91
|
+
return `$ ${command.slice(0, maxLength)}${command.length > maxLength ? "..." : ""}`;
|
|
92
|
+
}
|
|
93
93
|
case "read":
|
|
94
|
-
return `read ${shortenPath((args.path || args.file_path || "") as string)}`;
|
|
95
94
|
case "write":
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
case "edit": {
|
|
96
|
+
const target = typeof args.path === "string"
|
|
97
|
+
? args.path
|
|
98
|
+
: typeof args.file_path === "string"
|
|
99
|
+
? args.file_path
|
|
100
|
+
: "";
|
|
101
|
+
return `${name} ${shortenPath(target)}`;
|
|
102
|
+
}
|
|
99
103
|
default: {
|
|
100
104
|
const s = JSON.stringify(args);
|
|
101
|
-
|
|
105
|
+
const maxLength = expanded ? 160 : 40;
|
|
106
|
+
return `${name} ${s.slice(0, maxLength)}${s.length > maxLength ? "..." : ""}`;
|
|
102
107
|
}
|
|
103
108
|
}
|
|
104
109
|
}
|
|
@@ -108,7 +113,6 @@ export function formatToolCall(name: string, args: Record<string, unknown>): str
|
|
|
108
113
|
*/
|
|
109
114
|
export function shortenPath(p: string): string {
|
|
110
115
|
const home = process.env.HOME;
|
|
111
|
-
// Only shorten if HOME is defined and non-empty, and path starts with it
|
|
112
116
|
if (home && p.startsWith(home)) {
|
|
113
117
|
return `~${p.slice(home.length)}`;
|
|
114
118
|
}
|
package/index.ts
CHANGED
|
@@ -253,7 +253,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
253
253
|
EXECUTION (use exactly ONE mode):
|
|
254
254
|
• SINGLE: { agent, task } - one task
|
|
255
255
|
• CHAIN: { chain: [{agent:"scout"}, {parallel:[{agent:"worker",count:3}]}] } - sequential pipeline with optional parallel fan-out
|
|
256
|
-
• PARALLEL: { tasks: [{agent,task,count?}, ...], worktree?: true } - concurrent execution (worktree: isolate each task in a git worktree)
|
|
256
|
+
• PARALLEL: { tasks: [{agent,task,count?}, ...], concurrency?: number, worktree?: true } - concurrent execution (worktree: isolate each task in a git worktree)
|
|
257
257
|
• Optional context: { context: "fresh" | "fork" } (default: "fresh")
|
|
258
258
|
|
|
259
259
|
CHAIN TEMPLATE VARIABLES (use in task strings):
|
|
@@ -266,7 +266,7 @@ Example: { chain: [{agent:"scout", task:"Analyze {task}"}, {agent:"planner", tas
|
|
|
266
266
|
MANAGEMENT (use action field, omit agent/task/chain/tasks):
|
|
267
267
|
• { action: "list" } - discover agents/chains
|
|
268
268
|
• { action: "get", agent: "name" } - full agent detail
|
|
269
|
-
• { action: "create", config: { name, systemPrompt, ... } }
|
|
269
|
+
• { action: "create", config: { name, systemPrompt, systemPromptMode, inheritProjectContext, inheritSkills, ... } }
|
|
270
270
|
• { action: "update", agent: "name", config: { ... } } - merge
|
|
271
271
|
• { action: "delete", agent: "name" }
|
|
272
272
|
• Use chainName for chain operations`,
|
package/package.json
CHANGED