/** * Pi-CodeMachine - Universal Workflow Orchestration Extension * * Multi-agent workflow orchestration for ANY complex tasks with: * - Workflow creation and execution * - Parallel and sequential agent coordination * - Real-time status monitoring * - Result aggregation and synthesis * - Checkpoint and resume capabilities * - Support for coding, research, writing, analysis, and more */ import { spawn } from "node:child_process"; import * as fs from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; import type { Message } from "@mariozechner/pi-ai"; import { StringEnum } from "@mariozechner/pi-ai"; import { type ExtensionAPI, getMarkdownTheme, withFileMutationQueue, } from "@mariozechner/pi-coding-agent"; import { Container, Markdown, Spacer, Text, } from "@mariozechner/pi-tui"; import { Type } from "@sinclair/typebox"; // ============================================================================= // Configuration & Constants // ============================================================================= const MAX_PARALLEL_TASKS = 16; const MAX_CONCURRENCY = 8; const COLLAPSED_ITEM_COUNT = 10; const WORKFLOW_DIR = ".codemachine"; const CHECKPOINT_INTERVAL_MS = 30000; // ============================================================================= // Type Definitions // ============================================================================= interface WorkflowStep { id: string; name: string; description: string; agent: string; task: string; dependsOn?: string[]; mode?: "single" | "parallel" | "chain" | "mapreduce"; timeout?: number; retryCount?: number; retryDelay?: number; checkpoint?: boolean; condition?: string; // Conditional execution (e.g., "previous.success", "previous.output.includes('error')") transform?: string; // Output transformation for next step } interface Workflow { id: string; name: string; description: string; category?: string; // "coding", "research", "writing", "analysis", "creative", "business", etc. status: "pending" | "running" | "paused" | "completed" | "failed" | "aborted" | "partial"; steps: WorkflowStep[]; createdAt: string; startedAt?: string; completedAt?: string; results: WorkflowResult[]; metadata: Record; tags?: string[]; priority?: "low" | "normal" | "high" | "urgent"; } interface WorkflowResult { stepId: string; status: "pending" | "running" | "completed" | "failed" | "skipped" | "conditional-skipped"; output?: string; error?: string; usage: UsageStats; startedAt?: string; completedAt?: string; agentSource?: "user" | "project" | "builtin" | "unknown"; messages?: Message[]; artifacts?: string[]; // File paths produced by this step } interface UsageStats { input: number; output: number; cacheRead: number; cacheWrite: number; cost: number; contextTokens: number; turns: number; } interface AgentConfig { name: string; description: string; category?: string[]; // "coding", "research", "writing", "analysis", etc. tools?: string[]; model?: string; systemPrompt: string; source: "user" | "project" | "builtin" | "unknown"; capabilities?: string[]; maxTokens?: number; temperature?: number; } interface WorkflowTemplate { name: string; description: string; category: string; steps: Omit[]; tags?: string[]; } // ============================================================================= // Utility Functions // ============================================================================= 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: UsageStats, 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 formatDuration(start?: string, end?: string): string { if (!start) return "N/A"; const startTime = new Date(start).getTime(); const endTime = end ? new Date(end).getTime() : Date.now(); const ms = endTime - startTime; if (ms < 1000) return `${ms}ms`; if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`; if (ms < 3600000) return `${Math.floor(ms / 60000)}m ${Math.floor((ms % 60000) / 1000)}s`; return `${Math.floor(ms / 3600000)}h ${Math.floor((ms % 3600000) / 60000)}m`; } function generateWorkflowId(): string { const timestamp = Date.now().toString(36); const random = Math.random().toString(36).substring(2, 6); return `wf-${timestamp}-${random}`; } function getWorkspaceDir(): string { return path.resolve(process.cwd(), WORKFLOW_DIR); } async function ensureWorkspace(): Promise { const workspace = getWorkspaceDir(); await fs.mkdir(workspace, { recursive: true }); await fs.mkdir(path.join(workspace, "workflows"), { recursive: true }); await fs.mkdir(path.join(workspace, "results"), { recursive: true }); await fs.mkdir(path.join(workspace, "logs"), { recursive: true }); await fs.mkdir(path.join(workspace, "state"), { recursive: true }); await fs.mkdir(path.join(workspace, "artifacts"), { recursive: true }); return workspace; } // ============================================================================= // Agent Discovery // ============================================================================= async function discoverAgents( agentScope: "user" | "project" | "both" | "builtin" = "user", category?: string ): Promise { const agents: AgentConfig[] = []; const home = os.homedir(); // Built-in agents if (agentScope === "builtin" || agentScope === "both") { agents.push(...getBuiltinAgents()); } // User-level agents if (agentScope === "user" || agentScope === "both") { const userDir = path.join(home, ".pi/agent/agents"); try { const entries = await fs.readdir(userDir, { withFileTypes: true }); for (const entry of entries) { if (entry.isFile() && entry.name.endsWith(".md")) { const agent = await parseAgentFile( path.join(userDir, entry.name), "user", ); if (agent && (!category || agent.category?.includes(category))) { agents.push(agent); } } } } catch { // Directory doesn't exist } } // Project-level agents if (agentScope === "project" || agentScope === "both") { const projectDir = path.join(process.cwd(), ".pi/agents"); try { const entries = await fs.readdir(projectDir, { withFileTypes: true }); for (const entry of entries) { if (entry.isFile() && entry.name.endsWith(".md")) { const agent = await parseAgentFile( path.join(projectDir, entry.name), "project", ); if (agent && (!category || agent.category?.includes(category))) { agents.push(agent); } } } } catch { // Directory doesn't exist } } return agents; } function getBuiltinAgents(): AgentConfig[] { return [ { name: "generalist", description: "General-purpose agent for any task", category: ["general"], systemPrompt: "You are a capable assistant that can help with a wide variety of tasks. Be thorough, accurate, and helpful.", source: "builtin", capabilities: ["analysis", "writing", "coding", "research"], }, { name: "researcher", description: "Research and information gathering specialist", category: ["research", "analysis"], systemPrompt: "You are a research specialist. Your job is to gather information, analyze sources, and synthesize findings. Be thorough and cite your sources when possible.", source: "builtin", capabilities: ["research", "analysis", "synthesis"], }, { name: "writer", description: "Content creation and writing specialist", category: ["writing", "creative"], systemPrompt: "You are a writing specialist. Create clear, engaging, and well-structured content. Adapt your tone and style to the task and audience.", source: "builtin", capabilities: ["writing", "editing", "creative"], }, { name: "analyst", description: "Data analysis and insights specialist", category: ["analysis", "research"], systemPrompt: "You are an analysis specialist. Examine data, identify patterns, draw conclusions, and provide actionable insights. Be objective and data-driven.", source: "builtin", capabilities: ["analysis", "data-processing", "insights"], }, { name: "critic", description: "Review and critique specialist", category: ["review", "analysis"], systemPrompt: "You are a critical reviewer. Examine work carefully, identify issues, suggest improvements, and provide constructive feedback. Be thorough but fair.", source: "builtin", capabilities: ["review", "critique", "quality-assurance"], }, { name: "planner", description: "Strategic planning and organization specialist", category: ["planning", "business"], systemPrompt: "You are a planning specialist. Break down complex goals into actionable steps, identify dependencies, and create clear execution plans.", source: "builtin", capabilities: ["planning", "organization", "strategy"], }, { name: "coder", description: "Software development specialist", category: ["coding", "technical"], systemPrompt: "You are a software development specialist. Write clean, efficient, and well-documented code. Follow best practices and consider edge cases.", source: "builtin", capabilities: ["coding", "debugging", "architecture"], }, { name: "reviewer", description: "Code review specialist", category: ["coding", "review"], systemPrompt: "You are a code review specialist. Examine code for bugs, security issues, performance problems, and maintainability concerns. Provide specific, actionable feedback.", source: "builtin", capabilities: ["code-review", "security", "optimization"], }, { name: "scout", description: "Fast reconnaissance and exploration specialist", category: ["research", "coding", "general"], systemPrompt: "You are a reconnaissance specialist. Quickly explore and map out territories (codebases, documents, topics). Provide concise summaries of structure and key findings.", source: "builtin", capabilities: ["exploration", "summarization", "mapping"], }, { name: "creative", description: "Creative ideation and brainstorming specialist", category: ["creative", "writing"], systemPrompt: "You are a creative specialist. Generate novel ideas, brainstorm possibilities, and think outside the box. Be imaginative and inspiring.", source: "builtin", capabilities: ["ideation", "brainstorming", "creative-thinking"], }, { name: "editor", description: "Editing and refinement specialist", category: ["writing", "review"], systemPrompt: "You are an editing specialist. Improve clarity, grammar, style, and flow. Polish content while preserving the author's voice and intent.", source: "builtin", capabilities: ["editing", "proofreading", "refinement"], }, { name: "summarizer", description: "Summarization and condensation specialist", category: ["analysis", "writing"], systemPrompt: "You are a summarization specialist. Distill complex information into clear, concise summaries. Capture essential points without losing important details.", source: "builtin", capabilities: ["summarization", "synthesis", "distillation"], }, ]; } async function parseAgentFile( filePath: string, source: "user" | "project", ): Promise { try { const content = await fs.readFile(filePath, "utf-8"); const lines = content.split("\n"); // Parse YAML frontmatter if (lines[0] !== "---") return null; const frontmatter: Record = {}; let i = 1; while (i < lines.length && lines[i] !== "---") { const line = lines[i]; const colonIndex = line.indexOf(":"); if (colonIndex > 0) { const key = line.slice(0, colonIndex).trim(); const value = line.slice(colonIndex + 1).trim(); frontmatter[key] = value; } i++; } const systemPrompt = lines.slice(i + 1).join("\n").trim(); return { name: frontmatter.name || path.basename(filePath, ".md"), description: frontmatter.description || "", category: frontmatter.category?.split(",").map((c) => c.trim()), tools: frontmatter.tools?.split(",").map((t) => t.trim()), model: frontmatter.model, systemPrompt, source, capabilities: frontmatter.capabilities?.split(",").map((c) => c.trim()), maxTokens: frontmatter.maxTokens ? parseInt(frontmatter.maxTokens) : undefined, temperature: frontmatter.temperature ? parseFloat(frontmatter.temperature) : undefined, }; } catch { return null; } } async function findAgent( name: string, agentScope: "user" | "project" | "both" | "builtin" ): Promise { // Check built-ins first if included if (agentScope === "builtin" || agentScope === "both") { const builtin = getBuiltinAgents().find((a) => a.name === name); if (builtin) return builtin; } const agents = await discoverAgents(agentScope === "builtin" ? "user" : agentScope); return agents.find((a) => a.name === name) || null; } // ============================================================================= // Subagent Execution // ============================================================================= interface SubagentOptions { agent: string; task: string; timeout?: number; model?: string; tools?: string[]; systemPrompt?: string; agentScope?: "user" | "project" | "both" | "builtin"; maxTokens?: number; temperature?: number; outputFormat?: "text" | "json" | "markdown"; } async function executeSubagent( options: SubagentOptions, onUpdate?: (status: string, messages: Message[]) => void, ): Promise { const { agent, task, timeout = 300000, model, tools, systemPrompt, agentScope = "builtin", maxTokens, temperature, outputFormat = "text", } = options; // Find agent configuration const agentConfig = await findAgent(agent, agentScope); const effectiveModel = model || agentConfig?.model; const effectiveTools = tools || agentConfig?.tools; const effectivePrompt = systemPrompt || agentConfig?.systemPrompt || ""; const effectiveMaxTokens = maxTokens || agentConfig?.maxTokens; const effectiveTemperature = temperature || agentConfig?.temperature; const args: string[] = [ "--no-interactive", "--json", ]; if (effectiveModel) { args.push("--model", effectiveModel); } if (effectiveTools && effectiveTools.length > 0) { args.push("--tools", effectiveTools.join(",")); } if (effectiveMaxTokens) { args.push("--max-tokens", effectiveMaxTokens.toString()); } // Build system prompt with output format instruction let formatInstruction = ""; switch (outputFormat) { case "json": formatInstruction = "\n\nReturn your response as valid JSON with a 'result' field containing your output."; break; case "markdown": formatInstruction = "\n\nFormat your response using Markdown for better readability."; break; } const fullSystemPrompt = `${effectivePrompt}\n\nYou are an autonomous agent working on a specific task. Complete it efficiently and thoroughly.${formatInstruction}`; args.push("--system-prompt", fullSystemPrompt); return new Promise((resolve, reject) => { const startTime = Date.now(); let stdout = ""; let stderr = ""; const messages: Message[] = []; const subProcess = spawn("pi", args, { cwd: process.cwd(), stdio: ["pipe", "pipe", "pipe"], }); subProcess.stdout?.on("data", (data) => { stdout += data.toString(); }); subProcess.stderr?.on("data", (data) => { stderr += data.toString(); }); const timeoutId = setTimeout(() => { subProcess.kill("SIGTERM"); reject(new Error(`Subagent timeout after ${timeout}ms`)); }, timeout); subProcess.on("exit", async (code) => { clearTimeout(timeoutId); try { // Parse messages from stdout (pi --json outputs JSON array of messages) let parsedMessages: Message[] = []; let output = ""; try { // Try to find JSON in stdout const jsonMatch = stdout.match(/\[[\s\S]*\]/); if (jsonMatch) { parsedMessages = JSON.parse(jsonMatch[0]) as Message[]; } // Also try to parse the whole stdout as JSON if (parsedMessages.length === 0) { const parsed = JSON.parse(stdout); if (Array.isArray(parsed)) { parsedMessages = parsed; } } } catch { // Not valid JSON, use raw output output = stdout; } messages.push(...parsedMessages); // Extract final output from messages if available if (messages.length > 0) { const finalMessage = messages[messages.length - 1]; if (finalMessage?.role === "assistant") { const textContent = finalMessage.content.find((c) => c.type === "text")?.text; if (textContent) { output = textContent; } } } // Try to parse JSON if that was the expected format if (outputFormat === "json" && output) { try { const parsed = JSON.parse(output); if (parsed.result !== undefined) { output = parsed.result; } } catch { // Not valid JSON, use raw output } } // Calculate usage stats const usage: UsageStats = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: Math.floor(messages.length / 2), }; resolve({ stepId: "", status: code === 0 ? "completed" : "failed", output: output || stdout, error: code !== 0 ? (stderr || `Exit code: ${code}`) : undefined, usage, startedAt: new Date(startTime).toISOString(), completedAt: new Date().toISOString(), agentSource: agentConfig?.source || "unknown", messages, }); } catch (error) { reject(error); } }); // Send the task via stdin subProcess.stdin?.write(task); subProcess.stdin?.end(); }); } // ============================================================================= // Workflow Engine // ============================================================================= class WorkflowEngine { private workflows: Map = new Map(); private activeExecutions: Map = new Map(); async createWorkflow( name: string, description: string, steps: WorkflowStep[], options?: { category?: string; tags?: string[]; priority?: "low" | "normal" | "high" | "urgent"; metadata?: Record; } ): Promise { const workflow: Workflow = { id: generateWorkflowId(), name, description, category: options?.category, status: "pending", steps, createdAt: new Date().toISOString(), results: steps.map((step) => ({ stepId: step.id, status: "pending", usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0, }, })), metadata: options?.metadata || {}, tags: options?.tags, priority: options?.priority, }; await this.saveWorkflow(workflow); this.workflows.set(workflow.id, workflow); return workflow; } async runWorkflow( workflowId: string, options?: { onProgress?: (workflow: Workflow, stepId: string, status: string) => void; onStepComplete?: (workflow: Workflow, step: WorkflowStep, result: WorkflowResult) => void; } ): Promise { const workflow = this.workflows.get(workflowId); if (!workflow) { throw new Error(`Workflow ${workflowId} not found`); } workflow.status = "running"; workflow.startedAt = new Date().toISOString(); await this.saveWorkflow(workflow); const abortController = new AbortController(); this.activeExecutions.set(workflowId, abortController); // Build dependency graph const pendingSteps = new Set(workflow.steps.map((s) => s.id)); const completedSteps = new Set(); const failedSteps = new Set(); const stepOutputs = new Map(); try { while (pendingSteps.size > 0 && !abortController.signal.aborted) { // Find ready steps (all dependencies satisfied and conditions met) const readySteps = workflow.steps.filter((step) => { if (!pendingSteps.has(step.id)) return false; // Check dependencies const depsSatisfied = (step.dependsOn || []).every((dep) => completedSteps.has(dep)); if (!depsSatisfied) return false; // Check condition if (step.condition) { const conditionMet = this.evaluateCondition(step.condition, stepOutputs, failedSteps); if (!conditionMet) { // Skip this step const result = workflow.results.find((r) => r.stepId === step.id)!; result.status = "conditional-skipped"; pendingSteps.delete(step.id); options?.onProgress?.(workflow, step.id, "conditional-skipped"); return false; } } return true; }); if (readySteps.length === 0 && pendingSteps.size > 0) { // Check if all remaining are stuck due to failed dependencies const remaining = Array.from(pendingSteps); let progressed = false; for (const stepId of remaining) { const step = workflow.steps.find((s) => s.id === stepId)!; const failedDeps = (step.dependsOn || []).filter((dep) => failedSteps.has(dep)); if (failedDeps.length > 0) { const result = workflow.results.find((r) => r.stepId === stepId)!; result.status = "skipped"; result.error = `Skipped due to failed dependencies: ${failedDeps.join(", ")}`; failedSteps.add(stepId); pendingSteps.delete(stepId); options?.onProgress?.(workflow, stepId, "skipped"); progressed = true; } } if (!progressed) { // Circular dependency detected for (const stepId of remaining) { const result = workflow.results.find((r) => r.stepId === stepId)!; result.status = "failed"; result.error = "Circular dependency or unresolved condition"; failedSteps.add(stepId); pendingSteps.delete(stepId); options?.onProgress?.(workflow, stepId, "failed"); } } continue; } // Execute ready steps (with concurrency limit) const batchSize = Math.min(readySteps.length, MAX_CONCURRENCY); const batch = readySteps.slice(0, batchSize); await Promise.all( batch.map(async (step) => { if (abortController.signal.aborted) return; const result = workflow.results.find((r) => r.stepId === step.id)!; result.status = "running"; result.startedAt = new Date().toISOString(); options?.onProgress?.(workflow, step.id, "running"); try { // Transform task if needed (substitute previous outputs) let task = step.task; for (const [stepId, output] of stepOutputs) { task = task.replace(new RegExp(`\\{${stepId}\\}`, "g"), output); } task = task.replace(/\{previous\}/g, Array.from(stepOutputs.values()).pop() || ""); // Execute the step const subagentResult = await executeSubagent( { agent: step.agent, task, timeout: step.timeout, }, (status, messages) => { options?.onProgress?.(workflow, step.id, status); }, ); // Update result result.status = subagentResult.status; result.output = subagentResult.output; result.error = subagentResult.error; result.usage = subagentResult.usage; result.completedAt = subagentResult.completedAt; result.agentSource = subagentResult.agentSource; result.messages = subagentResult.messages; if (subagentResult.output) { stepOutputs.set(step.id, subagentResult.output); } if (subagentResult.status === "completed") { completedSteps.add(step.id); } else { failedSteps.add(step.id); } // Handle retry if (result.status === "failed" && step.retryCount && step.retryCount > 0) { step.retryCount--; result.status = "pending"; if (step.retryDelay) { await new Promise((r) => setTimeout(r, step.retryDelay)); } return; } pendingSteps.delete(step.id); options?.onStepComplete?.(workflow, step, result); options?.onProgress?.(workflow, step.id, result.status); // Save checkpoint if enabled if (step.checkpoint) { await this.saveWorkflow(workflow); } } catch (error) { result.status = "failed"; result.error = error instanceof Error ? error.message : String(error); result.completedAt = new Date().toISOString(); failedSteps.add(step.id); pendingSteps.delete(step.id); options?.onStepComplete?.(workflow, step, result); options?.onProgress?.(workflow, step.id, "failed"); } }), ); } // Determine final status if (abortController.signal.aborted) { workflow.status = "aborted"; } else if (failedSteps.size > 0) { // Check if any steps actually completed const completedCount = workflow.results.filter((r) => r.status === "completed").length; workflow.status = completedCount > 0 ? "partial" : "failed"; } else { workflow.status = "completed"; } workflow.completedAt = new Date().toISOString(); await this.saveWorkflow(workflow); return workflow; } finally { this.activeExecutions.delete(workflowId); } } private evaluateCondition( condition: string, stepOutputs: Map, failedSteps: Set ): boolean { // Simple condition evaluation // Supports: "previous.success", "previous.failure", "stepId.success", "stepId.failure" // "output.includes('text')", "output.startsWith('text')" condition = condition.trim(); // Handle special conditions if (condition === "always") return true; if (condition === "never") return false; // Check for step.success or step.failure const successMatch = condition.match(/^(\w+)\.success$/); if (successMatch) { const stepId = successMatch[1]; if (stepId === "previous") { const lastOutput = Array.from(stepOutputs.values()).pop(); return lastOutput !== undefined && !failedSteps.has(Array.from(stepOutputs.keys()).pop() || ""); } return stepOutputs.has(stepId) && !failedSteps.has(stepId); } const failureMatch = condition.match(/^(\w+)\.failure$/); if (failureMatch) { const stepId = failureMatch[1]; if (stepId === "previous") { const lastStepId = Array.from(stepOutputs.keys()).pop(); return lastStepId ? failedSteps.has(lastStepId) : false; } return failedSteps.has(stepId); } // Check for output contains const containsMatch = condition.match(/output\.includes\(['"](.+)['"]\)/); if (containsMatch) { const searchText = containsMatch[1]; const lastOutput = Array.from(stepOutputs.values()).pop() || ""; return lastOutput.includes(searchText); } // Default: try to evaluate as boolean try { return Boolean(JSON.parse(condition)); } catch { return true; // Default to running if condition can't be parsed } } async abortWorkflow(workflowId: string): Promise { const controller = this.activeExecutions.get(workflowId); if (controller) { controller.abort(); } const workflow = this.workflows.get(workflowId); if (workflow) { workflow.status = "aborted"; workflow.completedAt = new Date().toISOString(); await this.saveWorkflow(workflow); } } async pauseWorkflow(workflowId: string): Promise { const controller = this.activeExecutions.get(workflowId); if (controller) { controller.abort(); } const workflow = this.workflows.get(workflowId); if (workflow && workflow.status === "running") { workflow.status = "paused"; await this.saveWorkflow(workflow); } } async resumeWorkflow( workflowId: string, options?: { onProgress?: (workflow: Workflow, stepId: string, status: string) => void; onStepComplete?: (workflow: Workflow, step: WorkflowStep, result: WorkflowResult) => void; } ): Promise { const workflow = await this.getWorkflow(workflowId); if (!workflow) { throw new Error(`Workflow ${workflowId} not found`); } if (workflow.status !== "paused" && workflow.status !== "partial") { throw new Error(`Cannot resume workflow with status: ${workflow.status}`); } // Reset failed/skipped steps that can be retried for (const result of workflow.results) { if (result.status === "failed" || result.status === "skipped") { const step = workflow.steps.find((s) => s.id === result.stepId)!; if (!step.dependsOn?.some((dep) => { const depResult = workflow.results.find((r) => r.stepId === dep); return depResult?.status === "failed"; })) { result.status = "pending"; result.error = undefined; } } } return this.runWorkflow(workflowId, options); } async getWorkflow(workflowId: string): Promise { // Check memory first if (this.workflows.has(workflowId)) { return this.workflows.get(workflowId)!; } // Load from disk try { const workspace = getWorkspaceDir(); const filePath = path.join(workspace, "workflows", `${workflowId}.json`); const content = await fs.readFile(filePath, "utf-8"); const workflow = JSON.parse(content) as Workflow; this.workflows.set(workflowId, workflow); return workflow; } catch { return null; } } async listWorkflows(filters?: { status?: string; category?: string; tag?: string; }): Promise { const workspace = getWorkspaceDir(); const workflows: Workflow[] = []; try { const entries = await fs.readdir(path.join(workspace, "workflows")); for (const entry of entries) { if (entry.endsWith(".json")) { const workflowId = entry.replace(".json", ""); const workflow = await this.getWorkflow(workflowId); if (workflow) { // Apply filters if (filters?.status && workflow.status !== filters.status) continue; if (filters?.category && workflow.category !== filters.category) continue; if (filters?.tag && !workflow.tags?.includes(filters.tag)) continue; workflows.push(workflow); } } } } catch { // Directory doesn't exist } return workflows.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime() ); } async deleteWorkflow(workflowId: string): Promise { const workflow = await this.getWorkflow(workflowId); if (!workflow) return false; const workspace = getWorkspaceDir(); const filePath = path.join(workspace, "workflows", `${workflowId}.json`); await fs.unlink(filePath).catch(() => {}); this.workflows.delete(workflowId); return true; } private async saveWorkflow(workflow: Workflow): Promise { const workspace = await ensureWorkspace(); const filePath = path.join(workspace, "workflows", `${workflow.id}.json`); await fs.writeFile(filePath, JSON.stringify(workflow, null, 2)); } } const workflowEngine = new WorkflowEngine(); // ============================================================================= // Workflow Templates // ============================================================================= const WORKFLOW_TEMPLATES: WorkflowTemplate[] = [ // Research & Analysis Templates { name: "deep-research", description: "Comprehensive research with multiple angles and synthesis", category: "research", tags: ["research", "analysis", "synthesis"], steps: [ { id: "scout", name: "Initial Exploration", description: "Quick reconnaissance of the topic", agent: "scout", task: "Explore the topic: {input}. Identify key areas, sources, and perspectives to investigate." }, { id: "gather-1", name: "Angle 1 Research", description: "Research from first angle", agent: "researcher", task: "Deep research on angle 1 of: {input}. Focus on: [specific angle from scout]." }, { id: "gather-2", name: "Angle 2 Research", description: "Research from second angle", agent: "researcher", task: "Deep research on angle 2 of: {input}. Focus on: [specific angle from scout]." }, { id: "gather-3", name: "Angle 3 Research", description: "Research from third angle", agent: "researcher", task: "Deep research on angle 3 of: {input}. Focus on: [specific angle from scout]." }, { id: "analyze", name: "Analysis", description: "Analyze findings", agent: "analyst", task: "Analyze these research findings:\n{gather-1}\n{gather-2}\n{gather-3}\n\nIdentify patterns, conflicts, and key insights." }, { id: "synthesize", name: "Synthesis", description: "Synthesize final report", agent: "summarizer", task: "Create a comprehensive research report from:\n\nContext: {input}\n\nResearch findings:\n{gather-1}\n{gather-2}\n{gather-3}\n\nAnalysis: {analyze}\n\nProduce a well-structured, balanced report." }, ], }, // Writing Templates { name: "polished-content", description: "Create content with drafting, review, and refinement", category: "writing", tags: ["writing", "editing", "content-creation"], steps: [ { id: "plan", name: "Content Planning", description: "Plan the content structure", agent: "planner", task: "Create a detailed outline for: {input}\n\nInclude structure, key points, and flow." }, { id: "draft", name: "First Draft", description: "Write initial draft", agent: "writer", task: "Write the first draft based on this plan:\n{plan}\n\nOriginal request: {input}" }, { id: "review", name: "Content Review", description: "Review and critique", agent: "critic", task: "Review this draft critically:\n{draft}\n\nIdentify strengths, weaknesses, and areas for improvement." }, { id: "refine", name: "Refinement", description: "Refine based on feedback", agent: "editor", task: "Improve this draft based on the review:\n\nDraft:\n{draft}\n\nReview feedback:\n{review}\n\nProduce the final polished version." }, ], }, // Coding Templates { name: "implement-feature", description: "Full feature implementation with planning, coding, and review", category: "coding", tags: ["coding", "implementation", "development"], steps: [ { id: "scout", name: "Codebase Exploration", description: "Explore relevant code", agent: "scout", task: "Find all code related to: {input}. Identify existing patterns, relevant files, and integration points." }, { id: "plan", name: "Implementation Plan", description: "Plan the implementation", agent: "planner", task: "Create an implementation plan for: {input}\n\nBased on codebase context:\n{scout}\n\nInclude file changes, function signatures, and integration steps." }, { id: "implement", name: "Implementation", description: "Write the code", agent: "coder", task: "Implement this feature based on the plan:\n{plan}\n\nCodebase context:\n{scout}\n\nOriginal request: {input}" }, { id: "review", name: "Code Review", description: "Review the implementation", agent: "reviewer", task: "Review this implementation:\n{implement}\n\nCheck for bugs, security issues, performance, and maintainability." }, { id: "fix", name: "Fix Issues", description: "Address review feedback", agent: "coder", task: "Address these review issues:\n\nOriginal implementation:\n{implement}\n\nReview feedback:\n{review}\n\nProduce the corrected code." }, ], }, // Analysis Templates { name: "comprehensive-analysis", description: "Multi-perspective analysis with synthesis", category: "analysis", tags: ["analysis", "evaluation", "assessment"], steps: [ { id: "data-gather", name: "Data Gathering", description: "Collect relevant data", agent: "researcher", task: "Gather all relevant information for analyzing: {input}" }, { id: "technical", name: "Technical Analysis", description: "Technical perspective", agent: "analyst", task: "Analyze from technical perspective:\n{data-gather}\n\nFocus on feasibility, complexity, and technical requirements." }, { id: "business", name: "Business Analysis", description: "Business perspective", agent: "analyst", task: "Analyze from business perspective:\n{data-gather}\n\nFocus on ROI, market impact, and strategic alignment." }, { id: "risk", name: "Risk Analysis", description: "Risk perspective", agent: "critic", task: "Identify risks and concerns:\n{data-gather}\n\nConsider technical, business, and operational risks." }, { id: "synthesis", name: "Synthesis", description: "Combine all analyses", agent: "analyst", task: "Synthesize these analyses into a comprehensive assessment:\n\nTechnical: {technical}\n\nBusiness: {business}\n\nRisks: {risk}\n\nProvide clear recommendations." }, ], }, // Creative Templates { name: "creative-project", description: "Creative project with ideation, development, and refinement", category: "creative", tags: ["creative", "ideation", "design"], steps: [ { id: "ideate", name: "Ideation", description: "Generate ideas", agent: "creative", task: "Generate diverse creative ideas for: {input}. Explore different approaches and concepts." }, { id: "develop", name: "Development", description: "Develop selected concept", agent: "writer", task: "Develop the most promising idea from:\n{ideate}\n\nFlesh out the details and create a full concept." }, { id: "refine", name: "Refinement", description: "Polish and improve", agent: "editor", task: "Refine this creative work:\n{develop}\n\nEnhance clarity, impact, and polish while preserving the creative vision." }, ], }, // Decision Making { name: "informed-decision", description: "Structured decision-making process", category: "business", tags: ["decision", "evaluation", "strategy"], steps: [ { id: "options", name: "Option Generation", description: "Generate alternatives", agent: "creative", task: "Generate diverse options for this decision:\n{input}\n\nInclude conventional and unconventional alternatives." }, { id: "criteria", name: "Criteria Definition", description: "Define evaluation criteria", agent: "planner", task: "Define clear criteria for evaluating options related to:\n{input}" }, { id: "evaluate", name: "Option Evaluation", description: "Evaluate each option", agent: "analyst", task: "Evaluate these options:\n{options}\n\nAgainst these criteria:\n{criteria}\n\nProvide a structured assessment." }, { id: "recommend", name: "Recommendation", description: "Make recommendation", agent: "analyst", task: "Based on this evaluation:\n{evaluate}\n\nProvide a clear recommendation with rationale for:\n{input}" }, ], }, // Quality Assurance { name: "quality-assurance", description: "Multi-stage quality review process", category: "review", tags: ["qa", "review", "quality"], steps: [ { id: "initial-review", name: "Initial Review", description: "First pass review", agent: "critic", task: "Review this work:\n{input}\n\nIdentify obvious issues and areas of concern." }, { id: "deep-review", name: "Deep Review", description: "Detailed analysis", agent: "critic", task: "Perform a detailed review of:\n{input}\n\nConsider edge cases, completeness, and quality standards. Initial findings: {initial-review}" }, { id: "improve", name: "Improvement", description: "Address issues", agent: "generalist", task: "Improve this work based on the reviews:\n\nOriginal:\n{input}\n\nReview 1:\n{initial-review}\n\nReview 2:\n{deep-review}\n\nProduce the corrected version." }, { id: "final-check", name: "Final Verification", description: "Verify quality", agent: "critic", task: "Final quality check of:\n{improve}\n\nConfirm all issues are resolved." }, ], }, ]; // ============================================================================= // Extension Registration // ============================================================================= export default function (pi: ExtensionAPI) { // Ensure workspace on startup pi.on("session_start", async () => { await ensureWorkspace(); }); // ============================================================================= // Tool: cm_workflow_create // ============================================================================= pi.registerTool({ name: "cm_workflow_create", label: "Create Workflow", description: "Create a new multi-agent workflow with defined steps, dependencies, and configuration", parameters: Type.Object({ name: Type.String({ description: "Workflow name" }), description: Type.String({ description: "Workflow description" }), steps: Type.Array( Type.Object({ id: Type.String({ description: "Unique step identifier" }), name: Type.String({ description: "Step name" }), description: Type.String({ description: "Step description" }), agent: Type.String({ description: "Agent to execute this step" }), task: Type.String({ description: "Task instructions for the agent" }), dependsOn: Type.Optional(Type.Array(Type.String(), { description: "IDs of steps that must complete before this one", })), timeout: Type.Optional(Type.Number({ description: "Timeout in milliseconds (default: 300000)", })), retryCount: Type.Optional(Type.Number({ description: "Number of retries on failure", })), retryDelay: Type.Optional(Type.Number({ description: "Delay between retries in milliseconds", })), checkpoint: Type.Optional(Type.Boolean({ description: "Save checkpoint after this step completes", })), condition: Type.Optional(Type.String({ description: "Condition for execution (e.g., 'previous.success', 'output.includes(\"error\")')", })), }), { description: "Workflow steps" }, ), category: Type.Optional(Type.String({ description: "Workflow category (e.g., coding, research, writing, analysis, creative, business)", })), tags: Type.Optional(Type.Array(Type.String(), { description: "Workflow tags" })), priority: Type.Optional(StringEnum(["low", "normal", "high", "urgent"], { description: "Workflow priority", })), metadata: Type.Optional(Type.Record(Type.String(), Type.Unknown(), { description: "Additional workflow metadata", })), }), async execute(toolCallId, params, signal, onUpdate, ctx) { const { name, description, steps, category, tags, priority, metadata } = params; const workflow = await workflowEngine.createWorkflow(name, description, steps, { category, tags, priority, metadata, }); return { content: [ { type: "text", text: `✓ Created workflow "${name}" (${workflow.id})\nCategory: ${category || "general"}\nPriority: ${priority || "normal"}\nSteps: ${steps.length}`, }, ], details: { workflowId: workflow.id, category, priority, stepCount: steps.length, steps: steps.map((s) => ({ id: s.id, name: s.name, agent: s.agent, dependsOn: s.dependsOn || [], condition: s.condition, })), }, }; }, }); // ============================================================================= // Tool: cm_workflow_from_template // ============================================================================= pi.registerTool({ name: "cm_workflow_from_template", label: "Create Workflow from Template", description: "Create a workflow using a predefined template", parameters: Type.Object({ template: Type.String({ description: "Template name", enum: WORKFLOW_TEMPLATES.map((t) => t.name), }), input: Type.String({ description: "The main input/topic for the workflow" }), name: Type.Optional(Type.String({ description: "Custom workflow name (default: uses template name)" })), customizations: Type.Optional(Type.Record(Type.String(), Type.String(), { description: "Template customizations (replace placeholder text)", })), }), async execute(toolCallId, params, signal, onUpdate, ctx) { const { template: templateName, input, name, customizations } = params; const template = WORKFLOW_TEMPLATES.find((t) => t.name === templateName); if (!template) { return { content: [{ type: "text", text: `✗ Template "${templateName}" not found` }], details: { availableTemplates: WORKFLOW_TEMPLATES.map((t) => t.name) }, isError: true, }; } // Apply customizations to steps const steps: WorkflowStep[] = template.steps.map((step, index) => ({ ...step, id: step.id || `step-${index}`, task: Object.entries(customizations || {}).reduce( (task, [key, value]) => task.replace(new RegExp(key, "g"), value), step.task.replace(/\{input\}/g, input) ), })); const workflowName = name || `${template.name}-${Date.now()}`; const workflow = await workflowEngine.createWorkflow(workflowName, template.description, steps, { category: template.category, tags: template.tags, }); return { content: [ { type: "text", text: `✓ Created workflow from template "${templateName}"\nName: ${workflowName}\nCategory: ${template.category}\nSteps: ${steps.length}\n\nSteps:\n${steps.map((s, i) => `${i + 1}. ${s.name} (${s.agent})`).join("\n")}`, }, ], details: { workflowId: workflow.id, template: templateName, category: template.category, tags: template.tags, steps: steps.map((s) => s.id), }, }; }, }); // ============================================================================= // Tool: cm_workflow_run // ============================================================================= pi.registerTool({ name: "cm_workflow_run", label: "Run Workflow", description: "Execute a workflow, spawning agents and managing parallel/sequential execution", parameters: Type.Object({ workflowId: Type.String({ description: "Workflow ID to execute" }), watch: Type.Optional(Type.Boolean({ description: "Stream progress updates", default: true, })), }), async execute(toolCallId, params, signal, onUpdate, ctx) { const { workflowId, watch = true } = params; const workflow = await workflowEngine.getWorkflow(workflowId); if (!workflow) { return { content: [{ type: "text", text: `✗ Workflow ${workflowId} not found` }], details: { error: "Workflow not found" }, isError: true, }; } if (workflow.status === "running") { return { content: [{ type: "text", text: `✗ Workflow ${workflowId} is already running` }], details: { error: "Workflow already running" }, isError: true, }; } const completedSteps = new Set(); const failedSteps = new Set(); const runWorkflow = await workflowEngine.runWorkflow(workflowId, { onProgress: (wf, stepId, status) => { if (status === "completed") completedSteps.add(stepId); if (status === "failed") failedSteps.add(stepId); if (watch) { const step = wf.steps.find((s) => s.id === stepId); const emoji = status === "completed" ? "✓" : status === "failed" ? "✗" : status === "skipped" ? "⊘" : status === "conditional-skipped" ? "⊘" : "⏳"; onUpdate({ content: [ { type: "text", text: `${emoji} [${wf.name}] Step "${step?.name || stepId}": ${status} (${completedSteps.size}/${wf.steps.length})`, }, ], }); } }, }); const successCount = runWorkflow.results.filter((r) => r.status === "completed").length; const failedCount = runWorkflow.results.filter((r) => r.status === "failed").length; const skippedCount = runWorkflow.results.filter((r) => r.status === "skipped" || r.status === "conditional-skipped").length; const totalUsage = runWorkflow.results.reduce( (acc, r) => ({ input: acc.input + r.usage.input, output: acc.output + r.usage.output, cacheRead: acc.cacheRead + r.usage.cacheRead, cacheWrite: acc.cacheWrite + r.usage.cacheWrite, cost: acc.cost + r.usage.cost, contextTokens: acc.contextTokens + r.usage.contextTokens, turns: acc.turns + r.usage.turns, }), { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 }, ); const statusEmoji = runWorkflow.status === "completed" ? "✓" : runWorkflow.status === "partial" ? "◐" : "✗"; const summary = `${statusEmoji} Workflow "${runWorkflow.name}" ${runWorkflow.status}\n\n${successCount}/${runWorkflow.steps.length} completed${failedCount > 0 ? `, ${failedCount} failed` : ""}${skippedCount > 0 ? `, ${skippedCount} skipped` : ""}\nDuration: ${formatDuration(runWorkflow.startedAt, runWorkflow.completedAt)}\n${formatUsageStats(totalUsage)}`; return { content: [{ type: "text", text: summary }], details: { workflowId: runWorkflow.id, status: runWorkflow.status, results: runWorkflow.results.map((r) => ({ stepId: r.stepId, status: r.status, output: r.output?.substring(0, 500), error: r.error, })), totalUsage, duration: formatDuration(runWorkflow.startedAt, runWorkflow.completedAt), }, }; }, }); // ============================================================================= // Tool: cm_workflow_resume // ============================================================================= pi.registerTool({ name: "cm_workflow_resume", label: "Resume Workflow", description: "Resume a paused or partially completed workflow", parameters: Type.Object({ workflowId: Type.String({ description: "Workflow ID to resume" }), watch: Type.Optional(Type.Boolean({ description: "Stream progress updates", default: true, })), }), async execute(toolCallId, params, signal, onUpdate, ctx) { const { workflowId, watch = true } = params; const workflow = await workflowEngine.getWorkflow(workflowId); if (!workflow) { return { content: [{ type: "text", text: `✗ Workflow ${workflowId} not found` }], details: { error: "Workflow not found" }, isError: true, }; } if (workflow.status !== "paused" && workflow.status !== "partial") { return { content: [{ type: "text", text: `✗ Workflow cannot be resumed (status: ${workflow.status})` }], details: { error: "Invalid workflow status" }, isError: true, }; } const runWorkflow = await workflowEngine.resumeWorkflow(workflowId, { onProgress: (wf, stepId, status) => { if (watch) { const step = wf.steps.find((s) => s.id === stepId); const emoji = status === "completed" ? "✓" : status === "failed" ? "✗" : "⏳"; onUpdate({ content: [ { type: "text", text: `${emoji} [${wf.name}] Step "${step?.name || stepId}": ${status}`, }, ], }); } }, }); return { content: [{ type: "text", text: `✓ Resumed workflow "${runWorkflow.name}"\nStatus: ${runWorkflow.status}` }], details: { workflowId: runWorkflow.id, status: runWorkflow.status, }, }; }, }); // ============================================================================= // Tool: cm_agent_spawn // ============================================================================= pi.registerTool({ name: "cm_agent_spawn", label: "Spawn Agent", description: "Spawn an individual subagent for a specific task", parameters: Type.Object({ agent: Type.String({ description: "Agent name (use 'cm_list_agents' to see available agents)" }), task: Type.String({ description: "Task for the agent" }), timeout: Type.Optional(Type.Number({ description: "Timeout in milliseconds (default: 300000)" })), agentScope: Type.Optional(StringEnum(["user", "project", "both", "builtin"], { description: "Agent discovery scope (default: builtin)", })), outputFormat: Type.Optional(StringEnum(["text", "json", "markdown"], { description: "Expected output format (default: text)", })), }), async execute(toolCallId, params, signal, onUpdate, ctx) { const { agent, task, timeout, agentScope = "builtin", outputFormat = "text" } = params; const result = await executeSubagent( { agent, task, timeout, agentScope, outputFormat }, (status, messages) => { onUpdate({ content: [{ type: "text", text: `⏳ ${status}` }] }); }, ); const emoji = result.status === "completed" ? "✓" : "✗"; const summary = `${emoji} Agent "${agent}" ${result.status}\nDuration: ${formatDuration(result.startedAt, result.completedAt)}\n${formatUsageStats(result.usage)}`; return { content: [ { type: "text", text: summary }, { type: "text", text: result.output || "" }, ], details: { status: result.status, output: result.output, error: result.error, usage: result.usage, agentSource: result.agentSource, duration: formatDuration(result.startedAt, result.completedAt), }, isError: result.status === "failed", }; }, }); // ============================================================================= // Tool: cm_list_agents // ============================================================================= pi.registerTool({ name: "cm_list_agents", label: "List Available Agents", description: "List all available agents with their capabilities", parameters: Type.Object({ category: Type.Optional(Type.String({ description: "Filter by category" })), agentScope: Type.Optional(StringEnum(["user", "project", "both", "builtin"], { description: "Agent discovery scope (default: all)", })), }), async execute(toolCallId, params, signal, onUpdate, ctx) { const { category, agentScope = "both" } = params; const agents = await discoverAgents(agentScope === "both" ? "both" : agentScope as any, category); const lines = [`Available Agents (${agents.length} total):\n`]; // Group by category const byCategory = new Map(); for (const agent of agents) { for (const cat of agent.category || ["general"]) { if (!byCategory.has(cat)) byCategory.set(cat, []); byCategory.get(cat)!.push(agent); } } for (const [cat, catAgents] of byCategory) { lines.push(`\n## ${cat.toUpperCase()}`); for (const agent of catAgents) { const source = agent.source === "builtin" ? "📦" : agent.source === "user" ? "👤" : "📁"; lines.push(` ${source} ${agent.name} - ${agent.description}`); if (agent.capabilities) { lines.push(` Capabilities: ${agent.capabilities.join(", ")}`); } } } return { content: [{ type: "text", text: lines.join("\n") }], details: { agents: agents.map((a) => ({ name: a.name, description: a.description, category: a.category, source: a.source, capabilities: a.capabilities, })), }, }; }, }); // ============================================================================= // Tool: cm_status_check // ============================================================================= pi.registerTool({ name: "cm_status_check", label: "Check Workflow Status", description: "Get current status of workflows", parameters: Type.Object({ workflowId: Type.Optional(Type.String({ description: "Specific workflow ID" })), all: Type.Optional(Type.Boolean({ description: "List all workflows", default: false, })), filter: Type.Optional(Type.Object({ status: Type.Optional(Type.String({ description: "Filter by status" })), category: Type.Optional(Type.String({ description: "Filter by category" })), tag: Type.Optional(Type.String({ description: "Filter by tag" })), })), }), async execute(toolCallId, params, signal, onUpdate, ctx) { const { workflowId, all = false, filter } = params; if (workflowId) { const workflow = await workflowEngine.getWorkflow(workflowId); if (!workflow) { return { content: [{ type: "text", text: `✗ Workflow ${workflowId} not found` }], details: { error: "Workflow not found" }, isError: true, }; } const completed = workflow.results.filter((r) => r.status === "completed").length; const running = workflow.results.filter((r) => r.status === "running").length; const pending = workflow.results.filter((r) => r.status === "pending").length; const failed = workflow.results.filter((r) => r.status === "failed").length; const skipped = workflow.results.filter((r) => r.status === "skipped" || r.status === "conditional-skipped").length; const lines = [ `Workflow: ${workflow.name} (${workflow.id})`, `Status: ${workflow.status}`, `Category: ${workflow.category || "general"}`, `Priority: ${workflow.priority || "normal"}`, `Progress: ${completed}/${workflow.steps.length} completed, ${running} running, ${pending} pending${failed > 0 ? `, ${failed} failed` : ""}${skipped > 0 ? `, ${skipped} skipped` : ""}`, `Duration: ${formatDuration(workflow.startedAt, workflow.completedAt)}`, `Created: ${workflow.createdAt}`, ]; if (workflow.startedAt) lines.push(`Started: ${workflow.startedAt}`); if (workflow.completedAt) lines.push(`Completed: ${workflow.completedAt}`); if (workflow.tags?.length) lines.push(`Tags: ${workflow.tags.join(", ")}`); lines.push("\nSteps:"); for (const step of workflow.steps) { const result = workflow.results.find((r) => r.stepId === step.id)!; const emoji = result.status === "completed" ? "✓" : result.status === "failed" ? "✗" : result.status === "running" ? "⏳" : result.status === "skipped" ? "⊘" : result.status === "conditional-skipped" ? "⊘" : "○"; const duration = formatDuration(result.startedAt, result.completedAt); lines.push(` ${emoji} ${step.name} (${step.agent}) - ${result.status} - ${duration}`); if (result.error) lines.push(` Error: ${result.error.substring(0, 100)}`); } return { content: [{ type: "text", text: lines.join("\n") }], details: { workflow }, }; } const workflows = await workflowEngine.listWorkflows(filter); if (workflows.length === 0) { return { content: [{ type: "text", text: "No workflows found" }], details: { count: 0 }, }; } const lines = [`Workflows (${workflows.length} total):\n`]; for (const wf of workflows.slice(0, 30)) { const completed = wf.results.filter((r) => r.status === "completed").length; const emoji = wf.status === "completed" ? "✓" : wf.status === "failed" ? "✗" : wf.status === "running" ? "⏳" : wf.status === "paused" ? "⏸" : wf.status === "partial" ? "◐" : "○"; const cat = wf.category ? `[${wf.category}] ` : ""; lines.push(`${emoji} ${cat}${wf.name} - ${completed}/${wf.steps.length} steps - ${wf.status}`); } return { content: [{ type: "text", text: lines.join("\n") }], details: { workflows: workflows.slice(0, 30).map((w) => ({ id: w.id, name: w.name, status: w.status, category: w.category, priority: w.priority, })), totalCount: workflows.length, }, }; }, }); // ============================================================================= // Tool: cm_results_collect // ============================================================================= pi.registerTool({ name: "cm_results_collect", label: "Collect Results", description: "Gather and synthesize results from completed workflow steps", parameters: Type.Object({ workflowId: Type.String({ description: "Workflow ID" }), stepIds: Type.Optional(Type.Array(Type.String(), { description: "Specific step IDs to collect (default: all)", })), format: Type.Optional(StringEnum(["summary", "full", "json", "markdown"], { description: "Output format", default: "summary", })), synthesize: Type.Optional(Type.Boolean({ description: "Synthesize results into coherent output", default: false, })), }), async execute(toolCallId, params, signal, onUpdate, ctx) { const { workflowId, stepIds, format = "summary", synthesize = false } = params; const workflow = await workflowEngine.getWorkflow(workflowId); if (!workflow) { return { content: [{ type: "text", text: `✗ Workflow ${workflowId} not found` }], details: { error: "Workflow not found" }, isError: true, }; } const results = stepIds ? workflow.results.filter((r) => stepIds.includes(r.stepId)) : workflow.results; if (format === "json") { return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }], details: { results }, }; } const lines = [`Results for workflow "${workflow.name}":\n`]; for (const result of results) { const step = workflow.steps.find((s) => s.id === result.stepId)!; lines.push(`\n## ${step.name} (${step.agent})`); lines.push(`Status: ${result.status}`); lines.push(`Duration: ${formatDuration(result.startedAt, result.completedAt)}`); if (format === "full" && result.output) { lines.push(`\n### Output:\n${result.output}`); } else if (result.output) { const preview = result.output.substring(0, 800); lines.push(`\n### Output Preview:\n${preview}${result.output.length > 800 ? "\n... (truncated)" : ""}`); } if (result.error) { lines.push(`\n### Error:\n${result.error}`); } if (result.artifacts?.length) { lines.push(`\n### Artifacts:\n${result.artifacts.join("\n")}`); } lines.push(`\n*Usage: ${formatUsageStats(result.usage)}*`); } // Synthesize if requested if (synthesize) { const completedResults = results.filter((r) => r.status === "completed" && r.output); if (completedResults.length > 1) { lines.push("\n\n---\n\n## Synthesis\n"); // Use a summarizer agent to synthesize const synthesisTask = `Synthesize these workflow results into a coherent summary:\n\n${completedResults.map((r, i) => `Result ${i + 1}:\n${r.output?.substring(0, 2000)}`).join("\n\n")}`; try { const synthesis = await executeSubagent({ agent: "summarizer", task: synthesisTask, timeout: 120000, }); if (synthesis.output) { lines.push(synthesis.output); } } catch { lines.push("(Synthesis unavailable)"); } } } return { content: [{ type: "text", text: lines.join("\n") }], details: { results }, }; }, }); // ============================================================================= // Tool: cm_workflow_abort // ============================================================================= pi.registerTool({ name: "cm_workflow_abort", label: "Abort Workflow", description: "Gracefully stop a running workflow", parameters: Type.Object({ workflowId: Type.String({ description: "Workflow ID to abort" }), }), async execute(toolCallId, params, signal, onUpdate, ctx) { const { workflowId } = params; await workflowEngine.abortWorkflow(workflowId); return { content: [{ type: "text", text: `✓ Aborted workflow ${workflowId}` }], details: { workflowId }, }; }, }); // ============================================================================= // Tool: cm_workflow_delete // ============================================================================= pi.registerTool({ name: "cm_workflow_delete", label: "Delete Workflow", description: "Delete a workflow and its associated data", parameters: Type.Object({ workflowId: Type.String({ description: "Workflow ID to delete" }), force: Type.Optional(Type.Boolean({ description: "Force delete even if running", default: false, })), }), async execute(toolCallId, params, signal, onUpdate, ctx) { const { workflowId, force = false } = params; const workflow = await workflowEngine.getWorkflow(workflowId); if (!workflow) { return { content: [{ type: "text", text: `✗ Workflow ${workflowId} not found` }], details: { error: "Workflow not found" }, isError: true, }; } // Confirm before deleting running workflow if (workflow.status === "running" && !force) { const confirmed = await ctx.ui.confirm( "Confirm Delete", `Workflow "${workflow.name}" is still running. Abort and delete?`, ); if (!confirmed) { return { content: [{ type: "text", text: "Delete cancelled" }], details: { cancelled: true }, }; } await workflowEngine.abortWorkflow(workflowId); } await workflowEngine.deleteWorkflow(workflowId); return { content: [{ type: "text", text: `✓ Deleted workflow "${workflow.name}"` }], details: { workflowId }, }; }, }); // ============================================================================= // Tool: cm_parallel_execute // ============================================================================= pi.registerTool({ name: "cm_parallel_execute", label: "Execute Parallel Tasks", description: "Execute multiple agents in parallel with the same or different tasks", parameters: Type.Object({ tasks: Type.Array( Type.Object({ agent: Type.String({ description: "Agent name" }), task: Type.String({ description: "Task for this agent" }), id: Type.Optional(Type.String({ description: "Optional task identifier" })), }), { description: "Tasks to execute in parallel", minItems: 1, maxItems: MAX_PARALLEL_TASKS }, ), maxConcurrency: Type.Optional(Type.Number({ description: `Maximum concurrent executions (default: ${MAX_CONCURRENCY})`, maximum: MAX_CONCURRENCY, minimum: 1, })), aggregate: Type.Optional(Type.Boolean({ description: "Aggregate results into single output", default: false, })), }), async execute(toolCallId, params, signal, onUpdate, ctx) { const { tasks, maxConcurrency = MAX_CONCURRENCY, aggregate = false } = params; const results: Array = []; const concurrency = Math.min(maxConcurrency, MAX_CONCURRENCY, tasks.length); let completed = 0; let failed = 0; // Create a simple tracking workflow const steps: WorkflowStep[] = tasks.map((t, i) => ({ id: t.id || `task-${i}`, name: t.agent, description: t.task.substring(0, 100), agent: t.agent, task: t.task, })); const workflow = await workflowEngine.createWorkflow( `Parallel-${Date.now()}`, `Parallel execution of ${tasks.length} tasks`, steps, { category: "parallel", tags: ["parallel", "batch"] } ); await Promise.all( tasks.map(async (task, index) => { const taskId = task.id || `task-${index}`; try { onUpdate({ content: [{ type: "text", text: `⏳ [${index + 1}/${tasks.length}] ${task.agent}: running` }], }); const result = await executeSubagent({ agent: task.agent, task: task.task, }); results.push({ ...result, taskId, agent: task.agent }); if (result.status === "completed") { completed++; onUpdate({ content: [{ type: "text", text: `✓ [${index + 1}/${tasks.length}] ${task.agent}: completed` }], }); } else { failed++; onUpdate({ content: [{ type: "text", text: `✗ [${index + 1}/${tasks.length}] ${task.agent}: failed` }], }); } } catch (error) { failed++; results.push({ taskId, stepId: taskId, agent: task.agent, status: "failed", error: error instanceof Error ? error.message : String(error), usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 }, }); } }), ); const totalUsage = results.reduce( (acc, r) => ({ input: acc.input + r.usage.input, output: acc.output + r.usage.output, cacheRead: acc.cacheRead + r.usage.cacheRead, cacheWrite: acc.cacheWrite + r.usage.cacheWrite, cost: acc.cost + r.usage.cost, contextTokens: acc.contextTokens + r.usage.contextTokens, turns: acc.turns + r.usage.turns, }), { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 }, ); let aggregatedOutput = ""; if (aggregate) { const successfulOutputs = results .filter((r) => r.status === "completed" && r.output) .map((r) => `## ${r.agent} (${r.taskId}):\n${r.output}`) .join("\n\n---\n\n"); aggregatedOutput = successfulOutputs; } const summary = `✓ Parallel execution complete\n${completed}/${tasks.length} succeeded${failed > 0 ? `, ${failed} failed` : ""}\n${formatUsageStats(totalUsage)}`; return { content: [ { type: "text", text: summary }, ...(aggregatedOutput ? [{ type: "text" as const, text: "\n\n## Aggregated Results:\n\n" + aggregatedOutput }] : []), ], details: { workflowId: workflow.id, results: results.map((r) => ({ taskId: r.taskId, agent: r.agent, status: r.status, output: r.output?.substring(0, 500), error: r.error, })), totalUsage, aggregatedOutput: aggregate ? aggregatedOutput : undefined, }, }; }, }); // ============================================================================= // Tool: cm_chain_execute // ============================================================================= pi.registerTool({ name: "cm_chain_execute", label: "Execute Chain", description: "Execute agents sequentially, passing output from one to the next", parameters: Type.Object({ chain: Type.Array( Type.Object({ agent: Type.String({ description: "Agent name" }), task: Type.String({ description: "Task (use {previous} or {stepId} to reference prior outputs)" }), id: Type.Optional(Type.String({ description: "Optional step identifier" })), condition: Type.Optional(Type.String({ description: "Condition to run this step (e.g., 'previous.success')", })), }), { description: "Chain of agents to execute", minItems: 1 }, ), initialInput: Type.Optional(Type.String({ description: "Initial input for the first agent", })), }), async execute(toolCallId, params, signal, onUpdate, ctx) { const { chain, initialInput } = params; const steps: WorkflowStep[] = chain.map((c, i) => ({ id: c.id || `chain-${i}`, name: c.agent, description: c.task.substring(0, 100), agent: c.agent, task: c.task, condition: c.condition, })); const workflow = await workflowEngine.createWorkflow( `Chain-${Date.now()}`, `Sequential chain of ${chain.length} agents`, steps, { category: "chain", tags: ["chain", "sequential"] } ); const stepOutputs = new Map(); if (initialInput) { stepOutputs.set("input", initialInput); } const results: Array = []; for (let i = 0; i < chain.length; i++) { const chainStep = chain[i]; // Check condition if (chainStep.condition) { const prevFailed = results.length > 0 && results[results.length - 1].status !== "completed"; if (chainStep.condition === "previous.success" && prevFailed) { results.push({ stepIndex: i, stepId: chainStep.id || `chain-${i}`, agent: chainStep.agent, status: "conditional-skipped", error: "Skipped: previous step failed", usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 }, }); continue; } if (chainStep.condition === "previous.failure" && !prevFailed) { results.push({ stepIndex: i, stepId: chainStep.id || `chain-${i}`, agent: chainStep.agent, status: "conditional-skipped", error: "Skipped: previous step succeeded", usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 }, }); continue; } } // Substitute outputs in task let task = chainStep.task; for (const [stepId, output] of stepOutputs) { task = task.replace(new RegExp(`\\{${stepId}\\}`, "g"), output); } task = task.replace(/\{previous\}/g, Array.from(stepOutputs.values()).pop() || ""); task = task.replace(/\{input\}/g, initialInput || ""); onUpdate({ content: [{ type: "text", text: `⏳ [${i + 1}/${chain.length}] ${chainStep.agent}: running` }], }); try { const result = await executeSubagent({ agent: chainStep.agent, task, }); results.push({ ...result, stepIndex: i, agent: chainStep.agent }); if (result.output) { stepOutputs.set(chainStep.id || `chain-${i}`, result.output); } const emoji = result.status === "completed" ? "✓" : "✗"; onUpdate({ content: [{ type: "text", text: `${emoji} [${i + 1}/${chain.length}] ${chainStep.agent}: ${result.status}` }], }); if (result.status === "failed" && !chainStep.condition) { break; } } catch (error) { results.push({ stepIndex: i, stepId: chainStep.id || `chain-${i}`, agent: chainStep.agent, status: "failed", error: error instanceof Error ? error.message : String(error), usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 }, }); break; } } const completedCount = results.filter((r) => r.status === "completed").length; const skippedCount = results.filter((r) => r.status === "conditional-skipped").length; const totalUsage = results.reduce( (acc, r) => ({ input: acc.input + r.usage.input, output: acc.output + r.usage.output, cacheRead: acc.cacheRead + r.usage.cacheRead, cacheWrite: acc.cacheWrite + r.usage.cacheWrite, cost: acc.cost + r.usage.cost, contextTokens: acc.contextTokens + r.usage.contextTokens, turns: acc.turns + r.usage.turns, }), { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 }, ); const finalOutput = Array.from(stepOutputs.values()).pop() || ""; const summary = `✓ Chain execution complete\n${completedCount}/${chain.length} completed${skippedCount > 0 ? `, ${skippedCount} skipped` : ""}\n${formatUsageStats(totalUsage)}\n\n### Final Output:\n${finalOutput.substring(0, 1500)}${finalOutput.length > 1500 ? "\n... (truncated)" : ""}`; return { content: [{ type: "text", text: summary }], details: { workflowId: workflow.id, results: results.map((r) => ({ stepIndex: r.stepIndex, agent: r.agent, status: r.status, output: r.output?.substring(0, 500), error: r.error, })), finalOutput, totalUsage, }, }; }, }); // ============================================================================= // Tool: cm_list_templates // ============================================================================= pi.registerTool({ name: "cm_list_templates", label: "List Workflow Templates", description: "List all available workflow templates", parameters: Type.Object({ category: Type.Optional(Type.String({ description: "Filter by category" })), }), async execute(toolCallId, params, signal, onUpdate, ctx) { const { category } = params; const templates = category ? WORKFLOW_TEMPLATES.filter((t) => t.category === category) : WORKFLOW_TEMPLATES; const lines = [`Available Templates (${templates.length}):\n`]; const byCategory = new Map(); for (const template of templates) { if (!byCategory.has(template.category)) { byCategory.set(template.category, []); } byCategory.get(template.category)!.push(template); } for (const [cat, catTemplates] of byCategory) { lines.push(`\n## ${cat.toUpperCase()}`); for (const t of catTemplates) { lines.push(`\n ${t.name}`); lines.push(` ${t.description}`); lines.push(` Steps: ${t.steps.length} | Tags: ${t.tags?.join(", ") || "none"}`); } } return { content: [{ type: "text", text: lines.join("\n") }], details: { templates: templates.map((t) => ({ name: t.name, description: t.description, category: t.category, tags: t.tags, stepCount: t.steps.length, })), }, }; }, }); // ============================================================================= // Commands // ============================================================================= pi.registerCommand("cm-list", { description: "List all workflows with optional filters", handler: async (args, ctx) => { const filters: any = {}; // Parse args like "status:running category:coding" if (args) { const parts = args.split(" "); for (const part of parts) { if (part.includes(":")) { const [key, value] = part.split(":"); filters[key] = value; } } } const workflows = await workflowEngine.listWorkflows(filters); if (workflows.length === 0) { ctx.ui.notify("No workflows found", "info"); return; } const lines = [`Workflows (${workflows.length} total):\n`]; for (const wf of workflows.slice(0, 20)) { const completed = wf.results.filter((r) => r.status === "completed").length; const emoji = wf.status === "completed" ? "✓" : wf.status === "failed" ? "✗" : wf.status === "running" ? "⏳" : wf.status === "paused" ? "⏸" : wf.status === "partial" ? "◐" : "○"; const cat = wf.category ? `[${wf.category}] ` : ""; lines.push(`${emoji} ${cat}${wf.name} - ${completed}/${wf.steps.length} - ${wf.status}`); } ctx.ui.notify(lines.join("\n"), "info"); }, }); pi.registerCommand("cm-agents", { description: "List all available agents", handler: async (args, ctx) => { const agents = await discoverAgents("both"); const builtin = agents.filter((a) => a.source === "builtin"); const custom = agents.filter((a) => a.source !== "builtin"); const lines = [ `Agents: ${builtin.length} built-in, ${custom.length} custom\n`, "\nBuilt-in:", ...builtin.map((a) => ` • ${a.name} - ${a.description}`), ]; if (custom.length > 0) { lines.push("\nCustom:", ...custom.map((a) => ` • ${a.name} (${a.source}) - ${a.description}`)); } ctx.ui.notify(lines.join("\n"), "info"); }, }); pi.registerCommand("cm-templates", { description: "List workflow templates", handler: async (args, ctx) => { const lines = [`Templates (${WORKFLOW_TEMPLATES.length}):\n`]; for (const t of WORKFLOW_TEMPLATES) { lines.push(` ${t.name} [${t.category}] - ${t.description}`); } ctx.ui.notify(lines.join("\n"), "info"); }, }); pi.registerCommand("cm-clean", { description: "Clean up completed workflows older than N days", handler: async (args, ctx) => { const days = parseInt(args || "7", 10); const cutoff = Date.now() - days * 24 * 60 * 60 * 1000; const workflows = await workflowEngine.listWorkflows(); const toDelete = workflows.filter( (w) => (w.status === "completed" || w.status === "failed" || w.status === "aborted") && new Date(w.completedAt || w.createdAt).getTime() < cutoff, ); if (toDelete.length === 0) { ctx.ui.notify("No old workflows to clean up", "info"); return; } const confirmed = await ctx.ui.confirm( "Confirm Cleanup", `Delete ${toDelete.length} workflows older than ${days} days?`, ); if (!confirmed) { ctx.ui.notify("Cleanup cancelled", "info"); return; } for (const wf of toDelete) { await workflowEngine.deleteWorkflow(wf.id); } ctx.ui.notify(`Deleted ${toDelete.length} workflows`, "success"); }, }); pi.registerCommand("cm-status", { description: "Show detailed status of a workflow", handler: async (args, ctx) => { if (!args) { ctx.ui.notify("Usage: /cm-status ", "warning"); return; } const workflow = await workflowEngine.getWorkflow(args.trim()); if (!workflow) { ctx.ui.notify(`Workflow ${args} not found`, "error"); return; } const completed = workflow.results.filter((r) => r.status === "completed").length; const lines = [ `${workflow.name} (${workflow.id})`, `Status: ${workflow.status}`, `Progress: ${completed}/${workflow.steps.length}`, `Duration: ${formatDuration(workflow.startedAt, workflow.completedAt)}`, "\nSteps:", ]; for (const step of workflow.steps) { const result = workflow.results.find((r) => r.stepId === step.id)!; const emoji = result.status === "completed" ? "✓" : result.status === "failed" ? "✗" : result.status === "running" ? "⏳" : "○"; lines.push(` ${emoji} ${step.name} (${step.agent}): ${result.status}`); } ctx.ui.notify(lines.join("\n"), "info"); }, }); // ============================================================================= // Session Persistence // ============================================================================= pi.on("session_end", async () => { // Save any running workflow states const runningWorkflows = Array.from(workflowEngine["workflows"].values()).filter( (w) => w.status === "running", ); for (const workflow of runningWorkflows) { workflow.status = "paused"; workflow.completedAt = new Date().toISOString(); await workflowEngine["saveWorkflow"](workflow); } }); }