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 type { PromptConfig } from "./types.ts"; import { FinalizeStatus } from "./types.ts"; // Parse Tool List - Frontmatter may use YAML arrays or comma-delimited strings. export function parseToolList(value: unknown): string[] { if (!value) return []; if (Array.isArray(value)) { return value .map(String) .map((tool) => tool.trim()) .filter(Boolean); } if (typeof value === "string") { return value .split(",") .map((tool) => tool.trim()) .filter(Boolean); } return []; } // Discover Prompts - Load prompt markdown files from this extension's prompts dir. export function discoverPrompts(): PromptConfig[] { if (!fs.existsSync(PROMPTS_DIR)) return []; const prompts: PromptConfig[] = []; const entries = fs.readdirSync(PROMPTS_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 content = fs.readFileSync(filePath, "utf-8"); const { frontmatter, body } = parseFrontmatter>(content); if ( typeof frontmatter.name !== "string" || typeof frontmatter.description !== "string" ) { continue; } prompts.push({ name: frontmatter.name, description: frontmatter.description, approvedTools: parseToolList( frontmatter.approved_tools ?? frontmatter.allowed_tools, ), deniedTools: parseToolList(frontmatter.denied_tools), systemPrompt: body.trim(), filePath, }); } return prompts.sort((a, b) => a.name.localeCompare(b.name)); } // Build Finalize Prompt - Child agents must terminate by calling this tool. export function buildSubagentPrompt(agent: PromptConfig): string { const finalizePrompt = [ "You are running as a subagent.", `When the task is complete, call ${FINALIZE_TOOL_NAME} as your final action.`, "Do not provide the final answer as normal assistant text.", `${FINALIZE_TOOL_NAME} requires status ${FinalizeStatus.SUCCESS} with result, or status ${FinalizeStatus.ERROR} with error and optional result.`, ].join("\n"); return [agent.systemPrompt, finalizePrompt].filter(Boolean).join("\n\n"); }