UNPKG

claude-flow

Version:

Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration

362 lines 17.1 kB
/** * V3 CLI Autopilot Command * Persistent swarm completion — keeps agents working until ALL tasks are done. * * ADR-072: Autopilot Integration */ import { output } from '../output.js'; import { loadState, saveState, appendLog, loadLog, discoverTasks, getProgress, calculateReward, tryLoadLearning, validateNumber, validateTaskSources, LOG_FILE, } from '../autopilot-state.js'; // ── Check Handler (for Stop hook) ───────────────────────────── export async function autopilotCheck() { const state = loadState(); if (!state.enabled) { return { allowStop: true, reason: 'Autopilot disabled' }; } // Safety: max iterations if (state.iterations >= state.maxIterations) { state.enabled = false; saveState(state); appendLog({ ts: Date.now(), event: 'max-iterations-reached', iterations: state.iterations }); return { allowStop: true, reason: `Max iterations (${state.maxIterations}) reached` }; } // Safety: timeout const elapsed = Date.now() - state.startTime; if (elapsed > state.timeoutMinutes * 60000) { state.enabled = false; saveState(state); appendLog({ ts: Date.now(), event: 'timeout-reached', elapsed: Math.round(elapsed / 60000) }); return { allowStop: true, reason: `Timeout (${state.timeoutMinutes} min) reached` }; } // Discover tasks const tasks = discoverTasks(state.taskSources); if (tasks.length === 0) { return { allowStop: true, reason: 'No tasks discovered from any source' }; } const progress = getProgress(tasks); if (progress.incomplete.length === 0) { const reward = calculateReward(state.iterations, elapsed); state.enabled = false; saveState(state); appendLog({ ts: Date.now(), event: 'all-tasks-complete', total: progress.total, iterations: state.iterations, durationMs: elapsed, reward }); return { allowStop: true, reason: `All ${progress.total} tasks complete (${state.iterations} iterations, reward: ${reward})` }; } // Stall detection const recentHistory = state.history.slice(-5); const isStalled = recentHistory.length >= 5 && recentHistory.every(h => h.completed === progress.completed); if (isStalled && state.history.length >= 10) { state.enabled = false; saveState(state); appendLog({ ts: Date.now(), event: 'stall-auto-disable', iterations: state.iterations, completed: progress.completed }); return { allowStop: true, reason: `Stalled: no progress in 10 iterations (${progress.completed}/${progress.total} complete)` }; } // Re-engage state.iterations++; state.lastCheck = Date.now(); state.history.push({ ts: Date.now(), iteration: state.iterations, completed: progress.completed, total: progress.total }); saveState(state); const stallWarning = isStalled ? '\nWARNING: No progress in 5 iterations. Consider breaking tasks into smaller subtasks or trying a different approach.' : ''; const taskList = progress.incomplete.slice(0, 5).map(t => `- ${t.subject} (${t.status})`).join('\n'); const moreCount = progress.incomplete.length > 5 ? `\n... and ${progress.incomplete.length - 5} more` : ''; const continuePrompt = [ `AUTOPILOT: ${progress.completed}/${progress.total} tasks complete (${progress.percent}%).`, `Iteration ${state.iterations}/${state.maxIterations}.`, `Remaining tasks:\n${taskList}${moreCount}`, stallWarning, `Continue working on the remaining tasks. Do not stop until all are complete.`, ].join('\n'); appendLog({ ts: Date.now(), event: 're-engaging', iteration: state.iterations, completed: progress.completed, remaining: progress.incomplete.length }); return { allowStop: false, reason: `${progress.incomplete.length}/${progress.total} tasks remaining (iteration ${state.iterations}/${state.maxIterations})`, continueWith: continuePrompt }; } // ── Subcommands ─────────────────────────────────────────────── const statusCommand = { name: 'status', description: 'Show autopilot state, iterations, and task progress', options: [{ name: 'json', type: 'boolean', description: 'Output as JSON' }], action: async (ctx) => { const state = loadState(); const tasks = discoverTasks(state.taskSources); const progress = getProgress(tasks); const elapsed = state.enabled ? Date.now() - state.startTime : 0; if (ctx.flags?.json) { output.printJson({ enabled: state.enabled, sessionId: state.sessionId, iterations: state.iterations, maxIterations: state.maxIterations, timeoutMinutes: state.timeoutMinutes, elapsedMs: elapsed, tasks: { completed: progress.completed, total: progress.total, percent: progress.percent }, taskSources: state.taskSources, }); return { success: true }; } output.writeln(`Autopilot: ${state.enabled ? '✓ ENABLED' : '✗ DISABLED'}`); output.writeln(`Session: ${state.sessionId.slice(0, 8)}...`); output.writeln(`Iterations: ${state.iterations}/${state.maxIterations}`); output.writeln(`Timeout: ${state.timeoutMinutes} min`); output.writeln(`Elapsed: ${Math.round(elapsed / 60000)} min`); output.writeln(`Tasks: ${progress.completed}/${progress.total} (${progress.percent}%)`); output.writeln(`Sources: ${state.taskSources.join(', ')}`); if (progress.incomplete.length > 0 && progress.incomplete.length <= 10) { output.writeln('\nRemaining tasks:'); for (const t of progress.incomplete) { output.writeln(` - [${t.source}] ${t.subject} (${t.status})`); } } return { success: true }; }, }; const enableCommand = { name: 'enable', description: 'Enable persistent completion', action: async () => { const state = loadState(); state.enabled = true; state.startTime = Date.now(); state.iterations = 0; saveState(state); appendLog({ ts: Date.now(), event: 'enabled', sessionId: state.sessionId, maxIterations: state.maxIterations }); output.writeln(output.success(`Autopilot enabled (max ${state.maxIterations} iterations, ${state.timeoutMinutes} min timeout)`)); return { success: true }; }, }; const disableCommand = { name: 'disable', description: 'Disable re-engagement loop', action: async () => { const state = loadState(); const wasEnabled = state.enabled; state.enabled = false; saveState(state); if (wasEnabled) appendLog({ ts: Date.now(), event: 'disabled', iterations: state.iterations }); output.writeln('Autopilot disabled'); return { success: true }; }, }; const configCommand = { name: 'config', description: 'Configure max iterations, timeout, and task sources', options: [ { name: 'max-iterations', type: 'string', description: 'Max re-engagement iterations (1-1000)' }, { name: 'timeout', type: 'string', description: 'Timeout in minutes (1-1440)' }, { name: 'task-sources', type: 'string', description: 'Comma-separated task sources' }, ], action: async (ctx) => { const state = loadState(); const maxIter = ctx.flags?.['max-iterations']; const timeout = ctx.flags?.timeout; const sources = ctx.flags?.['task-sources']; if (maxIter) state.maxIterations = validateNumber(maxIter, 1, 1000, state.maxIterations); if (timeout) state.timeoutMinutes = validateNumber(timeout, 1, 1440, state.timeoutMinutes); if (sources) state.taskSources = validateTaskSources(sources.split(',').map(s => s.trim()).filter(Boolean)); saveState(state); appendLog({ ts: Date.now(), event: 'config-updated', maxIterations: state.maxIterations, timeoutMinutes: state.timeoutMinutes, taskSources: state.taskSources }); output.writeln(`Config updated: maxIterations=${state.maxIterations}, timeout=${state.timeoutMinutes}min, sources=${state.taskSources.join(',')}`); return { success: true }; }, }; const resetCommand = { name: 'reset', description: 'Reset iteration counter and timer', action: async () => { const state = loadState(); state.iterations = 0; state.startTime = Date.now(); state.history = []; state.lastCheck = null; saveState(state); appendLog({ ts: Date.now(), event: 'reset' }); output.writeln('Autopilot state reset (iterations=0, timer restarted)'); return { success: true }; }, }; const logCommand = { name: 'log', description: 'View autopilot event log', options: [ { name: 'last', type: 'string', description: 'Show last N entries' }, { name: 'json', type: 'boolean', description: 'Output as JSON' }, { name: 'clear', type: 'boolean', description: 'Clear the log' }, ], action: async (ctx) => { if (ctx.flags?.clear) { const { writeFileSync } = await import('node:fs'); const { resolve } = await import('node:path'); try { writeFileSync(resolve(LOG_FILE), '[]'); } catch { /* ignore */ } output.writeln('Autopilot log cleared'); return { success: true }; } const log = loadLog(); const lastRaw = ctx.flags?.last; const last = lastRaw ? validateNumber(lastRaw, 1, 10000, 50) : undefined; const entries = last ? log.slice(-last) : log; if (ctx.flags?.json) { output.printJson(entries); return { success: true }; } if (entries.length === 0) { output.writeln('No autopilot events logged'); return { success: true }; } for (const e of entries) { const time = new Date(e.ts).toISOString().slice(11, 19); const details = Object.entries(e).filter(([k]) => k !== 'ts' && k !== 'event').map(([k, v]) => `${k}=${v}`).join(' '); output.writeln(`[${time}] ${e.event} ${details}`); } return { success: true }; }, }; const learnCommand = { name: 'learn', description: 'Discover success patterns from past completions', options: [{ name: 'json', type: 'boolean', description: 'Output as JSON' }], action: async (ctx) => { const learning = await tryLoadLearning(); if (!learning) { output.writeln('Learning not available (AgentDB not initialized). Autopilot still works for task completion tracking.'); return { success: true }; } const metrics = await learning.getMetrics(); const patterns = await learning.discoverSuccessPatterns(); if (ctx.flags?.json) { output.printJson({ metrics, patterns }); return { success: true }; } output.writeln(`Episodes: ${metrics.episodes}`); output.writeln(`Patterns: ${metrics.patterns}`); output.writeln(`Trajectories: ${metrics.trajectories}`); if (patterns.length > 0) { output.writeln('\nDiscovered patterns:'); for (const p of patterns) { output.writeln(` - ${p.pattern} (freq: ${p.frequency}, reward: ${p.avgReward.toFixed(2)})`); } } return { success: true }; }, }; const historyCommand = { name: 'history', description: 'Search past completion episodes', options: [ { name: 'query', type: 'string', description: 'Search query', required: true }, { name: 'limit', type: 'string', description: 'Max results (default 10)' }, { name: 'json', type: 'boolean', description: 'Output as JSON' }, ], action: async (ctx) => { const query = (ctx.flags?.query || ''); const limit = validateNumber(ctx.flags?.limit, 1, 100, 10); if (!query) { output.writeln('Usage: autopilot history --query "search terms" [--limit N]'); return { success: false, message: 'Missing --query' }; } const learning = await tryLoadLearning(); if (!learning) { output.writeln('Learning not available. No history to search.'); return { success: true }; } const results = await learning.recallSimilarTasks(query, limit); if (ctx.flags?.json) { output.printJson(results); } else if (results.length === 0) { output.writeln(`No matching episodes for: "${query}"`); } else { output.printJson(results); } return { success: true }; }, }; const predictCommand = { name: 'predict', description: 'Predict optimal next action', options: [{ name: 'json', type: 'boolean', description: 'Output as JSON' }], action: async (ctx) => { const state = loadState(); const learning = await tryLoadLearning(); if (learning) { const prediction = await learning.predictNextAction(state); if (ctx.flags?.json) { output.printJson(prediction); } else { output.writeln(`Action: ${prediction?.action || 'unknown'}`); output.writeln(`Confidence: ${prediction?.confidence || 0}`); if (prediction?.alternatives && prediction.alternatives.length > 0) output.writeln(`Alternatives: ${prediction.alternatives.join(', ')}`); } return { success: true }; } // Heuristic fallback const tasks = discoverTasks(state.taskSources); const progress = getProgress(tasks); if (progress.incomplete.length === 0) { output.writeln('All tasks complete. No action needed.'); return { success: true }; } const next = progress.incomplete[0]; const result = { action: `Work on: ${next.subject}`, confidence: 0.5, remaining: progress.incomplete.length }; if (ctx.flags?.json) { output.printJson(result); } else { output.writeln(`Action: ${result.action}`); output.writeln(`Confidence: ${result.confidence} (heuristic — learning not available)`); output.writeln(`Remaining: ${result.remaining} tasks`); } return { success: true }; }, }; const checkCommand = { name: 'check', description: 'Run completion check (used by stop hook)', options: [{ name: 'json', type: 'boolean', description: 'Output as JSON' }], action: async (ctx) => { const result = await autopilotCheck(); if (ctx.flags?.json) { output.printJson(result); } else { output.writeln(`${result.allowStop ? 'ALLOW STOP' : 'CONTINUE'}: ${result.reason}`); } return { success: true }; }, }; // ── Main Command ────────────────────────────────────────────── export const autopilotCommand = { name: 'autopilot', description: 'Persistent swarm completion — keeps agents working until ALL tasks are done', aliases: ['ap'], subcommands: [statusCommand, enableCommand, disableCommand, configCommand, resetCommand, logCommand, learnCommand, historyCommand, predictCommand, checkCommand], examples: [ { command: 'claude-flow autopilot status', description: 'Show current state and progress' }, { command: 'claude-flow autopilot enable', description: 'Enable persistent completion' }, { command: 'claude-flow autopilot config --max-iterations 100 --timeout 180', description: 'Configure limits' }, { command: 'claude-flow autopilot predict', description: 'Get recommended next action' }, ], action: async () => { output.writeln(output.bold('Autopilot — Persistent Swarm Completion')); output.writeln(output.dim('Keeps agents working until ALL tasks are done')); output.writeln(); output.printList([ 'status — Show state, iterations, and task progress', 'enable — Enable persistent completion', 'disable — Disable re-engagement loop', 'config — Configure max iterations, timeout, sources', 'reset — Reset iteration counter and timer', 'log — View autopilot event log', 'learn — Discover success patterns', 'history — Search past completion episodes', 'predict — Predict optimal next action', 'check — Run completion check (stop hook)', ]); return { success: true }; }, }; export default autopilotCommand; //# sourceMappingURL=autopilot.js.map