UNPKG

capsule-ai-cli

Version:

The AI Model Orchestrator - Intelligent multi-model workflows with device-locked licensing

239 lines (238 loc) 10.3 kB
import { BaseTool } from '../base.js'; import { toolRegistry } from '../registry.js'; import { stateService } from '../../services/state.js'; import { configManager } from '../../core/config.js'; import { editConfirmationService } from '../../services/edit-confirmation.js'; import { bashConfirmationService } from '../../services/bash-confirmation.js'; export class SubAgentTool extends BaseTool { name = 'task_spawn'; displayName = '🤖 Sub-Agents'; description = 'Run multiple MICRO-TASKS in parallel. Perfect for repetitive operations like reading multiple files, running same command in different directories, or checking multiple conditions. Sub-agents are lightweight and fast.'; category = 'productivity'; parameters = [ { name: 'tasks', type: 'array', description: 'Array of simple, specific micro-tasks (e.g., ["Read file1.ts", "Check if port 3000 is open", "Get size of node_modules"])', required: true, items: { type: 'string' } }, { name: 'parallel', type: 'boolean', description: 'Whether to run sub-agents in parallel (true) or sequentially (false)', required: false, default: true }, { name: 'model', type: 'string', description: 'Model to use for sub-agents (optional, uses saved defaults or current model)', required: false }, { name: 'provider', type: 'string', description: 'Provider to use for sub-agents (optional, uses saved defaults or current provider)', required: false } ]; icon = '🤖'; permissions = {}; ui = { showProgress: true, collapsible: false, dangerous: false }; async run(params, context) { const { tasks, parallel = true } = params; let { model, provider } = params; if (!tasks || tasks.length === 0) { throw new Error('No tasks provided for sub-agents'); } const originalModel = stateService.getModel(); const originalProvider = stateService.getProvider(); const originalEditAutoApprove = editConfirmationService.isAutoApprove(); const originalBashAutoApprove = bashConfirmationService.isAutoApprove(); if (!model || !provider) { const config = configManager.getConfig(); const subAgentDefaults = config.subAgentDefaults; if (subAgentDefaults) { model = model || subAgentDefaults.model; provider = provider || subAgentDefaults.provider; } } if (model || provider) { if (provider) stateService.setProvider(provider); if (model) stateService.setModel(model); } editConfirmationService.setAutoApprove(true); bashConfirmationService.setAutoApprove(true); try { const currentModel = model || stateService.getModel(); const modelShort = currentModel.split('/').pop()?.substring(0, 12) || 'agent'; const subAgentTasks = tasks.map((task, index) => ({ id: `${modelShort}-${index + 1}`, description: task, status: 'pending' })); const modelDisplay = modelShort.charAt(0).toUpperCase() + modelShort.slice(1); this.reportProgress(context, `${modelDisplay} \u2022 ${tasks.length} task${tasks.length > 1 ? 's' : ''}`, 0, { tasks: subAgentTasks }); let results; if (parallel) { results = await this.executeParallel(subAgentTasks, context); } else { results = await this.executeSequential(subAgentTasks, context); } const successful = results.filter(r => r.status === 'completed').length; const failed = results.filter(r => r.status === 'failed').length; return { summary: `Completed ${successful}/${tasks.length} tasks${failed > 0 ? ` (${failed} failed)` : ''}`, agents: results.map(task => ({ id: task.id, task: task.description, status: task.status, result: task.result, error: task.error, toolsUsed: task.toolsUsed || [] })) }; } finally { editConfirmationService.setAutoApprove(originalEditAutoApprove); bashConfirmationService.setAutoApprove(originalBashAutoApprove); if (model || provider) { stateService.setProvider(originalProvider); stateService.setModel(originalModel); } } } async executeParallel(tasks, context) { const promises = tasks.map(async (task) => { try { const result = await this.executeSubAgent(task, context); return result; } catch (error) { task.status = 'failed'; task.error = error.message; return task; } }); return Promise.all(promises); } async executeSequential(tasks, context) { const results = []; for (let i = 0; i < tasks.length; i++) { const task = tasks[i]; const progress = Math.round(((i + 1) / tasks.length) * 100); try { const result = await this.executeSubAgent(task, context); results.push(result); this.reportProgress(context, `Completed ${i + 1}/${tasks.length} tasks`, progress, { currentTask: result }); } catch (error) { task.status = 'failed'; task.error = error.message; results.push(task); this.reportProgress(context, `Task ${i + 1} failed: ${error.message}`, progress); } } return results; } async executeSubAgent(task, context) { task.status = 'running'; this.reportProgress(context, task.description, undefined, { agentId: task.id, status: 'starting' }); try { const prompt = `MICRO-TASK: ${task.description} Execute this single, specific task. Be direct and fast. Return only essential results.`; const formattedTools = toolRegistry.formatForProvider(stateService.getProvider()) .filter((tool) => { const functionName = tool.function?.name || tool.name; return functionName !== 'task_spawn' && functionName !== 'todo_list'; }); const provider = stateService.getProvider(); const model = stateService.getModel(); const providerInstance = (await import('../../providers/base.js')).providerRegistry.get(provider); if (!providerInstance) { throw new Error(`Provider ${provider} not available`); } let response = await providerInstance.complete([ { role: 'system', content: 'You are a micro-task executor. Complete the given task efficiently using available tools. Be concise.' }, { role: 'user', content: prompt } ], { model, tools: formattedTools, temperature: 0.3 }); const toolsUsed = []; if (response.toolCalls && response.toolCalls.length > 0) { const toolResults = []; for (const toolCall of response.toolCalls) { try { const tool = toolRegistry.get(toolCall.name); if (!tool) { throw new Error(`Tool ${toolCall.name} not found`); } toolsUsed.push(toolCall.name); const result = await tool.execute(toolCall.arguments, context); toolResults.push({ tool_call_id: toolCall.id, content: typeof result.output === 'string' ? result.output : JSON.stringify(result.output), name: toolCall.name }); } catch (error) { toolResults.push({ tool_call_id: toolCall.id, content: JSON.stringify({ error: error.message }), name: toolCall.name }); } } const messages = [ { role: 'system', content: 'You are a micro-task executor. Complete the given task efficiently using available tools. Be concise.' }, { role: 'user', content: prompt }, { role: 'assistant', content: '', tool_calls: response.toolCalls }, ...toolResults.map(tr => ({ role: 'tool_result', tool_call_id: tr.tool_call_id, content: tr.content, name: tr.name })) ]; response = await providerInstance.complete(messages, { model, temperature: 0.3, tools: formattedTools }); } task.status = 'completed'; task.result = response.content; task.toolsUsed = toolsUsed; this.reportProgress(context, task.description, undefined, { agentId: task.id, status: 'completed', toolsUsed: task.toolsUsed }); return task; } catch (error) { task.status = 'failed'; task.error = error.message; this.reportProgress(context, `${task.description}: ${error.message}`, undefined, { agentId: task.id, status: 'failed' }); throw error; } } } //# sourceMappingURL=sub-agent.js.map