@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 { discoverAgents, type AgentScope } from './agents';
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 } = discoverAgents(cwd, options.agentScope ?? 'user');
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 discovery = discoverAgents(ctx.cwd, agentScope);
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 = discoverAgents(ctx.cwd, agentScope);
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.4.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": {