@dreki-gg/pi-subagent 0.5.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 +10 -0
- package/README.md +32 -3
- package/agents/reviewer.md +1 -0
- package/agents/worker.md +1 -0
- package/extensions/subagent/agents.ts +11 -0
- package/extensions/subagent/delegate-args.ts +61 -0
- package/extensions/subagent/index.ts +263 -14
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
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
|
+
|
|
3
13
|
## 0.5.0
|
|
4
14
|
|
|
5
15
|
### 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.
|
|
@@ -16,6 +16,7 @@ import type { ResolvedPaths } from '@mariozechner/pi-coding-agent';
|
|
|
16
16
|
|
|
17
17
|
export type AgentScope = 'user' | 'project' | 'both';
|
|
18
18
|
export type AgentSource = 'bundled' | 'user' | 'project' | 'package';
|
|
19
|
+
export type AgentSessionStrategy = 'inline' | 'fork-at';
|
|
19
20
|
|
|
20
21
|
export interface AgentConfig {
|
|
21
22
|
name: string;
|
|
@@ -23,6 +24,7 @@ export interface AgentConfig {
|
|
|
23
24
|
tools?: string[];
|
|
24
25
|
model?: string;
|
|
25
26
|
thinking?: string;
|
|
27
|
+
sessionStrategy?: AgentSessionStrategy;
|
|
26
28
|
systemPrompt: string;
|
|
27
29
|
source: AgentSource;
|
|
28
30
|
filePath: string;
|
|
@@ -36,6 +38,13 @@ export interface AgentDiscoveryResult {
|
|
|
36
38
|
/** Bundled agents ship with the package (../../agents relative to extensions/subagent/) */
|
|
37
39
|
const bundledAgentsDir = path.resolve(import.meta.dirname, '..', '..', 'agents');
|
|
38
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
|
+
|
|
39
48
|
function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {
|
|
40
49
|
const agents: AgentConfig[] = [];
|
|
41
50
|
|
|
@@ -79,6 +88,7 @@ function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {
|
|
|
79
88
|
tools: tools && tools.length > 0 ? tools : undefined,
|
|
80
89
|
model: frontmatter.model,
|
|
81
90
|
thinking: frontmatter.thinking,
|
|
91
|
+
sessionStrategy: parseSessionStrategy(frontmatter.sessionStrategy),
|
|
82
92
|
systemPrompt: body,
|
|
83
93
|
source,
|
|
84
94
|
filePath,
|
|
@@ -152,6 +162,7 @@ function loadAgentFromFile(filePath: string, source: AgentSource): AgentConfig |
|
|
|
152
162
|
tools: tools && tools.length > 0 ? tools : undefined,
|
|
153
163
|
model: frontmatter.model,
|
|
154
164
|
thinking: frontmatter.thinking,
|
|
165
|
+
sessionStrategy: parseSessionStrategy(frontmatter.sessionStrategy),
|
|
155
166
|
systemPrompt: body,
|
|
156
167
|
source,
|
|
157
168
|
filePath,
|
|
@@ -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
|
+
}
|
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
withFileMutationQueue,
|
|
33
33
|
} from '@mariozechner/pi-coding-agent';
|
|
34
34
|
import type { ResolvedPaths } from '@mariozechner/pi-coding-agent';
|
|
35
|
-
import { Container, Markdown, Spacer, Text } from '@mariozechner/pi-tui';
|
|
35
|
+
import { Box, Container, Markdown, Spacer, Text } from '@mariozechner/pi-tui';
|
|
36
36
|
import { Type } from '@sinclair/typebox';
|
|
37
37
|
import {
|
|
38
38
|
type AgentConfig,
|
|
@@ -42,7 +42,12 @@ import {
|
|
|
42
42
|
discoverAgents,
|
|
43
43
|
discoverAgentsWithPackages,
|
|
44
44
|
} from './agents.js';
|
|
45
|
-
import {
|
|
45
|
+
import {
|
|
46
|
+
formatDelegateUsage,
|
|
47
|
+
formatRunAgentUsage,
|
|
48
|
+
parseDelegateArgs,
|
|
49
|
+
parseRunAgentArgs,
|
|
50
|
+
} from './delegate-args.js';
|
|
46
51
|
import { runAgent, runParallel } from './delegate-executor.js';
|
|
47
52
|
import { buildSynthesisPrompt, extractRecentConversation } from './synthesis.js';
|
|
48
53
|
import type {
|
|
@@ -182,6 +187,20 @@ interface UsageStats {
|
|
|
182
187
|
turns: number;
|
|
183
188
|
}
|
|
184
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
|
+
|
|
185
204
|
interface SingleResult {
|
|
186
205
|
agent: string;
|
|
187
206
|
agentSource: AgentSource | 'unknown';
|
|
@@ -1160,6 +1179,130 @@ export default function (pi: ExtensionAPI) {
|
|
|
1160
1179
|
.join('\n\n');
|
|
1161
1180
|
}
|
|
1162
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
|
+
|
|
1163
1306
|
async function executePhase(
|
|
1164
1307
|
cwd: string,
|
|
1165
1308
|
phase: PhaseDefinition,
|
|
@@ -1328,6 +1471,114 @@ export default function (pi: ExtensionAPI) {
|
|
|
1328
1471
|
},
|
|
1329
1472
|
});
|
|
1330
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
|
+
|
|
1331
1582
|
pi.registerCommand('delegate', {
|
|
1332
1583
|
description:
|
|
1333
1584
|
'Orchestrate subagent workflows — supports --scope, --workflow, and project-local agents',
|
|
@@ -1379,24 +1630,22 @@ export default function (pi: ExtensionAPI) {
|
|
|
1379
1630
|
return;
|
|
1380
1631
|
}
|
|
1381
1632
|
|
|
1382
|
-
if (
|
|
1633
|
+
if (agentScope === 'project' || agentScope === 'both') {
|
|
1383
1634
|
const discovery = discoverAgentsWithPackages(ctx.cwd, agentScope, resolvedPaths);
|
|
1384
1635
|
const requestedProjectAgents = workflow.phases
|
|
1385
1636
|
.flatMap((phase) => phase.agents)
|
|
1386
1637
|
.map((name) => discovery.agents.find((agent) => agent.name === name))
|
|
1387
1638
|
.filter((agent): agent is AgentConfig => agent?.source === 'project');
|
|
1388
1639
|
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
return;
|
|
1399
|
-
}
|
|
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;
|
|
1400
1649
|
}
|
|
1401
1650
|
}
|
|
1402
1651
|
|
package/package.json
CHANGED