Add 5 pi extensions: pi-subagents, pi-crew, rpiv-pi, pi-interactive-shell, pi-intercom
This commit is contained in:
30
extensions/pi-crew/src/agents/agent-config.ts
Normal file
30
extensions/pi-crew/src/agents/agent-config.ts
Normal 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 };
|
||||
}
|
||||
34
extensions/pi-crew/src/agents/agent-serializer.ts
Normal file
34
extensions/pi-crew/src/agents/agent-serializer.ts
Normal 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");
|
||||
}
|
||||
104
extensions/pi-crew/src/agents/discover-agents.ts
Normal file
104
extensions/pi-crew/src/agents/discover-agents.ts
Normal 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));
|
||||
}
|
||||
Reference in New Issue
Block a user