@fosterg4/pi-subagent 1.0.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/README.md +170 -0
- package/agents/planner.md +71 -0
- package/agents/reviewer.md +81 -0
- package/agents/scout.md +66 -0
- package/agents/worker.md +48 -0
- package/agents.ts +196 -0
- package/index.ts +1436 -0
- package/package.json +47 -0
- package/prompts/implement-and-review.md +10 -0
- package/prompts/implement.md +10 -0
- package/prompts/scout-and-plan.md +9 -0
- package/validate.ts +168 -0
package/README.md
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# @fosterg4/pi-subagent
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@fosterg4/pi-subagent)
|
|
4
|
+
|
|
5
|
+
Delegate complex tasks to specialized sub-agents with isolated context windows, structured JSON handoff, contract schemas, and live TUI streaming — all within [pi](https://pi.dev).
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Isolated context** — Each subagent runs in a separate `pi` process with its own context window
|
|
10
|
+
- **Three execution modes** — Single, parallel (max 8, concurrency 4), and chain (sequential with data handoff)
|
|
11
|
+
- **Structured JSON handoff** — Agents pass typed data between each other, not freeform markdown
|
|
12
|
+
- **Contract schemas** — `inputSchema`/`outputSchema` in agent frontmatter ensures valid handoffs
|
|
13
|
+
- **Live TUI streaming** — Subagent tool calls (read, bash, grep, etc.) stream in real-time
|
|
14
|
+
- **Bundled agents** — 4 built-in agents ready to use: scout, planner, reviewer, worker
|
|
15
|
+
- **Workflow prompts** — `/implement`, `/scout-and-plan`, `/implement-and-review` commands
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pi install npm:@fosterg4/pi-subagent
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
For a quick test without installing:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pi -e npm:@fosterg4/pi-subagent
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
### Single agent
|
|
32
|
+
|
|
33
|
+
Ask the LLM to use a subagent:
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
Use scout to find all authentication code in the project
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The LLM will call the `subagent` tool with `{ agent: "scout", task: "..." }`.
|
|
40
|
+
|
|
41
|
+
### Parallel execution
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
Run 2 scouts in parallel: one to find models, one to find providers
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Chained workflow
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
Use a chain: first have scout find the read tool, then have planner suggest improvements
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Workflow prompts
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
/implement add Redis caching to the session store
|
|
57
|
+
/scout-and-plan refactor auth to support OAuth
|
|
58
|
+
/implement-and-review add input validation to API endpoints
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Bundled Agents
|
|
62
|
+
|
|
63
|
+
| Agent | Purpose | Default Model |
|
|
64
|
+
|-------|---------|---------------|
|
|
65
|
+
| `scout` | Fast codebase recon — returns structured findings | claude-haiku-4-5 |
|
|
66
|
+
| `planner` | Creates implementation plans from context & requirements | claude-sonnet-4-5 |
|
|
67
|
+
| `reviewer` | Code review — quality, security, maintainability | claude-sonnet-4-5 |
|
|
68
|
+
| `worker` | General-purpose implementation with full capabilities | claude-sonnet-4-5 |
|
|
69
|
+
|
|
70
|
+
Each agent has a defined `inputSchema` and `outputSchema` in its frontmatter, ensuring structured data flows between chained agents.
|
|
71
|
+
|
|
72
|
+
## Tool Parameters
|
|
73
|
+
|
|
74
|
+
The `subagent` tool accepts three mutually exclusive modes:
|
|
75
|
+
|
|
76
|
+
### Single mode
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"agent": "scout",
|
|
81
|
+
"task": "Find all authentication code",
|
|
82
|
+
"cwd": "/optional/working/directory"
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Parallel mode
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"tasks": [
|
|
91
|
+
{ "agent": "scout", "task": "Find models" },
|
|
92
|
+
{ "agent": "scout", "task": "Find providers" }
|
|
93
|
+
]
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Chain mode
|
|
98
|
+
|
|
99
|
+
```json
|
|
100
|
+
{
|
|
101
|
+
"chain": [
|
|
102
|
+
{ "agent": "scout", "task": "Investigate the codebase" },
|
|
103
|
+
{ "agent": "planner", "task": "Create a plan from: {previous}" }
|
|
104
|
+
]
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Common options
|
|
109
|
+
|
|
110
|
+
| Option | Type | Default | Description |
|
|
111
|
+
|--------|------|---------|-------------|
|
|
112
|
+
| `agentScope` | `"user"`, `"project"`, `"both"` | `"user"` | Which agent directories to search |
|
|
113
|
+
| `confirmProjectAgents` | `boolean` | `true` | Prompt before running project agents |
|
|
114
|
+
| `cwd` | `string` | current dir | Working directory for subprocess |
|
|
115
|
+
|
|
116
|
+
## Custom Agents
|
|
117
|
+
|
|
118
|
+
Create your own agents as `.md` files with YAML frontmatter:
|
|
119
|
+
|
|
120
|
+
```markdown
|
|
121
|
+
---
|
|
122
|
+
name: my-agent
|
|
123
|
+
description: What this agent does
|
|
124
|
+
tools: read, grep, find, ls, bash
|
|
125
|
+
model: claude-haiku-4-5
|
|
126
|
+
inputSchema:
|
|
127
|
+
type: object
|
|
128
|
+
properties:
|
|
129
|
+
query:
|
|
130
|
+
type: string
|
|
131
|
+
required: [query]
|
|
132
|
+
outputSchema:
|
|
133
|
+
type: object
|
|
134
|
+
properties:
|
|
135
|
+
result:
|
|
136
|
+
type: string
|
|
137
|
+
required: [result]
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
System prompt for the agent goes here.
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**Agent locations** (priority: project > user > bundled):
|
|
144
|
+
- `~/.pi/agent/agents/*.md` — User-level (always loaded)
|
|
145
|
+
- `.pi/agents/*.md` — Project-level (requires `agentScope: "project"` or `"both"`)
|
|
146
|
+
- Bundled with package — Lowest priority, always available
|
|
147
|
+
|
|
148
|
+
## Security
|
|
149
|
+
|
|
150
|
+
- **User agents** (`~/.pi/agent/agents/`): Always trusted
|
|
151
|
+
- **Project agents** (`.pi/agents/`): Requires confirmation prompt before execution
|
|
152
|
+
- **Bundled agents**: Trusted by virtue of package installation
|
|
153
|
+
|
|
154
|
+
## Development
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
# Clone and test locally
|
|
158
|
+
git clone https://github.com/fosterg4/pi-subagent.git
|
|
159
|
+
cd pi-subagent
|
|
160
|
+
|
|
161
|
+
# Test with pi
|
|
162
|
+
pi -e ./index.ts
|
|
163
|
+
|
|
164
|
+
# Publish
|
|
165
|
+
npm publish --access public
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## License
|
|
169
|
+
|
|
170
|
+
MIT
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: planner
|
|
3
|
+
description: Creates implementation plans from context and requirements
|
|
4
|
+
tools: read, grep, find, ls
|
|
5
|
+
model: claude-sonnet-4-5
|
|
6
|
+
inputSchema:
|
|
7
|
+
type: object
|
|
8
|
+
properties:
|
|
9
|
+
findings:
|
|
10
|
+
type: object
|
|
11
|
+
description: "Scout findings or context about the codebase"
|
|
12
|
+
requirements:
|
|
13
|
+
type: string
|
|
14
|
+
description: "What needs to be built or changed"
|
|
15
|
+
required: [requirements]
|
|
16
|
+
outputSchema:
|
|
17
|
+
type: object
|
|
18
|
+
properties:
|
|
19
|
+
goal:
|
|
20
|
+
type: string
|
|
21
|
+
steps:
|
|
22
|
+
type: array
|
|
23
|
+
items:
|
|
24
|
+
type: object
|
|
25
|
+
properties:
|
|
26
|
+
step: { type: number }
|
|
27
|
+
action: { type: string }
|
|
28
|
+
file: { type: string }
|
|
29
|
+
details: { type: string }
|
|
30
|
+
filesToModify:
|
|
31
|
+
type: array
|
|
32
|
+
items:
|
|
33
|
+
type: object
|
|
34
|
+
properties:
|
|
35
|
+
path: { type: string }
|
|
36
|
+
changes: { type: string }
|
|
37
|
+
newFiles:
|
|
38
|
+
type: array
|
|
39
|
+
items:
|
|
40
|
+
type: object
|
|
41
|
+
properties:
|
|
42
|
+
path: { type: string }
|
|
43
|
+
purpose: { type: string }
|
|
44
|
+
risks:
|
|
45
|
+
type: string
|
|
46
|
+
required: [goal, steps, filesToModify]
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
You are a planning specialist. You receive context (from a scout) and requirements, then produce a clear implementation plan.
|
|
50
|
+
|
|
51
|
+
You must NOT make any changes. Only read, analyze, and plan.
|
|
52
|
+
|
|
53
|
+
Return your plan as a JSON object matching the outputSchema.
|
|
54
|
+
|
|
55
|
+
## Output format (JSON)
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"goal": "One sentence summary",
|
|
60
|
+
"steps": [
|
|
61
|
+
{ "step": 1, "action": "Modify function X", "file": "src/file.ts", "details": "What to change" }
|
|
62
|
+
],
|
|
63
|
+
"filesToModify": [
|
|
64
|
+
{ "path": "src/file.ts", "changes": "What changes" }
|
|
65
|
+
],
|
|
66
|
+
"newFiles": [
|
|
67
|
+
{ "path": "src/new.ts", "purpose": "Purpose" }
|
|
68
|
+
],
|
|
69
|
+
"risks": "Anything to watch out for"
|
|
70
|
+
}
|
|
71
|
+
```
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: reviewer
|
|
3
|
+
description: Code review specialist for quality and security analysis
|
|
4
|
+
tools: read, grep, find, ls, bash
|
|
5
|
+
model: claude-sonnet-4-5
|
|
6
|
+
inputSchema:
|
|
7
|
+
type: object
|
|
8
|
+
properties:
|
|
9
|
+
changes:
|
|
10
|
+
type: string
|
|
11
|
+
description: "Description of what was changed"
|
|
12
|
+
files:
|
|
13
|
+
type: array
|
|
14
|
+
items:
|
|
15
|
+
type: object
|
|
16
|
+
properties:
|
|
17
|
+
path: { type: string }
|
|
18
|
+
description: { type: string }
|
|
19
|
+
required: [changes, files]
|
|
20
|
+
outputSchema:
|
|
21
|
+
type: object
|
|
22
|
+
properties:
|
|
23
|
+
filesReviewed:
|
|
24
|
+
type: array
|
|
25
|
+
items:
|
|
26
|
+
type: string
|
|
27
|
+
critical:
|
|
28
|
+
type: array
|
|
29
|
+
items:
|
|
30
|
+
type: object
|
|
31
|
+
properties:
|
|
32
|
+
location: { type: string }
|
|
33
|
+
issue: { type: string }
|
|
34
|
+
warnings:
|
|
35
|
+
type: array
|
|
36
|
+
items:
|
|
37
|
+
type: object
|
|
38
|
+
properties:
|
|
39
|
+
location: { type: string }
|
|
40
|
+
issue: { type: string }
|
|
41
|
+
suggestions:
|
|
42
|
+
type: array
|
|
43
|
+
items:
|
|
44
|
+
type: object
|
|
45
|
+
properties:
|
|
46
|
+
location: { type: string }
|
|
47
|
+
idea: { type: string }
|
|
48
|
+
summary:
|
|
49
|
+
type: string
|
|
50
|
+
required: [filesReviewed, critical, warnings, summary]
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
You are a senior code reviewer. Analyze code for quality, security, and maintainability.
|
|
54
|
+
|
|
55
|
+
Bash is for read-only commands only: `git diff`, `git log`, `git show`. Do NOT modify files or run builds.
|
|
56
|
+
Assume tool permissions are not perfectly enforceable; keep all bash usage strictly read-only.
|
|
57
|
+
|
|
58
|
+
Strategy:
|
|
59
|
+
1. Run `git diff` to see recent changes (if applicable)
|
|
60
|
+
2. Read the modified files
|
|
61
|
+
3. Check for bugs, security issues, code smells
|
|
62
|
+
|
|
63
|
+
Return your review as a JSON object matching the outputSchema.
|
|
64
|
+
|
|
65
|
+
## Output format (JSON)
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"filesReviewed": ["src/file.ts"],
|
|
70
|
+
"critical": [
|
|
71
|
+
{ "location": "file.ts:42", "issue": "Issue description" }
|
|
72
|
+
],
|
|
73
|
+
"warnings": [
|
|
74
|
+
{ "location": "file.ts:100", "issue": "Issue description" }
|
|
75
|
+
],
|
|
76
|
+
"suggestions": [
|
|
77
|
+
{ "location": "file.ts:150", "idea": "Improvement idea" }
|
|
78
|
+
],
|
|
79
|
+
"summary": "Overall assessment in 2-3 sentences."
|
|
80
|
+
}
|
|
81
|
+
```
|
package/agents/scout.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: scout
|
|
3
|
+
description: Fast codebase recon that returns compressed context for handoff to other agents
|
|
4
|
+
tools: read, grep, find, ls, bash
|
|
5
|
+
model: claude-haiku-4-5
|
|
6
|
+
inputSchema:
|
|
7
|
+
type: object
|
|
8
|
+
properties:
|
|
9
|
+
query:
|
|
10
|
+
type: string
|
|
11
|
+
description: "What to investigate"
|
|
12
|
+
thoroughness:
|
|
13
|
+
type: string
|
|
14
|
+
enum: [quick, medium, thorough]
|
|
15
|
+
description: "Depth of investigation"
|
|
16
|
+
default: medium
|
|
17
|
+
required: [query]
|
|
18
|
+
outputSchema:
|
|
19
|
+
type: object
|
|
20
|
+
properties:
|
|
21
|
+
filesRetrieved:
|
|
22
|
+
type: array
|
|
23
|
+
items:
|
|
24
|
+
type: object
|
|
25
|
+
properties:
|
|
26
|
+
path: { type: string }
|
|
27
|
+
lines: { type: string }
|
|
28
|
+
description: { type: string }
|
|
29
|
+
architecture:
|
|
30
|
+
type: string
|
|
31
|
+
keyCode:
|
|
32
|
+
type: string
|
|
33
|
+
startHere:
|
|
34
|
+
type: string
|
|
35
|
+
required: [filesRetrieved, architecture]
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
You are a scout. Quickly investigate a codebase and return structured findings that another agent can use without re-reading everything.
|
|
39
|
+
|
|
40
|
+
Your output will be passed to an agent who has NOT seen the files you explored.
|
|
41
|
+
|
|
42
|
+
Thoroughness (infer from task, default medium):
|
|
43
|
+
- Quick: Targeted lookups, key files only
|
|
44
|
+
- Medium: Follow imports, read critical sections
|
|
45
|
+
- Thorough: Trace all dependencies, check tests/types
|
|
46
|
+
|
|
47
|
+
Strategy:
|
|
48
|
+
1. grep/find to locate relevant code
|
|
49
|
+
2. Read key sections (not entire files)
|
|
50
|
+
3. Identify types, interfaces, key functions
|
|
51
|
+
4. Note dependencies between files
|
|
52
|
+
|
|
53
|
+
Return your findings as a JSON object matching the outputSchema.
|
|
54
|
+
|
|
55
|
+
## Output format (JSON)
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"filesRetrieved": [
|
|
60
|
+
{ "path": "src/file.ts", "lines": "10-50", "description": "What's here" }
|
|
61
|
+
],
|
|
62
|
+
"architecture": "Brief explanation of how the pieces connect.",
|
|
63
|
+
"keyCode": "Critical code snippet",
|
|
64
|
+
"startHere": "Which file to look at first and why"
|
|
65
|
+
}
|
|
66
|
+
```
|
package/agents/worker.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: worker
|
|
3
|
+
description: General-purpose subagent with full capabilities, isolated context
|
|
4
|
+
model: claude-sonnet-4-5
|
|
5
|
+
inputSchema:
|
|
6
|
+
type: object
|
|
7
|
+
properties:
|
|
8
|
+
plan:
|
|
9
|
+
type: object
|
|
10
|
+
description: "Implementation plan with steps, files, and context"
|
|
11
|
+
context:
|
|
12
|
+
type: string
|
|
13
|
+
description: "Additional context about the codebase"
|
|
14
|
+
required: [plan]
|
|
15
|
+
outputSchema:
|
|
16
|
+
type: object
|
|
17
|
+
properties:
|
|
18
|
+
completed:
|
|
19
|
+
type: string
|
|
20
|
+
filesChanged:
|
|
21
|
+
type: array
|
|
22
|
+
items:
|
|
23
|
+
type: object
|
|
24
|
+
properties:
|
|
25
|
+
path: { type: string }
|
|
26
|
+
change: { type: string }
|
|
27
|
+
notes:
|
|
28
|
+
type: string
|
|
29
|
+
required: [completed, filesChanged]
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
You are a worker agent with full capabilities. You operate in an isolated context window to handle delegated tasks without polluting the main conversation.
|
|
33
|
+
|
|
34
|
+
Work autonomously to complete the assigned task. Use all available tools as needed.
|
|
35
|
+
|
|
36
|
+
Return your results as a JSON object matching the outputSchema.
|
|
37
|
+
|
|
38
|
+
## Output format (JSON)
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"completed": "What was done.",
|
|
43
|
+
"filesChanged": [
|
|
44
|
+
{ "path": "src/file.ts", "change": "What changed" }
|
|
45
|
+
],
|
|
46
|
+
"notes": "Anything the main agent should know."
|
|
47
|
+
}
|
|
48
|
+
```
|
package/agents.ts
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent discovery and configuration
|
|
3
|
+
*
|
|
4
|
+
* Discovers agents from three sources (lowest to highest priority):
|
|
5
|
+
* 1. Bundled agents (shipped with the @fosterg4/pi-subagent package)
|
|
6
|
+
* 2. User agents (~/.pi/agent/agents/)
|
|
7
|
+
* 3. Project agents (.pi/agents/ - requires "project" or "both" scope)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import * as fs from "node:fs";
|
|
11
|
+
import * as path from "node:path";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
13
|
+
import { getAgentDir, parseFrontmatter } from "@earendil-works/pi-coding-agent";
|
|
14
|
+
|
|
15
|
+
export type AgentScope = "user" | "project" | "both";
|
|
16
|
+
|
|
17
|
+
export interface AgentConfig {
|
|
18
|
+
name: string;
|
|
19
|
+
description: string;
|
|
20
|
+
tools?: string[];
|
|
21
|
+
model?: string;
|
|
22
|
+
systemPrompt: string;
|
|
23
|
+
source: "user" | "project" | "bundled";
|
|
24
|
+
filePath: string;
|
|
25
|
+
inputSchema?: Record<string, unknown>;
|
|
26
|
+
outputSchema?: Record<string, unknown>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface AgentDiscoveryResult {
|
|
30
|
+
agents: AgentConfig[];
|
|
31
|
+
projectAgentsDir: string | null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Resolve the directory where the package's bundled agents live.
|
|
36
|
+
* Uses import.meta.url to find the package directory at runtime.
|
|
37
|
+
*/
|
|
38
|
+
function getBundledAgentsDir(): string | null {
|
|
39
|
+
try {
|
|
40
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
41
|
+
const packageDir = path.dirname(currentFile);
|
|
42
|
+
const bundledDir = path.join(packageDir, "agents");
|
|
43
|
+
return fs.existsSync(bundledDir) ? bundledDir : null;
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function loadAgentsFromDir(
|
|
50
|
+
dir: string,
|
|
51
|
+
source: AgentConfig["source"],
|
|
52
|
+
): AgentConfig[] {
|
|
53
|
+
const agents: AgentConfig[] = [];
|
|
54
|
+
|
|
55
|
+
if (!fs.existsSync(dir)) return agents;
|
|
56
|
+
|
|
57
|
+
let entries: fs.Dirent[];
|
|
58
|
+
try {
|
|
59
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
60
|
+
} catch {
|
|
61
|
+
return agents;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
for (const entry of entries) {
|
|
65
|
+
if (!entry.name.endsWith(".md")) continue;
|
|
66
|
+
if (!entry.isFile() && !entry.isSymbolicLink()) continue;
|
|
67
|
+
|
|
68
|
+
const filePath = path.join(dir, entry.name);
|
|
69
|
+
let content: string;
|
|
70
|
+
try {
|
|
71
|
+
content = fs.readFileSync(filePath, "utf-8");
|
|
72
|
+
} catch {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const { frontmatter, body } = parseFrontmatter<Record<string, unknown>>(content);
|
|
77
|
+
|
|
78
|
+
if (!frontmatter.name || !frontmatter.description) continue;
|
|
79
|
+
|
|
80
|
+
const name = String(frontmatter.name);
|
|
81
|
+
const description = String(frontmatter.description);
|
|
82
|
+
|
|
83
|
+
const toolsStr = frontmatter.tools;
|
|
84
|
+
const tools =
|
|
85
|
+
typeof toolsStr === "string"
|
|
86
|
+
? toolsStr
|
|
87
|
+
.split(",")
|
|
88
|
+
.map((t: string) => t.trim())
|
|
89
|
+
.filter(Boolean)
|
|
90
|
+
: undefined;
|
|
91
|
+
|
|
92
|
+
const model = typeof frontmatter.model === "string" ? frontmatter.model : undefined;
|
|
93
|
+
|
|
94
|
+
// Parse contract schemas (optional)
|
|
95
|
+
const inputSchema =
|
|
96
|
+
frontmatter.inputSchema && typeof frontmatter.inputSchema === "object"
|
|
97
|
+
? (frontmatter.inputSchema as Record<string, unknown>)
|
|
98
|
+
: undefined;
|
|
99
|
+
|
|
100
|
+
const outputSchema =
|
|
101
|
+
frontmatter.outputSchema && typeof frontmatter.outputSchema === "object"
|
|
102
|
+
? (frontmatter.outputSchema as Record<string, unknown>)
|
|
103
|
+
: undefined;
|
|
104
|
+
|
|
105
|
+
agents.push({
|
|
106
|
+
name,
|
|
107
|
+
description,
|
|
108
|
+
tools: tools && tools.length > 0 ? tools : undefined,
|
|
109
|
+
model,
|
|
110
|
+
systemPrompt: body,
|
|
111
|
+
source,
|
|
112
|
+
filePath,
|
|
113
|
+
inputSchema,
|
|
114
|
+
outputSchema,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return agents;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function isDirectory(p: string): boolean {
|
|
122
|
+
try {
|
|
123
|
+
return fs.statSync(p).isDirectory();
|
|
124
|
+
} catch {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function findNearestProjectAgentsDir(cwd: string): string | null {
|
|
130
|
+
let currentDir = cwd;
|
|
131
|
+
while (true) {
|
|
132
|
+
const candidate = path.join(currentDir, ".pi", "agents");
|
|
133
|
+
if (isDirectory(candidate)) return candidate;
|
|
134
|
+
|
|
135
|
+
const parentDir = path.dirname(currentDir);
|
|
136
|
+
if (parentDir === currentDir) return null;
|
|
137
|
+
currentDir = parentDir;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function discoverAgents(
|
|
142
|
+
cwd: string,
|
|
143
|
+
scope: AgentScope,
|
|
144
|
+
): AgentDiscoveryResult {
|
|
145
|
+
const userDir = path.join(getAgentDir(), "agents");
|
|
146
|
+
const projectAgentsDir = findNearestProjectAgentsDir(cwd);
|
|
147
|
+
const bundledDir = getBundledAgentsDir();
|
|
148
|
+
|
|
149
|
+
// Load agents from each source
|
|
150
|
+
const bundledAgents = bundledDir ? loadAgentsFromDir(bundledDir, "bundled") : [];
|
|
151
|
+
const userAgents = scope === "project" ? [] : loadAgentsFromDir(userDir, "user");
|
|
152
|
+
const projectAgents =
|
|
153
|
+
scope === "user" || !projectAgentsDir
|
|
154
|
+
? []
|
|
155
|
+
: loadAgentsFromDir(projectAgentsDir, "project");
|
|
156
|
+
|
|
157
|
+
// Merge with override priority: project > user > bundled
|
|
158
|
+
const agentMap = new Map<string, AgentConfig>();
|
|
159
|
+
|
|
160
|
+
// Lowest priority: bundled
|
|
161
|
+
for (const agent of bundledAgents) {
|
|
162
|
+
agentMap.set(agent.name, agent);
|
|
163
|
+
}
|
|
164
|
+
// Middle priority: user (overrides bundled)
|
|
165
|
+
if (scope !== "project") {
|
|
166
|
+
for (const agent of userAgents) {
|
|
167
|
+
agentMap.set(agent.name, agent);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// Highest priority: project (overrides user and bundled)
|
|
171
|
+
if (scope !== "user" && projectAgentsDir) {
|
|
172
|
+
for (const agent of projectAgents) {
|
|
173
|
+
agentMap.set(agent.name, agent);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
agents: Array.from(agentMap.values()),
|
|
179
|
+
projectAgentsDir,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function formatAgentList(
|
|
184
|
+
agents: AgentConfig[],
|
|
185
|
+
maxItems: number,
|
|
186
|
+
): { text: string; remaining: number } {
|
|
187
|
+
if (agents.length === 0) return { text: "none", remaining: 0 };
|
|
188
|
+
const listed = agents.slice(0, maxItems);
|
|
189
|
+
const remaining = agents.length - listed.length;
|
|
190
|
+
return {
|
|
191
|
+
text: listed
|
|
192
|
+
.map((a) => `${a.name} (${a.source}): ${a.description}`)
|
|
193
|
+
.join("; "),
|
|
194
|
+
remaining,
|
|
195
|
+
};
|
|
196
|
+
}
|