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

487 lines 21.2 kB
/** * Task MCP Tools for CLI * * Tool definitions for task management with file persistence. */ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs'; import { join } from 'node:path'; import { getProjectCwd } from './types.js'; import { validateIdentifier, validateText } from './validate-input.js'; // Storage paths const STORAGE_DIR = '.claude-flow'; const TASK_DIR = 'tasks'; const TASK_FILE = 'store.json'; function getTaskDir() { return join(getProjectCwd(), STORAGE_DIR, TASK_DIR); } function getTaskPath() { return join(getTaskDir(), TASK_FILE); } function ensureTaskDir() { const dir = getTaskDir(); if (!existsSync(dir)) { mkdirSync(dir, { recursive: true }); } } function loadTaskStore() { try { const path = getTaskPath(); if (existsSync(path)) { const data = readFileSync(path, 'utf-8'); return JSON.parse(data); } } catch { // Return empty store on error } return { tasks: {}, version: '3.0.0' }; } function saveTaskStore(store) { ensureTaskDir(); writeFileSync(getTaskPath(), JSON.stringify(store, null, 2), 'utf-8'); } export const taskTools = [ { name: 'task_create', description: 'Create a new task Use when native TodoWrite is wrong because you need cross-session task persistence, agent assignment, dependency tracking, or completion analytics in the .swarm/memory.db. For in-session checklists native TodoWrite is simpler and faster.', category: 'task', inputSchema: { type: 'object', properties: { type: { type: 'string', description: 'Task type (feature, bugfix, research, refactor)' }, description: { type: 'string', description: 'Task description' }, priority: { type: 'string', description: 'Task priority (low, normal, high, critical)' }, assignTo: { type: 'array', items: { type: 'string' }, description: 'Agent IDs to assign' }, tags: { type: 'array', items: { type: 'string' }, description: 'Task tags' }, }, required: ['type', 'description'], }, handler: async (input) => { // Validate user-provided input (#1425) const vType = validateIdentifier(input.type, 'type'); if (!vType.valid) return { success: false, error: vType.error }; const vDesc = validateText(input.description, 'description'); if (!vDesc.valid) return { success: false, error: vDesc.error }; const store = loadTaskStore(); const taskId = `task-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; const task = { taskId, type: input.type, description: input.description, priority: input.priority || 'normal', status: 'pending', progress: 0, assignedTo: input.assignTo || [], tags: input.tags || [], createdAt: new Date().toISOString(), startedAt: null, completedAt: null, }; store.tasks[taskId] = task; saveTaskStore(store); return { taskId, type: task.type, description: task.description, priority: task.priority, status: task.status, createdAt: task.createdAt, assignedTo: task.assignedTo, tags: task.tags, }; }, }, { name: 'task_status', description: 'Get task status Use when native TodoWrite is wrong because you need cross-session task persistence, agent assignment, dependency tracking, or completion analytics in the .swarm/memory.db. For in-session checklists native TodoWrite is simpler and faster.', category: 'task', inputSchema: { type: 'object', properties: { taskId: { type: 'string', description: 'Task ID' }, }, required: ['taskId'], }, handler: async (input) => { // Validate user-provided input (#1425) const vId = validateIdentifier(input.taskId, 'taskId'); if (!vId.valid) return { success: false, error: vId.error }; const store = loadTaskStore(); const taskId = input.taskId; const task = store.tasks[taskId]; if (task) { return { taskId: task.taskId, type: task.type, description: task.description, status: task.status, progress: task.progress, priority: task.priority, assignedTo: task.assignedTo, tags: task.tags, createdAt: task.createdAt, startedAt: task.startedAt, completedAt: task.completedAt, result: task.result || null, }; } return { taskId, status: 'not_found', error: 'Task not found', }; }, }, { name: 'task_list', description: 'List all tasks Use when native TodoWrite is wrong because you need cross-session task persistence, agent assignment, dependency tracking, or completion analytics in the .swarm/memory.db. For in-session checklists native TodoWrite is simpler and faster.', category: 'task', inputSchema: { type: 'object', properties: { status: { type: 'string', description: 'Filter by status' }, type: { type: 'string', description: 'Filter by type' }, assignedTo: { type: 'string', description: 'Filter by assigned agent' }, priority: { type: 'string', description: 'Filter by priority' }, limit: { type: 'number', description: 'Max tasks to return' }, }, }, handler: async (input) => { const store = loadTaskStore(); let tasks = Object.values(store.tasks); // Apply filters if (input.status) { // Support comma-separated status values const statuses = input.status.split(',').map(s => s.trim()); tasks = tasks.filter(t => statuses.includes(t.status)); } if (input.type) { tasks = tasks.filter(t => t.type === input.type); } if (input.assignedTo) { tasks = tasks.filter(t => t.assignedTo.includes(input.assignedTo)); } if (input.priority) { tasks = tasks.filter(t => t.priority === input.priority); } // Sort by creation date (newest first) tasks.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()); // Apply limit const limit = input.limit || 50; tasks = tasks.slice(0, limit); return { tasks: tasks.map(t => ({ taskId: t.taskId, type: t.type, description: t.description, status: t.status, progress: t.progress, priority: t.priority, assignedTo: t.assignedTo, createdAt: t.createdAt, })), total: tasks.length, filters: { status: input.status, type: input.type, assignedTo: input.assignedTo, priority: input.priority, }, }; }, }, { name: 'task_complete', description: 'Mark task as complete Use when native TodoWrite is wrong because you need cross-session task persistence, agent assignment, dependency tracking, or completion analytics in the .swarm/memory.db. For in-session checklists native TodoWrite is simpler and faster.', category: 'task', inputSchema: { type: 'object', properties: { taskId: { type: 'string', description: 'Task ID' }, result: { type: 'object', description: 'Task result data' }, }, required: ['taskId'], }, handler: async (input) => { // Validate user-provided input (#1425) const vId = validateIdentifier(input.taskId, 'taskId'); if (!vId.valid) return { success: false, error: vId.error }; const store = loadTaskStore(); const taskId = input.taskId; const task = store.tasks[taskId]; if (task) { task.status = 'completed'; task.progress = 100; task.completedAt = new Date().toISOString(); task.result = input.result || {}; saveTaskStore(store); // Sync assigned agents back to idle and increment taskCount if (task.assignedTo.length > 0) { const agentStorePath = join(getProjectCwd(), STORAGE_DIR, 'agents', 'store.json'); try { let agentStore = { agents: {} }; if (existsSync(agentStorePath)) { agentStore = JSON.parse(readFileSync(agentStorePath, 'utf-8')); } for (const agentId of task.assignedTo) { if (agentStore.agents[agentId]) { agentStore.agents[agentId].status = 'idle'; agentStore.agents[agentId].currentTask = null; agentStore.agents[agentId].taskCount = (agentStore.agents[agentId].taskCount || 0) + 1; } } writeFileSync(agentStorePath, JSON.stringify(agentStore, null, 2), 'utf-8'); } catch { // Best-effort agent sync } } return { taskId: task.taskId, status: task.status, completedAt: task.completedAt, result: task.result, }; } return { taskId, status: 'not_found', error: 'Task not found', }; }, }, { name: 'task_update', description: 'Update task status or progress Use when native TodoWrite is wrong because you need cross-session task persistence, agent assignment, dependency tracking, or completion analytics in the .swarm/memory.db. For in-session checklists native TodoWrite is simpler and faster.', category: 'task', inputSchema: { type: 'object', properties: { taskId: { type: 'string', description: 'Task ID' }, status: { type: 'string', description: 'New status' }, progress: { type: 'number', description: 'Progress percentage (0-100)' }, assignTo: { type: 'array', items: { type: 'string' }, description: 'Agent IDs to assign' }, }, required: ['taskId'], }, handler: async (input) => { // Validate user-provided input (#1425) const vId = validateIdentifier(input.taskId, 'taskId'); if (!vId.valid) return { success: false, error: vId.error }; const store = loadTaskStore(); const taskId = input.taskId; const task = store.tasks[taskId]; if (task) { if (input.status) { const newStatus = input.status; task.status = newStatus; if (newStatus === 'in_progress' && !task.startedAt) { task.startedAt = new Date().toISOString(); } } if (typeof input.progress === 'number') { task.progress = Math.min(100, Math.max(0, input.progress)); } if (input.assignTo) { task.assignedTo = input.assignTo; } saveTaskStore(store); return { success: true, taskId: task.taskId, status: task.status, progress: task.progress, assignedTo: task.assignedTo, }; } return { success: false, taskId, error: 'Task not found', }; }, }, { name: 'task_assign', description: 'Assign a task to one or more agents Use when native TodoWrite is wrong because you need cross-session task persistence, agent assignment, dependency tracking, or completion analytics in the .swarm/memory.db. For in-session checklists native TodoWrite is simpler and faster.', category: 'task', inputSchema: { type: 'object', properties: { taskId: { type: 'string', description: 'Task ID to assign' }, agentIds: { type: 'array', items: { type: 'string' }, description: 'Agent IDs to assign' }, unassign: { type: 'boolean', description: 'Unassign all agents from task' }, }, required: ['taskId'], }, handler: async (input) => { // Validate user-provided input (#1425) const vId = validateIdentifier(input.taskId, 'taskId'); if (!vId.valid) return { success: false, error: vId.error }; const store = loadTaskStore(); const taskId = input.taskId; const task = store.tasks[taskId]; if (!task) { return { taskId, error: 'Task not found' }; } const previouslyAssigned = [...task.assignedTo]; // Load agent store to sync worker state const agentStorePath = join(getProjectCwd(), STORAGE_DIR, 'agents', 'store.json'); let agentStore = { agents: {} }; try { if (existsSync(agentStorePath)) { agentStore = JSON.parse(readFileSync(agentStorePath, 'utf-8')); } } catch { /* ignore */ } if (input.unassign) { // Revert previously assigned agents to idle for (const agentId of previouslyAssigned) { if (agentStore.agents[agentId]) { agentStore.agents[agentId].status = 'idle'; agentStore.agents[agentId].currentTask = null; } } task.assignedTo = []; } else { const agentIds = input.agentIds || []; // Revert old agents to idle for (const agentId of previouslyAssigned) { if (!agentIds.includes(agentId) && agentStore.agents[agentId]) { agentStore.agents[agentId].status = 'idle'; agentStore.agents[agentId].currentTask = null; } } // Set new agents to active for (const agentId of agentIds) { if (agentStore.agents[agentId]) { agentStore.agents[agentId].status = 'active'; agentStore.agents[agentId].currentTask = taskId; } } task.assignedTo = agentIds; // Auto-transition task to in_progress if pending if (task.status === 'pending' && agentIds.length > 0) { task.status = 'in_progress'; if (!task.startedAt) { task.startedAt = new Date().toISOString(); } } } saveTaskStore(store); // Save agent store const agentDir = join(getProjectCwd(), STORAGE_DIR, 'agents'); if (!existsSync(agentDir)) { mkdirSync(agentDir, { recursive: true }); } writeFileSync(agentStorePath, JSON.stringify(agentStore, null, 2), 'utf-8'); return { taskId: task.taskId, assignedTo: task.assignedTo, previouslyAssigned, status: task.status, }; }, }, { name: 'task_cancel', description: 'Cancel a task Use when native TodoWrite is wrong because you need cross-session task persistence, agent assignment, dependency tracking, or completion analytics in the .swarm/memory.db. For in-session checklists native TodoWrite is simpler and faster.', category: 'task', inputSchema: { type: 'object', properties: { taskId: { type: 'string', description: 'Task ID' }, reason: { type: 'string', description: 'Cancellation reason' }, }, required: ['taskId'], }, handler: async (input) => { // Validate user-provided input (#1425) const vId = validateIdentifier(input.taskId, 'taskId'); if (!vId.valid) return { success: false, error: vId.error }; if (input.reason) { const v = validateText(input.reason, 'reason'); if (!v.valid) return { success: false, error: v.error }; } const store = loadTaskStore(); const taskId = input.taskId; const task = store.tasks[taskId]; if (task) { task.status = 'cancelled'; task.completedAt = new Date().toISOString(); task.result = { cancelReason: input.reason || 'Cancelled by user' }; saveTaskStore(store); return { success: true, taskId: task.taskId, status: task.status, cancelledAt: task.completedAt, }; } return { success: false, taskId, error: 'Task not found', }; }, }, { // #1916: the `ruflo task retry <id>` CLI subcommand referenced an // unregistered `task_retry` tool. Re-queues a finished/cancelled task by // cloning its spec into a fresh pending task (the original is left intact // as history). name: 'task_retry', description: 'Re-queue a failed/cancelled/completed task by cloning its spec into a fresh pending task (the original record is kept as history). Use when native TodoWrite is wrong because you need the original task\'s persisted spec (type, priority, assignees, tags) and a stable taskId chain across runs rather than hand-retyping a checklist item. For ad-hoc re-runs, native TodoWrite is fine.', category: 'task', inputSchema: { type: 'object', properties: { taskId: { type: 'string', description: 'ID of the task to retry' }, resetState: { type: 'boolean', description: 'Reset progress/result on the new task (default true)' }, }, required: ['taskId'], }, handler: async (input) => { const v = validateIdentifier(input.taskId, 'taskId'); if (!v.valid) return { success: false, error: v.error }; const store = loadTaskStore(); const taskId = input.taskId; const original = store.tasks[taskId]; if (!original) return { success: false, taskId, error: 'Task not found' }; const newTaskId = `task-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; store.tasks[newTaskId] = { taskId: newTaskId, type: original.type, description: original.description, priority: original.priority, status: 'pending', progress: 0, assignedTo: [...original.assignedTo], tags: [...original.tags, 'retry-of:' + taskId], createdAt: new Date().toISOString(), startedAt: null, completedAt: null, }; saveTaskStore(store); return { taskId, newTaskId, previousStatus: original.status, status: 'pending', }; }, }, ]; //# sourceMappingURL=task-tools.js.map