pi-fast-subagent 0.5.1 → 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,56 @@ 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
+
64
114
  ## Background Agents
65
115
 
66
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.
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.1",
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": [