@johnnywu/pi-subagents 2.1.0 → 2.2.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,3 +1,17 @@
1
+ # [2.2.0](https://github.com/jwu/pi-subagents/compare/v2.1.1...v2.2.0) (2026-08-06)
2
+
3
+
4
+ ### Features
5
+
6
+ * inject runtime tools automatically ([eac6f01](https://github.com/jwu/pi-subagents/commit/eac6f01b4f27e463cba7623fede2602498de421d))
7
+
8
+ ## [2.1.1](https://github.com/jwu/pi-subagents/compare/v2.1.0...v2.1.1) (2026-07-21)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * migrate model registry to Pi 0.80 runtime ([00026d6](https://github.com/jwu/pi-subagents/commit/00026d6e6aaea112896f358c0f86d20f48e73318))
14
+
1
15
  # [2.1.0](https://github.com/jwu/pi-subagents/compare/v2.0.0...v2.1.0) (2026-06-17)
2
16
 
3
17
 
package/README.md CHANGED
@@ -51,14 +51,18 @@ Run the code-reviewer agent on the last three commits
51
51
  By default, sub-agents do not inherit parent conversation history. For tasks that need the current conversation, request a forked session:
52
52
 
53
53
  ```ts
54
- subagent({ agent: "code-reviewer", task: "Review the approach we just discussed", session: "fork" })
54
+ subagent({
55
+ agent: 'code-reviewer',
56
+ task: 'Review the approach we just discussed',
57
+ session: 'fork',
58
+ });
55
59
  ```
56
60
 
57
61
  `session` is optional and accepts:
58
62
 
59
- | Value | Behavior |
60
- |-------|----------|
61
- | `none` | Default. Start a new sub-agent session in the subagents session directory. |
63
+ | Value | Behavior |
64
+ | ------ | ---------------------------------------------------------------------------------------------------- |
65
+ | `none` | Default. Start a new sub-agent session in the subagents session directory. |
62
66
  | `fork` | Fork the current parent session at its active leaf and run the sub-agent with that branched session. |
63
67
 
64
68
  If `fork` is requested but unavailable, pi-subagents falls back to `none` and shows a warning in the tool details/rendering. Fallback happens when the parent session is not persisted, has no current leaf, the forked session file is not materialized, or the call uses a `cwd` different from the parent session cwd.
@@ -84,22 +88,24 @@ The prompt file passed to the child process contains only the agent prompt plus
84
88
 
85
89
  ### What goes into the sub-agent system prompt
86
90
 
87
- | Component | `append` | `replace` | `replace-all` |
88
- |------|----------|-----------|---------------|
89
- | pi default system prompt | ✅ kept | ❌ replaced | ❌ replaced |
90
- | Project context files (AGENTS.md/CLAUDE.md, etc.) | ✅ included | ✅ included | ❌ skipped |
91
- | Agent body (.md file body) | ✅ appended | ✅ becomes the prompt | ✅ becomes the prompt |
92
- | Skills XML block | ✅ appended | ✅ appended | ✅ appended |
93
- | Available tools / Guidelines block | from default prompt | re-injected by `before_agent_start` hook | re-injected by `before_agent_start` hook |
94
- | Available subagents block | injected at agent start | injected at agent start | injected at agent start |
91
+ | Component | `append` | `replace` | `replace-all` |
92
+ | ------------------------------------------------- | ------------------------------------------- | --------------------------------------------- | --------------------------------------------- |
93
+ | pi default system prompt | ✅ kept | ❌ replaced | ❌ replaced |
94
+ | Project context files (AGENTS.md/CLAUDE.md, etc.) | ✅ included | ✅ included | ❌ skipped |
95
+ | Agent body (.md file body) | ✅ appended | ✅ becomes the prompt | ✅ becomes the prompt |
96
+ | Skills XML block | ✅ appended | ✅ appended | ✅ appended |
97
+ | Available tools / Guidelines block | from default prompt | automatically injected after agent body | automatically injected after agent body |
98
+ | Available subagents | supplied by active `subagent` tool metadata | supplied by active `subagent` tool metadata | supplied by active `subagent` tool metadata |
95
99
 
96
- `append` keeps pi's full default prompt (with project context) and adds the agent body at the end.
100
+ `append` keeps pi's default prompt and its built-in tools/guidelines block. The agent body and its skills block are appended before pi project context.
97
101
  `replace` swaps out pi's default prompt for the agent body while keeping pi context files.
98
- `replace-all` is the fully isolated mode: it swaps out pi's default prompt and skips pi context files, then the runtime hook re-injects tool and guideline blocks to preserve tool visibility.
102
+ `replace-all` is the fully isolated mode: it swaps out pi's default prompt and skips pi context files.
103
+
104
+ For `replace` and `replace-all`, pi-subagents automatically injects Pi's runtime `Available tools` and `Guidelines` blocks after the agent body and before its skills block. Agent definitions do not need a placeholder.
99
105
 
100
106
  Breaking change: the old `replace` behavior is now `replace-all`. Existing agents that need to keep skipping AGENTS.md/CLAUDE.md should change `systemPrompt: replace` to `systemPrompt: replace-all`.
101
107
 
102
- The `Available subagents` block is injected by the child process at agent-start time, after `PI_SUBAGENT_ALLOWED` and recursion depth filtering are applied.
108
+ Available subagents are supplied through the active `subagent` tool's `promptGuidelines`, after `PI_SUBAGENT_ALLOWED` and recursion depth filtering are applied.
103
109
 
104
110
  ### Debug a sub-agent prompt
105
111
 
@@ -112,24 +118,24 @@ debug: true
112
118
  ---
113
119
  ```
114
120
 
115
- The child process writes `debug-system-prompt.md` in the project cwd. The file contains the prompt visible during `before_agent_start`, including `systemPrompt` append/replace/replace-all behavior, tools/guidelines, skills, project context files in append and replace modes, and pi-subagents' runtime `Available subagents` block when applicable.
121
+ The child process writes `debug-system-prompt.md` in the project cwd. The file contains the prompt visible during `before_agent_start`, including `systemPrompt` append/replace/replace-all behavior, tools/guidelines, skills, project context files in append and replace modes, and active tool prompt metadata.
116
122
 
117
123
  ## Agent configuration
118
124
 
119
125
  Agents are Markdown files with YAML frontmatter.
120
126
 
121
- | Field | Required | Default | Description |
122
- |-------|----------|---------|-------------|
123
- | `name` | **yes** | — | Unique agent identifier |
124
- | `description` | no | — | Human-readable summary |
125
- | `tools` | no | _none_ | Comma-separated tool whitelist (`read, write, bash, grep`, etc.) |
126
- | `model` | no | parent's model | Provider/model-id (`anthropic/claude-sonnet-4-6`) |
127
- | `thinking` | no | `off` | Reasoning level: `off`, `minimal`, `low`, `medium`, `high`, `xhigh` |
128
- | `systemPrompt` | no | `append` | How the body is applied: `append` (append to pi default system prompt and project context), `replace` (replace pi default prompt while keeping project context), or `replace-all` (replace pi default prompt and skip project context) |
129
- | `skills` | no | _none_ | Comma-separated skill names or simple wildcard patterns (`*`, `obsidian-*`) to load (resolved from project `.agents/skills/`, `.pi/skills/`, global `~/.pi/agent/skills/`, or npm packages) |
130
- | `allowedAgents` | no | _all_ | Comma-separated list of sub-agents this agent may spawn |
131
- | `maxDepth` | no | `10` | Maximum recursion depth (`0` = no sub-agents, `1` = one level, etc.) |
132
- | `debug` | no | `false` | When `true`, export the effective runtime system prompt to `debug-system-prompt.md` |
127
+ | Field | Required | Default | Description |
128
+ | --------------- | -------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
129
+ | `name` | **yes** | — | Unique agent identifier |
130
+ | `description` | no | — | Human-readable summary |
131
+ | `tools` | no | _none_ | Comma-separated tool whitelist (`read, write, bash, grep`, etc.) |
132
+ | `model` | no | parent's model | Provider/model-id (`anthropic/claude-sonnet-4-6`) |
133
+ | `thinking` | no | `off` | Reasoning level: `off`, `minimal`, `low`, `medium`, `high`, `xhigh` |
134
+ | `systemPrompt` | no | `append` | How the body is applied: `append` (append to pi default system prompt and project context), `replace` (replace pi default prompt while keeping project context), or `replace-all` (replace pi default prompt and skip project context). In `replace` modes, the runtime tools/guidelines block is injected automatically. |
135
+ | `skills` | no | _none_ | Comma-separated skill names or simple wildcard patterns (`*`, `obsidian-*`) to load (resolved from project `.agents/skills/`, `.pi/skills/`, global `~/.pi/agent/skills/`, or npm packages) |
136
+ | `allowedAgents` | no | _all_ | Comma-separated list of sub-agents this agent may spawn |
137
+ | `maxDepth` | no | `10` | Maximum recursion depth (`0` = no sub-agents, `1` = one level, etc.) |
138
+ | `debug` | no | `false` | When `true`, export the effective runtime system prompt to `debug-system-prompt.md` |
133
139
 
134
140
  The Markdown body after the frontmatter is the agent's system prompt.
135
141
 
@@ -159,10 +165,10 @@ them to specialist agents. Combine their results and report a summary.
159
165
 
160
166
  Agents are discovered from two locations (project overrides global):
161
167
 
162
- | Scope | Path |
163
- |-------|------|
164
- | Global | `~/.pi/agent/agents/*.md` |
165
- | Project | `.pi/agents/*.md` |
168
+ | Scope | Path |
169
+ | ------- | ------------------------- |
170
+ | Global | `~/.pi/agent/agents/*.md` |
171
+ | Project | `.pi/agents/*.md` |
166
172
 
167
173
  Only `.md` files are scanned. Files are parsed at extension load time; parse errors produce warnings but don't block other agents.
168
174
 
@@ -1,6 +1,7 @@
1
1
  import * as fs from 'node:fs/promises';
2
2
  import * as os from 'node:os';
3
3
  import * as path from 'node:path';
4
+ import { LEGACY_RUNTIME_TOOLS_MARKER } from './subagent-prompt.ts';
4
5
 
5
6
  export type ThinkingLevel = 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh';
6
7
  export type SystemPromptMode = 'replace' | 'replace-all' | 'append';
@@ -126,13 +127,14 @@ function parseAgentFile(content: string, filePath: string, source: AgentSource):
126
127
  debug = data.debug === 'true';
127
128
  }
128
129
 
130
+ const tools = splitCsv(data.tools);
129
131
  const allowedAgents = splitCsv(data.allowedAgents);
130
132
  const skills = splitCsv(data.skills);
131
133
 
132
134
  return {
133
135
  name: data.name,
134
136
  description: data.description,
135
- tools: splitCsv(data.tools),
137
+ tools,
136
138
  skills: skills.length > 0 ? skills : undefined,
137
139
  model: data.model || undefined,
138
140
  thinking,
@@ -140,7 +142,7 @@ function parseAgentFile(content: string, filePath: string, source: AgentSource):
140
142
  allowedAgents: allowedAgents.length > 0 ? allowedAgents : undefined,
141
143
  maxDepth,
142
144
  debug,
143
- prompt: body,
145
+ prompt: body.replaceAll(LEGACY_RUNTIME_TOOLS_MARKER, ''),
144
146
  source,
145
147
  filePath,
146
148
  };
@@ -2,11 +2,8 @@ import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
2
2
  import * as fs from 'node:fs';
3
3
  import * as path from 'node:path';
4
4
  import { loadAgentDefinitions } from './agent-loader.ts';
5
- import { allowedAgentNames, isPastMaxDepth, isSubagentReplaceSystemPrompt } from './env-utils.ts';
6
- import {
7
- appendAvailableSubagentsBlock,
8
- appendAvailableToolsAndGuidelinesBlock,
9
- } from './subagent-prompt.ts';
5
+ import { isSubagentReplaceSystemPrompt } from './env-utils.ts';
6
+ import { injectRuntimeToolsBlock } from './subagent-prompt.ts';
10
7
  import { registerSubagentTool } from './subagent-tool.ts';
11
8
 
12
9
  export default async function (pi: ExtensionAPI) {
@@ -16,31 +13,27 @@ export default async function (pi: ExtensionAPI) {
16
13
  console.warn(`[pi-subagents] skipped ${warning.filePath}: ${warning.message}`);
17
14
  }
18
15
 
19
- const allowed = allowedAgentNames(process.env);
20
- const agents = allowed
21
- ? result.agents.filter((candidate) => allowed.has(candidate.name))
22
- : result.agents;
23
- const agentNames = agents.map((agent) => agent.name);
16
+ const registerScopedSubagentTool = (allowedAgents?: readonly string[]) => {
17
+ registerSubagentTool(pi, { agents: result.agents, allowedAgents });
18
+ };
24
19
 
25
20
  if (isSubagentReplaceSystemPrompt(process.env)) {
26
- pi.on('before_agent_start', (event) => {
27
- return {
28
- systemPrompt: appendAvailableToolsAndGuidelinesBlock(
29
- event.systemPrompt,
30
- event.systemPromptOptions,
31
- ),
32
- };
33
- });
21
+ pi.on('before_agent_start', (event) => ({
22
+ systemPrompt: injectRuntimeToolsBlock(event.systemPrompt, event.systemPromptOptions),
23
+ }));
34
24
  }
35
25
 
36
- if (!isPastMaxDepth(process.env) && agentNames.length > 0) {
37
- pi.on('before_agent_start', (event) => {
38
- if (!event.systemPromptOptions.selectedTools?.includes('subagent')) return;
39
- return {
40
- systemPrompt: appendAvailableSubagentsBlock(event.systemPrompt, agentNames),
41
- };
42
- });
43
- }
26
+ pi.events.on('pi-subagents:configure', (configuration: unknown) => {
27
+ const allowedAgents =
28
+ configuration &&
29
+ typeof configuration === 'object' &&
30
+ Array.isArray((configuration as { allowedAgents?: unknown }).allowedAgents)
31
+ ? (configuration as { allowedAgents: unknown[] }).allowedAgents.filter(
32
+ (name): name is string => typeof name === 'string',
33
+ )
34
+ : undefined;
35
+ registerScopedSubagentTool(allowedAgents);
36
+ });
44
37
 
45
38
  if (process.env.PI_SUBAGENT_DEBUG === 'true') {
46
39
  pi.on('before_agent_start', (_event, ctx) => {
@@ -50,5 +43,5 @@ export default async function (pi: ExtensionAPI) {
50
43
  });
51
44
  }
52
45
 
53
- registerSubagentTool(pi, { agents: result.agents });
46
+ registerScopedSubagentTool();
54
47
  }
@@ -1,10 +1,15 @@
1
- import { AuthStorage, ModelRegistry, withFileMutationQueue } from '@earendil-works/pi-coding-agent';
1
+ import {
2
+ ModelRegistry,
3
+ ModelRuntime,
4
+ withFileMutationQueue,
5
+ } from '@earendil-works/pi-coding-agent';
2
6
  import { spawn } from 'node:child_process';
3
7
  import * as fs from 'node:fs/promises';
4
8
  import * as os from 'node:os';
5
9
  import * as path from 'node:path';
6
10
  import { fileURLToPath } from 'node:url';
7
11
  import type { AgentConfig } from './agent-loader.ts';
12
+ import { AUTO_RUNTIME_TOOLS_MARKER } from './subagent-prompt.ts';
8
13
  import { resolveSkills } from './skill-resolver.ts';
9
14
 
10
15
  export interface AgentUsage {
@@ -376,6 +381,12 @@ export async function buildSubagentSystemPrompt(
376
381
  options: BuildSubagentSystemPromptOptions,
377
382
  ): Promise<BuildSubagentSystemPromptResult> {
378
383
  let prompt = options.agent.prompt;
384
+ if (
385
+ options.agent.systemPromptMode === 'replace' ||
386
+ options.agent.systemPromptMode === 'replace-all'
387
+ ) {
388
+ prompt = `${prompt}\n\n${AUTO_RUNTIME_TOOLS_MARKER}`;
389
+ }
379
390
 
380
391
  const missingSkills: string[] = [];
381
392
  const skippedSkillPackages: string[] = [];
@@ -467,10 +478,11 @@ export async function runSubagent(options: RunSubagentOptions): Promise<AgentRes
467
478
  }
468
479
 
469
480
  const pi = await resolvePi();
470
- const modelRegistry = ModelRegistry.create(
471
- AuthStorage.create(options.agentDir ? path.join(options.agentDir, 'auth.json') : undefined),
472
- options.agentDir ? path.join(options.agentDir, 'models.json') : undefined,
473
- );
481
+ const runtime = await ModelRuntime.create({
482
+ authPath: options.agentDir ? path.join(options.agentDir, 'auth.json') : undefined,
483
+ modelsPath: options.agentDir ? path.join(options.agentDir, 'models.json') : undefined,
484
+ });
485
+ const modelRegistry = new ModelRegistry(runtime);
474
486
  const args = [pi.entryPoint, '--mode', 'json', '-p', '--no-skills', '--no-prompt-templates'];
475
487
 
476
488
  if (options.agent.systemPromptMode === 'replace-all') args.push('--no-context-files');
@@ -1,3 +1,6 @@
1
+ export const AUTO_RUNTIME_TOOLS_MARKER = '<pi-subagents-runtime-tools />';
2
+ export const LEGACY_RUNTIME_TOOLS_MARKER = '<pi-runtime-tools />';
3
+
1
4
  export interface ToolGuidelinePromptOptions {
2
5
  selectedTools?: string[];
3
6
  toolSnippets?: Record<string, string>;
@@ -50,38 +53,15 @@ export function formatAvailableToolsAndGuidelinesBlock(
50
53
  ].join('\n');
51
54
  }
52
55
 
53
- function appendBeforeTrailingRuntimeMetadata(systemPrompt: string, block: string): string {
54
- const marker = '\nCurrent date:';
55
- const index = systemPrompt.lastIndexOf(marker);
56
- if (index === -1) return `${systemPrompt.trimEnd()}\n\n${block}`;
57
-
58
- const before = systemPrompt.slice(0, index).trimEnd();
59
- const after = systemPrompt.slice(index);
60
- return `${before}\n\n${block}${after}`;
61
- }
62
-
63
- export function appendAvailableToolsAndGuidelinesBlock(
56
+ export function injectRuntimeToolsBlock(
64
57
  systemPrompt: string,
65
58
  options: ToolGuidelinePromptOptions,
66
59
  ): string {
67
- const block = formatAvailableToolsAndGuidelinesBlock(options);
68
- if (!block || systemPrompt.includes(block)) {
69
- return systemPrompt;
60
+ const block = formatAvailableToolsAndGuidelinesBlock(options) ?? '';
61
+ if (systemPrompt.includes(AUTO_RUNTIME_TOOLS_MARKER)) {
62
+ return systemPrompt
63
+ .replace(AUTO_RUNTIME_TOOLS_MARKER, block)
64
+ .replaceAll(LEGACY_RUNTIME_TOOLS_MARKER, '');
70
65
  }
71
-
72
- return appendBeforeTrailingRuntimeMetadata(systemPrompt, block);
73
- }
74
-
75
- export function formatAvailableSubagentsBlock(agentNames: string[]): string | undefined {
76
- const names = [...new Set(agentNames.map((name) => name.trim()).filter(Boolean))].sort();
77
- if (names.length === 0) return undefined;
78
-
79
- return ['Available subagents:', ...names.map((name) => `- ${name}`)].join('\n');
80
- }
81
-
82
- export function appendAvailableSubagentsBlock(systemPrompt: string, agentNames: string[]): string {
83
- const block = formatAvailableSubagentsBlock(agentNames);
84
- if (!block || systemPrompt.includes(block)) return systemPrompt;
85
-
86
- return `${systemPrompt.trimEnd()}\n\n${block}`;
66
+ return systemPrompt.replace(LEGACY_RUNTIME_TOOLS_MARKER, block);
87
67
  }
@@ -59,6 +59,8 @@ export interface RegisterSubagentToolOptions {
59
59
  agents: AgentConfig[];
60
60
  run?: typeof runSubagent;
61
61
  env?: RecursionEnv;
62
+ /** Explicit allowlist for the current session. An empty list disables delegation. */
63
+ allowedAgents?: readonly string[];
62
64
  agentDir?: string;
63
65
  }
64
66
 
@@ -300,7 +302,8 @@ export function registerSubagentTool(
300
302
  const env: RecursionEnv = options.env ?? process.env;
301
303
  if (isPastMaxDepth(env)) return;
302
304
 
303
- const allowed = allowedAgentNames(env);
305
+ const allowed =
306
+ options.allowedAgents === undefined ? allowedAgentNames(env) : new Set(options.allowedAgents);
304
307
  const agents = allowed
305
308
  ? options.agents.filter((candidate) => allowed.has(candidate.name))
306
309
  : options.agents;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@johnnywu/pi-subagents",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "Sub-agents extension for pi coding agent.",
5
5
  "homepage": "https://github.com/jwu/pi-subagents#readme",
6
6
  "repository": {
@@ -82,12 +82,13 @@
82
82
  },
83
83
  "peerDependencies": {
84
84
  "@earendil-works/pi-ai": "*",
85
- "@earendil-works/pi-coding-agent": "*",
85
+ "@earendil-works/pi-coding-agent": ">=0.83.0",
86
86
  "@earendil-works/pi-tui": "*",
87
87
  "typebox": "*"
88
88
  },
89
89
  "devDependencies": {
90
90
  "@commitlint/cli": "^20.5.3",
91
+ "@earendil-works/pi-coding-agent": "^0.83.0",
91
92
  "@commitlint/config-conventional": "^20.5.3",
92
93
  "@semantic-release/changelog": "^6.0.3",
93
94
  "@semantic-release/commit-analyzer": "^13.0.1",