@dreki-gg/pi-subagent 0.6.0 → 0.8.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/.fallowrc.json +6 -0
- package/CHANGELOG.md +37 -0
- package/README.md +82 -70
- package/docs/orchestration-principles.md +132 -0
- package/docs/plans/01_structured-handoffs-and-synthesis.plan.md +376 -0
- package/docs/plans/02_clean-context-review-loop.plan.md +240 -0
- package/docs/plans/03_parallel-recon-single-writer-docs.plan.md +199 -0
- package/docs/plans/04_manager-workflow.plan.md +248 -0
- package/docs/plans/05_advisor-routing.plan.md +244 -0
- package/extensions/subagent/agent-result-utils.ts +12 -0
- package/extensions/subagent/agent-runner-types.ts +23 -0
- package/extensions/subagent/agent-runner.ts +90 -0
- package/extensions/subagent/agents.ts +31 -77
- package/extensions/subagent/handoffs.ts +273 -0
- package/extensions/subagent/index.ts +359 -581
- package/extensions/subagent/run-agent-args.ts +90 -0
- package/extensions/subagent/{delegate-executor.ts → spawn-utils.ts} +64 -87
- package/extensions/subagent/synthesis.ts +1 -51
- package/package.json +7 -10
- package/prompts/advisor.md +37 -0
- package/prompts/bug-prover.md +42 -0
- package/{agents → prompts}/docs-scout.md +2 -2
- package/prompts/manager.md +52 -0
- package/{agents → prompts}/planner.md +16 -1
- package/prompts/reviewer.md +93 -0
- package/{agents → prompts}/scout.md +5 -1
- package/prompts/validator.md +42 -0
- package/prompts/worker.md +61 -0
- package/skills/spawn-subagents/SKILL.md +80 -8
- package/skills/write-an-agent/SKILL.md +5 -5
- package/agents/reviewer.md +0 -37
- package/agents/worker.md +0 -26
- package/extensions/subagent/delegate-args.ts +0 -145
- package/extensions/subagent/delegate-types.ts +0 -62
- package/extensions/subagent/delegate-ui.ts +0 -132
- package/extensions/subagent/workflows.ts +0 -150
- package/prompts/implement-and-review.md +0 -10
- package/prompts/implement.md +0 -10
- package/prompts/scout-and-plan.md +0 -9
- /package/{agents → prompts}/ux-designer.md +0 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: "Advisor / smart-friend routing"
|
|
3
|
+
overview: "Add a bundled `advisor` role and lightweight escalation guidance so strong primary agents in `@dreki-gg/pi-subagent` can ask for focused second opinions on tricky cases without turning every task into a swarm. This slice is about capability routing, not replacing the main worker."
|
|
4
|
+
todo:
|
|
5
|
+
- id: "advisor-routing-1"
|
|
6
|
+
task: "Add a bundled `advisor` agent with a focused consultative contract: diagnose, suggest next investigation, and surface risks without taking ownership of implementation"
|
|
7
|
+
status: done
|
|
8
|
+
- id: "advisor-routing-2"
|
|
9
|
+
task: "Teach bundled worker/planner/reviewer prompts when to call the advisor and what context to send"
|
|
10
|
+
status: done
|
|
11
|
+
- id: "advisor-routing-3"
|
|
12
|
+
task: "Add a reusable prompt entry point and docs examples for advisor-assisted workflows"
|
|
13
|
+
status: done
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Goal
|
|
17
|
+
|
|
18
|
+
Introduce a lightweight advisor pattern so primary agents can consult a strong second opinion for hard cases while keeping a single primary owner of the work.
|
|
19
|
+
|
|
20
|
+
# Context
|
|
21
|
+
|
|
22
|
+
- Parent rationale: we want capability routing and targeted escalation, not a default “everyone talks to everyone” swarm.
|
|
23
|
+
- Module root: `packages/subagent`
|
|
24
|
+
- This slice should work with the current package architecture and should not depend on a new runtime mode.
|
|
25
|
+
- Keep the advisor narrowly scoped: diagnose, redirect, highlight risk, or suggest the next question. The advisor should not silently become another worker.
|
|
26
|
+
|
|
27
|
+
## What exists
|
|
28
|
+
|
|
29
|
+
Current relevant file tree on disk:
|
|
30
|
+
|
|
31
|
+
- `packages/subagent/prompts/planner.md`
|
|
32
|
+
- `packages/subagent/prompts/reviewer.md`
|
|
33
|
+
- `packages/subagent/prompts/worker.md`
|
|
34
|
+
- `packages/subagent/prompts/docs-scout.md`
|
|
35
|
+
- `packages/subagent/prompts/implement.md`
|
|
36
|
+
- `packages/subagent/prompts/implement-and-review.md`
|
|
37
|
+
- `packages/subagent/README.md`
|
|
38
|
+
- `packages/subagent/extensions/subagent/index.ts`
|
|
39
|
+
- `packages/subagent/extensions/subagent/agent-runner.ts`
|
|
40
|
+
- `packages/subagent/skills/spawn-subagents/SKILL.md`
|
|
41
|
+
- `packages/subagent/skills/write-an-agent/SKILL.md`
|
|
42
|
+
|
|
43
|
+
Actual current state on disk:
|
|
44
|
+
|
|
45
|
+
- There is no bundled `advisor` agent in `packages/subagent/prompts/`.
|
|
46
|
+
- There is no advisor-oriented prompt template in `packages/subagent/prompts/`.
|
|
47
|
+
- The current bundled roles are:
|
|
48
|
+
- `planner` for planning (`packages/subagent/prompts/planner.md`)
|
|
49
|
+
- `worker` for implementation (`packages/subagent/prompts/worker.md`)
|
|
50
|
+
- `reviewer` for verification (`packages/subagent/prompts/reviewer.md`)
|
|
51
|
+
- plus `scout`, `docs-scout`, and `ux-designer`
|
|
52
|
+
- The package runner already supports spawned agents consulting tools as needed:
|
|
53
|
+
- in `packages/subagent/extensions/subagent/index.ts:325-328`, `runSingleAgent()` passes `--tools` only when frontmatter declares them
|
|
54
|
+
- in `packages/subagent/extensions/subagent/agent-runner.ts:73-76`, `runAgent()` behaves the same way
|
|
55
|
+
- therefore bundled agents with omitted `tools:` can keep access to the broader tool surface, including `subagent`
|
|
56
|
+
- The current prompt contracts do **not** teach any role when to escalate:
|
|
57
|
+
- `planner.md` only says to produce a concrete plan
|
|
58
|
+
- `worker.md` only says to complete the task autonomously
|
|
59
|
+
- `reviewer.md` only says to review the diff / files
|
|
60
|
+
- The `spawn-subagents` skill recommends specialists and review loops, but does not mention an advisor or “consult when tricky” routing rule.
|
|
61
|
+
|
|
62
|
+
# API inventory
|
|
63
|
+
|
|
64
|
+
## Existing agent prompt contracts that may invoke advisor
|
|
65
|
+
|
|
66
|
+
From `packages/subagent/prompts/planner.md`:
|
|
67
|
+
|
|
68
|
+
```md
|
|
69
|
+
You are a planning specialist.
|
|
70
|
+
You must NOT make any changes. Only read, analyze, and plan.
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
From `packages/subagent/prompts/worker.md`:
|
|
74
|
+
|
|
75
|
+
```md
|
|
76
|
+
You are a worker agent with full capabilities.
|
|
77
|
+
Work autonomously to complete the assigned task. Use all available tools as needed.
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
From `packages/subagent/prompts/reviewer.md`:
|
|
81
|
+
|
|
82
|
+
```md
|
|
83
|
+
You are a senior code reviewer.
|
|
84
|
+
Analyze code for quality, security, and maintainability.
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Existing subagent entry shapes available to an advisor pattern
|
|
88
|
+
|
|
89
|
+
From the `subagent` tool surface:
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
// single consult
|
|
93
|
+
{ agent: 'advisor', task: '...' }
|
|
94
|
+
|
|
95
|
+
// consult inside a chain
|
|
96
|
+
{ chain: [{ agent: 'worker', task: '...' }, { agent: 'advisor', task: '... {previous} ...' }] }
|
|
97
|
+
|
|
98
|
+
// focused parallel consults
|
|
99
|
+
{ tasks: [{ agent: 'advisor', task: '...' }, { agent: 'docs-scout', task: '...' }] }
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Proposed advisor contract
|
|
103
|
+
|
|
104
|
+
The advisor should return guidance the caller can act on immediately:
|
|
105
|
+
|
|
106
|
+
```md
|
|
107
|
+
## Assessment
|
|
108
|
+
- what looks tricky / risky / ambiguous
|
|
109
|
+
|
|
110
|
+
## What to inspect next
|
|
111
|
+
- concrete files, commands, or questions
|
|
112
|
+
|
|
113
|
+
## Recommendation
|
|
114
|
+
- best next action for the caller
|
|
115
|
+
|
|
116
|
+
## Risks
|
|
117
|
+
- what could still go wrong
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The advisor should not claim to have implemented or verified anything it did not actually inspect.
|
|
121
|
+
|
|
122
|
+
# Tasks
|
|
123
|
+
|
|
124
|
+
## 1. Add a bundled `advisor` agent with a focused consultative contract
|
|
125
|
+
|
|
126
|
+
### Files
|
|
127
|
+
- Create `packages/subagent/prompts/advisor.md`
|
|
128
|
+
|
|
129
|
+
### What to add
|
|
130
|
+
Create a concise bundled agent whose sole job is to provide focused second-opinion guidance.
|
|
131
|
+
|
|
132
|
+
### Required behavior
|
|
133
|
+
- Work from the caller’s question plus any provided handoff.
|
|
134
|
+
- Ask the caller to investigate specific files / commands when the answer depends on context the advisor has not yet inspected.
|
|
135
|
+
- Surface hidden risks or better approaches.
|
|
136
|
+
- Avoid taking over implementation.
|
|
137
|
+
- Prefer decisive recommendations over brainstorming lists.
|
|
138
|
+
|
|
139
|
+
### Frontmatter guidance
|
|
140
|
+
- Use an existing strong model already in package conventions (default to `openai/gpt-5.4` unless there is a strong reason to diversify).
|
|
141
|
+
- Keep the prompt under ~100 lines per `packages/subagent/skills/write-an-agent/SKILL.md`.
|
|
142
|
+
- Omit `tools:` only if you want the advisor to have broad access; otherwise keep the tool list minimal and read-oriented.
|
|
143
|
+
|
|
144
|
+
### Suggested prompt rules
|
|
145
|
+
1. Diagnose the hard part, not the whole task from scratch.
|
|
146
|
+
2. If more repo context is needed, say exactly what to inspect next.
|
|
147
|
+
3. Return a recommendation the caller can act on immediately.
|
|
148
|
+
4. Do not implement code or rewrite plans unless explicitly asked.
|
|
149
|
+
|
|
150
|
+
## 2. Teach bundled prompts when to call the advisor and what context to send
|
|
151
|
+
|
|
152
|
+
### Files
|
|
153
|
+
- Modify `packages/subagent/prompts/worker.md`
|
|
154
|
+
- Modify `packages/subagent/prompts/planner.md`
|
|
155
|
+
- Modify `packages/subagent/prompts/reviewer.md`
|
|
156
|
+
|
|
157
|
+
### What to change
|
|
158
|
+
Add small, explicit escalation rules to the existing bundled roles.
|
|
159
|
+
|
|
160
|
+
### Recommended escalation triggers
|
|
161
|
+
- `worker`
|
|
162
|
+
- ambiguous architecture tradeoff
|
|
163
|
+
- persistent failing tests or unexplained errors
|
|
164
|
+
- merge conflicts / tangled diffs
|
|
165
|
+
- security-sensitive or migration-heavy changes
|
|
166
|
+
- `planner`
|
|
167
|
+
- multiple viable designs with materially different blast radius
|
|
168
|
+
- unclear ownership boundaries across packages
|
|
169
|
+
- need for sharper decomposition before implementation
|
|
170
|
+
- `reviewer`
|
|
171
|
+
- uncertainty whether an issue is real vs. intended
|
|
172
|
+
- suspected deeper architectural problem beyond the current diff
|
|
173
|
+
- need for a second opinion on severity
|
|
174
|
+
|
|
175
|
+
### Required context to send when consulting advisor
|
|
176
|
+
Keep the consult lightweight:
|
|
177
|
+
- current role (`worker` / `planner` / `reviewer`)
|
|
178
|
+
- the exact question
|
|
179
|
+
- touched files / symbols if known
|
|
180
|
+
- the smallest relevant task summary
|
|
181
|
+
- what has already been tried or observed
|
|
182
|
+
|
|
183
|
+
### Notes
|
|
184
|
+
- Do not tell every role to always call the advisor. This is an escalation path, not a mandatory step.
|
|
185
|
+
- Keep the primary role in charge; the advisor should inform decisions, not replace ownership.
|
|
186
|
+
|
|
187
|
+
## 3. Add a reusable prompt entry point and docs examples for advisor-assisted workflows
|
|
188
|
+
|
|
189
|
+
### Files
|
|
190
|
+
- Create `packages/subagent/prompts/consult-advisor.md`
|
|
191
|
+
- Modify `packages/subagent/README.md`
|
|
192
|
+
- Modify `packages/subagent/skills/spawn-subagents/SKILL.md`
|
|
193
|
+
|
|
194
|
+
### What to add
|
|
195
|
+
Create a simple prompt template that encourages focused advisor consultation without changing core workflow semantics.
|
|
196
|
+
|
|
197
|
+
### Suggested prompt shape
|
|
198
|
+
|
|
199
|
+
```md
|
|
200
|
+
Use the subagent tool to consult the `advisor` agent about: $@
|
|
201
|
+
|
|
202
|
+
The advisor should:
|
|
203
|
+
1. identify the hard part
|
|
204
|
+
2. suggest the next investigation or decision
|
|
205
|
+
3. surface concrete risks
|
|
206
|
+
4. return a recommendation, not a full implementation
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### README / skill updates
|
|
210
|
+
- Add `advisor` to the bundled agent list once shipped.
|
|
211
|
+
- Add one example such as:
|
|
212
|
+
- “ask advisor whether this migration should be split before sending it to worker”
|
|
213
|
+
- “have worker consult advisor on the failing test loop”
|
|
214
|
+
- In `spawn-subagents`, add guidance that advisor use is optional and should be reserved for tricky or high-risk cases.
|
|
215
|
+
|
|
216
|
+
# Files to create
|
|
217
|
+
|
|
218
|
+
- `packages/subagent/prompts/advisor.md`
|
|
219
|
+
- `packages/subagent/prompts/consult-advisor.md`
|
|
220
|
+
|
|
221
|
+
# Files to modify
|
|
222
|
+
|
|
223
|
+
- `packages/subagent/prompts/worker.md` — add narrow escalation guidance
|
|
224
|
+
- `packages/subagent/prompts/planner.md` — add narrow escalation guidance
|
|
225
|
+
- `packages/subagent/prompts/reviewer.md` — add narrow escalation guidance
|
|
226
|
+
- `packages/subagent/README.md` — document the new advisor role and one or two examples
|
|
227
|
+
- `packages/subagent/skills/spawn-subagents/SKILL.md` — explain when to use advisor vs. planner/reviewer
|
|
228
|
+
|
|
229
|
+
# Testing notes
|
|
230
|
+
|
|
231
|
+
- If this remains markdown-only, there is no TypeScript validation requirement.
|
|
232
|
+
- Manually test one focused consult, such as a planner or worker asking advisor a hard question, and verify:
|
|
233
|
+
- the advisor returns a recommendation rather than trying to own the full task
|
|
234
|
+
- the caller stays in charge of implementation or review
|
|
235
|
+
- the exchange is shorter and more targeted than a full additional workflow
|
|
236
|
+
- If you introduce any helper TS module later to render consult packets, run `bun run --filter '@dreki-gg/pi-subagent' typecheck`.
|
|
237
|
+
|
|
238
|
+
# Patterns to follow
|
|
239
|
+
|
|
240
|
+
- `packages/subagent/skills/write-an-agent/SKILL.md:15-20` and `:46-67` — keep the new agent narrow, concise, and output-contract-driven
|
|
241
|
+
- `packages/subagent/prompts/planner.md:9-38` — example of a specialist planner role that the advisor should complement, not replace
|
|
242
|
+
- `packages/subagent/prompts/reviewer.md:10-37` — example of a verifier role that may occasionally need escalation
|
|
243
|
+
- `packages/subagent/prompts/worker.md:9-26` — example of a primary owner role that should remain in charge after consulting advisor
|
|
244
|
+
- `packages/subagent/skills/spawn-subagents/SKILL.md:23-58` — existing specialist-selection guidance to extend with optional escalation
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AgentResult } from './agent-runner-types.js';
|
|
2
|
+
|
|
3
|
+
export function getFinalText(result: AgentResult): string {
|
|
4
|
+
for (let i = result.messages.length - 1; i >= 0; i--) {
|
|
5
|
+
const msg = result.messages[i];
|
|
6
|
+
if (msg.role !== 'assistant') continue;
|
|
7
|
+
for (const part of msg.content) {
|
|
8
|
+
if (part.type === 'text') return part.text;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
return '(no output)';
|
|
12
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Message } from '@earendil-works/pi-ai';
|
|
2
|
+
|
|
3
|
+
export interface UsageStats {
|
|
4
|
+
input: number;
|
|
5
|
+
output: number;
|
|
6
|
+
cacheRead: number;
|
|
7
|
+
cacheWrite: number;
|
|
8
|
+
cost: number;
|
|
9
|
+
contextTokens: number;
|
|
10
|
+
turns: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface AgentResult {
|
|
14
|
+
agent: string;
|
|
15
|
+
task: string;
|
|
16
|
+
exitCode: number;
|
|
17
|
+
messages: Message[];
|
|
18
|
+
stderr: string;
|
|
19
|
+
usage: UsageStats;
|
|
20
|
+
model?: string;
|
|
21
|
+
stopReason?: string;
|
|
22
|
+
errorMessage?: string;
|
|
23
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { ResolvedPaths } from '@earendil-works/pi-coding-agent';
|
|
2
|
+
import { discoverAgents, type AgentScope } from './agents.js';
|
|
3
|
+
import type { AgentResult } from './agent-runner-types.js';
|
|
4
|
+
import { emptyUsage, spawnPiAgent } from './spawn-utils.js';
|
|
5
|
+
|
|
6
|
+
export type OnPhaseUpdate = (phaseName: string, agentName: string, result: AgentResult) => void;
|
|
7
|
+
|
|
8
|
+
export interface RunAgentOptions {
|
|
9
|
+
agentScope?: AgentScope;
|
|
10
|
+
cwd?: string;
|
|
11
|
+
model?: string;
|
|
12
|
+
thinking?: string;
|
|
13
|
+
onUpdate?: OnPhaseUpdate;
|
|
14
|
+
phaseName?: string;
|
|
15
|
+
signal?: AbortSignal;
|
|
16
|
+
resolvedPaths?: ResolvedPaths;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function runAgent(
|
|
20
|
+
cwd: string,
|
|
21
|
+
agentName: string,
|
|
22
|
+
task: string,
|
|
23
|
+
options: RunAgentOptions = {},
|
|
24
|
+
): Promise<AgentResult> {
|
|
25
|
+
const { agents } = discoverAgents(cwd, options.agentScope ?? 'user', options.resolvedPaths);
|
|
26
|
+
const agent = agents.find((a) => a.name === agentName);
|
|
27
|
+
|
|
28
|
+
if (!agent) {
|
|
29
|
+
const available = agents.map((a) => a.name).join(', ') || 'none';
|
|
30
|
+
return {
|
|
31
|
+
agent: agentName,
|
|
32
|
+
task,
|
|
33
|
+
exitCode: 1,
|
|
34
|
+
messages: [],
|
|
35
|
+
stderr: `Unknown agent: "${agentName}". Available: ${available}.`,
|
|
36
|
+
usage: emptyUsage(),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const selectedModel = options.model ?? agent.model;
|
|
41
|
+
const selectedThinking = options.thinking ?? agent.thinking;
|
|
42
|
+
|
|
43
|
+
const result: AgentResult = {
|
|
44
|
+
agent: agentName,
|
|
45
|
+
task,
|
|
46
|
+
exitCode: 0,
|
|
47
|
+
messages: [],
|
|
48
|
+
stderr: '',
|
|
49
|
+
usage: emptyUsage(),
|
|
50
|
+
model: selectedModel,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const spawnResult = await spawnPiAgent({
|
|
54
|
+
cwd: options.cwd ?? cwd,
|
|
55
|
+
agentName: agent.name,
|
|
56
|
+
task,
|
|
57
|
+
systemPrompt: agent.systemPrompt,
|
|
58
|
+
model: selectedModel,
|
|
59
|
+
thinking: selectedThinking,
|
|
60
|
+
tools: agent.tools,
|
|
61
|
+
signal: options.signal,
|
|
62
|
+
onMessage: () => {
|
|
63
|
+
// Copy accumulated state for the phase update callback
|
|
64
|
+
if (options.onUpdate) {
|
|
65
|
+
result.messages = spawnResult.messages;
|
|
66
|
+
result.usage = spawnResult.usage;
|
|
67
|
+
result.model = spawnResult.model ?? result.model;
|
|
68
|
+
result.stopReason = spawnResult.stopReason;
|
|
69
|
+
result.errorMessage = spawnResult.errorMessage;
|
|
70
|
+
options.onUpdate(options.phaseName ?? 'unknown', agentName, { ...result });
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
onToolResult: () => {
|
|
74
|
+
if (options.onUpdate) {
|
|
75
|
+
result.messages = spawnResult.messages;
|
|
76
|
+
options.onUpdate(options.phaseName ?? 'unknown', agentName, { ...result });
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
result.exitCode = spawnResult.exitCode;
|
|
82
|
+
result.messages = spawnResult.messages;
|
|
83
|
+
result.stderr = spawnResult.stderr;
|
|
84
|
+
result.usage = spawnResult.usage;
|
|
85
|
+
result.model = spawnResult.model ?? result.model;
|
|
86
|
+
result.stopReason = spawnResult.stopReason;
|
|
87
|
+
result.errorMessage = spawnResult.errorMessage;
|
|
88
|
+
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Agent discovery and configuration
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* Package agents are resolved, filtered, and toggleable via pi config.
|
|
8
|
-
* 2. Legacy: Manual filesystem discovery from bundled, user, and project dirs.
|
|
9
|
-
* Used as fallback when ResolvedPaths.agents is not available.
|
|
4
|
+
* Uses pi's ResolvedPaths.prompts from the package manager to discover
|
|
5
|
+
* agent prompts from installed packages. User and project prompts are
|
|
6
|
+
* discovered from the filesystem and override package prompts by name.
|
|
10
7
|
*/
|
|
11
8
|
|
|
12
9
|
import * as fs from 'node:fs';
|
|
13
10
|
import * as path from 'node:path';
|
|
14
|
-
import { getAgentDir, parseFrontmatter } from '@
|
|
15
|
-
import type { ResolvedPaths } from '@
|
|
11
|
+
import { getAgentDir, parseFrontmatter } from '@earendil-works/pi-coding-agent';
|
|
12
|
+
import type { ResolvedPaths } from '@earendil-works/pi-coding-agent';
|
|
16
13
|
|
|
17
14
|
export type AgentScope = 'user' | 'project' | 'both';
|
|
18
|
-
export type AgentSource = '
|
|
15
|
+
export type AgentSource = 'user' | 'project' | 'package';
|
|
19
16
|
export type AgentSessionStrategy = 'inline' | 'fork-at';
|
|
20
17
|
|
|
21
18
|
export interface AgentConfig {
|
|
@@ -32,12 +29,9 @@ export interface AgentConfig {
|
|
|
32
29
|
|
|
33
30
|
export interface AgentDiscoveryResult {
|
|
34
31
|
agents: AgentConfig[];
|
|
35
|
-
|
|
32
|
+
projectPromptsDir: string | null;
|
|
36
33
|
}
|
|
37
34
|
|
|
38
|
-
/** Bundled agents ship with the package (../../agents relative to extensions/subagent/) */
|
|
39
|
-
const bundledAgentsDir = path.resolve(import.meta.dirname, '..', '..', 'agents');
|
|
40
|
-
|
|
41
35
|
function parseSessionStrategy(value?: string): AgentSessionStrategy | undefined {
|
|
42
36
|
if (!value) return undefined;
|
|
43
37
|
const normalized = value.trim().toLowerCase();
|
|
@@ -106,10 +100,10 @@ function isDirectory(p: string): boolean {
|
|
|
106
100
|
}
|
|
107
101
|
}
|
|
108
102
|
|
|
109
|
-
function
|
|
103
|
+
function findNearestProjectPromptsDir(cwd: string): string | null {
|
|
110
104
|
let currentDir = cwd;
|
|
111
105
|
while (true) {
|
|
112
|
-
const candidate = path.join(currentDir, '.pi', '
|
|
106
|
+
const candidate = path.join(currentDir, '.pi', 'prompts');
|
|
113
107
|
if (isDirectory(candidate)) return candidate;
|
|
114
108
|
|
|
115
109
|
const parentDir = path.dirname(currentDir);
|
|
@@ -119,18 +113,14 @@ function findNearestProjectAgentsDir(cwd: string): string | null {
|
|
|
119
113
|
}
|
|
120
114
|
|
|
121
115
|
/**
|
|
122
|
-
* Load agents from package-manager resolved paths (enabled only).
|
|
123
|
-
* This is called with pre-resolved paths from an async context.
|
|
116
|
+
* Load agents from package-manager resolved prompt paths (enabled only).
|
|
124
117
|
*/
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if (!agentResources || !Array.isArray(agentResources)) {
|
|
129
|
-
return [];
|
|
130
|
-
}
|
|
118
|
+
function loadAgentsFromResolvedPaths(resolvedPaths: ResolvedPaths): AgentConfig[] {
|
|
119
|
+
const promptResources = (resolvedPaths as unknown as Record<string, unknown>).prompts;
|
|
120
|
+
if (!Array.isArray(promptResources)) return [];
|
|
131
121
|
|
|
132
122
|
const agents: AgentConfig[] = [];
|
|
133
|
-
for (const resource of
|
|
123
|
+
for (const resource of promptResources) {
|
|
134
124
|
if (!resource.enabled) continue;
|
|
135
125
|
const agent = loadAgentFromFile(resource.path, 'package');
|
|
136
126
|
if (agent) agents.push(agent);
|
|
@@ -169,21 +159,28 @@ function loadAgentFromFile(filePath: string, source: AgentSource): AgentConfig |
|
|
|
169
159
|
};
|
|
170
160
|
}
|
|
171
161
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
162
|
+
/**
|
|
163
|
+
* Discover agents from package-resolved prompts, user, and project directories.
|
|
164
|
+
*
|
|
165
|
+
* Priority: package (lowest) → user → project (highest, overrides by name).
|
|
166
|
+
*/
|
|
167
|
+
export function discoverAgents(
|
|
168
|
+
cwd: string,
|
|
169
|
+
scope: AgentScope,
|
|
170
|
+
resolvedPaths?: ResolvedPaths,
|
|
171
|
+
): AgentDiscoveryResult {
|
|
172
|
+
const userDir = path.join(getAgentDir(), 'prompts');
|
|
173
|
+
const projectPromptsDir = findNearestProjectPromptsDir(cwd);
|
|
178
174
|
|
|
175
|
+
const packageAgents = resolvedPaths ? loadAgentsFromResolvedPaths(resolvedPaths) : [];
|
|
179
176
|
const userAgents = scope === 'project' ? [] : loadAgentsFromDir(userDir, 'user');
|
|
180
177
|
const projectAgents =
|
|
181
|
-
scope === 'user' || !
|
|
178
|
+
scope === 'user' || !projectPromptsDir ? [] : loadAgentsFromDir(projectPromptsDir, 'project');
|
|
182
179
|
|
|
183
|
-
// Priority: bundled (lowest) → user → project (highest, overrides by name)
|
|
184
180
|
const agentMap = new Map<string, AgentConfig>();
|
|
185
181
|
|
|
186
|
-
|
|
182
|
+
// Package agents are lowest priority
|
|
183
|
+
for (const agent of packageAgents) agentMap.set(agent.name, agent);
|
|
187
184
|
|
|
188
185
|
if (scope === 'both') {
|
|
189
186
|
for (const agent of userAgents) agentMap.set(agent.name, agent);
|
|
@@ -194,48 +191,5 @@ export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryRe
|
|
|
194
191
|
for (const agent of projectAgents) agentMap.set(agent.name, agent);
|
|
195
192
|
}
|
|
196
193
|
|
|
197
|
-
return { agents: Array.from(agentMap.values()),
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Discover agents with package-resolved paths merged in.
|
|
202
|
-
* Package-resolved agents (from pi.agents in installed packages) are loaded
|
|
203
|
-
* at lowest priority, then overridden by bundled, user, and project agents.
|
|
204
|
-
*
|
|
205
|
-
* Falls back to discoverAgents() when resolvedPaths is not provided or
|
|
206
|
-
* does not contain the agents field (upstream pi compatibility).
|
|
207
|
-
*/
|
|
208
|
-
export function discoverAgentsWithPackages(
|
|
209
|
-
cwd: string,
|
|
210
|
-
scope: AgentScope,
|
|
211
|
-
resolvedPaths?: ResolvedPaths,
|
|
212
|
-
): AgentDiscoveryResult {
|
|
213
|
-
const base = discoverAgents(cwd, scope);
|
|
214
|
-
|
|
215
|
-
if (!resolvedPaths) return base;
|
|
216
|
-
|
|
217
|
-
const packageAgents = loadAgentsFromResolvedPaths(resolvedPaths);
|
|
218
|
-
if (packageAgents.length === 0) return base;
|
|
219
|
-
|
|
220
|
-
// Package agents are lowest priority: existing agents override by name
|
|
221
|
-
const agentMap = new Map<string, AgentConfig>();
|
|
222
|
-
for (const agent of packageAgents) agentMap.set(agent.name, agent);
|
|
223
|
-
for (const agent of base.agents) agentMap.set(agent.name, agent);
|
|
224
|
-
|
|
225
|
-
return { agents: Array.from(agentMap.values()), projectAgentsDir: base.projectAgentsDir };
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
export { bundledAgentsDir };
|
|
229
|
-
|
|
230
|
-
export function formatAgentList(
|
|
231
|
-
agents: AgentConfig[],
|
|
232
|
-
maxItems: number,
|
|
233
|
-
): { text: string; remaining: number } {
|
|
234
|
-
if (agents.length === 0) return { text: 'none', remaining: 0 };
|
|
235
|
-
const listed = agents.slice(0, maxItems);
|
|
236
|
-
const remaining = agents.length - listed.length;
|
|
237
|
-
return {
|
|
238
|
-
text: listed.map((a) => `${a.name} (${a.source}): ${a.description}`).join('; '),
|
|
239
|
-
remaining,
|
|
240
|
-
};
|
|
194
|
+
return { agents: Array.from(agentMap.values()), projectPromptsDir };
|
|
241
195
|
}
|