UNPKG

@every-env/cli

Version:

Multi-agent orchestrator for AI-powered development workflows

187 lines • 7.59 kB
import { FilePatternMatcher } from './file-pattern.js'; import { ContentPatternMatcher } from './content-pattern.js'; import { AgentManager } from '../core/agent-manager.js'; import { VariableResolver } from '../templates/variable-resolver.js'; import { logger } from '../utils/logger.js'; import { access } from 'fs/promises'; import chalk from 'chalk'; export class CommandPatternExecutor { fileMatcher = new FilePatternMatcher(); contentMatcher = new ContentPatternMatcher(); variableResolver = new VariableResolver(); async executePatternsForCommand(patterns, context, options = {}) { const tasks = []; for (const pattern of patterns) { const patternTasks = await this.preparePattern(pattern, context, options); tasks.push(...patternTasks); } if (options.dryRun) { this.printDryRun(tasks, context.command); return []; } const manager = new AgentManager(options.maxAgents || 5); return manager.runBatch(tasks); } async prepareTasksForCommand(patterns, context, options = {}) { const tasks = []; for (const pattern of patterns) { const patternTasks = await this.preparePattern(pattern, context, options); tasks.push(...patternTasks); } return tasks; } async preparePattern(pattern, context, options) { const tasks = []; // Get matches if pattern has matching rules const matches = pattern.match ? await this.getMatches(pattern, options) : [null]; // Single execution if no match rules // Create tasks for each agent for (const agent of pattern.agents) { if (agent.forEach === 'match' && matches.length > 1) { // Create task for each match for (let i = 0; i < matches.length; i++) { const match = matches[i]; const task = await this.createAgentTask(pattern, agent, context, { match, matchIndex: i, matchTotal: matches.length, }, options); if (task) tasks.push(task); } } else { // Single task for this agent const task = await this.createAgentTask(pattern, agent, context, {}, options); if (task) tasks.push(task); } } return tasks; } async getMatches(pattern, options) { if (!pattern.match) return []; const matchConfigs = Array.isArray(pattern.match) ? pattern.match : [pattern.match]; const allMatches = []; for (const matchConfig of matchConfigs) { // Get file matches const fileMatches = await this.fileMatcher.match(matchConfig); if ('content' in matchConfig && matchConfig.content) { // Extract content from matched files const contentMatches = await this.contentMatcher.match(matchConfig.content, fileMatches.map(f => f.path)); // Filter by --only option const filtered = options.only ? contentMatches.filter(m => options.only.includes(m.match)) : contentMatches; allMatches.push(...filtered.map(m => m.match)); } else { // Use file matches directly allMatches.push(...fileMatches); } } return allMatches; } async createAgentTask(pattern, agent, context, matchContext, options) { // Check runIf conditions if (agent.runIf && !await this.checkRunConditions(agent.runIf)) { logger.debug(`Skipping agent ${agent.id} due to runIf conditions`); return null; } // Determine output path const outputPath = this.resolveOutputPath(agent, matchContext.match); // Merge variables from all sources const mergedVariables = { ...context.globalVariables, ...context.commandVariables, ...pattern.variables, command: context.command, }; // Create a temporary config-like object for variable resolution const tempConfig = { ...context.config, variables: mergedVariables, defaultCommand: context.config?.defaultCommand || 'claude', defaultAllowedTools: context.config?.defaultAllowedTools || [], parallelism: context.config?.parallelism || { maxAgents: 5, batchSize: 5 }, }; // Resolve variables const variables = await this.variableResolver.resolve(tempConfig, pattern, agent, { outputPath, matchItem: matchContext.match, matchIndex: matchContext.matchIndex, matchTotal: matchContext.matchTotal, }); // Determine essential tools const essentialTools = options.essentialTools || [ 'Read', 'Write', 'Edit', 'MultiEdit', 'Glob', 'Grep', 'LS' ]; const mergedAllowedTools = [ ...essentialTools, ...(context.config?.defaultAllowedTools || []), ...(agent.allowedTools || []) ]; // Remove duplicates const uniqueAllowedTools = [...new Set(mergedAllowedTools)]; const agentWithDefaults = { ...agent, command: agent.command || context.config?.defaultCommand || 'claude', allowedTools: uniqueAllowedTools }; logger.debug(`Agent ${agent.id} allowedTools:`, { essentialTools, defaultAllowedTools: context.config?.defaultAllowedTools, agentAllowedTools: agent.allowedTools, mergedAllowedTools: agentWithDefaults.allowedTools }); return { pattern, agent: agentWithDefaults, variables, outputPath, }; } resolveOutputPath(agent, match) { if (agent.output) { return agent.output; } if (agent.outputPattern && match) { // Replace {name} with match value const name = typeof match === 'string' ? match : match.name || match.match; return agent.outputPattern.replace(/{name}/g, name); } throw new Error(`No output path specified for agent ${agent.id}`); } async checkRunConditions(conditions) { if (conditions.filesExist) { // Check if all required files exist for (const file of conditions.filesExist) { try { await access(file); } catch { return false; } } } return true; } printDryRun(tasks, command) { console.log(chalk.yellow(`\nšŸ” Dry run for '${command}' command - would execute:\n`)); for (const task of tasks) { console.log(` Agent: ${chalk.bold(task.agent.id)}`); console.log(` Output: ${task.outputPath}`); console.log(` Prompt: ${task.agent.promptFile || '(inline prompt)'}`); if (task.variables.name) { console.log(` Target: ${task.variables.name}`); } console.log(); } console.log(chalk.gray(`Total: ${tasks.length} agents\n`)); } } //# sourceMappingURL=command-pattern-executor.js.map