Add 5 pi extensions: pi-subagents, pi-crew, rpiv-pi, pi-interactive-shell, pi-intercom

This commit is contained in:
2026-05-08 15:59:25 +10:00
parent d0d1d9b045
commit 31b4110c87
457 changed files with 85157 additions and 0 deletions

View File

@@ -0,0 +1,30 @@
export type ResourceSource = "builtin" | "user" | "project" | "git";
export interface RoutingMetadata {
triggers?: string[];
useWhen?: string[];
avoidWhen?: string[];
cost?: "free" | "cheap" | "expensive";
category?: string;
}
export interface AgentConfig {
name: string;
description: string;
source: ResourceSource;
filePath: string;
systemPrompt: string;
model?: string;
fallbackModels?: string[];
thinking?: string;
tools?: string[];
extensions?: string[];
skills?: string[];
systemPromptMode?: "replace" | "append";
inheritProjectContext?: boolean;
inheritSkills?: boolean;
routing?: RoutingMetadata;
memory?: "user" | "project" | "local";
disabled?: boolean;
override?: { source: "config"; path: string };
}

View File

@@ -0,0 +1,34 @@
import type { AgentConfig } from "./agent-config.ts";
function line(key: string, value: string | boolean | string[] | undefined): string | undefined {
if (value === undefined) return undefined;
if (Array.isArray(value)) return `${key}: ${value.join(", ")}`;
return `${key}: ${String(value)}`;
}
export function serializeAgent(agent: AgentConfig): string {
const lines = [
"---",
`name: ${agent.name}`,
`description: ${agent.description}`,
line("model", agent.model),
line("fallbackModels", agent.fallbackModels),
line("thinking", agent.thinking),
line("tools", agent.tools),
agent.extensions !== undefined ? line("extensions", agent.extensions) ?? "extensions:" : undefined,
line("skills", agent.skills),
line("systemPromptMode", agent.systemPromptMode),
line("inheritProjectContext", agent.inheritProjectContext),
line("inheritSkills", agent.inheritSkills),
line("triggers", agent.routing?.triggers),
line("useWhen", agent.routing?.useWhen),
line("avoidWhen", agent.routing?.avoidWhen),
line("cost", agent.routing?.cost),
line("category", agent.routing?.category),
"---",
"",
agent.systemPrompt.trim(),
"",
].filter((entry): entry is string => entry !== undefined);
return lines.join("\n");
}

View File

@@ -0,0 +1,104 @@
import * as fs from "node:fs";
import * as path from "node:path";
import type { AgentConfig, ResourceSource } from "./agent-config.ts";
import { loadConfig, type LoadedPiTeamsConfig } from "../config/config.ts";
import { parseCsv, parseFrontmatter } from "../utils/frontmatter.ts";
import { packageRoot, projectCrewRoot, userPiRoot } from "../utils/paths.ts";
export interface AgentDiscoveryResult {
builtin: AgentConfig[];
user: AgentConfig[];
project: AgentConfig[];
}
function parseCost(value: string | undefined): "free" | "cheap" | "expensive" | undefined {
return value === "free" || value === "cheap" || value === "expensive" ? value : undefined;
}
function parseMemory(value: string | undefined): "user" | "project" | "local" | undefined {
return value === "user" || value === "project" || value === "local" ? value : undefined;
}
function parseAgentFile(filePath: string, source: ResourceSource): AgentConfig | undefined {
try {
const content = fs.readFileSync(filePath, "utf-8");
const { frontmatter, body } = parseFrontmatter(content);
const name = frontmatter.name?.trim() || path.basename(filePath, path.extname(filePath));
const description = frontmatter.description?.trim() || "No description provided.";
const triggers = parseCsv(frontmatter.triggers ?? frontmatter.trigger);
const useWhen = parseCsv(frontmatter.useWhen);
const avoidWhen = parseCsv(frontmatter.avoidWhen);
const cost = parseCost(frontmatter.cost);
const category = frontmatter.category?.trim() || undefined;
return {
name,
description,
source,
filePath,
systemPrompt: body.trim(),
model: frontmatter.model === "false" ? undefined : frontmatter.model || undefined,
fallbackModels: parseCsv(frontmatter.fallbackModels),
thinking: frontmatter.thinking === "false" ? undefined : frontmatter.thinking || undefined,
tools: parseCsv(frontmatter.tools),
extensions: frontmatter.extensions === "" ? [] : parseCsv(frontmatter.extensions),
skills: parseCsv(frontmatter.skills ?? frontmatter.skill),
systemPromptMode: frontmatter.systemPromptMode === "append" ? "append" : "replace",
inheritProjectContext: frontmatter.inheritProjectContext as unknown === true || frontmatter.inheritProjectContext === "true",
inheritSkills: frontmatter.inheritSkills as unknown === true || frontmatter.inheritSkills === "true",
memory: parseMemory(frontmatter.memory),
disabled: frontmatter.disabled as unknown === true || frontmatter.disabled === "true" || frontmatter.enabled as unknown === false || frontmatter.enabled === "false",
routing: triggers || useWhen || avoidWhen || cost || category ? { triggers, useWhen, avoidWhen, cost, category } : undefined,
};
} catch {
return undefined;
}
}
function readAgentDir(dir: string, source: ResourceSource): AgentConfig[] {
if (!fs.existsSync(dir)) return [];
return fs.readdirSync(dir)
.filter((entry) => entry.endsWith(".md") && !entry.endsWith(".team.md") && !entry.endsWith(".workflow.md"))
.map((entry) => parseAgentFile(path.join(dir, entry), source))
.filter((agent): agent is AgentConfig => agent !== undefined)
.sort((a, b) => a.name.localeCompare(b.name));
}
function applyAgentOverrides(agents: AgentConfig[], cwd: string, loadedConfig?: LoadedPiTeamsConfig): AgentConfig[] {
const loaded = loadedConfig ?? loadConfig(cwd);
const agentsConfig = loaded.config.agents;
const overrides = agentsConfig?.overrides ?? {};
return agents
.filter((agent) => !(agentsConfig?.disableBuiltins && agent.source === "builtin"))
.map((agent) => {
const overrideEntry = Object.entries(overrides).find(([name]) => name.toLowerCase() === agent.name.toLowerCase());
if (!overrideEntry) return agent;
const [, override] = overrideEntry;
return {
...agent,
disabled: override.disabled ?? agent.disabled,
model: override.model === false ? undefined : override.model ?? agent.model,
fallbackModels: override.fallbackModels === false ? undefined : override.fallbackModels ?? agent.fallbackModels,
thinking: override.thinking === false ? undefined : override.thinking ?? agent.thinking,
tools: override.tools === false ? undefined : override.tools ?? agent.tools,
skills: override.skills === false ? undefined : override.skills ?? agent.skills,
override: { source: "config", path: loaded.path },
};
});
}
export function discoverAgents(cwd: string): AgentDiscoveryResult {
const loaded = loadConfig(cwd);
return {
builtin: applyAgentOverrides(readAgentDir(path.join(packageRoot(), "agents"), "builtin"), cwd, loaded),
user: applyAgentOverrides(readAgentDir(path.join(userPiRoot(), "agents"), "user"), cwd, loaded),
project: applyAgentOverrides(readAgentDir(path.join(projectCrewRoot(cwd), "agents"), "project"), cwd, loaded),
};
}
export function allAgents(discovery: AgentDiscoveryResult): AgentConfig[] {
const byName = new Map<string, AgentConfig>();
for (const agent of [...discovery.project, ...discovery.builtin, ...discovery.user]) {
byName.set(agent.name.toLowerCase(), agent);
}
return [...byName.values()].filter((agent) => !agent.disabled).sort((a, b) => a.name.localeCompare(b.name));
}