capsule-ai-cli
Version:
The AI Model Orchestrator - Intelligent multi-model workflows with device-locked licensing
239 lines (238 loc) • 10.3 kB
JavaScript
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