@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,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "xiaoma-workflow-coordinator",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "工作流协调器 - 驱动XiaoMa-CLI工作流自动执行",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"xiaoma-coordinator": "src/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node src/index.js",
|
|
11
|
+
"dev": "nodemon src/index.js",
|
|
12
|
+
"test": "jest",
|
|
13
|
+
"test:watch": "jest --watch"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"workflow",
|
|
17
|
+
"automation",
|
|
18
|
+
"coordinator"
|
|
19
|
+
],
|
|
20
|
+
"author": "XiaoMa Team",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"chalk": "^4.1.2",
|
|
24
|
+
"cors": "^2.8.5",
|
|
25
|
+
"dotenv": "^16.3.1",
|
|
26
|
+
"express": "^4.18.2",
|
|
27
|
+
"fs-extra": "^11.1.1",
|
|
28
|
+
"js-yaml": "^4.1.0",
|
|
29
|
+
"node-fetch": "^2.7.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"jest": "^29.7.0",
|
|
33
|
+
"nodemon": "^3.0.1"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const cors = require('cors');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
|
|
5
|
+
class CoordinatorServer {
|
|
6
|
+
constructor(controller) {
|
|
7
|
+
this.app = express();
|
|
8
|
+
this.controller = controller;
|
|
9
|
+
this.setupMiddleware();
|
|
10
|
+
this.setupRoutes();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
setupMiddleware() {
|
|
14
|
+
this.app.use(express.json({ limit: '50mb' }));
|
|
15
|
+
this.app.use(cors());
|
|
16
|
+
|
|
17
|
+
// 请求日志
|
|
18
|
+
this.app.use((req, res, next) => {
|
|
19
|
+
const timestamp = new Date().toISOString();
|
|
20
|
+
console.log(chalk.gray(`[${timestamp}] ${req.method} ${req.path}`));
|
|
21
|
+
next();
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
setupRoutes() {
|
|
26
|
+
// API: 启动工作流
|
|
27
|
+
this.app.post('/workflow/start', async (req, res) => {
|
|
28
|
+
try {
|
|
29
|
+
const { workflowId, context } = req.body;
|
|
30
|
+
|
|
31
|
+
console.log(chalk.cyan(`\n📥 收到启动请求: ${workflowId}`));
|
|
32
|
+
|
|
33
|
+
const result = await this.controller.startWorkflow(workflowId, context);
|
|
34
|
+
|
|
35
|
+
console.log(chalk.green(`✅ 返回第一步指令: ${result.firstStep.stepId}`));
|
|
36
|
+
console.log(chalk.gray(` 智能体: ${result.firstStep.agent}`));
|
|
37
|
+
|
|
38
|
+
res.json(result);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error(chalk.red(`❌ 启动失败: ${error.message}`));
|
|
41
|
+
res.status(500).json({
|
|
42
|
+
success: false,
|
|
43
|
+
error: error.message
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// API: 步骤完成回调
|
|
49
|
+
this.app.post('/workflow/step-complete', async (req, res) => {
|
|
50
|
+
try {
|
|
51
|
+
const { workflowId, stepId, status, outputs, duration } = req.body;
|
|
52
|
+
|
|
53
|
+
console.log(chalk.cyan(`\n📥 步骤完成: ${stepId}`));
|
|
54
|
+
console.log(chalk.gray(` 耗时: ${Math.round(duration / 1000)}秒`));
|
|
55
|
+
console.log(chalk.gray(` 输出: ${outputs.length}个文件`));
|
|
56
|
+
|
|
57
|
+
outputs.forEach(output => {
|
|
58
|
+
const icon = output.exists ? '✓' : '✗';
|
|
59
|
+
const color = output.exists ? chalk.green : chalk.red;
|
|
60
|
+
console.log(color(` ${icon} ${output.file} (${output.size} bytes)`));
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const result = await this.controller.onStepComplete({
|
|
64
|
+
workflowId,
|
|
65
|
+
stepId,
|
|
66
|
+
status,
|
|
67
|
+
outputs,
|
|
68
|
+
duration
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
if (result.hasNextStep) {
|
|
72
|
+
console.log(chalk.green(`✅ 返回下一步指令: ${result.nextStep.stepId}`));
|
|
73
|
+
console.log(chalk.yellow(` 进度: ${result.progress.percentComplete}% (${result.progress.currentStepIndex}/${result.progress.totalSteps})`));
|
|
74
|
+
} else {
|
|
75
|
+
console.log(chalk.green.bold(`\n🎉 工作流执行完成!\n`));
|
|
76
|
+
if (result.summary) {
|
|
77
|
+
console.log(chalk.cyan(`总耗时: ${result.summary.totalDurationFormatted}`));
|
|
78
|
+
console.log(chalk.cyan(`完成步骤: ${result.summary.totalSteps}`));
|
|
79
|
+
console.log(chalk.cyan(`生成文档: ${result.summary.outputs.length}个`));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
res.json(result);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
console.error(chalk.red(`❌ 处理失败: ${error.message}`));
|
|
86
|
+
res.status(500).json({
|
|
87
|
+
success: false,
|
|
88
|
+
error: error.message
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// API: 步骤失败回调
|
|
94
|
+
this.app.post('/workflow/step-failed', async (req, res) => {
|
|
95
|
+
try {
|
|
96
|
+
const { workflowId, stepId, error, errorDetails } = req.body;
|
|
97
|
+
|
|
98
|
+
console.log(chalk.red(`\n❌ 步骤失败: ${stepId}`));
|
|
99
|
+
console.log(chalk.red(` 错误: ${error}`));
|
|
100
|
+
|
|
101
|
+
const result = await this.controller.onStepFailed({
|
|
102
|
+
workflowId,
|
|
103
|
+
stepId,
|
|
104
|
+
error,
|
|
105
|
+
errorDetails
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
if (result.action === 'retry') {
|
|
109
|
+
console.log(chalk.yellow(`⏳ 准备重试: ${result.retryCount}/${result.maxRetries}`));
|
|
110
|
+
} else if (result.action === 'abort') {
|
|
111
|
+
console.log(chalk.red.bold(`🚨 工作流中止`));
|
|
112
|
+
console.log(chalk.red(` ${result.escalation}`));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
res.json(result);
|
|
116
|
+
} catch (error) {
|
|
117
|
+
console.error(chalk.red(`❌ 处理失败: ${error.message}`));
|
|
118
|
+
res.status(500).json({
|
|
119
|
+
success: false,
|
|
120
|
+
error: error.message
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// API: 查询状态
|
|
126
|
+
this.app.get('/workflow/status', (req, res) => {
|
|
127
|
+
try {
|
|
128
|
+
const status = this.controller.getStatus();
|
|
129
|
+
res.json(status);
|
|
130
|
+
} catch (error) {
|
|
131
|
+
res.status(500).json({
|
|
132
|
+
success: false,
|
|
133
|
+
error: error.message
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// API: 中止工作流
|
|
139
|
+
this.app.post('/workflow/abort', async (req, res) => {
|
|
140
|
+
try {
|
|
141
|
+
const { workflowId, reason } = req.body;
|
|
142
|
+
|
|
143
|
+
console.log(chalk.yellow(`\n⏸️ 中止工作流: ${reason}`));
|
|
144
|
+
|
|
145
|
+
await this.controller.abort(workflowId, reason);
|
|
146
|
+
|
|
147
|
+
res.json({
|
|
148
|
+
success: true,
|
|
149
|
+
message: '工作流已中止,状态已保存'
|
|
150
|
+
});
|
|
151
|
+
} catch (error) {
|
|
152
|
+
res.status(500).json({
|
|
153
|
+
success: false,
|
|
154
|
+
error: error.message
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// 健康检查
|
|
160
|
+
this.app.get('/health', (req, res) => {
|
|
161
|
+
res.json({
|
|
162
|
+
status: 'ok',
|
|
163
|
+
timestamp: new Date().toISOString()
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// 根路径
|
|
168
|
+
this.app.get('/', (req, res) => {
|
|
169
|
+
res.json({
|
|
170
|
+
name: 'XiaoMa Workflow Coordinator',
|
|
171
|
+
version: '1.0.0',
|
|
172
|
+
endpoints: [
|
|
173
|
+
'POST /workflow/start',
|
|
174
|
+
'POST /workflow/step-complete',
|
|
175
|
+
'POST /workflow/step-failed',
|
|
176
|
+
'GET /workflow/status',
|
|
177
|
+
'POST /workflow/abort',
|
|
178
|
+
'GET /health'
|
|
179
|
+
]
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async start(port) {
|
|
185
|
+
return new Promise((resolve, reject) => {
|
|
186
|
+
this.server = this.app.listen(port, (err) => {
|
|
187
|
+
if (err) {
|
|
188
|
+
reject(err);
|
|
189
|
+
} else {
|
|
190
|
+
resolve();
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async stop() {
|
|
197
|
+
if (this.server) {
|
|
198
|
+
return new Promise((resolve) => {
|
|
199
|
+
this.server.close(() => {
|
|
200
|
+
resolve();
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
module.exports = CoordinatorServer;
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
const StateManager = require('../utils/state-manager');
|
|
2
|
+
const Validator = require('../utils/validator');
|
|
3
|
+
|
|
4
|
+
class WorkflowController {
|
|
5
|
+
constructor(executionPlan) {
|
|
6
|
+
this.executionPlan = executionPlan;
|
|
7
|
+
this.stateManager = new StateManager();
|
|
8
|
+
this.validator = new Validator();
|
|
9
|
+
this.state = null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 启动工作流
|
|
14
|
+
*/
|
|
15
|
+
async startWorkflow(workflowId, context = {}) {
|
|
16
|
+
// 初始化状态
|
|
17
|
+
this.state = {
|
|
18
|
+
workflowId: workflowId,
|
|
19
|
+
workflowName: this.executionPlan.metadata.name,
|
|
20
|
+
status: 'in_progress',
|
|
21
|
+
startTime: new Date().toISOString(),
|
|
22
|
+
currentStepIndex: 0,
|
|
23
|
+
completedSteps: [],
|
|
24
|
+
context: context,
|
|
25
|
+
errors: [],
|
|
26
|
+
retries: {}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// 保存状态
|
|
30
|
+
await this.stateManager.save(this.state);
|
|
31
|
+
|
|
32
|
+
// 获取第一步
|
|
33
|
+
const firstStepDef = this.executionPlan.steps[0];
|
|
34
|
+
const firstStep = this.buildStepInstruction(firstStepDef);
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
success: true,
|
|
38
|
+
workflowId: workflowId,
|
|
39
|
+
workflowName: this.executionPlan.metadata.name,
|
|
40
|
+
totalSteps: this.executionPlan.steps.length,
|
|
41
|
+
firstStep: firstStep
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 步骤完成回调
|
|
47
|
+
*/
|
|
48
|
+
async onStepComplete({ workflowId, stepId, status, outputs, duration }) {
|
|
49
|
+
// 1. 验证步骤输出
|
|
50
|
+
const stepDef = this.executionPlan.steps[this.state.currentStepIndex];
|
|
51
|
+
const validationResult = await this.validator.validateOutputs(stepDef, outputs);
|
|
52
|
+
|
|
53
|
+
if (!validationResult.passed) {
|
|
54
|
+
return {
|
|
55
|
+
success: false,
|
|
56
|
+
action: 'fix_quality',
|
|
57
|
+
issues: validationResult.issues,
|
|
58
|
+
message: '步骤输出质量验证失败,请修复问题后重试'
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 2. 记录完成步骤
|
|
63
|
+
this.state.completedSteps.push({
|
|
64
|
+
stepId: stepId,
|
|
65
|
+
agent: stepDef.agent,
|
|
66
|
+
status: status,
|
|
67
|
+
outputs: outputs,
|
|
68
|
+
duration: duration,
|
|
69
|
+
completedAt: new Date().toISOString()
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// 3. 移动到下一步
|
|
73
|
+
this.state.currentStepIndex++;
|
|
74
|
+
|
|
75
|
+
// 4. 保存状态
|
|
76
|
+
await this.stateManager.save(this.state);
|
|
77
|
+
|
|
78
|
+
// 5. 检查是否还有下一步
|
|
79
|
+
if (this.state.currentStepIndex >= this.executionPlan.steps.length) {
|
|
80
|
+
// 工作流完成
|
|
81
|
+
this.state.status = 'completed';
|
|
82
|
+
this.state.endTime = new Date().toISOString();
|
|
83
|
+
await this.stateManager.save(this.state);
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
success: true,
|
|
87
|
+
hasNextStep: false,
|
|
88
|
+
message: '🎉 工作流执行完成!',
|
|
89
|
+
summary: this.generateSummary()
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// 6. 获取下一步指令
|
|
94
|
+
const nextStepDef = this.executionPlan.steps[this.state.currentStepIndex];
|
|
95
|
+
const nextStep = this.buildStepInstruction(nextStepDef);
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
success: true,
|
|
99
|
+
hasNextStep: true,
|
|
100
|
+
nextStep: nextStep,
|
|
101
|
+
progress: {
|
|
102
|
+
currentStepIndex: this.state.currentStepIndex + 1,
|
|
103
|
+
totalSteps: this.executionPlan.steps.length,
|
|
104
|
+
percentComplete: Math.round(((this.state.currentStepIndex + 1) / this.executionPlan.steps.length) * 100)
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* 步骤失败回调
|
|
111
|
+
*/
|
|
112
|
+
async onStepFailed({ workflowId, stepId, error, errorDetails }) {
|
|
113
|
+
const stepDef = this.executionPlan.steps[this.state.currentStepIndex];
|
|
114
|
+
|
|
115
|
+
// 记录错误
|
|
116
|
+
this.state.errors.push({
|
|
117
|
+
stepId: stepId,
|
|
118
|
+
error: error,
|
|
119
|
+
errorDetails: errorDetails,
|
|
120
|
+
timestamp: new Date().toISOString()
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// 检查是否可以重试
|
|
124
|
+
const maxRetries = stepDef.onFailure?.max_retries || 3;
|
|
125
|
+
const currentRetries = this.state.retries[stepId] || 0;
|
|
126
|
+
|
|
127
|
+
if (currentRetries < maxRetries) {
|
|
128
|
+
// 可以重试
|
|
129
|
+
this.state.retries[stepId] = currentRetries + 1;
|
|
130
|
+
await this.stateManager.save(this.state);
|
|
131
|
+
|
|
132
|
+
const retryStep = this.buildStepInstruction(stepDef);
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
success: true,
|
|
136
|
+
action: 'retry',
|
|
137
|
+
retryCount: currentRetries + 1,
|
|
138
|
+
maxRetries: maxRetries,
|
|
139
|
+
retryStep: retryStep,
|
|
140
|
+
message: `将进行第 ${currentRetries + 1} 次重试`
|
|
141
|
+
};
|
|
142
|
+
} else {
|
|
143
|
+
// 达到最大重试次数,中止工作流
|
|
144
|
+
this.state.status = 'failed';
|
|
145
|
+
this.state.endTime = new Date().toISOString();
|
|
146
|
+
await this.stateManager.save(this.state);
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
success: false,
|
|
150
|
+
action: 'abort',
|
|
151
|
+
message: `步骤 "${stepId}" 执行失败,已达最大重试次数`,
|
|
152
|
+
escalation: stepDef.onFailure?.escalation || '需要人工介入'
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* 构建步骤执行指令
|
|
159
|
+
*/
|
|
160
|
+
buildStepInstruction(stepDef) {
|
|
161
|
+
// 从 detailed_steps 中提取命令和提示词
|
|
162
|
+
const detailedSteps = stepDef.detailedSteps || [];
|
|
163
|
+
|
|
164
|
+
// 找到第一个命令步骤(非智能体切换)
|
|
165
|
+
const commandStep = detailedSteps.find(ds =>
|
|
166
|
+
ds.command && ds.command.startsWith('*')
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
// 找到智能体切换步骤
|
|
170
|
+
const agentSwitchStep = detailedSteps.find(ds =>
|
|
171
|
+
ds.command && ds.command.startsWith('/')
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
stepId: stepDef.id,
|
|
176
|
+
stepDescription: stepDef.description,
|
|
177
|
+
agent: stepDef.agent,
|
|
178
|
+
switchCommand: agentSwitchStep?.command || `/agent ${stepDef.agent}`,
|
|
179
|
+
executeCommand: commandStep?.command || '',
|
|
180
|
+
prompt: commandStep?.prompt_template || '',
|
|
181
|
+
inputFiles: stepDef.requires || [],
|
|
182
|
+
expectedOutputs: stepDef.outputs || [],
|
|
183
|
+
detailedSteps: detailedSteps,
|
|
184
|
+
estimatedDuration: stepDef.duration
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* 生成完成摘要
|
|
190
|
+
*/
|
|
191
|
+
generateSummary() {
|
|
192
|
+
const totalDuration = this.state.completedSteps.reduce((sum, step) => sum + (step.duration || 0), 0);
|
|
193
|
+
const allOutputs = this.state.completedSteps.flatMap(step => step.outputs || []);
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
workflowId: this.state.workflowId,
|
|
197
|
+
workflowName: this.state.workflowName,
|
|
198
|
+
status: this.state.status,
|
|
199
|
+
startTime: this.state.startTime,
|
|
200
|
+
endTime: this.state.endTime,
|
|
201
|
+
totalDuration: totalDuration,
|
|
202
|
+
totalDurationFormatted: this.formatDuration(totalDuration),
|
|
203
|
+
totalSteps: this.state.completedSteps.length,
|
|
204
|
+
outputs: allOutputs.map(o => o.file),
|
|
205
|
+
errors: this.state.errors.length
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* 格式化时长
|
|
211
|
+
*/
|
|
212
|
+
formatDuration(ms) {
|
|
213
|
+
const seconds = Math.floor(ms / 1000);
|
|
214
|
+
const minutes = Math.floor(seconds / 60);
|
|
215
|
+
const hours = Math.floor(minutes / 60);
|
|
216
|
+
|
|
217
|
+
if (hours > 0) {
|
|
218
|
+
return `${hours}小时${minutes % 60}分钟`;
|
|
219
|
+
} else if (minutes > 0) {
|
|
220
|
+
return `${minutes}分钟${seconds % 60}秒`;
|
|
221
|
+
} else {
|
|
222
|
+
return `${seconds}秒`;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* 获取当前状态
|
|
228
|
+
*/
|
|
229
|
+
getStatus() {
|
|
230
|
+
if (!this.state) {
|
|
231
|
+
return {
|
|
232
|
+
status: 'not_started',
|
|
233
|
+
message: '协调器已启动,等待工作流开始'
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
workflowId: this.state.workflowId,
|
|
239
|
+
workflowName: this.state.workflowName,
|
|
240
|
+
status: this.state.status,
|
|
241
|
+
currentStepIndex: this.state.currentStepIndex + 1,
|
|
242
|
+
totalSteps: this.executionPlan.steps.length,
|
|
243
|
+
completedSteps: this.state.completedSteps.map(s => s.stepId),
|
|
244
|
+
currentStep: this.executionPlan.steps[this.state.currentStepIndex]?.id,
|
|
245
|
+
errors: this.state.errors.length,
|
|
246
|
+
startTime: this.state.startTime
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* 中止工作流
|
|
252
|
+
*/
|
|
253
|
+
async abort(workflowId, reason) {
|
|
254
|
+
if (this.state) {
|
|
255
|
+
this.state.status = 'aborted';
|
|
256
|
+
this.state.abortReason = reason;
|
|
257
|
+
this.state.endTime = new Date().toISOString();
|
|
258
|
+
await this.stateManager.save(this.state);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
module.exports = WorkflowController;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
const WorkflowParser = require('./parser/workflow-parser');
|
|
6
|
+
const WorkflowController = require('./controller/workflow-controller');
|
|
7
|
+
const CoordinatorServer = require('./api/server');
|
|
8
|
+
|
|
9
|
+
require('dotenv').config();
|
|
10
|
+
|
|
11
|
+
async function main() {
|
|
12
|
+
const args = process.argv.slice(2);
|
|
13
|
+
const command = args[0];
|
|
14
|
+
const workflowName = args[1];
|
|
15
|
+
|
|
16
|
+
console.log(chalk.blue.bold('\n╔════════════════════════════════════════╗'));
|
|
17
|
+
console.log(chalk.blue.bold('║ XiaoMa 工作流协调器 ║'));
|
|
18
|
+
console.log(chalk.blue.bold('╚════════════════════════════════════════╝\n'));
|
|
19
|
+
|
|
20
|
+
if (command === 'start' && workflowName) {
|
|
21
|
+
try {
|
|
22
|
+
// 1. 解析工作流
|
|
23
|
+
console.log(chalk.cyan(`📋 解析工作流: ${workflowName}`));
|
|
24
|
+
const parser = new WorkflowParser();
|
|
25
|
+
const executionPlan = await parser.parse(workflowName);
|
|
26
|
+
|
|
27
|
+
console.log(chalk.green(`✅ 工作流解析完成`));
|
|
28
|
+
console.log(chalk.gray(` 名称: ${executionPlan.metadata.name}`));
|
|
29
|
+
console.log(chalk.gray(` 步骤数: ${executionPlan.steps.length}`));
|
|
30
|
+
console.log(chalk.gray(` 类型: ${executionPlan.metadata.type}`));
|
|
31
|
+
|
|
32
|
+
// 2. 创建控制器
|
|
33
|
+
const controller = new WorkflowController(executionPlan);
|
|
34
|
+
|
|
35
|
+
// 3. 启动HTTP服务
|
|
36
|
+
console.log(chalk.cyan(`\n🌐 启动HTTP服务...`));
|
|
37
|
+
const server = new CoordinatorServer(controller);
|
|
38
|
+
const port = process.env.COORDINATOR_PORT || 3001;
|
|
39
|
+
|
|
40
|
+
await server.start(port);
|
|
41
|
+
|
|
42
|
+
console.log(chalk.green.bold(`✅ HTTP服务已启动: http://localhost:${port}\n`));
|
|
43
|
+
|
|
44
|
+
console.log(chalk.yellow(`⏳ 等待Claude Code连接...\n`));
|
|
45
|
+
console.log(chalk.gray(`提示: 在Claude Code中执行以下命令启动工作流:\n`));
|
|
46
|
+
console.log(chalk.cyan(` /workflow-helper`));
|
|
47
|
+
console.log(chalk.cyan(` *start-workflow ${workflowName}\n`));
|
|
48
|
+
|
|
49
|
+
console.log(chalk.gray(`按 Ctrl+C 停止协调器\n`));
|
|
50
|
+
|
|
51
|
+
// 处理退出信号
|
|
52
|
+
process.on('SIGINT', async () => {
|
|
53
|
+
console.log(chalk.yellow(`\n\n⏸️ 收到停止信号,正在关闭...`));
|
|
54
|
+
await server.stop();
|
|
55
|
+
console.log(chalk.green(`✅ 协调器已停止\n`));
|
|
56
|
+
process.exit(0);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.error(chalk.red.bold(`\n❌ 启动失败\n`));
|
|
61
|
+
console.error(chalk.red(`错误: ${error.message}\n`));
|
|
62
|
+
|
|
63
|
+
if (error.message.includes('工作流文件未找到')) {
|
|
64
|
+
console.log(chalk.yellow(`提示: 请确认工作流文件存在于 xiaoma-core/workflows/ 目录下\n`));
|
|
65
|
+
console.log(chalk.gray(`可用的工作流文件应该是:\n`));
|
|
66
|
+
console.log(chalk.gray(` - requirements-analysis.yaml`));
|
|
67
|
+
console.log(chalk.gray(` - story-development.yaml`));
|
|
68
|
+
console.log(chalk.gray(` - 或其他 .yaml 文件\n`));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
} else if (command === 'test-api') {
|
|
75
|
+
// 测试API端点
|
|
76
|
+
console.log(chalk.cyan(`🧪 API端点测试模式\n`));
|
|
77
|
+
console.log(chalk.gray(`启动简单的测试服务器...\n`));
|
|
78
|
+
|
|
79
|
+
const express = require('express');
|
|
80
|
+
const app = express();
|
|
81
|
+
app.use(express.json());
|
|
82
|
+
|
|
83
|
+
app.post('/workflow/start', (req, res) => {
|
|
84
|
+
console.log(chalk.green(`✓ 收到 /workflow/start 请求`));
|
|
85
|
+
console.log(chalk.gray(` 请求体: ${JSON.stringify(req.body, null, 2)}`));
|
|
86
|
+
res.json({ success: true, message: 'Test OK' });
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const port = process.env.COORDINATOR_PORT || 3001;
|
|
90
|
+
app.listen(port, () => {
|
|
91
|
+
console.log(chalk.green(`✅ 测试服务器运行在 http://localhost:${port}\n`));
|
|
92
|
+
console.log(chalk.gray(`测试命令:`));
|
|
93
|
+
console.log(chalk.cyan(` curl -X POST http://localhost:${port}/workflow/start -H "Content-Type: application/json" -d '{"workflowId":"test"}'\n`));
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
} else {
|
|
97
|
+
// 显示帮助
|
|
98
|
+
console.log(chalk.gray('用法:\n'));
|
|
99
|
+
console.log(chalk.white(' xiaoma-coordinator start <workflow-name>'));
|
|
100
|
+
console.log(chalk.gray(' 启动工作流协调器\n'));
|
|
101
|
+
console.log(chalk.white(' xiaoma-coordinator test-api'));
|
|
102
|
+
console.log(chalk.gray(' 启动API测试服务器\n'));
|
|
103
|
+
console.log(chalk.gray('示例:\n'));
|
|
104
|
+
console.log(chalk.cyan(' xiaoma-coordinator start requirements-analysis'));
|
|
105
|
+
console.log(chalk.cyan(' xiaoma-coordinator start story-development\n'));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
main().catch(error => {
|
|
110
|
+
console.error(chalk.red(`\n❌ 未处理的错误: ${error.message}\n`));
|
|
111
|
+
console.error(error.stack);
|
|
112
|
+
process.exit(1);
|
|
113
|
+
});
|