@dreki-gg/pi-subagent 0.4.0 → 0.5.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
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @dreki-gg/pi-subagent
|
|
2
2
|
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [`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
|
|
8
|
+
|
|
3
9
|
## 0.4.0
|
|
4
10
|
|
|
5
11
|
### Minor Changes
|
|
@@ -1,13 +1,21 @@
|
|
|
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';
|
|
11
19
|
|
|
12
20
|
export interface AgentConfig {
|
|
13
21
|
name: string;
|
|
@@ -100,6 +108,56 @@ function findNearestProjectAgentsDir(cwd: string): string | null {
|
|
|
100
108
|
}
|
|
101
109
|
}
|
|
102
110
|
|
|
111
|
+
/**
|
|
112
|
+
* Load agents from package-manager resolved paths (enabled only).
|
|
113
|
+
* This is called with pre-resolved paths from an async context.
|
|
114
|
+
*/
|
|
115
|
+
export function loadAgentsFromResolvedPaths(resolvedPaths: ResolvedPaths): AgentConfig[] {
|
|
116
|
+
// Check if the agents field exists (compatibility with upstream pi)
|
|
117
|
+
const agentResources = (resolvedPaths as unknown as Record<string, unknown>).agents;
|
|
118
|
+
if (!agentResources || !Array.isArray(agentResources)) {
|
|
119
|
+
return [];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const agents: AgentConfig[] = [];
|
|
123
|
+
for (const resource of agentResources) {
|
|
124
|
+
if (!resource.enabled) continue;
|
|
125
|
+
const agent = loadAgentFromFile(resource.path, 'package');
|
|
126
|
+
if (agent) agents.push(agent);
|
|
127
|
+
}
|
|
128
|
+
return agents;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function loadAgentFromFile(filePath: string, source: AgentSource): AgentConfig | null {
|
|
132
|
+
let content: string;
|
|
133
|
+
try {
|
|
134
|
+
content = fs.readFileSync(filePath, 'utf-8');
|
|
135
|
+
} catch {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const { frontmatter, body } = parseFrontmatter<Record<string, string>>(content);
|
|
140
|
+
if (!frontmatter.name || !frontmatter.description) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const tools = frontmatter.tools
|
|
145
|
+
?.split(',')
|
|
146
|
+
.map((t: string) => t.trim())
|
|
147
|
+
.filter(Boolean);
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
name: frontmatter.name,
|
|
151
|
+
description: frontmatter.description,
|
|
152
|
+
tools: tools && tools.length > 0 ? tools : undefined,
|
|
153
|
+
model: frontmatter.model,
|
|
154
|
+
thinking: frontmatter.thinking,
|
|
155
|
+
systemPrompt: body,
|
|
156
|
+
source,
|
|
157
|
+
filePath,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
103
161
|
export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryResult {
|
|
104
162
|
const userDir = path.join(getAgentDir(), 'agents');
|
|
105
163
|
const projectAgentsDir = findNearestProjectAgentsDir(cwd);
|
|
@@ -128,6 +186,34 @@ export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryRe
|
|
|
128
186
|
return { agents: Array.from(agentMap.values()), projectAgentsDir };
|
|
129
187
|
}
|
|
130
188
|
|
|
189
|
+
/**
|
|
190
|
+
* Discover agents with package-resolved paths merged in.
|
|
191
|
+
* Package-resolved agents (from pi.agents in installed packages) are loaded
|
|
192
|
+
* at lowest priority, then overridden by bundled, user, and project agents.
|
|
193
|
+
*
|
|
194
|
+
* Falls back to discoverAgents() when resolvedPaths is not provided or
|
|
195
|
+
* does not contain the agents field (upstream pi compatibility).
|
|
196
|
+
*/
|
|
197
|
+
export function discoverAgentsWithPackages(
|
|
198
|
+
cwd: string,
|
|
199
|
+
scope: AgentScope,
|
|
200
|
+
resolvedPaths?: ResolvedPaths,
|
|
201
|
+
): AgentDiscoveryResult {
|
|
202
|
+
const base = discoverAgents(cwd, scope);
|
|
203
|
+
|
|
204
|
+
if (!resolvedPaths) return base;
|
|
205
|
+
|
|
206
|
+
const packageAgents = loadAgentsFromResolvedPaths(resolvedPaths);
|
|
207
|
+
if (packageAgents.length === 0) return base;
|
|
208
|
+
|
|
209
|
+
// Package agents are lowest priority: existing agents override by name
|
|
210
|
+
const agentMap = new Map<string, AgentConfig>();
|
|
211
|
+
for (const agent of packageAgents) agentMap.set(agent.name, agent);
|
|
212
|
+
for (const agent of base.agents) agentMap.set(agent.name, agent);
|
|
213
|
+
|
|
214
|
+
return { agents: Array.from(agentMap.values()), projectAgentsDir: base.projectAgentsDir };
|
|
215
|
+
}
|
|
216
|
+
|
|
131
217
|
export { bundledAgentsDir };
|
|
132
218
|
|
|
133
219
|
export function formatAgentList(
|
|
@@ -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,10 +25,13 @@ 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';
|
|
34
|
+
import type { ResolvedPaths } from '@mariozechner/pi-coding-agent';
|
|
32
35
|
import { Container, Markdown, Spacer, Text } from '@mariozechner/pi-tui';
|
|
33
36
|
import { Type } from '@sinclair/typebox';
|
|
34
37
|
import {
|
|
@@ -37,6 +40,7 @@ import {
|
|
|
37
40
|
type AgentSource,
|
|
38
41
|
bundledAgentsDir,
|
|
39
42
|
discoverAgents,
|
|
43
|
+
discoverAgentsWithPackages,
|
|
40
44
|
} from './agents.js';
|
|
41
45
|
import { formatDelegateUsage, parseDelegateArgs } from './delegate-args.js';
|
|
42
46
|
import { runAgent, runParallel } from './delegate-executor.js';
|
|
@@ -507,6 +511,26 @@ const SubagentParams = Type.Object({
|
|
|
507
511
|
),
|
|
508
512
|
});
|
|
509
513
|
|
|
514
|
+
/**
|
|
515
|
+
* Try to resolve package paths including the agents resource.
|
|
516
|
+
* Returns null if the pi version does not support ResolvedPaths.agents.
|
|
517
|
+
*/
|
|
518
|
+
async function tryResolvePackagePaths(cwd: string): Promise<ResolvedPaths | undefined> {
|
|
519
|
+
try {
|
|
520
|
+
const agentDir = getAgentDir();
|
|
521
|
+
const settingsManager = SettingsManager.create(cwd, agentDir);
|
|
522
|
+
const packageManager = new DefaultPackageManager({ cwd, agentDir, settingsManager });
|
|
523
|
+
const resolved = await packageManager.resolve();
|
|
524
|
+
// Check if this pi version has agents support
|
|
525
|
+
if ('agents' in resolved && Array.isArray((resolved as Record<string, unknown>).agents)) {
|
|
526
|
+
return resolved;
|
|
527
|
+
}
|
|
528
|
+
} catch {
|
|
529
|
+
// Incompatible pi version or missing API
|
|
530
|
+
}
|
|
531
|
+
return undefined;
|
|
532
|
+
}
|
|
533
|
+
|
|
510
534
|
export default function (pi: ExtensionAPI) {
|
|
511
535
|
pi.registerTool({
|
|
512
536
|
name: 'subagent',
|
|
@@ -521,7 +545,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
521
545
|
|
|
522
546
|
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
523
547
|
const agentScope: AgentScope = params.agentScope ?? 'user';
|
|
524
|
-
const
|
|
548
|
+
const resolvedPaths = await tryResolvePackagePaths(ctx.cwd);
|
|
549
|
+
const discovery = discoverAgentsWithPackages(ctx.cwd, agentScope, resolvedPaths);
|
|
525
550
|
const agents = discovery.agents;
|
|
526
551
|
const confirmProjectAgents = params.confirmProjectAgents ?? true;
|
|
527
552
|
|
|
@@ -1142,6 +1167,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1142
1167
|
previousOutput: string,
|
|
1143
1168
|
ctx: ExtensionCommandContext,
|
|
1144
1169
|
agentScope: AgentScope,
|
|
1170
|
+
resolvedPaths?: ResolvedPaths,
|
|
1145
1171
|
): Promise<{ results: AgentResult[]; output: string; aborted: boolean }> {
|
|
1146
1172
|
const task = phase.taskTemplate
|
|
1147
1173
|
.replace(/\{synthesis\}/g, synthesis)
|
|
@@ -1160,11 +1186,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
1160
1186
|
// Streaming updates happen per-message
|
|
1161
1187
|
},
|
|
1162
1188
|
phaseName: phase.name,
|
|
1189
|
+
resolvedPaths,
|
|
1163
1190
|
});
|
|
1164
1191
|
} else {
|
|
1165
1192
|
const result = await runAgent(cwd, phase.agents[0], task, {
|
|
1166
1193
|
agentScope,
|
|
1167
1194
|
phaseName: phase.name,
|
|
1195
|
+
resolvedPaths,
|
|
1168
1196
|
});
|
|
1169
1197
|
results = [result];
|
|
1170
1198
|
}
|
|
@@ -1311,6 +1339,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1311
1339
|
}
|
|
1312
1340
|
|
|
1313
1341
|
const { explicitTask, agentScope, workflowId, confirmProjectAgents } = parsed.options;
|
|
1342
|
+
const resolvedPaths = await tryResolvePackagePaths(ctx.cwd);
|
|
1314
1343
|
|
|
1315
1344
|
// Step 1: Synthesize
|
|
1316
1345
|
const conversation = extractRecentConversation(ctx);
|
|
@@ -1322,7 +1351,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1322
1351
|
const prompt = buildSynthesisPrompt(conversation, explicitTask);
|
|
1323
1352
|
ctx.ui.notify('⏳ Synthesizing task from conversation...', 'info');
|
|
1324
1353
|
|
|
1325
|
-
const synthResult = await runAgent(ctx.cwd, 'planner', prompt, { agentScope });
|
|
1354
|
+
const synthResult = await runAgent(ctx.cwd, 'planner', prompt, { agentScope, resolvedPaths });
|
|
1326
1355
|
const synthesis = getFinalText(synthResult);
|
|
1327
1356
|
|
|
1328
1357
|
if (!synthesis.trim()) {
|
|
@@ -1351,7 +1380,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1351
1380
|
}
|
|
1352
1381
|
|
|
1353
1382
|
if ((agentScope === 'project' || agentScope === 'both') && confirmProjectAgents && ctx.hasUI) {
|
|
1354
|
-
const discovery =
|
|
1383
|
+
const discovery = discoverAgentsWithPackages(ctx.cwd, agentScope, resolvedPaths);
|
|
1355
1384
|
const requestedProjectAgents = workflow.phases
|
|
1356
1385
|
.flatMap((phase) => phase.agents)
|
|
1357
1386
|
.map((name) => discovery.agents.find((agent) => agent.name === name))
|
|
@@ -1391,6 +1420,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1391
1420
|
previousOutput,
|
|
1392
1421
|
ctx,
|
|
1393
1422
|
agentScope,
|
|
1423
|
+
resolvedPaths,
|
|
1394
1424
|
);
|
|
1395
1425
|
|
|
1396
1426
|
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.5.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": {
|