@yeaft/webchat-agent 0.1.248 → 0.1.249
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/connection/message-router.js +0 -26
- package/index.js +0 -2
- package/package.json +1 -1
- package/conductor/actor.js +0 -478
- package/conductor/claudemd-generator.js +0 -194
- package/conductor/conductor-claude.js +0 -480
- package/conductor/index.js +0 -65
- package/conductor/orchestrator-flows.js +0 -498
- package/conductor/orchestrator.js +0 -888
- package/conductor/persistence.js +0 -297
- package/conductor/personas/dev.js +0 -97
- package/conductor/personas/index.js +0 -77
- package/conductor/personas/trading.js +0 -92
- package/conductor/personas/video.js +0 -75
- package/conductor/personas/writing.js +0 -82
- package/conductor/semaphore.js +0 -73
- package/conductor/session.js +0 -210
- package/conductor/specialties/common.js +0 -350
- package/conductor/specialties/index.js +0 -135
- package/conductor/specialties/trading.js +0 -165
- package/conductor/specialties/video.js +0 -175
- package/conductor/specialties/writing.js +0 -120
- package/conductor/task.js +0 -333
- package/conductor/ui-messages.js +0 -170
- package/conductor/worktree.js +0 -182
- package/conductor.js +0 -35
|
@@ -1,888 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Conductor Orchestrator — JS 状态机核心
|
|
3
|
-
*
|
|
4
|
-
* 不是 Claude 实例。是纯 JS 状态机。
|
|
5
|
-
* 每个 Task 一个 Orchestrator 实例,驱动 TRIAGE → EXECUTION → ACCEPTANCE 全流程。
|
|
6
|
-
*
|
|
7
|
-
* 核心职责:
|
|
8
|
-
* 1. 状态流转 — Phase 之间的转换
|
|
9
|
-
* 2. TRIAGE 决策 — TRIVIAL / SMALL / COMPLEX
|
|
10
|
-
* 3. Actor 调度 — 通过 actorManager.createActor() 创建/释放
|
|
11
|
-
* 4. Quality Gate — review pass/fail → 重试 / 升级 discussion
|
|
12
|
-
* 5. 迭代控制 — maxIterations = 3
|
|
13
|
-
*
|
|
14
|
-
* 接口约定:
|
|
15
|
-
* actorManager.createActor(persona, specialty, taskContext) → Promise<ActorResult>
|
|
16
|
-
* actorManager.releaseActor(instanceId) → void
|
|
17
|
-
* statusCallback(task) → void — 状态变更通知
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import {
|
|
21
|
-
Phase, Complexity,
|
|
22
|
-
createTask, createActorInstance, createExecutionPlan,
|
|
23
|
-
transitionPhase, addActiveActor, completeActor, failActor,
|
|
24
|
-
recordStepFail, getStepFailCount, serializeTask
|
|
25
|
-
} from './task.js';
|
|
26
|
-
|
|
27
|
-
import {
|
|
28
|
-
getFlow, getThinkingMode,
|
|
29
|
-
getDiscussionActors, getPlanningActors, getPlanningLead,
|
|
30
|
-
getQualityGateConfig, getAcceptanceVerifier,
|
|
31
|
-
needsQualityGate, getPostPlanningDiscussion, getExecutionStages,
|
|
32
|
-
isQualityGateBeforeExecution
|
|
33
|
-
} from './orchestrator-flows.js';
|
|
34
|
-
|
|
35
|
-
// =====================================================================
|
|
36
|
-
// Orchestrator Class
|
|
37
|
-
// =====================================================================
|
|
38
|
-
|
|
39
|
-
export class Orchestrator {
|
|
40
|
-
/**
|
|
41
|
-
* @param {object} params
|
|
42
|
-
* @param {object} params.actorManager — { createActor, releaseActor }
|
|
43
|
-
* @param {Function} params.onStatusChange — 状态变更回调
|
|
44
|
-
* @param {Function} [params.onLog] — 日志回调
|
|
45
|
-
*/
|
|
46
|
-
constructor({ actorManager, onStatusChange, onLog }) {
|
|
47
|
-
if (!actorManager?.createActor) {
|
|
48
|
-
throw new Error('actorManager.createActor is required');
|
|
49
|
-
}
|
|
50
|
-
this.actorManager = actorManager;
|
|
51
|
-
this.onStatusChange = onStatusChange || (() => {});
|
|
52
|
-
this.log = onLog || ((...args) => console.log('[Orchestrator]', ...args));
|
|
53
|
-
|
|
54
|
-
/** @type {Map<string, Task>} */
|
|
55
|
-
this.tasks = new Map();
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// ===================================================================
|
|
59
|
-
// Public API
|
|
60
|
-
// ===================================================================
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* 创建并启动新 Task
|
|
64
|
-
*
|
|
65
|
-
* @param {object} params
|
|
66
|
-
* @param {string} params.title
|
|
67
|
-
* @param {string} params.description
|
|
68
|
-
* @param {string} params.scenario — 'dev' | 'writing' | 'trading' | 'video'
|
|
69
|
-
* @param {string} params.workDir
|
|
70
|
-
* @returns {Task}
|
|
71
|
-
*/
|
|
72
|
-
async startTask({ title, description, scenario, workDir }) {
|
|
73
|
-
const flow = getFlow(scenario); // 验证 scenario 合法性
|
|
74
|
-
const task = createTask({ title, description, scenario, workDir });
|
|
75
|
-
this.tasks.set(task.taskId, task);
|
|
76
|
-
this._notify(task);
|
|
77
|
-
|
|
78
|
-
this.log(`Task created: ${task.taskId} "${title}" [${scenario}]`);
|
|
79
|
-
|
|
80
|
-
// 异步启动状态机,不阻塞调用者
|
|
81
|
-
this._runStateMachine(task, flow).catch(err => {
|
|
82
|
-
this.log(`Task ${task.taskId} state machine error:`, err);
|
|
83
|
-
task.error = err.message || String(err);
|
|
84
|
-
if (task.phase !== Phase.DONE && task.phase !== Phase.FAILED) {
|
|
85
|
-
transitionPhase(task, Phase.FAILED);
|
|
86
|
-
}
|
|
87
|
-
this._notify(task);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
return task;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* 获取 Task 状态
|
|
95
|
-
*/
|
|
96
|
-
getTask(taskId) {
|
|
97
|
-
return this.tasks.get(taskId) || null;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* 获取所有 Task
|
|
102
|
-
*/
|
|
103
|
-
getAllTasks() {
|
|
104
|
-
return [...this.tasks.values()];
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* 获取 Task 序列化数据(用于前端推送)
|
|
109
|
-
*/
|
|
110
|
-
getTaskStatus(taskId) {
|
|
111
|
-
const task = this.tasks.get(taskId);
|
|
112
|
-
return task ? serializeTask(task) : null;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* 用户消息注入(Task 对话页直接交互时)
|
|
117
|
-
* TODO: 后续实现用户消息处理
|
|
118
|
-
*/
|
|
119
|
-
async handleUserMessage(taskId, message) {
|
|
120
|
-
const task = this.tasks.get(taskId);
|
|
121
|
-
if (!task) return;
|
|
122
|
-
this.log(`User message for ${taskId}: ${message.substring(0, 100)}...`);
|
|
123
|
-
// 用户消息处理逻辑由 Conductor 引擎层实现
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// ===================================================================
|
|
127
|
-
// State Machine — 主循环
|
|
128
|
-
// ===================================================================
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* 状态机主循环:Phase 之间的驱动
|
|
132
|
-
*/
|
|
133
|
-
async _runStateMachine(task, flow) {
|
|
134
|
-
while (task.phase !== Phase.DONE && task.phase !== Phase.FAILED) {
|
|
135
|
-
this.log(`Task ${task.taskId} entering phase: ${task.phase}`);
|
|
136
|
-
|
|
137
|
-
switch (task.phase) {
|
|
138
|
-
case Phase.TRIAGE:
|
|
139
|
-
await this._phaseTriage(task, flow);
|
|
140
|
-
break;
|
|
141
|
-
case Phase.DISCUSSION:
|
|
142
|
-
await this._phaseDiscussion(task, flow);
|
|
143
|
-
break;
|
|
144
|
-
case Phase.PLANNING:
|
|
145
|
-
await this._phasePlanning(task, flow);
|
|
146
|
-
break;
|
|
147
|
-
case Phase.EXECUTION:
|
|
148
|
-
await this._phaseExecution(task, flow);
|
|
149
|
-
break;
|
|
150
|
-
case Phase.QUALITY_GATE:
|
|
151
|
-
await this._phaseQualityGate(task, flow);
|
|
152
|
-
break;
|
|
153
|
-
case Phase.ACCEPTANCE:
|
|
154
|
-
await this._phaseAcceptance(task, flow);
|
|
155
|
-
break;
|
|
156
|
-
default:
|
|
157
|
-
throw new Error(`Unhandled phase: ${task.phase}`);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
this.log(`Task ${task.taskId} finished: ${task.phase}`);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// ===================================================================
|
|
165
|
-
// Phase: TRIAGE
|
|
166
|
-
// ===================================================================
|
|
167
|
-
|
|
168
|
-
async _phaseTriage(task, flow) {
|
|
169
|
-
this.log(`TRIAGE: analyzing task "${task.title}"`);
|
|
170
|
-
|
|
171
|
-
// 使用 actorManager 创建一个 triage actor
|
|
172
|
-
// triage 本质上可以是一次简单的 LLM 判定
|
|
173
|
-
const triageResult = await this._runTriageActor(task, flow);
|
|
174
|
-
const complexity = triageResult?.complexity || Complexity.SMALL;
|
|
175
|
-
|
|
176
|
-
task.complexity = complexity;
|
|
177
|
-
this.log(`TRIAGE result: ${complexity}`);
|
|
178
|
-
|
|
179
|
-
switch (complexity) {
|
|
180
|
-
case Complexity.TRIVIAL:
|
|
181
|
-
// 跳过 discussion + planning, 直接 execution
|
|
182
|
-
transitionPhase(task, Phase.EXECUTION);
|
|
183
|
-
break;
|
|
184
|
-
case Complexity.SMALL:
|
|
185
|
-
// 跳过 discussion, 直接 planning
|
|
186
|
-
transitionPhase(task, Phase.PLANNING);
|
|
187
|
-
break;
|
|
188
|
-
case Complexity.COMPLEX:
|
|
189
|
-
// 进入 discussion
|
|
190
|
-
transitionPhase(task, Phase.DISCUSSION);
|
|
191
|
-
break;
|
|
192
|
-
default:
|
|
193
|
-
transitionPhase(task, Phase.PLANNING);
|
|
194
|
-
}
|
|
195
|
-
this._notify(task);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* 运行 TRIAGE actor — 分析任务复杂度
|
|
200
|
-
*/
|
|
201
|
-
async _runTriageActor(task, flow) {
|
|
202
|
-
const triageContext = {
|
|
203
|
-
taskId: task.taskId,
|
|
204
|
-
description: task.description,
|
|
205
|
-
scenario: task.scenario,
|
|
206
|
-
triageRules: flow.triageRules,
|
|
207
|
-
workDir: task.workDir,
|
|
208
|
-
instruction: `Analyze the following task and determine its complexity level.
|
|
209
|
-
Rules:
|
|
210
|
-
- TRIVIAL: ${flow.triageRules.trivial.join('; ')}
|
|
211
|
-
- SMALL: ${flow.triageRules.small.join('; ')}
|
|
212
|
-
- COMPLEX: ${flow.triageRules.complex.join('; ')}
|
|
213
|
-
|
|
214
|
-
Task: "${task.description}"
|
|
215
|
-
|
|
216
|
-
Respond with JSON: { "complexity": "trivial"|"small"|"complex", "reasoning": "..." }`
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
try {
|
|
220
|
-
const result = await this.actorManager.createActor(
|
|
221
|
-
{ id: '_triage', name: 'Triage' },
|
|
222
|
-
'planning',
|
|
223
|
-
triageContext
|
|
224
|
-
);
|
|
225
|
-
return this._parseTriageResult(result);
|
|
226
|
-
} catch (err) {
|
|
227
|
-
this.log(`TRIAGE actor failed: ${err.message}, defaulting to SMALL`);
|
|
228
|
-
return { complexity: Complexity.SMALL };
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* 解析 TRIAGE actor 返回值
|
|
234
|
-
*/
|
|
235
|
-
_parseTriageResult(result) {
|
|
236
|
-
if (!result) return { complexity: Complexity.SMALL };
|
|
237
|
-
|
|
238
|
-
// result 可能是 { complexity: '...' } 或包含 text 的结构
|
|
239
|
-
if (result.complexity && Object.values(Complexity).includes(result.complexity)) {
|
|
240
|
-
return result;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// 尝试从文本结果中提取
|
|
244
|
-
const text = typeof result === 'string' ? result : (result.text || result.content || '');
|
|
245
|
-
const lower = text.toLowerCase();
|
|
246
|
-
|
|
247
|
-
if (lower.includes('trivial')) return { complexity: Complexity.TRIVIAL };
|
|
248
|
-
if (lower.includes('complex')) return { complexity: Complexity.COMPLEX };
|
|
249
|
-
return { complexity: Complexity.SMALL };
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// ===================================================================
|
|
253
|
-
// Phase: DISCUSSION
|
|
254
|
-
// ===================================================================
|
|
255
|
-
|
|
256
|
-
async _phaseDiscussion(task, flow) {
|
|
257
|
-
// 区分两种 discussion:
|
|
258
|
-
// 1. 初始 discussion (task.plan === null) — 标准发散讨论
|
|
259
|
-
// 2. Post-planning discussion (task.plan !== null) — Trading 对抗性审查
|
|
260
|
-
const isPostPlanning = task.plan !== null;
|
|
261
|
-
|
|
262
|
-
if (isPostPlanning) {
|
|
263
|
-
const ppConfig = getPostPlanningDiscussion(flow);
|
|
264
|
-
if (ppConfig) {
|
|
265
|
-
this.log(`DISCUSSION (post-planning): ${ppConfig.purpose}`);
|
|
266
|
-
await this._runActorsParallel(task, ppConfig.actors, {
|
|
267
|
-
instruction: `${ppConfig.purpose}\n\nChallenge points:\n${ppConfig.challengePoints.map(p => `- ${p}`).join('\n')}\n\nStrategy to review: ${JSON.stringify(task.plan)}`,
|
|
268
|
-
memoryPath: this._getMemoryPath(task)
|
|
269
|
-
});
|
|
270
|
-
this.log(`DISCUSSION (post-planning): completed`);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// Post-planning discussion 之后:
|
|
274
|
-
// Trading: QG before execution → QUALITY_GATE
|
|
275
|
-
// Others: → EXECUTION
|
|
276
|
-
if (isQualityGateBeforeExecution(flow)) {
|
|
277
|
-
transitionPhase(task, Phase.QUALITY_GATE);
|
|
278
|
-
} else {
|
|
279
|
-
transitionPhase(task, Phase.EXECUTION);
|
|
280
|
-
}
|
|
281
|
-
this._notify(task);
|
|
282
|
-
return;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// 标准初始 discussion
|
|
286
|
-
this.log(`DISCUSSION: starting for task ${task.taskId}`);
|
|
287
|
-
|
|
288
|
-
const context = { uiInvolved: this._isUiInvolved(task) };
|
|
289
|
-
const actorDefs = getDiscussionActors(flow, context);
|
|
290
|
-
|
|
291
|
-
if (actorDefs.length === 0) {
|
|
292
|
-
this.log('DISCUSSION: no actors needed, skipping to PLANNING');
|
|
293
|
-
transitionPhase(task, Phase.PLANNING);
|
|
294
|
-
this._notify(task);
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
const results = await this._runActorsParallel(task, actorDefs, {
|
|
299
|
-
instruction: `Discussion topic: ${task.description}`,
|
|
300
|
-
memoryPath: this._getMemoryPath(task)
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
this.log(`DISCUSSION: ${results.length} actors completed`);
|
|
304
|
-
|
|
305
|
-
transitionPhase(task, Phase.PLANNING);
|
|
306
|
-
this._notify(task);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
// ===================================================================
|
|
310
|
-
// Phase: PLANNING
|
|
311
|
-
// ===================================================================
|
|
312
|
-
|
|
313
|
-
async _phasePlanning(task, flow) {
|
|
314
|
-
this.log(`PLANNING: starting for task ${task.taskId}`);
|
|
315
|
-
|
|
316
|
-
const plannerDefs = getPlanningActors(flow);
|
|
317
|
-
const lead = getPlanningLead(flow);
|
|
318
|
-
|
|
319
|
-
// 创建 planning actors
|
|
320
|
-
const results = await this._runActorsParallel(task, plannerDefs, {
|
|
321
|
-
instruction: `Create an execution plan for: ${task.description}`,
|
|
322
|
-
memoryPath: this._getMemoryPath(task),
|
|
323
|
-
planPath: this._getPlanPath(task)
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
// 合并 planning 结果 — Lead 的结果优先
|
|
327
|
-
const plan = this._mergePlanningResults(results, lead);
|
|
328
|
-
|
|
329
|
-
if (!plan || !plan.steps || plan.steps.length === 0) {
|
|
330
|
-
this.log('PLANNING: failed to produce a valid plan');
|
|
331
|
-
// 第一次 planning 失败可以重试
|
|
332
|
-
if (task.iterations < task.maxIterations) {
|
|
333
|
-
task.iterations++;
|
|
334
|
-
this.log(`PLANNING: retry (iteration ${task.iterations}/${task.maxIterations})`);
|
|
335
|
-
this._notify(task);
|
|
336
|
-
return; // 重新进入 PLANNING
|
|
337
|
-
}
|
|
338
|
-
task.error = 'Failed to produce execution plan after max iterations';
|
|
339
|
-
transitionPhase(task, Phase.FAILED);
|
|
340
|
-
this._notify(task);
|
|
341
|
-
return;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
task.plan = plan;
|
|
345
|
-
task.currentStepIndex = 0;
|
|
346
|
-
this.log(`PLANNING: produced plan with ${plan.steps.length} steps`);
|
|
347
|
-
|
|
348
|
-
// 如果场景有 post-planning discussion(Trading 对抗性审查),transition 到 DISCUSSION
|
|
349
|
-
const ppConfig = getPostPlanningDiscussion(flow);
|
|
350
|
-
if (ppConfig) {
|
|
351
|
-
transitionPhase(task, Phase.DISCUSSION);
|
|
352
|
-
} else if (isQualityGateBeforeExecution(flow)) {
|
|
353
|
-
// QG before execution(不常见,但保持灵活性)
|
|
354
|
-
transitionPhase(task, Phase.QUALITY_GATE);
|
|
355
|
-
} else {
|
|
356
|
-
transitionPhase(task, Phase.EXECUTION);
|
|
357
|
-
}
|
|
358
|
-
this._notify(task);
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
/**
|
|
362
|
-
* 合并多个 planner 的结果,以 lead 的结果为主
|
|
363
|
-
*/
|
|
364
|
-
_mergePlanningResults(results, leadId) {
|
|
365
|
-
if (!results || results.length === 0) return null;
|
|
366
|
-
|
|
367
|
-
// 找到 lead 的结果
|
|
368
|
-
const leadResult = results.find(r => r?.personaId === leadId);
|
|
369
|
-
const base = leadResult || results[0];
|
|
370
|
-
|
|
371
|
-
if (!base) return null;
|
|
372
|
-
|
|
373
|
-
// 如果 result 已经是 plan 格式
|
|
374
|
-
if (base.plan) return base.plan;
|
|
375
|
-
|
|
376
|
-
// 尝试解析文本格式的 plan
|
|
377
|
-
if (base.text || base.content) {
|
|
378
|
-
try {
|
|
379
|
-
const text = base.text || base.content;
|
|
380
|
-
const parsed = JSON.parse(text);
|
|
381
|
-
if (parsed.steps) {
|
|
382
|
-
return createExecutionPlan(parsed);
|
|
383
|
-
}
|
|
384
|
-
} catch {
|
|
385
|
-
// 无法解析,用默认格式
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
// 如果 result 直接包含 steps
|
|
390
|
-
if (base.steps) {
|
|
391
|
-
return createExecutionPlan(base);
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
return null;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
// ===================================================================
|
|
398
|
-
// Phase: EXECUTION
|
|
399
|
-
// ===================================================================
|
|
400
|
-
|
|
401
|
-
async _phaseExecution(task, flow) {
|
|
402
|
-
this.log(`EXECUTION: starting for task ${task.taskId}`);
|
|
403
|
-
|
|
404
|
-
// Video 场景:严格串行 stages
|
|
405
|
-
const stages = getExecutionStages(flow);
|
|
406
|
-
if (stages) {
|
|
407
|
-
await this._executeVideoStages(task, flow, stages);
|
|
408
|
-
return;
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
// TRIVIAL: 没有 plan,直接创建一个 actor 执行
|
|
412
|
-
if (!task.plan) {
|
|
413
|
-
await this._executeTrivial(task, flow);
|
|
414
|
-
return;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
// 标准执行:按 plan 的步骤执行
|
|
418
|
-
await this._executeByPlan(task, flow);
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
/**
|
|
422
|
-
* TRIVIAL 任务执行:直接创建一个收敛 actor
|
|
423
|
-
*/
|
|
424
|
-
async _executeTrivial(task, flow) {
|
|
425
|
-
this.log('EXECUTION: trivial task, single actor');
|
|
426
|
-
|
|
427
|
-
const coderPersona = flow.execution.primaryCoder
|
|
428
|
-
|| flow.execution.primaryWriter
|
|
429
|
-
|| flow.execution.executor;
|
|
430
|
-
|
|
431
|
-
const specialty = flow.execution.executorSpecialty || 'coding';
|
|
432
|
-
|
|
433
|
-
const result = await this._runSingleActor(task, {
|
|
434
|
-
personaId: coderPersona,
|
|
435
|
-
specialty
|
|
436
|
-
}, {
|
|
437
|
-
instruction: task.description,
|
|
438
|
-
workDir: task.workDir
|
|
439
|
-
});
|
|
440
|
-
|
|
441
|
-
// Trivial 完成后检查是否需要 quality gate
|
|
442
|
-
if (needsQualityGate(flow, specialty)) {
|
|
443
|
-
transitionPhase(task, Phase.QUALITY_GATE);
|
|
444
|
-
} else {
|
|
445
|
-
transitionPhase(task, Phase.ACCEPTANCE);
|
|
446
|
-
}
|
|
447
|
-
this._notify(task);
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
/**
|
|
451
|
-
* 按 Plan 步骤执行
|
|
452
|
-
*/
|
|
453
|
-
async _executeByPlan(task, flow) {
|
|
454
|
-
const steps = task.plan.steps;
|
|
455
|
-
|
|
456
|
-
while (task.currentStepIndex < steps.length) {
|
|
457
|
-
const step = steps[task.currentStepIndex];
|
|
458
|
-
|
|
459
|
-
if (step.status === 'completed') {
|
|
460
|
-
task.currentStepIndex++;
|
|
461
|
-
continue;
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
// 收集可并行的步骤
|
|
465
|
-
const parallelSteps = this._collectParallelSteps(steps, task.currentStepIndex);
|
|
466
|
-
|
|
467
|
-
this.log(`EXECUTION: executing step(s) ${parallelSteps.map(s => s.index).join(', ')}`);
|
|
468
|
-
|
|
469
|
-
// 并行执行
|
|
470
|
-
const actorDefs = parallelSteps.map(s => ({
|
|
471
|
-
personaId: s.assignedPersona,
|
|
472
|
-
specialty: s.specialty
|
|
473
|
-
}));
|
|
474
|
-
|
|
475
|
-
const results = await this._runActorsParallel(task, actorDefs, {
|
|
476
|
-
instruction: parallelSteps.map(s => `Step ${s.index}: ${s.title}\n${s.description}`).join('\n\n'),
|
|
477
|
-
workDir: task.workDir
|
|
478
|
-
});
|
|
479
|
-
|
|
480
|
-
// 标记步骤完成
|
|
481
|
-
for (let i = 0; i < parallelSteps.length; i++) {
|
|
482
|
-
const s = parallelSteps[i];
|
|
483
|
-
const r = results[i];
|
|
484
|
-
s.status = r?.error ? 'failed' : 'completed';
|
|
485
|
-
s.result = r;
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
// 每组并行步骤完成后,检查是否需要 quality gate
|
|
489
|
-
const executedSpecialties = parallelSteps.map(s => s.specialty);
|
|
490
|
-
const anyNeedsQG = executedSpecialties.some(sp => needsQualityGate(flow, sp));
|
|
491
|
-
|
|
492
|
-
if (anyNeedsQG) {
|
|
493
|
-
transitionPhase(task, Phase.QUALITY_GATE);
|
|
494
|
-
this._notify(task);
|
|
495
|
-
return; // 交给 quality gate phase 处理
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
// 前进到并行组之后的下一个 pending 步骤
|
|
499
|
-
task.currentStepIndex = this._advancePastCompletedSteps(task);
|
|
500
|
-
this._notify(task);
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
// 所有步骤执行完毕
|
|
504
|
-
this.log('EXECUTION: all steps completed');
|
|
505
|
-
transitionPhase(task, Phase.ACCEPTANCE);
|
|
506
|
-
this._notify(task);
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
/**
|
|
510
|
-
* 收集从 startIndex 开始的可并行步骤
|
|
511
|
-
*/
|
|
512
|
-
_collectParallelSteps(steps, startIndex) {
|
|
513
|
-
const start = steps[startIndex];
|
|
514
|
-
if (!start) return [];
|
|
515
|
-
|
|
516
|
-
const result = [start];
|
|
517
|
-
|
|
518
|
-
// 如果当前步骤标记为可并行,继续收集同组的
|
|
519
|
-
if (start.parallel && start.parallelGroup) {
|
|
520
|
-
for (let i = startIndex + 1; i < steps.length; i++) {
|
|
521
|
-
const s = steps[i];
|
|
522
|
-
if (s.parallel && s.parallelGroup === start.parallelGroup && s.status === 'pending') {
|
|
523
|
-
// 检查依赖是否满足
|
|
524
|
-
const depsMet = s.dependencies.every(dep => steps[dep]?.status === 'completed');
|
|
525
|
-
if (depsMet) result.push(s);
|
|
526
|
-
} else {
|
|
527
|
-
break;
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
return result;
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
/**
|
|
536
|
-
* Video 场景:严格串行执行 stages
|
|
537
|
-
*/
|
|
538
|
-
async _executeVideoStages(task, flow, stages) {
|
|
539
|
-
this.log('EXECUTION: video strict serial stages');
|
|
540
|
-
|
|
541
|
-
for (let i = 0; i < stages.length; i++) {
|
|
542
|
-
const stage = stages[i];
|
|
543
|
-
this.log(`EXECUTION: video stage ${i + 1}/${stages.length}: ${stage.name}`);
|
|
544
|
-
|
|
545
|
-
// 执行 stage actor
|
|
546
|
-
const result = await this._runSingleActor(task, stage.actor, {
|
|
547
|
-
instruction: stage.description,
|
|
548
|
-
workDir: task.workDir,
|
|
549
|
-
memoryPath: this._getMemoryPath(task)
|
|
550
|
-
});
|
|
551
|
-
|
|
552
|
-
// Stage 内置 review(如果配置了)
|
|
553
|
-
if (stage.reviewAfter) {
|
|
554
|
-
this.log(`EXECUTION: video stage ${stage.name} review`);
|
|
555
|
-
const reviewResult = await this._runSingleActor(task, stage.reviewAfter, {
|
|
556
|
-
instruction: `Review the ${stage.name} output for quality and consistency`,
|
|
557
|
-
workDir: task.workDir,
|
|
558
|
-
memoryPath: this._getMemoryPath(task)
|
|
559
|
-
});
|
|
560
|
-
|
|
561
|
-
// 如果 review 不通过,重做该 stage
|
|
562
|
-
if (reviewResult && this._isReviewFail(reviewResult)) {
|
|
563
|
-
this.log(`EXECUTION: video stage ${stage.name} review FAILED, retrying stage`);
|
|
564
|
-
const failCount = recordStepFail(task, i);
|
|
565
|
-
if (failCount >= 2) {
|
|
566
|
-
this.log(`EXECUTION: video stage ${stage.name} failed ${failCount} times, escalating`);
|
|
567
|
-
task.error = `Video stage "${stage.name}" failed review ${failCount} times`;
|
|
568
|
-
transitionPhase(task, Phase.FAILED);
|
|
569
|
-
this._notify(task);
|
|
570
|
-
return;
|
|
571
|
-
}
|
|
572
|
-
i--; // 重做当前 stage
|
|
573
|
-
continue;
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
// 所有 stages 完成,进入 quality gate
|
|
579
|
-
transitionPhase(task, Phase.QUALITY_GATE);
|
|
580
|
-
this._notify(task);
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
// ===================================================================
|
|
584
|
-
// Phase: QUALITY GATE
|
|
585
|
-
// ===================================================================
|
|
586
|
-
|
|
587
|
-
async _phaseQualityGate(task, flow) {
|
|
588
|
-
this.log(`QUALITY GATE: starting for task ${task.taskId}`);
|
|
589
|
-
|
|
590
|
-
const qgConfig = getQualityGateConfig(flow);
|
|
591
|
-
const qgInstruction = `Review the execution output for quality, correctness, and standards compliance.
|
|
592
|
-
Pass threshold: ${qgConfig.passThreshold || 'N/A'}
|
|
593
|
-
Check areas: ${qgConfig.checkAreas?.join(', ') || 'all'}`;
|
|
594
|
-
const qgContext = {
|
|
595
|
-
workDir: task.workDir,
|
|
596
|
-
memoryPath: this._getMemoryPath(task)
|
|
597
|
-
};
|
|
598
|
-
|
|
599
|
-
let results;
|
|
600
|
-
|
|
601
|
-
// 如果配置了并行且同时有 reviewer + tester,并行运行
|
|
602
|
-
if (qgConfig.parallel && qgConfig.reviewer && qgConfig.tester) {
|
|
603
|
-
const actorDefs = [qgConfig.reviewer, qgConfig.tester];
|
|
604
|
-
const parallelResults = await this._runActorsParallel(task, actorDefs, {
|
|
605
|
-
...qgContext,
|
|
606
|
-
instruction: qgInstruction
|
|
607
|
-
});
|
|
608
|
-
results = [
|
|
609
|
-
{ type: 'review', result: parallelResults[0] },
|
|
610
|
-
{ type: 'testing', result: parallelResults[1] }
|
|
611
|
-
];
|
|
612
|
-
} else {
|
|
613
|
-
// 串行
|
|
614
|
-
results = [];
|
|
615
|
-
if (qgConfig.reviewer) {
|
|
616
|
-
const reviewResult = await this._runSingleActor(task, qgConfig.reviewer, {
|
|
617
|
-
...qgContext,
|
|
618
|
-
instruction: qgInstruction
|
|
619
|
-
});
|
|
620
|
-
results.push({ type: 'review', result: reviewResult });
|
|
621
|
-
}
|
|
622
|
-
if (qgConfig.tester) {
|
|
623
|
-
const testResult = await this._runSingleActor(task, qgConfig.tester, {
|
|
624
|
-
...qgContext,
|
|
625
|
-
instruction: 'Test the implementation. Cover core logic and edge cases.'
|
|
626
|
-
});
|
|
627
|
-
results.push({ type: 'testing', result: testResult });
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
// 判定结果
|
|
632
|
-
const allPass = results.every(r => !this._isReviewFail(r.result));
|
|
633
|
-
|
|
634
|
-
if (allPass) {
|
|
635
|
-
this.log('QUALITY GATE: PASS');
|
|
636
|
-
|
|
637
|
-
// Trading: QG before execution → pass → EXECUTION
|
|
638
|
-
if (isQualityGateBeforeExecution(flow)) {
|
|
639
|
-
transitionPhase(task, Phase.EXECUTION);
|
|
640
|
-
} else if (task.plan && task.currentStepIndex < task.plan.steps.length - 1) {
|
|
641
|
-
// 还有后续步骤,回到 execution 继续
|
|
642
|
-
task.currentStepIndex = this._advancePastCompletedSteps(task);
|
|
643
|
-
transitionPhase(task, Phase.EXECUTION);
|
|
644
|
-
} else {
|
|
645
|
-
transitionPhase(task, Phase.ACCEPTANCE);
|
|
646
|
-
}
|
|
647
|
-
} else {
|
|
648
|
-
this.log('QUALITY GATE: FAIL');
|
|
649
|
-
|
|
650
|
-
const stepIdx = task.currentStepIndex;
|
|
651
|
-
const failCount = recordStepFail(task, stepIdx);
|
|
652
|
-
const maxRetries = qgConfig.maxRetries || 3;
|
|
653
|
-
|
|
654
|
-
if (failCount >= maxRetries) {
|
|
655
|
-
// 升级:创建 discussion 重新讨论方案
|
|
656
|
-
this.log(`QUALITY GATE: step ${stepIdx} failed ${failCount} times, escalating to DISCUSSION`);
|
|
657
|
-
// 重置 plan 以触发完整的 re-plan 流程(而非误入 post-planning discussion 路径)
|
|
658
|
-
task.plan = null;
|
|
659
|
-
task.currentStepIndex = -1;
|
|
660
|
-
transitionPhase(task, Phase.DISCUSSION);
|
|
661
|
-
} else {
|
|
662
|
-
// 打回修改:回到 execution 重做当前步骤
|
|
663
|
-
this.log(`QUALITY GATE: step ${stepIdx} fail #${failCount}, retrying execution`);
|
|
664
|
-
if (task.plan?.steps[stepIdx]) {
|
|
665
|
-
task.plan.steps[stepIdx].status = 'pending';
|
|
666
|
-
}
|
|
667
|
-
transitionPhase(task, Phase.EXECUTION);
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
this._notify(task);
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
/**
|
|
675
|
-
* 判断 review/test 结果是否为 fail
|
|
676
|
-
*/
|
|
677
|
-
_isReviewFail(result) {
|
|
678
|
-
if (!result) return true;
|
|
679
|
-
if (result.error) return true;
|
|
680
|
-
|
|
681
|
-
// 检查 pass/fail 标记
|
|
682
|
-
if (result.pass === false) return true;
|
|
683
|
-
if (result.pass === true) return false;
|
|
684
|
-
|
|
685
|
-
// 检查评分
|
|
686
|
-
if (typeof result.score === 'number') {
|
|
687
|
-
return result.score < 9;
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
// 从文本中推断
|
|
691
|
-
const text = (result.text || result.content || '').toLowerCase();
|
|
692
|
-
if (text.includes('fail') || text.includes('reject')) return true;
|
|
693
|
-
if (text.includes('pass') || text.includes('approve')) return false;
|
|
694
|
-
|
|
695
|
-
// 默认通过(宁可多跑一轮也不卡住)
|
|
696
|
-
return false;
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
// ===================================================================
|
|
700
|
-
// Phase: ACCEPTANCE
|
|
701
|
-
// ===================================================================
|
|
702
|
-
|
|
703
|
-
async _phaseAcceptance(task, flow) {
|
|
704
|
-
this.log(`ACCEPTANCE: starting for task ${task.taskId}`);
|
|
705
|
-
|
|
706
|
-
const verifier = getAcceptanceVerifier(flow);
|
|
707
|
-
|
|
708
|
-
const result = await this._runSingleActor(task, verifier, {
|
|
709
|
-
instruction: `Acceptance verification for task "${task.title}".
|
|
710
|
-
Verify all success criteria are met:
|
|
711
|
-
${task.plan?.successCriteria?.map((c, i) => `${i + 1}. ${c}`).join('\n') || 'N/A'}
|
|
712
|
-
|
|
713
|
-
Focus areas: ${flow.acceptance.focusAreas.join(', ')}`,
|
|
714
|
-
workDir: task.workDir,
|
|
715
|
-
memoryPath: this._getMemoryPath(task)
|
|
716
|
-
});
|
|
717
|
-
|
|
718
|
-
const accepted = result && !this._isReviewFail(result);
|
|
719
|
-
|
|
720
|
-
if (accepted) {
|
|
721
|
-
this.log('ACCEPTANCE: PASSED');
|
|
722
|
-
task.summary = result?.summary || result?.text || 'Task completed successfully';
|
|
723
|
-
transitionPhase(task, Phase.DONE);
|
|
724
|
-
} else {
|
|
725
|
-
this.log('ACCEPTANCE: REJECTED');
|
|
726
|
-
task.iterations++;
|
|
727
|
-
|
|
728
|
-
if (task.iterations >= task.maxIterations) {
|
|
729
|
-
this.log(`ACCEPTANCE: exceeded max iterations (${task.maxIterations}), marking FAILED`);
|
|
730
|
-
task.error = `Failed acceptance after ${task.maxIterations} iterations`;
|
|
731
|
-
transitionPhase(task, Phase.FAILED);
|
|
732
|
-
} else {
|
|
733
|
-
this.log(`ACCEPTANCE: iteration ${task.iterations}/${task.maxIterations}, returning to planning`);
|
|
734
|
-
// 回到合适的 Phase
|
|
735
|
-
// 如果是 plan 问题 → PLANNING; 如果是执行问题 → EXECUTION
|
|
736
|
-
const target = this._determineRetryTarget(result);
|
|
737
|
-
transitionPhase(task, target);
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
this._notify(task);
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
/**
|
|
745
|
-
* 根据 acceptance 结果决定回退到哪个 phase
|
|
746
|
-
*/
|
|
747
|
-
_determineRetryTarget(result) {
|
|
748
|
-
if (!result) return Phase.PLANNING;
|
|
749
|
-
|
|
750
|
-
const text = (result.text || result.content || '').toLowerCase();
|
|
751
|
-
if (text.includes('plan') || text.includes('architecture') || text.includes('design')) {
|
|
752
|
-
return Phase.PLANNING;
|
|
753
|
-
}
|
|
754
|
-
return Phase.EXECUTION;
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
// ===================================================================
|
|
758
|
-
// Actor 调度 Helpers
|
|
759
|
-
// ===================================================================
|
|
760
|
-
|
|
761
|
-
/**
|
|
762
|
-
* 运行单个 Actor,等待完成
|
|
763
|
-
*/
|
|
764
|
-
async _runSingleActor(task, actorDef, context) {
|
|
765
|
-
const thinkingMode = getThinkingMode(actorDef.specialty);
|
|
766
|
-
const actor = createActorInstance({
|
|
767
|
-
personaId: actorDef.personaId,
|
|
768
|
-
personaName: actorDef.personaName || actorDef.personaId,
|
|
769
|
-
specialty: actorDef.specialty,
|
|
770
|
-
thinkingMode,
|
|
771
|
-
taskId: task.taskId,
|
|
772
|
-
threadId: context.threadId,
|
|
773
|
-
worktreePath: context.workDir,
|
|
774
|
-
activity: `${actorDef.specialty} for "${task.title}"`
|
|
775
|
-
});
|
|
776
|
-
|
|
777
|
-
addActiveActor(task, actor);
|
|
778
|
-
this._notify(task);
|
|
779
|
-
|
|
780
|
-
try {
|
|
781
|
-
const result = await this.actorManager.createActor(
|
|
782
|
-
{ id: actorDef.personaId, name: actor.personaName },
|
|
783
|
-
actorDef.specialty,
|
|
784
|
-
{ ...context, taskId: task.taskId, actorInstanceId: actor.instanceId }
|
|
785
|
-
);
|
|
786
|
-
|
|
787
|
-
completeActor(task, actor.instanceId, result);
|
|
788
|
-
this._notify(task);
|
|
789
|
-
return result;
|
|
790
|
-
} catch (err) {
|
|
791
|
-
this.log(`Actor ${actor.instanceId} failed: ${err.message}`);
|
|
792
|
-
failActor(task, actor.instanceId, err);
|
|
793
|
-
this._notify(task);
|
|
794
|
-
return { error: err.message };
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
/**
|
|
799
|
-
* 并行运行多个 Actor
|
|
800
|
-
*
|
|
801
|
-
* @returns {Array<ActorResult>}
|
|
802
|
-
*/
|
|
803
|
-
async _runActorsParallel(task, actorDefs, context) {
|
|
804
|
-
if (actorDefs.length === 0) return [];
|
|
805
|
-
|
|
806
|
-
const promises = actorDefs.map(def =>
|
|
807
|
-
this._runSingleActor(task, def, context)
|
|
808
|
-
);
|
|
809
|
-
|
|
810
|
-
const results = await Promise.allSettled(promises);
|
|
811
|
-
|
|
812
|
-
return results.map((r, i) => {
|
|
813
|
-
if (r.status === 'fulfilled') {
|
|
814
|
-
// 不修改原始 result 对象,创建新对象附加 personaId
|
|
815
|
-
return { ...r.value, personaId: actorDefs[i].personaId };
|
|
816
|
-
}
|
|
817
|
-
return { error: r.reason?.message || 'Unknown error', personaId: actorDefs[i].personaId };
|
|
818
|
-
});
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
// ===================================================================
|
|
822
|
-
// Utility
|
|
823
|
-
// ===================================================================
|
|
824
|
-
|
|
825
|
-
/**
|
|
826
|
-
* 跳过已完成的步骤,返回下一个 pending 步骤的 index
|
|
827
|
-
* 修复 #2: 并行步骤组完成后正确推进到下一个未完成的步骤
|
|
828
|
-
*/
|
|
829
|
-
_advancePastCompletedSteps(task) {
|
|
830
|
-
if (!task.plan) return task.currentStepIndex;
|
|
831
|
-
const steps = task.plan.steps;
|
|
832
|
-
let idx = task.currentStepIndex;
|
|
833
|
-
while (idx < steps.length && steps[idx].status === 'completed') {
|
|
834
|
-
idx++;
|
|
835
|
-
}
|
|
836
|
-
return idx;
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
/**
|
|
840
|
-
* 判断任务是否涉及 UI
|
|
841
|
-
*/
|
|
842
|
-
_isUiInvolved(task) {
|
|
843
|
-
const desc = (task.description || '').toLowerCase();
|
|
844
|
-
return /\b(ui|前端|frontend|界面|页面|组件|component|css|style|布局|layout|设计|design|按钮|button|表单|form)\b/i.test(desc);
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
/**
|
|
848
|
-
* 获取 task 的 memory.md 路径(约定)
|
|
849
|
-
*/
|
|
850
|
-
_getMemoryPath(task) {
|
|
851
|
-
return `.conductor/tasks/${task.taskId}/memory.md`;
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
/**
|
|
855
|
-
* 获取 task 的 plan.json 路径(约定)
|
|
856
|
-
*/
|
|
857
|
-
_getPlanPath(task) {
|
|
858
|
-
return `.conductor/tasks/${task.taskId}/plan.json`;
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
/**
|
|
862
|
-
* 通知状态变更
|
|
863
|
-
*/
|
|
864
|
-
_notify(task) {
|
|
865
|
-
try {
|
|
866
|
-
this.onStatusChange(serializeTask(task));
|
|
867
|
-
} catch (err) {
|
|
868
|
-
this.log(`Status callback error: ${err.message}`);
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
// =====================================================================
|
|
874
|
-
// Factory Function
|
|
875
|
-
// =====================================================================
|
|
876
|
-
|
|
877
|
-
/**
|
|
878
|
-
* 创建 Orchestrator 实例
|
|
879
|
-
*
|
|
880
|
-
* @param {object} params
|
|
881
|
-
* @param {object} params.actorManager — { createActor(persona, specialty, ctx) → Promise<Result> }
|
|
882
|
-
* @param {Function} [params.onStatusChange]
|
|
883
|
-
* @param {Function} [params.onLog]
|
|
884
|
-
* @returns {Orchestrator}
|
|
885
|
-
*/
|
|
886
|
-
export function createOrchestrator({ actorManager, onStatusChange, onLog }) {
|
|
887
|
-
return new Orchestrator({ actorManager, onStatusChange, onLog });
|
|
888
|
-
}
|