pi-fast-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/README.md CHANGED
@@ -61,6 +61,101 @@ model: anthropic/claude-haiku-4-5
61
61
  You are code exploration specialist. Read relevant files, trace data flow, summarize findings clearly.
62
62
  ```
63
63
 
64
+ ### Agent frontmatter
65
+
66
+ | Field | Required | Description |
67
+ |-------|----------|-------------|
68
+ | `name` | yes | Unique agent identifier used in `subagent({ agent: "..." })` |
69
+ | `description` | yes | One-line description shown in `/fast-subagent:agent` |
70
+ | `model` | no | Model override, format `provider/model-id` (e.g. `anthropic/claude-haiku-4-5`) |
71
+ | `tools` | no | Tool allowlist (see below) |
72
+
73
+ ### `tools:` field
74
+
75
+ Controls which tools the subagent has access to. Subagents inherit parent extensions (web_search, fetch_content, mcp, …) by default.
76
+
77
+ | Value | Behavior |
78
+ |-------|----------|
79
+ | *(omitted)* | Inherit everything — all builtins + all parent extensions (**default**) |
80
+ | `all` | Same as omitted — explicit "everything" |
81
+ | `none` | No tools at all — pure reasoning agent |
82
+ | comma list | Allowlist; extensions auto-load only if any listed tool is non-builtin |
83
+
84
+ Built-in tools: `read`, `bash`, `edit`, `write`, `grep`, `find`, `ls`.
85
+
86
+ Examples:
87
+
88
+ ```md
89
+ ---
90
+ name: writer
91
+ description: Pure-reasoning writing assistant
92
+ tools: none
93
+ ---
94
+ ```
95
+
96
+ ```md
97
+ ---
98
+ name: coder
99
+ description: Lean code-editing agent, no extensions
100
+ tools: read, bash, edit, write, grep, find, ls
101
+ ---
102
+ ```
103
+
104
+ ```md
105
+ ---
106
+ name: researcher
107
+ description: Web research agent
108
+ tools: read, write, web_search, fetch_content
109
+ ---
110
+ ```
111
+
112
+ > **Performance note:** inheriting all extensions adds startup cost (extension init) and token cost (larger system prompt). For tight, focused agents, list tools explicitly — extensions are only loaded when the allowlist actually needs them.
113
+
114
+ ## Background Agents
115
+
116
+ Every foreground subagent can be moved to background at any time. Background jobs run concurrently while you continue chatting. When a job finishes, pi automatically posts the result as a follow-up message.
117
+
118
+ ### Status bar
119
+
120
+ While a foreground subagent is running, the pi status bar shows:
121
+ ```
122
+ {agent-name} running · Ctrl+Shift+B to move to background
123
+ ```
124
+
125
+ While background jobs are running:
126
+ ```
127
+ ⧗ N bg agents
128
+ ```
129
+
130
+ ### Moving to background
131
+
132
+ **Keyboard shortcut (while subagent is running):**
133
+ ```
134
+ Ctrl+Shift+B
135
+ ```
136
+
137
+ **Slash command:**
138
+ ```
139
+ /fast-subagent:bg fg_ab12cd34
140
+ ```
141
+
142
+ **Via tool call:**
143
+ ```js
144
+ subagent({ action: "detach", jobId: "fg_ab12cd34" })
145
+ ```
146
+
147
+ ### Auto-completion announcement
148
+
149
+ When a background job finishes, pi injects a follow-up message automatically:
150
+ ```
151
+ Background subagent ✓: sa_ab12cd34 (scout, 4.2s)
152
+ > Explore src and summarize architecture
153
+
154
+ <result output>
155
+ ```
156
+
157
+ Failed jobs are announced the same way with ✗ and the error message.
158
+
64
159
  ## Slash Commands
65
160
 
66
161
  ### `/fast-subagent:agent`
@@ -81,13 +176,13 @@ Tab-completion is supported for agent names.
81
176
 
82
177
  ### `/fast-subagent:bg`
83
178
 
84
- Detach running foreground subagent to background:
179
+ Detach a running foreground subagent to background. Each foreground job has a `fg_` prefixed ID shown in the status bar.
85
180
 
86
181
  ```
87
182
  /fast-subagent:bg fg_ab12cd34
88
183
  ```
89
184
 
90
- Omit job id to list active foreground jobs:
185
+ Omit ID to list all active foreground jobs:
91
186
 
92
187
  ```
93
188
  /fast-subagent:bg
@@ -95,13 +190,13 @@ Omit job id to list active foreground jobs:
95
190
 
96
191
  ### `/fast-subagent:bg-status`
97
192
 
98
- Show active background subagents in selector UI. Arrow keys move selection. Enter shows full details for selected job.
193
+ Open selector UI showing all active background jobs. Arrow keys to navigate, Enter to view full details.
99
194
 
100
195
  ```
101
196
  /fast-subagent:bg-status
102
197
  ```
103
198
 
104
- Show details for specific background job:
199
+ Skip the selector — show details for a specific job directly:
105
200
 
106
201
  ```
107
202
  /fast-subagent:bg-status sa_ab12cd34
@@ -109,52 +204,58 @@ Show details for specific background job:
109
204
 
110
205
  ### `/fast-subagent:bg-cancel`
111
206
 
112
- Cancel running background subagent. Omit job id to open selector UI, then choose job with arrow keys.
207
+ Open selector UI to choose a running job to cancel:
113
208
 
114
209
  ```
115
210
  /fast-subagent:bg-cancel
116
211
  ```
117
212
 
118
- Cancel specific background job directly:
213
+ Cancel a specific job directly:
119
214
 
120
215
  ```
121
216
  /fast-subagent:bg-cancel sa_ab12cd34
122
217
  ```
123
218
 
124
- ## Usage
219
+ ## Keyboard Shortcuts
125
220
 
126
- ### List agents
221
+ | Shortcut | Action |
222
+ |---|---|
223
+ | `Ctrl+Shift+B` | Move active foreground subagent to background |
127
224
 
128
- ```js
129
- subagent({ action: "list" })
130
- ```
225
+ ## Roadmap
131
226
 
132
- ### Single
227
+ Goal: keep this extension **small and focused** — aligned with pi's philosophy of minimal, composable tooling. No feature creep. Every addition must earn its place.
133
228
 
134
- ```js
135
- subagent({
136
- agent: "scout",
137
- task: "Explore src and summarize architecture"
138
- })
139
- ```
229
+ - **UI/UX polish** — improve visibility of running subagents: clearer status lines, better progress feedback, agent name + task always visible during execution
230
+ - ~~**Background subagents**~~ ✔ shipped in v0.4.0 — fire-and-forget with `background: true`, poll/cancel/detach support
231
+
232
+ ## Notes
233
+
234
+ - Async/background isolation not supported in-process
235
+ - Git worktree isolation not supported
236
+ - Nested subagent depth limited to 2 by default
237
+
238
+ ## Tool Reference
140
239
 
141
- ### General-purpose built-in agent
240
+ > These are `subagent` tool call examples used by the LLM internally. Not typically invoked directly by users.
241
+
242
+ ### List / discover agents
142
243
 
143
244
  ```js
144
- subagent({
145
- agent: "general",
146
- task: "Summarize open TODOs and propose next step"
147
- })
245
+ // List all agents
246
+ subagent({ action: "list" })
247
+
248
+ // Get details for a specific agent
249
+ subagent({ action: "get", agent: "scout" })
250
+
251
+ // Scope filter: "user" | "project" | "both" (default)
252
+ subagent({ action: "list", agentScope: "project" })
148
253
  ```
149
254
 
150
- ### Override model
255
+ ### Single
151
256
 
152
257
  ```js
153
- subagent({
154
- agent: "scout",
155
- task: "Explore src and summarize architecture",
156
- model: "anthropic/claude-haiku-4-5"
157
- })
258
+ subagent({ agent: "scout", task: "Explore src and summarize architecture" })
158
259
  ```
159
260
 
160
261
  ### Parallel
@@ -165,42 +266,32 @@ subagent({
165
266
  { agent: "scout", task: "Map auth flow" },
166
267
  { agent: "scout", task: "Map navigation" }
167
268
  ],
168
- concurrency: 2
269
+ concurrency: 2 // default: 4
169
270
  })
271
+
272
+ // Repeat one task N times
273
+ subagent({ tasks: [{ agent: "scout", task: "Explore src", count: 3 }] })
170
274
  ```
171
275
 
172
- ### Background (fire-and-forget)
276
+ ### Background
173
277
 
174
278
  ```js
175
- // Dispatch — returns job ID immediately
279
+ // Fire-and-forget — returns job ID immediately
176
280
  subagent({ agent: "scout", task: "Explore src", background: true })
177
281
  // → { jobId: "sa_ab12cd34", status: "running" }
178
282
 
179
- // Poll check result / progress
180
- subagent({ action: "poll", jobId: "sa_ab12cd34" })
181
-
182
- // Cancel
183
- subagent({ action: "cancel", jobId: "sa_ab12cd34" })
184
-
185
- // List all background jobs
186
- subagent({ action: "status" })
187
-
188
- // Detach a running foreground job to background
189
- subagent({ action: "detach", jobId: "fg_ab12cd34" })
283
+ subagent({ action: "poll", jobId: "sa_ab12cd34" }) // check progress
284
+ subagent({ action: "cancel", jobId: "sa_ab12cd34" }) // abort
285
+ subagent({ action: "status" }) // list all bg jobs
286
+ subagent({ action: "detach", jobId: "fg_ab12cd34" }) // move fg → bg
190
287
  ```
191
288
 
192
- ## Roadmap
289
+ ### Options
193
290
 
194
- Goal: keep this extension **small and focused** — aligned with pi's philosophy of minimal, composable tooling. No feature creep. Every addition must earn its place.
195
-
196
- - **UI/UX polish** — improve visibility of running subagents: clearer status lines, better progress feedback, agent name + task always visible during execution
197
- - **Background agents** — ala claude code
198
-
199
- ## Notes
200
-
201
- - Async/background isolation not supported in-process
202
- - Git worktree isolation not supported
203
- - Nested subagent depth limited to 2 by default
291
+ ```js
292
+ subagent({ agent: "scout", task: "...", model: "anthropic/claude-haiku-4-5" })
293
+ subagent({ agent: "scout", task: "...", cwd: "/path/to/project" })
294
+ ```
204
295
 
205
296
  ## Publish
206
297
 
package/agents/scout.md CHANGED
@@ -2,6 +2,7 @@
2
2
  name: scout
3
3
  description: Explores codebases, maps structure, traces data flow, answers how things work across many files
4
4
  model: anthropic/claude-haiku-4-5
5
+ tools: read, bash, edit, write, grep, find, ls
5
6
  ---
6
7
 
7
8
  You are code exploration specialist.
package/agents.ts CHANGED
@@ -8,16 +8,54 @@ import * as path from "node:path";
8
8
  import { fileURLToPath } from "node:url";
9
9
  import { getAgentDir, parseFrontmatter } from "@mariozechner/pi-coding-agent";
10
10
 
11
+ /**
12
+ * tools frontmatter semantics:
13
+ * unset → inherit everything (builtins + extensions) — same as `all`
14
+ * `all` → all builtins + all extension tools (web_search, fetch_content, mcp, …)
15
+ * `none` → no tools at all
16
+ * comma list → allowlist; extensions auto-loaded if any listed tool is non-builtin
17
+ * for lean "builtins-only" mode, list them explicitly:
18
+ * tools: read, bash, edit, write, grep, find, ls
19
+ *
20
+ * Represented as:
21
+ * "all" → everything (default when frontmatter omits `tools`)
22
+ * "none" → no tools
23
+ * string[] → allowlist
24
+ */
25
+ export type AgentTools = "all" | "none" | string[];
26
+
27
+ export const BUILTIN_TOOL_NAMES = ["read", "bash", "edit", "write", "grep", "find", "ls"] as const;
28
+
11
29
  export interface AgentConfig {
12
30
  name: string;
13
31
  description: string;
14
32
  model?: string;
15
- tools?: string[];
33
+ tools: AgentTools;
16
34
  systemPrompt: string;
17
35
  source: "user" | "project";
18
36
  filePath: string;
19
37
  }
20
38
 
39
+ const BUILTIN_TOOLS = new Set<string>(BUILTIN_TOOL_NAMES);
40
+
41
+ export function agentNeedsExtensions(tools: AgentTools): boolean {
42
+ if (tools === "all") return true;
43
+ if (tools === "none") return false;
44
+ return tools.some((t) => !BUILTIN_TOOLS.has(t));
45
+ }
46
+
47
+ // Default: everything. Agents list specific tools for lean / restricted mode.
48
+ function parseToolsField(raw: unknown): AgentTools {
49
+ if (raw === undefined || raw === null) return "all";
50
+ const str = String(raw).trim();
51
+ if (!str) return "all";
52
+ const lower = str.toLowerCase();
53
+ if (lower === "all") return "all";
54
+ if (lower === "none") return "none";
55
+ const list = str.split(",").map((t) => t.trim()).filter(Boolean);
56
+ return list.length ? list : "all";
57
+ }
58
+
21
59
  function loadAgentsFromDir(dir: string, source: "user" | "project"): AgentConfig[] {
22
60
  if (!fs.existsSync(dir)) return [];
23
61
  let entries: fs.Dirent[];
@@ -36,13 +74,12 @@ function loadAgentsFromDir(dir: string, source: "user" | "project"): AgentConfig
36
74
  const content = fs.readFileSync(filePath, "utf-8");
37
75
  const { frontmatter, body } = parseFrontmatter<Record<string, string>>(content);
38
76
  if (!frontmatter?.name || !frontmatter?.description) continue;
39
- const rawTools = frontmatter.tools;
40
- const tools = rawTools?.split(",").map((t: string) => t.trim()).filter(Boolean);
77
+ const tools = parseToolsField(frontmatter.tools);
41
78
  agents.push({
42
79
  name: frontmatter.name,
43
80
  description: frontmatter.description,
44
81
  model: frontmatter.model,
45
- tools: tools?.length ? tools : undefined,
82
+ tools,
46
83
  systemPrompt: body.trim(),
47
84
  source,
48
85
  filePath,
package/index.ts CHANGED
@@ -32,7 +32,13 @@ import {
32
32
 
33
33
  type DefaultResourceLoaderOptions = ConstructorParameters<typeof DefaultResourceLoader>[0];
34
34
  import { Type } from "@sinclair/typebox";
35
- import { type AgentConfig, discoverAgents } from "./agents.js";
35
+ import { type AgentConfig, agentNeedsExtensions, discoverAgents } from "./agents.js";
36
+
37
+ function formatTools(tools: AgentConfig["tools"]): string {
38
+ if (tools === "all") return "all";
39
+ if (tools === "none") return "none";
40
+ return tools.join(", ");
41
+ }
36
42
 
37
43
  // ─── Tool arg summarizer (compact one-liner per tool call) ─────────────────────
38
44
 
@@ -228,11 +234,14 @@ async function runAgent(
228
234
  const { authStorage, modelRegistry } = getAuth();
229
235
  const agentDir = getAgentDir();
230
236
 
231
- // Build resource loader — no extensions/context files to keep subagent lean
237
+ // Build resource loader — no extensions/context files to keep subagent lean.
238
+ // Agents can opt in to extensions via `extensions: true` in frontmatter, which
239
+ // makes tools like web_search / fetch_content / mcp / etc. available to the
240
+ // subagent (subject to the optional `tools:` allowlist below).
232
241
  const loaderOptions: DefaultResourceLoaderOptions = {
233
242
  cwd,
234
243
  agentDir,
235
- noExtensions: true,
244
+ noExtensions: !agentNeedsExtensions(agent.tools),
236
245
  noContextFiles: true,
237
246
  noSkills: true,
238
247
  };
@@ -264,8 +273,13 @@ async function runAgent(
264
273
  }
265
274
  }
266
275
 
267
- // Restrict tools if agent specifies them
268
- if (agent.tools && agent.tools.length > 0) {
276
+ // Apply tools allowlist.
277
+ // "all" → no restriction (everything registered stays active)
278
+ // "none" → disable every tool
279
+ // string[] → explicit allowlist
280
+ if (agent.tools === "none") {
281
+ session.setActiveToolsByName([]);
282
+ } else if (Array.isArray(agent.tools) && agent.tools.length > 0) {
269
283
  session.setActiveToolsByName(agent.tools);
270
284
  }
271
285
 
@@ -598,7 +612,7 @@ export default function (pi: ExtensionAPI) {
598
612
  `File: ${agent.filePath}`,
599
613
  `Description: ${agent.description}`,
600
614
  agent.model ? `Model: ${agent.model}` : "",
601
- agent.tools ? `Tools: ${agent.tools.join(", ")}` : "",
615
+ `Tools: ${formatTools(agent.tools)}`,
602
616
  agent.systemPrompt ? `\nSystem prompt:\n${agent.systemPrompt}` : "",
603
617
  ].filter(Boolean).join("\n");
604
618
  ctx.ui.notify(lines, "info");
@@ -984,7 +998,7 @@ export default function (pi: ExtensionAPI) {
984
998
  `## ${agent.name} [${agent.source}]`,
985
999
  `**Description:** ${agent.description}`,
986
1000
  agent.model ? `**Model:** ${agent.model}` : null,
987
- agent.tools ? `**Tools:** ${agent.tools.join(", ")}` : null,
1001
+ `**Tools:** ${formatTools(agent.tools)}`,
988
1002
  agent.systemPrompt ? `\n**System prompt:**\n${agent.systemPrompt}` : null,
989
1003
  ].filter(Boolean).join("\n");
990
1004
  return { content: [{ type: "text", text: info }] };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-fast-subagent",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "In-process subagent delegation for pi with single, parallel, and background modes",
5
5
  "type": "module",
6
6
  "keywords": [