From 35ad932bbf4ce8eca3e98b2ea2a2e91ebfde9ee8 Mon Sep 17 00:00:00 2001 From: Matteo Rosati Date: Sat, 9 May 2026 20:59:43 +0200 Subject: [PATCH] remove subagent --- .gitignore | 1 + agents/planner.md | 37 - agents/reviewer.md | 35 - agents/scout.md | 50 - agents/worker.md | 24 - extensions/subagent/README.md | 172 --- extensions/subagent/agents.ts | 126 --- extensions/subagent/index.ts | 987 ------------------ .../subagent/prompts/implement-and-review.md | 10 - extensions/subagent/prompts/implement.md | 10 - extensions/subagent/prompts/scout-and-plan.md | 9 - settings.json | 3 +- 12 files changed, 3 insertions(+), 1461 deletions(-) delete mode 100644 agents/planner.md delete mode 100644 agents/reviewer.md delete mode 100644 agents/scout.md delete mode 100644 agents/worker.md delete mode 100644 extensions/subagent/README.md delete mode 100644 extensions/subagent/agents.ts delete mode 100644 extensions/subagent/index.ts delete mode 100644 extensions/subagent/prompts/implement-and-review.md delete mode 100644 extensions/subagent/prompts/implement.md delete mode 100644 extensions/subagent/prompts/scout-and-plan.md diff --git a/.gitignore b/.gitignore index bf6d63b..09a0ecb 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ sessions .mcp.json mcp-cache.json mcp-npx-cache.json +run-history.jsonl diff --git a/agents/planner.md b/agents/planner.md deleted file mode 100644 index fbbe33b..0000000 --- a/agents/planner.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -name: planner -description: Creates implementation plans from context and requirements -tools: read, grep, find, ls -model: openai-codex/gpt-5.3-codex ---- - -You are a planning specialist. You receive context (from a scout) and requirements, then produce a clear implementation plan. - -You must NOT make any changes. Only read, analyze, and plan. - -Input format you'll receive: -- Context/findings from a scout agent -- Original query or requirements - -Output format: - -## Goal -One sentence summary of what needs to be done. - -## Plan -Numbered steps, each small and actionable: -1. Step one - specific file/function to modify -2. Step two - what to add/change -3. ... - -## Files to Modify -- `path/to/file.ts` - what changes -- `path/to/other.ts` - what changes - -## New Files (if any) -- `path/to/new.ts` - purpose - -## Risks -Anything to watch out for. - -Keep the plan concrete. The worker agent will execute it verbatim. diff --git a/agents/reviewer.md b/agents/reviewer.md deleted file mode 100644 index fd0a55a..0000000 --- a/agents/reviewer.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -name: reviewer -description: Code review specialist for quality and security analysis -tools: read, grep, find, ls, bash -model: openai-codex/gpt-5.3-codex ---- - -You are a senior code reviewer. Analyze code for quality, security, and maintainability. - -Bash is for read-only commands only: `git diff`, `git log`, `git show`. Do NOT modify files or run builds. -Assume tool permissions are not perfectly enforceable; keep all bash usage strictly read-only. - -Strategy: -1. Run `git diff` to see recent changes (if applicable) -2. Read the modified files -3. Check for bugs, security issues, code smells - -Output format: - -## Files Reviewed -- `path/to/file.ts` (lines X-Y) - -## Critical (must fix) -- `file.ts:42` - Issue description - -## Warnings (should fix) -- `file.ts:100` - Issue description - -## Suggestions (consider) -- `file.ts:150` - Improvement idea - -## Summary -Overall assessment in 2-3 sentences. - -Be specific with file paths and line numbers. diff --git a/agents/scout.md b/agents/scout.md deleted file mode 100644 index f837f6c..0000000 --- a/agents/scout.md +++ /dev/null @@ -1,50 +0,0 @@ ---- -name: scout -description: Fast codebase recon that returns compressed context for handoff to other agents -tools: read, grep, find, ls, bash -model: openai-codex/gpt-5.3-codex ---- - -You are a scout. Quickly investigate a codebase and return structured findings that another agent can use without re-reading everything. - -Your output will be passed to an agent who has NOT seen the files you explored. - -Thoroughness (infer from task, default medium): -- Quick: Targeted lookups, key files only -- Medium: Follow imports, read critical sections -- Thorough: Trace all dependencies, check tests/types - -Strategy: -1. grep/find to locate relevant code -2. Read key sections (not entire files) -3. Identify types, interfaces, key functions -4. Note dependencies between files - -Output format: - -## Files Retrieved -List with exact line ranges: -1. `path/to/file.ts` (lines 10-50) - Description of what's here -2. `path/to/other.ts` (lines 100-150) - Description -3. ... - -## Key Code -Critical types, interfaces, or functions: - -```typescript -interface Example { - // actual code from the files -} -``` - -```typescript -function keyFunction() { - // actual implementation -} -``` - -## Architecture -Brief explanation of how the pieces connect. - -## Start Here -Which file to look at first and why. diff --git a/agents/worker.md b/agents/worker.md deleted file mode 100644 index 12749e7..0000000 --- a/agents/worker.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -name: worker -description: General-purpose subagent with full capabilities, isolated context -model: openai-codex/gpt-5.3-codex ---- - -You are a worker agent with full capabilities. You operate in an isolated context window to handle delegated tasks without polluting the main conversation. - -Work autonomously to complete the assigned task. Use all available tools as needed. - -Output format when finished: - -## Completed -What was done. - -## Files Changed -- `path/to/file.ts` - what changed - -## Notes (if any) -Anything the main agent should know. - -If handing off to another agent (e.g. reviewer), include: -- Exact file paths changed -- Key functions/types touched (short list) diff --git a/extensions/subagent/README.md b/extensions/subagent/README.md deleted file mode 100644 index 8599679..0000000 --- a/extensions/subagent/README.md +++ /dev/null @@ -1,172 +0,0 @@ -# Subagent Example - -Delegate tasks to specialized subagents with isolated context windows. - -## Features - -- **Isolated context**: Each subagent runs in a separate `pi` process -- **Streaming output**: See tool calls and progress as they happen -- **Parallel streaming**: All parallel tasks stream updates simultaneously -- **Markdown rendering**: Final output rendered with proper formatting (expanded view) -- **Usage tracking**: Shows turns, tokens, cost, and context usage per agent -- **Abort support**: Ctrl+C propagates to kill subagent processes - -## Structure - -``` -subagent/ -├── README.md # This file -├── index.ts # The extension (entry point) -├── agents.ts # Agent discovery logic -├── agents/ # Sample agent definitions -│ ├── scout.md # Fast recon, returns compressed context -│ ├── planner.md # Creates implementation plans -│ ├── reviewer.md # Code review -│ └── worker.md # General-purpose (full capabilities) -└── prompts/ # Workflow presets (prompt templates) - ├── implement.md # scout -> planner -> worker - ├── scout-and-plan.md # scout -> planner (no implementation) - └── implement-and-review.md # worker -> reviewer -> worker -``` - -## Installation - -From the repository root, symlink the files: - -```bash -# Symlink the extension (must be in a subdirectory with index.ts) -mkdir -p ~/.pi/agent/extensions/subagent -ln -sf "$(pwd)/packages/coding-agent/examples/extensions/subagent/index.ts" ~/.pi/agent/extensions/subagent/index.ts -ln -sf "$(pwd)/packages/coding-agent/examples/extensions/subagent/agents.ts" ~/.pi/agent/extensions/subagent/agents.ts - -# Symlink agents -mkdir -p ~/.pi/agent/agents -for f in packages/coding-agent/examples/extensions/subagent/agents/*.md; do - ln -sf "$(pwd)/$f" ~/.pi/agent/agents/$(basename "$f") -done - -# Symlink workflow prompts -mkdir -p ~/.pi/agent/prompts -for f in packages/coding-agent/examples/extensions/subagent/prompts/*.md; do - ln -sf "$(pwd)/$f" ~/.pi/agent/prompts/$(basename "$f") -done -``` - -## Security Model - -This tool executes a separate `pi` subprocess with a delegated system prompt and tool/model configuration. - -**Project-local agents** (`.pi/agents/*.md`) are repo-controlled prompts that can instruct the model to read files, run bash commands, etc. - -**Default behavior:** Only loads **user-level agents** from `~/.pi/agent/agents`. - -To enable project-local agents, pass `agentScope: "both"` (or `"project"`). Only do this for repositories you trust. - -When running interactively, the tool prompts for confirmation before running project-local agents. Set `confirmProjectAgents: false` to disable. - -## Usage - -### Single agent -``` -Use scout to find all authentication code -``` - -### Parallel execution -``` -Run 2 scouts in parallel: one to find models, one to find providers -``` - -### Chained workflow -``` -Use a chain: first have scout find the read tool, then have planner suggest improvements -``` - -### Workflow prompts -``` -/implement add Redis caching to the session store -/scout-and-plan refactor auth to support OAuth -/implement-and-review add input validation to API endpoints -``` - -## Tool Modes - -| Mode | Parameter | Description | -|------|-----------|-------------| -| Single | `{ agent, task }` | One agent, one task | -| Parallel | `{ tasks: [...] }` | Multiple agents run concurrently (max 8, 4 concurrent) | -| Chain | `{ chain: [...] }` | Sequential with `{previous}` placeholder | - -## Output Display - -**Collapsed view** (default): -- Status icon (✓/✗/⏳) and agent name -- Last 5-10 items (tool calls and text) -- Usage stats: `3 turns ↑input ↓output RcacheRead WcacheWrite $cost ctx:contextTokens model` - -**Expanded view** (Ctrl+O): -- Full task text -- All tool calls with formatted arguments -- Final output rendered as Markdown -- Per-task usage (for chain/parallel) - -**Parallel mode streaming**: -- Shows all tasks with live status (⏳ running, ✓ done, ✗ failed) -- Updates as each task makes progress -- Shows "2/3 done, 1 running" status - -**Tool call formatting** (mimics built-in tools): -- `$ command` for bash -- `read ~/path:1-10` for read -- `grep /pattern/ in ~/path` for grep -- etc. - -## Agent Definitions - -Agents are markdown files with YAML frontmatter: - -```markdown ---- -name: my-agent -description: What this agent does -tools: read, grep, find, ls -model: claude-haiku-4-5 ---- - -System prompt for the agent goes here. -``` - -**Locations:** -- `~/.pi/agent/agents/*.md` - User-level (always loaded) -- `.pi/agents/*.md` - Project-level (only with `agentScope: "project"` or `"both"`) - -Project agents override user agents with the same name when `agentScope: "both"`. - -## Sample Agents - -| Agent | Purpose | Model | Tools | -|-------|---------|-------|-------| -| `scout` | Fast codebase recon | Haiku | read, grep, find, ls, bash | -| `planner` | Implementation plans | Sonnet | read, grep, find, ls | -| `reviewer` | Code review | Sonnet | read, grep, find, ls, bash | -| `worker` | General-purpose | Sonnet | (all default) | - -## Workflow Prompts - -| Prompt | Flow | -|--------|------| -| `/implement ` | scout → planner → worker | -| `/scout-and-plan ` | scout → planner | -| `/implement-and-review ` | worker → reviewer → worker | - -## Error Handling - -- **Exit code != 0**: Tool returns error with stderr/output -- **stopReason "error"**: LLM error propagated with error message -- **stopReason "aborted"**: User abort (Ctrl+C) kills subprocess, throws error -- **Chain mode**: Stops at first failing step, reports which step failed - -## Limitations - -- Output truncated to last 10 items in collapsed view (expand to see all) -- Agents discovered fresh on each invocation (allows editing mid-session) -- Parallel mode limited to 8 tasks, 4 concurrent diff --git a/extensions/subagent/agents.ts b/extensions/subagent/agents.ts deleted file mode 100644 index eab6c63..0000000 --- a/extensions/subagent/agents.ts +++ /dev/null @@ -1,126 +0,0 @@ -/** - * Agent discovery and configuration - */ - -import * as fs from "node:fs"; -import * as path from "node:path"; -import { getAgentDir, parseFrontmatter } from "@earendil-works/pi-coding-agent"; - -export type AgentScope = "user" | "project" | "both"; - -export interface AgentConfig { - name: string; - description: string; - tools?: string[]; - model?: string; - systemPrompt: string; - source: "user" | "project"; - filePath: string; -} - -export interface AgentDiscoveryResult { - agents: AgentConfig[]; - projectAgentsDir: string | null; -} - -function loadAgentsFromDir(dir: string, source: "user" | "project"): AgentConfig[] { - const agents: AgentConfig[] = []; - - if (!fs.existsSync(dir)) { - return agents; - } - - let entries: fs.Dirent[]; - try { - entries = fs.readdirSync(dir, { withFileTypes: true }); - } catch { - return agents; - } - - for (const entry of entries) { - if (!entry.name.endsWith(".md")) continue; - if (!entry.isFile() && !entry.isSymbolicLink()) continue; - - const filePath = path.join(dir, entry.name); - let content: string; - try { - content = fs.readFileSync(filePath, "utf-8"); - } catch { - continue; - } - - const { frontmatter, body } = parseFrontmatter>(content); - - if (!frontmatter.name || !frontmatter.description) { - continue; - } - - const tools = frontmatter.tools - ?.split(",") - .map((t: string) => t.trim()) - .filter(Boolean); - - agents.push({ - name: frontmatter.name, - description: frontmatter.description, - tools: tools && tools.length > 0 ? tools : undefined, - model: frontmatter.model, - systemPrompt: body, - source, - filePath, - }); - } - - return agents; -} - -function isDirectory(p: string): boolean { - try { - return fs.statSync(p).isDirectory(); - } catch { - return false; - } -} - -function findNearestProjectAgentsDir(cwd: string): string | null { - let currentDir = cwd; - while (true) { - const candidate = path.join(currentDir, ".pi", "agents"); - if (isDirectory(candidate)) return candidate; - - const parentDir = path.dirname(currentDir); - if (parentDir === currentDir) return null; - currentDir = parentDir; - } -} - -export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryResult { - const userDir = path.join(getAgentDir(), "agents"); - const projectAgentsDir = findNearestProjectAgentsDir(cwd); - - const userAgents = scope === "project" ? [] : loadAgentsFromDir(userDir, "user"); - const projectAgents = scope === "user" || !projectAgentsDir ? [] : loadAgentsFromDir(projectAgentsDir, "project"); - - const agentMap = new Map(); - - if (scope === "both") { - for (const agent of userAgents) agentMap.set(agent.name, agent); - for (const agent of projectAgents) agentMap.set(agent.name, agent); - } else if (scope === "user") { - for (const agent of userAgents) agentMap.set(agent.name, agent); - } else { - for (const agent of projectAgents) agentMap.set(agent.name, agent); - } - - return { agents: Array.from(agentMap.values()), projectAgentsDir }; -} - -export function formatAgentList(agents: AgentConfig[], maxItems: number): { text: string; remaining: number } { - if (agents.length === 0) return { text: "none", remaining: 0 }; - const listed = agents.slice(0, maxItems); - const remaining = agents.length - listed.length; - return { - text: listed.map((a) => `${a.name} (${a.source}): ${a.description}`).join("; "), - remaining, - }; -} diff --git a/extensions/subagent/index.ts b/extensions/subagent/index.ts deleted file mode 100644 index a9b6da4..0000000 --- a/extensions/subagent/index.ts +++ /dev/null @@ -1,987 +0,0 @@ -/** - * Subagent Tool - Delegate tasks to specialized agents - * - * Spawns a separate `pi` process for each subagent invocation, - * giving it an isolated context window. - * - * Supports three modes: - * - Single: { agent: "name", task: "..." } - * - Parallel: { tasks: [{ agent: "name", task: "..." }, ...] } - * - Chain: { chain: [{ agent: "name", task: "... {previous} ..." }, ...] } - * - * Uses JSON mode to capture structured output from subagents. - */ - -import { spawn } from "node:child_process"; -import * as fs from "node:fs"; -import * as os from "node:os"; -import * as path from "node:path"; -import type { AgentToolResult } from "@earendil-works/pi-agent-core"; -import type { Message } from "@earendil-works/pi-ai"; -import { StringEnum } from "@earendil-works/pi-ai"; -import { type ExtensionAPI, getMarkdownTheme, withFileMutationQueue } from "@earendil-works/pi-coding-agent"; -import { Container, Markdown, Spacer, Text } from "@earendil-works/pi-tui"; -import { Type } from "typebox"; -import { type AgentConfig, type AgentScope, discoverAgents } from "./agents.js"; - -const MAX_PARALLEL_TASKS = 8; -const MAX_CONCURRENCY = 4; -const COLLAPSED_ITEM_COUNT = 10; - -function formatTokens(count: number): string { - if (count < 1000) return count.toString(); - if (count < 10000) return `${(count / 1000).toFixed(1)}k`; - if (count < 1000000) return `${Math.round(count / 1000)}k`; - return `${(count / 1000000).toFixed(1)}M`; -} - -function formatUsageStats( - usage: { - input: number; - output: number; - cacheRead: number; - cacheWrite: number; - cost: number; - contextTokens?: number; - turns?: number; - }, - model?: string, -): string { - const parts: string[] = []; - if (usage.turns) parts.push(`${usage.turns} turn${usage.turns > 1 ? "s" : ""}`); - if (usage.input) parts.push(`↑${formatTokens(usage.input)}`); - if (usage.output) parts.push(`↓${formatTokens(usage.output)}`); - if (usage.cacheRead) parts.push(`R${formatTokens(usage.cacheRead)}`); - if (usage.cacheWrite) parts.push(`W${formatTokens(usage.cacheWrite)}`); - if (usage.cost) parts.push(`$${usage.cost.toFixed(4)}`); - if (usage.contextTokens && usage.contextTokens > 0) { - parts.push(`ctx:${formatTokens(usage.contextTokens)}`); - } - if (model) parts.push(model); - return parts.join(" "); -} - -function formatToolCall( - toolName: string, - args: Record, - themeFg: (color: any, text: string) => string, -): string { - const shortenPath = (p: string) => { - const home = os.homedir(); - return p.startsWith(home) ? `~${p.slice(home.length)}` : p; - }; - - switch (toolName) { - case "bash": { - const command = (args.command as string) || "..."; - const preview = command.length > 60 ? `${command.slice(0, 60)}...` : command; - return themeFg("muted", "$ ") + themeFg("toolOutput", preview); - } - case "read": { - const rawPath = (args.file_path || args.path || "...") as string; - const filePath = shortenPath(rawPath); - const offset = args.offset as number | undefined; - const limit = args.limit as number | undefined; - let text = themeFg("accent", filePath); - if (offset !== undefined || limit !== undefined) { - const startLine = offset ?? 1; - const endLine = limit !== undefined ? startLine + limit - 1 : ""; - text += themeFg("warning", `:${startLine}${endLine ? `-${endLine}` : ""}`); - } - return themeFg("muted", "read ") + text; - } - case "write": { - const rawPath = (args.file_path || args.path || "...") as string; - const filePath = shortenPath(rawPath); - const content = (args.content || "") as string; - const lines = content.split("\n").length; - let text = themeFg("muted", "write ") + themeFg("accent", filePath); - if (lines > 1) text += themeFg("dim", ` (${lines} lines)`); - return text; - } - case "edit": { - const rawPath = (args.file_path || args.path || "...") as string; - return themeFg("muted", "edit ") + themeFg("accent", shortenPath(rawPath)); - } - case "ls": { - const rawPath = (args.path || ".") as string; - return themeFg("muted", "ls ") + themeFg("accent", shortenPath(rawPath)); - } - case "find": { - const pattern = (args.pattern || "*") as string; - const rawPath = (args.path || ".") as string; - return themeFg("muted", "find ") + themeFg("accent", pattern) + themeFg("dim", ` in ${shortenPath(rawPath)}`); - } - case "grep": { - const pattern = (args.pattern || "") as string; - const rawPath = (args.path || ".") as string; - return ( - themeFg("muted", "grep ") + - themeFg("accent", `/${pattern}/`) + - themeFg("dim", ` in ${shortenPath(rawPath)}`) - ); - } - default: { - const argsStr = JSON.stringify(args); - const preview = argsStr.length > 50 ? `${argsStr.slice(0, 50)}...` : argsStr; - return themeFg("accent", toolName) + themeFg("dim", ` ${preview}`); - } - } -} - -interface UsageStats { - input: number; - output: number; - cacheRead: number; - cacheWrite: number; - cost: number; - contextTokens: number; - turns: number; -} - -interface SingleResult { - agent: string; - agentSource: "user" | "project" | "unknown"; - task: string; - exitCode: number; - messages: Message[]; - stderr: string; - usage: UsageStats; - model?: string; - stopReason?: string; - errorMessage?: string; - step?: number; -} - -interface SubagentDetails { - mode: "single" | "parallel" | "chain"; - agentScope: AgentScope; - projectAgentsDir: string | null; - results: SingleResult[]; -} - -function getFinalOutput(messages: Message[]): string { - for (let i = messages.length - 1; i >= 0; i--) { - const msg = messages[i]; - if (msg.role === "assistant") { - for (const part of msg.content) { - if (part.type === "text") return part.text; - } - } - } - return ""; -} - -type DisplayItem = { type: "text"; text: string } | { type: "toolCall"; name: string; args: Record }; - -function getDisplayItems(messages: Message[]): DisplayItem[] { - const items: DisplayItem[] = []; - for (const msg of messages) { - if (msg.role === "assistant") { - for (const part of msg.content) { - if (part.type === "text") items.push({ type: "text", text: part.text }); - else if (part.type === "toolCall") items.push({ type: "toolCall", name: part.name, args: part.arguments }); - } - } - } - return items; -} - -async function mapWithConcurrencyLimit( - items: TIn[], - concurrency: number, - fn: (item: TIn, index: number) => Promise, -): Promise { - if (items.length === 0) return []; - const limit = Math.max(1, Math.min(concurrency, items.length)); - const results: TOut[] = new Array(items.length); - let nextIndex = 0; - const workers = new Array(limit).fill(null).map(async () => { - while (true) { - const current = nextIndex++; - if (current >= items.length) return; - results[current] = await fn(items[current], current); - } - }); - await Promise.all(workers); - return results; -} - -async function writePromptToTempFile(agentName: string, prompt: string): Promise<{ dir: string; filePath: string }> { - const tmpDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "pi-subagent-")); - const safeName = agentName.replace(/[^\w.-]+/g, "_"); - const filePath = path.join(tmpDir, `prompt-${safeName}.md`); - await withFileMutationQueue(filePath, async () => { - await fs.promises.writeFile(filePath, prompt, { encoding: "utf-8", mode: 0o600 }); - }); - return { dir: tmpDir, filePath }; -} - -function getPiInvocation(args: string[]): { command: string; args: string[] } { - const currentScript = process.argv[1]; - const isBunVirtualScript = currentScript?.startsWith("/$bunfs/root/"); - if (currentScript && !isBunVirtualScript && fs.existsSync(currentScript)) { - return { command: process.execPath, args: [currentScript, ...args] }; - } - - const execName = path.basename(process.execPath).toLowerCase(); - const isGenericRuntime = /^(node|bun)(\.exe)?$/.test(execName); - if (!isGenericRuntime) { - return { command: process.execPath, args }; - } - - return { command: "pi", args }; -} - -type OnUpdateCallback = (partial: AgentToolResult) => void; - -async function runSingleAgent( - defaultCwd: string, - agents: AgentConfig[], - agentName: string, - task: string, - cwd: string | undefined, - step: number | undefined, - signal: AbortSignal | undefined, - onUpdate: OnUpdateCallback | undefined, - makeDetails: (results: SingleResult[]) => SubagentDetails, -): Promise { - const agent = agents.find((a) => a.name === agentName); - - if (!agent) { - const available = agents.map((a) => `"${a.name}"`).join(", ") || "none"; - return { - agent: agentName, - agentSource: "unknown", - task, - exitCode: 1, - messages: [], - stderr: `Unknown agent: "${agentName}". Available agents: ${available}.`, - usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 }, - step, - }; - } - - const args: string[] = ["--mode", "json", "-p", "--no-session"]; - if (agent.model) args.push("--model", agent.model); - if (agent.tools && agent.tools.length > 0) args.push("--tools", agent.tools.join(",")); - - let tmpPromptDir: string | null = null; - let tmpPromptPath: string | null = null; - - const currentResult: SingleResult = { - agent: agentName, - agentSource: agent.source, - task, - exitCode: 0, - messages: [], - stderr: "", - usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 }, - model: agent.model, - step, - }; - - const emitUpdate = () => { - if (onUpdate) { - onUpdate({ - content: [{ type: "text", text: getFinalOutput(currentResult.messages) || "(running...)" }], - details: makeDetails([currentResult]), - }); - } - }; - - try { - if (agent.systemPrompt.trim()) { - const tmp = await writePromptToTempFile(agent.name, agent.systemPrompt); - tmpPromptDir = tmp.dir; - tmpPromptPath = tmp.filePath; - args.push("--append-system-prompt", tmpPromptPath); - } - - args.push(`Task: ${task}`); - let wasAborted = false; - - const exitCode = await new Promise((resolve) => { - const invocation = getPiInvocation(args); - const proc = spawn(invocation.command, invocation.args, { - cwd: cwd ?? defaultCwd, - shell: false, - stdio: ["ignore", "pipe", "pipe"], - }); - let buffer = ""; - - const processLine = (line: string) => { - if (!line.trim()) return; - let event: any; - try { - event = JSON.parse(line); - } catch { - return; - } - - if (event.type === "message_end" && event.message) { - const msg = event.message as Message; - currentResult.messages.push(msg); - - if (msg.role === "assistant") { - currentResult.usage.turns++; - const usage = msg.usage; - if (usage) { - currentResult.usage.input += usage.input || 0; - currentResult.usage.output += usage.output || 0; - currentResult.usage.cacheRead += usage.cacheRead || 0; - currentResult.usage.cacheWrite += usage.cacheWrite || 0; - currentResult.usage.cost += usage.cost?.total || 0; - currentResult.usage.contextTokens = usage.totalTokens || 0; - } - if (!currentResult.model && msg.model) currentResult.model = msg.model; - if (msg.stopReason) currentResult.stopReason = msg.stopReason; - if (msg.errorMessage) currentResult.errorMessage = msg.errorMessage; - } - emitUpdate(); - } - - if (event.type === "tool_result_end" && event.message) { - currentResult.messages.push(event.message as Message); - emitUpdate(); - } - }; - - proc.stdout.on("data", (data) => { - buffer += data.toString(); - const lines = buffer.split("\n"); - buffer = lines.pop() || ""; - for (const line of lines) processLine(line); - }); - - proc.stderr.on("data", (data) => { - currentResult.stderr += data.toString(); - }); - - proc.on("close", (code) => { - if (buffer.trim()) processLine(buffer); - resolve(code ?? 0); - }); - - proc.on("error", () => { - resolve(1); - }); - - if (signal) { - const killProc = () => { - wasAborted = true; - proc.kill("SIGTERM"); - setTimeout(() => { - if (!proc.killed) proc.kill("SIGKILL"); - }, 5000); - }; - if (signal.aborted) killProc(); - else signal.addEventListener("abort", killProc, { once: true }); - } - }); - - currentResult.exitCode = exitCode; - if (wasAborted) throw new Error("Subagent was aborted"); - return currentResult; - } finally { - if (tmpPromptPath) - try { - fs.unlinkSync(tmpPromptPath); - } catch { - /* ignore */ - } - if (tmpPromptDir) - try { - fs.rmdirSync(tmpPromptDir); - } catch { - /* ignore */ - } - } -} - -const TaskItem = Type.Object({ - agent: Type.String({ description: "Name of the agent to invoke" }), - task: Type.String({ description: "Task to delegate to the agent" }), - cwd: Type.Optional(Type.String({ description: "Working directory for the agent process" })), -}); - -const ChainItem = Type.Object({ - agent: Type.String({ description: "Name of the agent to invoke" }), - task: Type.String({ description: "Task with optional {previous} placeholder for prior output" }), - cwd: Type.Optional(Type.String({ description: "Working directory for the agent process" })), -}); - -const AgentScopeSchema = StringEnum(["user", "project", "both"] as const, { - description: 'Which agent directories to use. Default: "user". Use "both" to include project-local agents.', - default: "user", -}); - -const SubagentParams = Type.Object({ - agent: Type.Optional(Type.String({ description: "Name of the agent to invoke (for single mode)" })), - task: Type.Optional(Type.String({ description: "Task to delegate (for single mode)" })), - tasks: Type.Optional(Type.Array(TaskItem, { description: "Array of {agent, task} for parallel execution" })), - chain: Type.Optional(Type.Array(ChainItem, { description: "Array of {agent, task} for sequential execution" })), - agentScope: Type.Optional(AgentScopeSchema), - confirmProjectAgents: Type.Optional( - Type.Boolean({ description: "Prompt before running project-local agents. Default: true.", default: true }), - ), - cwd: Type.Optional(Type.String({ description: "Working directory for the agent process (single mode)" })), -}); - -export default function (pi: ExtensionAPI) { - pi.registerTool({ - name: "subagent", - label: "Subagent", - description: [ - "Delegate tasks to specialized subagents with isolated context.", - "Modes: single (agent + task), parallel (tasks array), chain (sequential with {previous} placeholder).", - 'Default agent scope is "user" (from ~/.pi/agent/agents).', - 'To enable project-local agents in .pi/agents, set agentScope: "both" (or "project").', - ].join(" "), - parameters: SubagentParams, - - async execute(_toolCallId, params, signal, onUpdate, ctx) { - const agentScope: AgentScope = params.agentScope ?? "user"; - const discovery = discoverAgents(ctx.cwd, agentScope); - const agents = discovery.agents; - const confirmProjectAgents = params.confirmProjectAgents ?? true; - - const hasChain = (params.chain?.length ?? 0) > 0; - const hasTasks = (params.tasks?.length ?? 0) > 0; - const hasSingle = Boolean(params.agent && params.task); - const modeCount = Number(hasChain) + Number(hasTasks) + Number(hasSingle); - - const makeDetails = - (mode: "single" | "parallel" | "chain") => - (results: SingleResult[]): SubagentDetails => ({ - mode, - agentScope, - projectAgentsDir: discovery.projectAgentsDir, - results, - }); - - if (modeCount !== 1) { - const available = agents.map((a) => `${a.name} (${a.source})`).join(", ") || "none"; - return { - content: [ - { - type: "text", - text: `Invalid parameters. Provide exactly one mode.\nAvailable agents: ${available}`, - }, - ], - details: makeDetails("single")([]), - }; - } - - if ((agentScope === "project" || agentScope === "both") && confirmProjectAgents && ctx.hasUI) { - const requestedAgentNames = new Set(); - if (params.chain) for (const step of params.chain) requestedAgentNames.add(step.agent); - if (params.tasks) for (const t of params.tasks) requestedAgentNames.add(t.agent); - if (params.agent) requestedAgentNames.add(params.agent); - - const projectAgentsRequested = Array.from(requestedAgentNames) - .map((name) => agents.find((a) => a.name === name)) - .filter((a): a is AgentConfig => a?.source === "project"); - - if (projectAgentsRequested.length > 0) { - const names = projectAgentsRequested.map((a) => a.name).join(", "); - const dir = discovery.projectAgentsDir ?? "(unknown)"; - const ok = await ctx.ui.confirm( - "Run project-local agents?", - `Agents: ${names}\nSource: ${dir}\n\nProject agents are repo-controlled. Only continue for trusted repositories.`, - ); - if (!ok) - return { - content: [{ type: "text", text: "Canceled: project-local agents not approved." }], - details: makeDetails(hasChain ? "chain" : hasTasks ? "parallel" : "single")([]), - }; - } - } - - if (params.chain && params.chain.length > 0) { - const results: SingleResult[] = []; - let previousOutput = ""; - - for (let i = 0; i < params.chain.length; i++) { - const step = params.chain[i]; - const taskWithContext = step.task.replace(/\{previous\}/g, previousOutput); - - // Create update callback that includes all previous results - const chainUpdate: OnUpdateCallback | undefined = onUpdate - ? (partial) => { - // Combine completed results with current streaming result - const currentResult = partial.details?.results[0]; - if (currentResult) { - const allResults = [...results, currentResult]; - onUpdate({ - content: partial.content, - details: makeDetails("chain")(allResults), - }); - } - } - : undefined; - - const result = await runSingleAgent( - ctx.cwd, - agents, - step.agent, - taskWithContext, - step.cwd, - i + 1, - signal, - chainUpdate, - makeDetails("chain"), - ); - results.push(result); - - const isError = - result.exitCode !== 0 || result.stopReason === "error" || result.stopReason === "aborted"; - if (isError) { - const errorMsg = - result.errorMessage || result.stderr || getFinalOutput(result.messages) || "(no output)"; - return { - content: [{ type: "text", text: `Chain stopped at step ${i + 1} (${step.agent}): ${errorMsg}` }], - details: makeDetails("chain")(results), - isError: true, - }; - } - previousOutput = getFinalOutput(result.messages); - } - return { - content: [{ type: "text", text: getFinalOutput(results[results.length - 1].messages) || "(no output)" }], - details: makeDetails("chain")(results), - }; - } - - if (params.tasks && params.tasks.length > 0) { - if (params.tasks.length > MAX_PARALLEL_TASKS) - return { - content: [ - { - type: "text", - text: `Too many parallel tasks (${params.tasks.length}). Max is ${MAX_PARALLEL_TASKS}.`, - }, - ], - details: makeDetails("parallel")([]), - }; - - // Track all results for streaming updates - const allResults: SingleResult[] = new Array(params.tasks.length); - - // Initialize placeholder results - for (let i = 0; i < params.tasks.length; i++) { - allResults[i] = { - agent: params.tasks[i].agent, - agentSource: "unknown", - task: params.tasks[i].task, - exitCode: -1, // -1 = still running - messages: [], - stderr: "", - usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 }, - }; - } - - const emitParallelUpdate = () => { - if (onUpdate) { - const running = allResults.filter((r) => r.exitCode === -1).length; - const done = allResults.filter((r) => r.exitCode !== -1).length; - onUpdate({ - content: [ - { type: "text", text: `Parallel: ${done}/${allResults.length} done, ${running} running...` }, - ], - details: makeDetails("parallel")([...allResults]), - }); - } - }; - - const results = await mapWithConcurrencyLimit(params.tasks, MAX_CONCURRENCY, async (t, index) => { - const result = await runSingleAgent( - ctx.cwd, - agents, - t.agent, - t.task, - t.cwd, - undefined, - signal, - // Per-task update callback - (partial) => { - if (partial.details?.results[0]) { - allResults[index] = partial.details.results[0]; - emitParallelUpdate(); - } - }, - makeDetails("parallel"), - ); - allResults[index] = result; - emitParallelUpdate(); - return result; - }); - - const successCount = results.filter((r) => r.exitCode === 0).length; - const summaries = results.map((r) => { - const output = getFinalOutput(r.messages); - const preview = output.slice(0, 100) + (output.length > 100 ? "..." : ""); - return `[${r.agent}] ${r.exitCode === 0 ? "completed" : "failed"}: ${preview || "(no output)"}`; - }); - return { - content: [ - { - type: "text", - text: `Parallel: ${successCount}/${results.length} succeeded\n\n${summaries.join("\n\n")}`, - }, - ], - details: makeDetails("parallel")(results), - }; - } - - if (params.agent && params.task) { - const result = await runSingleAgent( - ctx.cwd, - agents, - params.agent, - params.task, - params.cwd, - undefined, - signal, - onUpdate, - makeDetails("single"), - ); - const isError = result.exitCode !== 0 || result.stopReason === "error" || result.stopReason === "aborted"; - if (isError) { - const errorMsg = - result.errorMessage || result.stderr || getFinalOutput(result.messages) || "(no output)"; - return { - content: [{ type: "text", text: `Agent ${result.stopReason || "failed"}: ${errorMsg}` }], - details: makeDetails("single")([result]), - isError: true, - }; - } - return { - content: [{ type: "text", text: getFinalOutput(result.messages) || "(no output)" }], - details: makeDetails("single")([result]), - }; - } - - const available = agents.map((a) => `${a.name} (${a.source})`).join(", ") || "none"; - return { - content: [{ type: "text", text: `Invalid parameters. Available agents: ${available}` }], - details: makeDetails("single")([]), - }; - }, - - renderCall(args, theme, _context) { - const scope: AgentScope = args.agentScope ?? "user"; - if (args.chain && args.chain.length > 0) { - let text = - theme.fg("toolTitle", theme.bold("subagent ")) + - theme.fg("accent", `chain (${args.chain.length} steps)`) + - theme.fg("muted", ` [${scope}]`); - for (let i = 0; i < Math.min(args.chain.length, 3); i++) { - const step = args.chain[i]; - // Clean up {previous} placeholder for display - const cleanTask = step.task.replace(/\{previous\}/g, "").trim(); - const preview = cleanTask.length > 40 ? `${cleanTask.slice(0, 40)}...` : cleanTask; - text += - "\n " + - theme.fg("muted", `${i + 1}.`) + - " " + - theme.fg("accent", step.agent) + - theme.fg("dim", ` ${preview}`); - } - if (args.chain.length > 3) text += `\n ${theme.fg("muted", `... +${args.chain.length - 3} more`)}`; - return new Text(text, 0, 0); - } - if (args.tasks && args.tasks.length > 0) { - let text = - theme.fg("toolTitle", theme.bold("subagent ")) + - theme.fg("accent", `parallel (${args.tasks.length} tasks)`) + - theme.fg("muted", ` [${scope}]`); - for (const t of args.tasks.slice(0, 3)) { - const preview = t.task.length > 40 ? `${t.task.slice(0, 40)}...` : t.task; - text += `\n ${theme.fg("accent", t.agent)}${theme.fg("dim", ` ${preview}`)}`; - } - if (args.tasks.length > 3) text += `\n ${theme.fg("muted", `... +${args.tasks.length - 3} more`)}`; - return new Text(text, 0, 0); - } - const agentName = args.agent || "..."; - const preview = args.task ? (args.task.length > 60 ? `${args.task.slice(0, 60)}...` : args.task) : "..."; - let text = - theme.fg("toolTitle", theme.bold("subagent ")) + - theme.fg("accent", agentName) + - theme.fg("muted", ` [${scope}]`); - text += `\n ${theme.fg("dim", preview)}`; - return new Text(text, 0, 0); - }, - - renderResult(result, { expanded }, theme, _context) { - const details = result.details as SubagentDetails | undefined; - if (!details || details.results.length === 0) { - const text = result.content[0]; - return new Text(text?.type === "text" ? text.text : "(no output)", 0, 0); - } - - const mdTheme = getMarkdownTheme(); - - const renderDisplayItems = (items: DisplayItem[], limit?: number) => { - const toShow = limit ? items.slice(-limit) : items; - const skipped = limit && items.length > limit ? items.length - limit : 0; - let text = ""; - if (skipped > 0) text += theme.fg("muted", `... ${skipped} earlier items\n`); - for (const item of toShow) { - if (item.type === "text") { - const preview = expanded ? item.text : item.text.split("\n").slice(0, 3).join("\n"); - text += `${theme.fg("toolOutput", preview)}\n`; - } else { - text += `${theme.fg("muted", "→ ") + formatToolCall(item.name, item.args, theme.fg.bind(theme))}\n`; - } - } - return text.trimEnd(); - }; - - if (details.mode === "single" && details.results.length === 1) { - const r = details.results[0]; - const isError = r.exitCode !== 0 || r.stopReason === "error" || r.stopReason === "aborted"; - const icon = isError ? theme.fg("error", "✗") : theme.fg("success", "✓"); - const displayItems = getDisplayItems(r.messages); - const finalOutput = getFinalOutput(r.messages); - - if (expanded) { - const container = new Container(); - let header = `${icon} ${theme.fg("toolTitle", theme.bold(r.agent))}${theme.fg("muted", ` (${r.agentSource})`)}`; - if (isError && r.stopReason) header += ` ${theme.fg("error", `[${r.stopReason}]`)}`; - container.addChild(new Text(header, 0, 0)); - if (isError && r.errorMessage) - container.addChild(new Text(theme.fg("error", `Error: ${r.errorMessage}`), 0, 0)); - container.addChild(new Spacer(1)); - container.addChild(new Text(theme.fg("muted", "─── Task ───"), 0, 0)); - container.addChild(new Text(theme.fg("dim", r.task), 0, 0)); - container.addChild(new Spacer(1)); - container.addChild(new Text(theme.fg("muted", "─── Output ───"), 0, 0)); - if (displayItems.length === 0 && !finalOutput) { - container.addChild(new Text(theme.fg("muted", "(no output)"), 0, 0)); - } else { - for (const item of displayItems) { - if (item.type === "toolCall") - container.addChild( - new Text( - theme.fg("muted", "→ ") + formatToolCall(item.name, item.args, theme.fg.bind(theme)), - 0, - 0, - ), - ); - } - if (finalOutput) { - container.addChild(new Spacer(1)); - container.addChild(new Markdown(finalOutput.trim(), 0, 0, mdTheme)); - } - } - const usageStr = formatUsageStats(r.usage, r.model); - if (usageStr) { - container.addChild(new Spacer(1)); - container.addChild(new Text(theme.fg("dim", usageStr), 0, 0)); - } - return container; - } - - let text = `${icon} ${theme.fg("toolTitle", theme.bold(r.agent))}${theme.fg("muted", ` (${r.agentSource})`)}`; - if (isError && r.stopReason) text += ` ${theme.fg("error", `[${r.stopReason}]`)}`; - if (isError && r.errorMessage) text += `\n${theme.fg("error", `Error: ${r.errorMessage}`)}`; - else if (displayItems.length === 0) text += `\n${theme.fg("muted", "(no output)")}`; - else { - text += `\n${renderDisplayItems(displayItems, COLLAPSED_ITEM_COUNT)}`; - if (displayItems.length > COLLAPSED_ITEM_COUNT) text += `\n${theme.fg("muted", "(Ctrl+O to expand)")}`; - } - const usageStr = formatUsageStats(r.usage, r.model); - if (usageStr) text += `\n${theme.fg("dim", usageStr)}`; - return new Text(text, 0, 0); - } - - const aggregateUsage = (results: SingleResult[]) => { - const total = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 }; - for (const r of results) { - total.input += r.usage.input; - total.output += r.usage.output; - total.cacheRead += r.usage.cacheRead; - total.cacheWrite += r.usage.cacheWrite; - total.cost += r.usage.cost; - total.turns += r.usage.turns; - } - return total; - }; - - if (details.mode === "chain") { - const successCount = details.results.filter((r) => r.exitCode === 0).length; - const icon = successCount === details.results.length ? theme.fg("success", "✓") : theme.fg("error", "✗"); - - if (expanded) { - const container = new Container(); - container.addChild( - new Text( - icon + - " " + - theme.fg("toolTitle", theme.bold("chain ")) + - theme.fg("accent", `${successCount}/${details.results.length} steps`), - 0, - 0, - ), - ); - - for (const r of details.results) { - const rIcon = r.exitCode === 0 ? theme.fg("success", "✓") : theme.fg("error", "✗"); - const displayItems = getDisplayItems(r.messages); - const finalOutput = getFinalOutput(r.messages); - - container.addChild(new Spacer(1)); - container.addChild( - new Text( - `${theme.fg("muted", `─── Step ${r.step}: `) + theme.fg("accent", r.agent)} ${rIcon}`, - 0, - 0, - ), - ); - container.addChild(new Text(theme.fg("muted", "Task: ") + theme.fg("dim", r.task), 0, 0)); - - // Show tool calls - for (const item of displayItems) { - if (item.type === "toolCall") { - container.addChild( - new Text( - theme.fg("muted", "→ ") + formatToolCall(item.name, item.args, theme.fg.bind(theme)), - 0, - 0, - ), - ); - } - } - - // Show final output as markdown - if (finalOutput) { - container.addChild(new Spacer(1)); - container.addChild(new Markdown(finalOutput.trim(), 0, 0, mdTheme)); - } - - const stepUsage = formatUsageStats(r.usage, r.model); - if (stepUsage) container.addChild(new Text(theme.fg("dim", stepUsage), 0, 0)); - } - - const usageStr = formatUsageStats(aggregateUsage(details.results)); - if (usageStr) { - container.addChild(new Spacer(1)); - container.addChild(new Text(theme.fg("dim", `Total: ${usageStr}`), 0, 0)); - } - return container; - } - - // Collapsed view - let text = - icon + - " " + - theme.fg("toolTitle", theme.bold("chain ")) + - theme.fg("accent", `${successCount}/${details.results.length} steps`); - for (const r of details.results) { - const rIcon = r.exitCode === 0 ? theme.fg("success", "✓") : theme.fg("error", "✗"); - const displayItems = getDisplayItems(r.messages); - text += `\n\n${theme.fg("muted", `─── Step ${r.step}: `)}${theme.fg("accent", r.agent)} ${rIcon}`; - if (displayItems.length === 0) text += `\n${theme.fg("muted", "(no output)")}`; - else text += `\n${renderDisplayItems(displayItems, 5)}`; - } - const usageStr = formatUsageStats(aggregateUsage(details.results)); - if (usageStr) text += `\n\n${theme.fg("dim", `Total: ${usageStr}`)}`; - text += `\n${theme.fg("muted", "(Ctrl+O to expand)")}`; - return new Text(text, 0, 0); - } - - if (details.mode === "parallel") { - const running = details.results.filter((r) => r.exitCode === -1).length; - const successCount = details.results.filter((r) => r.exitCode === 0).length; - const failCount = details.results.filter((r) => r.exitCode > 0).length; - const isRunning = running > 0; - const icon = isRunning - ? theme.fg("warning", "⏳") - : failCount > 0 - ? theme.fg("warning", "◐") - : theme.fg("success", "✓"); - const status = isRunning - ? `${successCount + failCount}/${details.results.length} done, ${running} running` - : `${successCount}/${details.results.length} tasks`; - - if (expanded && !isRunning) { - const container = new Container(); - container.addChild( - new Text( - `${icon} ${theme.fg("toolTitle", theme.bold("parallel "))}${theme.fg("accent", status)}`, - 0, - 0, - ), - ); - - for (const r of details.results) { - const rIcon = r.exitCode === 0 ? theme.fg("success", "✓") : theme.fg("error", "✗"); - const displayItems = getDisplayItems(r.messages); - const finalOutput = getFinalOutput(r.messages); - - container.addChild(new Spacer(1)); - container.addChild( - new Text(`${theme.fg("muted", "─── ") + theme.fg("accent", r.agent)} ${rIcon}`, 0, 0), - ); - container.addChild(new Text(theme.fg("muted", "Task: ") + theme.fg("dim", r.task), 0, 0)); - - // Show tool calls - for (const item of displayItems) { - if (item.type === "toolCall") { - container.addChild( - new Text( - theme.fg("muted", "→ ") + formatToolCall(item.name, item.args, theme.fg.bind(theme)), - 0, - 0, - ), - ); - } - } - - // Show final output as markdown - if (finalOutput) { - container.addChild(new Spacer(1)); - container.addChild(new Markdown(finalOutput.trim(), 0, 0, mdTheme)); - } - - const taskUsage = formatUsageStats(r.usage, r.model); - if (taskUsage) container.addChild(new Text(theme.fg("dim", taskUsage), 0, 0)); - } - - const usageStr = formatUsageStats(aggregateUsage(details.results)); - if (usageStr) { - container.addChild(new Spacer(1)); - container.addChild(new Text(theme.fg("dim", `Total: ${usageStr}`), 0, 0)); - } - return container; - } - - // Collapsed view (or still running) - let text = `${icon} ${theme.fg("toolTitle", theme.bold("parallel "))}${theme.fg("accent", status)}`; - for (const r of details.results) { - const rIcon = - r.exitCode === -1 - ? theme.fg("warning", "⏳") - : r.exitCode === 0 - ? theme.fg("success", "✓") - : theme.fg("error", "✗"); - const displayItems = getDisplayItems(r.messages); - text += `\n\n${theme.fg("muted", "─── ")}${theme.fg("accent", r.agent)} ${rIcon}`; - if (displayItems.length === 0) - text += `\n${theme.fg("muted", r.exitCode === -1 ? "(running...)" : "(no output)")}`; - else text += `\n${renderDisplayItems(displayItems, 5)}`; - } - if (!isRunning) { - const usageStr = formatUsageStats(aggregateUsage(details.results)); - if (usageStr) text += `\n\n${theme.fg("dim", `Total: ${usageStr}`)}`; - } - if (!expanded) text += `\n${theme.fg("muted", "(Ctrl+O to expand)")}`; - return new Text(text, 0, 0); - } - - const text = result.content[0]; - return new Text(text?.type === "text" ? text.text : "(no output)", 0, 0); - }, - }); -} diff --git a/extensions/subagent/prompts/implement-and-review.md b/extensions/subagent/prompts/implement-and-review.md deleted file mode 100644 index 6493b3d..0000000 --- a/extensions/subagent/prompts/implement-and-review.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -description: Worker implements, reviewer reviews, worker applies feedback ---- -Use the subagent tool with the chain parameter to execute this workflow: - -1. First, use the "worker" agent to implement: $@ -2. Then, use the "reviewer" agent to review the implementation from the previous step (use {previous} placeholder) -3. Finally, use the "worker" agent to apply the feedback from the review (use {previous} placeholder) - -Execute this as a chain, passing output between steps via {previous}. diff --git a/extensions/subagent/prompts/implement.md b/extensions/subagent/prompts/implement.md deleted file mode 100644 index 559da4d..0000000 --- a/extensions/subagent/prompts/implement.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -description: Full implementation workflow - scout gathers context, planner creates plan, worker implements ---- -Use the subagent tool with the chain parameter to execute this workflow: - -1. First, use the "scout" agent to find all code relevant to: $@ -2. Then, use the "planner" agent to create an implementation plan for "$@" using the context from the previous step (use {previous} placeholder) -3. Finally, use the "worker" agent to implement the plan from the previous step (use {previous} placeholder) - -Execute this as a chain, passing output between steps via {previous}. diff --git a/extensions/subagent/prompts/scout-and-plan.md b/extensions/subagent/prompts/scout-and-plan.md deleted file mode 100644 index 093b633..0000000 --- a/extensions/subagent/prompts/scout-and-plan.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -description: Scout gathers context, planner creates implementation plan (no implementation) ---- -Use the subagent tool with the chain parameter to execute this workflow: - -1. First, use the "scout" agent to find all code relevant to: $@ -2. Then, use the "planner" agent to create an implementation plan for "$@" using the context from the previous step (use {previous} placeholder) - -Execute this as a chain, passing output between steps via {previous}. Do NOT implement - just return the plan. diff --git a/settings.json b/settings.json index 62bbc39..68953ee 100644 --- a/settings.json +++ b/settings.json @@ -5,7 +5,8 @@ "packages": [ "npm:@sherif-fanous/pi-catppuccin", "npm:pi-mcp-adapter", - "npm:pi-web-access" + "npm:pi-web-access", + "npm:pi-subagents" ], "theme": "catppuccin-frappe", "editorPaddingX": 0,