@zeyue0329/xiaoma-cli 1.0.40 → 1.0.42
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/.idea/workspace.xml +8 -1
- package/.xiaoma-core/.coordinator-state.json +19 -0
- package/dist/agents/architect.txt +192 -0
- package/dist/agents/workflow-executor.txt +151 -1
- package/dist/agents/workflow-helper.txt +93 -0
- package/dist/teams/team-all.txt +396 -191
- package/dist/teams/team-fullstack-with-database.txt +2 -0
- package/dist/teams/team-fullstack.txt +2 -0
- package/dist/teams/team-no-ui.txt +2 -0
- package/docs/architecture/workflow-coordinator-implementation.md +1188 -0
- package/docs/prd/workflow-coordinator-prd.md +1214 -0
- package/package.json +6 -1
- package/tools/api-server.js +367 -0
- package/tools/installer/package.json +1 -1
- package/tools/workflow-coordinator/README.md +38 -0
- package/tools/workflow-coordinator/USAGE.md +548 -0
- package/tools/workflow-coordinator/package-lock.json +4868 -0
- package/tools/workflow-coordinator/package.json +35 -0
- package/tools/workflow-coordinator/src/api/server.js +207 -0
- package/tools/workflow-coordinator/src/controller/workflow-controller.js +263 -0
- package/tools/workflow-coordinator/src/index.js +113 -0
- package/tools/workflow-coordinator/src/parser/workflow-parser.js +144 -0
- package/tools/workflow-coordinator/src/utils/state-manager.js +59 -0
- package/tools/workflow-coordinator/src/utils/validator.js +86 -0
- package/tools/workflow-coordinator/test/integration-test.js +266 -0
- package/tools/workflow-coordinator/test/quick-test.js +127 -0
- package/xiaoma-core/agents/workflow-executor.md +155 -1
- package/xiaoma-core/agents/workflow-helper.md +481 -0
- package/xiaoma-core/workflows/automated-requirements-development.yaml +739 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const yaml = require('js-yaml');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
class WorkflowParser {
|
|
6
|
+
constructor() {
|
|
7
|
+
// 工作流目录相对于项目根目录
|
|
8
|
+
// 从 tools/workflow-coordinator/src/parser 向上找到项目根目录
|
|
9
|
+
const projectRoot = path.resolve(__dirname, '../../../..');
|
|
10
|
+
this.workflowsDir = path.join(projectRoot, 'xiaoma-core/workflows');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 解析工作流YAML文件
|
|
15
|
+
* @param {string} workflowName - 工作流名称
|
|
16
|
+
* @returns {Promise<Object>} 执行计划
|
|
17
|
+
*/
|
|
18
|
+
async parse(workflowName) {
|
|
19
|
+
// 1. 查找工作流文件
|
|
20
|
+
const workflowFile = this.findWorkflowFile(workflowName);
|
|
21
|
+
|
|
22
|
+
if (!workflowFile) {
|
|
23
|
+
throw new Error(`工作流文件未找到: ${workflowName}\n查找路径: ${this.workflowsDir}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// 2. 读取并解析YAML
|
|
27
|
+
const yamlContent = await fs.readFile(workflowFile, 'utf-8');
|
|
28
|
+
const workflowDef = yaml.load(yamlContent);
|
|
29
|
+
|
|
30
|
+
// 3. 验证工作流定义
|
|
31
|
+
this.validate(workflowDef);
|
|
32
|
+
|
|
33
|
+
// 4. 构建执行计划
|
|
34
|
+
const executionPlan = {
|
|
35
|
+
metadata: {
|
|
36
|
+
id: workflowDef.workflow.id,
|
|
37
|
+
name: workflowDef.workflow.name,
|
|
38
|
+
description: workflowDef.workflow.description,
|
|
39
|
+
type: workflowDef.workflow.type,
|
|
40
|
+
projectTypes: workflowDef.workflow.project_types || []
|
|
41
|
+
},
|
|
42
|
+
steps: workflowDef.workflow.sequence.map(step => this.parseStep(step)),
|
|
43
|
+
qualityGates: workflowDef.workflow.quality_gates || {},
|
|
44
|
+
errorHandling: workflowDef.workflow.error_handling || {}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
return executionPlan;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 解析单个步骤
|
|
52
|
+
*/
|
|
53
|
+
parseStep(stepYaml) {
|
|
54
|
+
return {
|
|
55
|
+
id: stepYaml.step,
|
|
56
|
+
description: stepYaml.action || stepYaml.step,
|
|
57
|
+
agent: stepYaml.agent,
|
|
58
|
+
duration: stepYaml.duration,
|
|
59
|
+
requires: stepYaml.requires || [],
|
|
60
|
+
detailedSteps: stepYaml.detailed_steps || [],
|
|
61
|
+
validationCriteria: stepYaml.validation_criteria || [],
|
|
62
|
+
outputs: this.extractOutputs(stepYaml),
|
|
63
|
+
onSuccess: stepYaml.on_success,
|
|
64
|
+
onFailure: stepYaml.on_failure
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 从步骤定义中提取输出文件列表
|
|
70
|
+
*/
|
|
71
|
+
extractOutputs(stepYaml) {
|
|
72
|
+
const outputs = [];
|
|
73
|
+
|
|
74
|
+
// 从validation_criteria提取file_created
|
|
75
|
+
if (stepYaml.validation_criteria) {
|
|
76
|
+
stepYaml.validation_criteria.forEach(criterion => {
|
|
77
|
+
if (typeof criterion === 'string' && criterion.includes('file_created:')) {
|
|
78
|
+
const filePath = criterion.split('file_created:')[1].trim().replace(/["']/g, '');
|
|
79
|
+
outputs.push({ file: filePath });
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 从detailed_steps的output字段提取
|
|
85
|
+
if (stepYaml.detailed_steps) {
|
|
86
|
+
stepYaml.detailed_steps.forEach(detailStep => {
|
|
87
|
+
if (detailStep.output && detailStep.output.file) {
|
|
88
|
+
outputs.push({ file: detailStep.output.file });
|
|
89
|
+
}
|
|
90
|
+
if (detailStep.output_files && Array.isArray(detailStep.output_files)) {
|
|
91
|
+
detailStep.output_files.forEach(file => {
|
|
92
|
+
outputs.push({ file: file });
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return outputs;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 查找工作流文件
|
|
103
|
+
*/
|
|
104
|
+
findWorkflowFile(workflowName) {
|
|
105
|
+
const possibleNames = [
|
|
106
|
+
`${workflowName}.yaml`,
|
|
107
|
+
`${workflowName}.yml`,
|
|
108
|
+
`automated-${workflowName}.yaml`,
|
|
109
|
+
`automated-${workflowName}.yml`
|
|
110
|
+
];
|
|
111
|
+
|
|
112
|
+
for (const name of possibleNames) {
|
|
113
|
+
const filePath = path.join(this.workflowsDir, name);
|
|
114
|
+
if (fs.existsSync(filePath)) {
|
|
115
|
+
return filePath;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* 验证工作流定义
|
|
124
|
+
*/
|
|
125
|
+
validate(workflowDef) {
|
|
126
|
+
if (!workflowDef.workflow) {
|
|
127
|
+
throw new Error('无效的工作流定义: 缺少 workflow 字段');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (!workflowDef.workflow.id) {
|
|
131
|
+
throw new Error('无效的工作流定义: 缺少 workflow.id');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (!workflowDef.workflow.sequence || !Array.isArray(workflowDef.workflow.sequence)) {
|
|
135
|
+
throw new Error('无效的工作流定义: 缺少 workflow.sequence 或格式错误');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (workflowDef.workflow.sequence.length === 0) {
|
|
139
|
+
throw new Error('无效的工作流定义: workflow.sequence 为空');
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
module.exports = WorkflowParser;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
class StateManager {
|
|
5
|
+
constructor() {
|
|
6
|
+
// 状态目录相对于项目根目录
|
|
7
|
+
// 从 tools/workflow-coordinator/src/utils 向上找到项目根目录
|
|
8
|
+
const projectRoot = path.resolve(__dirname, '../../../..');
|
|
9
|
+
this.stateDir = path.join(projectRoot, '.xiaoma-core');
|
|
10
|
+
this.stateFile = path.join(this.stateDir, '.coordinator-state.json');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 保存工作流状态
|
|
15
|
+
*/
|
|
16
|
+
async save(state) {
|
|
17
|
+
await fs.ensureDir(this.stateDir);
|
|
18
|
+
await fs.writeFile(
|
|
19
|
+
this.stateFile,
|
|
20
|
+
JSON.stringify(state, null, 2),
|
|
21
|
+
'utf-8'
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 加载工作流状态
|
|
27
|
+
*/
|
|
28
|
+
async load() {
|
|
29
|
+
if (!fs.existsSync(this.stateFile)) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const content = await fs.readFile(this.stateFile, 'utf-8');
|
|
35
|
+
return JSON.parse(content);
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.error('状态文件加载失败:', error.message);
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 清除状态文件
|
|
44
|
+
*/
|
|
45
|
+
async clear() {
|
|
46
|
+
if (fs.existsSync(this.stateFile)) {
|
|
47
|
+
await fs.remove(this.stateFile);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 检查是否存在状态文件
|
|
53
|
+
*/
|
|
54
|
+
exists() {
|
|
55
|
+
return fs.existsSync(this.stateFile);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = StateManager;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
class Validator {
|
|
5
|
+
/**
|
|
6
|
+
* 验证步骤输出
|
|
7
|
+
*/
|
|
8
|
+
async validateOutputs(stepDef, actualOutputs) {
|
|
9
|
+
const issues = [];
|
|
10
|
+
|
|
11
|
+
// 验证所有预期输出文件
|
|
12
|
+
for (const expectedOutput of stepDef.outputs || []) {
|
|
13
|
+
const filePath = expectedOutput.file;
|
|
14
|
+
const actualOutput = actualOutputs.find(o => o.file === filePath);
|
|
15
|
+
|
|
16
|
+
if (!actualOutput) {
|
|
17
|
+
issues.push({
|
|
18
|
+
level: 'error',
|
|
19
|
+
message: `输出文件未报告: ${filePath}`
|
|
20
|
+
});
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!actualOutput.exists) {
|
|
25
|
+
issues.push({
|
|
26
|
+
level: 'error',
|
|
27
|
+
message: `输出文件不存在: ${filePath}`
|
|
28
|
+
});
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (actualOutput.size === 0) {
|
|
33
|
+
issues.push({
|
|
34
|
+
level: 'warning',
|
|
35
|
+
message: `输出文件为空: ${filePath}`
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 验证validation_criteria
|
|
41
|
+
for (const criterion of stepDef.validationCriteria || []) {
|
|
42
|
+
if (typeof criterion === 'string') {
|
|
43
|
+
// 验证file_created标准
|
|
44
|
+
if (criterion.includes('file_created:')) {
|
|
45
|
+
const filePath = criterion.split('file_created:')[1].trim().replace(/["']/g, '');
|
|
46
|
+
const actualOutput = actualOutputs.find(o => o.file === filePath);
|
|
47
|
+
|
|
48
|
+
if (!actualOutput || !actualOutput.exists) {
|
|
49
|
+
issues.push({
|
|
50
|
+
level: 'error',
|
|
51
|
+
message: `验证失败: ${criterion}`
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
passed: issues.filter(i => i.level === 'error').length === 0,
|
|
60
|
+
issues: issues,
|
|
61
|
+
warnings: issues.filter(i => i.level === 'warning').length,
|
|
62
|
+
errors: issues.filter(i => i.level === 'error').length
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 验证文件是否存在
|
|
68
|
+
*/
|
|
69
|
+
validateFileExists(filePath) {
|
|
70
|
+
return fs.existsSync(filePath);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* 验证文件内容是否非空
|
|
75
|
+
*/
|
|
76
|
+
async validateFileNotEmpty(filePath) {
|
|
77
|
+
if (!fs.existsSync(filePath)) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const stats = await fs.stat(filePath);
|
|
82
|
+
return stats.size > 0;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
module.exports = Validator;
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 工作流协调器集成测试
|
|
3
|
+
*
|
|
4
|
+
* 测试完整的工作流执行流程:
|
|
5
|
+
* 1. 启动工作流
|
|
6
|
+
* 2. 模拟步骤完成
|
|
7
|
+
* 3. 验证下一步指令
|
|
8
|
+
* 4. 测试失败重试
|
|
9
|
+
* 5. 测试工作流完成
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fetch = require('node-fetch');
|
|
13
|
+
|
|
14
|
+
const BASE_URL = 'http://localhost:3001';
|
|
15
|
+
|
|
16
|
+
// 辅助函数:发送POST请求
|
|
17
|
+
async function post(endpoint, body) {
|
|
18
|
+
const response = await fetch(`${BASE_URL}${endpoint}`, {
|
|
19
|
+
method: 'POST',
|
|
20
|
+
headers: { 'Content-Type': 'application/json' },
|
|
21
|
+
body: JSON.stringify(body)
|
|
22
|
+
});
|
|
23
|
+
return response.json();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// 辅助函数:发送GET请求
|
|
27
|
+
async function get(endpoint) {
|
|
28
|
+
const response = await fetch(`${BASE_URL}${endpoint}`);
|
|
29
|
+
return response.json();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 测试1: 健康检查
|
|
33
|
+
async function testHealthCheck() {
|
|
34
|
+
console.log('\n=== 测试1: 健康检查 ===');
|
|
35
|
+
|
|
36
|
+
const result = await get('/health');
|
|
37
|
+
console.log('✓ 健康检查响应:', result);
|
|
38
|
+
|
|
39
|
+
if (result.status !== 'ok') {
|
|
40
|
+
throw new Error('健康检查失败');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 测试2: 启动工作流
|
|
45
|
+
async function testStartWorkflow() {
|
|
46
|
+
console.log('\n=== 测试2: 启动工作流 ===');
|
|
47
|
+
|
|
48
|
+
const result = await post('/workflow/start', {
|
|
49
|
+
workflowId: 'test-workflow-' + Date.now(),
|
|
50
|
+
context: {
|
|
51
|
+
userRequirement: '用户需求示例'
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
console.log('✓ 工作流启动响应:');
|
|
56
|
+
console.log(' - 工作流名称:', result.workflowName);
|
|
57
|
+
console.log(' - 总步骤数:', result.totalSteps);
|
|
58
|
+
console.log(' - 第一步ID:', result.firstStep.stepId);
|
|
59
|
+
console.log(' - 智能体:', result.firstStep.agent);
|
|
60
|
+
|
|
61
|
+
if (!result.success) {
|
|
62
|
+
throw new Error('工作流启动失败');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 测试3: 查询状态
|
|
69
|
+
async function testQueryStatus() {
|
|
70
|
+
console.log('\n=== 测试3: 查询状态 ===');
|
|
71
|
+
|
|
72
|
+
const result = await get('/workflow/status');
|
|
73
|
+
console.log('✓ 工作流状态:', result);
|
|
74
|
+
|
|
75
|
+
if (result.status === 'not_started') {
|
|
76
|
+
throw new Error('工作流未启动');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 测试4: 步骤完成(带输出验证失败)
|
|
81
|
+
async function testStepCompleteWithValidationFailure(workflowData) {
|
|
82
|
+
console.log('\n=== 测试4: 步骤完成(输出验证失败) ===');
|
|
83
|
+
|
|
84
|
+
// 模拟文件不存在的情况
|
|
85
|
+
const result = await post('/workflow/step-complete', {
|
|
86
|
+
workflowId: workflowData.workflowId,
|
|
87
|
+
stepId: workflowData.firstStep.stepId,
|
|
88
|
+
status: 'completed',
|
|
89
|
+
outputs: [
|
|
90
|
+
{
|
|
91
|
+
file: workflowData.firstStep.expectedOutputs[0],
|
|
92
|
+
exists: false, // 文件不存在
|
|
93
|
+
size: 0
|
|
94
|
+
}
|
|
95
|
+
],
|
|
96
|
+
duration: 5000
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
console.log('✓ 验证失败响应:');
|
|
100
|
+
console.log(' - 成功标志:', result.success);
|
|
101
|
+
console.log(' - 操作:', result.action);
|
|
102
|
+
console.log(' - 问题数:', result.issues?.length || 0);
|
|
103
|
+
|
|
104
|
+
if (result.success !== false || result.action !== 'fix_quality') {
|
|
105
|
+
throw new Error('应该返回验证失败');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// 测试5: 步骤完成(成功)
|
|
110
|
+
async function testStepCompleteSuccess(workflowData) {
|
|
111
|
+
console.log('\n=== 测试5: 步骤完成(成功) ===');
|
|
112
|
+
|
|
113
|
+
// 模拟所有文件都存在
|
|
114
|
+
const outputs = workflowData.firstStep.expectedOutputs.map(file => ({
|
|
115
|
+
file: file,
|
|
116
|
+
exists: true,
|
|
117
|
+
size: 1024
|
|
118
|
+
}));
|
|
119
|
+
|
|
120
|
+
const result = await post('/workflow/step-complete', {
|
|
121
|
+
workflowId: workflowData.workflowId,
|
|
122
|
+
stepId: workflowData.firstStep.stepId,
|
|
123
|
+
status: 'completed',
|
|
124
|
+
outputs: outputs,
|
|
125
|
+
duration: 10000
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
console.log('✓ 步骤完成响应:');
|
|
129
|
+
console.log(' - 成功标志:', result.success);
|
|
130
|
+
console.log(' - 有下一步:', result.hasNextStep);
|
|
131
|
+
|
|
132
|
+
if (result.hasNextStep) {
|
|
133
|
+
console.log(' - 下一步ID:', result.nextStep.stepId);
|
|
134
|
+
console.log(' - 智能体:', result.nextStep.agent);
|
|
135
|
+
console.log(' - 进度:', `${result.progress.percentComplete}%`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (!result.success) {
|
|
139
|
+
throw new Error('步骤完成失败');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return result;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// 测试6: 步骤失败和重试
|
|
146
|
+
async function testStepFailure(workflowData, currentStepId) {
|
|
147
|
+
console.log('\n=== 测试6: 步骤失败和重试 ===');
|
|
148
|
+
|
|
149
|
+
const result = await post('/workflow/step-failed', {
|
|
150
|
+
workflowId: workflowData.workflowId,
|
|
151
|
+
stepId: currentStepId,
|
|
152
|
+
error: '模拟错误:文件未找到',
|
|
153
|
+
errorDetails: {
|
|
154
|
+
missingFile: 'templates/test.md'
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
console.log('✓ 失败处理响应:');
|
|
159
|
+
console.log(' - 成功标志:', result.success);
|
|
160
|
+
console.log(' - 操作:', result.action);
|
|
161
|
+
|
|
162
|
+
if (result.action === 'retry') {
|
|
163
|
+
console.log(' - 重试次数:', `${result.retryCount}/${result.maxRetries}`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return result;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// 测试7: 完成所有剩余步骤
|
|
170
|
+
async function testCompleteAllSteps(workflowData) {
|
|
171
|
+
console.log('\n=== 测试7: 完成所有剩余步骤 ===');
|
|
172
|
+
|
|
173
|
+
let hasNext = true;
|
|
174
|
+
let currentStepIndex = 1; // 已经完成了第一步
|
|
175
|
+
let nextStep = null;
|
|
176
|
+
|
|
177
|
+
// 获取当前状态
|
|
178
|
+
const status = await get('/workflow/status');
|
|
179
|
+
const totalSteps = status.totalSteps;
|
|
180
|
+
|
|
181
|
+
console.log(`需要完成 ${totalSteps - currentStepIndex} 个步骤`);
|
|
182
|
+
|
|
183
|
+
while (hasNext && currentStepIndex < totalSteps) {
|
|
184
|
+
// 获取当前步骤
|
|
185
|
+
const statusNow = await get('/workflow/status');
|
|
186
|
+
const currentStep = statusNow.currentStep;
|
|
187
|
+
|
|
188
|
+
console.log(`\n正在完成步骤 ${currentStepIndex + 1}/${totalSteps}: ${currentStep}`);
|
|
189
|
+
|
|
190
|
+
// 模拟步骤完成
|
|
191
|
+
const result = await post('/workflow/step-complete', {
|
|
192
|
+
workflowId: workflowData.workflowId,
|
|
193
|
+
stepId: currentStep,
|
|
194
|
+
status: 'completed',
|
|
195
|
+
outputs: [], // 简化:不验证输出
|
|
196
|
+
duration: 5000
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
hasNext = result.hasNextStep;
|
|
200
|
+
currentStepIndex++;
|
|
201
|
+
|
|
202
|
+
if (result.hasNextStep) {
|
|
203
|
+
console.log(`✓ 进度: ${result.progress.percentComplete}%`);
|
|
204
|
+
} else {
|
|
205
|
+
console.log('\n✓ 工作流执行完成!');
|
|
206
|
+
console.log('摘要:');
|
|
207
|
+
console.log(' - 总耗时:', result.summary.totalDurationFormatted);
|
|
208
|
+
console.log(' - 完成步骤:', result.summary.totalSteps);
|
|
209
|
+
console.log(' - 错误数:', result.summary.errors);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// 主测试函数
|
|
215
|
+
async function runTests() {
|
|
216
|
+
console.log('╔════════════════════════════════════════╗');
|
|
217
|
+
console.log('║ 工作流协调器集成测试 ║');
|
|
218
|
+
console.log('╚════════════════════════════════════════╝');
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
// 确保协调器正在运行
|
|
222
|
+
console.log('\n检查协调器是否运行...');
|
|
223
|
+
await testHealthCheck();
|
|
224
|
+
|
|
225
|
+
// 启动工作流
|
|
226
|
+
const workflowData = await testStartWorkflow();
|
|
227
|
+
|
|
228
|
+
// 等待一下让状态保存
|
|
229
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
230
|
+
|
|
231
|
+
// 查询状态
|
|
232
|
+
await testQueryStatus();
|
|
233
|
+
|
|
234
|
+
// 测试输出验证失败
|
|
235
|
+
await testStepCompleteWithValidationFailure(workflowData);
|
|
236
|
+
|
|
237
|
+
// 测试步骤成功完成
|
|
238
|
+
const step1Result = await testStepCompleteSuccess(workflowData);
|
|
239
|
+
|
|
240
|
+
if (step1Result.hasNextStep) {
|
|
241
|
+
// 测试步骤失败和重试
|
|
242
|
+
await testStepFailure(workflowData, step1Result.nextStep.stepId);
|
|
243
|
+
|
|
244
|
+
// 重试后成功完成
|
|
245
|
+
await testStepCompleteSuccess({
|
|
246
|
+
...workflowData,
|
|
247
|
+
firstStep: step1Result.nextStep
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// 完成所有剩余步骤
|
|
252
|
+
await testCompleteAllSteps(workflowData);
|
|
253
|
+
|
|
254
|
+
console.log('\n╔════════════════════════════════════════╗');
|
|
255
|
+
console.log('║ 所有测试通过! ✓ ║');
|
|
256
|
+
console.log('╚════════════════════════════════════════╝\n');
|
|
257
|
+
|
|
258
|
+
} catch (error) {
|
|
259
|
+
console.error('\n❌ 测试失败:', error.message);
|
|
260
|
+
console.error(error.stack);
|
|
261
|
+
process.exit(1);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// 运行测试
|
|
266
|
+
runTests().catch(console.error);
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 快速测试脚本
|
|
3
|
+
* 测试核心功能:启动->完成多步->查看进度
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fetch = require('node-fetch');
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
|
|
9
|
+
const BASE_URL = 'http://localhost:3001';
|
|
10
|
+
|
|
11
|
+
async function post(endpoint, body) {
|
|
12
|
+
const response = await fetch(`${BASE_URL}${endpoint}`, {
|
|
13
|
+
method: 'POST',
|
|
14
|
+
headers: { 'Content-Type': 'application/json' },
|
|
15
|
+
body: JSON.stringify(body)
|
|
16
|
+
});
|
|
17
|
+
return response.json();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function get(endpoint) {
|
|
21
|
+
const response = await fetch(`${BASE_URL}${endpoint}`);
|
|
22
|
+
return response.json();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function main() {
|
|
26
|
+
console.log(chalk.blue.bold('\n╔════════════════════════════════════════╗'));
|
|
27
|
+
console.log(chalk.blue.bold('║ 工作流协调器快速测试 ║'));
|
|
28
|
+
console.log(chalk.blue.bold('╚════════════════════════════════════════╝\n'));
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
// 1. 健康检查
|
|
32
|
+
console.log(chalk.cyan('1. 健康检查...'));
|
|
33
|
+
const health = await get('/health');
|
|
34
|
+
console.log(chalk.green(` ✓ 状态: ${health.status}\n`));
|
|
35
|
+
|
|
36
|
+
// 2. 启动工作流
|
|
37
|
+
console.log(chalk.cyan('2. 启动工作流...'));
|
|
38
|
+
const workflowId = 'test-' + Date.now();
|
|
39
|
+
const startResult = await post('/workflow/start', {
|
|
40
|
+
workflowId: workflowId,
|
|
41
|
+
context: {}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
if (!startResult.success) {
|
|
45
|
+
throw new Error('工作流启动失败');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
console.log(chalk.green(` ✓ 工作流: ${startResult.workflowName}`));
|
|
49
|
+
console.log(chalk.gray(` - ID: ${startResult.workflowId}`));
|
|
50
|
+
console.log(chalk.gray(` - 总步骤: ${startResult.totalSteps}`));
|
|
51
|
+
console.log(chalk.gray(` - 第一步: ${startResult.firstStep.stepId}`));
|
|
52
|
+
console.log(chalk.gray(` - 智能体: ${startResult.firstStep.agent || '未指定'}\n`));
|
|
53
|
+
|
|
54
|
+
// 3. 查询状态
|
|
55
|
+
console.log(chalk.cyan('3. 查询初始状态...'));
|
|
56
|
+
const status1 = await get('/workflow/status');
|
|
57
|
+
console.log(chalk.green(` ✓ 状态: ${status1.status}`));
|
|
58
|
+
console.log(chalk.gray(` - 当前步骤: ${status1.currentStep}`));
|
|
59
|
+
console.log(chalk.gray(` - 进度: ${status1.currentStepIndex}/${status1.totalSteps}\n`));
|
|
60
|
+
|
|
61
|
+
// 4. 完成多个步骤
|
|
62
|
+
console.log(chalk.cyan('4. 模拟完成所有步骤...\n'));
|
|
63
|
+
|
|
64
|
+
let hasNext = true;
|
|
65
|
+
let completedCount = 0;
|
|
66
|
+
|
|
67
|
+
while (hasNext) {
|
|
68
|
+
const currentStatus = await get('/workflow/status');
|
|
69
|
+
const currentStepId = currentStatus.currentStep;
|
|
70
|
+
|
|
71
|
+
console.log(chalk.yellow(` 步骤 ${completedCount + 1}: ${currentStepId}`));
|
|
72
|
+
|
|
73
|
+
const completeResult = await post('/workflow/step-complete', {
|
|
74
|
+
workflowId: workflowId,
|
|
75
|
+
stepId: currentStepId,
|
|
76
|
+
status: 'completed',
|
|
77
|
+
outputs: [], // 简化:不检查输出
|
|
78
|
+
duration: 5000
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
completedCount++;
|
|
82
|
+
|
|
83
|
+
if (completeResult.hasNextStep) {
|
|
84
|
+
console.log(chalk.green(` ✓ 完成 (进度: ${completeResult.progress.percentComplete}%)`));
|
|
85
|
+
hasNext = true;
|
|
86
|
+
} else {
|
|
87
|
+
console.log(chalk.green.bold(` ✓ 完成 - 工作流全部完成!\n`));
|
|
88
|
+
hasNext = false;
|
|
89
|
+
|
|
90
|
+
// 显示摘要
|
|
91
|
+
const summary = completeResult.summary;
|
|
92
|
+
console.log(chalk.cyan('5. 工作流完成摘要:'));
|
|
93
|
+
console.log(chalk.green(` ✓ 工作流名称: ${summary.workflowName}`));
|
|
94
|
+
console.log(chalk.gray(` - 总耗时: ${summary.totalDurationFormatted}`));
|
|
95
|
+
console.log(chalk.gray(` - 完成步骤: ${summary.totalSteps}`));
|
|
96
|
+
console.log(chalk.gray(` - 错误数: ${summary.errors}`));
|
|
97
|
+
|
|
98
|
+
if (summary.outputs.length > 0) {
|
|
99
|
+
console.log(chalk.gray(` - 输出文件: ${summary.outputs.length}个`));
|
|
100
|
+
summary.outputs.slice(0, 5).forEach(file => {
|
|
101
|
+
console.log(chalk.gray(` • ${file}`));
|
|
102
|
+
});
|
|
103
|
+
if (summary.outputs.length > 5) {
|
|
104
|
+
console.log(chalk.gray(` ... 还有 ${summary.outputs.length - 5} 个文件`));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
console.log(chalk.green.bold('\n╔════════════════════════════════════════╗'));
|
|
111
|
+
console.log(chalk.green.bold('║ 测试全部通过! ✓ ║'));
|
|
112
|
+
console.log(chalk.green.bold('╚════════════════════════════════════════╝\n'));
|
|
113
|
+
|
|
114
|
+
} catch (error) {
|
|
115
|
+
console.error(chalk.red.bold('\n❌ 测试失败'));
|
|
116
|
+
console.error(chalk.red(`错误: ${error.message}\n`));
|
|
117
|
+
|
|
118
|
+
if (error.code === 'ECONNREFUSED') {
|
|
119
|
+
console.log(chalk.yellow('提示: 请确保协调器正在运行'));
|
|
120
|
+
console.log(chalk.gray('启动命令: node src/index.js start automated-requirements-analysis\n'));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
main();
|