pi-fast-subagent 0.5.1 → 0.6.1
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 +50 -0
- package/agents/scout.md +1 -0
- package/agents.ts +41 -4
- package/index.ts +40 -15
- package/package.json +1 -1
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
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
-
//
|
|
268
|
-
|
|
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
|
-
|
|
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");
|
|
@@ -877,7 +891,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
877
891
|
try { return wrapTextWithAnsi(text, w); } catch { return [truncateToWidth(text, w, "...")]; }
|
|
878
892
|
}
|
|
879
893
|
|
|
880
|
-
const cache: {
|
|
894
|
+
const cache: {
|
|
895
|
+
width?: number;
|
|
896
|
+
promptLines?: string[];
|
|
897
|
+
promptSkipped?: number;
|
|
898
|
+
responseLines?: string[];
|
|
899
|
+
skipped?: number;
|
|
900
|
+
} = {};
|
|
881
901
|
|
|
882
902
|
return {
|
|
883
903
|
invalidate() { cache.width = undefined; },
|
|
@@ -888,15 +908,19 @@ export default function (pi: ExtensionAPI) {
|
|
|
888
908
|
// ── Prompt ────────────────────────────────────────────────────
|
|
889
909
|
if (details.task) {
|
|
890
910
|
out.push("Prompt:");
|
|
891
|
-
const taskLines = details.task.split("\n");
|
|
892
911
|
if (expanded) {
|
|
893
|
-
for (const line of
|
|
912
|
+
for (const line of details.task.split("\n")) {
|
|
894
913
|
for (const w of wrapLine(indent + line, width)) out.push(w);
|
|
895
914
|
}
|
|
896
915
|
} else {
|
|
897
|
-
//
|
|
898
|
-
const
|
|
899
|
-
|
|
916
|
+
// Up to 8 visual lines in collapsed mode
|
|
917
|
+
const PROMPT_PREVIEW_LINES = 8;
|
|
918
|
+
if (cache.width !== width || cache.promptLines === undefined) {
|
|
919
|
+
const preview = truncateToVisualLines(details.task, PROMPT_PREVIEW_LINES, width - indent.length);
|
|
920
|
+
cache.promptLines = preview.visualLines.map((l) => truncateToWidth(indent + l, width, "..."));
|
|
921
|
+
cache.promptSkipped = preview.skippedCount;
|
|
922
|
+
}
|
|
923
|
+
out.push(...cache.promptLines);
|
|
900
924
|
}
|
|
901
925
|
}
|
|
902
926
|
|
|
@@ -932,8 +956,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
932
956
|
|
|
933
957
|
// ── Status ───────────────────────────────────────────────
|
|
934
958
|
const status = statusLine();
|
|
935
|
-
const
|
|
936
|
-
|
|
959
|
+
const totalSkipped = (cache.skipped ?? 0) + (cache.promptSkipped ?? 0);
|
|
960
|
+
const expandHint = !expanded && totalSkipped > 0
|
|
961
|
+
? keyHint("app.tools.expand", `expand · ${totalSkipped} lines hidden`)
|
|
937
962
|
: !expanded && toolCalls.some((t) => t.result !== undefined)
|
|
938
963
|
? keyHint("app.tools.expand", "expand for tool outputs")
|
|
939
964
|
: "";
|
|
@@ -984,7 +1009,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
984
1009
|
`## ${agent.name} [${agent.source}]`,
|
|
985
1010
|
`**Description:** ${agent.description}`,
|
|
986
1011
|
agent.model ? `**Model:** ${agent.model}` : null,
|
|
987
|
-
|
|
1012
|
+
`**Tools:** ${formatTools(agent.tools)}`,
|
|
988
1013
|
agent.systemPrompt ? `\n**System prompt:**\n${agent.systemPrompt}` : null,
|
|
989
1014
|
].filter(Boolean).join("\n");
|
|
990
1015
|
return { content: [{ type: "text", text: info }] };
|