claude-flow-novice
Version:
Claude Flow Novice - Advanced orchestration platform for multi-agent AI workflows with CFN Loop architecture Includes Local RuVector Accelerator and all CFN skills for complete functionality.
562 lines (561 loc) • 19.1 kB
JavaScript
/**
* Task Management API Endpoints
*
* RESTful API endpoints for managing CFN Loop tasks, including
* creation, updates, status tracking, and lifecycle management.
*/ import { Router } from 'express';
import { v4 as uuidv4 } from 'uuid';
import { z } from 'zod';
// Validation schemas
const createTaskSchema = z.object({
name: z.string().min(1, 'Task name is required').max(200, 'Task name too long'),
description: z.string().min(1, 'Task description is required').max(2000, 'Task description too long'),
priority: z.enum([
'low',
'medium',
'high',
'critical'
]).optional().default('medium'),
assignedTo: z.string().optional(),
metadata: z.record(z.any()).optional().default({})
});
const updateTaskSchema = z.object({
name: z.string().min(1).max(200).optional(),
description: z.string().min(1).max(2000).optional(),
status: z.enum([
'pending',
'running',
'completed',
'failed',
'cancelled'
]).optional(),
priority: z.enum([
'low',
'medium',
'high',
'critical'
]).optional(),
assignedTo: z.string().optional(),
metadata: z.record(z.any()).optional(),
progress: z.object({
current: z.number().min(0),
total: z.number().min(1)
}).optional()
});
const queryTaskSchema = z.object({
status: z.enum([
'pending',
'running',
'completed',
'failed',
'cancelled'
]).optional(),
priority: z.enum([
'low',
'medium',
'high',
'critical'
]).optional(),
assignedTo: z.string().optional(),
createdBy: z.string().optional(),
limit: z.string().transform(Number).refine((n)=>n > 0 && n <= 100).optional().default('20'),
offset: z.string().transform(Number).refine((n)=>n >= 0).optional().default('0'),
sortBy: z.enum([
'createdAt',
'updatedAt',
'priority',
'status'
]).optional().default('createdAt'),
sortOrder: z.enum([
'asc',
'desc'
]).optional().default('desc')
});
/**
* Task Endpoints Class
*/ export class TaskEndpoints {
taskManager;
tasks = new Map();
constructor(taskManager){
this.taskManager = taskManager;
// Initialize with some sample tasks for development
this.initializeSampleTasks();
}
/**
* Initialize sample tasks for development
*/ initializeSampleTasks() {
const sampleTasks = [
{
id: uuidv4(),
name: 'Implement User Authentication',
description: 'Add JWT-based authentication system with refresh tokens',
status: 'running',
priority: 'high',
createdBy: 'system',
createdAt: new Date(Date.now() - 3600000),
updatedAt: new Date(),
startedAt: new Date(Date.now() - 1800000),
metadata: {
phase: 'auth',
epic: 'user-management'
},
progress: {
current: 7,
total: 10,
percentage: 70
},
agents: [
{
id: 'backend-dev-1',
type: 'backend-developer',
status: 'completed',
confidence: 0.92
},
{
id: 'security-1',
type: 'security-specialist',
status: 'running',
confidence: 0.88
}
]
},
{
id: uuidv4(),
name: 'Database Migration Script',
description: 'Create migration script for new user roles table',
status: 'completed',
priority: 'medium',
createdBy: 'admin',
createdAt: new Date(Date.now() - 7200000),
updatedAt: new Date(Date.now() - 600000),
startedAt: new Date(Date.now() - 3600000),
completedAt: new Date(Date.now() - 600000),
metadata: {
phase: 'database',
migration: '003_add_user_roles'
},
progress: {
current: 5,
total: 5,
percentage: 100
},
agents: [
{
id: 'backend-dev-2',
type: 'backend-developer',
status: 'completed',
confidence: 0.95
}
]
}
];
sampleTasks.forEach((task)=>this.tasks.set(task.id, task));
}
/**
* Get Express router with all task endpoints
*/ getRouter() {
const router = Router();
// GET /tasks - List tasks with filtering
router.get('/', this.handleGetTasks.bind(this));
// GET /tasks/:id - Get specific task
router.get('/:id', this.handleGetTask.bind(this));
// POST /tasks - Create new task
router.post('/', this.handleCreateTask.bind(this));
// PUT /tasks/:id - Update task
router.put('/:id', this.handleUpdateTask.bind(this));
// PATCH /tasks/:id/status - Update task status
router.patch('/:id/status', this.handleUpdateTaskStatus.bind(this));
// DELETE /tasks/:id - Delete task
router.delete('/:id', this.handleDeleteTask.bind(this));
// POST /tasks/:id/agents - Add agent to task
router.post('/:id/agents', this.handleAddAgentToTask.bind(this));
// GET /tasks/:id/agents - Get task agents
router.get('/:id/agents', this.handleGetTaskAgents.bind(this));
// POST /tasks/:id/cancel - Cancel task
router.post('/:id/cancel', this.handleCancelTask.bind(this));
return router;
}
/**
* GET /tasks - List tasks with filtering and pagination
*/ async handleGetTasks(req, res, next) {
try {
const query = queryTaskSchema.parse(req.query);
let tasks = Array.from(this.tasks.values());
// Apply filters
if (query.status) {
tasks = tasks.filter((task)=>task.status === query.status);
}
if (query.priority) {
tasks = tasks.filter((task)=>task.priority === query.priority);
}
if (query.assignedTo) {
tasks = tasks.filter((task)=>task.assignedTo === query.assignedTo);
}
if (query.createdBy) {
tasks = tasks.filter((task)=>task.createdBy === query.createdBy);
}
// Sort tasks
tasks.sort((a, b)=>{
const aValue = a[query.sortBy];
const bValue = b[query.sortBy];
if (aValue instanceof Date && bValue instanceof Date) {
return query.sortOrder === 'asc' ? aValue.getTime() - bValue.getTime() : bValue.getTime() - aValue.getTime();
}
if (typeof aValue === 'string' && typeof bValue === 'string') {
return query.sortOrder === 'asc' ? aValue.localeCompare(bValue) : bValue.localeCompare(aValue);
}
return 0;
});
// Apply pagination
const total = tasks.length;
const paginatedTasks = tasks.slice(query.offset, query.offset + query.limit);
res.json({
tasks: paginatedTasks,
pagination: {
total,
limit: query.limit,
offset: query.offset,
hasMore: query.offset + query.limit < total
},
filters: {
status: query.status,
priority: query.priority,
assignedTo: query.assignedTo,
createdBy: query.createdBy
}
});
} catch (error) {
next(error);
}
}
/**
* GET /tasks/:id - Get specific task
*/ async handleGetTask(req, res, next) {
try {
const { id } = req.params;
const task = this.tasks.get(id);
if (!task) {
res.status(404).json({
error: 'Not Found',
message: `Task with ID ${id} not found`
});
return;
}
res.json({
task
});
} catch (error) {
next(error);
}
}
/**
* POST /tasks - Create new task
*/ async handleCreateTask(req, res, next) {
try {
const validatedData = createTaskSchema.parse(req.body);
const createdBy = req.user?.id || 'anonymous';
const task = {
id: uuidv4(),
name: validatedData.name,
description: validatedData.description,
status: 'pending',
priority: validatedData.priority,
assignedTo: validatedData.assignedTo,
createdBy,
createdAt: new Date(),
updatedAt: new Date(),
metadata: validatedData.metadata,
progress: {
current: 0,
total: 1,
percentage: 0
},
agents: []
};
this.tasks.set(task.id, task);
// Notify task manager if available
if (this.taskManager) {
await this.taskManager.createTask(task);
}
res.status(201).json({
message: 'Task created successfully',
task
});
} catch (error) {
next(error);
}
}
/**
* PUT /tasks/:id - Update task
*/ async handleUpdateTask(req, res, next) {
try {
const { id } = req.params;
const validatedData = updateTaskSchema.parse(req.body);
const task = this.tasks.get(id);
if (!task) {
res.status(404).json({
error: 'Not Found',
message: `Task with ID ${id} not found`
});
return;
}
// Update task fields
const updatedTask = {
...task,
...validatedData,
updatedAt: new Date()
};
// Update progress percentage if progress data provided
if (validatedData.progress) {
updatedTask.progress = {
...validatedData.progress,
percentage: Math.round(validatedData.progress.current / validatedData.progress.total * 100)
};
}
// Handle status transitions
if (validatedData.status) {
if (validatedData.status === 'running' && task.status !== 'running') {
updatedTask.startedAt = new Date();
} else if ([
'completed',
'failed',
'cancelled'
].includes(validatedData.status) && ![
'completed',
'failed',
'cancelled'
].includes(task.status)) {
updatedTask.completedAt = new Date();
updatedTask.progress.percentage = 100;
}
}
this.tasks.set(id, updatedTask);
// Notify task manager if available
if (this.taskManager) {
await this.taskManager.updateTask(updatedTask);
}
res.json({
message: 'Task updated successfully',
task: updatedTask
});
} catch (error) {
next(error);
}
}
/**
* PATCH /tasks/:id/status - Update task status
*/ async handleUpdateTaskStatus(req, res, next) {
try {
const { id } = req.params;
const { status } = req.body;
if (![
'pending',
'running',
'completed',
'failed',
'cancelled'
].includes(status)) {
res.status(400).json({
error: 'Bad Request',
message: 'Invalid status value'
});
return;
}
const task = this.tasks.get(id);
if (!task) {
res.status(404).json({
error: 'Not Found',
message: `Task with ID ${id} not found`
});
return;
}
const updatedTask = {
...task,
status,
updatedAt: new Date()
};
// Handle status transitions
if (status === 'running' && task.status !== 'running') {
updatedTask.startedAt = new Date();
} else if ([
'completed',
'failed',
'cancelled'
].includes(status) && ![
'completed',
'failed',
'cancelled'
].includes(task.status)) {
updatedTask.completedAt = new Date();
updatedTask.progress.percentage = 100;
}
this.tasks.set(id, updatedTask);
// Notify task manager if available
if (this.taskManager) {
await this.taskManager.updateTaskStatus(id, status);
}
res.json({
message: 'Task status updated successfully',
task: updatedTask
});
} catch (error) {
next(error);
}
}
/**
* DELETE /tasks/:id - Delete task
*/ async handleDeleteTask(req, res, next) {
try {
const { id } = req.params;
const task = this.tasks.get(id);
if (!task) {
res.status(404).json({
error: 'Not Found',
message: `Task with ID ${id} not found`
});
return;
}
// Don't allow deletion of running tasks
if (task.status === 'running') {
res.status(409).json({
error: 'Conflict',
message: 'Cannot delete a running task. Cancel it first.'
});
return;
}
this.tasks.delete(id);
// Notify task manager if available
if (this.taskManager) {
await this.taskManager.deleteTask(id);
}
res.json({
message: 'Task deleted successfully',
taskId: id
});
} catch (error) {
next(error);
}
}
/**
* POST /tasks/:id/agents - Add agent to task
*/ async handleAddAgentToTask(req, res, next) {
try {
const { id } = req.params;
const { agentId, agentType, status = 'assigned', confidence } = req.body;
if (!agentId || !agentType) {
res.status(400).json({
error: 'Bad Request',
message: 'agentId and agentType are required'
});
return;
}
const task = this.tasks.get(id);
if (!task) {
res.status(404).json({
error: 'Not Found',
message: `Task with ID ${id} not found`
});
return;
}
// Check if agent already exists
const existingAgent = task.agents.find((agent)=>agent.id === agentId);
if (existingAgent) {
res.status(409).json({
error: 'Conflict',
message: 'Agent already assigned to this task'
});
return;
}
const newAgent = {
id: agentId,
type: agentType,
status,
confidence
};
task.agents.push(newAgent);
task.updatedAt = new Date();
this.tasks.set(id, task);
res.status(201).json({
message: 'Agent added to task successfully',
agent: newAgent,
task
});
} catch (error) {
next(error);
}
}
/**
* GET /tasks/:id/agents - Get task agents
*/ async handleGetTaskAgents(req, res, next) {
try {
const { id } = req.params;
const task = this.tasks.get(id);
if (!task) {
res.status(404).json({
error: 'Not Found',
message: `Task with ID ${id} not found`
});
return;
}
res.json({
taskId: id,
agents: task.agents,
totalAgents: task.agents.length
});
} catch (error) {
next(error);
}
}
/**
* POST /tasks/:id/cancel - Cancel task
*/ async handleCancelTask(req, res, next) {
try {
const { id } = req.params;
const task = this.tasks.get(id);
if (!task) {
res.status(404).json({
error: 'Not Found',
message: `Task with ID ${id} not found`
});
return;
}
if (task.status === 'completed') {
res.status(409).json({
error: 'Conflict',
message: 'Cannot cancel a completed task'
});
return;
}
if (task.status === 'cancelled') {
res.status(409).json({
error: 'Conflict',
message: 'Task is already cancelled'
});
return;
}
const updatedTask = {
...task,
status: 'cancelled',
updatedAt: new Date(),
completedAt: new Date()
};
this.tasks.set(id, updatedTask);
// Notify task manager if available
if (this.taskManager) {
await this.taskManager.cancelTask(id);
}
res.json({
message: 'Task cancelled successfully',
task: updatedTask
});
} catch (error) {
next(error);
}
}
}
/**
* Factory function to create task endpoints
*/ export function createTaskEndpoints(taskManager) {
return new TaskEndpoints(taskManager);
}
//# sourceMappingURL=task-endpoints.js.map