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
481 lines • 18.8 kB
JavaScript
/**
* Workflow MCP Tools for CLI
*
* Tool definitions for workflow automation and orchestration.
*/
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
import { join } from 'node:path';
// Storage paths
const STORAGE_DIR = '.claude-flow';
const WORKFLOW_DIR = 'workflows';
const WORKFLOW_FILE = 'store.json';
function getWorkflowDir() {
return join(process.cwd(), STORAGE_DIR, WORKFLOW_DIR);
}
function getWorkflowPath() {
return join(getWorkflowDir(), WORKFLOW_FILE);
}
function ensureWorkflowDir() {
const dir = getWorkflowDir();
if (!existsSync(dir)) {
mkdirSync(dir, { recursive: true });
}
}
function loadWorkflowStore() {
try {
const path = getWorkflowPath();
if (existsSync(path)) {
const data = readFileSync(path, 'utf-8');
return JSON.parse(data);
}
}
catch {
// Return default store on error
}
return { workflows: {}, templates: {}, version: '3.0.0' };
}
function saveWorkflowStore(store) {
ensureWorkflowDir();
writeFileSync(getWorkflowPath(), JSON.stringify(store, null, 2), 'utf-8');
}
export const workflowTools = [
{
name: 'workflow_create',
description: 'Create a new workflow',
category: 'workflow',
inputSchema: {
type: 'object',
properties: {
name: { type: 'string', description: 'Workflow name' },
description: { type: 'string', description: 'Workflow description' },
steps: {
type: 'array',
description: 'Workflow steps',
items: {
type: 'object',
properties: {
name: { type: 'string' },
type: { type: 'string', enum: ['task', 'condition', 'parallel', 'loop', 'wait'] },
config: { type: 'object' },
},
},
},
variables: { type: 'object', description: 'Initial variables' },
},
required: ['name'],
},
handler: async (input) => {
const store = loadWorkflowStore();
const workflowId = `workflow-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
const steps = (input.steps || []).map((s, i) => ({
stepId: `step-${i + 1}`,
name: s.name || `Step ${i + 1}`,
type: s.type || 'task',
config: s.config || {},
status: 'pending',
}));
const workflow = {
workflowId,
name: input.name,
description: input.description,
steps,
status: steps.length > 0 ? 'ready' : 'draft',
currentStep: 0,
variables: input.variables || {},
createdAt: new Date().toISOString(),
};
store.workflows[workflowId] = workflow;
saveWorkflowStore(store);
return {
workflowId,
name: workflow.name,
status: workflow.status,
stepCount: steps.length,
createdAt: workflow.createdAt,
};
},
},
{
name: 'workflow_execute',
description: 'Execute a workflow',
category: 'workflow',
inputSchema: {
type: 'object',
properties: {
workflowId: { type: 'string', description: 'Workflow ID to execute' },
variables: { type: 'object', description: 'Runtime variables to inject' },
startFromStep: { type: 'number', description: 'Step to start from (0-indexed)' },
},
required: ['workflowId'],
},
handler: async (input) => {
const store = loadWorkflowStore();
const workflowId = input.workflowId;
const workflow = store.workflows[workflowId];
if (!workflow) {
return { workflowId, error: 'Workflow not found' };
}
if (workflow.status === 'running') {
return { workflowId, error: 'Workflow already running' };
}
// Inject runtime variables
if (input.variables) {
workflow.variables = { ...workflow.variables, ...input.variables };
}
workflow.status = 'running';
workflow.startedAt = new Date().toISOString();
workflow.currentStep = input.startFromStep || 0;
// Execute steps (in real implementation, this would be async/event-driven)
const results = [];
for (let i = workflow.currentStep; i < workflow.steps.length; i++) {
const step = workflow.steps[i];
step.status = 'running';
step.startedAt = new Date().toISOString();
// For now, mark as completed (real implementation would execute actual tasks)
step.status = 'completed';
step.completedAt = new Date().toISOString();
step.result = { executed: true, stepType: step.type };
results.push({ stepId: step.stepId, status: step.status });
workflow.currentStep = i + 1;
}
workflow.status = 'completed';
workflow.completedAt = new Date().toISOString();
saveWorkflowStore(store);
return {
workflowId,
status: workflow.status,
stepsExecuted: results.length,
results,
startedAt: workflow.startedAt,
completedAt: workflow.completedAt,
duration: new Date(workflow.completedAt).getTime() - new Date(workflow.startedAt).getTime(),
};
},
},
{
name: 'workflow_status',
description: 'Get workflow status',
category: 'workflow',
inputSchema: {
type: 'object',
properties: {
workflowId: { type: 'string', description: 'Workflow ID' },
verbose: { type: 'boolean', description: 'Include step details' },
},
required: ['workflowId'],
},
handler: async (input) => {
const store = loadWorkflowStore();
const workflowId = input.workflowId;
const workflow = store.workflows[workflowId];
if (!workflow) {
return { workflowId, error: 'Workflow not found' };
}
const completedSteps = workflow.steps.filter(s => s.status === 'completed').length;
const progress = workflow.steps.length > 0 ? (completedSteps / workflow.steps.length) * 100 : 0;
const status = {
workflowId: workflow.workflowId,
name: workflow.name,
status: workflow.status,
progress,
currentStep: workflow.currentStep,
totalSteps: workflow.steps.length,
completedSteps,
createdAt: workflow.createdAt,
startedAt: workflow.startedAt,
completedAt: workflow.completedAt,
};
if (input.verbose) {
return {
...status,
description: workflow.description,
variables: workflow.variables,
steps: workflow.steps.map(s => ({
stepId: s.stepId,
name: s.name,
type: s.type,
status: s.status,
startedAt: s.startedAt,
completedAt: s.completedAt,
})),
error: workflow.error,
};
}
return status;
},
},
{
name: 'workflow_list',
description: 'List all workflows',
category: 'workflow',
inputSchema: {
type: 'object',
properties: {
status: { type: 'string', description: 'Filter by status' },
limit: { type: 'number', description: 'Max workflows to return' },
},
},
handler: async (input) => {
const store = loadWorkflowStore();
let workflows = Object.values(store.workflows);
// Apply filters
if (input.status) {
workflows = workflows.filter(w => w.status === input.status);
}
// Sort by creation date (newest first)
workflows.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
// Apply limit
const limit = input.limit || 20;
workflows = workflows.slice(0, limit);
return {
workflows: workflows.map(w => ({
workflowId: w.workflowId,
name: w.name,
status: w.status,
stepCount: w.steps.length,
createdAt: w.createdAt,
completedAt: w.completedAt,
})),
total: workflows.length,
filters: { status: input.status },
};
},
},
{
name: 'workflow_pause',
description: 'Pause a running workflow',
category: 'workflow',
inputSchema: {
type: 'object',
properties: {
workflowId: { type: 'string', description: 'Workflow ID' },
},
required: ['workflowId'],
},
handler: async (input) => {
const store = loadWorkflowStore();
const workflowId = input.workflowId;
const workflow = store.workflows[workflowId];
if (!workflow) {
return { workflowId, error: 'Workflow not found' };
}
if (workflow.status !== 'running') {
return { workflowId, error: 'Workflow not running' };
}
workflow.status = 'paused';
saveWorkflowStore(store);
return {
workflowId,
status: workflow.status,
pausedAt: new Date().toISOString(),
currentStep: workflow.currentStep,
};
},
},
{
name: 'workflow_resume',
description: 'Resume a paused workflow',
category: 'workflow',
inputSchema: {
type: 'object',
properties: {
workflowId: { type: 'string', description: 'Workflow ID' },
},
required: ['workflowId'],
},
handler: async (input) => {
const store = loadWorkflowStore();
const workflowId = input.workflowId;
const workflow = store.workflows[workflowId];
if (!workflow) {
return { workflowId, error: 'Workflow not found' };
}
if (workflow.status !== 'paused') {
return { workflowId, error: 'Workflow not paused' };
}
workflow.status = 'running';
saveWorkflowStore(store);
// Continue execution from current step
const results = [];
for (let i = workflow.currentStep; i < workflow.steps.length; i++) {
const step = workflow.steps[i];
step.status = 'running';
step.startedAt = new Date().toISOString();
step.status = 'completed';
step.completedAt = new Date().toISOString();
step.result = { executed: true };
results.push({ stepId: step.stepId, status: step.status });
workflow.currentStep = i + 1;
}
workflow.status = 'completed';
workflow.completedAt = new Date().toISOString();
saveWorkflowStore(store);
return {
workflowId,
status: workflow.status,
resumed: true,
stepsExecuted: results.length,
completedAt: workflow.completedAt,
};
},
},
{
name: 'workflow_cancel',
description: 'Cancel a workflow',
category: 'workflow',
inputSchema: {
type: 'object',
properties: {
workflowId: { type: 'string', description: 'Workflow ID' },
reason: { type: 'string', description: 'Cancellation reason' },
},
required: ['workflowId'],
},
handler: async (input) => {
const store = loadWorkflowStore();
const workflowId = input.workflowId;
const workflow = store.workflows[workflowId];
if (!workflow) {
return { workflowId, error: 'Workflow not found' };
}
if (workflow.status === 'completed' || workflow.status === 'failed') {
return { workflowId, error: 'Workflow already finished' };
}
workflow.status = 'failed';
workflow.error = input.reason || 'Cancelled by user';
workflow.completedAt = new Date().toISOString();
// Mark remaining steps as skipped
for (let i = workflow.currentStep; i < workflow.steps.length; i++) {
workflow.steps[i].status = 'skipped';
}
saveWorkflowStore(store);
return {
workflowId,
status: workflow.status,
cancelledAt: workflow.completedAt,
reason: workflow.error,
skippedSteps: workflow.steps.length - workflow.currentStep,
};
},
},
{
name: 'workflow_delete',
description: 'Delete a workflow',
category: 'workflow',
inputSchema: {
type: 'object',
properties: {
workflowId: { type: 'string', description: 'Workflow ID' },
},
required: ['workflowId'],
},
handler: async (input) => {
const store = loadWorkflowStore();
const workflowId = input.workflowId;
if (!store.workflows[workflowId]) {
return { workflowId, error: 'Workflow not found' };
}
const workflow = store.workflows[workflowId];
if (workflow.status === 'running') {
return { workflowId, error: 'Cannot delete running workflow' };
}
delete store.workflows[workflowId];
saveWorkflowStore(store);
return {
workflowId,
deleted: true,
deletedAt: new Date().toISOString(),
};
},
},
{
name: 'workflow_template',
description: 'Save workflow as template or create from template',
category: 'workflow',
inputSchema: {
type: 'object',
properties: {
action: { type: 'string', enum: ['save', 'create', 'list'], description: 'Template action' },
workflowId: { type: 'string', description: 'Workflow ID (for save)' },
templateId: { type: 'string', description: 'Template ID (for create)' },
templateName: { type: 'string', description: 'Template name (for save)' },
newName: { type: 'string', description: 'New workflow name (for create)' },
},
required: ['action'],
},
handler: async (input) => {
const store = loadWorkflowStore();
const action = input.action;
if (action === 'save') {
const workflow = store.workflows[input.workflowId];
if (!workflow) {
return { action, error: 'Workflow not found' };
}
const templateId = `template-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
const template = {
...workflow,
workflowId: templateId,
name: input.templateName || `${workflow.name} Template`,
status: 'draft',
currentStep: 0,
createdAt: new Date().toISOString(),
startedAt: undefined,
completedAt: undefined,
};
// Reset step statuses
template.steps = template.steps.map(s => ({
...s,
status: 'pending',
result: undefined,
startedAt: undefined,
completedAt: undefined,
}));
store.templates[templateId] = template;
saveWorkflowStore(store);
return {
action,
templateId,
name: template.name,
savedAt: new Date().toISOString(),
};
}
if (action === 'create') {
const template = store.templates[input.templateId];
if (!template) {
return { action, error: 'Template not found' };
}
const workflowId = `workflow-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
const workflow = {
...template,
workflowId,
name: input.newName || template.name.replace(' Template', ''),
status: 'ready',
createdAt: new Date().toISOString(),
};
store.workflows[workflowId] = workflow;
saveWorkflowStore(store);
return {
action,
workflowId,
name: workflow.name,
fromTemplate: input.templateId,
createdAt: workflow.createdAt,
};
}
if (action === 'list') {
return {
action,
templates: Object.values(store.templates).map(t => ({
templateId: t.workflowId,
name: t.name,
stepCount: t.steps.length,
createdAt: t.createdAt,
})),
total: Object.keys(store.templates).length,
};
}
return { action, error: 'Unknown action' };
},
},
];
//# sourceMappingURL=workflow-tools.js.map