Files
pi-subagents/index.ts
Evan Reichard 5c5cdb3aec refactor(subagent): split responsibilities and isolate child prompt
- Extract validateAgent, toToolResult, runAgentWithRetries so index.ts
  is wiring only; orchestration, validation, and result shaping each own
  their concern.
- Separate runner internals: createRunState, handleEvent (pure event
  reducer), spawnPi (process lifecycle), runOnce (single attempt).
- Track attempt/maxAttempts on SubagentStatus; surface "try x/y" in the
  UI without overwriting the user-facing task with the retry preamble.
- Replace pi's default system prompt (--system-prompt) instead of
  appending, so the subagent .md body is authoritative.
- Document prompt-replacement and retry/cache semantics in AGENTS.md.
2026-05-12 15:02:49 -04:00

89 lines
2.9 KiB
TypeScript

// Subagent Extension - Registers a tool for delegating work to prompt-defined
// subagents with constrained tool permissions.
import { randomUUID } from "node:crypto";
import * as path from "node:path";
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
import { Type } from "typebox";
import { FINALIZE_TOOL_NAME } from "./src/constants.ts";
import { formatPromptList, toToolResult } from "./src/format.ts";
import { discoverPrompts } from "./src/prompts.ts";
import { renderSubagentCall, renderSubagentResult } from "./src/render.ts";
import { runAgentWithRetries } from "./src/runner.ts";
import { SubagentParams } from "./src/tools.ts";
import { FinalizeStatus } from "./src/types.ts";
import { validateAgent } from "./src/validate.ts";
export default function (pi: ExtensionAPI) {
if (process.env.PI_SUBAGENT_CHILD === "1") {
pi.registerTool({
name: FINALIZE_TOOL_NAME,
label: "Subagent Finalize",
description:
"Internal subagent-only tool. Call this as your final action when delegated work is complete.",
promptSnippet:
"Call subagent_finalize as your final action when subagent work is complete.",
parameters: Type.Object({
status: Type.Enum(FinalizeStatus),
result: Type.Optional(Type.String()),
error: Type.Optional(Type.String()),
}),
async execute(_toolCallId, params) {
return {
content: [{ type: "text", text: "Subagent finalized." }],
details: params,
terminate: true,
};
},
});
}
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())}`,
promptSnippet:
"Delegate tasks to subagents by name. Subagent prompts live in prompts/*.md and define approved_tools/denied_tools.",
parameters: SubagentParams,
async execute(_toolCallId, params, signal, onUpdate, ctx) {
// Validate Agent
const validation = validateAgent(
params.agent,
discoverPrompts(),
pi.getActiveTools(),
);
if (!validation.ok) {
return {
content: [{ type: "text", text: validation.text }],
details: validation.details,
isError: true,
};
}
// Run Subagent
const result = await runAgentWithRetries({
cwd: path.resolve(ctx.cwd, params.cwd ?? "."),
agent: validation.agent,
task: params.task,
tools: validation.tools,
sessionId: params.sessionId ?? randomUUID(),
signal,
onUpdate,
});
return toToolResult(result);
},
renderCall(args, theme) {
return renderSubagentCall(args, theme);
},
renderResult(result, options, theme) {
return renderSubagentResult(result, options, theme);
},
});
}