@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.
@@ -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
- }