pi-subagents 0.24.3 → 0.24.4
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 +18 -5
- package/README.md +3 -1
- package/package.json +4 -8
- package/src/agents/agent-management.ts +5 -0
- package/src/agents/agent-serializer.ts +2 -0
- package/src/agents/agents.ts +30 -6
- package/src/agents/skills.ts +25 -23
- package/src/extension/config.ts +16 -0
- package/src/extension/index.ts +7 -23
- package/src/intercom/intercom-bridge.ts +2 -1
- package/src/runs/background/async-execution.ts +6 -3
- package/src/runs/background/async-job-tracker.ts +16 -8
- package/src/runs/background/subagent-runner.ts +18 -7
- package/src/runs/foreground/execution.ts +17 -5
- package/src/runs/foreground/subagent-executor.ts +4 -4
- package/src/runs/shared/completion-guard.ts +23 -1
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +365 -0
- package/src/runs/shared/parallel-utils.ts +1 -0
- package/src/runs/shared/pi-args.ts +5 -0
- package/src/runs/shared/run-history.ts +12 -7
- package/src/runs/shared/single-output.ts +12 -2
- package/src/shared/artifacts.ts +2 -2
- package/src/shared/utils.ts +11 -1
- package/src/tui/render.ts +148 -144
package/CHANGELOG.md
CHANGED
|
@@ -1,19 +1,32 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## [
|
|
3
|
+
## [Unreleased]
|
|
4
4
|
|
|
5
5
|
### Added
|
|
6
|
-
- Show provider-free model and thinking labels in async subagent widgets and status views.
|
|
7
|
-
- Added a packaged `/review-loop` prompt for parent-controlled worker, fresh-reviewer, and fix-worker cycles that can run as an initial async chain or as follow-up subagent runs after async worker completions, stopping when reviewers find no fixes worth doing now or the review-round cap is reached.
|
|
8
6
|
|
|
9
7
|
### Fixed
|
|
10
|
-
- Let `async: true` chain tool calls run in the background when `clarify` is omitted, and avoid showing the async badge for explicit foreground clarify runs.
|
|
11
8
|
|
|
12
|
-
## [
|
|
9
|
+
## [0.24.4] - 2026-05-20
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- Treat provider-coerced single-run `output: "false"` the same as boolean `false`, preventing literal `false` output files in foreground and async runs.
|
|
13
|
+
- Include selected direct MCP tool names in explicit child `--tools` allowlists when metadata cache/config resolution is available.
|
|
14
|
+
- Honor `PI_CODING_AGENT_DIR` for runtime config, agent/chain/settings discovery, skills, run history, artifact cleanup, and intercom defaults.
|
|
15
|
+
- Hide nested child Pi process windows on Windows for both foreground and background subagent runs.
|
|
16
|
+
- Avoid completion-guard false positives for declared read-only agents, and add `completionGuard: false` for bash-enabled non-implementation agents that should not be required to edit files.
|
|
17
|
+
- Skip empty or whitespace-only assistant text parts when selecting subagent final output, so later meaningful text in the same or earlier assistant message is not masked.
|
|
18
|
+
- Declare `@earendil-works/pi-tui` as a runtime dependency so packaged installs can load the extension without relying on dev dependencies or optional peers.
|
|
19
|
+
- Treat recovered intermediate child tool/provider errors as successful when a later clean final assistant response is emitted, preventing false failed subagent results.
|
|
20
|
+
- Use progress-driven spinner frames in subagent result rows and async widgets, avoiding timer-driven off-screen redraw flicker in small terminals.
|
|
21
|
+
|
|
22
|
+
## [0.24.3] - 2026-05-14
|
|
13
23
|
|
|
14
24
|
### Added
|
|
25
|
+
- Show provider-free model and thinking labels in async subagent widgets and status views.
|
|
26
|
+
- Added a packaged `/review-loop` prompt for parent-controlled worker, fresh-reviewer, and fix-worker cycles that can run as an initial async chain or as follow-up subagent runs after async worker completions, stopping when reviewers find no fixes worth doing now or the review-round cap is reached.
|
|
15
27
|
|
|
16
28
|
### Fixed
|
|
29
|
+
- Let `async: true` chain tool calls run in the background when `clarify` is omitted, and avoid showing the async badge for explicit foreground clarify runs.
|
|
17
30
|
|
|
18
31
|
## [0.24.2] - 2026-05-10
|
|
19
32
|
|
package/README.md
CHANGED
|
@@ -433,6 +433,7 @@ skills: safe-bash, chrome-devtools
|
|
|
433
433
|
output: context.md
|
|
434
434
|
defaultReads: context.md
|
|
435
435
|
defaultProgress: true
|
|
436
|
+
completionGuard: false
|
|
436
437
|
interactive: true
|
|
437
438
|
maxSubagentDepth: 1
|
|
438
439
|
---
|
|
@@ -458,12 +459,13 @@ Important fields:
|
|
|
458
459
|
| `output` | Default single-agent output file. |
|
|
459
460
|
| `defaultReads` | Files to read before running in chain/parallel behavior. |
|
|
460
461
|
| `defaultProgress` | Maintain `progress.md`. |
|
|
462
|
+
| `completionGuard` | Set `false` only for non-implementation agents that may mention implementation words while using mutation-capable tools such as `bash`. |
|
|
461
463
|
| `interactive` | Parsed for compatibility but not enforced in v1. |
|
|
462
464
|
| `maxSubagentDepth` | Tightens nested delegation for this agent’s children. |
|
|
463
465
|
|
|
464
466
|
### Tool and extension selection
|
|
465
467
|
|
|
466
|
-
If `tools` is omitted, `pi-subagents` does not pass `--tools`, so the child gets Pi’s normal builtin tools. If `tools` is present, regular tool names become an explicit allowlist. `mcp:` entries are split out and forwarded as direct MCP selections. Path-like `tools` entries, such as extension paths or `.ts`/`.js` files, are treated as tool-extension paths rather than builtin tool names.
|
|
468
|
+
If `tools` is omitted, `pi-subagents` does not pass `--tools`, so the child gets Pi’s normal builtin tools. If `tools` is present, regular tool names become an explicit allowlist. `mcp:` entries are split out and forwarded as direct MCP selections. Path-like `tools` entries, such as extension paths or `.ts`/`.js` files, are treated as tool-extension paths rather than builtin tool names. Agents that declare only known read-only builtin tools skip the implementation completion guard, but `bash`, unknown tools, and MCP tools stay mutation-capable. Use `completionGuard: false` for bash-enabled validators or advisors that should never be judged as implementation agents.
|
|
467
469
|
|
|
468
470
|
Examples:
|
|
469
471
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-subagents",
|
|
3
|
-
"version": "0.24.
|
|
3
|
+
"version": "0.24.4",
|
|
4
4
|
"description": "Pi extension for delegating tasks to subagents with chains, parallel execution, and TUI clarification",
|
|
5
5
|
"author": "Nico Bailon",
|
|
6
6
|
"license": "MIT",
|
|
@@ -54,8 +54,7 @@
|
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"@earendil-works/pi-agent-core": "*",
|
|
56
56
|
"@earendil-works/pi-ai": "*",
|
|
57
|
-
"@earendil-works/pi-coding-agent": "*"
|
|
58
|
-
"@earendil-works/pi-tui": "*"
|
|
57
|
+
"@earendil-works/pi-coding-agent": "*"
|
|
59
58
|
},
|
|
60
59
|
"peerDependenciesMeta": {
|
|
61
60
|
"@earendil-works/pi-agent-core": {
|
|
@@ -66,19 +65,16 @@
|
|
|
66
65
|
},
|
|
67
66
|
"@earendil-works/pi-coding-agent": {
|
|
68
67
|
"optional": true
|
|
69
|
-
},
|
|
70
|
-
"@earendil-works/pi-tui": {
|
|
71
|
-
"optional": true
|
|
72
68
|
}
|
|
73
69
|
},
|
|
74
70
|
"dependencies": {
|
|
71
|
+
"@earendil-works/pi-tui": "^0.74.0",
|
|
75
72
|
"jiti": "^2.7.0",
|
|
76
73
|
"typebox": "^1.1.24"
|
|
77
74
|
},
|
|
78
75
|
"devDependencies": {
|
|
79
76
|
"@earendil-works/pi-agent-core": "^0.74.0",
|
|
80
77
|
"@earendil-works/pi-ai": "^0.74.0",
|
|
81
|
-
"@earendil-works/pi-coding-agent": "^0.74.0"
|
|
82
|
-
"@earendil-works/pi-tui": "^0.74.0"
|
|
78
|
+
"@earendil-works/pi-coding-agent": "^0.74.0"
|
|
83
79
|
}
|
|
84
80
|
}
|
|
@@ -297,6 +297,10 @@ function applyAgentConfig(target: AgentConfig, cfg: Record<string, unknown>): st
|
|
|
297
297
|
target.maxSubagentDepth = cfg.maxSubagentDepth;
|
|
298
298
|
} else return "config.maxSubagentDepth must be an integer >= 0 or false when provided.";
|
|
299
299
|
}
|
|
300
|
+
if (hasKey(cfg, "completionGuard")) {
|
|
301
|
+
if (typeof cfg.completionGuard !== "boolean") return "config.completionGuard must be a boolean when provided.";
|
|
302
|
+
target.completionGuard = cfg.completionGuard;
|
|
303
|
+
}
|
|
300
304
|
return undefined;
|
|
301
305
|
}
|
|
302
306
|
|
|
@@ -366,6 +370,7 @@ function formatAgentDetail(agent: AgentConfig): string {
|
|
|
366
370
|
if (agent.defaultReads?.length) lines.push(`Reads: ${agent.defaultReads.join(", ")}`);
|
|
367
371
|
if (agent.defaultProgress) lines.push("Progress: true");
|
|
368
372
|
if (agent.maxSubagentDepth !== undefined) lines.push(`Max subagent depth: ${agent.maxSubagentDepth}`);
|
|
373
|
+
if (agent.completionGuard === false) lines.push("Completion guard: false");
|
|
369
374
|
if (agent.systemPrompt.trim()) lines.push("", "System Prompt:", agent.systemPrompt);
|
|
370
375
|
return lines.join("\n");
|
|
371
376
|
}
|
|
@@ -21,6 +21,7 @@ export const KNOWN_FIELDS = new Set([
|
|
|
21
21
|
"defaultProgress",
|
|
22
22
|
"interactive",
|
|
23
23
|
"maxSubagentDepth",
|
|
24
|
+
"completionGuard",
|
|
24
25
|
]);
|
|
25
26
|
|
|
26
27
|
function joinComma(values: string[] | undefined): string | undefined {
|
|
@@ -69,6 +70,7 @@ export function serializeAgent(config: AgentConfig): string {
|
|
|
69
70
|
if (Number.isInteger(config.maxSubagentDepth) && config.maxSubagentDepth >= 0) {
|
|
70
71
|
lines.push(`maxSubagentDepth: ${config.maxSubagentDepth}`);
|
|
71
72
|
}
|
|
73
|
+
if (config.completionGuard === false) lines.push("completionGuard: false");
|
|
72
74
|
|
|
73
75
|
if (config.extraFields) {
|
|
74
76
|
for (const [key, value] of Object.entries(config.extraFields)) {
|
package/src/agents/agents.ts
CHANGED
|
@@ -7,6 +7,7 @@ import * as os from "node:os";
|
|
|
7
7
|
import * as path from "node:path";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
9
|
import type { OutputMode } from "../shared/types.ts";
|
|
10
|
+
import { getAgentDir } from "../shared/utils.ts";
|
|
10
11
|
import { KNOWN_FIELDS } from "./agent-serializer.ts";
|
|
11
12
|
import { parseChain } from "./chain-serializer.ts";
|
|
12
13
|
import { mergeAgentsForScope } from "./agent-selection.ts";
|
|
@@ -45,6 +46,7 @@ export interface BuiltinAgentOverrideBase {
|
|
|
45
46
|
skills?: string[];
|
|
46
47
|
tools?: string[];
|
|
47
48
|
mcpDirectTools?: string[];
|
|
49
|
+
completionGuard?: boolean;
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
interface BuiltinAgentOverrideConfig {
|
|
@@ -59,6 +61,7 @@ interface BuiltinAgentOverrideConfig {
|
|
|
59
61
|
systemPrompt?: string;
|
|
60
62
|
skills?: string[] | false;
|
|
61
63
|
tools?: string[] | false;
|
|
64
|
+
completionGuard?: boolean;
|
|
62
65
|
}
|
|
63
66
|
|
|
64
67
|
interface BuiltinAgentOverrideInfo {
|
|
@@ -91,6 +94,7 @@ export interface AgentConfig {
|
|
|
91
94
|
defaultProgress?: boolean;
|
|
92
95
|
interactive?: boolean;
|
|
93
96
|
maxSubagentDepth?: number;
|
|
97
|
+
completionGuard?: boolean;
|
|
94
98
|
disabled?: boolean;
|
|
95
99
|
extraFields?: Record<string, string>;
|
|
96
100
|
override?: BuiltinAgentOverrideInfo;
|
|
@@ -131,7 +135,7 @@ interface AgentDiscoveryResult {
|
|
|
131
135
|
}
|
|
132
136
|
|
|
133
137
|
function getUserChainDir(): string {
|
|
134
|
-
return path.join(
|
|
138
|
+
return path.join(getAgentDir(), "chains");
|
|
135
139
|
}
|
|
136
140
|
|
|
137
141
|
function splitToolList(rawTools: string[] | undefined): { tools?: string[]; mcpDirectTools?: string[] } {
|
|
@@ -182,6 +186,7 @@ function cloneOverrideBase(agent: AgentConfig): BuiltinAgentOverrideBase {
|
|
|
182
186
|
skills: agent.skills ? [...agent.skills] : undefined,
|
|
183
187
|
tools: agent.tools ? [...agent.tools] : undefined,
|
|
184
188
|
mcpDirectTools: agent.mcpDirectTools ? [...agent.mcpDirectTools] : undefined,
|
|
189
|
+
completionGuard: agent.completionGuard,
|
|
185
190
|
};
|
|
186
191
|
}
|
|
187
192
|
|
|
@@ -200,6 +205,7 @@ function cloneOverrideValue(override: BuiltinAgentOverrideConfig): BuiltinAgentO
|
|
|
200
205
|
...(override.systemPrompt !== undefined ? { systemPrompt: override.systemPrompt } : {}),
|
|
201
206
|
...(override.skills !== undefined ? { skills: override.skills === false ? false : [...override.skills] } : {}),
|
|
202
207
|
...(override.tools !== undefined ? { tools: override.tools === false ? false : [...override.tools] } : {}),
|
|
208
|
+
...(override.completionGuard !== undefined ? { completionGuard: override.completionGuard } : {}),
|
|
203
209
|
};
|
|
204
210
|
}
|
|
205
211
|
|
|
@@ -217,7 +223,7 @@ function findNearestProjectRoot(cwd: string): string | null {
|
|
|
217
223
|
}
|
|
218
224
|
|
|
219
225
|
function getUserAgentSettingsPath(): string {
|
|
220
|
-
return path.join(
|
|
226
|
+
return path.join(getAgentDir(), "settings.json");
|
|
221
227
|
}
|
|
222
228
|
|
|
223
229
|
function getProjectAgentSettingsPath(cwd: string): string | null {
|
|
@@ -336,6 +342,14 @@ function parseBuiltinOverrideEntry(
|
|
|
336
342
|
}
|
|
337
343
|
}
|
|
338
344
|
|
|
345
|
+
if ("completionGuard" in input) {
|
|
346
|
+
if (typeof input.completionGuard === "boolean") {
|
|
347
|
+
override.completionGuard = input.completionGuard;
|
|
348
|
+
} else {
|
|
349
|
+
throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'completionGuard'; expected a boolean.`);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
339
353
|
if ("systemPrompt" in input) {
|
|
340
354
|
if (typeof input.systemPrompt === "string") override.systemPrompt = input.systemPrompt;
|
|
341
355
|
else throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'systemPrompt'; expected a string.`);
|
|
@@ -408,6 +422,7 @@ function applyBuiltinOverride(
|
|
|
408
422
|
next.tools = tools;
|
|
409
423
|
next.mcpDirectTools = mcpDirectTools;
|
|
410
424
|
}
|
|
425
|
+
if (override.completionGuard !== undefined) next.completionGuard = override.completionGuard;
|
|
411
426
|
|
|
412
427
|
return next;
|
|
413
428
|
}
|
|
@@ -447,7 +462,7 @@ function applyBuiltinOverrides(
|
|
|
447
462
|
|
|
448
463
|
export function buildBuiltinOverrideConfig(
|
|
449
464
|
base: BuiltinAgentOverrideBase,
|
|
450
|
-
draft: Pick<AgentConfig, "model" | "fallbackModels" | "thinking" | "systemPromptMode" | "inheritProjectContext" | "inheritSkills" | "defaultContext" | "disabled" | "systemPrompt" | "skills" | "tools" | "mcpDirectTools">,
|
|
465
|
+
draft: Pick<AgentConfig, "model" | "fallbackModels" | "thinking" | "systemPromptMode" | "inheritProjectContext" | "inheritSkills" | "defaultContext" | "disabled" | "systemPrompt" | "skills" | "tools" | "mcpDirectTools" | "completionGuard">,
|
|
451
466
|
): BuiltinAgentOverrideConfig | undefined {
|
|
452
467
|
const override: BuiltinAgentOverrideConfig = {};
|
|
453
468
|
|
|
@@ -465,6 +480,9 @@ export function buildBuiltinOverrideConfig(
|
|
|
465
480
|
const baseTools = joinToolList(base);
|
|
466
481
|
const draftTools = joinToolList(draft);
|
|
467
482
|
if (!arraysEqual(draftTools, baseTools)) override.tools = draftTools ? [...draftTools] : false;
|
|
483
|
+
if ((draft.completionGuard !== false) !== (base.completionGuard !== false)) {
|
|
484
|
+
override.completionGuard = draft.completionGuard !== false;
|
|
485
|
+
}
|
|
468
486
|
|
|
469
487
|
return Object.keys(override).length > 0 ? override : undefined;
|
|
470
488
|
}
|
|
@@ -630,6 +648,11 @@ function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {
|
|
|
630
648
|
}
|
|
631
649
|
|
|
632
650
|
const parsedMaxSubagentDepth = Number(frontmatter.maxSubagentDepth);
|
|
651
|
+
const completionGuard = frontmatter.completionGuard === "false"
|
|
652
|
+
? false
|
|
653
|
+
: frontmatter.completionGuard === "true"
|
|
654
|
+
? true
|
|
655
|
+
: undefined;
|
|
633
656
|
|
|
634
657
|
agents.push({
|
|
635
658
|
name: runtimeName,
|
|
@@ -658,6 +681,7 @@ function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {
|
|
|
658
681
|
Number.isInteger(parsedMaxSubagentDepth) && parsedMaxSubagentDepth >= 0
|
|
659
682
|
? parsedMaxSubagentDepth
|
|
660
683
|
: undefined,
|
|
684
|
+
completionGuard,
|
|
661
685
|
extraFields: Object.keys(extraFields).length > 0 ? extraFields : undefined,
|
|
662
686
|
});
|
|
663
687
|
}
|
|
@@ -723,7 +747,7 @@ function resolveNearestProjectChainDirs(cwd: string): { readDirs: string[]; pref
|
|
|
723
747
|
const BUILTIN_AGENTS_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "agents");
|
|
724
748
|
|
|
725
749
|
export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryResult {
|
|
726
|
-
const userDirOld = path.join(
|
|
750
|
+
const userDirOld = path.join(getAgentDir(), "agents");
|
|
727
751
|
const userDirNew = path.join(os.homedir(), ".agents");
|
|
728
752
|
const { readDirs: projectAgentDirs, preferredDir: projectAgentsDir } = resolveNearestProjectAgentDirs(cwd);
|
|
729
753
|
const userSettingsPath = getUserAgentSettingsPath();
|
|
@@ -762,7 +786,7 @@ export function discoverAgentsAll(cwd: string): {
|
|
|
762
786
|
userSettingsPath: string;
|
|
763
787
|
projectSettingsPath: string | null;
|
|
764
788
|
} {
|
|
765
|
-
const userDirOld = path.join(
|
|
789
|
+
const userDirOld = path.join(getAgentDir(), "agents");
|
|
766
790
|
const userDirNew = path.join(os.homedir(), ".agents");
|
|
767
791
|
const userChainDir = getUserChainDir();
|
|
768
792
|
const { readDirs: projectDirs, preferredDir: projectDir } = resolveNearestProjectAgentDirs(cwd);
|
|
@@ -802,7 +826,7 @@ export function discoverAgentsAll(cwd: string): {
|
|
|
802
826
|
...Array.from(chainMap.values()),
|
|
803
827
|
];
|
|
804
828
|
|
|
805
|
-
const userDir = fs.existsSync(userDirNew) ? userDirNew : userDirOld;
|
|
829
|
+
const userDir = process.env.PI_CODING_AGENT_DIR ? userDirOld : fs.existsSync(userDirNew) ? userDirNew : userDirOld;
|
|
806
830
|
|
|
807
831
|
return { builtin, user, project, chains, userDir, projectDir, userChainDir, projectChainDir, userSettingsPath, projectSettingsPath };
|
|
808
832
|
}
|
package/src/agents/skills.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { execSync } from "node:child_process";
|
|
|
6
6
|
import * as fs from "node:fs";
|
|
7
7
|
import * as os from "node:os";
|
|
8
8
|
import * as path from "node:path";
|
|
9
|
+
import { getAgentDir } from "../shared/utils.ts";
|
|
9
10
|
|
|
10
11
|
export type SkillSource =
|
|
11
12
|
| "project"
|
|
@@ -46,11 +47,10 @@ interface SkillSearchPath {
|
|
|
46
47
|
const skillCache = new Map<string, SkillCacheEntry>();
|
|
47
48
|
const MAX_CACHE_SIZE = 50;
|
|
48
49
|
|
|
49
|
-
let loadSkillsCache: { cwd: string; skills: CachedSkillEntry[]; timestamp: number } | null = null;
|
|
50
|
+
let loadSkillsCache: { cwd: string; agentDir: string; skills: CachedSkillEntry[]; timestamp: number } | null = null;
|
|
50
51
|
const LOAD_SKILLS_CACHE_TTL_MS = 5000;
|
|
51
52
|
|
|
52
53
|
const CONFIG_DIR = ".pi";
|
|
53
|
-
const AGENT_DIR = path.join(os.homedir(), ".pi", "agent");
|
|
54
54
|
const SUBAGENT_ORCHESTRATION_SKILL = "pi-subagents";
|
|
55
55
|
|
|
56
56
|
const SOURCE_PRIORITY: Record<SkillSource, number> = {
|
|
@@ -133,10 +133,10 @@ function getGlobalNpmRoot(): string | null {
|
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
function collectInstalledPackageSkillPaths(cwd: string): SkillSearchPath[] {
|
|
136
|
+
function collectInstalledPackageSkillPaths(cwd: string, agentDir: string): SkillSearchPath[] {
|
|
137
137
|
const dirs: SkillSearchPath[] = [
|
|
138
138
|
{ path: path.join(cwd, CONFIG_DIR, "npm", "node_modules"), source: "project-package" },
|
|
139
|
-
{ path: path.join(
|
|
139
|
+
{ path: path.join(agentDir, "npm", "node_modules"), source: "user-package" },
|
|
140
140
|
];
|
|
141
141
|
|
|
142
142
|
const globalRoot = getGlobalNpmRoot();
|
|
@@ -184,11 +184,11 @@ function collectInstalledPackageSkillPaths(cwd: string): SkillSearchPath[] {
|
|
|
184
184
|
return results;
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
-
function collectSettingsSkillPaths(cwd: string): SkillSearchPath[] {
|
|
187
|
+
function collectSettingsSkillPaths(cwd: string, agentDir: string): SkillSearchPath[] {
|
|
188
188
|
const results: SkillSearchPath[] = [];
|
|
189
189
|
const settingsFiles = [
|
|
190
190
|
{ file: path.join(cwd, CONFIG_DIR, "settings.json"), base: path.join(cwd, CONFIG_DIR), source: "project-settings" as const },
|
|
191
|
-
{ file: path.join(
|
|
191
|
+
{ file: path.join(agentDir, "settings.json"), base: agentDir, source: "user-settings" as const },
|
|
192
192
|
];
|
|
193
193
|
|
|
194
194
|
for (const { file, base, source } of settingsFiles) {
|
|
@@ -285,10 +285,10 @@ function resolveSettingsPackageRoot(source: string, baseDir: string): string | u
|
|
|
285
285
|
return undefined;
|
|
286
286
|
}
|
|
287
287
|
|
|
288
|
-
function collectSettingsPackageSkillPaths(cwd: string): SkillSearchPath[] {
|
|
288
|
+
function collectSettingsPackageSkillPaths(cwd: string, agentDir: string): SkillSearchPath[] {
|
|
289
289
|
const settingsFiles = [
|
|
290
290
|
{ file: path.join(cwd, CONFIG_DIR, "settings.json"), base: path.join(cwd, CONFIG_DIR), source: "project-package" as const },
|
|
291
|
-
{ file: path.join(
|
|
291
|
+
{ file: path.join(agentDir, "settings.json"), base: agentDir, source: "user-package" as const },
|
|
292
292
|
];
|
|
293
293
|
const results: SkillSearchPath[] = [];
|
|
294
294
|
|
|
@@ -315,16 +315,16 @@ function collectSettingsPackageSkillPaths(cwd: string): SkillSearchPath[] {
|
|
|
315
315
|
return results;
|
|
316
316
|
}
|
|
317
317
|
|
|
318
|
-
function buildSkillPaths(cwd: string): SkillSearchPath[] {
|
|
318
|
+
function buildSkillPaths(cwd: string, agentDir: string): SkillSearchPath[] {
|
|
319
319
|
const skillPaths: SkillSearchPath[] = [
|
|
320
320
|
{ path: path.join(cwd, CONFIG_DIR, "skills"), source: "project" },
|
|
321
321
|
{ path: path.join(cwd, ".agents", "skills"), source: "project" },
|
|
322
|
-
{ path: path.join(
|
|
322
|
+
{ path: path.join(agentDir, "skills"), source: "user" },
|
|
323
323
|
{ path: path.join(os.homedir(), ".agents", "skills"), source: "user" },
|
|
324
|
-
...collectInstalledPackageSkillPaths(cwd),
|
|
325
|
-
...collectSettingsPackageSkillPaths(cwd),
|
|
324
|
+
...collectInstalledPackageSkillPaths(cwd, agentDir),
|
|
325
|
+
...collectSettingsPackageSkillPaths(cwd, agentDir),
|
|
326
326
|
...extractSkillPathsFromPackageRoot(cwd, "project-package"),
|
|
327
|
-
...collectSettingsSkillPaths(cwd),
|
|
327
|
+
...collectSettingsSkillPaths(cwd, agentDir),
|
|
328
328
|
];
|
|
329
329
|
|
|
330
330
|
const deduped = new Map<string, SkillSearchPath>();
|
|
@@ -337,15 +337,16 @@ function buildSkillPaths(cwd: string): SkillSearchPath[] {
|
|
|
337
337
|
return [...deduped.values()];
|
|
338
338
|
}
|
|
339
339
|
|
|
340
|
-
function inferSkillSource(filePath: string, cwd: string, sourceHint?: SkillSource): SkillSource {
|
|
340
|
+
function inferSkillSource(filePath: string, cwd: string, agentDir: string, sourceHint?: SkillSource): SkillSource {
|
|
341
341
|
if (sourceHint) return sourceHint;
|
|
342
342
|
|
|
343
343
|
const projectConfigRoot = path.resolve(cwd, CONFIG_DIR);
|
|
344
344
|
const projectSkillsRoot = path.resolve(cwd, CONFIG_DIR, "skills");
|
|
345
345
|
const projectPackagesRoot = path.resolve(cwd, CONFIG_DIR, "npm", "node_modules");
|
|
346
346
|
const projectAgentsRoot = path.resolve(cwd, ".agents");
|
|
347
|
-
const userSkillsRoot = path.resolve(
|
|
348
|
-
const userPackagesRoot = path.resolve(
|
|
347
|
+
const userSkillsRoot = path.resolve(agentDir, "skills");
|
|
348
|
+
const userPackagesRoot = path.resolve(agentDir, "npm", "node_modules");
|
|
349
|
+
const userAgentRoot = path.resolve(agentDir);
|
|
349
350
|
const userAgentsRoot = path.resolve(os.homedir(), ".agents");
|
|
350
351
|
|
|
351
352
|
if (isWithinPath(filePath, projectPackagesRoot)) return "project-package";
|
|
@@ -354,7 +355,7 @@ function inferSkillSource(filePath: string, cwd: string, sourceHint?: SkillSourc
|
|
|
354
355
|
|
|
355
356
|
if (isWithinPath(filePath, userPackagesRoot)) return "user-package";
|
|
356
357
|
if (isWithinPath(filePath, userSkillsRoot) || isWithinPath(filePath, userAgentsRoot)) return "user";
|
|
357
|
-
if (isWithinPath(filePath,
|
|
358
|
+
if (isWithinPath(filePath, userAgentRoot)) return "user-settings";
|
|
358
359
|
|
|
359
360
|
const globalRoot = getGlobalNpmRoot();
|
|
360
361
|
if (globalRoot && isWithinPath(filePath, globalRoot)) return "user-package";
|
|
@@ -390,7 +391,7 @@ function maybeReadSkillDescription(filePath: string): string | undefined {
|
|
|
390
391
|
}
|
|
391
392
|
}
|
|
392
393
|
|
|
393
|
-
function collectFilesystemSkills(cwd: string, skillPaths: SkillSearchPath[]): CachedSkillEntry[] {
|
|
394
|
+
function collectFilesystemSkills(cwd: string, agentDir: string, skillPaths: SkillSearchPath[]): CachedSkillEntry[] {
|
|
394
395
|
const entries: CachedSkillEntry[] = [];
|
|
395
396
|
const seen = new Set<string>();
|
|
396
397
|
let order = 0;
|
|
@@ -403,7 +404,7 @@ function collectFilesystemSkills(cwd: string, skillPaths: SkillSearchPath[]): Ca
|
|
|
403
404
|
entries.push({
|
|
404
405
|
name,
|
|
405
406
|
filePath: resolvedFile,
|
|
406
|
-
source: inferSkillSource(resolvedFile, cwd, sourceHint),
|
|
407
|
+
source: inferSkillSource(resolvedFile, cwd, agentDir, sourceHint),
|
|
407
408
|
description: maybeReadSkillDescription(resolvedFile),
|
|
408
409
|
order: order++,
|
|
409
410
|
});
|
|
@@ -464,12 +465,13 @@ function collectFilesystemSkills(cwd: string, skillPaths: SkillSearchPath[]): Ca
|
|
|
464
465
|
|
|
465
466
|
function getCachedSkills(cwd: string): CachedSkillEntry[] {
|
|
466
467
|
const now = Date.now();
|
|
467
|
-
|
|
468
|
+
const agentDir = getAgentDir();
|
|
469
|
+
if (loadSkillsCache && loadSkillsCache.cwd === cwd && loadSkillsCache.agentDir === agentDir && now - loadSkillsCache.timestamp < LOAD_SKILLS_CACHE_TTL_MS) {
|
|
468
470
|
return loadSkillsCache.skills;
|
|
469
471
|
}
|
|
470
472
|
|
|
471
|
-
const skillPaths = buildSkillPaths(cwd);
|
|
472
|
-
const loaded = collectFilesystemSkills(cwd, skillPaths);
|
|
473
|
+
const skillPaths = buildSkillPaths(cwd, agentDir);
|
|
474
|
+
const loaded = collectFilesystemSkills(cwd, agentDir, skillPaths);
|
|
473
475
|
const dedupedByName = new Map<string, CachedSkillEntry>();
|
|
474
476
|
|
|
475
477
|
for (const entry of loaded) {
|
|
@@ -478,7 +480,7 @@ function getCachedSkills(cwd: string): CachedSkillEntry[] {
|
|
|
478
480
|
}
|
|
479
481
|
|
|
480
482
|
const skills = [...dedupedByName.values()].sort((a, b) => a.order - b.order);
|
|
481
|
-
loadSkillsCache = { cwd, skills, timestamp: now };
|
|
483
|
+
loadSkillsCache = { cwd, agentDir, skills, timestamp: now };
|
|
482
484
|
return skills;
|
|
483
485
|
}
|
|
484
486
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import type { ExtensionConfig } from "../shared/types.ts";
|
|
4
|
+
import { getAgentDir } from "../shared/utils.ts";
|
|
5
|
+
|
|
6
|
+
export function loadConfig(): ExtensionConfig {
|
|
7
|
+
const configPath = path.join(getAgentDir(), "extensions", "subagent", "config.json");
|
|
8
|
+
try {
|
|
9
|
+
if (fs.existsSync(configPath)) {
|
|
10
|
+
return JSON.parse(fs.readFileSync(configPath, "utf-8")) as ExtensionConfig;
|
|
11
|
+
}
|
|
12
|
+
} catch (error) {
|
|
13
|
+
console.error(`Failed to load subagent config from '${configPath}':`, error);
|
|
14
|
+
}
|
|
15
|
+
return {};
|
|
16
|
+
}
|
package/src/extension/index.ts
CHANGED
|
@@ -22,7 +22,7 @@ import { discoverAgents } from "../agents/agents.ts";
|
|
|
22
22
|
import { cleanupAllArtifactDirs, cleanupOldArtifacts, getArtifactsDir } from "../shared/artifacts.ts";
|
|
23
23
|
import { resolveCurrentSessionId } from "../shared/session-identity.ts";
|
|
24
24
|
import { cleanupOldChainDirs } from "../shared/settings.ts";
|
|
25
|
-
import { renderWidget, renderSubagentResult
|
|
25
|
+
import { clearLegacyResultAnimationTimer, renderWidget, renderSubagentResult } from "../tui/render.ts";
|
|
26
26
|
import { SubagentParams } from "./schemas.ts";
|
|
27
27
|
import { createSubagentExecutor, type SubagentParamsLike } from "../runs/foreground/subagent-executor.ts";
|
|
28
28
|
import { createAsyncJobTracker } from "../runs/background/async-job-tracker.ts";
|
|
@@ -35,9 +35,9 @@ import { inspectSubagentStatus } from "../runs/background/run-status.ts";
|
|
|
35
35
|
import registerSubagentNotify, { type SubagentNotifyDetails } from "../runs/background/notify.ts";
|
|
36
36
|
import { SUBAGENT_CHILD_ENV } from "../runs/shared/pi-args.ts";
|
|
37
37
|
import { formatDuration, shortenPath } from "../shared/formatters.ts";
|
|
38
|
+
import { loadConfig } from "./config.ts";
|
|
38
39
|
import {
|
|
39
40
|
type Details,
|
|
40
|
-
type ExtensionConfig,
|
|
41
41
|
type SubagentState,
|
|
42
42
|
ASYNC_DIR,
|
|
43
43
|
DEFAULT_ARTIFACT_CONFIG,
|
|
@@ -56,6 +56,8 @@ import {
|
|
|
56
56
|
type SubagentControlMessageDetails,
|
|
57
57
|
} from "./control-notices.ts";
|
|
58
58
|
|
|
59
|
+
export { loadConfig } from "./config.ts";
|
|
60
|
+
|
|
59
61
|
/**
|
|
60
62
|
* Derive subagent session base directory from parent session file.
|
|
61
63
|
* If parent session is ~/.pi/agent/sessions/abc123.jsonl,
|
|
@@ -72,18 +74,6 @@ function getSubagentSessionRoot(parentSessionFile: string | null): string {
|
|
|
72
74
|
return fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagent-session-"));
|
|
73
75
|
}
|
|
74
76
|
|
|
75
|
-
function loadConfig(): ExtensionConfig {
|
|
76
|
-
const configPath = path.join(os.homedir(), ".pi", "agent", "extensions", "subagent", "config.json");
|
|
77
|
-
try {
|
|
78
|
-
if (fs.existsSync(configPath)) {
|
|
79
|
-
return JSON.parse(fs.readFileSync(configPath, "utf-8")) as ExtensionConfig;
|
|
80
|
-
}
|
|
81
|
-
} catch (error) {
|
|
82
|
-
console.error(`Failed to load subagent config from '${configPath}':`, error);
|
|
83
|
-
}
|
|
84
|
-
return {};
|
|
85
|
-
}
|
|
86
|
-
|
|
87
77
|
function expandTilde(p: string): string {
|
|
88
78
|
return p.startsWith("~/") ? path.join(os.homedir(), p.slice(2)) : p;
|
|
89
79
|
}
|
|
@@ -142,14 +132,11 @@ function createSlashResultComponent(
|
|
|
142
132
|
details: SlashMessageDetails,
|
|
143
133
|
options: { expanded: boolean },
|
|
144
134
|
theme: ExtensionContext["ui"]["theme"],
|
|
145
|
-
requestRender: () => void,
|
|
146
135
|
): Container {
|
|
147
136
|
const container = new Container();
|
|
148
|
-
const animationState: { subagentResultAnimationTimer?: ReturnType<typeof setInterval> } = {};
|
|
149
137
|
let lastVersion = -1;
|
|
150
138
|
container.render = (width: number): string[] => {
|
|
151
139
|
const snapshot = getSlashRenderableSnapshot(details);
|
|
152
|
-
syncResultAnimation(snapshot.result, { state: animationState, invalidate: requestRender });
|
|
153
140
|
if (snapshot.version !== lastVersion || isSlashResultRunning(snapshot.result)) {
|
|
154
141
|
lastVersion = snapshot.version;
|
|
155
142
|
rebuildSlashResultContainer(container, snapshot.result, options, theme);
|
|
@@ -271,8 +258,6 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
271
258
|
primeExistingResults();
|
|
272
259
|
|
|
273
260
|
const runtimeCleanup = () => {
|
|
274
|
-
stopWidgetAnimation();
|
|
275
|
-
stopResultAnimations();
|
|
276
261
|
stopResultWatcher();
|
|
277
262
|
clearPendingForegroundControlNotices(state);
|
|
278
263
|
if (state.poller) {
|
|
@@ -297,7 +282,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
297
282
|
pi.registerMessageRenderer<SlashMessageDetails>(SLASH_RESULT_TYPE, (message, options, theme) => {
|
|
298
283
|
const details = resolveSlashMessageDetails(message.details);
|
|
299
284
|
if (!details) return undefined;
|
|
300
|
-
return createSlashResultComponent(details, options, theme
|
|
285
|
+
return createSlashResultComponent(details, options, theme);
|
|
301
286
|
});
|
|
302
287
|
|
|
303
288
|
pi.registerMessageRenderer<SubagentNotifyDetails>("subagent-notify", (message, options, theme) => {
|
|
@@ -466,7 +451,7 @@ DIAGNOSTICS:
|
|
|
466
451
|
},
|
|
467
452
|
|
|
468
453
|
renderResult(result, options, theme, context) {
|
|
469
|
-
|
|
454
|
+
clearLegacyResultAnimationTimer(context);
|
|
470
455
|
return renderSubagentResult(result, options, theme);
|
|
471
456
|
},
|
|
472
457
|
|
|
@@ -514,6 +499,7 @@ DIAGNOSTICS:
|
|
|
514
499
|
state.lastUiContext = ctx;
|
|
515
500
|
if (state.asyncJobs.size > 0) {
|
|
516
501
|
renderWidget(ctx, Array.from(state.asyncJobs.values()));
|
|
502
|
+
ctx.ui.requestRender?.();
|
|
517
503
|
ensurePoller();
|
|
518
504
|
}
|
|
519
505
|
});
|
|
@@ -569,8 +555,6 @@ DIAGNOSTICS:
|
|
|
569
555
|
slashBridge.dispose();
|
|
570
556
|
promptTemplateBridge.cancelAll();
|
|
571
557
|
promptTemplateBridge.dispose();
|
|
572
|
-
stopWidgetAnimation();
|
|
573
|
-
stopResultAnimations();
|
|
574
558
|
if (globalStore[runtimeCleanupStoreKey] === runtimeCleanup) {
|
|
575
559
|
delete globalStore[runtimeCleanupStoreKey];
|
|
576
560
|
}
|
|
@@ -4,12 +4,13 @@ import * as os from "node:os";
|
|
|
4
4
|
import * as path from "node:path";
|
|
5
5
|
import type { AgentConfig } from "../agents/agents.ts";
|
|
6
6
|
import type { ExtensionConfig, IntercomBridgeConfig, IntercomBridgeMode } from "../shared/types.ts";
|
|
7
|
+
import { getAgentDir } from "../shared/utils.ts";
|
|
7
8
|
|
|
8
9
|
const PI_INTERCOM_PACKAGE_NAME = "pi-intercom";
|
|
9
10
|
const CONFIG_DIR = ".pi";
|
|
10
11
|
|
|
11
12
|
function defaultAgentDir(): string {
|
|
12
|
-
return
|
|
13
|
+
return getAgentDir();
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
function defaultIntercomExtensionDir(agentDir = defaultAgentDir()): string {
|
|
@@ -11,7 +11,7 @@ import { createRequire } from "node:module";
|
|
|
11
11
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
12
12
|
import type { AgentConfig } from "../../agents/agents.ts";
|
|
13
13
|
import { applyThinkingSuffix } from "../shared/pi-args.ts";
|
|
14
|
-
import { injectSingleOutputInstruction, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
|
|
14
|
+
import { injectSingleOutputInstruction, normalizeSingleOutputOverride, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
|
|
15
15
|
import { buildChainInstructions, isParallelStep, resolveStepBehavior, suppressProgressForReadOnlyTask, writeInitialProgressFile, type ChainStep, type ResolvedStepBehavior, type SequentialStep, type StepOverrides } from "../../shared/settings.ts";
|
|
16
16
|
import type { RunnerStep } from "../shared/parallel-utils.ts";
|
|
17
17
|
import { resolvePiPackageRoot } from "../shared/pi-spawn.ts";
|
|
@@ -126,7 +126,7 @@ interface AsyncSingleParams {
|
|
|
126
126
|
sessionRoot?: string;
|
|
127
127
|
sessionFile?: string;
|
|
128
128
|
skills?: string[];
|
|
129
|
-
output?: string |
|
|
129
|
+
output?: string | boolean;
|
|
130
130
|
outputMode?: "inline" | "file-only";
|
|
131
131
|
modelOverride?: string;
|
|
132
132
|
availableModels?: AvailableModelInfo[];
|
|
@@ -323,6 +323,7 @@ export function executeAsyncChain(
|
|
|
323
323
|
tools: a.tools,
|
|
324
324
|
extensions: a.extensions,
|
|
325
325
|
mcpDirectTools: a.mcpDirectTools,
|
|
326
|
+
completionGuard: a.completionGuard,
|
|
326
327
|
systemPrompt,
|
|
327
328
|
systemPromptMode: a.systemPromptMode,
|
|
328
329
|
inheritProjectContext: a.inheritProjectContext,
|
|
@@ -523,7 +524,8 @@ export function executeAsyncSingle(
|
|
|
523
524
|
};
|
|
524
525
|
}
|
|
525
526
|
|
|
526
|
-
const
|
|
527
|
+
const effectiveOutput = normalizeSingleOutputOverride(params.output, agentConfig.output);
|
|
528
|
+
const outputPath = resolveSingleOutputPath(effectiveOutput, ctx.cwd, runnerCwd);
|
|
527
529
|
const outputMode = params.outputMode ?? "inline";
|
|
528
530
|
const validationError = validateFileOnlyOutputMode(outputMode, outputPath, `Async single run (${agent})`);
|
|
529
531
|
if (validationError) return formatAsyncStartError("single", validationError);
|
|
@@ -550,6 +552,7 @@ export function executeAsyncSingle(
|
|
|
550
552
|
tools: agentConfig.tools,
|
|
551
553
|
extensions: agentConfig.extensions,
|
|
552
554
|
mcpDirectTools: agentConfig.mcpDirectTools,
|
|
555
|
+
completionGuard: agentConfig.completionGuard,
|
|
553
556
|
systemPrompt,
|
|
554
557
|
systemPromptMode: agentConfig.systemPromptMode,
|
|
555
558
|
inheritProjectContext: agentConfig.inheritProjectContext,
|