diff --git a/src/resources/extensions/gsd/tests/subagent-agent-discovery.test.ts b/src/resources/extensions/gsd/tests/subagent-agent-discovery.test.ts index 14cf587e5..5d8a6bd12 100644 --- a/src/resources/extensions/gsd/tests/subagent-agent-discovery.test.ts +++ b/src/resources/extensions/gsd/tests/subagent-agent-discovery.test.ts @@ -42,3 +42,50 @@ test("discoverAgents falls back to legacy .pi/agents when needed", (t) => { assert.equal(discovery.projectAgentsDir, agentsDir); assert.deepEqual(discovery.agents.map((agent) => agent.name), ["ping"]); }); + +test("discoverAgents accepts tools frontmatter as a YAML list", (t) => { + const root = makeProjectRoot(t); + const agentsDir = join(root, ".gsd", "agents"); + mkdirSync(agentsDir, { recursive: true }); + writeFileSync( + join(agentsDir, "reviewer.md"), + [ + "---", + "name: reviewer", + "description: review agent", + "tools:", + " - bash", + " - read", + "---", + "Review code", + "", + ].join("\n"), + ); + + const discovery = discoverAgents(root, "project"); + + assert.deepEqual(discovery.agents.map((agent) => agent.name), ["reviewer"]); + assert.deepEqual(discovery.agents[0]?.tools, ["bash", "read"]); +}); + +test("discoverAgents still accepts comma-separated tools frontmatter", (t) => { + const root = makeProjectRoot(t); + const agentsDir = join(root, ".gsd", "agents"); + mkdirSync(agentsDir, { recursive: true }); + writeFileSync( + join(agentsDir, "reviewer.md"), + [ + "---", + "name: reviewer", + "description: review agent", + "tools: bash, read", + "---", + "Review code", + "", + ].join("\n"), + ); + + const discovery = discoverAgents(root, "project"); + + assert.deepEqual(discovery.agents[0]?.tools, ["bash", "read"]); +}); diff --git a/src/resources/extensions/subagent/agents.ts b/src/resources/extensions/subagent/agents.ts index 43e124fa3..6f14c3bcf 100644 --- a/src/resources/extensions/subagent/agents.ts +++ b/src/resources/extensions/subagent/agents.ts @@ -25,6 +25,33 @@ export interface AgentDiscoveryResult { projectAgentsDir: string | null; } +interface AgentFrontmatter extends Record { + name?: string; + description?: string; + tools?: string | string[]; + model?: string; +} + +function parseAgentTools(value: string | string[] | undefined): string[] | undefined { + if (typeof value === "string") { + const tools = value + .split(",") + .map((tool) => tool.trim()) + .filter(Boolean); + return tools.length > 0 ? tools : undefined; + } + + if (Array.isArray(value)) { + const tools = value + .flatMap((tool) => typeof tool === "string" ? tool.split(",") : []) + .map((tool) => tool.trim()) + .filter(Boolean); + return tools.length > 0 ? tools : undefined; + } + + return undefined; +} + function loadAgentsFromDir(dir: string, source: "user" | "project"): AgentConfig[] { const agents: AgentConfig[] = []; @@ -51,16 +78,13 @@ function loadAgentsFromDir(dir: string, source: "user" | "project"): AgentConfig continue; } - const { frontmatter, body } = parseFrontmatter>(content); + const { frontmatter, body } = parseFrontmatter(content); - if (!frontmatter.name || !frontmatter.description) { + if (typeof frontmatter.name !== "string" || typeof frontmatter.description !== "string") { continue; } - const tools = frontmatter.tools - ?.split(",") - .map((t: string) => t.trim()) - .filter(Boolean); + const tools = parseAgentTools(frontmatter.tools); agents.push({ name: frontmatter.name,