claude-flow 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +612 -0
- package/bin/claude-flow +0 -0
- package/bin/claude-flow-simple +0 -0
- package/bin/claude-flow-typecheck +0 -0
- package/deno.json +84 -0
- package/package.json +45 -0
- package/scripts/check-links.ts +274 -0
- package/scripts/check-performance-regression.ts +168 -0
- package/scripts/claude-sparc.sh +562 -0
- package/scripts/coverage-report.ts +692 -0
- package/scripts/demo-task-system.ts +224 -0
- package/scripts/install.js +72 -0
- package/scripts/test-batch-tasks.ts +29 -0
- package/scripts/test-coordination-features.ts +238 -0
- package/scripts/test-mcp.ts +251 -0
- package/scripts/test-runner.ts +571 -0
- package/scripts/validate-examples.ts +288 -0
- package/src/cli/cli-core.ts +273 -0
- package/src/cli/commands/agent.ts +83 -0
- package/src/cli/commands/config.ts +442 -0
- package/src/cli/commands/help.ts +765 -0
- package/src/cli/commands/index.ts +963 -0
- package/src/cli/commands/mcp.ts +191 -0
- package/src/cli/commands/memory.ts +74 -0
- package/src/cli/commands/monitor.ts +403 -0
- package/src/cli/commands/session.ts +595 -0
- package/src/cli/commands/start.ts +156 -0
- package/src/cli/commands/status.ts +345 -0
- package/src/cli/commands/task.ts +79 -0
- package/src/cli/commands/workflow.ts +763 -0
- package/src/cli/completion.ts +553 -0
- package/src/cli/formatter.ts +310 -0
- package/src/cli/index.ts +211 -0
- package/src/cli/main.ts +23 -0
- package/src/cli/repl.ts +1050 -0
- package/src/cli/simple-cli.js +211 -0
- package/src/cli/simple-cli.ts +211 -0
- package/src/coordination/README.md +400 -0
- package/src/coordination/advanced-scheduler.ts +487 -0
- package/src/coordination/circuit-breaker.ts +366 -0
- package/src/coordination/conflict-resolution.ts +490 -0
- package/src/coordination/dependency-graph.ts +475 -0
- package/src/coordination/index.ts +63 -0
- package/src/coordination/manager.ts +460 -0
- package/src/coordination/messaging.ts +290 -0
- package/src/coordination/metrics.ts +585 -0
- package/src/coordination/resources.ts +322 -0
- package/src/coordination/scheduler.ts +390 -0
- package/src/coordination/work-stealing.ts +224 -0
- package/src/core/config.ts +627 -0
- package/src/core/event-bus.ts +186 -0
- package/src/core/json-persistence.ts +183 -0
- package/src/core/logger.ts +262 -0
- package/src/core/orchestrator-fixed.ts +312 -0
- package/src/core/orchestrator.ts +1234 -0
- package/src/core/persistence.ts +276 -0
- package/src/mcp/auth.ts +438 -0
- package/src/mcp/claude-flow-tools.ts +1280 -0
- package/src/mcp/load-balancer.ts +510 -0
- package/src/mcp/router.ts +240 -0
- package/src/mcp/server.ts +548 -0
- package/src/mcp/session-manager.ts +418 -0
- package/src/mcp/tools.ts +180 -0
- package/src/mcp/transports/base.ts +21 -0
- package/src/mcp/transports/http.ts +457 -0
- package/src/mcp/transports/stdio.ts +254 -0
- package/src/memory/backends/base.ts +22 -0
- package/src/memory/backends/markdown.ts +283 -0
- package/src/memory/backends/sqlite.ts +329 -0
- package/src/memory/cache.ts +238 -0
- package/src/memory/indexer.ts +238 -0
- package/src/memory/manager.ts +572 -0
- package/src/terminal/adapters/base.ts +29 -0
- package/src/terminal/adapters/native.ts +504 -0
- package/src/terminal/adapters/vscode.ts +340 -0
- package/src/terminal/manager.ts +308 -0
- package/src/terminal/pool.ts +271 -0
- package/src/terminal/session.ts +250 -0
- package/src/terminal/vscode-bridge.ts +242 -0
- package/src/utils/errors.ts +231 -0
- package/src/utils/helpers.ts +476 -0
- package/src/utils/types.ts +493 -0
|
@@ -0,0 +1,763 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow execution commands for Claude-Flow
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Command } from '@cliffy/command';
|
|
6
|
+
import { colors } from '@cliffy/ansi/colors';
|
|
7
|
+
import { Table } from '@cliffy/table';
|
|
8
|
+
import { Confirm, Input } from '@cliffy/prompt';
|
|
9
|
+
import { formatDuration, formatStatusIndicator, formatProgressBar } from '../formatter.ts';
|
|
10
|
+
import { generateId } from '../../utils/helpers.ts';
|
|
11
|
+
|
|
12
|
+
export const workflowCommand = new Command()
|
|
13
|
+
.description('Execute and manage workflows')
|
|
14
|
+
.action(() => {
|
|
15
|
+
workflowCommand.showHelp();
|
|
16
|
+
})
|
|
17
|
+
.command('run', new Command()
|
|
18
|
+
.description('Execute a workflow from file')
|
|
19
|
+
.arguments('<workflow-file:string>')
|
|
20
|
+
.option('-d, --dry-run', 'Validate workflow without executing')
|
|
21
|
+
.option('-v, --variables <vars:string>', 'Override variables (JSON format)')
|
|
22
|
+
.option('-w, --watch', 'Watch workflow execution progress')
|
|
23
|
+
.option('--parallel', 'Allow parallel execution where possible')
|
|
24
|
+
.option('--fail-fast', 'Stop on first task failure')
|
|
25
|
+
.action(async (options: any, workflowFile: string) => {
|
|
26
|
+
await runWorkflow(workflowFile, options);
|
|
27
|
+
}),
|
|
28
|
+
)
|
|
29
|
+
.command('validate', new Command()
|
|
30
|
+
.description('Validate a workflow file')
|
|
31
|
+
.arguments('<workflow-file:string>')
|
|
32
|
+
.option('--strict', 'Use strict validation mode')
|
|
33
|
+
.action(async (options: any, workflowFile: string) => {
|
|
34
|
+
await validateWorkflow(workflowFile, options);
|
|
35
|
+
}),
|
|
36
|
+
)
|
|
37
|
+
.command('list', new Command()
|
|
38
|
+
.description('List running workflows')
|
|
39
|
+
.option('--all', 'Include completed workflows')
|
|
40
|
+
.option('--format <format:string>', 'Output format (table, json)', { default: 'table' })
|
|
41
|
+
.action(async (options: any) => {
|
|
42
|
+
await listWorkflows(options);
|
|
43
|
+
}),
|
|
44
|
+
)
|
|
45
|
+
.command('status', new Command()
|
|
46
|
+
.description('Show workflow execution status')
|
|
47
|
+
.arguments('<workflow-id:string>')
|
|
48
|
+
.option('-w, --watch', 'Watch workflow progress')
|
|
49
|
+
.action(async (options: any, workflowId: string) => {
|
|
50
|
+
await showWorkflowStatus(workflowId, options);
|
|
51
|
+
}),
|
|
52
|
+
)
|
|
53
|
+
.command('stop', new Command()
|
|
54
|
+
.description('Stop a running workflow')
|
|
55
|
+
.arguments('<workflow-id:string>')
|
|
56
|
+
.option('-f, --force', 'Force stop without cleanup')
|
|
57
|
+
.action(async (options: any, workflowId: string) => {
|
|
58
|
+
await stopWorkflow(workflowId, options);
|
|
59
|
+
}),
|
|
60
|
+
)
|
|
61
|
+
.command('template', new Command()
|
|
62
|
+
.description('Generate workflow templates')
|
|
63
|
+
.arguments('<template-type:string>')
|
|
64
|
+
.option('-o, --output <file:string>', 'Output file path')
|
|
65
|
+
.option('--format <format:string>', 'Template format (json, yaml)', { default: 'json' })
|
|
66
|
+
.action(async (options: any, templateType: string) => {
|
|
67
|
+
await generateTemplate(templateType, options);
|
|
68
|
+
}),
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
interface WorkflowDefinition {
|
|
72
|
+
name: string;
|
|
73
|
+
version?: string;
|
|
74
|
+
description?: string;
|
|
75
|
+
variables?: Record<string, any>;
|
|
76
|
+
agents?: AgentDefinition[];
|
|
77
|
+
tasks: TaskDefinition[];
|
|
78
|
+
dependencies?: Record<string, string[]>;
|
|
79
|
+
settings?: WorkflowSettings;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
interface AgentDefinition {
|
|
83
|
+
id: string;
|
|
84
|
+
type: string;
|
|
85
|
+
name?: string;
|
|
86
|
+
config?: Record<string, any>;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
interface TaskDefinition {
|
|
90
|
+
id: string;
|
|
91
|
+
name?: string;
|
|
92
|
+
type: string;
|
|
93
|
+
description: string;
|
|
94
|
+
assignTo?: string;
|
|
95
|
+
depends?: string[];
|
|
96
|
+
input?: Record<string, any>;
|
|
97
|
+
timeout?: number;
|
|
98
|
+
retries?: number;
|
|
99
|
+
condition?: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
interface WorkflowSettings {
|
|
103
|
+
maxConcurrency?: number;
|
|
104
|
+
timeout?: number;
|
|
105
|
+
retryPolicy?: 'none' | 'immediate' | 'exponential';
|
|
106
|
+
failurePolicy?: 'fail-fast' | 'continue' | 'ignore';
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
interface WorkflowExecution {
|
|
110
|
+
id: string;
|
|
111
|
+
workflowName: string;
|
|
112
|
+
status: 'pending' | 'running' | 'completed' | 'failed' | 'stopped';
|
|
113
|
+
startedAt: Date;
|
|
114
|
+
completedAt?: Date;
|
|
115
|
+
progress: {
|
|
116
|
+
total: number;
|
|
117
|
+
completed: number;
|
|
118
|
+
failed: number;
|
|
119
|
+
};
|
|
120
|
+
tasks: TaskExecution[];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
interface TaskExecution {
|
|
124
|
+
id: string;
|
|
125
|
+
taskId: string;
|
|
126
|
+
status: 'pending' | 'running' | 'completed' | 'failed' | 'skipped';
|
|
127
|
+
startedAt?: Date;
|
|
128
|
+
completedAt?: Date;
|
|
129
|
+
assignedAgent?: string;
|
|
130
|
+
error?: string;
|
|
131
|
+
output?: any;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function runWorkflow(workflowFile: string, options: any): Promise<void> {
|
|
135
|
+
try {
|
|
136
|
+
// Load and validate workflow
|
|
137
|
+
const workflow = await loadWorkflow(workflowFile);
|
|
138
|
+
|
|
139
|
+
if (options.dryRun) {
|
|
140
|
+
await validateWorkflowDefinition(workflow, true);
|
|
141
|
+
console.log(colors.green('✓ Workflow validation passed'));
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Override variables if provided
|
|
146
|
+
if (options.variables) {
|
|
147
|
+
try {
|
|
148
|
+
const vars = JSON.parse(options.variables);
|
|
149
|
+
workflow.variables = { ...workflow.variables, ...vars };
|
|
150
|
+
} catch (error) {
|
|
151
|
+
throw new Error(`Invalid variables JSON: ${(error as Error).message}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Create execution plan
|
|
156
|
+
const execution = await createExecution(workflow);
|
|
157
|
+
|
|
158
|
+
console.log(colors.cyan.bold('Starting workflow execution'));
|
|
159
|
+
console.log(`${colors.white('Workflow:')} ${workflow.name}`);
|
|
160
|
+
console.log(`${colors.white('ID:')} ${execution.id}`);
|
|
161
|
+
console.log(`${colors.white('Tasks:')} ${execution.tasks.length}`);
|
|
162
|
+
console.log();
|
|
163
|
+
|
|
164
|
+
// Execute workflow
|
|
165
|
+
if (options.watch) {
|
|
166
|
+
await executeWorkflowWithWatch(execution, workflow, options);
|
|
167
|
+
} else {
|
|
168
|
+
await executeWorkflow(execution, workflow, options);
|
|
169
|
+
}
|
|
170
|
+
} catch (error) {
|
|
171
|
+
console.error(colors.red('Workflow execution failed:'), (error as Error).message);
|
|
172
|
+
Deno.exit(1);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function validateWorkflow(workflowFile: string, options: any): Promise<void> {
|
|
177
|
+
try {
|
|
178
|
+
const workflow = await loadWorkflow(workflowFile);
|
|
179
|
+
await validateWorkflowDefinition(workflow, options.strict);
|
|
180
|
+
|
|
181
|
+
console.log(colors.green('✓ Workflow validation passed'));
|
|
182
|
+
console.log(`${colors.white('Name:')} ${workflow.name}`);
|
|
183
|
+
console.log(`${colors.white('Tasks:')} ${workflow.tasks.length}`);
|
|
184
|
+
console.log(`${colors.white('Agents:')} ${workflow.agents?.length || 0}`);
|
|
185
|
+
|
|
186
|
+
if (workflow.dependencies) {
|
|
187
|
+
const depCount = Object.values(workflow.dependencies).flat().length;
|
|
188
|
+
console.log(`${colors.white('Dependencies:')} ${depCount}`);
|
|
189
|
+
}
|
|
190
|
+
} catch (error) {
|
|
191
|
+
console.error(colors.red('✗ Workflow validation failed:'), (error as Error).message);
|
|
192
|
+
Deno.exit(1);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async function listWorkflows(options: any): Promise<void> {
|
|
197
|
+
try {
|
|
198
|
+
// Mock workflow list - in production, this would query the orchestrator
|
|
199
|
+
const workflows = await getRunningWorkflows(options.all);
|
|
200
|
+
|
|
201
|
+
if (options.format === 'json') {
|
|
202
|
+
console.log(JSON.stringify(workflows, null, 2));
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (workflows.length === 0) {
|
|
207
|
+
console.log(colors.gray('No workflows found'));
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
console.log(colors.cyan.bold(`Workflows (${workflows.length})`));
|
|
212
|
+
console.log('─'.repeat(60));
|
|
213
|
+
|
|
214
|
+
const table = new Table()
|
|
215
|
+
.header(['ID', 'Name', 'Status', 'Progress', 'Started', 'Duration'])
|
|
216
|
+
.border(true);
|
|
217
|
+
|
|
218
|
+
for (const workflow of workflows) {
|
|
219
|
+
const statusIcon = formatStatusIndicator(workflow.status);
|
|
220
|
+
const progress = `${workflow.progress.completed}/${workflow.progress.total}`;
|
|
221
|
+
const progressBar = formatProgressBar(
|
|
222
|
+
workflow.progress.completed,
|
|
223
|
+
workflow.progress.total,
|
|
224
|
+
10
|
|
225
|
+
);
|
|
226
|
+
const duration = workflow.completedAt
|
|
227
|
+
? formatDuration(workflow.completedAt.getTime() - workflow.startedAt.getTime())
|
|
228
|
+
: formatDuration(Date.now() - workflow.startedAt.getTime());
|
|
229
|
+
|
|
230
|
+
table.push([
|
|
231
|
+
colors.gray(workflow.id.substring(0, 8) + '...'),
|
|
232
|
+
colors.white(workflow.workflowName),
|
|
233
|
+
`${statusIcon} ${workflow.status}`,
|
|
234
|
+
`${progressBar} ${progress}`,
|
|
235
|
+
workflow.startedAt.toLocaleTimeString(),
|
|
236
|
+
duration
|
|
237
|
+
]);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
table.render();
|
|
241
|
+
} catch (error) {
|
|
242
|
+
console.error(colors.red('Failed to list workflows:'), (error as Error).message);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function showWorkflowStatus(workflowId: string, options: any): Promise<void> {
|
|
247
|
+
try {
|
|
248
|
+
if (options.watch) {
|
|
249
|
+
await watchWorkflowStatus(workflowId);
|
|
250
|
+
} else {
|
|
251
|
+
const execution = await getWorkflowExecution(workflowId);
|
|
252
|
+
displayWorkflowStatus(execution);
|
|
253
|
+
}
|
|
254
|
+
} catch (error) {
|
|
255
|
+
console.error(colors.red('Failed to get workflow status:'), (error as Error).message);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async function stopWorkflow(workflowId: string, options: any): Promise<void> {
|
|
260
|
+
try {
|
|
261
|
+
const execution = await getWorkflowExecution(workflowId);
|
|
262
|
+
|
|
263
|
+
if (execution.status !== 'running') {
|
|
264
|
+
console.log(colors.yellow(`Workflow is not running (status: ${execution.status})`));
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (!options.force) {
|
|
269
|
+
const confirmed = await Confirm.prompt({
|
|
270
|
+
message: `Stop workflow "${execution.workflowName}"?`,
|
|
271
|
+
default: false,
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
if (!confirmed) {
|
|
275
|
+
console.log(colors.gray('Stop cancelled'));
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
console.log(colors.yellow('Stopping workflow...'));
|
|
281
|
+
|
|
282
|
+
// Mock stopping - in production, this would call the orchestrator
|
|
283
|
+
if (options.force) {
|
|
284
|
+
console.log(colors.red('• Force stopping all tasks'));
|
|
285
|
+
} else {
|
|
286
|
+
console.log(colors.blue('• Gracefully stopping tasks'));
|
|
287
|
+
console.log(colors.blue('• Cleaning up resources'));
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
console.log(colors.green('✓ Workflow stopped'));
|
|
291
|
+
} catch (error) {
|
|
292
|
+
console.error(colors.red('Failed to stop workflow:'), (error as Error).message);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async function generateTemplate(templateType: string, options: any): Promise<void> {
|
|
297
|
+
const templates: Record<string, WorkflowDefinition> = {
|
|
298
|
+
'research': {
|
|
299
|
+
name: 'Research Workflow',
|
|
300
|
+
description: 'Multi-stage research and analysis workflow',
|
|
301
|
+
variables: {
|
|
302
|
+
'topic': 'quantum computing',
|
|
303
|
+
'depth': 'comprehensive'
|
|
304
|
+
},
|
|
305
|
+
agents: [
|
|
306
|
+
{ id: 'researcher', type: 'researcher', name: 'Research Agent' },
|
|
307
|
+
{ id: 'analyzer', type: 'analyst', name: 'Analysis Agent' }
|
|
308
|
+
],
|
|
309
|
+
tasks: [
|
|
310
|
+
{
|
|
311
|
+
id: 'research-task',
|
|
312
|
+
type: 'research',
|
|
313
|
+
description: 'Research the given topic',
|
|
314
|
+
assignTo: 'researcher',
|
|
315
|
+
input: { topic: '${topic}', depth: '${depth}' }
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
id: 'analyze-task',
|
|
319
|
+
type: 'analysis',
|
|
320
|
+
description: 'Analyze research findings',
|
|
321
|
+
assignTo: 'analyzer',
|
|
322
|
+
depends: ['research-task'],
|
|
323
|
+
input: { data: '${research-task.output}' }
|
|
324
|
+
}
|
|
325
|
+
],
|
|
326
|
+
settings: {
|
|
327
|
+
maxConcurrency: 2,
|
|
328
|
+
timeout: 300000,
|
|
329
|
+
failurePolicy: 'fail-fast'
|
|
330
|
+
}
|
|
331
|
+
},
|
|
332
|
+
'implementation': {
|
|
333
|
+
name: 'Implementation Workflow',
|
|
334
|
+
description: 'Code implementation and testing workflow',
|
|
335
|
+
agents: [
|
|
336
|
+
{ id: 'implementer', type: 'implementer', name: 'Implementation Agent' },
|
|
337
|
+
{ id: 'tester', type: 'implementer', name: 'Testing Agent' }
|
|
338
|
+
],
|
|
339
|
+
tasks: [
|
|
340
|
+
{
|
|
341
|
+
id: 'implement',
|
|
342
|
+
type: 'implementation',
|
|
343
|
+
description: 'Implement the solution',
|
|
344
|
+
assignTo: 'implementer'
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
id: 'test',
|
|
348
|
+
type: 'testing',
|
|
349
|
+
description: 'Test the implementation',
|
|
350
|
+
assignTo: 'tester',
|
|
351
|
+
depends: ['implement']
|
|
352
|
+
}
|
|
353
|
+
]
|
|
354
|
+
},
|
|
355
|
+
'coordination': {
|
|
356
|
+
name: 'Multi-Agent Coordination',
|
|
357
|
+
description: 'Complex multi-agent coordination workflow',
|
|
358
|
+
agents: [
|
|
359
|
+
{ id: 'coordinator', type: 'coordinator', name: 'Coordinator Agent' },
|
|
360
|
+
{ id: 'worker1', type: 'implementer', name: 'Worker Agent 1' },
|
|
361
|
+
{ id: 'worker2', type: 'implementer', name: 'Worker Agent 2' }
|
|
362
|
+
],
|
|
363
|
+
tasks: [
|
|
364
|
+
{
|
|
365
|
+
id: 'plan',
|
|
366
|
+
type: 'planning',
|
|
367
|
+
description: 'Create execution plan',
|
|
368
|
+
assignTo: 'coordinator'
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
id: 'work1',
|
|
372
|
+
type: 'implementation',
|
|
373
|
+
description: 'Execute part 1',
|
|
374
|
+
assignTo: 'worker1',
|
|
375
|
+
depends: ['plan']
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
id: 'work2',
|
|
379
|
+
type: 'implementation',
|
|
380
|
+
description: 'Execute part 2',
|
|
381
|
+
assignTo: 'worker2',
|
|
382
|
+
depends: ['plan']
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
id: 'integrate',
|
|
386
|
+
type: 'integration',
|
|
387
|
+
description: 'Integrate results',
|
|
388
|
+
assignTo: 'coordinator',
|
|
389
|
+
depends: ['work1', 'work2']
|
|
390
|
+
}
|
|
391
|
+
],
|
|
392
|
+
settings: {
|
|
393
|
+
maxConcurrency: 3,
|
|
394
|
+
failurePolicy: 'continue'
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
const template = templates[templateType];
|
|
400
|
+
if (!template) {
|
|
401
|
+
console.error(colors.red(`Unknown template type: ${templateType}`));
|
|
402
|
+
console.log(colors.gray('Available templates:'), Object.keys(templates).join(', '));
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const outputFile = options.output || `${templateType}-workflow.${options.format}`;
|
|
407
|
+
|
|
408
|
+
let content: string;
|
|
409
|
+
if (options.format === 'yaml') {
|
|
410
|
+
// In production, use a proper YAML library
|
|
411
|
+
console.log(colors.yellow('YAML format not implemented, using JSON'));
|
|
412
|
+
content = JSON.stringify(template, null, 2);
|
|
413
|
+
} else {
|
|
414
|
+
content = JSON.stringify(template, null, 2);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
await Deno.writeTextFile(outputFile, content);
|
|
418
|
+
|
|
419
|
+
console.log(colors.green('✓ Workflow template generated'));
|
|
420
|
+
console.log(`${colors.white('Template:')} ${templateType}`);
|
|
421
|
+
console.log(`${colors.white('File:')} ${outputFile}`);
|
|
422
|
+
console.log(`${colors.white('Tasks:')} ${template.tasks.length}`);
|
|
423
|
+
console.log(`${colors.white('Agents:')} ${template.agents?.length || 0}`);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
async function loadWorkflow(workflowFile: string): Promise<WorkflowDefinition> {
|
|
427
|
+
try {
|
|
428
|
+
const content = await Deno.readTextFile(workflowFile);
|
|
429
|
+
|
|
430
|
+
if (workflowFile.endsWith('.yaml') || workflowFile.endsWith('.yml')) {
|
|
431
|
+
// In production, use a proper YAML parser
|
|
432
|
+
throw new Error('YAML workflows not yet supported');
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
return JSON.parse(content) as WorkflowDefinition;
|
|
436
|
+
} catch (error) {
|
|
437
|
+
throw new Error(`Failed to load workflow file: ${(error as Error).message}`);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
async function validateWorkflowDefinition(workflow: WorkflowDefinition, strict = false): Promise<void> {
|
|
442
|
+
const errors: string[] = [];
|
|
443
|
+
|
|
444
|
+
// Basic validation
|
|
445
|
+
if (!workflow.name) errors.push('Workflow name is required');
|
|
446
|
+
if (!workflow.tasks || workflow.tasks.length === 0) errors.push('At least one task is required');
|
|
447
|
+
|
|
448
|
+
// Task validation
|
|
449
|
+
const taskIds = new Set<string>();
|
|
450
|
+
for (const task of workflow.tasks || []) {
|
|
451
|
+
if (!task.id) errors.push('Task ID is required');
|
|
452
|
+
if (taskIds.has(task.id)) errors.push(`Duplicate task ID: ${task.id}`);
|
|
453
|
+
taskIds.add(task.id);
|
|
454
|
+
|
|
455
|
+
if (!task.type) errors.push(`Task ${task.id}: type is required`);
|
|
456
|
+
if (!task.description) errors.push(`Task ${task.id}: description is required`);
|
|
457
|
+
|
|
458
|
+
// Validate dependencies
|
|
459
|
+
if (task.depends) {
|
|
460
|
+
for (const dep of task.depends) {
|
|
461
|
+
if (!taskIds.has(dep)) {
|
|
462
|
+
// Check if dependency exists in previous tasks
|
|
463
|
+
const taskIndex = workflow.tasks.indexOf(task);
|
|
464
|
+
const depExists = workflow.tasks.slice(0, taskIndex).some(t => t.id === dep);
|
|
465
|
+
if (!depExists) {
|
|
466
|
+
errors.push(`Task ${task.id}: unknown dependency ${dep}`);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Agent validation
|
|
474
|
+
if (workflow.agents) {
|
|
475
|
+
const agentIds = new Set<string>();
|
|
476
|
+
for (const agent of workflow.agents) {
|
|
477
|
+
if (!agent.id) errors.push('Agent ID is required');
|
|
478
|
+
if (agentIds.has(agent.id)) errors.push(`Duplicate agent ID: ${agent.id}`);
|
|
479
|
+
agentIds.add(agent.id);
|
|
480
|
+
|
|
481
|
+
if (!agent.type) errors.push(`Agent ${agent.id}: type is required`);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Validate task assignments
|
|
485
|
+
for (const task of workflow.tasks) {
|
|
486
|
+
if (task.assignTo && !agentIds.has(task.assignTo)) {
|
|
487
|
+
errors.push(`Task ${task.id}: assigned to unknown agent ${task.assignTo}`);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Strict validation
|
|
493
|
+
if (strict) {
|
|
494
|
+
// Check for circular dependencies
|
|
495
|
+
const graph = new Map<string, string[]>();
|
|
496
|
+
for (const task of workflow.tasks) {
|
|
497
|
+
graph.set(task.id, task.depends || []);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
if (hasCircularDependencies(graph)) {
|
|
501
|
+
errors.push('Circular dependencies detected');
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (errors.length > 0) {
|
|
506
|
+
throw new Error('Workflow validation failed:\n• ' + errors.join('\n• '));
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
async function createExecution(workflow: WorkflowDefinition): Promise<WorkflowExecution> {
|
|
511
|
+
const tasks: TaskExecution[] = workflow.tasks.map(task => ({
|
|
512
|
+
id: generateId('task-exec'),
|
|
513
|
+
taskId: task.id,
|
|
514
|
+
status: 'pending'
|
|
515
|
+
}));
|
|
516
|
+
|
|
517
|
+
return {
|
|
518
|
+
id: generateId('workflow-exec'),
|
|
519
|
+
workflowName: workflow.name,
|
|
520
|
+
status: 'pending',
|
|
521
|
+
startedAt: new Date(),
|
|
522
|
+
progress: {
|
|
523
|
+
total: tasks.length,
|
|
524
|
+
completed: 0,
|
|
525
|
+
failed: 0
|
|
526
|
+
},
|
|
527
|
+
tasks
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
async function executeWorkflow(execution: WorkflowExecution, workflow: WorkflowDefinition, options: any): Promise<void> {
|
|
532
|
+
execution.status = 'running';
|
|
533
|
+
|
|
534
|
+
console.log(colors.blue('Executing workflow...'));
|
|
535
|
+
console.log();
|
|
536
|
+
|
|
537
|
+
// Mock execution - in production, this would use the orchestrator
|
|
538
|
+
for (let i = 0; i < execution.tasks.length; i++) {
|
|
539
|
+
const taskExec = execution.tasks[i];
|
|
540
|
+
const taskDef = workflow.tasks.find(t => t.id === taskExec.taskId)!;
|
|
541
|
+
|
|
542
|
+
console.log(`${colors.cyan('→')} Starting task: ${taskDef.description}`);
|
|
543
|
+
|
|
544
|
+
taskExec.status = 'running';
|
|
545
|
+
taskExec.startedAt = new Date();
|
|
546
|
+
|
|
547
|
+
// Simulate task execution
|
|
548
|
+
await new Promise(resolve => setTimeout(resolve, 1000 + Math.random() * 2000));
|
|
549
|
+
|
|
550
|
+
// Random success/failure for demo
|
|
551
|
+
const success = Math.random() > 0.1; // 90% success rate
|
|
552
|
+
|
|
553
|
+
if (success) {
|
|
554
|
+
taskExec.status = 'completed';
|
|
555
|
+
taskExec.completedAt = new Date();
|
|
556
|
+
execution.progress.completed++;
|
|
557
|
+
console.log(`${colors.green('✓')} Completed: ${taskDef.description}`);
|
|
558
|
+
} else {
|
|
559
|
+
taskExec.status = 'failed';
|
|
560
|
+
taskExec.completedAt = new Date();
|
|
561
|
+
taskExec.error = 'Simulated task failure';
|
|
562
|
+
execution.progress.failed++;
|
|
563
|
+
console.log(`${colors.red('✗')} Failed: ${taskDef.description}`);
|
|
564
|
+
|
|
565
|
+
if (options.failFast || workflow.settings?.failurePolicy === 'fail-fast') {
|
|
566
|
+
execution.status = 'failed';
|
|
567
|
+
console.log(colors.red('\nWorkflow failed (fail-fast mode)'));
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
console.log();
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
execution.status = execution.progress.failed > 0 ? 'failed' : 'completed';
|
|
576
|
+
execution.completedAt = new Date();
|
|
577
|
+
|
|
578
|
+
const duration = formatDuration(execution.completedAt.getTime() - execution.startedAt.getTime());
|
|
579
|
+
|
|
580
|
+
if (execution.status === 'completed') {
|
|
581
|
+
console.log(colors.green.bold('✓ Workflow completed successfully'));
|
|
582
|
+
} else {
|
|
583
|
+
console.log(colors.red.bold('✗ Workflow completed with failures'));
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
console.log(`${colors.white('Duration:')} ${duration}`);
|
|
587
|
+
console.log(`${colors.white('Tasks:')} ${execution.progress.completed}/${execution.progress.total} completed`);
|
|
588
|
+
|
|
589
|
+
if (execution.progress.failed > 0) {
|
|
590
|
+
console.log(`${colors.white('Failed:')} ${execution.progress.failed}`);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
async function executeWorkflowWithWatch(execution: WorkflowExecution, workflow: WorkflowDefinition, options: any): Promise<void> {
|
|
595
|
+
console.log(colors.yellow('Starting workflow execution in watch mode...'));
|
|
596
|
+
console.log(colors.gray('Press Ctrl+C to stop\n'));
|
|
597
|
+
|
|
598
|
+
// Start execution in background and watch progress
|
|
599
|
+
const executionPromise = executeWorkflow(execution, workflow, options);
|
|
600
|
+
|
|
601
|
+
// Watch loop
|
|
602
|
+
const watchInterval = setInterval(() => {
|
|
603
|
+
displayWorkflowProgress(execution);
|
|
604
|
+
}, 1000);
|
|
605
|
+
|
|
606
|
+
try {
|
|
607
|
+
await executionPromise;
|
|
608
|
+
} finally {
|
|
609
|
+
clearInterval(watchInterval);
|
|
610
|
+
displayWorkflowProgress(execution);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
async function watchWorkflowStatus(workflowId: string): Promise<void> {
|
|
615
|
+
console.log(colors.cyan('Watching workflow status...'));
|
|
616
|
+
console.log(colors.gray('Press Ctrl+C to stop\n'));
|
|
617
|
+
|
|
618
|
+
// eslint-disable-next-line no-constant-condition
|
|
619
|
+
while (true) {
|
|
620
|
+
try {
|
|
621
|
+
console.clear();
|
|
622
|
+
const execution = await getWorkflowExecution(workflowId);
|
|
623
|
+
displayWorkflowStatus(execution);
|
|
624
|
+
|
|
625
|
+
if (execution.status === 'completed' || execution.status === 'failed' || execution.status === 'stopped') {
|
|
626
|
+
console.log('\n' + colors.gray('Workflow finished. Exiting watch mode.'));
|
|
627
|
+
break;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
631
|
+
} catch (error) {
|
|
632
|
+
console.error(colors.red('Error watching workflow:'), (error as Error).message);
|
|
633
|
+
break;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
function displayWorkflowStatus(execution: WorkflowExecution): void {
|
|
639
|
+
console.log(colors.cyan.bold('Workflow Status'));
|
|
640
|
+
console.log('─'.repeat(50));
|
|
641
|
+
|
|
642
|
+
const statusIcon = formatStatusIndicator(execution.status);
|
|
643
|
+
const duration = execution.completedAt
|
|
644
|
+
? formatDuration(execution.completedAt.getTime() - execution.startedAt.getTime())
|
|
645
|
+
: formatDuration(Date.now() - execution.startedAt.getTime());
|
|
646
|
+
|
|
647
|
+
console.log(`${colors.white('Name:')} ${execution.workflowName}`);
|
|
648
|
+
console.log(`${colors.white('ID:')} ${execution.id}`);
|
|
649
|
+
console.log(`${colors.white('Status:')} ${statusIcon} ${execution.status}`);
|
|
650
|
+
console.log(`${colors.white('Started:')} ${execution.startedAt.toLocaleString()}`);
|
|
651
|
+
console.log(`${colors.white('Duration:')} ${duration}`);
|
|
652
|
+
|
|
653
|
+
const progressBar = formatProgressBar(
|
|
654
|
+
execution.progress.completed,
|
|
655
|
+
execution.progress.total,
|
|
656
|
+
40,
|
|
657
|
+
'Progress'
|
|
658
|
+
);
|
|
659
|
+
console.log(`${progressBar} ${execution.progress.completed}/${execution.progress.total}`);
|
|
660
|
+
|
|
661
|
+
if (execution.progress.failed > 0) {
|
|
662
|
+
console.log(`${colors.white('Failed Tasks:')} ${colors.red(execution.progress.failed.toString())}`);
|
|
663
|
+
}
|
|
664
|
+
console.log();
|
|
665
|
+
|
|
666
|
+
// Task details
|
|
667
|
+
console.log(colors.cyan.bold('Tasks'));
|
|
668
|
+
console.log('─'.repeat(50));
|
|
669
|
+
|
|
670
|
+
const table = new Table()
|
|
671
|
+
.header(['Task', 'Status', 'Duration', 'Agent'])
|
|
672
|
+
.border(true);
|
|
673
|
+
|
|
674
|
+
for (const taskExec of execution.tasks) {
|
|
675
|
+
const statusIcon = formatStatusIndicator(taskExec.status);
|
|
676
|
+
const duration = taskExec.completedAt && taskExec.startedAt
|
|
677
|
+
? formatDuration(taskExec.completedAt.getTime() - taskExec.startedAt.getTime())
|
|
678
|
+
: taskExec.startedAt
|
|
679
|
+
? formatDuration(Date.now() - taskExec.startedAt.getTime())
|
|
680
|
+
: '-';
|
|
681
|
+
|
|
682
|
+
table.push([
|
|
683
|
+
colors.white(taskExec.taskId),
|
|
684
|
+
`${statusIcon} ${taskExec.status}`,
|
|
685
|
+
duration,
|
|
686
|
+
taskExec.assignedAgent || '-'
|
|
687
|
+
]);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
table.render();
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
function displayWorkflowProgress(execution: WorkflowExecution): void {
|
|
694
|
+
const progress = `${execution.progress.completed}/${execution.progress.total}`;
|
|
695
|
+
const progressBar = formatProgressBar(
|
|
696
|
+
execution.progress.completed,
|
|
697
|
+
execution.progress.total,
|
|
698
|
+
30
|
|
699
|
+
);
|
|
700
|
+
|
|
701
|
+
console.log(`\r${progressBar} ${progress} tasks completed`);
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
async function getRunningWorkflows(includeAll = false): Promise<WorkflowExecution[]> {
|
|
705
|
+
// Mock workflow list - in production, this would query the orchestrator
|
|
706
|
+
return [
|
|
707
|
+
{
|
|
708
|
+
id: 'workflow-001',
|
|
709
|
+
workflowName: 'Research Workflow',
|
|
710
|
+
status: 'running' as const,
|
|
711
|
+
startedAt: new Date(Date.now() - 120000), // 2 minutes ago
|
|
712
|
+
progress: { total: 5, completed: 3, failed: 0 },
|
|
713
|
+
tasks: []
|
|
714
|
+
},
|
|
715
|
+
{
|
|
716
|
+
id: 'workflow-002',
|
|
717
|
+
workflowName: 'Implementation Workflow',
|
|
718
|
+
status: 'completed' as const,
|
|
719
|
+
startedAt: new Date(Date.now() - 300000), // 5 minutes ago
|
|
720
|
+
completedAt: new Date(Date.now() - 60000), // 1 minute ago
|
|
721
|
+
progress: { total: 3, completed: 3, failed: 0 },
|
|
722
|
+
tasks: []
|
|
723
|
+
}
|
|
724
|
+
].filter(w => includeAll || w.status === 'running');
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
async function getWorkflowExecution(workflowId: string): Promise<WorkflowExecution> {
|
|
728
|
+
const workflows = await getRunningWorkflows(true);
|
|
729
|
+
const workflow = workflows.find(w => w.id === workflowId || w.id.startsWith(workflowId));
|
|
730
|
+
|
|
731
|
+
if (!workflow) {
|
|
732
|
+
throw new Error(`Workflow '${workflowId}' not found`);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
return workflow;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
function hasCircularDependencies(graph: Map<string, string[]>): boolean {
|
|
739
|
+
const visited = new Set<string>();
|
|
740
|
+
const recursionStack = new Set<string>();
|
|
741
|
+
|
|
742
|
+
function hasCycle(node: string): boolean {
|
|
743
|
+
if (recursionStack.has(node)) return true;
|
|
744
|
+
if (visited.has(node)) return false;
|
|
745
|
+
|
|
746
|
+
visited.add(node);
|
|
747
|
+
recursionStack.add(node);
|
|
748
|
+
|
|
749
|
+
const dependencies = graph.get(node) || [];
|
|
750
|
+
for (const dep of dependencies) {
|
|
751
|
+
if (hasCycle(dep)) return true;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
recursionStack.delete(node);
|
|
755
|
+
return false;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
for (const node of graph.keys()) {
|
|
759
|
+
if (hasCycle(node)) return true;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
return false;
|
|
763
|
+
}
|