@ai-setting/roy-agent-cli 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/README.md +126 -0
- package/dist/bin/roy.js +127297 -0
- package/dist/roy-agent-darwin-arm64/bin/roy.js +127297 -0
- package/dist/roy-agent-darwin-x64/bin/roy.js +127297 -0
- package/dist/roy-agent-linux-arm64/bin/roy.js +127297 -0
- package/dist/roy-agent-linux-x64/bin/roy.js +127297 -0
- package/dist/roy-agent-windows-x64/bin/roy.js +127297 -0
- package/package.json +91 -0
- package/src/bin/roy.ts +12 -0
- package/src/cli.ts +101 -0
- package/src/commands/act.ts +480 -0
- package/src/commands/commands-add.ts +110 -0
- package/src/commands/commands-dirs.ts +70 -0
- package/src/commands/commands-info.ts +90 -0
- package/src/commands/commands-list.ts +161 -0
- package/src/commands/commands-remove.ts +147 -0
- package/src/commands/commands.ts +55 -0
- package/src/commands/config/config-service.test.ts +449 -0
- package/src/commands/config/config-service.ts +312 -0
- package/src/commands/config/deep-merge.test.ts +168 -0
- package/src/commands/config/deep-merge.ts +63 -0
- package/src/commands/config/export.ts +97 -0
- package/src/commands/config/filter-history-e2e.test.ts +141 -0
- package/src/commands/config/import-preserve-refs.test.ts +212 -0
- package/src/commands/config/import.ts +119 -0
- package/src/commands/config/index.ts +35 -0
- package/src/commands/config/list.ts +281 -0
- package/src/commands/config/roy-config-e2e.test.ts +297 -0
- package/src/commands/config/types.ts +54 -0
- package/src/commands/debug/index.ts +38 -0
- package/src/commands/debug/log.test.ts +233 -0
- package/src/commands/debug/log.ts +123 -0
- package/src/commands/debug/span.test.ts +297 -0
- package/src/commands/debug/span.ts +211 -0
- package/src/commands/debug/trace.test.ts +254 -0
- package/src/commands/debug/trace.ts +140 -0
- package/src/commands/eventsource/add.ts +133 -0
- package/src/commands/eventsource/index.ts +48 -0
- package/src/commands/eventsource/list.ts +194 -0
- package/src/commands/eventsource/remove.ts +95 -0
- package/src/commands/eventsource/start.ts +103 -0
- package/src/commands/eventsource/status.ts +185 -0
- package/src/commands/eventsource/stop.ts +89 -0
- package/src/commands/index.ts +22 -0
- package/src/commands/input-handler.test.ts +76 -0
- package/src/commands/input-handler.ts +43 -0
- package/src/commands/interactive-esc.test.ts +254 -0
- package/src/commands/interactive.shutdown.test.ts +122 -0
- package/src/commands/interactive.test.ts +221 -0
- package/src/commands/interactive.ts +1015 -0
- package/src/commands/lsp/check.ts +92 -0
- package/src/commands/lsp/index.ts +32 -0
- package/src/commands/lsp/install.ts +126 -0
- package/src/commands/lsp/list.ts +64 -0
- package/src/commands/mcp/index.ts +27 -0
- package/src/commands/mcp/list.ts +116 -0
- package/src/commands/mcp/reload.ts +70 -0
- package/src/commands/mcp/tools.ts +121 -0
- package/src/commands/memory/extract-e2e.test.ts +388 -0
- package/src/commands/memory/index.ts +11 -0
- package/src/commands/memory/memory-simplified.test.ts +58 -0
- package/src/commands/memory/memory.ts +25 -0
- package/src/commands/memory/organize.ts +300 -0
- package/src/commands/memory/recall.test.ts +120 -0
- package/src/commands/memory/recall.ts +88 -0
- package/src/commands/memory/record-extract-handle-query.test.ts +385 -0
- package/src/commands/memory/record-prompt-component.test.ts +343 -0
- package/src/commands/memory/record.test.ts +92 -0
- package/src/commands/memory/record.ts +332 -0
- package/src/commands/plugin.test.ts +292 -0
- package/src/commands/plugin.ts +267 -0
- package/src/commands/sessions/active.ts +96 -0
- package/src/commands/sessions/add-message.ts +96 -0
- package/src/commands/sessions/checkpoints.ts +154 -0
- package/src/commands/sessions/compact.test.ts +215 -0
- package/src/commands/sessions/compact.ts +269 -0
- package/src/commands/sessions/delete.ts +236 -0
- package/src/commands/sessions/get.ts +165 -0
- package/src/commands/sessions/grep.ts +233 -0
- package/src/commands/sessions/index.ts +95 -0
- package/src/commands/sessions/list.ts +210 -0
- package/src/commands/sessions/messages.test.ts +333 -0
- package/src/commands/sessions/messages.ts +248 -0
- package/src/commands/sessions/mock.ts +194 -0
- package/src/commands/sessions/new.ts +82 -0
- package/src/commands/sessions/rename.ts +98 -0
- package/src/commands/shared/event-handler.ts +213 -0
- package/src/commands/shared/event-message-formatter.ts +295 -0
- package/src/commands/shared/index.ts +11 -0
- package/src/commands/shared/query-executor.test.ts +434 -0
- package/src/commands/shared/query-executor.ts +324 -0
- package/src/commands/shared/repl-engine.test.ts +354 -0
- package/src/commands/shared/session-manager.test.ts +212 -0
- package/src/commands/shared/session-manager.ts +114 -0
- package/src/commands/skills/get.ts +90 -0
- package/src/commands/skills/index.ts +39 -0
- package/src/commands/skills/list.ts +129 -0
- package/src/commands/skills/reload.ts +59 -0
- package/src/commands/skills/search.ts +132 -0
- package/src/commands/skills/show-config.ts +93 -0
- package/src/commands/tasks/complete.ts +92 -0
- package/src/commands/tasks/create.ts +118 -0
- package/src/commands/tasks/delete.ts +86 -0
- package/src/commands/tasks/get.ts +116 -0
- package/src/commands/tasks/index.ts +53 -0
- package/src/commands/tasks/list.ts +140 -0
- package/src/commands/tasks/operations.ts +120 -0
- package/src/commands/tasks/update.ts +122 -0
- package/src/commands/tools/exec-tool.ts +128 -0
- package/src/commands/tools/get.ts +114 -0
- package/src/commands/tools/index.ts +35 -0
- package/src/commands/tools/list.ts +107 -0
- package/src/commands/tools/shared/index.ts +7 -0
- package/src/commands/tools/shared/schema-helper.ts +111 -0
- package/src/commands/workflow/commands/add.ts +315 -0
- package/src/commands/workflow/commands/get.ts +193 -0
- package/src/commands/workflow/commands/list.ts +137 -0
- package/src/commands/workflow/commands/nodes.ts +528 -0
- package/src/commands/workflow/commands/remove.ts +94 -0
- package/src/commands/workflow/commands/run.ts +398 -0
- package/src/commands/workflow/commands/status.ts +147 -0
- package/src/commands/workflow/commands/stop.ts +91 -0
- package/src/commands/workflow/commands/update.ts +130 -0
- package/src/commands/workflow/commands/validate.ts +139 -0
- package/src/commands/workflow/commands/workflow-cli.test.ts +196 -0
- package/src/commands/workflow/index.ts +65 -0
- package/src/commands/workflow/renderers.ts +358 -0
- package/src/commands/workflow/validators/index.ts +8 -0
- package/src/commands/workflow/validators/node-validator-factory.ts +40 -0
- package/src/commands/workflow/validators/node-validator.ts +125 -0
- package/src/commands/workflow/validators/nodes/agent-node-validator.ts +58 -0
- package/src/commands/workflow/validators/nodes/condition-node-validator.ts +34 -0
- package/src/commands/workflow/validators/nodes/decorator-node-validator.ts +45 -0
- package/src/commands/workflow/validators/nodes/merge-node-validator.ts +46 -0
- package/src/commands/workflow/validators/nodes/skill-node-validator.ts +33 -0
- package/src/commands/workflow/validators/nodes/tool-node-validator.ts +54 -0
- package/src/commands/workflow/validators/nodes/workflow-node-validator.ts +33 -0
- package/src/commands/workflow/validators/types.ts +78 -0
- package/src/commands/workflow/validators/workflow-validator.test.ts +273 -0
- package/src/commands/workflow/validators/workflow-validator.ts +320 -0
- package/src/index.ts +19 -0
- package/src/plugin/apply.ts +103 -0
- package/src/plugin/discover.ts +219 -0
- package/src/plugin/index.ts +45 -0
- package/src/plugin/registry.ts +272 -0
- package/src/plugin/types.ts +165 -0
- package/src/services/context-handler.service.test.ts +501 -0
- package/src/services/context-handler.service.ts +372 -0
- package/src/services/environment.service.commands-prompt.test.ts +167 -0
- package/src/services/environment.service.ts +656 -0
- package/src/services/output.service.test.ts +92 -0
- package/src/services/output.service.ts +122 -0
- package/src/services/quiet-mode.service.test.ts +114 -0
- package/src/services/quiet-mode.service.ts +81 -0
- package/src/services/stream-output.service.test.ts +214 -0
- package/src/services/stream-output.service.ts +323 -0
- package/src/util/which.test.ts +101 -0
- package/src/util/which.ts +55 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Workflow Update Command
|
|
3
|
+
*
|
|
4
|
+
* Update an existing workflow
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { CommandModule } from 'yargs';
|
|
8
|
+
import { EnvironmentService } from '../../../services/environment.service';
|
|
9
|
+
import { OutputService } from '../../../services/output.service';
|
|
10
|
+
import { renderWorkflowUpdated } from '../renderers';
|
|
11
|
+
import chalk from 'chalk';
|
|
12
|
+
import fs from 'fs';
|
|
13
|
+
import path from 'path';
|
|
14
|
+
import type { WorkflowService } from '@ai-setting/roy-agent-core/env/workflow/service';
|
|
15
|
+
|
|
16
|
+
export interface WorkflowUpdateOptions {
|
|
17
|
+
name: string;
|
|
18
|
+
file?: string;
|
|
19
|
+
description?: string;
|
|
20
|
+
tags?: string[];
|
|
21
|
+
taskId?: number;
|
|
22
|
+
config?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* WorkflowUpdateCommand - Update an existing workflow
|
|
27
|
+
*/
|
|
28
|
+
export const WorkflowUpdateCommand: CommandModule<object, object> = {
|
|
29
|
+
command: 'update <name>',
|
|
30
|
+
describe: '更新已存在的 Workflow',
|
|
31
|
+
|
|
32
|
+
builder: (yargs) =>
|
|
33
|
+
yargs
|
|
34
|
+
.positional('name', {
|
|
35
|
+
describe: 'Workflow 名称',
|
|
36
|
+
type: 'string',
|
|
37
|
+
demandOption: true,
|
|
38
|
+
})
|
|
39
|
+
.option('file', {
|
|
40
|
+
alias: 'f',
|
|
41
|
+
describe: 'Workflow 定义文件 (YAML/JSON)',
|
|
42
|
+
type: 'string',
|
|
43
|
+
})
|
|
44
|
+
.option('description', {
|
|
45
|
+
alias: 'd',
|
|
46
|
+
describe: '更新描述',
|
|
47
|
+
type: 'string',
|
|
48
|
+
})
|
|
49
|
+
.option('tags', {
|
|
50
|
+
alias: 't',
|
|
51
|
+
describe: '更新标签(逗号分隔)',
|
|
52
|
+
type: 'string',
|
|
53
|
+
})
|
|
54
|
+
.option('task-id', {
|
|
55
|
+
alias: 'i',
|
|
56
|
+
describe: '更新关联任务 ID',
|
|
57
|
+
type: 'number',
|
|
58
|
+
})
|
|
59
|
+
.option('config', {
|
|
60
|
+
describe: '配置文件路径',
|
|
61
|
+
type: 'string',
|
|
62
|
+
}),
|
|
63
|
+
|
|
64
|
+
async handler(args) {
|
|
65
|
+
const a = args as any;
|
|
66
|
+
const output = new OutputService();
|
|
67
|
+
const envService = new EnvironmentService(output);
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
await envService.create({ configPath: a.config });
|
|
71
|
+
const env = envService.getEnvironment();
|
|
72
|
+
if (!env) {
|
|
73
|
+
output.error("Failed to create environment");
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
const workflowComponent = env.getComponent('workflow') as any;
|
|
77
|
+
|
|
78
|
+
if (!workflowComponent) {
|
|
79
|
+
output.error('WorkflowComponent not available');
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const service = workflowComponent.getService() as WorkflowService;
|
|
84
|
+
|
|
85
|
+
// Check if workflow exists
|
|
86
|
+
const existing = service.getWorkflow(a.name);
|
|
87
|
+
if (!existing) {
|
|
88
|
+
output.error(`Workflow not found: ${a.name}`);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Prepare updates
|
|
93
|
+
const updates: any = {};
|
|
94
|
+
|
|
95
|
+
if (a.description !== undefined) {
|
|
96
|
+
updates.description = a.description;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (a.taskId !== undefined) {
|
|
100
|
+
updates.metadata = { ...existing.metadata, taskId: a.taskId };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (a.file) {
|
|
104
|
+
const filePath = path.resolve(a.file);
|
|
105
|
+
if (!fs.existsSync(filePath)) {
|
|
106
|
+
output.error(`File not found: ${filePath}`);
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
111
|
+
const { definition } = await workflowComponent.parseWorkflowFile(content, filePath);
|
|
112
|
+
updates.definition = definition;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Parse tags if provided
|
|
116
|
+
const tags = a.tags ? a.tags.split(',').map((t: string) => t.trim()) : undefined;
|
|
117
|
+
|
|
118
|
+
// Update workflow
|
|
119
|
+
const workflow = await service.updateWorkflow(a.name, updates, { tags });
|
|
120
|
+
|
|
121
|
+
output.log(renderWorkflowUpdated(workflow));
|
|
122
|
+
output.log(chalk.green(`\n✅ Workflow '${a.name}' updated successfully`));
|
|
123
|
+
} catch (error) {
|
|
124
|
+
output.error(`Failed to update workflow: ${error}`);
|
|
125
|
+
process.exit(1);
|
|
126
|
+
} finally {
|
|
127
|
+
await envService.dispose();
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Workflow validate command
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { CommandModule } from 'yargs';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { readFileSync } from 'fs';
|
|
8
|
+
import { WorkflowValidator } from '../validators/workflow-validator';
|
|
9
|
+
import type { RawWorkflowDefinition, ValidationError } from '../validators/types';
|
|
10
|
+
|
|
11
|
+
export interface WorkflowValidateOptions {
|
|
12
|
+
input?: string;
|
|
13
|
+
yaml?: string;
|
|
14
|
+
json?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Parse workflow from various input formats
|
|
19
|
+
*/
|
|
20
|
+
async function parseWorkflowInput(input?: string, yamlStr?: string): Promise<RawWorkflowDefinition> {
|
|
21
|
+
// Helper to parse YAML
|
|
22
|
+
const parseYaml = async (content: string): Promise<RawWorkflowDefinition> => {
|
|
23
|
+
const YAML = await import('yaml');
|
|
24
|
+
return YAML.parse(content) as RawWorkflowDefinition;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// From file path
|
|
28
|
+
if (input && !yamlStr) {
|
|
29
|
+
try {
|
|
30
|
+
const content = readFileSync(input, 'utf-8');
|
|
31
|
+
return await parseYaml(content);
|
|
32
|
+
} catch (error: any) {
|
|
33
|
+
throw new Error(`Failed to read file "${input}": ${error.message}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// From --yaml flag
|
|
38
|
+
if (yamlStr) {
|
|
39
|
+
try {
|
|
40
|
+
return await parseYaml(yamlStr);
|
|
41
|
+
} catch (error: any) {
|
|
42
|
+
throw new Error(`Failed to parse YAML: ${error.message}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// From workflow ID (requires database lookup)
|
|
47
|
+
if (input && !input.includes(':') && !input.includes('-')) {
|
|
48
|
+
// TODO: Load from database if needed
|
|
49
|
+
throw new Error('Workflow ID lookup not implemented yet. Please use file path or --yaml flag.');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
throw new Error('Please provide workflow input: file path, --yaml string, or workflow ID');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Render validation result
|
|
57
|
+
*/
|
|
58
|
+
function renderResult(result: ReturnType<WorkflowValidator['validate']>, options: WorkflowValidateOptions): void {
|
|
59
|
+
// JSON output
|
|
60
|
+
if (options.json) {
|
|
61
|
+
console.log(JSON.stringify(result, null, 2));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Text output
|
|
66
|
+
if (result.valid) {
|
|
67
|
+
console.log(chalk.green('\n✅ Workflow validation PASSED\n'));
|
|
68
|
+
console.log(chalk.bold(`Workflow: ${result.workflowName}`));
|
|
69
|
+
console.log(`Nodes: ${result.nodeCount}`);
|
|
70
|
+
|
|
71
|
+
// List valid nodes
|
|
72
|
+
console.log('\nValid nodes:');
|
|
73
|
+
// Since we don't track valid nodes separately, we just show the count
|
|
74
|
+
console.log(chalk.green(' ✓ All nodes passed validation'));
|
|
75
|
+
} else {
|
|
76
|
+
console.log(chalk.red(`\n❌ Workflow validation FAILED (${result.errors.length} errors found)\n`));
|
|
77
|
+
|
|
78
|
+
result.errors.forEach((error: ValidationError, index: number) => {
|
|
79
|
+
console.log(chalk.red(`[Error ${index + 1}/${result.errors.length}]${error.nodeId ? ` Node "${error.nodeId}"` : ''}`));
|
|
80
|
+
console.log(` ${chalk.bold('Type:')} ${error.type}`);
|
|
81
|
+
console.log(` ${chalk.bold('Description:')} ${error.description}`);
|
|
82
|
+
console.log(` ${chalk.bold('Expected:')} ${error.expected}`);
|
|
83
|
+
console.log(` ${chalk.bold('Actual:')} ${error.actual}`);
|
|
84
|
+
console.log(` ${chalk.green('Fix:')} ${error.fix}`);
|
|
85
|
+
console.log();
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* WorkflowValidateCommand - Validate workflow YAML
|
|
92
|
+
*/
|
|
93
|
+
export const WorkflowValidateCommand: CommandModule<object, WorkflowValidateOptions> = {
|
|
94
|
+
command: 'validate [input]',
|
|
95
|
+
describe: 'Validate workflow YAML syntax and node configurations',
|
|
96
|
+
|
|
97
|
+
builder: (yargs) =>
|
|
98
|
+
yargs
|
|
99
|
+
.positional('input', {
|
|
100
|
+
describe: 'Workflow file path or workflow ID',
|
|
101
|
+
type: 'string',
|
|
102
|
+
})
|
|
103
|
+
.option('yaml', {
|
|
104
|
+
describe: 'YAML content string (alternative to positional input)',
|
|
105
|
+
type: 'string',
|
|
106
|
+
alias: 'y',
|
|
107
|
+
})
|
|
108
|
+
.option('json', {
|
|
109
|
+
describe: 'Output in JSON format',
|
|
110
|
+
type: 'boolean',
|
|
111
|
+
alias: 'j',
|
|
112
|
+
}),
|
|
113
|
+
|
|
114
|
+
async handler(args) {
|
|
115
|
+
const options = args as WorkflowValidateOptions;
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
// Parse input
|
|
119
|
+
const workflow = await parseWorkflowInput(options.input, options.yaml);
|
|
120
|
+
if (!workflow) {
|
|
121
|
+
console.error(chalk.red('Failed to parse workflow input'));
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Validate
|
|
126
|
+
const validator = new WorkflowValidator();
|
|
127
|
+
const result = validator.validate(workflow);
|
|
128
|
+
|
|
129
|
+
// Render result
|
|
130
|
+
renderResult(result, options);
|
|
131
|
+
|
|
132
|
+
// Exit with appropriate code
|
|
133
|
+
process.exit(result.valid ? 0 : 1);
|
|
134
|
+
} catch (error: any) {
|
|
135
|
+
console.error(chalk.red(`\nError: ${error.message}`));
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
};
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Workflow CLI Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests workflow CLI commands with taskId and search functionality.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect } from 'bun:test';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
import { setTimeout as sleep } from 'timers/promises';
|
|
11
|
+
|
|
12
|
+
// Get the project root
|
|
13
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const projectRoot = path.resolve(__dirname, '../../../../../..');
|
|
15
|
+
const royBin = path.join(projectRoot, 'packages/cli/src/bin/roy.ts');
|
|
16
|
+
|
|
17
|
+
// Helper function to run CLI command with proper timeout using Bun subprocess
|
|
18
|
+
async function runCliCommand(args: string[], timeoutMs: number = 10000): Promise<{ stdout: string; stderr: string }> {
|
|
19
|
+
const cmd = `bun run ${royBin} ${args.join(' ')}`;
|
|
20
|
+
|
|
21
|
+
// Create subprocess promise
|
|
22
|
+
const runProcess = async () => {
|
|
23
|
+
const proc = Bun.spawn({
|
|
24
|
+
cmd: ['bun', 'run', royBin, ...args],
|
|
25
|
+
cwd: projectRoot,
|
|
26
|
+
stdout: 'pipe',
|
|
27
|
+
stderr: 'pipe',
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const [stdout, stderr] = await Promise.all([
|
|
31
|
+
new Response(proc.stdout).text(),
|
|
32
|
+
new Response(proc.stderr).text(),
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
const code = await proc.exited;
|
|
36
|
+
if (code !== 0 && code !== null) {
|
|
37
|
+
throw new Error(`Command exited with code ${code}: ${cmd}\nStderr: ${stderr}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return { stdout, stderr };
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Use Promise.race for timeout
|
|
44
|
+
return Promise.race([
|
|
45
|
+
runProcess(),
|
|
46
|
+
sleep(timeoutMs).then(() => {
|
|
47
|
+
throw new Error(`Command timed out after ${timeoutMs}ms: ${cmd}`);
|
|
48
|
+
}),
|
|
49
|
+
]);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
describe('Workflow CLI Commands', () => {
|
|
53
|
+
describe('workflow list --help', () => {
|
|
54
|
+
it('should show --task-id option', async () => {
|
|
55
|
+
const result = await runCliCommand(['workflow', 'list', '--help']);
|
|
56
|
+
|
|
57
|
+
expect(result.stdout).toContain('--task-id');
|
|
58
|
+
expect(result.stdout).toContain('按关联任务 ID 筛选');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should show --search option', async () => {
|
|
62
|
+
const result = await runCliCommand(['workflow', 'list', '--help']);
|
|
63
|
+
|
|
64
|
+
expect(result.stdout).toContain('--search');
|
|
65
|
+
expect(result.stdout).toContain('搜索 description 和 tags');
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe('workflow add --help', () => {
|
|
70
|
+
it('should show --task-id option', async () => {
|
|
71
|
+
const result = await runCliCommand(['workflow', 'add', '--help']);
|
|
72
|
+
|
|
73
|
+
expect(result.stdout).toContain('--task-id');
|
|
74
|
+
expect(result.stdout).toContain('关联任务 ID');
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe('workflow update --help', () => {
|
|
79
|
+
it('should show --task-id option', async () => {
|
|
80
|
+
const result = await runCliCommand(['workflow', 'update', '--help']);
|
|
81
|
+
|
|
82
|
+
expect(result.stdout).toContain('--task-id');
|
|
83
|
+
expect(result.stdout).toContain('更新关联任务 ID');
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe("workflow run CLI", () => {
|
|
89
|
+
describe("run --help", () => {
|
|
90
|
+
it("should show --session-id option for resume", async () => {
|
|
91
|
+
const result = await runCliCommand(['workflow', 'run', '--help']);
|
|
92
|
+
|
|
93
|
+
expect(result.stdout).toContain('--session-id');
|
|
94
|
+
expect(result.stdout).toContain('-s');
|
|
95
|
+
expect(result.stdout).toContain('恢复运行');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("should show --file option for file-based workflows", async () => {
|
|
99
|
+
const result = await runCliCommand(['workflow', 'run', '--help']);
|
|
100
|
+
|
|
101
|
+
expect(result.stdout).toContain('--file');
|
|
102
|
+
expect(result.stdout).toContain('-f');
|
|
103
|
+
expect(result.stdout).toContain('文件路径');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("should show --name option for custom registration", async () => {
|
|
107
|
+
const result = await runCliCommand(['workflow', 'run', '--help']);
|
|
108
|
+
|
|
109
|
+
expect(result.stdout).toContain('--name');
|
|
110
|
+
expect(result.stdout).toContain('-n');
|
|
111
|
+
expect(result.stdout).toContain('注册');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("should show --register-only option", async () => {
|
|
115
|
+
const result = await runCliCommand(['workflow', 'run', '--help']);
|
|
116
|
+
|
|
117
|
+
expect(result.stdout).toContain('--register-only');
|
|
118
|
+
expect(result.stdout).toContain('-r');
|
|
119
|
+
expect(result.stdout).toContain('只注册不运行');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("should show positional argument identifier", async () => {
|
|
123
|
+
const result = await runCliCommand(['workflow', 'run', '--help']);
|
|
124
|
+
|
|
125
|
+
expect(result.stdout).toContain('<identifier>');
|
|
126
|
+
expect(result.stdout).toContain('Workflow 名称或定义文件路径');
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Note: Integration tests for actual run/resume functionality are skipped
|
|
131
|
+
// because they require:
|
|
132
|
+
// 1. A valid workflow to be registered in the system
|
|
133
|
+
// 2. Environment setup with proper configuration
|
|
134
|
+
// 3. Long execution time for workflow runs
|
|
135
|
+
//
|
|
136
|
+
// These should be covered by E2E tests or integration tests with proper mocks.
|
|
137
|
+
|
|
138
|
+
describe.skip("run with actual workflow", () => {
|
|
139
|
+
it("should create new session and run", async () => {
|
|
140
|
+
// TODO: This requires a registered workflow and proper environment
|
|
141
|
+
// Would test: roy workflow run <workflowName>
|
|
142
|
+
// Expected: creates session, runs workflow, returns result
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("should validate session exists on resume", async () => {
|
|
146
|
+
// TODO: This requires a paused workflow session
|
|
147
|
+
// Would test: roy workflow run -s <sessionId> --input "response"
|
|
148
|
+
// Expected: validates session type is 'workflow', resumes execution
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe("workflow stop CLI", () => {
|
|
154
|
+
describe("stop --help", () => {
|
|
155
|
+
it("should show positional argument runId", async () => {
|
|
156
|
+
const result = await runCliCommand(['workflow', 'stop', '--help']);
|
|
157
|
+
|
|
158
|
+
expect(result.stdout).toContain('<runId>');
|
|
159
|
+
expect(result.stdout).toContain('Run ID');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("should show --config option", async () => {
|
|
163
|
+
const result = await runCliCommand(['workflow', 'stop', '--help']);
|
|
164
|
+
|
|
165
|
+
expect(result.stdout).toContain('--config');
|
|
166
|
+
expect(result.stdout).toContain('配置文件路径');
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Note: Testing actual stop functionality requires a running/paused workflow session.
|
|
171
|
+
// This should be covered by E2E tests with proper setup.
|
|
172
|
+
|
|
173
|
+
describe.skip("stop with actual run", () => {
|
|
174
|
+
it("should call engine.stop for running session", async () => {
|
|
175
|
+
// TODO: This requires a running workflow session
|
|
176
|
+
// Would test: roy workflow stop <runId>
|
|
177
|
+
// Expected: calls service.stopRun(runId), confirms stop
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("should call engine.stop for paused session", async () => {
|
|
181
|
+
// TODO: This requires a paused workflow session
|
|
182
|
+
// Would test: roy workflow stop <sessionId>
|
|
183
|
+
// Expected: calls service.stopRun(sessionId), confirms stop
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it("should error when session not found", async () => {
|
|
187
|
+
// TODO: This requires mocking the service to return null for getSession
|
|
188
|
+
// Expected: exits with error "Run not found"
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("should error when session not in stoppable state", async () => {
|
|
192
|
+
// TODO: This requires a completed/failed session
|
|
193
|
+
// Expected: exits with error "Cannot stop run in status: completed"
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Workflow Command - Main entry point
|
|
3
|
+
*
|
|
4
|
+
* Workflow DAG management and execution CLI commands
|
|
5
|
+
*
|
|
6
|
+
* Note: Resume functionality is unified into `workflow run -s <sessionId>`.
|
|
7
|
+
* Use `workflow run -s <sessionId> --input "response"` to resume a paused workflow.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { CommandModule } from 'yargs';
|
|
11
|
+
import { WorkflowListCommand } from './commands/list';
|
|
12
|
+
import { WorkflowAddCommand } from './commands/add';
|
|
13
|
+
import { WorkflowGetCommand } from './commands/get';
|
|
14
|
+
import { WorkflowUpdateCommand } from './commands/update';
|
|
15
|
+
import { WorkflowRemoveCommand } from './commands/remove';
|
|
16
|
+
import { WorkflowRunCommand } from './commands/run';
|
|
17
|
+
import { WorkflowStopCommand } from './commands/stop';
|
|
18
|
+
import { WorkflowStatusCommand } from './commands/status';
|
|
19
|
+
import { WorkflowNodesCommand } from './commands/nodes';
|
|
20
|
+
import { WorkflowValidateCommand } from './commands/validate';
|
|
21
|
+
|
|
22
|
+
export interface WorkflowOptions {
|
|
23
|
+
command?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* WorkflowCommand - Workflow DAG management
|
|
28
|
+
*/
|
|
29
|
+
export const WorkflowCommand: CommandModule<object, WorkflowOptions> = {
|
|
30
|
+
command: 'workflow',
|
|
31
|
+
describe: 'Workflow DAG management and execution - create, run, and monitor workflows',
|
|
32
|
+
|
|
33
|
+
builder: (yargs) =>
|
|
34
|
+
yargs
|
|
35
|
+
.command(WorkflowListCommand)
|
|
36
|
+
.command(WorkflowAddCommand)
|
|
37
|
+
.command(WorkflowNodesCommand)
|
|
38
|
+
.command(WorkflowValidateCommand)
|
|
39
|
+
.command(WorkflowGetCommand)
|
|
40
|
+
.command(WorkflowUpdateCommand)
|
|
41
|
+
.command(WorkflowRemoveCommand)
|
|
42
|
+
.command(WorkflowRunCommand)
|
|
43
|
+
.command(WorkflowStopCommand)
|
|
44
|
+
.command(WorkflowStatusCommand)
|
|
45
|
+
.demandCommand(1, '请指定子命令:list、add、validate、nodes、get、update、remove、run、stop、status\n\nResume: 使用 `workflow run -s <sessionId>` 恢复暂停的工作流')
|
|
46
|
+
.help(),
|
|
47
|
+
|
|
48
|
+
handler() {
|
|
49
|
+
// Default to help (handled by yargs demandCommand)
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Export subcommands
|
|
54
|
+
export {
|
|
55
|
+
WorkflowListCommand,
|
|
56
|
+
WorkflowAddCommand,
|
|
57
|
+
WorkflowGetCommand,
|
|
58
|
+
WorkflowUpdateCommand,
|
|
59
|
+
WorkflowRemoveCommand,
|
|
60
|
+
WorkflowRunCommand,
|
|
61
|
+
WorkflowStopCommand,
|
|
62
|
+
WorkflowStatusCommand,
|
|
63
|
+
WorkflowNodesCommand,
|
|
64
|
+
WorkflowValidateCommand,
|
|
65
|
+
};
|