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`.
|
- Parent-facing tool: `subagent`.
|
||||||
- Internal child-only tool: `subagent_finalize`.
|
- Internal child-only tool: `subagent_finalize`.
|
||||||
- Never register `subagent_finalize` in the parent context; only when `PI_SUBAGENT_CHILD=1`.
|
- 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:
|
- Subagent sessions are sticky and persisted at:
|
||||||
`~/.pi/subagent-sessions/<cwd-hash>/<agent>_<sessionId>.jsonl`.
|
`~/.pi/subagent-sessions/<cwd-hash>/<agent>_<sessionId>.jsonl`.
|
||||||
- Omitting `sessionId` creates a new UUID-backed session.
|
- 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({
|
pi.registerTool({
|
||||||
name: "subagent",
|
name: "subagent",
|
||||||
label: "Subagent",
|
label: "Subagent",
|
||||||
description:
|
description:
|
||||||
"Delegate a task to a prompt-defined subagent from this extension's prompts/ directory. " +
|
"Delegate a task to a prompt-defined subagent. " +
|
||||||
`Available at startup: ${formatPromptList(discoverPrompts())}`,
|
`Available at startup: ${formatPromptList(prompts)}`,
|
||||||
promptSnippet:
|
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,
|
parameters: SubagentParams,
|
||||||
|
|
||||||
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
||||||
// Validate Agent
|
// Validate Agent
|
||||||
const validation = validateAgent(
|
const validation = validateAgent(
|
||||||
params.agent,
|
params.agent,
|
||||||
discoverPrompts(),
|
prompts,
|
||||||
pi.getActiveTools(),
|
pi.getActiveTools(),
|
||||||
);
|
);
|
||||||
if (!validation.ok) {
|
if (!validation.ok) {
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import * as path from "node:path";
|
import * as path from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
|
import { homedir } from "node:os";
|
||||||
|
|
||||||
const SRC_DIR = path.dirname(fileURLToPath(import.meta.url));
|
const SRC_DIR = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
export const EXTENSION_DIR = path.dirname(SRC_DIR);
|
export const EXTENSION_DIR = path.dirname(SRC_DIR);
|
||||||
export const EXTENSION_ENTRY = path.join(EXTENSION_DIR, "index.ts");
|
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 FINALIZE_TOOL_NAME = "subagent_finalize";
|
||||||
export const MAX_FINALIZE_RETRIES = 2;
|
export const MAX_FINALIZE_RETRIES = 2;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as fs from "node:fs";
|
import * as fs from "node:fs";
|
||||||
import * as path from "node:path";
|
import * as path from "node:path";
|
||||||
import { parseFrontmatter } from "@mariozechner/pi-coding-agent";
|
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 type { PromptConfig } from "./types.ts";
|
||||||
import { FinalizeStatus } from "./types.ts";
|
import { FinalizeStatus } from "./types.ts";
|
||||||
|
|
||||||
@@ -23,17 +23,17 @@ export function parseToolList(value: unknown): string[] {
|
|||||||
return [];
|
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[] {
|
export function discoverPrompts(): PromptConfig[] {
|
||||||
if (!fs.existsSync(PROMPTS_DIR)) return [];
|
if (!fs.existsSync(SUBAGENTS_DIR)) return [];
|
||||||
|
|
||||||
const prompts: PromptConfig[] = [];
|
const prompts: PromptConfig[] = [];
|
||||||
const entries = fs.readdirSync(PROMPTS_DIR, { withFileTypes: true });
|
const entries = fs.readdirSync(SUBAGENTS_DIR, { withFileTypes: true });
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
if (!entry.name.endsWith(".md")) continue;
|
if (!entry.name.endsWith(".md")) continue;
|
||||||
if (!entry.isFile() && !entry.isSymbolicLink()) 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 content = fs.readFileSync(filePath, "utf-8");
|
||||||
const { frontmatter, body } =
|
const { frontmatter, body } =
|
||||||
parseFrontmatter<Record<string, unknown>>(content);
|
parseFrontmatter<Record<string, unknown>>(content);
|
||||||
|
|||||||
Reference in New Issue
Block a user