oira666_pi-subagent 0.2.21 → 0.2.22
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/LICENSE +21 -21
- package/README.md +460 -456
- package/agents/code-architect.md +22 -22
- package/agents/code-reviwer.md +21 -21
- package/agents/code-writer.md +16 -16
- package/agents.ts +188 -188
- package/index.ts +17 -3
- package/package.json +74 -74
- package/render.ts +471 -471
- package/resume.ts +227 -227
- package/runner.ts +1059 -1059
- package/shared.ts +97 -97
- package/types.ts +311 -311
package/agents/code-architect.md
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: code-architect
|
|
3
|
-
description: Technical design agent for shaping implementations, APIs, module boundaries, and tradeoffs before coding. Use this agent for plans and architecture decisions.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
You are a senior software architect focused on practical design.
|
|
7
|
-
|
|
8
|
-
Your job is to propose implementation approaches that balance simplicity,
|
|
9
|
-
maintainability, extensibility, and delivery speed.
|
|
10
|
-
|
|
11
|
-
Guidelines:
|
|
12
|
-
- Start from the current codebase and constraints, not an idealized rewrite.
|
|
13
|
-
- Prefer simple designs with clear ownership and minimal moving parts.
|
|
14
|
-
- Call out tradeoffs, risks, migration concerns, and compatibility implications.
|
|
15
|
-
- Recommend concrete module boundaries, data flow, and rollout steps when useful.
|
|
16
|
-
- Avoid unnecessary abstraction.
|
|
17
|
-
|
|
18
|
-
In your final response:
|
|
19
|
-
- Present the recommended approach first.
|
|
20
|
-
- Include 1-2 viable alternatives when relevant.
|
|
21
|
-
- Explain why the recommendation fits this codebase.
|
|
22
|
-
- Highlight the biggest implementation risks or unknowns.
|
|
1
|
+
---
|
|
2
|
+
name: code-architect
|
|
3
|
+
description: Technical design agent for shaping implementations, APIs, module boundaries, and tradeoffs before coding. Use this agent for plans and architecture decisions.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
You are a senior software architect focused on practical design.
|
|
7
|
+
|
|
8
|
+
Your job is to propose implementation approaches that balance simplicity,
|
|
9
|
+
maintainability, extensibility, and delivery speed.
|
|
10
|
+
|
|
11
|
+
Guidelines:
|
|
12
|
+
- Start from the current codebase and constraints, not an idealized rewrite.
|
|
13
|
+
- Prefer simple designs with clear ownership and minimal moving parts.
|
|
14
|
+
- Call out tradeoffs, risks, migration concerns, and compatibility implications.
|
|
15
|
+
- Recommend concrete module boundaries, data flow, and rollout steps when useful.
|
|
16
|
+
- Avoid unnecessary abstraction.
|
|
17
|
+
|
|
18
|
+
In your final response:
|
|
19
|
+
- Present the recommended approach first.
|
|
20
|
+
- Include 1-2 viable alternatives when relevant.
|
|
21
|
+
- Explain why the recommendation fits this codebase.
|
|
22
|
+
- Highlight the biggest implementation risks or unknowns.
|
package/agents/code-reviwer.md
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: code-reviwer
|
|
3
|
-
description: Code review specialist for finding bugs, regressions, edge cases, and maintainability issues. Use this agent to review code, plans, or patches.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
You are a skeptical, detail-oriented code reviewer.
|
|
7
|
-
|
|
8
|
-
Your goal is to identify the most important correctness, reliability, security,
|
|
9
|
-
and maintainability issues in the provided code or plan.
|
|
10
|
-
|
|
11
|
-
Guidelines:
|
|
12
|
-
- Prioritize concrete issues over stylistic preferences.
|
|
13
|
-
- Look for broken assumptions, missing edge-case handling, risky changes, and test gaps.
|
|
14
|
-
- Prefer concise findings with clear reasoning and likely impact.
|
|
15
|
-
- If the code looks good, say so explicitly instead of inventing problems.
|
|
16
|
-
- Do not edit files; focus on analysis and recommendations.
|
|
17
|
-
|
|
18
|
-
In your final response:
|
|
19
|
-
- List findings ordered by severity.
|
|
20
|
-
- Include file paths or symbols when possible.
|
|
21
|
-
- If there are no meaningful issues, say "No significant issues found" and mention any residual risks briefly.
|
|
1
|
+
---
|
|
2
|
+
name: code-reviwer
|
|
3
|
+
description: Code review specialist for finding bugs, regressions, edge cases, and maintainability issues. Use this agent to review code, plans, or patches.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
You are a skeptical, detail-oriented code reviewer.
|
|
7
|
+
|
|
8
|
+
Your goal is to identify the most important correctness, reliability, security,
|
|
9
|
+
and maintainability issues in the provided code or plan.
|
|
10
|
+
|
|
11
|
+
Guidelines:
|
|
12
|
+
- Prioritize concrete issues over stylistic preferences.
|
|
13
|
+
- Look for broken assumptions, missing edge-case handling, risky changes, and test gaps.
|
|
14
|
+
- Prefer concise findings with clear reasoning and likely impact.
|
|
15
|
+
- If the code looks good, say so explicitly instead of inventing problems.
|
|
16
|
+
- Do not edit files; focus on analysis and recommendations.
|
|
17
|
+
|
|
18
|
+
In your final response:
|
|
19
|
+
- List findings ordered by severity.
|
|
20
|
+
- Include file paths or symbols when possible.
|
|
21
|
+
- If there are no meaningful issues, say "No significant issues found" and mention any residual risks briefly.
|
package/agents/code-writer.md
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: code-writer
|
|
3
|
-
description: Focused implementation agent for writing and refactoring code with small, reliable diffs. Use this agent when you want code changes made directly.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
You are a pragmatic software engineer focused on implementation.
|
|
7
|
-
|
|
8
|
-
Your job is to turn requirements into small, correct code changes.
|
|
9
|
-
|
|
10
|
-
Guidelines:
|
|
11
|
-
- Read the relevant files before editing.
|
|
12
|
-
- Prefer minimal diffs that fit the existing style and architecture.
|
|
13
|
-
- Preserve working behavior unless the task explicitly changes it.
|
|
14
|
-
- When details are ambiguous, choose the simplest reasonable implementation and state your assumption.
|
|
15
|
-
- If helpful, run targeted commands to inspect the codebase or validate your changes.
|
|
16
|
-
- In your final response, summarize what you changed, note any assumptions, and mention any validation you performed.
|
|
1
|
+
---
|
|
2
|
+
name: code-writer
|
|
3
|
+
description: Focused implementation agent for writing and refactoring code with small, reliable diffs. Use this agent when you want code changes made directly.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
You are a pragmatic software engineer focused on implementation.
|
|
7
|
+
|
|
8
|
+
Your job is to turn requirements into small, correct code changes.
|
|
9
|
+
|
|
10
|
+
Guidelines:
|
|
11
|
+
- Read the relevant files before editing.
|
|
12
|
+
- Prefer minimal diffs that fit the existing style and architecture.
|
|
13
|
+
- Preserve working behavior unless the task explicitly changes it.
|
|
14
|
+
- When details are ambiguous, choose the simplest reasonable implementation and state your assumption.
|
|
15
|
+
- If helpful, run targeted commands to inspect the codebase or validate your changes.
|
|
16
|
+
- In your final response, summarize what you changed, note any assumptions, and mention any validation you performed.
|
package/agents.ts
CHANGED
|
@@ -1,188 +1,188 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Agent discovery and configuration.
|
|
3
|
-
*
|
|
4
|
-
* Agents are Markdown files with YAML frontmatter that define name, description,
|
|
5
|
-
* optional model/tools, and a system prompt body.
|
|
6
|
-
*
|
|
7
|
-
* Lookup locations:
|
|
8
|
-
* - User agents: ~/.pi/agent/agents/*.md (or $PI_CODING_AGENT_DIR/agents/ when env var is set)
|
|
9
|
-
* - Project agents: .pi/agents/*.md (walks up from cwd)
|
|
10
|
-
* - Bundled agents: ./agents/*.md (fallback only when no user/project agents exist)
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { getAgentDir, parseFrontmatter } from "@mariozechner/pi-coding-agent";
|
|
14
|
-
import * as fs from "node:fs";
|
|
15
|
-
import * as path from "node:path";
|
|
16
|
-
import { fileURLToPath } from "node:url";
|
|
17
|
-
|
|
18
|
-
export type AgentScope = "user" | "project" | "both";
|
|
19
|
-
export type AgentSource = "user" | "project" | "builtin";
|
|
20
|
-
|
|
21
|
-
export interface AgentConfig {
|
|
22
|
-
name: string;
|
|
23
|
-
description: string;
|
|
24
|
-
tools?: string[];
|
|
25
|
-
model?: string;
|
|
26
|
-
thinking?: string;
|
|
27
|
-
systemPrompt: string;
|
|
28
|
-
source: AgentSource;
|
|
29
|
-
filePath: string;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface AgentDiscoveryResult {
|
|
33
|
-
agents: AgentConfig[];
|
|
34
|
-
projectAgentsDir: string | null;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const BUNDLED_AGENTS_DIR = path.join(
|
|
38
|
-
path.dirname(fileURLToPath(import.meta.url)),
|
|
39
|
-
"agents",
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
// ---------------------------------------------------------------------------
|
|
43
|
-
// Internal helpers
|
|
44
|
-
// ---------------------------------------------------------------------------
|
|
45
|
-
|
|
46
|
-
function isDirectory(p: string): boolean {
|
|
47
|
-
try {
|
|
48
|
-
return fs.statSync(p).isDirectory();
|
|
49
|
-
} catch {
|
|
50
|
-
return false;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/** Walk up from `cwd` looking for a `.pi/agents` directory. */
|
|
55
|
-
function findNearestProjectAgentsDir(cwd: string): string | null {
|
|
56
|
-
let dir = cwd;
|
|
57
|
-
while (true) {
|
|
58
|
-
const candidate = path.join(dir, ".pi", "agents");
|
|
59
|
-
if (isDirectory(candidate)) return candidate;
|
|
60
|
-
const parent = path.dirname(dir);
|
|
61
|
-
if (parent === dir) return null;
|
|
62
|
-
dir = parent;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/** Parse a single agent markdown file into an AgentConfig. Returns null on skip. */
|
|
67
|
-
function parseAgentFile(filePath: string, source: AgentSource): AgentConfig | null {
|
|
68
|
-
let content: string;
|
|
69
|
-
try {
|
|
70
|
-
content = fs.readFileSync(filePath, "utf-8");
|
|
71
|
-
} catch {
|
|
72
|
-
return null;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
let parsed: { frontmatter: Record<string, unknown>; body: string };
|
|
76
|
-
try {
|
|
77
|
-
parsed = parseFrontmatter<Record<string, unknown>>(content);
|
|
78
|
-
} catch (err) {
|
|
79
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
80
|
-
console.warn(`[pi-subagent] Skipping invalid agent file "${filePath}": ${message}`);
|
|
81
|
-
return null;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const frontmatter = parsed.frontmatter ?? {};
|
|
85
|
-
const body = parsed.body ?? "";
|
|
86
|
-
|
|
87
|
-
const name = typeof frontmatter.name === "string" ? frontmatter.name.trim() : "";
|
|
88
|
-
const description = typeof frontmatter.description === "string" ? frontmatter.description.trim() : "";
|
|
89
|
-
if (!name || !description) return null;
|
|
90
|
-
|
|
91
|
-
let tools: string[] | undefined;
|
|
92
|
-
if (typeof frontmatter.tools === "string") {
|
|
93
|
-
const parsedTools = frontmatter.tools
|
|
94
|
-
.split(",")
|
|
95
|
-
.map((t) => t.trim())
|
|
96
|
-
.filter(Boolean);
|
|
97
|
-
if (parsedTools.length > 0) tools = parsedTools;
|
|
98
|
-
} else if (Array.isArray(frontmatter.tools)) {
|
|
99
|
-
const parsedTools = frontmatter.tools
|
|
100
|
-
.filter((t): t is string => typeof t === "string")
|
|
101
|
-
.map((t) => t.trim())
|
|
102
|
-
.filter(Boolean);
|
|
103
|
-
if (parsedTools.length > 0) tools = parsedTools;
|
|
104
|
-
} else if (frontmatter.tools !== undefined) {
|
|
105
|
-
console.warn(
|
|
106
|
-
`[pi-subagent] Ignoring invalid tools field in "${filePath}". Expected a comma-separated string or string array.`,
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return {
|
|
111
|
-
name,
|
|
112
|
-
description,
|
|
113
|
-
tools,
|
|
114
|
-
model: typeof frontmatter.model === "string" ? frontmatter.model : undefined,
|
|
115
|
-
thinking: typeof frontmatter.thinking === "string" ? frontmatter.thinking : undefined,
|
|
116
|
-
systemPrompt: body,
|
|
117
|
-
source,
|
|
118
|
-
filePath,
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/** Load all agent definitions from a directory. */
|
|
123
|
-
function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {
|
|
124
|
-
if (!fs.existsSync(dir)) return [];
|
|
125
|
-
|
|
126
|
-
let entries: fs.Dirent[];
|
|
127
|
-
try {
|
|
128
|
-
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
129
|
-
} catch {
|
|
130
|
-
return [];
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
134
|
-
|
|
135
|
-
const agents: AgentConfig[] = [];
|
|
136
|
-
for (const entry of entries) {
|
|
137
|
-
if (!entry.name.endsWith(".md")) continue;
|
|
138
|
-
if (!entry.isFile() && !entry.isSymbolicLink()) continue;
|
|
139
|
-
|
|
140
|
-
const agent = parseAgentFile(path.join(dir, entry.name), source);
|
|
141
|
-
if (agent) agents.push(agent);
|
|
142
|
-
}
|
|
143
|
-
return agents;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Merge agents with last-write-wins deduplication by name.
|
|
148
|
-
* Priority (lowest → highest): user < project.
|
|
149
|
-
*/
|
|
150
|
-
function dedupeAgents(
|
|
151
|
-
userAgents: AgentConfig[],
|
|
152
|
-
projectAgents: AgentConfig[],
|
|
153
|
-
): AgentConfig[] {
|
|
154
|
-
const agentMap = new Map<string, AgentConfig>();
|
|
155
|
-
for (const agent of userAgents) agentMap.set(agent.name, agent);
|
|
156
|
-
for (const agent of projectAgents) agentMap.set(agent.name, agent);
|
|
157
|
-
return Array.from(agentMap.values());
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// ---------------------------------------------------------------------------
|
|
161
|
-
// Public API
|
|
162
|
-
// ---------------------------------------------------------------------------
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Discover all available agents according to the requested scope.
|
|
166
|
-
*
|
|
167
|
-
* When scope is "both", project agents override user agents with the same name.
|
|
168
|
-
* If no user or project agents exist at all, bundled fallback agents are returned.
|
|
169
|
-
*/
|
|
170
|
-
export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryResult {
|
|
171
|
-
const userDir = path.join(getAgentDir(), "agents");
|
|
172
|
-
const projectAgentsDir = findNearestProjectAgentsDir(cwd);
|
|
173
|
-
|
|
174
|
-
const userAgents = loadAgentsFromDir(userDir, "user");
|
|
175
|
-
const projectAgents = projectAgentsDir ? loadAgentsFromDir(projectAgentsDir, "project") : [];
|
|
176
|
-
|
|
177
|
-
const hasConfiguredAgents = userAgents.length > 0 || projectAgents.length > 0;
|
|
178
|
-
if (!hasConfiguredAgents) {
|
|
179
|
-
return {
|
|
180
|
-
agents: loadAgentsFromDir(BUNDLED_AGENTS_DIR, "builtin"),
|
|
181
|
-
projectAgentsDir,
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if (scope === "user") return { agents: userAgents, projectAgentsDir };
|
|
186
|
-
if (scope === "project") return { agents: projectAgents, projectAgentsDir };
|
|
187
|
-
return { agents: dedupeAgents(userAgents, projectAgents), projectAgentsDir };
|
|
188
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Agent discovery and configuration.
|
|
3
|
+
*
|
|
4
|
+
* Agents are Markdown files with YAML frontmatter that define name, description,
|
|
5
|
+
* optional model/tools, and a system prompt body.
|
|
6
|
+
*
|
|
7
|
+
* Lookup locations:
|
|
8
|
+
* - User agents: ~/.pi/agent/agents/*.md (or $PI_CODING_AGENT_DIR/agents/ when env var is set)
|
|
9
|
+
* - Project agents: .pi/agents/*.md (walks up from cwd)
|
|
10
|
+
* - Bundled agents: ./agents/*.md (fallback only when no user/project agents exist)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { getAgentDir, parseFrontmatter } from "@mariozechner/pi-coding-agent";
|
|
14
|
+
import * as fs from "node:fs";
|
|
15
|
+
import * as path from "node:path";
|
|
16
|
+
import { fileURLToPath } from "node:url";
|
|
17
|
+
|
|
18
|
+
export type AgentScope = "user" | "project" | "both";
|
|
19
|
+
export type AgentSource = "user" | "project" | "builtin";
|
|
20
|
+
|
|
21
|
+
export interface AgentConfig {
|
|
22
|
+
name: string;
|
|
23
|
+
description: string;
|
|
24
|
+
tools?: string[];
|
|
25
|
+
model?: string;
|
|
26
|
+
thinking?: string;
|
|
27
|
+
systemPrompt: string;
|
|
28
|
+
source: AgentSource;
|
|
29
|
+
filePath: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface AgentDiscoveryResult {
|
|
33
|
+
agents: AgentConfig[];
|
|
34
|
+
projectAgentsDir: string | null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const BUNDLED_AGENTS_DIR = path.join(
|
|
38
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
39
|
+
"agents",
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// Internal helpers
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
function isDirectory(p: string): boolean {
|
|
47
|
+
try {
|
|
48
|
+
return fs.statSync(p).isDirectory();
|
|
49
|
+
} catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Walk up from `cwd` looking for a `.pi/agents` directory. */
|
|
55
|
+
function findNearestProjectAgentsDir(cwd: string): string | null {
|
|
56
|
+
let dir = cwd;
|
|
57
|
+
while (true) {
|
|
58
|
+
const candidate = path.join(dir, ".pi", "agents");
|
|
59
|
+
if (isDirectory(candidate)) return candidate;
|
|
60
|
+
const parent = path.dirname(dir);
|
|
61
|
+
if (parent === dir) return null;
|
|
62
|
+
dir = parent;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Parse a single agent markdown file into an AgentConfig. Returns null on skip. */
|
|
67
|
+
function parseAgentFile(filePath: string, source: AgentSource): AgentConfig | null {
|
|
68
|
+
let content: string;
|
|
69
|
+
try {
|
|
70
|
+
content = fs.readFileSync(filePath, "utf-8");
|
|
71
|
+
} catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
let parsed: { frontmatter: Record<string, unknown>; body: string };
|
|
76
|
+
try {
|
|
77
|
+
parsed = parseFrontmatter<Record<string, unknown>>(content);
|
|
78
|
+
} catch (err) {
|
|
79
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
80
|
+
console.warn(`[pi-subagent] Skipping invalid agent file "${filePath}": ${message}`);
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const frontmatter = parsed.frontmatter ?? {};
|
|
85
|
+
const body = parsed.body ?? "";
|
|
86
|
+
|
|
87
|
+
const name = typeof frontmatter.name === "string" ? frontmatter.name.trim() : "";
|
|
88
|
+
const description = typeof frontmatter.description === "string" ? frontmatter.description.trim() : "";
|
|
89
|
+
if (!name || !description) return null;
|
|
90
|
+
|
|
91
|
+
let tools: string[] | undefined;
|
|
92
|
+
if (typeof frontmatter.tools === "string") {
|
|
93
|
+
const parsedTools = frontmatter.tools
|
|
94
|
+
.split(",")
|
|
95
|
+
.map((t) => t.trim())
|
|
96
|
+
.filter(Boolean);
|
|
97
|
+
if (parsedTools.length > 0) tools = parsedTools;
|
|
98
|
+
} else if (Array.isArray(frontmatter.tools)) {
|
|
99
|
+
const parsedTools = frontmatter.tools
|
|
100
|
+
.filter((t): t is string => typeof t === "string")
|
|
101
|
+
.map((t) => t.trim())
|
|
102
|
+
.filter(Boolean);
|
|
103
|
+
if (parsedTools.length > 0) tools = parsedTools;
|
|
104
|
+
} else if (frontmatter.tools !== undefined) {
|
|
105
|
+
console.warn(
|
|
106
|
+
`[pi-subagent] Ignoring invalid tools field in "${filePath}". Expected a comma-separated string or string array.`,
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
name,
|
|
112
|
+
description,
|
|
113
|
+
tools,
|
|
114
|
+
model: typeof frontmatter.model === "string" ? frontmatter.model : undefined,
|
|
115
|
+
thinking: typeof frontmatter.thinking === "string" ? frontmatter.thinking : undefined,
|
|
116
|
+
systemPrompt: body,
|
|
117
|
+
source,
|
|
118
|
+
filePath,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Load all agent definitions from a directory. */
|
|
123
|
+
function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {
|
|
124
|
+
if (!fs.existsSync(dir)) return [];
|
|
125
|
+
|
|
126
|
+
let entries: fs.Dirent[];
|
|
127
|
+
try {
|
|
128
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
129
|
+
} catch {
|
|
130
|
+
return [];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
134
|
+
|
|
135
|
+
const agents: AgentConfig[] = [];
|
|
136
|
+
for (const entry of entries) {
|
|
137
|
+
if (!entry.name.endsWith(".md")) continue;
|
|
138
|
+
if (!entry.isFile() && !entry.isSymbolicLink()) continue;
|
|
139
|
+
|
|
140
|
+
const agent = parseAgentFile(path.join(dir, entry.name), source);
|
|
141
|
+
if (agent) agents.push(agent);
|
|
142
|
+
}
|
|
143
|
+
return agents;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Merge agents with last-write-wins deduplication by name.
|
|
148
|
+
* Priority (lowest → highest): user < project.
|
|
149
|
+
*/
|
|
150
|
+
function dedupeAgents(
|
|
151
|
+
userAgents: AgentConfig[],
|
|
152
|
+
projectAgents: AgentConfig[],
|
|
153
|
+
): AgentConfig[] {
|
|
154
|
+
const agentMap = new Map<string, AgentConfig>();
|
|
155
|
+
for (const agent of userAgents) agentMap.set(agent.name, agent);
|
|
156
|
+
for (const agent of projectAgents) agentMap.set(agent.name, agent);
|
|
157
|
+
return Array.from(agentMap.values());
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ---------------------------------------------------------------------------
|
|
161
|
+
// Public API
|
|
162
|
+
// ---------------------------------------------------------------------------
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Discover all available agents according to the requested scope.
|
|
166
|
+
*
|
|
167
|
+
* When scope is "both", project agents override user agents with the same name.
|
|
168
|
+
* If no user or project agents exist at all, bundled fallback agents are returned.
|
|
169
|
+
*/
|
|
170
|
+
export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryResult {
|
|
171
|
+
const userDir = path.join(getAgentDir(), "agents");
|
|
172
|
+
const projectAgentsDir = findNearestProjectAgentsDir(cwd);
|
|
173
|
+
|
|
174
|
+
const userAgents = loadAgentsFromDir(userDir, "user");
|
|
175
|
+
const projectAgents = projectAgentsDir ? loadAgentsFromDir(projectAgentsDir, "project") : [];
|
|
176
|
+
|
|
177
|
+
const hasConfiguredAgents = userAgents.length > 0 || projectAgents.length > 0;
|
|
178
|
+
if (!hasConfiguredAgents) {
|
|
179
|
+
return {
|
|
180
|
+
agents: loadAgentsFromDir(BUNDLED_AGENTS_DIR, "builtin"),
|
|
181
|
+
projectAgentsDir,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (scope === "user") return { agents: userAgents, projectAgentsDir };
|
|
186
|
+
if (scope === "project") return { agents: projectAgents, projectAgentsDir };
|
|
187
|
+
return { agents: dedupeAgents(userAgents, projectAgents), projectAgentsDir };
|
|
188
|
+
}
|
package/index.ts
CHANGED
|
@@ -363,6 +363,19 @@ function hasCliInitialPrompt(argv: string[]): boolean {
|
|
|
363
363
|
return false;
|
|
364
364
|
}
|
|
365
365
|
|
|
366
|
+
function isStreamingSteerInput(event: any, ctx: { isIdle: () => boolean }): boolean {
|
|
367
|
+
// Newer Pi versions provide this directly. Undefined means the input was
|
|
368
|
+
// submitted while idle; "followUp" means it is queued for the next turn.
|
|
369
|
+
// Only "steer" should be intercepted for subagent broadcast routing.
|
|
370
|
+
if (Object.prototype.hasOwnProperty.call(event ?? {}, "streamingBehavior")) {
|
|
371
|
+
return event.streamingBehavior === "steer";
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Backward compatibility for older Pi versions that emitted `input` without
|
|
375
|
+
// delivery metadata: non-idle input was treated as a steering message.
|
|
376
|
+
return !ctx.isIdle();
|
|
377
|
+
}
|
|
378
|
+
|
|
366
379
|
const RESUME_STATE_KEY = "__piSubagentResumeState";
|
|
367
380
|
const SUBAGENT_FALLBACK_MODEL_ENV = "PI_SUBAGENT_FALLBACK_MODEL";
|
|
368
381
|
const RESUME_INTERACTIVE_DELAY_MS = 50;
|
|
@@ -1033,10 +1046,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
1033
1046
|
pi.on("input", async (event, ctx) => {
|
|
1034
1047
|
try {
|
|
1035
1048
|
// Pi emits this before it applies the built-in streaming behavior. When a
|
|
1036
|
-
// subagent tool is running,
|
|
1037
|
-
//
|
|
1049
|
+
// subagent tool is running, only mid-stream steering messages should be
|
|
1050
|
+
// candidates for child broadcast. Idle prompts and queued follow-ups must
|
|
1051
|
+
// continue normally so they reach the parent conversation as intended.
|
|
1038
1052
|
if (activeSubagents.size === 0) return { action: "continue" as const };
|
|
1039
|
-
if (ctx
|
|
1053
|
+
if (!isStreamingSteerInput(event, ctx)) return { action: "continue" as const };
|
|
1040
1054
|
const result = await askBroadcastForSteering(event.text, ctx);
|
|
1041
1055
|
return result === "handled"
|
|
1042
1056
|
? { action: "handled" as const }
|