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.
- package/bin/cli.js +13 -5
- package/dashboard/dist/assets/index-BTAsOZv2.js +128 -0
- package/dashboard/dist/assets/index-C_72Ct98.css +1 -0
- package/dashboard/dist/index.html +2 -2
- package/lib/cli/AiScanService.js +26 -29
- package/lib/cli/SetupService.js +1 -1
- package/lib/core/AstAnalyzer.js +27 -5
- package/lib/core/analysis/CallEdgeResolver.js +402 -0
- package/lib/core/analysis/CallGraphAnalyzer.js +367 -0
- package/lib/core/analysis/CallSiteExtractor.js +629 -0
- package/lib/core/analysis/DataFlowInferrer.js +57 -0
- package/lib/core/analysis/ImportPathResolver.js +189 -0
- package/lib/core/analysis/ImportRecord.js +105 -0
- package/lib/core/analysis/SymbolTableBuilder.js +211 -0
- package/lib/core/ast/ProjectGraph.js +8 -0
- package/lib/core/ast/lang-dart.js +352 -5
- package/lib/core/ast/lang-go.js +212 -10
- package/lib/core/ast/lang-java.js +205 -1
- package/lib/core/ast/lang-kotlin.js +330 -1
- package/lib/core/ast/lang-python.js +31 -2
- package/lib/core/ast/lang-rust.js +284 -3
- package/lib/core/ast/lang-swift.js +180 -1
- package/lib/core/ast/lang-typescript.js +290 -1
- package/lib/core/discovery/index.js +2 -2
- package/lib/external/ai/AiProvider.js +66 -172
- package/lib/external/ai/providers/GoogleGeminiProvider.js +23 -1
- package/lib/external/mcp/McpServer.js +1 -0
- package/lib/external/mcp/handlers/bootstrap/BootstrapSession.js +1 -1
- package/lib/external/mcp/handlers/bootstrap/ExternalSubmissionTracker.js +3 -3
- package/lib/external/mcp/handlers/bootstrap/MissionBriefingBuilder.js +22 -1
- package/lib/external/mcp/handlers/bootstrap/pipeline/IncrementalBootstrap.js +1 -1
- package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-configs.js +2 -1
- package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-context.js +8 -8
- package/lib/external/mcp/handlers/bootstrap/pipeline/noAiFallback.js +1 -1
- package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +311 -162
- package/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.js +102 -7
- package/lib/external/mcp/handlers/bootstrap/shared/dimension-sop.js +1 -1
- package/lib/external/mcp/handlers/bootstrap-external.js +9 -2
- package/lib/external/mcp/handlers/bootstrap-internal.js +19 -8
- package/lib/external/mcp/handlers/consolidated.js +9 -0
- package/lib/external/mcp/handlers/dimension-complete-external.js +6 -6
- package/lib/external/mcp/handlers/guard.js +3 -3
- package/lib/external/mcp/handlers/structure.js +62 -0
- package/lib/external/mcp/handlers/wiki-external.js +66 -3
- package/lib/external/mcp/tools.js +36 -1
- package/lib/http/HttpServer.js +1 -1
- package/lib/http/middleware/requestLogger.js +1 -0
- package/lib/http/routes/ai.js +240 -35
- package/lib/http/routes/candidates.js +2 -3
- package/lib/http/routes/extract.js +13 -11
- package/lib/http/routes/modules.js +2 -2
- package/lib/http/routes/recipes.js +9 -5
- package/lib/http/routes/remote.js +149 -270
- package/lib/http/routes/violations.js +0 -54
- package/lib/http/utils/sse-sessions.js +1 -1
- package/lib/infrastructure/logging/Logger.js +5 -4
- package/lib/infrastructure/monitoring/PerformanceMonitor.js +3 -2
- package/lib/injection/ServiceContainer.js +70 -28
- package/lib/platform/ScreenCaptureService.js +177 -0
- package/lib/platform/ios/index.js +2 -2
- package/lib/platform/ios/routes/spm.js +2 -2
- package/lib/platform/ios/spm/PackageSwiftParser.js +14 -3
- package/lib/platform/ios/spm/SpmDiscoverer.js +123 -17
- package/lib/platform/ios/spm/{SpmService.js → SpmHelper.js} +43 -675
- package/lib/platform/ios/xcode/XcodeWriteUtils.js +1 -1
- package/lib/service/agent/AgentEventBus.js +207 -0
- package/lib/service/agent/AgentFactory.js +490 -0
- package/lib/service/agent/AgentMessage.js +240 -0
- package/lib/service/agent/AgentRouter.js +228 -0
- package/lib/service/agent/AgentRuntime.js +1016 -0
- package/lib/service/agent/AgentState.js +217 -0
- package/lib/service/agent/IntentClassifier.js +331 -0
- package/lib/service/agent/LarkTransport.js +389 -0
- package/lib/service/agent/capabilities.js +408 -0
- package/lib/service/{chat → agent/context}/ContextWindow.js +37 -12
- package/lib/service/{chat → agent/context}/ExplorationTracker.js +77 -22
- package/lib/service/{chat → agent/core}/ChatAgentPrompts.js +14 -2
- package/lib/service/agent/core/LoopContext.js +170 -0
- package/lib/service/agent/core/MessageAdapter.js +223 -0
- package/lib/service/agent/core/ToolExecutionPipeline.js +376 -0
- package/lib/service/{chat → agent/domain}/ChatAgentTasks.js +19 -98
- package/lib/service/{chat → agent/domain}/EpisodicConsolidator.js +7 -7
- package/lib/service/{chat → agent/domain}/EvidenceCollector.js +4 -2
- package/lib/service/{chat/AnalystAgent.js → agent/domain/insight-analyst.js} +37 -172
- package/lib/service/{chat/HandoffProtocol.js → agent/domain/insight-gate.js} +91 -123
- package/lib/service/agent/domain/insight-producer.js +267 -0
- package/lib/service/agent/domain/scan-prompts.js +105 -0
- package/lib/service/agent/forced-summary.js +266 -0
- package/lib/service/agent/index.js +91 -0
- package/lib/service/{chat → agent}/memory/ActiveContext.js +3 -1
- package/lib/service/{chat → agent}/memory/MemoryCoordinator.js +7 -7
- package/lib/service/{chat/ProjectSemanticMemory.js → agent/memory/PersistentMemory.js} +359 -89
- package/lib/service/{chat → agent}/memory/SessionStore.js +5 -4
- package/lib/service/{chat → agent}/memory/index.js +1 -1
- package/lib/service/agent/policies.js +442 -0
- package/lib/service/agent/presets.js +303 -0
- package/lib/service/agent/strategies.js +717 -0
- package/lib/service/{chat → agent/tools}/ToolRegistry.js +3 -3
- package/lib/service/agent/tools/ai-analysis.js +75 -0
- package/lib/service/{chat → agent}/tools/ast-graph.js +229 -32
- package/lib/service/{chat → agent}/tools/composite.js +2 -1
- package/lib/service/{chat → agent}/tools/guard.js +1 -121
- package/lib/service/{chat → agent}/tools/index.js +33 -22
- package/lib/service/{chat → agent}/tools/infrastructure.js +6 -1
- package/lib/service/agent/tools/knowledge-graph.js +112 -0
- package/lib/service/agent/tools/scan-recipe.js +189 -0
- package/lib/service/agent/tools/system-interaction.js +476 -0
- package/lib/service/automation/DirectiveDetector.js +0 -1
- package/lib/service/automation/FileWatcher.js +0 -8
- package/lib/service/automation/handlers/CreateHandler.js +7 -3
- package/lib/service/automation/handlers/DraftHandler.js +7 -6
- package/lib/service/cursor/CursorDeliveryPipeline.js +167 -1
- package/lib/service/knowledge/CodeEntityGraph.js +327 -2
- package/lib/service/knowledge/KnowledgeService.js +5 -1
- package/lib/service/module/ModuleService.js +49 -73
- package/lib/service/skills/SignalCollector.js +26 -19
- package/lib/service/snippet/codecs/VSCodeCodec.js +1 -1
- package/lib/service/wiki/WikiGenerator.js +1 -1
- package/lib/shared/FieldSpec.js +1 -1
- package/lib/shared/PathGuard.js +1 -1
- package/lib/shared/StyleGuide.js +1 -1
- package/package.json +4 -1
- package/resources/native-ui/screenshot.swift +228 -0
- package/dashboard/dist/assets/index-BaGY7kJI.css +0 -1
- package/dashboard/dist/assets/index-DfHY_3ln.js +0 -128
- package/lib/core/discovery/SpmDiscoverer.js +0 -5
- package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +0 -749
- package/lib/external/mcp/handlers/bootstrap/pipeline/ToolResultCache.js +0 -277
- package/lib/http/routes/spm.js +0 -5
- package/lib/infrastructure/external/XcodeAutomation.js +0 -15
- package/lib/service/chat/ChatAgent.js +0 -1602
- package/lib/service/chat/Memory.js +0 -161
- package/lib/service/chat/ProducerAgent.js +0 -431
- package/lib/service/chat/ReasoningTrace.js +0 -523
- package/lib/service/chat/TaskPipeline.js +0 -357
- package/lib/service/chat/WorkingMemory.js +0 -357
- package/lib/service/chat/memory/PersistentMemory.js +0 -450
- package/lib/service/chat/tools/ai-analysis.js +0 -267
- package/lib/service/chat/tools/knowledge-graph.js +0 -234
- package/lib/service/chat/tools.js +0 -18
- package/lib/service/snippet/PlaceholderConverter.js +0 -5
- package/lib/service/snippet/codecs/XcodeCodec.js +0 -5
- /package/lib/service/{chat → agent}/ConversationStore.js +0 -0
- /package/lib/service/{chat → agent}/tools/_shared.js +0 -0
- /package/lib/service/{chat → agent}/tools/lifecycle.js +0 -0
- /package/lib/service/{chat → agent}/tools/project-access.js +0 -0
- /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 };
|