autosnippet 3.2.8 → 3.2.10

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.
Files changed (114) hide show
  1. package/bin/cli.js +6 -5
  2. package/dashboard/dist/assets/index-BTAsOZv2.js +128 -0
  3. package/dashboard/dist/assets/index-C_72Ct98.css +1 -0
  4. package/dashboard/dist/index.html +2 -2
  5. package/lib/cli/AiScanService.js +23 -26
  6. package/lib/cli/SetupService.js +1 -1
  7. package/lib/cli/deploy/FileManifest.js +1 -1
  8. package/lib/core/AstAnalyzer.js +1 -1
  9. package/lib/core/discovery/index.js +2 -2
  10. package/lib/external/ai/AiProvider.js +66 -172
  11. package/lib/external/ai/providers/GoogleGeminiProvider.js +29 -5
  12. package/lib/external/mcp/handlers/bootstrap/BootstrapSession.js +1 -1
  13. package/lib/external/mcp/handlers/bootstrap/ExternalSubmissionTracker.js +3 -3
  14. package/lib/external/mcp/handlers/bootstrap/MissionBriefingBuilder.js +1 -1
  15. package/lib/external/mcp/handlers/bootstrap/pipeline/IncrementalBootstrap.js +1 -1
  16. package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-context.js +8 -8
  17. package/lib/external/mcp/handlers/bootstrap/pipeline/noAiFallback.js +1 -1
  18. package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +291 -204
  19. package/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.js +7 -6
  20. package/lib/external/mcp/handlers/bootstrap/shared/dimension-sop.js +1 -1
  21. package/lib/external/mcp/handlers/bootstrap-internal.js +2 -2
  22. package/lib/external/mcp/handlers/dimension-complete-external.js +6 -6
  23. package/lib/http/HttpServer.js +1 -1
  24. package/lib/http/middleware/requestLogger.js +1 -0
  25. package/lib/http/routes/ai.js +240 -35
  26. package/lib/http/routes/candidates.js +2 -3
  27. package/lib/http/routes/extract.js +13 -11
  28. package/lib/http/routes/modules.js +2 -2
  29. package/lib/http/routes/recipes.js +5 -5
  30. package/lib/http/routes/remote.js +134 -255
  31. package/lib/http/routes/violations.js +0 -54
  32. package/lib/http/utils/sse-sessions.js +1 -1
  33. package/lib/infrastructure/logging/Logger.js +5 -4
  34. package/lib/infrastructure/monitoring/PerformanceMonitor.js +3 -2
  35. package/lib/injection/ServiceContainer.js +64 -17
  36. package/lib/platform/ScreenCaptureService.js +177 -0
  37. package/lib/platform/ios/routes/spm.js +2 -2
  38. package/lib/service/agent/AgentEventBus.js +207 -0
  39. package/lib/service/agent/AgentFactory.js +535 -0
  40. package/lib/service/agent/AgentMessage.js +240 -0
  41. package/lib/service/agent/AgentRouter.js +228 -0
  42. package/lib/service/agent/AgentRuntime.js +1056 -0
  43. package/lib/service/agent/AgentState.js +217 -0
  44. package/lib/service/agent/IntentClassifier.js +331 -0
  45. package/lib/service/agent/LarkTransport.js +389 -0
  46. package/lib/service/agent/capabilities.js +409 -0
  47. package/lib/service/{chat → agent/context}/ContextWindow.js +37 -12
  48. package/lib/service/{chat → agent/context}/ExplorationTracker.js +112 -33
  49. package/lib/service/{chat → agent/core}/ChatAgentPrompts.js +5 -3
  50. package/lib/service/agent/core/LoopContext.js +170 -0
  51. package/lib/service/agent/core/MessageAdapter.js +223 -0
  52. package/lib/service/agent/core/ToolExecutionPipeline.js +376 -0
  53. package/lib/service/{chat → agent/domain}/ChatAgentTasks.js +15 -98
  54. package/lib/service/{chat → agent/domain}/EpisodicConsolidator.js +7 -7
  55. package/lib/service/{chat → agent/domain}/EvidenceCollector.js +4 -2
  56. package/lib/service/{chat/AnalystAgent.js → agent/domain/insight-analyst.js} +37 -172
  57. package/lib/service/{chat/HandoffProtocol.js → agent/domain/insight-gate.js} +85 -135
  58. package/lib/service/agent/domain/insight-producer.js +270 -0
  59. package/lib/service/agent/domain/scan-prompts.js +444 -0
  60. package/lib/service/agent/forced-summary.js +266 -0
  61. package/lib/service/agent/index.js +91 -0
  62. package/lib/service/{chat → agent}/memory/ActiveContext.js +29 -1
  63. package/lib/service/{chat → agent}/memory/MemoryCoordinator.js +7 -7
  64. package/lib/service/{chat/ProjectSemanticMemory.js → agent/memory/PersistentMemory.js} +359 -89
  65. package/lib/service/{chat → agent}/memory/SessionStore.js +1 -1
  66. package/lib/service/{chat → agent}/memory/index.js +1 -1
  67. package/lib/service/agent/policies.js +442 -0
  68. package/lib/service/agent/presets.js +305 -0
  69. package/lib/service/agent/strategies.js +756 -0
  70. package/lib/service/{chat → agent/tools}/ToolRegistry.js +3 -3
  71. package/lib/service/agent/tools/ai-analysis.js +75 -0
  72. package/lib/service/{chat → agent}/tools/composite.js +2 -1
  73. package/lib/service/{chat → agent}/tools/guard.js +1 -121
  74. package/lib/service/{chat → agent}/tools/index.js +27 -21
  75. package/lib/service/{chat → agent}/tools/infrastructure.js +1 -1
  76. package/lib/service/agent/tools/knowledge-graph.js +112 -0
  77. package/lib/service/agent/tools/scan-recipe.js +189 -0
  78. package/lib/service/agent/tools/system-interaction.js +476 -0
  79. package/lib/service/automation/DirectiveDetector.js +0 -1
  80. package/lib/service/automation/FileWatcher.js +0 -8
  81. package/lib/service/automation/handlers/CreateHandler.js +7 -3
  82. package/lib/service/automation/handlers/DraftHandler.js +7 -6
  83. package/lib/service/module/ModuleService.js +40 -73
  84. package/lib/service/skills/SignalCollector.js +26 -19
  85. package/lib/service/snippet/codecs/VSCodeCodec.js +1 -1
  86. package/lib/shared/FieldSpec.js +1 -1
  87. package/lib/shared/StyleGuide.js +1 -1
  88. package/package.json +4 -1
  89. package/resources/native-ui/screenshot.swift +228 -0
  90. package/dashboard/dist/assets/index-D5jiDBQG.css +0 -1
  91. package/dashboard/dist/assets/index-e5OKj-Ni.js +0 -128
  92. package/lib/core/discovery/SpmDiscoverer.js +0 -5
  93. package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +0 -750
  94. package/lib/external/mcp/handlers/bootstrap/pipeline/ToolResultCache.js +0 -277
  95. package/lib/http/routes/spm.js +0 -5
  96. package/lib/infrastructure/external/XcodeAutomation.js +0 -15
  97. package/lib/service/chat/ChatAgent.js +0 -1602
  98. package/lib/service/chat/Memory.js +0 -161
  99. package/lib/service/chat/ProducerAgent.js +0 -431
  100. package/lib/service/chat/ReasoningTrace.js +0 -523
  101. package/lib/service/chat/TaskPipeline.js +0 -357
  102. package/lib/service/chat/WorkingMemory.js +0 -359
  103. package/lib/service/chat/memory/PersistentMemory.js +0 -450
  104. package/lib/service/chat/tools/ai-analysis.js +0 -267
  105. package/lib/service/chat/tools/knowledge-graph.js +0 -234
  106. package/lib/service/chat/tools.js +0 -18
  107. package/lib/service/snippet/PlaceholderConverter.js +0 -5
  108. package/lib/service/snippet/codecs/XcodeCodec.js +0 -5
  109. /package/lib/service/{chat → agent}/ConversationStore.js +0 -0
  110. /package/lib/service/{chat → agent}/tools/_shared.js +0 -0
  111. /package/lib/service/{chat → agent}/tools/ast-graph.js +0 -0
  112. /package/lib/service/{chat → agent}/tools/lifecycle.js +0 -0
  113. /package/lib/service/{chat → agent}/tools/project-access.js +0 -0
  114. /package/lib/service/{chat → agent}/tools/query.js +0 -0
@@ -0,0 +1,756 @@
1
+ /**
2
+ * Strategies — Agent 执行策略
3
+ *
4
+ * 核心思想: "如何组织工作" 与 "能做什么" 正交。
5
+ *
6
+ * 四种策略:
7
+ * 1. SingleStrategy — 单次 ReAct 循环 (最简单,用于对话)
8
+ * 2. PipelineStrategy — 顺序多阶段 + 质量门控 (分析→提交)
9
+ * 3. FanOutStrategy — 并行执行 + 合并 (多维度冷启动)
10
+ * 4. AdaptiveStrategy — 运行时自动选择策略 (智能模式)
11
+ *
12
+ * 这就是为什么 "冷启动" 和 "扫描" 产出一致:
13
+ * - 冷启动 = FanOut(items=dimensions, itemStrategy=Pipeline(analyze→gate→produce))
14
+ * - 扫描 = Pipeline(analyze→gate→produce)
15
+ * - 唯一区别: 作用域 (全项目 vs 单目录) 和并行度
16
+ *
17
+ * 借鉴:
18
+ * - Anthropic: Prompt Chaining, Parallelization, Orchestrator-Worker
19
+ * - LangGraph: StateGraph, parallel branches
20
+ * - AutoGen: Sequential/Parallel teams
21
+ *
22
+ * @module strategies
23
+ */
24
+
25
+ import { randomUUID } from 'node:crypto';
26
+ import { AgentEventBus, AgentEvents } from './AgentEventBus.js';
27
+ import { AgentMessage } from './AgentMessage.js';
28
+ import { ExplorationTracker } from './context/ExplorationTracker.js';
29
+ import Logger from '../../infrastructure/logging/Logger.js';
30
+
31
+ const _pipelineLogger = Logger.getInstance();
32
+
33
+ // ─── Base Strategy ─────────────────────────────
34
+
35
+ /**
36
+ * Strategy 基类 — 定义 Agent 如何组织工作
37
+ */
38
+ export class Strategy {
39
+ /** @type {string} */
40
+ get name() { throw new Error('Subclass must implement name'); }
41
+
42
+ /**
43
+ * 执行策略
44
+ *
45
+ * @param {Object} runtime — AgentRuntime 实例
46
+ * @param {import('./AgentMessage.js').AgentMessage} message — 输入消息
47
+ * @param {Object} [opts] — 策略特定选项
48
+ * @returns {Promise<StrategyResult>}
49
+ *
50
+ * @typedef {Object} StrategyResult
51
+ * @property {string} reply — 最终文本回复
52
+ * @property {Array} toolCalls — 所有工具调用记录
53
+ * @property {Object} tokenUsage — Token 统计
54
+ * @property {number} iterations — 总循环次数
55
+ * @property {Object} [phases] — 阶段详情 (Pipeline/FanOut)
56
+ */
57
+ async execute(_runtime, _message, _opts) {
58
+ throw new Error('Subclass must implement execute()');
59
+ }
60
+ }
61
+
62
+ // ─── SingleStrategy — 直接 ReAct ─────────────
63
+
64
+ /**
65
+ * 最简单的策略: 直接运行 ReAct 循环。
66
+ *
67
+ * 适合: 用户对话、简单分析、任何单步骤任务。
68
+ *
69
+ * 等价于 Anthropic 的 "Augmented LLM" 模式。
70
+ */
71
+ export class SingleStrategy extends Strategy {
72
+ get name() { return 'single'; }
73
+
74
+ async execute(runtime, message, opts = {}) {
75
+ return runtime.reactLoop(message.content, {
76
+ history: message.history,
77
+ context: message.metadata.context || {},
78
+ ...opts,
79
+ });
80
+ }
81
+ }
82
+
83
+ // ─── PipelineStrategy — 顺序多阶段 ──────────
84
+
85
+ /**
86
+ * 多阶段顺序执行,每个阶段可以有不同的 Capability 和 Budget,
87
+ * 阶段间可插入质量门控 (Quality Gate)。
88
+ *
89
+ * 适合: 分析→提交、扫描→审计→报告
90
+ *
91
+ * 等价于 Anthropic 的 "Prompt Chaining" + "Evaluator-Optimizer"。
92
+ *
93
+ * 增强特性 (v3):
94
+ * - Gate 支持自定义 evaluator 函数 (三态: pass/retry/degrade)
95
+ * - Gate retry: 失败时回退重新执行前一阶段
96
+ * - Stage 支持 promptBuilder(context) 替代简单 promptTransform
97
+ * - Stage 支持 systemPrompt 覆盖 (per-phase 系统提示词)
98
+ * - Stage 支持 onToolCall 钩子 (per-phase 工具调用通知)
99
+ * - strategyContext: 通过 opts 传入的领域级上下文 (Bootstrap 注入 dimConfig/sessionStore/...)
100
+ *
101
+ * @example
102
+ * // 基础用法 (向后兼容)
103
+ * new PipelineStrategy({
104
+ * stages: [
105
+ * { name: 'analyze', capabilities: ['code_analysis'], budget: { maxIterations: 16 } },
106
+ * { name: 'gate', gate: { minEvidenceLength: 500, minFileRefs: 3 } },
107
+ * { name: 'produce', capabilities: ['knowledge_production'], budget: { maxIterations: 16 },
108
+ * promptTransform: (input, prev) => `基于以下分析:\n${prev.analyze.reply}\n\n${input}` },
109
+ * ],
110
+ * })
111
+ *
112
+ * @example
113
+ * // 增强用法 (Bootstrap)
114
+ * new PipelineStrategy({
115
+ * stages: [
116
+ * { name: 'analyze', capabilities: ['code_analysis'],
117
+ * systemPrompt: ANALYST_SYSTEM_PROMPT,
118
+ * promptBuilder: (ctx) => buildAnalystPrompt(ctx.dimConfig, ctx.projectInfo, ctx),
119
+ * retryPromptBuilder: (retryCtx, input, prev) => `${prev.analyze.reply}\n\n${buildRetryPrompt(retryCtx.reason)}`,
120
+ * onToolCall: (name, args, result, iter) => ac.recordToolCall(name, args, result, true),
121
+ * },
122
+ * { name: 'quality_gate', gate: {
123
+ * evaluator: (source, phaseResults, ctx) => ({ action: 'pass'|'retry'|'degrade', reason, artifact }),
124
+ * maxRetries: 1,
125
+ * }},
126
+ * { name: 'produce', capabilities: ['knowledge_production'],
127
+ * systemPrompt: PRODUCER_SYSTEM_PROMPT,
128
+ * promptBuilder: (ctx) => buildProducerPrompt(ctx.gateArtifact, ctx.dimConfig),
129
+ * skipOnDegrade: true,
130
+ * },
131
+ * ],
132
+ * })
133
+ */
134
+ export class PipelineStrategy extends Strategy {
135
+ /** @type {Array<Object>} */
136
+ #stages;
137
+ /** @type {number} 最大重试次数 (Gate 失败时全局兜底) */
138
+ #maxRetries;
139
+
140
+ constructor({ stages = [], maxRetries = 1 } = {}) {
141
+ super();
142
+ this.#stages = stages;
143
+ this.#maxRetries = maxRetries;
144
+ }
145
+
146
+ get name() { return 'pipeline'; }
147
+
148
+ async execute(runtime, message, opts = {}) {
149
+ const bus = AgentEventBus.getInstance();
150
+ const phaseResults = {};
151
+ const strategyContext = opts.strategyContext || {};
152
+ let totalToolCalls = [];
153
+ let totalTokenUsage = { input: 0, output: 0 };
154
+ let totalIterations = 0;
155
+ let gateArtifact = null;
156
+ let degraded = false;
157
+ let execStageCount = 0; // 已执行的阶段计数 (用于阶段隔离)
158
+ let lastExecutedStageName = null; // 上一个执行阶段名 (用于区分 retry vs 新阶段)
159
+
160
+ for (let i = 0; i < this.#stages.length; i++) {
161
+ const stage = this.#stages[i];
162
+
163
+ // ── Quality Gate 阶段 ──
164
+ if (stage.gate) {
165
+ // 跳过 degrade 之后的 gate
166
+ if (degraded) continue;
167
+
168
+ const sourceName = stage.source || this.#prevStageName(stage);
169
+ const source = phaseResults[sourceName];
170
+ let gateResult;
171
+
172
+ // v3: 自定义评估器 (Bootstrap 用)
173
+ if (typeof stage.gate.evaluator === 'function') {
174
+ gateResult = stage.gate.evaluator(source, phaseResults, strategyContext);
175
+ // 规范化: 确保 action 字段存在
176
+ if (!gateResult.action) {
177
+ gateResult.action = gateResult.pass ? 'pass' : 'retry';
178
+ }
179
+ } else {
180
+ // 向后兼容: 阈值评估
181
+ const legacyResult = this.#evaluateGate(stage.gate, phaseResults, sourceName);
182
+ gateResult = {
183
+ action: legacyResult.pass ? 'pass' : 'retry',
184
+ pass: legacyResult.pass,
185
+ reason: legacyResult.reason,
186
+ };
187
+ }
188
+
189
+ bus.publish(AgentEvents.PROGRESS, {
190
+ type: 'quality_gate',
191
+ pass: gateResult.action === 'pass',
192
+ action: gateResult.action,
193
+ reason: gateResult.reason,
194
+ stage: stage.name || 'gate',
195
+ });
196
+
197
+ // 存储 gate 结果和产物
198
+ phaseResults[stage.name || 'gate'] = {
199
+ pass: gateResult.action === 'pass',
200
+ action: gateResult.action,
201
+ reason: gateResult.reason || '',
202
+ artifact: gateResult.artifact || null,
203
+ };
204
+ if (gateResult.artifact) gateArtifact = gateResult.artifact;
205
+
206
+ // v3: 三态处理
207
+ if (gateResult.action === 'pass') {
208
+ continue;
209
+ }
210
+
211
+ if (gateResult.action === 'degrade') {
212
+ degraded = true;
213
+ break;
214
+ }
215
+
216
+ if (gateResult.action === 'retry') {
217
+ const maxRetries = stage.gate.maxRetries ?? this.#maxRetries;
218
+ const retryKey = `_retries_${stage.name || 'gate'}`;
219
+ phaseResults[retryKey] = (phaseResults[retryKey] || 0) + 1;
220
+
221
+ if (phaseResults[retryKey] <= maxRetries) {
222
+ const prevIdx = this.#findPrevExecStageIdx(i);
223
+ if (prevIdx >= 0) {
224
+ const retryTargetStage = this.#stages[prevIdx];
225
+ phaseResults._retryContext = {
226
+ reason: gateResult.reason,
227
+ artifact: gateResult.artifact,
228
+ };
229
+ // 标记目标阶段为 retry, 供 retryBudget 判定
230
+ phaseResults[`_was_retry_${retryTargetStage.name}`] = true;
231
+ i = prevIdx - 1; // 循环 i++ 后回到 prevIdx
232
+ continue;
233
+ }
234
+ }
235
+ // 重试次数耗尽: 向后兼容 skipOnFail 逻辑
236
+ if (stage.skipOnFail !== false) break;
237
+ continue;
238
+ }
239
+
240
+ // 兜底: 未知 action 视为失败
241
+ if (stage.skipOnFail !== false) break;
242
+ continue;
243
+ }
244
+
245
+ // ── 执行阶段 ──
246
+
247
+ // 跳过 degrade 后的阶段 (除非显式标记 skipOnDegrade: false)
248
+ if (degraded && stage.skipOnDegrade !== false) continue;
249
+
250
+ bus.publish(AgentEvents.PROGRESS, {
251
+ type: 'pipeline_stage_start',
252
+ stage: stage.name,
253
+ capabilities: stage.capabilities?.map(c => typeof c === 'string' ? c : c.name),
254
+ });
255
+
256
+ // 构建阶段 prompt (优先级: retryPromptBuilder > promptBuilder > promptTransform > 原始)
257
+ let stagePrompt;
258
+ if (phaseResults._retryContext && stage.retryPromptBuilder) {
259
+ stagePrompt = stage.retryPromptBuilder(
260
+ phaseResults._retryContext, message.content, phaseResults,
261
+ );
262
+ delete phaseResults._retryContext;
263
+ } else if (stage.promptBuilder) {
264
+ // v3: 完整上下文感知的 prompt 构建
265
+ stagePrompt = stage.promptBuilder({
266
+ message: message.content,
267
+ phaseResults,
268
+ gateArtifact,
269
+ ...strategyContext,
270
+ });
271
+ } else if (stage.promptTransform) {
272
+ stagePrompt = stage.promptTransform(message.content, phaseResults);
273
+ } else {
274
+ stagePrompt = message.content;
275
+ }
276
+
277
+ // 清除已消费的 retryContext
278
+ if (phaseResults._retryContext) delete phaseResults._retryContext;
279
+
280
+ // Fork runtime with stage-specific capabilities and budget
281
+ // v3.1: retry 时使用 retryBudget (缩减预算, 如 Producer 拒绝修正轮)
282
+ const isRetry = !!phaseResults[`_was_retry_${stage.name}`];
283
+ const effectiveBudget = (isRetry && stage.retryBudget) ? stage.retryBudget : stage.budget;
284
+ delete phaseResults[`_was_retry_${stage.name}`];
285
+
286
+ // ── 阶段隔离 (v3.2) ──
287
+ // 避免 ContextWindow / ExplorationTracker 状态在阶段间泄漏
288
+ // B4 fix: retry 同一阶段时不清空 ContextWindow,保留上次分析上下文
289
+ const ctxWin = strategyContext.contextWindow || null;
290
+ const isNewStage = lastExecutedStageName !== stage.name;
291
+ if (ctxWin && execStageCount > 0 && isNewStage) {
292
+ ctxWin.resetForNewStage();
293
+ } else if (ctxWin && execStageCount > 0 && !isNewStage) {
294
+ _pipelineLogger.info(
295
+ `[PipelineStrategy] ♻️ Retry stage "${stage.name}" — preserving ContextWindow (${ctxWin.tokenCount || 0} tokens)`,
296
+ );
297
+ }
298
+
299
+ // 为每个阶段创建适当范围的 ExplorationTracker:
300
+ // - analyze → 复用 orchestrator 创建的 bootstrap tracker (首个阶段)
301
+ // - produce → 创建 producer 策略的独立 tracker
302
+ // - 其他 → 创建 analyst 策略的独立 tracker
303
+ // B4 fix: 从 stage 配置 + strategyContext 双重来源解析 submitToolName,
304
+ // 确保 retry 时 tracker 的 submitToolName 与原阶段一致
305
+ let stageTracker = strategyContext.tracker || null;
306
+ const submitToolName = stage.submitToolName || strategyContext.submitToolName || undefined;
307
+ if (stageTracker && execStageCount > 0) {
308
+ const trackerStrategy = (stage.name === 'produce' || stage.name === 'producer')
309
+ ? 'producer'
310
+ : 'analyst';
311
+ stageTracker = ExplorationTracker.resolve(
312
+ { source: strategyContext.source || 'system', strategy: trackerStrategy },
313
+ { ...(effectiveBudget || {}), ...(submitToolName ? { submitToolName } : {}) },
314
+ );
315
+ } else if (stageTracker && execStageCount === 0 && submitToolName) {
316
+ // 首阶段: 如果 stage 指定了 submitToolName 但 orchestrator 创建的 tracker 未设置,
317
+ // 确保首阶段 tracker 也感知到正确的 submitToolName
318
+ if (stageTracker.submitToolName !== submitToolName) {
319
+ stageTracker = ExplorationTracker.resolve(
320
+ { source: strategyContext.source || 'system', strategy: 'analyst' },
321
+ { ...(effectiveBudget || {}), submitToolName },
322
+ );
323
+ }
324
+ }
325
+ lastExecutedStageName = stage.name;
326
+ execStageCount++;
327
+
328
+ _pipelineLogger.info(
329
+ `[PipelineStrategy] ▶ Stage "${stage.name}"${isRetry ? ' (retry)' : ''} — ` +
330
+ `budget: ${effectiveBudget?.maxIterations || '∞'} iters, ` +
331
+ `timeout: ${effectiveBudget?.timeoutMs ? (effectiveBudget.timeoutMs / 1000) + 's' : '∞'}, ` +
332
+ `tracker: ${stageTracker?.constructor?.name || 'none'}` +
333
+ `${submitToolName ? `, submitTool: ${submitToolName}` : ''}`,
334
+ );
335
+
336
+ // ── 执行 reactLoop (含 per-stage 硬超时保护) ──
337
+ const reactPromise = runtime.reactLoop(stagePrompt, {
338
+ history: message.history,
339
+ context: { ...message.metadata.context, pipelinePhase: stage.name, previousPhases: phaseResults },
340
+ capabilityOverride: stage.capabilities,
341
+ budgetOverride: effectiveBudget,
342
+ systemPromptOverride: stage.systemPrompt, // v3: per-phase 系统提示词
343
+ onToolCall: stage.onToolCall, // v3: per-phase 工具调用钩子
344
+ // ── 引擎增强参数: 从 strategyContext 透传 (tracker 使用阶段级实例) ──
345
+ contextWindow: ctxWin,
346
+ tracker: stageTracker,
347
+ trace: strategyContext.trace || null,
348
+ memoryCoordinator: strategyContext.memoryCoordinator || null,
349
+ sharedState: strategyContext.sharedState || null,
350
+ source: strategyContext.source || null,
351
+ });
352
+
353
+ // ── Per-stage hard timeout (安全网) ──
354
+ // 协作超时由 AgentRuntime.#shouldExit() 中的 budget.timeoutMs 检查处理 (优雅退出)
355
+ // 此处的硬超时 = budget.timeoutMs + 30s 缓冲,防止单次 LLM/Tool 调用阻塞过久
356
+ const stageTimeoutMs = effectiveBudget?.timeoutMs;
357
+ let stageResult;
358
+ if (stageTimeoutMs) {
359
+ const hardLimitMs = stageTimeoutMs + 30_000;
360
+ let hardTimer;
361
+ stageResult = await Promise.race([
362
+ reactPromise,
363
+ new Promise((_, reject) => {
364
+ hardTimer = setTimeout(
365
+ () => reject(new Error('__STAGE_HARD_TIMEOUT__')),
366
+ hardLimitMs,
367
+ );
368
+ }),
369
+ ]).catch(err => {
370
+ if (err.message === '__STAGE_HARD_TIMEOUT__') {
371
+ runtime.logger?.info?.(
372
+ `[PipelineStrategy] ⏰ Stage "${stage.name}" hard timeout (${hardLimitMs}ms) — continuing pipeline`,
373
+ );
374
+ bus.publish(AgentEvents.PROGRESS, {
375
+ type: 'pipeline_stage_timeout',
376
+ stage: stage.name,
377
+ timeoutMs: hardLimitMs,
378
+ });
379
+ return {
380
+ reply: '',
381
+ toolCalls: [],
382
+ iterations: 0,
383
+ tokenUsage: { input: 0, output: 0 },
384
+ timedOut: true,
385
+ };
386
+ }
387
+ throw err;
388
+ }).finally(() => clearTimeout(hardTimer));
389
+ } else {
390
+ stageResult = await reactPromise;
391
+ }
392
+
393
+ phaseResults[stage.name] = stageResult;
394
+ totalToolCalls.push(...(stageResult.toolCalls || []));
395
+ totalIterations += stageResult.iterations || 0;
396
+ if (stageResult.tokenUsage) {
397
+ totalTokenUsage.input += stageResult.tokenUsage.input || 0;
398
+ totalTokenUsage.output += stageResult.tokenUsage.output || 0;
399
+ }
400
+
401
+ const stageToolCount = stageResult.toolCalls?.length || 0;
402
+ const stageReplyLen = stageResult.reply?.length || 0;
403
+ _pipelineLogger.info(
404
+ `[PipelineStrategy] ✅ Stage "${stage.name}" done — ` +
405
+ `${stageResult.iterations || 0} iters, ${stageToolCount} tool calls, ` +
406
+ `reply: ${stageReplyLen} chars${stageResult.timedOut ? ' (TIMED OUT)' : ''}`,
407
+ );
408
+
409
+ bus.publish(AgentEvents.PROGRESS, {
410
+ type: 'pipeline_stage_done',
411
+ stage: stage.name,
412
+ iterations: stageResult.iterations,
413
+ });
414
+ }
415
+
416
+ // 最终回复 = 最后一个执行阶段的输出
417
+ const lastStage = Object.values(phaseResults).filter(r => r.reply).pop();
418
+
419
+ return {
420
+ reply: lastStage?.reply || '',
421
+ toolCalls: totalToolCalls,
422
+ tokenUsage: totalTokenUsage,
423
+ iterations: totalIterations,
424
+ phases: phaseResults,
425
+ degraded,
426
+ };
427
+ }
428
+
429
+ /**
430
+ * 质量门控评估 (向后兼容: 阈值模式)
431
+ */
432
+ #evaluateGate(gateConfig, phaseResults, sourceName) {
433
+ const source = phaseResults[sourceName];
434
+ if (!source?.reply) {
435
+ return { pass: false, reason: `No output from stage "${sourceName}"` };
436
+ }
437
+
438
+ const reply = source.reply;
439
+ const reasons = [];
440
+
441
+ if (gateConfig.minEvidenceLength && reply.length < gateConfig.minEvidenceLength) {
442
+ reasons.push(`分析长度不足: ${reply.length} < ${gateConfig.minEvidenceLength}`);
443
+ }
444
+
445
+ if (gateConfig.minFileRefs) {
446
+ const fileRefCount = (reply.match(/[\w/]+\.\w+/g) || []).length;
447
+ if (fileRefCount < gateConfig.minFileRefs) {
448
+ reasons.push(`文件引用不足: ${fileRefCount} < ${gateConfig.minFileRefs}`);
449
+ }
450
+ }
451
+
452
+ if (gateConfig.minToolCalls) {
453
+ const toolCalls = source.toolCalls?.length || 0;
454
+ if (toolCalls < gateConfig.minToolCalls) {
455
+ reasons.push(`工具调用不足: ${toolCalls} < ${gateConfig.minToolCalls}`);
456
+ }
457
+ }
458
+
459
+ if (gateConfig.custom && typeof gateConfig.custom === 'function') {
460
+ const customResult = gateConfig.custom(source);
461
+ if (!customResult.pass) reasons.push(customResult.reason);
462
+ }
463
+
464
+ return reasons.length === 0
465
+ ? { pass: true }
466
+ : { pass: false, reason: reasons.join('; ') };
467
+ }
468
+
469
+ /**
470
+ * 找到当前 gate 之前最近的执行阶段索引 (用于 retry 回退)
471
+ */
472
+ #findPrevExecStageIdx(currentIdx) {
473
+ for (let j = currentIdx - 1; j >= 0; j--) {
474
+ if (!this.#stages[j].gate) return j;
475
+ }
476
+ return -1;
477
+ }
478
+
479
+ #prevStageName(currentStage) {
480
+ const idx = this.#stages.indexOf(currentStage);
481
+ for (let i = idx - 1; i >= 0; i--) {
482
+ if (!this.#stages[i].gate && this.#stages[i].name) {
483
+ return this.#stages[i].name;
484
+ }
485
+ }
486
+ return null;
487
+ }
488
+ }
489
+
490
+ // ─── FanOutStrategy — 并行执行 ──────────────
491
+
492
+ /**
493
+ * 并行执行多个子任务,每个子任务使用 itemStrategy (通常是 Pipeline)。
494
+ * 支持分层并发控制 (Tier)。
495
+ *
496
+ * 适合: 冷启动多维度、批量分析
497
+ *
498
+ * 等价于 Anthropic 的 "Parallelization" + "Orchestrator-Worker" 组合。
499
+ *
500
+ * @example
501
+ * new FanOutStrategy({
502
+ * itemStrategy: new PipelineStrategy({
503
+ * stages: [
504
+ * { name: 'analyze', capabilities: ['code_analysis'], budget: { maxIterations: 24 } },
505
+ * { name: 'gate', gate: { minEvidenceLength: 500, minFileRefs: 3 } },
506
+ * { name: 'produce', capabilities: ['knowledge_production'], budget: { maxIterations: 24 },
507
+ * promptTransform: (_, prev) => `将以下分析转为知识候选:\n${prev.analyze.reply}` },
508
+ * ],
509
+ * }),
510
+ * tiers: { 1: { concurrency: 3 }, 2: { concurrency: 2 }, 3: { concurrency: 1 } },
511
+ * })
512
+ */
513
+ export class FanOutStrategy extends Strategy {
514
+ /** @type {Strategy} 每个子任务的执行策略 */
515
+ #itemStrategy;
516
+ /** @type {Object} 分层并发配置 */
517
+ #tiers;
518
+ /** @type {Function} 结果合并函数 */
519
+ #merge;
520
+
521
+ /**
522
+ * @param {Object} opts
523
+ * @param {Strategy} opts.itemStrategy — 每个子任务使用的策略
524
+ * @param {Object} [opts.tiers] — { 1: { concurrency: 3 }, 2: { concurrency: 2 }, ... }
525
+ * @param {Function} [opts.merge] — 自定义合并函数 (results[]) => finalResult
526
+ */
527
+ constructor({ itemStrategy, tiers, merge } = {}) {
528
+ super();
529
+ this.#itemStrategy = itemStrategy || new SingleStrategy();
530
+ this.#tiers = tiers || { 1: { concurrency: 3 } };
531
+ this.#merge = merge || FanOutStrategy.#defaultMerge;
532
+ }
533
+
534
+ get name() { return 'fan_out'; }
535
+
536
+ /**
537
+ * @param {Object} runtime
538
+ * @param {import('./AgentMessage.js').AgentMessage} message
539
+ * @param {Object} opts
540
+ * @param {Array<{id: string, label: string, tier?: number, prompt?: string, guide?: string}>} opts.items — 子任务列表
541
+ */
542
+ async execute(runtime, message, opts = {}) {
543
+ const { items = [] } = opts;
544
+ const bus = AgentEventBus.getInstance();
545
+
546
+ if (items.length === 0) {
547
+ return { reply: 'No items to process', toolCalls: [], tokenUsage: { input: 0, output: 0 }, iterations: 0 };
548
+ }
549
+
550
+ // 按 tier 分组
551
+ const tierGroups = this.#groupByTier(items);
552
+ const allResults = [];
553
+
554
+ for (const [tier, tierItems] of Object.entries(tierGroups).sort(([a], [b]) => a - b)) {
555
+ const tierConfig = this.#tiers[tier] || this.#tiers[1] || { concurrency: 2 };
556
+
557
+ bus.publish(AgentEvents.PROGRESS, {
558
+ type: 'fan_out_tier_start',
559
+ tier: Number(tier),
560
+ count: tierItems.length,
561
+ concurrency: tierConfig.concurrency,
562
+ });
563
+
564
+ // 按并发度分批执行
565
+ const chunks = this.#chunk(tierItems, tierConfig.concurrency);
566
+ for (const chunk of chunks) {
567
+ const chunkPromises = chunk.map(async (item) => {
568
+ const itemMessage = AgentMessage.internal(
569
+ item.prompt || `${message.content}\n\n## 当前维度: ${item.label}\n${item.guide || ''}`,
570
+ {
571
+ sessionId: message.session.id,
572
+ dimension: item.id,
573
+ parentAgentId: runtime.id,
574
+ history: message.history,
575
+ metadata: { ...message.metadata, dimension: item },
576
+ }
577
+ );
578
+
579
+ bus.publish(AgentEvents.PROGRESS, {
580
+ type: 'fan_out_item_start',
581
+ itemId: item.id,
582
+ label: item.label,
583
+ });
584
+
585
+ try {
586
+ const result = await this.#itemStrategy.execute(runtime, itemMessage, {
587
+ dimension: item,
588
+ });
589
+ return { id: item.id, label: item.label, status: 'completed', ...result };
590
+ } catch (err) {
591
+ return { id: item.id, label: item.label, status: 'failed', error: err.message, reply: '', toolCalls: [], tokenUsage: { input: 0, output: 0 } };
592
+ }
593
+ });
594
+
595
+ const chunkResults = await Promise.all(chunkPromises);
596
+ allResults.push(...chunkResults);
597
+ }
598
+
599
+ bus.publish(AgentEvents.PROGRESS, {
600
+ type: 'fan_out_tier_done',
601
+ tier: Number(tier),
602
+ completed: allResults.filter(r => r.status === 'completed').length,
603
+ failed: allResults.filter(r => r.status === 'failed').length,
604
+ });
605
+ }
606
+
607
+ return this.#merge(allResults);
608
+ }
609
+
610
+ #groupByTier(items) {
611
+ const groups = {};
612
+ for (const item of items) {
613
+ const tier = item.tier || 1;
614
+ if (!groups[tier]) groups[tier] = [];
615
+ groups[tier].push(item);
616
+ }
617
+ return groups;
618
+ }
619
+
620
+ #chunk(arr, size) {
621
+ const chunks = [];
622
+ for (let i = 0; i < arr.length; i += size) {
623
+ chunks.push(arr.slice(i, i + size));
624
+ }
625
+ return chunks;
626
+ }
627
+
628
+ static #defaultMerge(results) {
629
+ const successful = results.filter(r => r.status === 'completed');
630
+ const failed = results.filter(r => r.status === 'failed');
631
+ return {
632
+ reply: [
633
+ `## 执行总结\n完成: ${successful.length}, 失败: ${failed.length}\n`,
634
+ ...successful.map(r => `### ${r.label}\n${r.reply || '(无输出)'}`),
635
+ ...failed.map(r => `### ${r.label} ❌\n${r.error}`),
636
+ ].join('\n\n'),
637
+ toolCalls: results.flatMap(r => r.toolCalls || []),
638
+ tokenUsage: {
639
+ input: results.reduce((sum, r) => sum + (r.tokenUsage?.input || 0), 0),
640
+ output: results.reduce((sum, r) => sum + (r.tokenUsage?.output || 0), 0),
641
+ },
642
+ iterations: results.reduce((sum, r) => sum + (r.iterations || 0), 0),
643
+ itemResults: results,
644
+ };
645
+ }
646
+ }
647
+
648
+ // ─── AdaptiveStrategy — 智能自适应 ──────────
649
+
650
+ /**
651
+ * 根据输入复杂度自动选择合适的策略。
652
+ *
653
+ * 判断逻辑:
654
+ * - 简单问答 → SingleStrategy
655
+ * - 单模块深度分析 → PipelineStrategy
656
+ * - 多维度/全项目 → FanOutStrategy
657
+ *
658
+ * 等价于 LangGraph 的 Router 节点 + 条件边。
659
+ *
660
+ * @example
661
+ * new AdaptiveStrategy({
662
+ * single: new SingleStrategy(),
663
+ * pipeline: new PipelineStrategy({ stages: [...] }),
664
+ * fanOut: new FanOutStrategy({ itemStrategy: ..., tiers: ... }),
665
+ * })
666
+ */
667
+ export class AdaptiveStrategy extends Strategy {
668
+ #strategies;
669
+
670
+ /**
671
+ * @param {Object} [strategies]
672
+ * @param {Strategy} [strategies.single]
673
+ * @param {Strategy} [strategies.pipeline]
674
+ * @param {Strategy} [strategies.fanOut]
675
+ */
676
+ constructor(strategies = {}) {
677
+ super();
678
+ this.#strategies = {
679
+ single: strategies.single || new SingleStrategy(),
680
+ pipeline: strategies.pipeline || null,
681
+ fanOut: strategies.fanOut || null,
682
+ };
683
+ }
684
+
685
+ get name() { return 'adaptive'; }
686
+
687
+ async execute(runtime, message, opts = {}) {
688
+ const complexity = this.#assessComplexity(message, opts);
689
+ const bus = AgentEventBus.getInstance();
690
+
691
+ bus.publish(AgentEvents.PROGRESS, {
692
+ type: 'adaptive_classification',
693
+ complexity,
694
+ selectedStrategy: complexity,
695
+ });
696
+
697
+ switch (complexity) {
698
+ case 'fan_out':
699
+ if (this.#strategies.fanOut) {
700
+ return this.#strategies.fanOut.execute(runtime, message, opts);
701
+ }
702
+ // fallthrough
703
+ case 'pipeline':
704
+ if (this.#strategies.pipeline) {
705
+ return this.#strategies.pipeline.execute(runtime, message, opts);
706
+ }
707
+ // fallthrough
708
+ default:
709
+ return this.#strategies.single.execute(runtime, message, opts);
710
+ }
711
+ }
712
+
713
+ /**
714
+ * 复杂度评估
715
+ */
716
+ #assessComplexity(message, opts) {
717
+ const text = message.content.toLowerCase();
718
+
719
+ // 有显式 items → fan_out
720
+ if (opts.items?.length > 1) return 'fan_out';
721
+
722
+ // 关键词启发
723
+ if (/冷启动|cold[\s-]?start|bootstrap|全项目|所有.*维度|all.*dimensions/i.test(text)) {
724
+ return 'fan_out';
725
+ }
726
+
727
+ if (/深度.*分析|扫描|审计|scan|deep.*analy|audit|知识提取|extract/i.test(text)) {
728
+ return 'pipeline';
729
+ }
730
+
731
+ return 'single';
732
+ }
733
+ }
734
+
735
+ // ─── Strategy 注册表 ─────────────────────────
736
+
737
+ export const StrategyRegistry = {
738
+ _registry: new Map([
739
+ ['single', SingleStrategy],
740
+ ['pipeline', PipelineStrategy],
741
+ ['fan_out', FanOutStrategy],
742
+ ['adaptive', AdaptiveStrategy],
743
+ ]),
744
+
745
+ create(name, opts = {}) {
746
+ const Cls = this._registry.get(name);
747
+ if (!Cls) throw new Error(`Unknown strategy: ${name}`);
748
+ return new Cls(opts);
749
+ },
750
+
751
+ register(name, cls) {
752
+ this._registry.set(name, cls);
753
+ },
754
+ };
755
+
756
+ export default { Strategy, SingleStrategy, PipelineStrategy, FanOutStrategy, AdaptiveStrategy, StrategyRegistry };