refactor: load subagents from user config (~/.pi/subagents/)
- Discover prompts from ~/.pi/subagents/*.md instead of bundled prompts/ - Only register the subagent tool when at least one subagent exists - Remove directory path from tool description (agents listed dynamically)
This commit is contained in:
@@ -9,6 +9,8 @@ This repo implements the `subagent` pi extension in `index.ts`.
|
||||
- Parent-facing tool: `subagent`.
|
||||
- Internal child-only tool: `subagent_finalize`.
|
||||
- Never register `subagent_finalize` in the parent context; only when `PI_SUBAGENT_CHILD=1`.
|
||||
- Subagents are loaded from `~/.pi/subagents/*.md` (user config only).
|
||||
- The `subagent` tool is only registered when at least one subagent exists.
|
||||
- Subagent sessions are sticky and persisted at:
|
||||
`~/.pi/subagent-sessions/<cwd-hash>/<agent>_<sessionId>.jsonl`.
|
||||
- Omitting `sessionId` creates a new UUID-backed session.
|
||||
|
||||
13
index.ts
13
index.ts
@@ -38,21 +38,26 @@ export default function (pi: ExtensionAPI) {
|
||||
});
|
||||
}
|
||||
|
||||
const prompts = discoverPrompts();
|
||||
|
||||
// Skip Registration - Only register the tool if there's at least one subagent.
|
||||
if (prompts.length === 0) return;
|
||||
|
||||
pi.registerTool({
|
||||
name: "subagent",
|
||||
label: "Subagent",
|
||||
description:
|
||||
"Delegate a task to a prompt-defined subagent from this extension's prompts/ directory. " +
|
||||
`Available at startup: ${formatPromptList(discoverPrompts())}`,
|
||||
"Delegate a task to a prompt-defined subagent. " +
|
||||
`Available at startup: ${formatPromptList(prompts)}`,
|
||||
promptSnippet:
|
||||
"Delegate tasks to subagents by name. Subagent prompts live in prompts/*.md and define approved_tools/denied_tools.",
|
||||
"Delegate tasks to subagents by name. Subagent prompts define approved_tools/denied_tools.",
|
||||
parameters: SubagentParams,
|
||||
|
||||
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
||||
// Validate Agent
|
||||
const validation = validateAgent(
|
||||
params.agent,
|
||||
discoverPrompts(),
|
||||
prompts,
|
||||
pi.getActiveTools(),
|
||||
);
|
||||
if (!validation.ok) {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import * as path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { homedir } from "node:os";
|
||||
|
||||
const SRC_DIR = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
export const EXTENSION_DIR = path.dirname(SRC_DIR);
|
||||
export const EXTENSION_ENTRY = path.join(EXTENSION_DIR, "index.ts");
|
||||
export const PROMPTS_DIR = path.join(EXTENSION_DIR, "prompts");
|
||||
export const SUBAGENTS_DIR = path.join(homedir(), ".pi", "subagents");
|
||||
export const FINALIZE_TOOL_NAME = "subagent_finalize";
|
||||
export const MAX_FINALIZE_RETRIES = 2;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import { parseFrontmatter } from "@mariozechner/pi-coding-agent";
|
||||
import { FINALIZE_TOOL_NAME, PROMPTS_DIR } from "./constants.ts";
|
||||
import { FINALIZE_TOOL_NAME, SUBAGENTS_DIR } from "./constants.ts";
|
||||
import type { PromptConfig } from "./types.ts";
|
||||
import { FinalizeStatus } from "./types.ts";
|
||||
|
||||
@@ -23,17 +23,17 @@ export function parseToolList(value: unknown): string[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Discover Prompts - Load prompt markdown files from this extension's prompts dir.
|
||||
// Discover Prompts - Load prompt markdown files from user config dir.
|
||||
export function discoverPrompts(): PromptConfig[] {
|
||||
if (!fs.existsSync(PROMPTS_DIR)) return [];
|
||||
if (!fs.existsSync(SUBAGENTS_DIR)) return [];
|
||||
|
||||
const prompts: PromptConfig[] = [];
|
||||
const entries = fs.readdirSync(PROMPTS_DIR, { withFileTypes: true });
|
||||
const entries = fs.readdirSync(SUBAGENTS_DIR, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
if (!entry.name.endsWith(".md")) continue;
|
||||
if (!entry.isFile() && !entry.isSymbolicLink()) continue;
|
||||
|
||||
const filePath = path.join(PROMPTS_DIR, entry.name);
|
||||
const filePath = path.join(SUBAGENTS_DIR, entry.name);
|
||||
const content = fs.readFileSync(filePath, "utf-8");
|
||||
const { frontmatter, body } =
|
||||
parseFrontmatter<Record<string, unknown>>(content);
|
||||
|
||||
Reference in New Issue
Block a user