UNPKG

@every-env/cli

Version:

Multi-agent orchestrator for AI-powered development workflows

196 lines 7.96 kB
import { FilePatternMatcher } from './file-pattern.js'; import { ContentPatternMatcher } from './content-pattern.js'; import { AgentManager } from '../core/agent-manager.js'; import { mkdir } from 'fs/promises'; import { dirname } from 'path'; import { logger } from '../utils/logger.js'; const ESSENTIAL_TOOLS = ['Read', 'Write', 'Edit', 'MultiEdit', 'Glob', 'Grep', 'LS']; export class PatternManager { fileMatcher = new FilePatternMatcher(); contentMatcher = new ContentPatternMatcher(); async executePatterns(patterns, config, options = {}) { const { dryRun = false, maxAgents = config.parallelism?.maxAgents || 5 } = options; logger.debug('Executing patterns', { patternCount: patterns.length, patternNames: patterns.map(p => p.name), maxAgents }); const tasks = await this.prepareTasks(patterns, config); logger.debug(`Prepared ${tasks.length} tasks from ${patterns.length} patterns`); if (dryRun) { this.printDryRun(tasks); return []; } const agentManager = new AgentManager(maxAgents); return agentManager.runBatch(tasks); } async prepareTasks(patterns, config) { const tasks = []; logger.debug('Preparing tasks for patterns', { patternCount: patterns.length }); for (const pattern of patterns) { logger.debug(`Preparing tasks for pattern: ${pattern.name}`); const patternTasks = await this.preparePatternTasks(pattern, config); logger.debug(`Pattern ${pattern.name} generated ${patternTasks.length} tasks`); tasks.push(...patternTasks); } logger.debug(`Total tasks prepared: ${tasks.length}`); return tasks; } async preparePatternTasks(pattern, config) { const tasks = []; // Resolve pattern variables const patternVariables = { ...config.variables, ...pattern.variables, }; logger.debug(`Pattern ${pattern.name} has ${pattern.agents.length} agents`); for (const agent of pattern.agents) { if (agent.forEach === 'match') { // Execute for each file match const matchedFiles = await this.getMatchedFiles(pattern, config); for (let i = 0; i < matchedFiles.length; i++) { const file = matchedFiles[i]; const variables = { ...patternVariables, file, name: file, index: i, total: matchedFiles.length, }; const task = await this.createTask(agent, variables, config, pattern); tasks.push(task); } } else if (agent.forEach === 'content') { // Execute for each content match const contentMatches = await this.getContentMatches(pattern, config); for (let i = 0; i < contentMatches.length; i++) { const match = contentMatches[i]; const variables = { ...patternVariables, ...match, name: match.match, index: i, total: contentMatches.length, }; const task = await this.createTask(agent, variables, config, pattern); tasks.push(task); } } else { // Single execution const task = await this.createTask(agent, patternVariables, config, pattern); tasks.push(task); } } return tasks; } async createTask(agent, variables, config, pattern) { logger.debug(`Creating task for agent ${agent.id}`, { pattern: pattern.name, hasOutput: !!agent.output, variableCount: Object.keys(variables).length }); // Merge allowed tools const allowedTools = [ ...ESSENTIAL_TOOLS, ...(config.defaultAllowedTools || []), ...(agent.allowedTools || []), ]; // Resolve output path with template variables let outputPath; if (agent.output) { // Simple template replacement for output paths outputPath = agent.output; for (const [key, value] of Object.entries(variables)) { if (typeof value === 'string' || typeof value === 'number') { outputPath = outputPath.replace(new RegExp(`\\{\\{\\s*${key}\\s*\\}\\}`, 'g'), String(value)); } } } // Ensure output directory exists if (outputPath && outputPath !== '') { logger.debug(`Creating output directory for ${outputPath}`); await mkdir(dirname(outputPath), { recursive: true }); } return { agent: { ...agent, command: agent.command || config.defaultCommand || 'claude', allowedTools: [...new Set(allowedTools)], }, outputPath: outputPath || '', variables, pattern, }; } async getMatchedFiles(pattern, _config) { if (!pattern.match) return []; // Handle union type for match const match = Array.isArray(pattern.match) ? pattern.match[0] : pattern.match; if (!match || !('files' in match)) return []; const results = await this.fileMatcher.match(match, process.cwd()); return results.map(r => typeof r === 'string' ? r : r.path); } async getContentMatches(pattern, _config) { if (!pattern.match) return []; // Handle union type for match const match = Array.isArray(pattern.match) ? pattern.match[0] : pattern.match; if (!match || !('content' in match) || !match.content) return []; const files = await this.getMatchedFiles(pattern, _config); return this.contentMatcher.match(match.content, files); } printDryRun(tasks) { console.log(`\nDry run - ${tasks.length} agent tasks would be executed:\n`); for (const task of tasks) { console.log(` Agent: ${task.agent.id}`); if (task.agent.promptFile) { console.log(` Prompt: ${task.agent.promptFile}`); } else { console.log(` Prompt: (inline prompt)`); } if (task.outputPath) { console.log(` Output: ${task.outputPath}`); } if (task.variables.name) { console.log(` Target: ${String(task.variables.name)}`); } console.log(''); } console.log(`Total: ${tasks.length} agents\n`); } validatePattern(pattern) { const errors = []; if (!pattern.name) { errors.push('Pattern must have a name'); } if (!pattern.agents || pattern.agents.length === 0) { errors.push('Pattern must have agents'); return errors; } for (let i = 0; i < pattern.agents.length; i++) { const agent = pattern.agents[i]; if (!agent.id) { errors.push(`Agent at index ${i} must have an id`); } if (!agent.promptFile) { errors.push(`Agent ${agent.id || i} must have promptFile`); } } return errors; } findPatterns(config, names) { if (!config.patterns) return []; if (names.length === 0) { return config.patterns; } return config.patterns.filter(p => names.includes(p.name)); } } //# sourceMappingURL=pattern-manager-old.js.map