feat: add subagent preset discovery and resolveChildModel change

This commit is contained in:
pi
2026-04-12 11:03:00 +01:00
parent 0438a7b384
commit bcf216518c
4 changed files with 164 additions and 6 deletions

95
src/presets.ts Normal file
View File

@@ -0,0 +1,95 @@
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
import { dirname, join } from "node:path";
import { getAgentDir, parseFrontmatter } from "@mariozechner/pi-coding-agent";
export interface SubagentPreset {
name: string;
description: string;
model?: string;
tools?: string[];
systemPrompt: string;
source: "global" | "project";
filePath: string;
}
export interface SubagentPresetDiscoveryResult {
presets: SubagentPreset[];
projectPresetsDir: string | null;
}
function findNearestProjectPresetsDir(cwd: string): string | null {
let current = cwd;
while (true) {
const candidate = join(current, ".pi", "subagents");
try {
if (statSync(candidate).isDirectory()) return candidate;
} catch (_err) {
// ignore
}
const parent = dirname(current);
if (parent === current) return null;
current = parent;
}
}
function loadPresetDir(dir: string, source: "global" | "project"): SubagentPreset[] {
if (!existsSync(dir)) return [];
const entries = readdirSync(dir, { withFileTypes: true });
const presets: SubagentPreset[] = [];
for (const entry of entries) {
if (!entry.name.endsWith(".md")) continue;
if (!entry.isFile() && !entry.isSymbolicLink()) continue;
const filePath = join(dir, entry.name);
let content: string;
try {
content = readFileSync(filePath, "utf8");
} catch (_err) {
continue;
}
const parsed = parseFrontmatter<Record<string, unknown>>(content);
const frontmatter = parsed.frontmatter ?? {};
const body = parsed.body ?? "";
if (typeof frontmatter.name !== "string") continue;
if (typeof frontmatter.description !== "string") continue;
const tools = typeof frontmatter.tools === "string"
? frontmatter.tools.split(",").map((t) => t.trim()).filter(Boolean)
: undefined;
presets.push({
name: frontmatter.name,
description: frontmatter.description,
model: typeof frontmatter.model === "string" ? frontmatter.model : undefined,
tools: tools && tools.length > 0 ? tools : undefined,
systemPrompt: body.trim(),
source,
filePath,
});
}
return presets;
}
export function discoverSubagentPresets(cwd: string, options: { homeDir?: string } = {}): SubagentPresetDiscoveryResult {
const globalAgentDir = options.homeDir ? join(options.homeDir, ".pi", "agent") : getAgentDir();
const globalDir = join(globalAgentDir, "subagents");
const projectPresetsDir = findNearestProjectPresetsDir(cwd);
const map = new Map<string, SubagentPreset>();
for (const preset of loadPresetDir(globalDir, "global")) {
map.set(preset.name, preset);
}
if (projectPresetsDir) {
for (const preset of loadPresetDir(projectPresetsDir, "project")) {
// project overrides global by name
map.set(preset.name, preset);
}
}
return { presets: Array.from(map.values()), projectPresetsDir };
}