autosnippet 3.2.7 → 3.2.9

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