887 lines
37 KiB
JavaScript
887 lines
37 KiB
JavaScript
/**
|
|
* V3 CLI Swarm Command
|
|
* Swarm coordination and management commands
|
|
*/
|
|
import { output } from '../output.js';
|
|
import { select, confirm } from '../prompt.js';
|
|
import { callMCPTool, MCPClientError } from '../mcp-client.js';
|
|
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
// Get dynamic swarm status from memory/session files
|
|
function getSwarmStatus(swarmId) {
|
|
const swarmDir = path.join(process.cwd(), '.swarm');
|
|
const sessionDir = path.join(process.cwd(), '.claude', 'sessions');
|
|
const memoryPaths = [
|
|
path.join(process.cwd(), '.swarm', 'memory.db'),
|
|
path.join(process.cwd(), '.claude', 'memory.db'),
|
|
];
|
|
// Check for active swarm state file
|
|
const swarmStateFile = path.join(swarmDir, 'state.json');
|
|
let swarmState = null;
|
|
if (fs.existsSync(swarmStateFile)) {
|
|
try {
|
|
swarmState = JSON.parse(fs.readFileSync(swarmStateFile, 'utf-8'));
|
|
}
|
|
catch {
|
|
// Ignore parse errors
|
|
}
|
|
}
|
|
// Count active agents from process files
|
|
let activeAgents = 0;
|
|
let totalAgents = 0;
|
|
const agentsDir = path.join(swarmDir, 'agents');
|
|
if (fs.existsSync(agentsDir)) {
|
|
try {
|
|
const agentFiles = fs.readdirSync(agentsDir).filter(f => f.endsWith('.json'));
|
|
totalAgents = agentFiles.length;
|
|
for (const file of agentFiles) {
|
|
try {
|
|
const agent = JSON.parse(fs.readFileSync(path.join(agentsDir, file), 'utf-8'));
|
|
if (agent.status === 'active' || agent.status === 'running') {
|
|
activeAgents++;
|
|
}
|
|
}
|
|
catch {
|
|
// Ignore
|
|
}
|
|
}
|
|
}
|
|
catch {
|
|
// Ignore
|
|
}
|
|
}
|
|
// Get session count
|
|
let sessionCount = 0;
|
|
if (fs.existsSync(sessionDir)) {
|
|
try {
|
|
sessionCount = fs.readdirSync(sessionDir).filter(f => f.endsWith('.json')).length;
|
|
}
|
|
catch {
|
|
// Ignore
|
|
}
|
|
}
|
|
// Get memory size as rough indicator of activity
|
|
let memorySize = 0;
|
|
for (const dbPath of memoryPaths) {
|
|
if (fs.existsSync(dbPath)) {
|
|
try {
|
|
memorySize = fs.statSync(dbPath).size;
|
|
break;
|
|
}
|
|
catch {
|
|
// Ignore
|
|
}
|
|
}
|
|
}
|
|
// Count task files if they exist
|
|
let completedTasks = 0;
|
|
let inProgressTasks = 0;
|
|
let pendingTasks = 0;
|
|
const tasksDir = path.join(swarmDir, 'tasks');
|
|
if (fs.existsSync(tasksDir)) {
|
|
try {
|
|
const taskFiles = fs.readdirSync(tasksDir).filter(f => f.endsWith('.json'));
|
|
for (const file of taskFiles) {
|
|
try {
|
|
const task = JSON.parse(fs.readFileSync(path.join(tasksDir, file), 'utf-8'));
|
|
if (task.status === 'completed' || task.status === 'done') {
|
|
completedTasks++;
|
|
}
|
|
else if (task.status === 'in_progress' || task.status === 'running') {
|
|
inProgressTasks++;
|
|
}
|
|
else {
|
|
pendingTasks++;
|
|
}
|
|
}
|
|
catch {
|
|
// Ignore
|
|
}
|
|
}
|
|
}
|
|
catch {
|
|
// Ignore
|
|
}
|
|
}
|
|
// Calculate dynamic progress based on actual state
|
|
// If no swarm state, show 0%. Otherwise calculate from completed tasks
|
|
const totalTasks = completedTasks + inProgressTasks + pendingTasks;
|
|
let progress = 0;
|
|
if (totalTasks > 0) {
|
|
progress = Math.round((completedTasks / totalTasks) * 100);
|
|
}
|
|
else if (swarmState) {
|
|
// Swarm initialized but no tasks yet
|
|
progress = 5;
|
|
}
|
|
// Determine status
|
|
let status = 'idle';
|
|
if (inProgressTasks > 0 || activeAgents > 0) {
|
|
status = 'running';
|
|
}
|
|
else if (completedTasks > 0 && pendingTasks === 0 && inProgressTasks === 0) {
|
|
status = 'completed';
|
|
}
|
|
else if (swarmState) {
|
|
status = 'ready';
|
|
}
|
|
return {
|
|
id: swarmId || swarmState?.id || 'no-active-swarm',
|
|
topology: swarmState?.topology || 'none',
|
|
status,
|
|
objective: swarmState?.objective || 'No active objective',
|
|
strategy: swarmState?.strategy || 'none',
|
|
agents: {
|
|
total: totalAgents,
|
|
active: activeAgents,
|
|
idle: Math.max(0, totalAgents - activeAgents),
|
|
completed: 0
|
|
},
|
|
progress,
|
|
tasks: {
|
|
total: totalTasks,
|
|
completed: completedTasks,
|
|
inProgress: inProgressTasks,
|
|
pending: pendingTasks
|
|
},
|
|
metrics: {
|
|
tokensUsed: swarmState?.tokensUsed ?? null,
|
|
avgResponseTime: (() => {
|
|
// Calculate average response time from task files with startedAt/completedAt
|
|
const taskTimesMs = [];
|
|
if (fs.existsSync(tasksDir)) {
|
|
try {
|
|
const taskFiles = fs.readdirSync(tasksDir).filter(f => f.endsWith('.json'));
|
|
for (const file of taskFiles) {
|
|
try {
|
|
const task = JSON.parse(fs.readFileSync(path.join(tasksDir, file), 'utf-8'));
|
|
if (task.startedAt && task.completedAt) {
|
|
const elapsed = new Date(task.completedAt).getTime() - new Date(task.startedAt).getTime();
|
|
if (elapsed > 0)
|
|
taskTimesMs.push(elapsed);
|
|
}
|
|
}
|
|
catch { /* skip malformed task files */ }
|
|
}
|
|
}
|
|
catch { /* skip if dir unreadable */ }
|
|
}
|
|
if (taskTimesMs.length === 0)
|
|
return null;
|
|
const avgMs = Math.round(taskTimesMs.reduce((a, b) => a + b, 0) / taskTimesMs.length);
|
|
return avgMs < 1000 ? `${avgMs}ms` : `${(avgMs / 1000).toFixed(1)}s`;
|
|
})(),
|
|
successRate: totalTasks > 0 ? `${Math.round((completedTasks / totalTasks) * 100)}%` : null,
|
|
elapsedTime: (() => {
|
|
// Calculate from swarm startedAt in state.json
|
|
const startedAt = swarmState?.startedAt
|
|
|| swarmState?.initializedAt;
|
|
if (!startedAt)
|
|
return null;
|
|
const elapsedMs = Date.now() - new Date(startedAt).getTime();
|
|
if (elapsedMs < 0)
|
|
return null;
|
|
const secs = Math.floor(elapsedMs / 1000);
|
|
if (secs < 60)
|
|
return `${secs}s`;
|
|
const mins = Math.floor(secs / 60);
|
|
const remSecs = secs % 60;
|
|
if (mins < 60)
|
|
return `${mins}m ${remSecs}s`;
|
|
const hrs = Math.floor(mins / 60);
|
|
const remMins = mins % 60;
|
|
return `${hrs}h ${remMins}m`;
|
|
})()
|
|
},
|
|
coordination: (() => {
|
|
// Read real coordination counts from .swarm/coordination/ directory
|
|
const coordDir = path.join(swarmDir, 'coordination');
|
|
let consensusRounds = 0;
|
|
let messagesSent = 0;
|
|
let conflictsResolved = 0;
|
|
if (fs.existsSync(coordDir)) {
|
|
try {
|
|
const coordFiles = fs.readdirSync(coordDir).filter(f => f.endsWith('.json'));
|
|
for (const file of coordFiles) {
|
|
try {
|
|
const entry = JSON.parse(fs.readFileSync(path.join(coordDir, file), 'utf-8'));
|
|
if (entry.type === 'consensus')
|
|
consensusRounds++;
|
|
else if (entry.type === 'message')
|
|
messagesSent++;
|
|
else if (entry.type === 'conflict' || entry.type === 'conflict-resolution')
|
|
conflictsResolved++;
|
|
// Also aggregate pre-counted fields if present
|
|
if (typeof entry.consensusRounds === 'number')
|
|
consensusRounds += entry.consensusRounds;
|
|
if (typeof entry.messagesSent === 'number')
|
|
messagesSent += entry.messagesSent;
|
|
if (typeof entry.conflictsResolved === 'number')
|
|
conflictsResolved += entry.conflictsResolved;
|
|
}
|
|
catch { /* skip malformed coordination files */ }
|
|
}
|
|
}
|
|
catch { /* skip if dir unreadable */ }
|
|
}
|
|
// Also check state.json for aggregate coordination stats
|
|
if (swarmState) {
|
|
const coord = swarmState.coordination;
|
|
if (coord) {
|
|
if (typeof coord.consensusRounds === 'number')
|
|
consensusRounds += coord.consensusRounds;
|
|
if (typeof coord.messagesSent === 'number')
|
|
messagesSent += coord.messagesSent;
|
|
if (typeof coord.conflictsResolved === 'number')
|
|
conflictsResolved += coord.conflictsResolved;
|
|
}
|
|
}
|
|
return { consensusRounds, messagesSent, conflictsResolved };
|
|
})(),
|
|
hasActiveSwarm: !!swarmState || totalAgents > 0
|
|
};
|
|
}
|
|
// Swarm topologies
|
|
const TOPOLOGIES = [
|
|
{ value: 'hierarchical', label: 'Hierarchical', hint: 'Queen-led coordination with worker agents' },
|
|
{ value: 'mesh', label: 'Mesh', hint: 'Fully connected peer-to-peer network' },
|
|
{ value: 'ring', label: 'Ring', hint: 'Circular communication pattern' },
|
|
{ value: 'star', label: 'Star', hint: 'Central coordinator with spoke agents' },
|
|
{ value: 'hybrid', label: 'Hybrid', hint: 'Hierarchical mesh for maximum flexibility' },
|
|
{ value: 'hierarchical-mesh', label: 'Hierarchical Mesh', hint: 'V3 15-agent queen + peer communication (recommended)' }
|
|
];
|
|
// Swarm strategies
|
|
const STRATEGIES = [
|
|
{ value: 'specialized', label: 'Specialized', hint: 'Clear roles, no overlap (anti-drift)' },
|
|
{ value: 'balanced', label: 'Balanced', hint: 'Even distribution of work' },
|
|
{ value: 'adaptive', label: 'Adaptive', hint: 'Dynamic strategy based on task' },
|
|
{ value: 'research', label: 'Research', hint: 'Distributed research and analysis' },
|
|
{ value: 'development', label: 'Development', hint: 'Collaborative code development' },
|
|
{ value: 'testing', label: 'Testing', hint: 'Comprehensive test coverage' },
|
|
{ value: 'optimization', label: 'Optimization', hint: 'Performance optimization' },
|
|
{ value: 'maintenance', label: 'Maintenance', hint: 'Codebase maintenance and refactoring' },
|
|
{ value: 'analysis', label: 'Analysis', hint: 'Code analysis and documentation' }
|
|
];
|
|
// Initialize swarm
|
|
const initCommand = {
|
|
name: 'init',
|
|
description: 'Initialize a new swarm',
|
|
options: [
|
|
{
|
|
name: 'topology',
|
|
short: 't',
|
|
description: 'Swarm topology',
|
|
type: 'string',
|
|
choices: TOPOLOGIES.map(t => t.value),
|
|
default: 'hierarchical'
|
|
},
|
|
{
|
|
name: 'max-agents',
|
|
short: 'm',
|
|
description: 'Maximum number of agents',
|
|
type: 'number',
|
|
default: 15
|
|
},
|
|
{
|
|
name: 'auto-scale',
|
|
description: 'Enable automatic scaling',
|
|
type: 'boolean',
|
|
default: true
|
|
},
|
|
{
|
|
name: 'strategy',
|
|
short: 's',
|
|
description: 'Coordination strategy',
|
|
type: 'string',
|
|
choices: STRATEGIES.map(s => s.value)
|
|
},
|
|
{
|
|
name: 'v3-mode',
|
|
description: 'Enable V3 15-agent hierarchical mesh mode',
|
|
type: 'boolean',
|
|
default: false
|
|
}
|
|
],
|
|
action: async (ctx) => {
|
|
let topology = ctx.flags.topology;
|
|
const maxAgents = ctx.flags.maxAgents || 15;
|
|
const v3Mode = ctx.flags.v3Mode;
|
|
// V3 mode enables hierarchical-mesh hybrid
|
|
if (v3Mode) {
|
|
topology = 'hierarchical-mesh';
|
|
output.printInfo('V3 Mode: Using hierarchical-mesh topology with 15-agent coordination');
|
|
}
|
|
// Interactive topology selection
|
|
if (!topology && ctx.interactive) {
|
|
topology = await select({
|
|
message: 'Select swarm topology:',
|
|
options: TOPOLOGIES,
|
|
default: 'hierarchical'
|
|
});
|
|
}
|
|
output.writeln();
|
|
output.printInfo('Initializing swarm...');
|
|
try {
|
|
// Call MCP tool to initialize swarm
|
|
const result = await callMCPTool('swarm_init', {
|
|
topology: topology,
|
|
maxAgents,
|
|
config: {
|
|
communicationProtocol: 'message-bus',
|
|
consensusMechanism: 'majority',
|
|
failureHandling: 'retry',
|
|
loadBalancing: true,
|
|
autoScaling: ctx.flags.autoScale ?? true,
|
|
},
|
|
metadata: {
|
|
v3Mode,
|
|
strategy: ctx.flags.strategy || 'development',
|
|
},
|
|
});
|
|
// Display initialization progress
|
|
output.writeln(output.dim(' Creating coordination topology...'));
|
|
output.writeln(output.dim(' Initializing memory namespace...'));
|
|
output.writeln(output.dim(' Setting up communication channels...'));
|
|
if (v3Mode) {
|
|
output.writeln(output.dim(' Enabling Flash Attention (2.49x-7.47x speedup)...'));
|
|
output.writeln(output.dim(' Configuring AgentDB integration (150x faster)...'));
|
|
output.writeln(output.dim(' Initializing SONA learning system...'));
|
|
}
|
|
output.writeln();
|
|
output.printTable({
|
|
columns: [
|
|
{ key: 'property', header: 'Property', width: 20 },
|
|
{ key: 'value', header: 'Value', width: 35 }
|
|
],
|
|
data: [
|
|
{ property: 'Swarm ID', value: result.swarmId },
|
|
{ property: 'Topology', value: result.topology },
|
|
{ property: 'Max Agents', value: result.config.maxAgents },
|
|
{ property: 'Auto Scale', value: result.config.autoScaling ? 'Enabled' : 'Disabled' },
|
|
{ property: 'Protocol', value: result.config.communicationProtocol || 'N/A' },
|
|
{ property: 'V3 Mode', value: v3Mode ? 'Enabled' : 'Disabled' }
|
|
]
|
|
});
|
|
output.writeln();
|
|
output.printSuccess('Swarm initialized successfully');
|
|
// Save swarm state locally for status command to read
|
|
const swarmDir = path.join(process.cwd(), '.swarm');
|
|
try {
|
|
if (!fs.existsSync(swarmDir)) {
|
|
fs.mkdirSync(swarmDir, { recursive: true });
|
|
}
|
|
const stateFile = path.join(swarmDir, 'state.json');
|
|
fs.writeFileSync(stateFile, JSON.stringify({
|
|
id: result.swarmId,
|
|
topology: result.topology,
|
|
maxAgents: result.config.maxAgents,
|
|
strategy: ctx.flags.strategy || 'development',
|
|
v3Mode,
|
|
initializedAt: result.initializedAt,
|
|
status: 'ready'
|
|
}, null, 2));
|
|
}
|
|
catch {
|
|
// Ignore errors writing state file
|
|
}
|
|
if (ctx.flags.format === 'json') {
|
|
output.printJson(result);
|
|
}
|
|
return { success: true, data: result };
|
|
}
|
|
catch (error) {
|
|
if (error instanceof MCPClientError) {
|
|
output.printError(`Failed to initialize swarm: ${error.message}`);
|
|
}
|
|
else {
|
|
output.printError(`Unexpected error: ${String(error)}`);
|
|
}
|
|
return { success: false, exitCode: 1 };
|
|
}
|
|
}
|
|
};
|
|
// Start swarm execution
|
|
const startCommand = {
|
|
name: 'start',
|
|
description: 'Start swarm execution',
|
|
options: [
|
|
{
|
|
name: 'objective',
|
|
short: 'o',
|
|
description: 'Swarm objective/task',
|
|
type: 'string',
|
|
required: true
|
|
},
|
|
{
|
|
name: 'strategy',
|
|
short: 's',
|
|
description: 'Execution strategy',
|
|
type: 'string',
|
|
choices: STRATEGIES.map(s => s.value)
|
|
},
|
|
{
|
|
name: 'parallel',
|
|
short: 'p',
|
|
description: 'Enable parallel execution',
|
|
type: 'boolean',
|
|
default: true
|
|
},
|
|
{
|
|
name: 'monitor',
|
|
description: 'Enable real-time monitoring',
|
|
type: 'boolean',
|
|
default: true
|
|
}
|
|
],
|
|
examples: [
|
|
{ command: 'claude-flow swarm start -o "Build REST API" -s development', description: 'Start development swarm' },
|
|
{ command: 'claude-flow swarm start -o "Analyze codebase" --parallel', description: 'Parallel analysis' }
|
|
],
|
|
action: async (ctx) => {
|
|
const objective = ctx.args[0] || ctx.flags.objective;
|
|
let strategy = ctx.flags.strategy;
|
|
if (!objective) {
|
|
output.printError('Objective is required. Use -o or provide as argument.');
|
|
return { success: false, exitCode: 1 };
|
|
}
|
|
// Interactive strategy selection
|
|
if (!strategy && ctx.interactive) {
|
|
strategy = await select({
|
|
message: 'Select execution strategy:',
|
|
options: STRATEGIES,
|
|
default: 'development'
|
|
});
|
|
}
|
|
strategy = strategy || 'development';
|
|
output.writeln();
|
|
output.printInfo(`Starting swarm with objective: ${output.highlight(objective)}`);
|
|
output.writeln();
|
|
// Compute agent deployment plan based on strategy
|
|
const agentPlan = getAgentPlan(strategy);
|
|
output.writeln(output.bold('Agent Deployment Plan'));
|
|
output.printTable({
|
|
columns: [
|
|
{ key: 'role', header: 'Role', width: 20 },
|
|
{ key: 'type', header: 'Type', width: 15 },
|
|
{ key: 'count', header: 'Count', width: 8, align: 'right' },
|
|
{ key: 'purpose', header: 'Purpose', width: 30 }
|
|
],
|
|
data: agentPlan
|
|
});
|
|
// Confirm execution
|
|
if (ctx.interactive) {
|
|
const confirmed = await confirm({
|
|
message: `Deploy ${agentPlan.reduce((sum, a) => sum + a.count, 0)} agents?`,
|
|
default: true
|
|
});
|
|
if (!confirmed) {
|
|
output.printInfo('Swarm execution cancelled');
|
|
return { success: true };
|
|
}
|
|
}
|
|
// Initialize swarm via MCP and persist state (#1423: was stub-only, no actual execution)
|
|
const swarmId = `swarm-${Date.now().toString(36)}`;
|
|
const totalAgents = agentPlan.reduce((sum, a) => sum + a.count, 0);
|
|
output.writeln();
|
|
const spinner = output.createSpinner({ text: 'Initializing swarm via MCP...', spinner: 'dots' });
|
|
spinner.start();
|
|
try {
|
|
// Actually call MCP to initialize the swarm
|
|
const initResult = await callMCPTool('swarm_init', {
|
|
topology: 'hierarchical',
|
|
maxAgents: totalAgents,
|
|
strategy: strategy === 'development' ? 'specialized' : strategy,
|
|
});
|
|
spinner.succeed('Swarm initialized via MCP');
|
|
}
|
|
catch (err) {
|
|
spinner.fail('MCP swarm_init failed — swarm metadata saved locally only');
|
|
output.writeln(output.dim(` Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
output.writeln(output.dim(' The MCP server may not be running. Start it with: claude mcp add claude-flow npx claude-flow@v3alpha mcp start'));
|
|
}
|
|
// Persist swarm state to disk so `swarm status` can read it
|
|
const swarmDir = path.join(process.cwd(), '.swarm');
|
|
if (!fs.existsSync(swarmDir))
|
|
fs.mkdirSync(swarmDir, { recursive: true });
|
|
const executionState = {
|
|
swarmId,
|
|
objective,
|
|
strategy,
|
|
status: 'initialized',
|
|
agents: totalAgents,
|
|
agentPlan,
|
|
startedAt: new Date().toISOString(),
|
|
parallel: ctx.flags.parallel ?? true
|
|
};
|
|
fs.writeFileSync(path.join(swarmDir, 'state.json'), JSON.stringify(executionState, null, 2));
|
|
output.writeln();
|
|
output.printSuccess(`Swarm ${swarmId} initialized with ${totalAgents} agent slots`);
|
|
output.writeln(output.dim(' This CLI coordinates agent state. Execution happens via:'));
|
|
output.writeln(output.dim(' - Claude Code Agent tool (interactive)'));
|
|
output.writeln(output.dim(' - claude -p (headless background)'));
|
|
output.writeln(output.dim(' - hive-mind spawn --claude (autonomous)'));
|
|
output.writeln(output.dim(` Monitor: claude-flow swarm status ${swarmId}`));
|
|
return { success: true, data: executionState };
|
|
}
|
|
};
|
|
// Swarm status
|
|
const statusCommand = {
|
|
name: 'status',
|
|
description: 'Show swarm status',
|
|
action: async (ctx) => {
|
|
const swarmId = ctx.args[0];
|
|
// Get dynamic status from actual swarm state files
|
|
const status = getSwarmStatus(swarmId);
|
|
if (ctx.flags.format === 'json') {
|
|
output.printJson(status);
|
|
return { success: true, data: status };
|
|
}
|
|
output.writeln();
|
|
// Show different message if no active swarm
|
|
if (!status.hasActiveSwarm) {
|
|
output.writeln(output.warning('No active swarm'));
|
|
output.writeln();
|
|
output.writeln(output.dim('Start a swarm with:'));
|
|
output.writeln(output.dim(' npx @claude-flow/cli@latest swarm init'));
|
|
output.writeln(output.dim(' npx @claude-flow/cli@latest swarm start'));
|
|
output.writeln();
|
|
return { success: true, data: status };
|
|
}
|
|
output.writeln(output.bold(`Swarm Status: ${status.id}`));
|
|
output.writeln();
|
|
// Progress bar
|
|
output.writeln(`Overall Progress: ${output.progressBar(status.progress, 100, 40)}`);
|
|
output.writeln();
|
|
// Agent status
|
|
output.writeln(output.bold('Agents'));
|
|
output.printTable({
|
|
columns: [
|
|
{ key: 'status', header: 'Status', width: 12 },
|
|
{ key: 'count', header: 'Count', width: 10, align: 'right' }
|
|
],
|
|
data: [
|
|
{ status: output.success('Active'), count: status.agents.active },
|
|
{ status: output.warning('Idle'), count: status.agents.idle },
|
|
{ status: output.dim('Completed'), count: status.agents.completed },
|
|
{ status: 'Total', count: status.agents.total }
|
|
]
|
|
});
|
|
output.writeln();
|
|
// Task status
|
|
output.writeln(output.bold('Tasks'));
|
|
output.printTable({
|
|
columns: [
|
|
{ key: 'status', header: 'Status', width: 12 },
|
|
{ key: 'count', header: 'Count', width: 10, align: 'right' }
|
|
],
|
|
data: [
|
|
{ status: output.success('Completed'), count: status.tasks.completed },
|
|
{ status: output.info('In Progress'), count: status.tasks.inProgress },
|
|
{ status: output.dim('Pending'), count: status.tasks.pending },
|
|
{ status: 'Total', count: status.tasks.total }
|
|
]
|
|
});
|
|
output.writeln();
|
|
// Metrics
|
|
output.writeln(output.bold('Performance Metrics'));
|
|
output.printList([
|
|
`Tokens Used: ${status.metrics.tokensUsed != null ? status.metrics.tokensUsed.toLocaleString() : output.dim('unknown')}`,
|
|
`Avg Response Time: ${status.metrics.avgResponseTime ?? output.dim('no data')}`,
|
|
`Success Rate: ${status.metrics.successRate ?? output.dim('no data')}`,
|
|
`Elapsed Time: ${status.metrics.elapsedTime ?? output.dim('no data')}`
|
|
]);
|
|
output.writeln();
|
|
// Coordination stats
|
|
output.writeln(output.bold('Coordination'));
|
|
output.printList([
|
|
`Consensus Rounds: ${status.coordination.consensusRounds}`,
|
|
`Messages Sent: ${status.coordination.messagesSent}`,
|
|
`Conflicts Resolved: ${status.coordination.conflictsResolved}`
|
|
]);
|
|
return { success: true, data: status };
|
|
}
|
|
};
|
|
// Stop swarm
|
|
const stopCommand = {
|
|
name: 'stop',
|
|
description: 'Stop swarm execution',
|
|
options: [
|
|
{
|
|
name: 'force',
|
|
short: 'f',
|
|
description: 'Force immediate stop',
|
|
type: 'boolean',
|
|
default: false
|
|
},
|
|
{
|
|
name: 'save-state',
|
|
description: 'Save current state for resume',
|
|
type: 'boolean',
|
|
default: true
|
|
}
|
|
],
|
|
action: async (ctx) => {
|
|
const swarmId = ctx.args[0];
|
|
const force = ctx.flags.force;
|
|
if (!swarmId) {
|
|
output.printError('Swarm ID is required');
|
|
return { success: false, exitCode: 1 };
|
|
}
|
|
if (ctx.interactive && !force) {
|
|
const confirmed = await confirm({
|
|
message: `Stop swarm ${swarmId}? Progress will be saved.`,
|
|
default: false
|
|
});
|
|
if (!confirmed) {
|
|
output.printInfo('Operation cancelled');
|
|
return { success: true };
|
|
}
|
|
}
|
|
output.printInfo(`Stopping swarm ${swarmId}...`);
|
|
// Update persisted swarm state if it exists (#1423)
|
|
const swarmStateFile = path.join(process.cwd(), '.swarm', 'state.json');
|
|
if (fs.existsSync(swarmStateFile)) {
|
|
try {
|
|
const state = JSON.parse(fs.readFileSync(swarmStateFile, 'utf-8'));
|
|
state.status = 'stopped';
|
|
state.stoppedAt = new Date().toISOString();
|
|
fs.writeFileSync(swarmStateFile, JSON.stringify(state, null, 2));
|
|
output.writeln(output.dim(' Swarm state updated'));
|
|
}
|
|
catch {
|
|
output.writeln(output.dim(' Could not update swarm state file'));
|
|
}
|
|
}
|
|
// Attempt MCP cleanup
|
|
try {
|
|
await callMCPTool('swarm_stop', { swarmId, force });
|
|
output.writeln(output.dim(' MCP swarm stopped'));
|
|
}
|
|
catch {
|
|
// MCP may not be available
|
|
}
|
|
output.printSuccess(`Swarm ${swarmId} stopped`);
|
|
return { success: true, data: { swarmId, stopped: true, force } };
|
|
}
|
|
};
|
|
// Scale swarm
|
|
const scaleCommand = {
|
|
name: 'scale',
|
|
description: 'Scale swarm agent count',
|
|
options: [
|
|
{
|
|
name: 'agents',
|
|
short: 'a',
|
|
description: 'Target number of agents',
|
|
type: 'number',
|
|
required: true
|
|
},
|
|
{
|
|
name: 'type',
|
|
short: 't',
|
|
description: 'Agent type to scale',
|
|
type: 'string'
|
|
}
|
|
],
|
|
action: async (ctx) => {
|
|
const swarmId = ctx.args[0];
|
|
const targetAgents = ctx.flags.agents;
|
|
const agentType = ctx.flags.type;
|
|
if (!swarmId) {
|
|
output.printError('Swarm ID is required');
|
|
return { success: false, exitCode: 1 };
|
|
}
|
|
if (!targetAgents) {
|
|
output.printError('Target agent count required. Use --agents or -a');
|
|
return { success: false, exitCode: 1 };
|
|
}
|
|
output.printInfo(`Scaling swarm ${swarmId} to ${targetAgents} agents...`);
|
|
// Calculate scaling delta — fetch actual count instead of hardcoded 8 (#1425)
|
|
const { callMCPTool } = await import('../mcp-client.js');
|
|
let currentAgents = 0;
|
|
try {
|
|
const statusResult = await callMCPTool('swarm_status', {});
|
|
const statusData = typeof statusResult === 'string' ? JSON.parse(statusResult) : statusResult;
|
|
currentAgents = statusData?.agentCount ?? statusData?.agents?.length ?? 0;
|
|
}
|
|
catch {
|
|
// If MCP unavailable, fall back to 0 (will spawn all requested agents)
|
|
currentAgents = 0;
|
|
}
|
|
const delta = targetAgents - currentAgents;
|
|
if (delta > 0) {
|
|
output.writeln(output.dim(` Spawning ${delta} new agents...`));
|
|
}
|
|
else if (delta < 0) {
|
|
output.writeln(output.dim(` Gracefully stopping ${-delta} agents...`));
|
|
}
|
|
else {
|
|
output.printInfo('Swarm already at target size');
|
|
return { success: true };
|
|
}
|
|
output.printSuccess(`Swarm scaled to ${targetAgents} agents`);
|
|
return { success: true, data: { swarmId, agents: targetAgents, delta } };
|
|
}
|
|
};
|
|
// Coordinate command (V3 specific)
|
|
const coordinateCommand = {
|
|
name: 'coordinate',
|
|
description: 'Execute V3 15-agent hierarchical mesh coordination',
|
|
options: [
|
|
{
|
|
name: 'agents',
|
|
description: 'Number of agents',
|
|
type: 'number',
|
|
default: 15
|
|
},
|
|
{
|
|
name: 'domains',
|
|
description: 'Domains to activate',
|
|
type: 'array'
|
|
}
|
|
],
|
|
action: async (ctx) => {
|
|
const agentCount = ctx.flags.agents || 15;
|
|
output.writeln();
|
|
output.writeln(output.bold('V3 15-Agent Hierarchical Mesh Coordination'));
|
|
output.writeln();
|
|
// V3 agent structure
|
|
const v3Agents = [
|
|
{ id: 1, role: 'Queen Coordinator', domain: 'Orchestration', status: 'primary' },
|
|
{ id: 2, role: 'Security Architect', domain: 'Security', status: 'active' },
|
|
{ id: 3, role: 'Security Auditor', domain: 'Security', status: 'active' },
|
|
{ id: 4, role: 'Test Architect', domain: 'Security', status: 'active' },
|
|
{ id: 5, role: 'Core Architect', domain: 'Core', status: 'active' },
|
|
{ id: 6, role: 'Memory Specialist', domain: 'Core', status: 'active' },
|
|
{ id: 7, role: 'Swarm Specialist', domain: 'Core', status: 'active' },
|
|
{ id: 8, role: 'Integration Architect', domain: 'Integration', status: 'active' },
|
|
{ id: 9, role: 'Performance Engineer', domain: 'Integration', status: 'active' },
|
|
{ id: 10, role: 'CLI Developer', domain: 'Integration', status: 'active' },
|
|
{ id: 11, role: 'Hooks Developer', domain: 'Integration', status: 'active' },
|
|
{ id: 12, role: 'MCP Specialist', domain: 'Integration', status: 'active' },
|
|
{ id: 13, role: 'Project Coordinator', domain: 'Management', status: 'active' },
|
|
{ id: 14, role: 'Documentation Lead', domain: 'Management', status: 'standby' },
|
|
{ id: 15, role: 'DevOps Engineer', domain: 'Management', status: 'standby' }
|
|
].slice(0, agentCount);
|
|
output.printTable({
|
|
columns: [
|
|
{ key: 'id', header: '#', width: 3, align: 'right' },
|
|
{ key: 'role', header: 'Role', width: 22 },
|
|
{ key: 'domain', header: 'Domain', width: 15 },
|
|
{ key: 'status', header: 'Status', width: 10, format: (v) => {
|
|
if (v === 'primary')
|
|
return output.highlight(String(v));
|
|
if (v === 'active')
|
|
return output.success(String(v));
|
|
return output.dim(String(v));
|
|
} }
|
|
],
|
|
data: v3Agents
|
|
});
|
|
// Actually initialize via MCP instead of just displaying (#1423)
|
|
output.writeln();
|
|
try {
|
|
await callMCPTool('swarm_init', {
|
|
topology: 'hierarchical-mesh',
|
|
maxAgents: agentCount,
|
|
strategy: 'specialized',
|
|
});
|
|
output.printSuccess(`Swarm coordination initialized with ${agentCount} agent slots via MCP`);
|
|
}
|
|
catch {
|
|
output.printWarning('MCP unavailable — showing agent plan only (no active coordination)');
|
|
}
|
|
output.writeln();
|
|
output.writeln(output.dim('Note: Use Claude Code Task tool or hive-mind spawn --claude to'));
|
|
output.writeln(output.dim('drive actual agent execution. This command sets up the topology.'));
|
|
return { success: true, data: { agents: v3Agents, count: agentCount } };
|
|
}
|
|
};
|
|
// Main swarm command
|
|
export const swarmCommand = {
|
|
name: 'swarm',
|
|
description: 'Swarm coordination commands',
|
|
subcommands: [initCommand, startCommand, statusCommand, stopCommand, scaleCommand, coordinateCommand],
|
|
options: [],
|
|
examples: [
|
|
{ command: 'claude-flow swarm init --v3-mode', description: 'Initialize V3 swarm' },
|
|
{ command: 'claude-flow swarm start -o "Build API" -s development', description: 'Start development swarm' },
|
|
{ command: 'claude-flow swarm coordinate --agents 15', description: 'V3 coordination' }
|
|
],
|
|
action: async (ctx) => {
|
|
output.writeln();
|
|
output.writeln(output.bold('Swarm Coordination Commands'));
|
|
output.writeln();
|
|
output.writeln('Usage: claude-flow swarm <subcommand> [options]');
|
|
output.writeln();
|
|
output.writeln('Subcommands:');
|
|
output.printList([
|
|
`${output.highlight('init')} - Initialize a new swarm`,
|
|
`${output.highlight('start')} - Start swarm execution`,
|
|
`${output.highlight('status')} - Show swarm status`,
|
|
`${output.highlight('stop')} - Stop swarm execution`,
|
|
`${output.highlight('scale')} - Scale swarm agent count`,
|
|
`${output.highlight('coordinate')} - V3 15-agent coordination`
|
|
]);
|
|
return { success: true };
|
|
}
|
|
};
|
|
// Helper function
|
|
function getAgentPlan(strategy) {
|
|
const plans = {
|
|
specialized: [
|
|
{ role: 'Coordinator', type: 'coordinator', count: 1, purpose: 'Central orchestration (anti-drift)' },
|
|
{ role: 'Researcher', type: 'researcher', count: 1, purpose: 'Requirements analysis' },
|
|
{ role: 'Architect', type: 'architect', count: 1, purpose: 'System design' },
|
|
{ role: 'Coder', type: 'coder', count: 2, purpose: 'Implementation' },
|
|
{ role: 'Tester', type: 'tester', count: 1, purpose: 'Quality assurance' },
|
|
{ role: 'Reviewer', type: 'reviewer', count: 1, purpose: 'Code review' }
|
|
],
|
|
balanced: [
|
|
{ role: 'Coordinator', type: 'coordinator', count: 1, purpose: 'Orchestrate workflow' },
|
|
{ role: 'Worker', type: 'coder', count: 4, purpose: 'General implementation' },
|
|
{ role: 'Reviewer', type: 'reviewer', count: 1, purpose: 'Quality review' }
|
|
],
|
|
adaptive: [
|
|
{ role: 'Coordinator', type: 'coordinator', count: 1, purpose: 'Dynamic orchestration' },
|
|
{ role: 'Scout', type: 'researcher', count: 1, purpose: 'Task analysis' },
|
|
{ role: 'Worker', type: 'coder', count: 3, purpose: 'Adaptive execution' }
|
|
],
|
|
development: [
|
|
{ role: 'Coordinator', type: 'coordinator', count: 1, purpose: 'Orchestrate workflow' },
|
|
{ role: 'Architect', type: 'architect', count: 1, purpose: 'System design' },
|
|
{ role: 'Coder', type: 'coder', count: 3, purpose: 'Implementation' },
|
|
{ role: 'Tester', type: 'tester', count: 2, purpose: 'Quality assurance' },
|
|
{ role: 'Reviewer', type: 'reviewer', count: 1, purpose: 'Code review' }
|
|
],
|
|
research: [
|
|
{ role: 'Coordinator', type: 'coordinator', count: 1, purpose: 'Research coordination' },
|
|
{ role: 'Researcher', type: 'researcher', count: 4, purpose: 'Data gathering' },
|
|
{ role: 'Analyst', type: 'analyst', count: 2, purpose: 'Analysis and synthesis' }
|
|
],
|
|
testing: [
|
|
{ role: 'Test Lead', type: 'tester', count: 1, purpose: 'Test strategy' },
|
|
{ role: 'Unit Tester', type: 'tester', count: 2, purpose: 'Unit tests' },
|
|
{ role: 'Integration Tester', type: 'tester', count: 2, purpose: 'Integration tests' },
|
|
{ role: 'QA Reviewer', type: 'reviewer', count: 1, purpose: 'Quality review' }
|
|
],
|
|
optimization: [
|
|
{ role: 'Performance Lead', type: 'optimizer', count: 1, purpose: 'Performance strategy' },
|
|
{ role: 'Profiler', type: 'analyst', count: 2, purpose: 'Profiling' },
|
|
{ role: 'Optimizer', type: 'coder', count: 2, purpose: 'Optimization' }
|
|
],
|
|
maintenance: [
|
|
{ role: 'Coordinator', type: 'coordinator', count: 1, purpose: 'Maintenance planning' },
|
|
{ role: 'Refactorer', type: 'coder', count: 2, purpose: 'Code cleanup' },
|
|
{ role: 'Documenter', type: 'researcher', count: 1, purpose: 'Documentation' }
|
|
],
|
|
analysis: [
|
|
{ role: 'Analyst Lead', type: 'analyst', count: 1, purpose: 'Analysis coordination' },
|
|
{ role: 'Code Analyst', type: 'analyst', count: 2, purpose: 'Code analysis' },
|
|
{ role: 'Security Analyst', type: 'reviewer', count: 1, purpose: 'Security review' }
|
|
]
|
|
};
|
|
return plans[strategy] || plans.development;
|
|
}
|
|
export default swarmCommand;
|
|
//# sourceMappingURL=swarm.js.map
|