@dreki-gg/pi-subagent 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/README.md +32 -3
- package/agents/reviewer.md +1 -0
- package/agents/worker.md +1 -0
- package/extensions/subagent/agents.ts +98 -1
- package/extensions/subagent/delegate-args.ts +61 -0
- package/extensions/subagent/delegate-executor.ts +4 -2
- package/extensions/subagent/index.ts +296 -17
- package/package.json +4 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @dreki-gg/pi-subagent
|
|
2
2
|
|
|
3
|
+
## 0.6.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [`5e853af`](https://github.com/dreki-gg/pi-extensions/commit/5e853af054a31c4bf87d80f944513e537a39201d) Thanks [@jalbarrang](https://github.com/jalbarrang)! - Sync the extensions repo with Pi 0.68.0 and improve direct agent runs.
|
|
8
|
+
|
|
9
|
+
- `@dreki-gg/pi-context7`: remove stale alias docs and align compatibility tests with the canonical tool names actually exported.
|
|
10
|
+
- `@dreki-gg/pi-modes`: use `before_agent_start.systemPromptOptions.selectedTools` when available so mode prompt text reflects the active prompt tool set.
|
|
11
|
+
- `@dreki-gg/pi-subagent`: add `/run-agent`, support `sessionStrategy: fork-at` in agent frontmatter, default bundled `worker` and `reviewer` to forked direct runs, and add a custom renderer for run summaries.
|
|
12
|
+
|
|
13
|
+
## 0.5.0
|
|
14
|
+
|
|
15
|
+
### Minor Changes
|
|
16
|
+
|
|
17
|
+
- [`4f2f148`](https://github.com/dreki-gg/pi-extensions/commit/4f2f148488e32ff43f97216a5c221fd9d1716a11) Thanks [@jalbarrang](https://github.com/jalbarrang)! - added agents to the registry so my fork is able to discover subagent files
|
|
18
|
+
|
|
3
19
|
## 0.4.0
|
|
4
20
|
|
|
5
21
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -28,6 +28,12 @@ Examples:
|
|
|
28
28
|
- "have planner make a plan, then worker implement it"
|
|
29
29
|
- "send this to reviewer"
|
|
30
30
|
|
|
31
|
+
For direct user-invoked single-agent runs, use:
|
|
32
|
+
|
|
33
|
+
```text
|
|
34
|
+
/run-agent worker implement the auth flow we discussed
|
|
35
|
+
```
|
|
36
|
+
|
|
31
37
|
`/delegate` is optional. It exists for rigid gated workflows, plan approval, and explicit workflow control.
|
|
32
38
|
|
|
33
39
|
## Optional Delegate Command
|
|
@@ -65,6 +71,24 @@ Pin a workflow and skip the project-agent confirmation prompt:
|
|
|
65
71
|
5. **Plan gate** — shows planner output for approval before worker runs
|
|
66
72
|
6. **Summary** — full phase-by-phase report with usage totals
|
|
67
73
|
|
|
74
|
+
## Direct Agent Runs
|
|
75
|
+
|
|
76
|
+
Run one agent directly from the current session:
|
|
77
|
+
|
|
78
|
+
```text
|
|
79
|
+
/run-agent [--scope user|project|both] [--yes-project-agents] <agent> [task]
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Examples:
|
|
83
|
+
|
|
84
|
+
```text
|
|
85
|
+
/run-agent scout trace how auth state is loaded
|
|
86
|
+
/run-agent worker implement the refactor we just planned
|
|
87
|
+
/run-agent --scope project reviewer review the latest changes
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
If the chosen agent frontmatter sets `sessionStrategy: fork-at`, the command clones the current active path into a new session before running the agent. That keeps long implementation runs isolated in their own branch while preserving the original conversation.
|
|
91
|
+
|
|
68
92
|
### Delegate Flags
|
|
69
93
|
|
|
70
94
|
| Flag | Meaning |
|
|
@@ -94,6 +118,7 @@ name: my-agent
|
|
|
94
118
|
description: What this agent does
|
|
95
119
|
tools: read, grep, find, ls
|
|
96
120
|
model: anthropic/claude-haiku-4-5
|
|
121
|
+
sessionStrategy: fork-at
|
|
97
122
|
---
|
|
98
123
|
|
|
99
124
|
System prompt for the agent.
|
|
@@ -106,13 +131,17 @@ The package ships with these agents out of the box:
|
|
|
106
131
|
- `scout` — fast codebase recon
|
|
107
132
|
- `docs-scout` — Context7-first documentation lookup
|
|
108
133
|
- `planner` — implementation planning
|
|
109
|
-
- `worker` — general-purpose implementation
|
|
110
|
-
- `reviewer` — code review
|
|
134
|
+
- `worker` — general-purpose implementation (`sessionStrategy: fork-at` by default)
|
|
135
|
+
- `reviewer` — code review (`sessionStrategy: fork-at` by default)
|
|
111
136
|
- `ux-designer` — frontend UI design
|
|
112
137
|
|
|
113
138
|
Resolution order is: bundled → user (`~/.pi/agent/agents/`) → project (`.pi/agents/`). Project agents override user and bundled agents by name.
|
|
114
139
|
|
|
115
|
-
|
|
140
|
+
Optional frontmatter:
|
|
141
|
+
- `thinking` — reasoning effort for the spawned pi process
|
|
142
|
+
- `sessionStrategy: fork-at` — when used with `/run-agent`, clone the current active branch into a new session before running
|
|
143
|
+
|
|
144
|
+
> Note: pi now supports package-shipped agents via `pi.agents` (or conventional `agents/` directories). This package publishes its bundled agents that way, while user agents in `~/.pi/agent/agents/` and project agents in `.pi/agents/` still override them by name.
|
|
116
145
|
|
|
117
146
|
### Managing Agents
|
|
118
147
|
|
package/agents/reviewer.md
CHANGED
|
@@ -4,6 +4,7 @@ description: Code review specialist for quality and security analysis
|
|
|
4
4
|
tools: read, grep, find, ls, bash
|
|
5
5
|
model: openai/gpt-5.4
|
|
6
6
|
thinking: medium
|
|
7
|
+
sessionStrategy: fork-at
|
|
7
8
|
---
|
|
8
9
|
|
|
9
10
|
You are a senior code reviewer. Analyze code for quality, security, and maintainability.
|
package/agents/worker.md
CHANGED
|
@@ -3,6 +3,7 @@ name: worker
|
|
|
3
3
|
description: General-purpose subagent with full capabilities, isolated context
|
|
4
4
|
model: openai/gpt-5.4
|
|
5
5
|
thinking: medium
|
|
6
|
+
sessionStrategy: fork-at
|
|
6
7
|
---
|
|
7
8
|
|
|
8
9
|
You are a worker agent with full capabilities. You operate in an isolated context window to handle delegated tasks without polluting the main conversation.
|
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Agent discovery and configuration
|
|
3
|
+
*
|
|
4
|
+
* Supports two discovery strategies:
|
|
5
|
+
* 1. Package-resolved: Uses pi's ResolvedPaths.agents from the package manager
|
|
6
|
+
* when available (pi forks with first-class agents resource support).
|
|
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.
|
|
3
10
|
*/
|
|
4
11
|
|
|
5
12
|
import * as fs from 'node:fs';
|
|
6
13
|
import * as path from 'node:path';
|
|
7
14
|
import { getAgentDir, parseFrontmatter } from '@mariozechner/pi-coding-agent';
|
|
15
|
+
import type { ResolvedPaths } from '@mariozechner/pi-coding-agent';
|
|
8
16
|
|
|
9
17
|
export type AgentScope = 'user' | 'project' | 'both';
|
|
10
|
-
export type AgentSource = 'bundled' | 'user' | 'project';
|
|
18
|
+
export type AgentSource = 'bundled' | 'user' | 'project' | 'package';
|
|
19
|
+
export type AgentSessionStrategy = 'inline' | 'fork-at';
|
|
11
20
|
|
|
12
21
|
export interface AgentConfig {
|
|
13
22
|
name: string;
|
|
@@ -15,6 +24,7 @@ export interface AgentConfig {
|
|
|
15
24
|
tools?: string[];
|
|
16
25
|
model?: string;
|
|
17
26
|
thinking?: string;
|
|
27
|
+
sessionStrategy?: AgentSessionStrategy;
|
|
18
28
|
systemPrompt: string;
|
|
19
29
|
source: AgentSource;
|
|
20
30
|
filePath: string;
|
|
@@ -28,6 +38,13 @@ export interface AgentDiscoveryResult {
|
|
|
28
38
|
/** Bundled agents ship with the package (../../agents relative to extensions/subagent/) */
|
|
29
39
|
const bundledAgentsDir = path.resolve(import.meta.dirname, '..', '..', 'agents');
|
|
30
40
|
|
|
41
|
+
function parseSessionStrategy(value?: string): AgentSessionStrategy | undefined {
|
|
42
|
+
if (!value) return undefined;
|
|
43
|
+
const normalized = value.trim().toLowerCase();
|
|
44
|
+
if (normalized === 'inline' || normalized === 'fork-at') return normalized;
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
31
48
|
function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {
|
|
32
49
|
const agents: AgentConfig[] = [];
|
|
33
50
|
|
|
@@ -71,6 +88,7 @@ function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {
|
|
|
71
88
|
tools: tools && tools.length > 0 ? tools : undefined,
|
|
72
89
|
model: frontmatter.model,
|
|
73
90
|
thinking: frontmatter.thinking,
|
|
91
|
+
sessionStrategy: parseSessionStrategy(frontmatter.sessionStrategy),
|
|
74
92
|
systemPrompt: body,
|
|
75
93
|
source,
|
|
76
94
|
filePath,
|
|
@@ -100,6 +118,57 @@ function findNearestProjectAgentsDir(cwd: string): string | null {
|
|
|
100
118
|
}
|
|
101
119
|
}
|
|
102
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Load agents from package-manager resolved paths (enabled only).
|
|
123
|
+
* This is called with pre-resolved paths from an async context.
|
|
124
|
+
*/
|
|
125
|
+
export function loadAgentsFromResolvedPaths(resolvedPaths: ResolvedPaths): AgentConfig[] {
|
|
126
|
+
// Check if the agents field exists (compatibility with upstream pi)
|
|
127
|
+
const agentResources = (resolvedPaths as unknown as Record<string, unknown>).agents;
|
|
128
|
+
if (!agentResources || !Array.isArray(agentResources)) {
|
|
129
|
+
return [];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const agents: AgentConfig[] = [];
|
|
133
|
+
for (const resource of agentResources) {
|
|
134
|
+
if (!resource.enabled) continue;
|
|
135
|
+
const agent = loadAgentFromFile(resource.path, 'package');
|
|
136
|
+
if (agent) agents.push(agent);
|
|
137
|
+
}
|
|
138
|
+
return agents;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function loadAgentFromFile(filePath: string, source: AgentSource): AgentConfig | null {
|
|
142
|
+
let content: string;
|
|
143
|
+
try {
|
|
144
|
+
content = fs.readFileSync(filePath, 'utf-8');
|
|
145
|
+
} catch {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const { frontmatter, body } = parseFrontmatter<Record<string, string>>(content);
|
|
150
|
+
if (!frontmatter.name || !frontmatter.description) {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const tools = frontmatter.tools
|
|
155
|
+
?.split(',')
|
|
156
|
+
.map((t: string) => t.trim())
|
|
157
|
+
.filter(Boolean);
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
name: frontmatter.name,
|
|
161
|
+
description: frontmatter.description,
|
|
162
|
+
tools: tools && tools.length > 0 ? tools : undefined,
|
|
163
|
+
model: frontmatter.model,
|
|
164
|
+
thinking: frontmatter.thinking,
|
|
165
|
+
sessionStrategy: parseSessionStrategy(frontmatter.sessionStrategy),
|
|
166
|
+
systemPrompt: body,
|
|
167
|
+
source,
|
|
168
|
+
filePath,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
103
172
|
export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryResult {
|
|
104
173
|
const userDir = path.join(getAgentDir(), 'agents');
|
|
105
174
|
const projectAgentsDir = findNearestProjectAgentsDir(cwd);
|
|
@@ -128,6 +197,34 @@ export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryRe
|
|
|
128
197
|
return { agents: Array.from(agentMap.values()), projectAgentsDir };
|
|
129
198
|
}
|
|
130
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
|
+
|
|
131
228
|
export { bundledAgentsDir };
|
|
132
229
|
|
|
133
230
|
export function formatAgentList(
|
|
@@ -8,6 +8,13 @@ export interface DelegateCommandOptions {
|
|
|
8
8
|
explicitTask?: string;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
export interface RunAgentCommandOptions {
|
|
12
|
+
agentScope: AgentScope;
|
|
13
|
+
confirmProjectAgents: boolean;
|
|
14
|
+
agentName?: string;
|
|
15
|
+
explicitTask?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
11
18
|
const WORKFLOW_IDS = new Set<WorkflowId>([
|
|
12
19
|
'scout-only',
|
|
13
20
|
'scout-and-plan',
|
|
@@ -82,3 +89,57 @@ export function parseDelegateArgs(rawArgs?: string):
|
|
|
82
89
|
},
|
|
83
90
|
};
|
|
84
91
|
}
|
|
92
|
+
|
|
93
|
+
export function formatRunAgentUsage(): string {
|
|
94
|
+
return 'Usage: /run-agent [--scope user|project|both] [--yes-project-agents] <agent> [task]';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function parseRunAgentArgs(rawArgs?: string):
|
|
98
|
+
| { ok: true; options: RunAgentCommandOptions }
|
|
99
|
+
| { ok: false; error: string } {
|
|
100
|
+
const tokens = tokenize(rawArgs?.trim() ?? '');
|
|
101
|
+
const taskTokens: string[] = [];
|
|
102
|
+
|
|
103
|
+
let agentScope: AgentScope = 'user';
|
|
104
|
+
let confirmProjectAgents = true;
|
|
105
|
+
let agentName: string | undefined;
|
|
106
|
+
|
|
107
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
108
|
+
const token = tokens[i];
|
|
109
|
+
|
|
110
|
+
if (token === '--scope') {
|
|
111
|
+
const value = tokens[++i];
|
|
112
|
+
if (!value || !['user', 'project', 'both'].includes(value)) {
|
|
113
|
+
return { ok: false, error: `Invalid --scope value.\n\n${formatRunAgentUsage()}` };
|
|
114
|
+
}
|
|
115
|
+
agentScope = value as AgentScope;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (token === '--yes-project-agents') {
|
|
120
|
+
confirmProjectAgents = false;
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (token.startsWith('--')) {
|
|
125
|
+
return { ok: false, error: `Unknown option: ${token}\n\n${formatRunAgentUsage()}` };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (!agentName) {
|
|
129
|
+
agentName = token;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
taskTokens.push(token);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
ok: true,
|
|
138
|
+
options: {
|
|
139
|
+
agentScope,
|
|
140
|
+
confirmProjectAgents,
|
|
141
|
+
agentName,
|
|
142
|
+
explicitTask: taskTokens.join(' ').trim() || undefined,
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
}
|
|
@@ -3,8 +3,9 @@ import * as fs from 'node:fs';
|
|
|
3
3
|
import * as os from 'node:os';
|
|
4
4
|
import * as path from 'node:path';
|
|
5
5
|
import { withFileMutationQueue } from '@mariozechner/pi-coding-agent';
|
|
6
|
+
import type { ResolvedPaths } from '@mariozechner/pi-coding-agent';
|
|
6
7
|
import type { Message } from '@mariozechner/pi-ai';
|
|
7
|
-
import {
|
|
8
|
+
import { discoverAgentsWithPackages, type AgentScope } from './agents';
|
|
8
9
|
import type { AgentResult, UsageStats } from './delegate-types';
|
|
9
10
|
|
|
10
11
|
function emptyUsage(): UsageStats {
|
|
@@ -45,6 +46,7 @@ export interface RunAgentOptions {
|
|
|
45
46
|
onUpdate?: OnPhaseUpdate;
|
|
46
47
|
phaseName?: string;
|
|
47
48
|
signal?: AbortSignal;
|
|
49
|
+
resolvedPaths?: ResolvedPaths;
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
export async function runAgent(
|
|
@@ -53,7 +55,7 @@ export async function runAgent(
|
|
|
53
55
|
task: string,
|
|
54
56
|
options: RunAgentOptions = {},
|
|
55
57
|
): Promise<AgentResult> {
|
|
56
|
-
const { agents } =
|
|
58
|
+
const { agents } = discoverAgentsWithPackages(cwd, options.agentScope ?? 'user', options.resolvedPaths);
|
|
57
59
|
const agent = agents.find((a) => a.name === agentName);
|
|
58
60
|
|
|
59
61
|
if (!agent) {
|
|
@@ -25,11 +25,14 @@ import { StringEnum } from '@mariozechner/pi-ai';
|
|
|
25
25
|
import {
|
|
26
26
|
type ExtensionAPI,
|
|
27
27
|
type ExtensionCommandContext,
|
|
28
|
+
DefaultPackageManager,
|
|
28
29
|
getAgentDir,
|
|
29
30
|
getMarkdownTheme,
|
|
31
|
+
SettingsManager,
|
|
30
32
|
withFileMutationQueue,
|
|
31
33
|
} from '@mariozechner/pi-coding-agent';
|
|
32
|
-
import {
|
|
34
|
+
import type { ResolvedPaths } from '@mariozechner/pi-coding-agent';
|
|
35
|
+
import { Box, Container, Markdown, Spacer, Text } from '@mariozechner/pi-tui';
|
|
33
36
|
import { Type } from '@sinclair/typebox';
|
|
34
37
|
import {
|
|
35
38
|
type AgentConfig,
|
|
@@ -37,8 +40,14 @@ import {
|
|
|
37
40
|
type AgentSource,
|
|
38
41
|
bundledAgentsDir,
|
|
39
42
|
discoverAgents,
|
|
43
|
+
discoverAgentsWithPackages,
|
|
40
44
|
} from './agents.js';
|
|
41
|
-
import {
|
|
45
|
+
import {
|
|
46
|
+
formatDelegateUsage,
|
|
47
|
+
formatRunAgentUsage,
|
|
48
|
+
parseDelegateArgs,
|
|
49
|
+
parseRunAgentArgs,
|
|
50
|
+
} from './delegate-args.js';
|
|
42
51
|
import { runAgent, runParallel } from './delegate-executor.js';
|
|
43
52
|
import { buildSynthesisPrompt, extractRecentConversation } from './synthesis.js';
|
|
44
53
|
import type {
|
|
@@ -178,6 +187,20 @@ interface UsageStats {
|
|
|
178
187
|
turns: number;
|
|
179
188
|
}
|
|
180
189
|
|
|
190
|
+
interface RunAgentSummaryDetails {
|
|
191
|
+
agent: string;
|
|
192
|
+
agentSource: AgentSource;
|
|
193
|
+
agentScope: AgentScope;
|
|
194
|
+
sessionStrategy: string;
|
|
195
|
+
forked: boolean;
|
|
196
|
+
exitCode: number;
|
|
197
|
+
stopReason?: string;
|
|
198
|
+
errorMessage?: string;
|
|
199
|
+
usage: UsageStats;
|
|
200
|
+
task: string;
|
|
201
|
+
output: string;
|
|
202
|
+
}
|
|
203
|
+
|
|
181
204
|
interface SingleResult {
|
|
182
205
|
agent: string;
|
|
183
206
|
agentSource: AgentSource | 'unknown';
|
|
@@ -507,6 +530,26 @@ const SubagentParams = Type.Object({
|
|
|
507
530
|
),
|
|
508
531
|
});
|
|
509
532
|
|
|
533
|
+
/**
|
|
534
|
+
* Try to resolve package paths including the agents resource.
|
|
535
|
+
* Returns null if the pi version does not support ResolvedPaths.agents.
|
|
536
|
+
*/
|
|
537
|
+
async function tryResolvePackagePaths(cwd: string): Promise<ResolvedPaths | undefined> {
|
|
538
|
+
try {
|
|
539
|
+
const agentDir = getAgentDir();
|
|
540
|
+
const settingsManager = SettingsManager.create(cwd, agentDir);
|
|
541
|
+
const packageManager = new DefaultPackageManager({ cwd, agentDir, settingsManager });
|
|
542
|
+
const resolved = await packageManager.resolve();
|
|
543
|
+
// Check if this pi version has agents support
|
|
544
|
+
if ('agents' in resolved && Array.isArray((resolved as Record<string, unknown>).agents)) {
|
|
545
|
+
return resolved;
|
|
546
|
+
}
|
|
547
|
+
} catch {
|
|
548
|
+
// Incompatible pi version or missing API
|
|
549
|
+
}
|
|
550
|
+
return undefined;
|
|
551
|
+
}
|
|
552
|
+
|
|
510
553
|
export default function (pi: ExtensionAPI) {
|
|
511
554
|
pi.registerTool({
|
|
512
555
|
name: 'subagent',
|
|
@@ -521,7 +564,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
521
564
|
|
|
522
565
|
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
523
566
|
const agentScope: AgentScope = params.agentScope ?? 'user';
|
|
524
|
-
const
|
|
567
|
+
const resolvedPaths = await tryResolvePackagePaths(ctx.cwd);
|
|
568
|
+
const discovery = discoverAgentsWithPackages(ctx.cwd, agentScope, resolvedPaths);
|
|
525
569
|
const agents = discovery.agents;
|
|
526
570
|
const confirmProjectAgents = params.confirmProjectAgents ?? true;
|
|
527
571
|
|
|
@@ -1135,6 +1179,130 @@ export default function (pi: ExtensionAPI) {
|
|
|
1135
1179
|
.join('\n\n');
|
|
1136
1180
|
}
|
|
1137
1181
|
|
|
1182
|
+
async function confirmProjectAgentsIfNeeded(
|
|
1183
|
+
ctx: ExtensionCommandContext,
|
|
1184
|
+
agents: AgentConfig[],
|
|
1185
|
+
projectAgentsDir: string | null,
|
|
1186
|
+
confirmProjectAgents: boolean,
|
|
1187
|
+
): Promise<boolean> {
|
|
1188
|
+
if (!ctx.hasUI || !confirmProjectAgents || agents.length === 0) return true;
|
|
1189
|
+
|
|
1190
|
+
const names = Array.from(new Set(agents.map((agent) => agent.name))).join(', ');
|
|
1191
|
+
const dir = projectAgentsDir ?? '(unknown)';
|
|
1192
|
+
return ctx.ui.confirm(
|
|
1193
|
+
'Run project-local agents?',
|
|
1194
|
+
`Agents: ${names}\nSource: ${dir}\n\nProject agents are repo-controlled. Only continue for trusted repositories.`,
|
|
1195
|
+
);
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
function buildRunAgentTask(conversation: string, explicitTask?: string): string | undefined {
|
|
1199
|
+
const trimmedConversation = conversation.trim();
|
|
1200
|
+
const trimmedTask = explicitTask?.trim();
|
|
1201
|
+
|
|
1202
|
+
if (trimmedTask && trimmedConversation) {
|
|
1203
|
+
return `Assigned task:\n${trimmedTask}\n\nRelevant main-session context:\n${trimmedConversation}`;
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
if (trimmedTask) return trimmedTask;
|
|
1207
|
+
if (trimmedConversation) {
|
|
1208
|
+
return `Use the following main-session context to determine and complete the task:\n\n${trimmedConversation}`;
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
return undefined;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
function formatRunAgentSummary(options: {
|
|
1215
|
+
agent: AgentConfig;
|
|
1216
|
+
agentScope: AgentScope;
|
|
1217
|
+
task: string;
|
|
1218
|
+
result: AgentResult;
|
|
1219
|
+
forked: boolean;
|
|
1220
|
+
}): string {
|
|
1221
|
+
const { agent, agentScope, task, result, forked } = options;
|
|
1222
|
+
const output = getFinalText(result).trim();
|
|
1223
|
+
const usage = formatUsageStats(result.usage, result.model);
|
|
1224
|
+
const failed = result.exitCode !== 0 || result.stopReason === 'error';
|
|
1225
|
+
const status = failed ? 'failed' : 'completed';
|
|
1226
|
+
const lines = [
|
|
1227
|
+
'## Agent Run',
|
|
1228
|
+
`- Agent: \`${agent.name}\``,
|
|
1229
|
+
`- Source: ${agent.source}`,
|
|
1230
|
+
`- Scope: ${agentScope}`,
|
|
1231
|
+
`- Session strategy: ${agent.sessionStrategy ?? 'inline'}`,
|
|
1232
|
+
`- Session handling: ${forked ? 'forked current branch at active entry' : 'inline in current session'}`,
|
|
1233
|
+
`- Status: ${status}`,
|
|
1234
|
+
'',
|
|
1235
|
+
'## Task',
|
|
1236
|
+
task,
|
|
1237
|
+
'',
|
|
1238
|
+
'## Output',
|
|
1239
|
+
output || '_No output returned._',
|
|
1240
|
+
];
|
|
1241
|
+
|
|
1242
|
+
if (failed) {
|
|
1243
|
+
const errorText = result.errorMessage?.trim() || result.stderr.trim();
|
|
1244
|
+
if (errorText) {
|
|
1245
|
+
lines.push('', '## Error', errorText);
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
if (usage) {
|
|
1250
|
+
lines.push('', '## Usage', usage);
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
return lines.join('\n');
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
pi.registerMessageRenderer('run-agent-summary', (message, { expanded }, theme) => {
|
|
1257
|
+
const details = message.details as RunAgentSummaryDetails | undefined;
|
|
1258
|
+
if (!details) return new Text(typeof message.content === 'string' ? message.content : '', 0, 0);
|
|
1259
|
+
|
|
1260
|
+
const failed = details.exitCode !== 0 || details.stopReason === 'error';
|
|
1261
|
+
const icon = failed ? theme.fg('error', '✗') : theme.fg('success', '✓');
|
|
1262
|
+
const title = `${icon} ${theme.fg('toolTitle', theme.bold(details.agent))}${theme.fg('muted', ` (${details.agentSource})`)}`;
|
|
1263
|
+
const modeLine = theme.fg(
|
|
1264
|
+
'dim',
|
|
1265
|
+
`${details.forked ? 'forked branch' : 'inline'} • ${details.agentScope} • ${details.sessionStrategy}`,
|
|
1266
|
+
);
|
|
1267
|
+
const usageLine = formatUsageStats(details.usage);
|
|
1268
|
+
const outputPreview = details.output.trim() || '(no output)';
|
|
1269
|
+
|
|
1270
|
+
if (!expanded) {
|
|
1271
|
+
const previewLines = outputPreview.split('\n').slice(0, 3).join('\n');
|
|
1272
|
+
const text = [
|
|
1273
|
+
title,
|
|
1274
|
+
modeLine,
|
|
1275
|
+
theme.fg('muted', `Task: ${details.task}`),
|
|
1276
|
+
failed && details.errorMessage ? theme.fg('error', `Error: ${details.errorMessage}`) : previewLines,
|
|
1277
|
+
usageLine ? theme.fg('dim', usageLine) : undefined,
|
|
1278
|
+
theme.fg('muted', '(Ctrl+O to expand)'),
|
|
1279
|
+
]
|
|
1280
|
+
.filter((line): line is string => Boolean(line))
|
|
1281
|
+
.join('\n');
|
|
1282
|
+
const box = new Box(1, 1, (text) => theme.bg('customMessageBg', text));
|
|
1283
|
+
box.addChild(new Text(text, 0, 0));
|
|
1284
|
+
return box;
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
const mdTheme = getMarkdownTheme();
|
|
1288
|
+
const box = new Box(1, 1, (text) => theme.bg('customMessageBg', text));
|
|
1289
|
+
box.addChild(new Text(title, 0, 0));
|
|
1290
|
+
box.addChild(new Text(modeLine, 0, 0));
|
|
1291
|
+
if (usageLine) box.addChild(new Text(theme.fg('dim', usageLine), 0, 0));
|
|
1292
|
+
box.addChild(new Spacer(1));
|
|
1293
|
+
box.addChild(new Text(theme.fg('muted', 'Task'), 0, 0));
|
|
1294
|
+
box.addChild(new Text(theme.fg('text', details.task), 0, 0));
|
|
1295
|
+
if (failed && details.errorMessage) {
|
|
1296
|
+
box.addChild(new Spacer(1));
|
|
1297
|
+
box.addChild(new Text(theme.fg('error', 'Error'), 0, 0));
|
|
1298
|
+
box.addChild(new Text(theme.fg('error', details.errorMessage), 0, 0));
|
|
1299
|
+
}
|
|
1300
|
+
box.addChild(new Spacer(1));
|
|
1301
|
+
box.addChild(new Text(theme.fg('muted', 'Output'), 0, 0));
|
|
1302
|
+
box.addChild(new Markdown(outputPreview, 0, 0, mdTheme));
|
|
1303
|
+
return box;
|
|
1304
|
+
});
|
|
1305
|
+
|
|
1138
1306
|
async function executePhase(
|
|
1139
1307
|
cwd: string,
|
|
1140
1308
|
phase: PhaseDefinition,
|
|
@@ -1142,6 +1310,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1142
1310
|
previousOutput: string,
|
|
1143
1311
|
ctx: ExtensionCommandContext,
|
|
1144
1312
|
agentScope: AgentScope,
|
|
1313
|
+
resolvedPaths?: ResolvedPaths,
|
|
1145
1314
|
): Promise<{ results: AgentResult[]; output: string; aborted: boolean }> {
|
|
1146
1315
|
const task = phase.taskTemplate
|
|
1147
1316
|
.replace(/\{synthesis\}/g, synthesis)
|
|
@@ -1160,11 +1329,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
1160
1329
|
// Streaming updates happen per-message
|
|
1161
1330
|
},
|
|
1162
1331
|
phaseName: phase.name,
|
|
1332
|
+
resolvedPaths,
|
|
1163
1333
|
});
|
|
1164
1334
|
} else {
|
|
1165
1335
|
const result = await runAgent(cwd, phase.agents[0], task, {
|
|
1166
1336
|
agentScope,
|
|
1167
1337
|
phaseName: phase.name,
|
|
1338
|
+
resolvedPaths,
|
|
1168
1339
|
});
|
|
1169
1340
|
results = [result];
|
|
1170
1341
|
}
|
|
@@ -1300,6 +1471,114 @@ export default function (pi: ExtensionAPI) {
|
|
|
1300
1471
|
},
|
|
1301
1472
|
});
|
|
1302
1473
|
|
|
1474
|
+
pi.registerCommand('run-agent', {
|
|
1475
|
+
description:
|
|
1476
|
+
'Run a single agent directly — honors agent sessionStrategy metadata such as fork-at',
|
|
1477
|
+
handler: async (args, ctx) => {
|
|
1478
|
+
const parsed = parseRunAgentArgs(args);
|
|
1479
|
+
if (!parsed.ok) {
|
|
1480
|
+
ctx.ui.notify(parsed.error, 'warning');
|
|
1481
|
+
return;
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
const { agentName, explicitTask, agentScope, confirmProjectAgents } = parsed.options;
|
|
1485
|
+
if (!agentName) {
|
|
1486
|
+
ctx.ui.notify(formatRunAgentUsage(), 'warning');
|
|
1487
|
+
return;
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
const resolvedPaths = await tryResolvePackagePaths(ctx.cwd);
|
|
1491
|
+
const discovery = discoverAgentsWithPackages(ctx.cwd, agentScope, resolvedPaths);
|
|
1492
|
+
const agent = discovery.agents.find((candidate) => candidate.name === agentName);
|
|
1493
|
+
|
|
1494
|
+
if (!agent) {
|
|
1495
|
+
const available = discovery.agents.map((candidate) => candidate.name).join(', ') || 'none';
|
|
1496
|
+
ctx.ui.notify(`Unknown agent: "${agentName}". Available: ${available}`, 'warning');
|
|
1497
|
+
return;
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
const requestedProjectAgents = agent.source === 'project' ? [agent] : [];
|
|
1501
|
+
const approved = await confirmProjectAgentsIfNeeded(
|
|
1502
|
+
ctx,
|
|
1503
|
+
requestedProjectAgents,
|
|
1504
|
+
discovery.projectAgentsDir,
|
|
1505
|
+
confirmProjectAgents,
|
|
1506
|
+
);
|
|
1507
|
+
if (!approved) {
|
|
1508
|
+
ctx.ui.notify('Run cancelled — project-local agents not approved.', 'info');
|
|
1509
|
+
return;
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
const conversation = extractRecentConversation(ctx);
|
|
1513
|
+
const task = buildRunAgentTask(conversation, explicitTask);
|
|
1514
|
+
if (!task) {
|
|
1515
|
+
ctx.ui.notify(`No conversation context and no task specified.\n\n${formatRunAgentUsage()}`, 'warning');
|
|
1516
|
+
return;
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
let forked = false;
|
|
1520
|
+
if (agent.sessionStrategy === 'fork-at') {
|
|
1521
|
+
const leafId = ctx.sessionManager.getLeafId?.();
|
|
1522
|
+
if (!leafId) {
|
|
1523
|
+
ctx.ui.notify(
|
|
1524
|
+
`Agent "${agent.name}" prefers fork-at, but the current session cannot be forked. Running inline instead.`,
|
|
1525
|
+
'warning',
|
|
1526
|
+
);
|
|
1527
|
+
} else {
|
|
1528
|
+
const forkCompat = ctx.fork as unknown as (
|
|
1529
|
+
entryId: string,
|
|
1530
|
+
options?: { position: 'at' },
|
|
1531
|
+
) => Promise<{ cancelled: boolean }>;
|
|
1532
|
+
const forkResult = await forkCompat(leafId, { position: 'at' });
|
|
1533
|
+
if (forkResult.cancelled) {
|
|
1534
|
+
ctx.ui.notify(`Run cancelled — unable to fork session for "${agent.name}".`, 'info');
|
|
1535
|
+
return;
|
|
1536
|
+
}
|
|
1537
|
+
forked = true;
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
ctx.ui.notify(
|
|
1542
|
+
`Running agent: ${agent.name} [${agent.source}${forked ? ', forked' : ', inline'}]`,
|
|
1543
|
+
'info',
|
|
1544
|
+
);
|
|
1545
|
+
|
|
1546
|
+
const result = await runAgent(ctx.cwd, agent.name, task, { agentScope, resolvedPaths });
|
|
1547
|
+
const failed = result.exitCode !== 0 || result.stopReason === 'error';
|
|
1548
|
+
const output = getFinalText(result).trim();
|
|
1549
|
+
|
|
1550
|
+
ctx.ui.notify(
|
|
1551
|
+
failed ? `Agent "${agent.name}" failed.` : `Agent "${agent.name}" complete.`,
|
|
1552
|
+
failed ? 'error' : 'info',
|
|
1553
|
+
);
|
|
1554
|
+
|
|
1555
|
+
pi.sendMessage({
|
|
1556
|
+
customType: 'run-agent-summary',
|
|
1557
|
+
content: formatRunAgentSummary({
|
|
1558
|
+
agent,
|
|
1559
|
+
agentScope,
|
|
1560
|
+
task,
|
|
1561
|
+
result,
|
|
1562
|
+
forked,
|
|
1563
|
+
}),
|
|
1564
|
+
display: true,
|
|
1565
|
+
details: {
|
|
1566
|
+
agent: agent.name,
|
|
1567
|
+
agentSource: agent.source,
|
|
1568
|
+
agentScope,
|
|
1569
|
+
sessionStrategy: agent.sessionStrategy ?? 'inline',
|
|
1570
|
+
forked,
|
|
1571
|
+
exitCode: result.exitCode,
|
|
1572
|
+
stopReason: result.stopReason,
|
|
1573
|
+
errorMessage: result.errorMessage,
|
|
1574
|
+
usage: result.usage,
|
|
1575
|
+
task,
|
|
1576
|
+
output,
|
|
1577
|
+
} satisfies RunAgentSummaryDetails,
|
|
1578
|
+
});
|
|
1579
|
+
},
|
|
1580
|
+
});
|
|
1581
|
+
|
|
1303
1582
|
pi.registerCommand('delegate', {
|
|
1304
1583
|
description:
|
|
1305
1584
|
'Orchestrate subagent workflows — supports --scope, --workflow, and project-local agents',
|
|
@@ -1311,6 +1590,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1311
1590
|
}
|
|
1312
1591
|
|
|
1313
1592
|
const { explicitTask, agentScope, workflowId, confirmProjectAgents } = parsed.options;
|
|
1593
|
+
const resolvedPaths = await tryResolvePackagePaths(ctx.cwd);
|
|
1314
1594
|
|
|
1315
1595
|
// Step 1: Synthesize
|
|
1316
1596
|
const conversation = extractRecentConversation(ctx);
|
|
@@ -1322,7 +1602,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1322
1602
|
const prompt = buildSynthesisPrompt(conversation, explicitTask);
|
|
1323
1603
|
ctx.ui.notify('⏳ Synthesizing task from conversation...', 'info');
|
|
1324
1604
|
|
|
1325
|
-
const synthResult = await runAgent(ctx.cwd, 'planner', prompt, { agentScope });
|
|
1605
|
+
const synthResult = await runAgent(ctx.cwd, 'planner', prompt, { agentScope, resolvedPaths });
|
|
1326
1606
|
const synthesis = getFinalText(synthResult);
|
|
1327
1607
|
|
|
1328
1608
|
if (!synthesis.trim()) {
|
|
@@ -1350,24 +1630,22 @@ export default function (pi: ExtensionAPI) {
|
|
|
1350
1630
|
return;
|
|
1351
1631
|
}
|
|
1352
1632
|
|
|
1353
|
-
if (
|
|
1354
|
-
const discovery =
|
|
1633
|
+
if (agentScope === 'project' || agentScope === 'both') {
|
|
1634
|
+
const discovery = discoverAgentsWithPackages(ctx.cwd, agentScope, resolvedPaths);
|
|
1355
1635
|
const requestedProjectAgents = workflow.phases
|
|
1356
1636
|
.flatMap((phase) => phase.agents)
|
|
1357
1637
|
.map((name) => discovery.agents.find((agent) => agent.name === name))
|
|
1358
1638
|
.filter((agent): agent is AgentConfig => agent?.source === 'project');
|
|
1359
1639
|
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
return;
|
|
1370
|
-
}
|
|
1640
|
+
const ok = await confirmProjectAgentsIfNeeded(
|
|
1641
|
+
ctx,
|
|
1642
|
+
requestedProjectAgents,
|
|
1643
|
+
discovery.projectAgentsDir,
|
|
1644
|
+
confirmProjectAgents,
|
|
1645
|
+
);
|
|
1646
|
+
if (!ok) {
|
|
1647
|
+
ctx.ui.notify('Delegate cancelled — project-local agents not approved.', 'info');
|
|
1648
|
+
return;
|
|
1371
1649
|
}
|
|
1372
1650
|
}
|
|
1373
1651
|
|
|
@@ -1391,6 +1669,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1391
1669
|
previousOutput,
|
|
1392
1670
|
ctx,
|
|
1393
1671
|
agentScope,
|
|
1672
|
+
resolvedPaths,
|
|
1394
1673
|
);
|
|
1395
1674
|
|
|
1396
1675
|
const phase: PhaseResult = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dreki-gg/pi-subagent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Subagent tool and delegate orchestration for pi — isolated agents, parallel scouts, planning gates, and workflow presets",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package"
|
|
@@ -28,6 +28,9 @@
|
|
|
28
28
|
],
|
|
29
29
|
"prompts": [
|
|
30
30
|
"./prompts"
|
|
31
|
+
],
|
|
32
|
+
"agents": [
|
|
33
|
+
"./agents"
|
|
31
34
|
]
|
|
32
35
|
},
|
|
33
36
|
"devDependencies": {
|